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

io.antmedia.statistic.HlsViewerStats Maven / Gradle / Ivy

Go to download

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

There is a newer version: 2.11.3
Show newest version
package io.antmedia.statistic;

import java.util.Date;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import io.antmedia.AntMediaApplicationAdapter;
import io.antmedia.AppSettings;
import io.antmedia.datastore.db.DataStore;
import io.antmedia.datastore.db.DataStoreFactory;
import io.antmedia.datastore.db.IDataStoreFactory;
import io.antmedia.datastore.db.types.Broadcast;
import io.antmedia.datastore.db.types.ConnectionEvent;
import io.antmedia.muxer.IAntMediaStreamHandler;
import io.vertx.core.Vertx;

public class HlsViewerStats implements IStreamStats, ApplicationContextAware{

	protected static Logger logger = LoggerFactory.getLogger(HlsViewerStats.class);
	
	public static final String BEAN_NAME = "hls.viewerstats";

	private DataStore dataStore;
	
	private Vertx vertx;
	
	private DataStoreFactory dataStoreFactory;

	public static final int DEFAULT_TIME_PERIOD_FOR_VIEWER_COUNT = 10000;
	/**
	 * Time period in milliseconds to check if viewer is dropped
	 */
	private int timePeriodMS = DEFAULT_TIME_PERIOD_FOR_VIEWER_COUNT;

	Map> streamsViewerMap = new ConcurrentHashMap<>();
	Map sessionId2subscriberId = new ConcurrentHashMap<>();
	Map increaseCounterMap = new ConcurrentHashMap<>();
	
	private Object lock = new Object();

	/**
	 * Time out value in milliseconds, it is regarded as user is not watching stream 
	 * if last request time is older than timeout value
	 */
	private int timeoutMS = 20000;

	@Override
	public void registerNewViewer(String streamId, String sessionId, String subscriberId) 
	{
		//do not block the thread, run in vertx event queue 
		vertx.runOnContext(h -> {
			
			synchronized (lock) {
				//synchronize with database update calculations, because some odd cases may happen
				
				Map viewerMap = streamsViewerMap.get(streamId);
				if (viewerMap == null) {
					viewerMap = new ConcurrentHashMap<>();
				}
				if (!viewerMap.containsKey(sessionId)) 
				{
					int streamIncrementCounter = getIncreaseCounterMap(streamId);
					streamIncrementCounter++;
					increaseCounterMap.put(streamId, streamIncrementCounter);
					
				}
				viewerMap.put(sessionId, System.currentTimeMillis());
				streamsViewerMap.put(streamId, viewerMap);
				if(subscriberId != null) {
					// map sessionId to subscriberId
					sessionId2subscriberId.put(sessionId, subscriberId);
					// add a connected event to the subscriber
					ConnectionEvent event = new ConnectionEvent();
					event.setEventType(ConnectionEvent.CONNECTED_EVENT);
					Date curDate = new Date();
					event.setTimestamp(curDate.getTime());
					getDataStore().addSubscriberConnectionEvent(streamId, subscriberId, event);
				}
			}
			
		});
		
	}
	
	public int getIncreaseCounterMap(String streamId) 
	{
		Integer increaseCounter = increaseCounterMap.get(streamId);
		return increaseCounter != null ? increaseCounter : 0;
	}

	@Override
	public int getViewerCount(String streamId) {
		Map viewerMap = streamsViewerMap.get(streamId);
		int viewerCount = 0;
		if (viewerMap != null) 
		{
			viewerCount = viewerMap.size();
		}
		return viewerCount;
	}
	
	public int getTotalViewerCount() {
		int viewerCount = 0;
		for (Map map : streamsViewerMap.values()) {
			viewerCount += map.size();
		}
		return viewerCount;
	}
	public void setVertx(Vertx vertx) {
		this.vertx = vertx;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext)  {
		dataStoreFactory = (DataStoreFactory) applicationContext.getBean(IDataStoreFactory.BEAN_NAME);
		
		vertx = (Vertx) applicationContext.getBean(IAntMediaStreamHandler.VERTX_BEAN_NAME);

		
		AppSettings settings = (AppSettings)applicationContext.getBean(AppSettings.BEAN_NAME);
		timeoutMS = getTimeoutMSFromSettings(settings, timeoutMS);
		
		
		vertx.setPeriodic(DEFAULT_TIME_PERIOD_FOR_VIEWER_COUNT, yt-> 
		{
			synchronized (lock) {
				
				Iterator>> streamIterator = streamsViewerMap.entrySet().iterator();
				
				Iterator> viewerIterator;
				Entry> streamViewerEntry;
				Map viewerMapEntry;
				
				long now = System.currentTimeMillis();
				
				while (streamIterator.hasNext()) 
				{
					streamViewerEntry = streamIterator.next();
					
					String streamId = streamViewerEntry.getKey();
					Broadcast broadcast = getDataStore().get(streamId);
					
					boolean isBroadcasting = false;
					
					// Check if it's deleted.
					// This case for the deleted streams(zombi streams)
					if(broadcast != null) {
					
						int numberOfDecrement = 0;
						
						viewerMapEntry = streamViewerEntry.getValue();
						viewerIterator = viewerMapEntry.entrySet().iterator();
					
						while (viewerIterator.hasNext()) 
						{
							Entry viewer = viewerIterator.next();
	
							if (viewer.getValue() < (now - getTimeoutMS())) 
							{
								// regard it as not a viewer
								viewerIterator.remove();
								numberOfDecrement++;
								
								String sessionId = viewer.getKey();
								String subscriberId = sessionId2subscriberId.get(sessionId);
								// set subscriber status to not connected
								if(subscriberId != null) {
									// add a disconnected event to the subscriber
									ConnectionEvent event = new ConnectionEvent();
									event.setEventType(ConnectionEvent.DISCONNECTED_EVENT);
									Date curDate = new Date();
									event.setTimestamp(curDate.getTime());
									getDataStore().addSubscriberConnectionEvent(streamId, subscriberId, event);
								}
							}
						}
						
						if(broadcast.getStatus().equals(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING)) {
							isBroadcasting = true;
						}
					
						numberOfDecrement = -1 * numberOfDecrement;
	
						int numberOfIncrement = getIncreaseCounterMap(streamId);
						if((numberOfIncrement != 0 || numberOfDecrement != 0) && isBroadcasting) {
							
							int hlsDiffCount = numberOfIncrement + numberOfDecrement;
	
							logger.info("Update HLS viewer in stream ID:{} increment count:{} decrement count:{} diff:{}", streamId, numberOfIncrement, numberOfDecrement, hlsDiffCount);
	
							getDataStore().updateHLSViewerCount(streamViewerEntry.getKey(), hlsDiffCount);
							increaseCounterMap.put(streamId, 0);
						}
					}

					if (!isBroadcasting) {
						// set all connection status information about the subscribers of the stream to false
						viewerMapEntry = streamViewerEntry.getValue();
						viewerIterator = viewerMapEntry.entrySet().iterator();
						while (viewerIterator.hasNext()) {
							Entry viewer = viewerIterator.next();
							
							String sessionId = viewer.getKey();
							String subscriberId = sessionId2subscriberId.get(sessionId);
							// set subscriber status to not connected
							if(subscriberId != null) {
								// add a disconnected event to the subscriber
								ConnectionEvent event = new ConnectionEvent();
								event.setEventType(ConnectionEvent.DISCONNECTED_EVENT);
								Date curDate = new Date();
								event.setTimestamp(curDate.getTime());
								getDataStore().addSubscriberConnectionEvent(streamId, subscriberId, event);
							}
						}
						
						streamIterator.remove();
						increaseCounterMap.remove(streamId);
					}
				}
			}
		});	
	}
	
	public void resetHLSViewerMap(String streamID) {
		Iterator> viewerIterator;
		Map viewerMapEntry = streamsViewerMap.get(streamID);
		if(viewerMapEntry != null) {
			// remove all the subscribers associated with the sessions in the stream 
			viewerIterator = viewerMapEntry.entrySet().iterator();
			while (viewerIterator.hasNext()) {
				Entry viewer = viewerIterator.next();
				
				String sessionId = viewer.getKey();
				if(sessionId2subscriberId.containsKey(sessionId)) {
					sessionId2subscriberId.remove(sessionId);					
				}
			}
			
			streamsViewerMap.get(streamID).clear();
			streamsViewerMap.remove(streamID);
			logger.info("Reset HLS Stream ID: {} removed successfully", streamID);			
		}
		else {
			logger.info("Reset HLS Stream ID: {} remove failed or null", streamID);
		}
	}

	public Map getSessionId2subscriberId() {
		return sessionId2subscriberId;
	}

	public void setSessionId2subscriberId(Map sessionId2subscriberId) {
		this.sessionId2subscriberId = sessionId2subscriberId;
	}

	public static int getTimeoutMSFromSettings(AppSettings settings, int defaultValue) {
		int newTimePeriodMS = defaultValue;
		String hlsTime = settings.getHlsTime();
		if (hlsTime != null && !hlsTime.isEmpty()) {
			try {
				newTimePeriodMS = Integer.valueOf(hlsTime) * 10 * 1000;
			}
			catch (Exception e) {
				logger.error(e.getMessage());
			}
		}
		return newTimePeriodMS;
	}

	public void setTimePeriodMS(int timePeriodMS) {
		this.timePeriodMS = timePeriodMS;
	}
	public int getTimePeriodMS() {
		return timePeriodMS;
	}

	public int getTimeoutMS() {
		return timeoutMS;
	}

	public DataStore getDataStore() {
		if (dataStore == null) {
			dataStore = getDataStoreFactory().getDataStore();
		}
		return dataStore;
	}
	
	public void setDataStore(DataStore dataStore) {
		this.dataStore = dataStore;
	}
	
	
	public DataStoreFactory getDataStoreFactory() {
		return dataStoreFactory;
	}
	
	public void setDataStoreFactory(DataStoreFactory dataStoreFactory) {
		this.dataStoreFactory = dataStoreFactory;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy