All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.antmedia.streamsource.StreamFetcherManager Maven / Gradle / Ivy

Go to download

Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.

There is a newer version: 2.10.0
Show newest version
package io.antmedia.streamsource;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.annotation.Nonnull;

import org.apache.tika.utils.ExceptionUtils;
import org.red5.server.api.scope.IScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.antmedia.AntMediaApplicationAdapter;
import io.antmedia.datastore.db.DataStore;
import io.antmedia.datastore.db.types.Broadcast;
import io.antmedia.datastore.db.types.Playlist;
import io.antmedia.muxer.MuxAdaptor;
import io.antmedia.rest.model.Result;
import io.vertx.core.Vertx;


/**
 * Organizes and checks stream fetcher and restarts them if it is required
 * @author davut
 *
 */
public class StreamFetcherManager {

	protected static Logger logger = LoggerFactory.getLogger(StreamFetcherManager.class);

	private int streamCheckerCount = 0;

	private Queue streamFetcherList = new ConcurrentLinkedQueue<>();

	/**
	 * Time period in milli seconds for checking stream fetchers status, restart issues etc. 
	 */
	private int streamCheckerIntervalMs = 10000;

	private DataStore datastore;

	private IScope scope;

	private long streamFetcherScheduleJobName = -1L;

	protected AtomicBoolean isJobRunning = new AtomicBoolean(false);

	private boolean restartStreamAutomatically = true;

	/**
	 * Time period in seconds for restarting stream fetchers
	 */
	private int restartStreamFetcherPeriodSeconds;

	private Vertx vertx;

	public StreamFetcherManager(Vertx vertx, DataStore datastore,IScope scope) {
		this.vertx = vertx;
		this.datastore = datastore;
		this.scope=scope;
	}

	public StreamFetcher make(Broadcast stream, IScope scope, Vertx vertx) {
		return new StreamFetcher(stream, scope, vertx);
	}

	public int getStreamCheckerInterval() {
		return streamCheckerIntervalMs;
	}


	/**
	 * Set stream checker interval, this value is used in periodically checking 
	 * the status of the stream fetchers
	 * 
	 * @param streamCheckerInterval, time period of the stream fetcher check interval in milliseconds
	 */
	public void setStreamCheckerInterval(int streamCheckerInterval) {
		this.streamCheckerIntervalMs = streamCheckerInterval;
	}

	/**
	 * Set stream fetcher restart period, this value is used in periodically stopping and starting
	 * stream fetchers. If this value is zero it will not restart stream fetchers
	 * 
	 * @param restartStreamFetcherPeriod, time period of the stream fetcher restart period in seconds
	 */
	public void setRestartStreamFetcherPeriod(int restartStreamFetcherPeriod) {
		this.restartStreamFetcherPeriodSeconds = restartStreamFetcherPeriod;	
	}
	
	public boolean checkAlreadyFetch(Broadcast broadcast) {
		
		boolean alreadyFetching = false;
		
		for (StreamFetcher streamFetcher : streamFetcherList) {
			if (streamFetcher.getStream().getStreamId().equals(broadcast.getStreamId())) {
				alreadyFetching = true;
				break;
			}
		}
		
		return alreadyFetching;
		
	}
	
	public void alreadyFetchProcess(StreamFetcher streamScheduler) {
		
		streamScheduler.startStream();

		if(!streamFetcherList.contains(streamScheduler)) {
			streamFetcherList.add(streamScheduler);
		}

		if (streamFetcherScheduleJobName == -1) {
			scheduleStreamFetcherJob();
		}
		
	}


	public StreamFetcher startStreaming(@Nonnull Broadcast broadcast) {	

		//check if broadcast is already being fetching
		boolean alreadyFetching;
		
		alreadyFetching = checkAlreadyFetch(broadcast);

		StreamFetcher streamScheduler = null;
		
		if (!alreadyFetching) {

			try {
				streamScheduler =  make(broadcast, scope, vertx);
				streamScheduler.setRestartStream(restartStreamAutomatically);
				
				alreadyFetchProcess(streamScheduler);
			}
			catch (Exception e) {
				streamScheduler = null;
				logger.error(e.getMessage());
			}
		}

		return streamScheduler;
	}


	public StreamFetcher playlistStartStreaming(@Nonnull Broadcast broadcast,StreamFetcher streamScheduler) {	

		//check if broadcast is already being fetching
		boolean alreadyFetching = false;
		
		alreadyFetching = checkAlreadyFetch(broadcast);

		if (!alreadyFetching) 
		{
			try {
				streamScheduler.setRestartStream(false);
				alreadyFetchProcess(streamScheduler);
			}
			catch (Exception e) {
				streamScheduler = null;
				logger.error(ExceptionUtils.getStackTrace(e));
			}
		}

		return streamScheduler;
	}

	public Result stopStreaming(Broadcast stream) {
		logger.warn("inside of stopStreaming for {}", stream.getStreamId());
		Result result = new Result(false);

		for (StreamFetcher scheduler : streamFetcherList) {
			if (scheduler.getStream().getStreamId().equals(stream.getStreamId())) {
				scheduler.stopStream();
				streamFetcherList.remove(scheduler);
				result.setSuccess(true);
				break;
			}
		}
		return result;
	}

	public void stopCheckerJob() {
		if (streamFetcherScheduleJobName != -1) {
			vertx.cancelTimer(streamFetcherScheduleJobName);
			streamFetcherScheduleJobName = -1;
		}
	}

	public static Result checkStreamUrlWithHTTP(String url){

		Result result = new Result(false);

		URL checkUrl;
		HttpURLConnection huc;
		int responseCode;

		try {
			checkUrl = new URL(url);
			huc = (HttpURLConnection) checkUrl.openConnection();
			responseCode = huc.getResponseCode();

			if(responseCode >= HttpURLConnection.HTTP_OK && responseCode < HttpURLConnection.HTTP_MOVED_PERM ) {
				result.setSuccess(true);
				return result;
			}
			else {
				result.setSuccess(false);
				return result;
			}

		} catch (IOException e) {
			result.setSuccess(false);
		}
		return result;		
	}


	public void startPlaylistThread(Playlist playlist){

		// Get current stream in Playlist
		Broadcast playlistBroadcastItem = playlist.getBroadcastItemList().get(playlist.getCurrentPlayIndex());

		// Check Stream URL is valid.
		// If stream URL is not valid, it's trying next broadcast and trying.
		if(checkStreamUrlWithHTTP(playlistBroadcastItem.getStreamUrl()).isSuccess()) 
		{

			// Create Stream Fetcher with Playlist Broadcast Item
			StreamFetcher streamScheduler = new StreamFetcher(playlistBroadcastItem, scope, vertx);
			// Update Playlist current playing status
			playlist.setPlaylistStatus(AntMediaApplicationAdapter.BROADCAST_STATUS_BROADCASTING);
			// Update broadcast current playing status
			playlist.getBroadcastItemList().get(playlist.getCurrentPlayIndex()).setStatus(AntMediaApplicationAdapter.BROADCAST_STATUS_BROADCASTING); 
			// Update Datastore current play broadcast
			datastore.editPlaylist(playlist.getPlaylistId(), playlist);

			streamScheduler.setStreamFetcherListener(listener -> {

				// It's necessary for skip new Stream Fetcher
				stopStreaming(playlistBroadcastItem);
				// Get current playlist in database
				Playlist newPlaylist = datastore.getPlaylist(playlistBroadcastItem.getStreamId());
				//Check playlist is not deleted and not stopped
				if(newPlaylist.getPlaylistStatus().equals(AntMediaApplicationAdapter.BROADCAST_STATUS_BROADCASTING) && newPlaylist.getPlaylistId() != null)
				{
					newPlaylist = skipNextPlaylistQueue(newPlaylist);
					// Get Current Playlist Stream Index
					int currentStreamIndex = newPlaylist.getCurrentPlayIndex();
					// Check Stream URL is valid.
					// If stream URL is not valid, it's trying next broadcast and trying.
					if(checkStreamUrlWithHTTP(newPlaylist.getBroadcastItemList().get(currentStreamIndex).getStreamUrl()).isSuccess()) 
					{
						//update broadcast informations
						Broadcast fetchedBroadcast = newPlaylist.getBroadcastItemList().get(currentStreamIndex);
						Result result = new Result(false);
						result.setSuccess(datastore.updateBroadcastFields(fetchedBroadcast.getStreamId(), fetchedBroadcast));
						StreamFetcher newStreamScheduler = new StreamFetcher(fetchedBroadcast,scope,vertx);
						newStreamScheduler.setStreamFetcherListener(listener);
						playlistStartStreaming(playlistBroadcastItem,newStreamScheduler);
					}
					else 
					{
						logger.info("Current Playlist Stream URL -> {} is invalid", newPlaylist.getBroadcastItemList().get(currentStreamIndex).getStreamUrl());
						newPlaylist = skipNextPlaylistQueue(newPlaylist);
						startPlaylistThread(newPlaylist);
					}
				}
			});

			playlistStartStreaming(playlistBroadcastItem,streamScheduler);

		}
		else {

			logger.warn("Current Playlist Stream URL -> {} is invalid", playlistBroadcastItem.getStreamUrl());
			
			// This method skip next playlist item
			playlist = skipNextPlaylistQueue(playlist);

			if(checkStreamUrlWithHTTP(playlist.getBroadcastItemList().get(playlist.getCurrentPlayIndex()).getStreamUrl()).isSuccess()) {
				startPlaylistThread(playlist);
			}
			else {
				playlist.setPlaylistStatus(AntMediaApplicationAdapter.BROADCAST_STATUS_FINISHED);
				// Update Datastore current play broadcast
				datastore.editPlaylist(playlist.getPlaylistId(), playlist);
			}

		}
	}

	public Playlist skipNextPlaylistQueue(Playlist playlist) {

		// Get Current Playlist Stream Index
		int currentStreamIndex = playlist.getCurrentPlayIndex()+1;

		if(playlist.getBroadcastItemList().size() <= currentStreamIndex) 
		{
			//update playlist first broadcast
			playlist.setCurrentPlayIndex(0);
			datastore.editPlaylist(playlist.getPlaylistId(), playlist);
		}

		else {
			// update playlist currentPlayIndex value.
			playlist.setCurrentPlayIndex(currentStreamIndex);
			datastore.editPlaylist(playlist.getPlaylistId(), playlist);
		}

		return playlist;
	}


	public void startStreams(List streams) {

		for (int i = 0; i < streams.size(); i++) {
			startStreaming(streams.get(i));
		}

		scheduleStreamFetcherJob();
	}

	private void scheduleStreamFetcherJob() {
		if (streamFetcherScheduleJobName != -1) {
			vertx.cancelTimer(streamFetcherScheduleJobName);
		}

		streamFetcherScheduleJobName = vertx.setPeriodic(streamCheckerIntervalMs, l-> {

			int lastRestartCount = 0;

			if (!streamFetcherList.isEmpty()) {

				streamCheckerCount++;

				logger.debug("StreamFetcher Check Count:{}" , streamCheckerCount);

				int countToRestart = 0;
				if (restartStreamFetcherPeriodSeconds > 0) 
				{
					int streamCheckIntervalSec = streamCheckerIntervalMs / 1000;
					countToRestart = (streamCheckerCount * streamCheckIntervalSec) / restartStreamFetcherPeriodSeconds;
				}


				if (countToRestart > lastRestartCount) {
					lastRestartCount = countToRestart;
					logger.info("This is {} times that restarting streams", lastRestartCount);
					restartStreamFetchers();
				} else {
					checkStreamFetchersStatus();
				}
			}

		});

		logger.info("StreamFetcherSchedule job name {}", streamFetcherScheduleJobName);
	}

	public void checkStreamFetchersStatus() {
		for (StreamFetcher streamScheduler : streamFetcherList) {
			Broadcast stream = streamScheduler.getStream();

			if (!streamScheduler.isStreamAlive() && datastore != null && stream.getStreamId() != null) 
			{
				logger.info("Stream is not alive and setting quality to poor of stream: {} url: {}", stream.getStreamId(), stream.getStreamUrl());
				datastore.updateSourceQualityParameters(stream.getStreamId(), null, 0, 0);
			}
		}
	}

	public void restartStreamFetchers() {
		for (StreamFetcher streamScheduler : streamFetcherList) {

			if (streamScheduler.isStreamAlive()) 
			{
				logger.info("Calling stop stream {}", streamScheduler.getStream().getStreamId());
				streamScheduler.stopStream();
			}
			else {
				logger.info("Stream is not alive {}", streamScheduler.getStream().getStreamId());
			}

			streamScheduler.startStream();
		}
	}

	public DataStore getDatastore() {
		return datastore;
	}

	public void setDatastore(DataStore datastore) {
		this.datastore = datastore;
	}

	public Queue getStreamFetcherList() {
		return streamFetcherList;
	}

	public void setStreamFetcherList(Queue streamFetcherList) {
		this.streamFetcherList = streamFetcherList;
	}



	public boolean isRestartStreamAutomatically() {
		return restartStreamAutomatically;
	}



	public void setRestartStreamAutomatically(boolean restartStreamAutomatically) {
		this.restartStreamAutomatically = restartStreamAutomatically;
	}



	public int getStreamCheckerCount() {
		return streamCheckerCount;
	}



	public void setStreamCheckerCount(int streamCheckerCount) {
		this.streamCheckerCount = streamCheckerCount;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy