io.antmedia.rest.RestServiceBase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ant-media-server Show documentation
Show all versions of ant-media-server Show documentation
Ant Media Server supports RTMP, RTSP, MP4, HLS, WebRTC, Adaptive Streaming, etc.
package io.antmedia.rest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.URL;
import java.nio.file.Files;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.jar.Manifest;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import jakarta.servlet.ServletContext;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.red5.server.api.scope.IBroadcastScope;
import org.red5.server.api.scope.IScope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import io.antmedia.AntMediaApplicationAdapter;
import io.antmedia.AppSettings;
import io.antmedia.RecordType;
import io.antmedia.cluster.IClusterNotifier;
import io.antmedia.datastore.db.DataStore;
import io.antmedia.datastore.db.DataStoreFactory;
import io.antmedia.datastore.db.types.Broadcast;
import io.antmedia.datastore.db.types.BroadcastUpdate;
import io.antmedia.datastore.db.types.Broadcast.PlayListItem;
import io.antmedia.datastore.db.types.ConferenceRoom;
import io.antmedia.datastore.db.types.Endpoint;
import io.antmedia.datastore.db.types.TensorFlowObject;
import io.antmedia.datastore.db.types.Token;
import io.antmedia.datastore.db.types.VoD;
import io.antmedia.ipcamera.OnvifCamera;
import io.antmedia.ipcamera.onvifdiscovery.OnvifDiscovery;
import io.antmedia.logger.LoggerUtils;
import io.antmedia.muxer.IAntMediaStreamHandler;
import io.antmedia.muxer.Mp4Muxer;
import io.antmedia.muxer.MuxAdaptor;
import io.antmedia.muxer.Muxer;
import io.antmedia.muxer.RecordMuxer;
import io.antmedia.rest.model.Result;
import io.antmedia.rest.model.Version;
import io.antmedia.security.ITokenService;
import io.antmedia.settings.ServerSettings;
import io.antmedia.statistic.DashViewerStats;
import io.antmedia.statistic.HlsViewerStats;
import io.antmedia.statistic.IStatsCollector;
import io.antmedia.storage.StorageClient;
import io.antmedia.streamsource.StreamFetcher;
import io.antmedia.streamsource.StreamFetcher.IStreamFetcherListener;
import io.antmedia.webrtc.api.IWebRTCAdaptor;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.ws.rs.core.Context;
public abstract class RestServiceBase {
private static final String MAIN_TRACK_OF_THE_STREAM = "Main track of the stream ";
public static final String REPLACE_CHARS_FOR_SECURITY = "[\n\r]";
public class BroadcastStatistics {
@Schema(description = "The total RTMP viewers of the stream")
public final int totalRTMPWatchersCount;
@Schema(description = "The total HLS viewers of the stream")
public final int totalHLSWatchersCount;
@Schema(description = "The total WebRTC viewers of the stream")
public final int totalWebRTCWatchersCount;
@Schema(description = "The total DASH viewers of the stream")
public final int totalDASHWatchersCount;
public BroadcastStatistics(int totalRTMPWatchersCount, int totalHLSWatchersCount,
int totalWebRTCWatchersCount, int totalDASHWatchersCount) {
this.totalRTMPWatchersCount = totalRTMPWatchersCount;
this.totalHLSWatchersCount = totalHLSWatchersCount;
this.totalWebRTCWatchersCount = totalWebRTCWatchersCount;
this.totalDASHWatchersCount = totalDASHWatchersCount;
}
}
public class AppBroadcastStatistics extends BroadcastStatistics {
@Schema(description = "The total active live stream count")
public final int activeLiveStreamCount;
public AppBroadcastStatistics(int totalRTMPWatchersCount, int totalHLSWatchersCount,
int totalWebRTCWatchersCount, int totalDASHWatchersCount, int activeLiveStreamCount) {
super(totalRTMPWatchersCount, totalHLSWatchersCount, totalWebRTCWatchersCount, totalDASHWatchersCount);
this.activeLiveStreamCount = activeLiveStreamCount;
}
}
public interface ProcessBuilderFactory {
Process make(String...args);
}
/**
* Key for Manifest entry of Build number. It should match with the value in pom.xml
*/
public static final String BUILD_NUMBER = "Build-Number";
public static final String ENTERPRISE_EDITION = "Enterprise Edition";
public static final String COMMUNITY_EDITION = "Community Edition";
public static final int MAX_ITEM_IN_ONE_LIST = 50;
public static final int ERROR_SOCIAL_ENDPOINT_UNDEFINED_CLIENT_ID = -1;
public static final int ERROR_SOCIAL_ENDPOINT_UNDEFINED_ENDPOINT = -2;
public static final int ERROR_SOCIAL_ENDPOINT_EXCEPTION_IN_ASKING_AUTHPARAMS = -3;
public static final int RECORD_ENABLE = 1;
public static final int RECORD_DISABLE = -1;
public static final int RECORD_NO_SET = 0;
public static final int HIGH_CPU_ERROR = -3;
public static final int FETCHER_NOT_STARTED_ERROR = -4;
public static final int INVALID_STREAM_NAME_ERROR = -5;
public static final String HTTP = "http://";
public static final String RTSP = "rtsp://";
public static final String ENDPOINT_GENERIC = "generic";
protected static Logger logger = LoggerFactory.getLogger(RestServiceBase.class);
private ProcessBuilderFactory processBuilderFactory = null;
//TODO: This REGEX does not fully match 10.10.157.200. It ignores the last 0 it matches 10.10.157.20 and it cause problem in replacements
public static final String IPV4_REGEX = "(([0-1]?[0-9]{1,2}\\.)|(2[0-4][0-9]\\.)|(25[0-5]\\.)){3}(([0-1]?[0-9]{1,2})|(2[0-4][0-9])|(25[0-5]))";
public static final String LOOPBACK_REGEX = "^localhost$|^127(?:\\.[0-9]+){0,2}\\.[0-9]+$|^(?:0*\\:)*?:?0*1$";
private static final String REPLACE_CHARS = "[\n|\r|\t]";
@Context
protected ServletContext servletContext;
protected DataStoreFactory dataStoreFactory;
private DataStore dbStore;
protected ApplicationContext appCtx;
protected IScope scope;
protected AntMediaApplicationAdapter appInstance;
private AppSettings appSettings;
private ServerSettings serverSettings;
public void setAppCtx(ApplicationContext appCtx) {
this.appCtx = appCtx;
}
@Nullable
public ApplicationContext getAppContext() {
if (servletContext != null) {
appCtx = (ApplicationContext) servletContext
.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
}
return appCtx;
}
/**
* this is for testing
* @param app
*/
public void setApplication(AntMediaApplicationAdapter app) {
this.appInstance = app;
}
public AntMediaApplicationAdapter getApplication() {
if (appInstance == null) {
ApplicationContext appContext = getAppContext();
if (appContext != null) {
appInstance = (AntMediaApplicationAdapter) appContext.getBean(AntMediaApplicationAdapter.BEAN_NAME);
}
}
return appInstance;
}
public IScope getScope() {
if (scope == null) {
scope = getApplication().getScope();
}
return scope;
}
public void setScope(IScope scope) {
this.scope = scope;
}
public DataStore getDataStore() {
if (dbStore == null) {
dbStore = getDataStoreFactory().getDataStore();
}
return dbStore;
}
public void setDataStore(DataStore dataStore) {
this.dbStore = dataStore;
}
public DataStoreFactory getDataStoreFactory() {
if(dataStoreFactory == null) {
WebApplicationContext ctxt = WebApplicationContextUtils.getWebApplicationContext(servletContext);
if (ctxt != null) {
dataStoreFactory = (DataStoreFactory) ctxt.getBean("dataStoreFactory");
}
}
return dataStoreFactory;
}
public void setDataStoreFactory(DataStoreFactory dataStoreFactory) {
this.dataStoreFactory = dataStoreFactory;
}
public Broadcast createBroadcastWithStreamID(Broadcast broadcast) {
Broadcast createdBroadcast = saveBroadcast(broadcast, IAntMediaStreamHandler.BROADCAST_STATUS_CREATED, getScope().getName(),
getDataStore(), getAppSettings().getListenerHookURL(), getServerSettings(), 0);
if (AntMediaApplicationAdapter.PLAY_LIST.equals(createdBroadcast.getType()))
{
long now = System.currentTimeMillis();
//Schedule playlist if plannedStartDate is ok
getApplication().schedulePlayList(now, createdBroadcast);
}
return createdBroadcast;
}
public static Broadcast saveBroadcast(Broadcast broadcast, String status, String scopeName, DataStore dataStore,
String settingsListenerHookURL, ServerSettings serverSettings, long absoluteStartTimeMs) {
if (broadcast == null) {
broadcast = new Broadcast();
}
broadcast.setStatus(status);
broadcast.setDate(System.currentTimeMillis());
String listenerHookURL = broadcast.getListenerHookURL();
if ((listenerHookURL == null || listenerHookURL.isEmpty())
&& settingsListenerHookURL != null && !settingsListenerHookURL.isEmpty()) {
broadcast.setListenerHookURL(settingsListenerHookURL);
}
String fqdn = serverSettings.getServerName();
if (fqdn == null || fqdn.length() == 0) {
fqdn = serverSettings.getHostAddress();
}
broadcast.setOriginAdress(serverSettings.getHostAddress());
broadcast.setAbsoluteStartTimeMs(absoluteStartTimeMs);
removeEmptyPlayListItems(broadcast.getPlayListItemList());
if (fqdn != null && fqdn.length() >= 0) {
broadcast.setRtmpURL("rtmp://" + fqdn + "/" + scopeName + "/");
}
updatePlayListItemDurationsIfApplicable(broadcast.getPlayListItemList(), broadcast.getStreamId());
dataStore.save(broadcast);
return broadcast;
}
public static void updatePlayListItemDurationsIfApplicable(List playListItemList, String streamId)
{
if (playListItemList != null)
{
for (PlayListItem playListItem : playListItemList)
{
if (AntMediaApplicationAdapter.VOD.equals(playListItem.getType())) {
playListItem.setDurationInMs(Muxer.getDurationInMs(playListItem.getStreamUrl(), streamId));
}
}
}
}
public AppSettings getAppSettings() {
if (appSettings == null) {
ApplicationContext appContext = getAppContext();
if (appContext != null) {
appSettings = (AppSettings) appContext.getBean(AppSettings.BEAN_NAME);
}
}
return appSettings;
}
public void setAppSettings(AppSettings appSettings) {
this.appSettings = appSettings;
}
public ServerSettings getServerSettings() {
if (serverSettings == null) {
ApplicationContext appContext = getAppContext();
if (appContext != null) {
serverSettings = (ServerSettings) appContext.getBean(ServerSettings.BEAN_NAME);
}
}
return serverSettings;
}
public void setServerSettings(ServerSettings serverSettings) {
this.serverSettings = serverSettings;
}
protected Result deleteBroadcast(String id) {
Result result = new Result (false);
boolean stopResult = false;
Broadcast broadcast = null;
if (id != null && (broadcast = getDataStore().get(id)) != null)
{
//no need to check if the stream is another node because RestProxyFilter makes this arrangement
stopResult = stopBroadcastInternal(broadcast);
//if it's something about scheduled playlist
getApplication().cancelPlaylistSchedule(broadcast.getStreamId());
result.setSuccess(getDataStore().delete(id));
if(result.isSuccess())
{
if (stopResult) {
logger.info("broadcast {} is deleted and stopped successfully", broadcast.getStreamId());
result.setMessage("broadcast is deleted and stopped successfully");
}
else {
logger.info("broadcast {} is deleted but could not stopped", broadcast);
result.setMessage("broadcast is deleted but could not stopped ");
}
}
}
else
{
logger.warn("Broadcast delete operation not successfull because broadcast is not found in db for stream id:{}", id != null ? id.replaceAll(REPLACE_CHARS, "_") : null);
}
return result;
}
protected Result deleteBroadcasts(String[] streamIds) {
Result result = new Result(false);
if(streamIds != null)
{
for (String id : streamIds)
{
result = deleteBroadcast(id);
if (!result.isSuccess())
{
id = id.replaceAll(REPLACE_CHARS_FOR_SECURITY, "_" );
logger.warn("It cannot delete {} and breaking the loop", id);
break;
}
}
}
else
{
logger.warn("Requested deletion for Stream Ids is empty");
}
return result;
}
protected boolean stopBroadcastInternal(Broadcast broadcast) {
boolean result = false;
if (broadcast != null) {
result = getApplication().stopStreaming(broadcast).isSuccess();
if (result) {
logger.info("broadcast is stopped streamId: {}", broadcast.getStreamId());
}
else {
logger.error("No active broadcast found with id {}, so could not stopped", broadcast.getStreamId());
}
}
return result;
}
protected Broadcast lookupBroadcast(String id) {
Broadcast broadcast = null;
try {
broadcast = getDataStore().get(id);
} catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
return broadcast;
}
protected Result updateBroadcast(String streamId, BroadcastUpdate updatedBroadcast) {
boolean result = getDataStore().updateBroadcastFields(streamId, updatedBroadcast);
return new Result(result);
}
private static void removeEmptyPlayListItems(List playListItemList)
{
if (playListItemList != null)
{
Iterator iterator = playListItemList.iterator();
while (iterator.hasNext())
{
PlayListItem listItem = iterator.next();
if (listItem.getStreamUrl() == null || listItem.getStreamUrl().isEmpty())
{
iterator.remove();
}
}
}
}
public boolean isStreaming(Broadcast broadcast) {
return AntMediaApplicationAdapter.isStreaming(broadcast);
}
/**
* Update Stream Source or IP Camera info
* @param updatedBroadcast
* @param socialNetworksToPublish
* @return
*/
protected Result updateStreamSource(String streamId, BroadcastUpdate updatedBroadcast, Broadcast broadcastInDB) {
logger.debug("Updating stream source for stream {}", updatedBroadcast.getStreamId());
boolean isPlayList = AntMediaApplicationAdapter.PLAY_LIST.equals(broadcastInDB.getType());
if (StringUtils.isNotBlank(updatedBroadcast.getStreamUrl()) && !checkStreamUrl(updatedBroadcast.getStreamUrl()) && !isPlayList) {
return new Result(false, "Stream URL is not valid");
}
boolean isStreamingActive = isStreaming(broadcastInDB);
//Stop if it's streaming and type is not playlist
if (isStreamingActive && !isPlayList) {
boolean resultStopStreaming = checkStopStreaming(broadcastInDB);
waitStopStreaming(broadcastInDB, resultStopStreaming);
}
if (AntMediaApplicationAdapter.IP_CAMERA.equals(broadcastInDB.getType()) &&
!StringUtils.isAllBlank(updatedBroadcast.getIpAddr(), updatedBroadcast.getUsername(),updatedBroadcast.getPassword())) {
if (StringUtils.isBlank(updatedBroadcast.getIpAddr())) {
updatedBroadcast.setIpAddr(broadcastInDB.getIpAddr());
}
if (StringUtils.isBlank(updatedBroadcast.getUsername())) {
updatedBroadcast.setUsername(broadcastInDB.getUsername());
}
if (StringUtils.isBlank(updatedBroadcast.getPassword())) {
updatedBroadcast.setPassword(broadcastInDB.getPassword());
}
Result connectionRes = connectToCamera(updatedBroadcast.getIpAddr(), updatedBroadcast.getUsername(), updatedBroadcast.getPassword());
if (!connectionRes.isSuccess()) {
return connectionRes;
}
String rtspURL = connectionRes.getMessage();
String authparam = updatedBroadcast.getUsername() + ":" + updatedBroadcast.getPassword() + "@";
String rtspURLWithAuth = RTSP + authparam + rtspURL.substring(RTSP.length());
logger.info("New Stream Source URL: {}", rtspURLWithAuth);
updatedBroadcast.setStreamUrl(rtspURLWithAuth);
}
removeEmptyPlayListItems(updatedBroadcast.getPlayListItemList());
updatePlayListItemDurationsIfApplicable(updatedBroadcast.getPlayListItemList(), updatedBroadcast.getStreamId());
boolean result = getDataStore().updateBroadcastFields(streamId, updatedBroadcast);
if (result) {
if (updatedBroadcast.getPlannedStartDate() != null
&& broadcastInDB.getPlannedStartDate() != updatedBroadcast.getPlannedStartDate()
&& isPlayList) {
getApplication().cancelPlaylistSchedule(broadcastInDB.getStreamId());
getApplication().schedulePlayList(System.currentTimeMillis(), getDataStore().get(streamId));
}
if (isStreamingActive && !isPlayList) {
//start streaming again if it was streaming and it's not Playlist
Broadcast fetchedBroadcast = getDataStore().get(streamId);
getApplication().startStreaming(fetchedBroadcast);
}
}
return new Result(result);
}
public boolean checkStopStreaming(Broadcast broadcast)
{
// If broadcast status is broadcasting, this will force stop the streaming.
if(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING.equals(broadcast.getStatus()))
{
return getApplication().stopStreaming(broadcast).isSuccess();
}
else if(getApplication().getStreamFetcherManager().isStreamRunning(broadcast)) {
return getApplication().stopStreaming(broadcast).isSuccess();
}
else
{
// If broadcast status is stopped, this will return true.
return true;
}
}
public boolean waitStopStreaming(Broadcast broadcast, Boolean resultStopStreaming) {
int i = 0;
int waitPeriod = 250;
// Broadcast status finished is not enough to be sure about broadcast's status.
while (!IAntMediaStreamHandler.BROADCAST_STATUS_FINISHED.equals(getDataStore().get(broadcast.getStreamId()).getStatus()) && !resultStopStreaming.equals(true)) {
try {
i++;
logger.info("Waiting for stop broadcast: {} Total wait time: {}ms", broadcast.getStreamId() , i*waitPeriod);
Thread.sleep(waitPeriod);
if(i > 20) {
logger.warn("{} Stream ID broadcast could not be stopped. Total wait time: {}ms", broadcast.getStreamId() , i*waitPeriod);
break;
}
} catch (InterruptedException e) {
logger.error(e.getMessage());
Thread.currentThread().interrupt();
}
}
return true;
}
@Deprecated
public Result addEndpoint(String id, String rtmpUrl) {
boolean success = false;
String message = null;
try {
if (validateStreamURL(rtmpUrl))
{
Endpoint endpoint = new Endpoint();
endpoint.setRtmpUrl(rtmpUrl);
endpoint.setType(ENDPOINT_GENERIC);
success = getDataStore().addEndpoint(id, endpoint);
}
} catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
return new Result(success, message);
}
public Result addEndpoint(String id, Endpoint endpoint) {
boolean success = false;
String message = null;
endpoint.setType(ENDPOINT_GENERIC);
String endpointServiceId = endpoint.getEndpointServiceId();
if (endpointServiceId == null || endpointServiceId.isEmpty()) {
//generate custom endpoint invidual ID
endpointServiceId = "custom"+RandomStringUtils.randomAlphabetic(6);
}
endpoint.setEndpointServiceId(endpointServiceId);
try {
if (validateStreamURL(endpoint.getRtmpUrl()))
{
success = getDataStore().addEndpoint(id, endpoint);
}
} catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
return new Result(success, endpointServiceId, message);
}
@Deprecated
public Result removeEndpoint(String id, String rtmpUrl)
{
Endpoint endpoint = new Endpoint();
endpoint.setRtmpUrl(rtmpUrl);
endpoint.setType(ENDPOINT_GENERIC);
boolean removed = getDataStore().removeEndpoint(id, endpoint, true);
return new Result(removed);
}
public Result removeRTMPEndpoint(String id, Endpoint endpoint)
{
boolean removed = getDataStore().removeEndpoint(id, endpoint, false);
return new Result(removed);
}
public boolean isInSameNodeInCluster(String originAddress) {
boolean isCluster = getAppContext().containsBean(IClusterNotifier.BEAN_NAME);
return !isCluster || originAddress.equals(getServerSettings().getHostAddress());
}
public Result processRTMPEndpoint(String streamId, String originAddress, String rtmpUrl, boolean addEndpoint, int resolution) {
Result result = new Result(false);
if(isInSameNodeInCluster(originAddress))
{
if(addEndpoint) {
result = getMuxAdaptor(streamId).startRtmpStreaming(rtmpUrl, resolution);
}
else {
result = getMuxAdaptor(streamId).stopRtmpStreaming(rtmpUrl, resolution);
}
}
else {
logger.error("Please send a RTMP Endpoint request to the {} node or {} RTMP Endpoint in a stopped broadcast.", originAddress, addEndpoint ? "add" : "remove");
result.setSuccess(false);
}
return result;
}
public Result importLiveStreams2Stalker()
{
String stalkerDBServer = getAppSettings().getStalkerDBServer();
String stalkerDBUsername = getAppSettings().getStalkerDBUsername();
String stalkerDBPassword = getAppSettings().getStalkerDBPassword();
boolean result = false;
String message = "";
int errorId = -1;
if (stalkerDBServer != null && stalkerDBServer.length() > 0
&& stalkerDBUsername != null && stalkerDBUsername.length() > 0
&& stalkerDBPassword != null && stalkerDBPassword.length() > 0)
{
long broadcastCount = getDataStore().getBroadcastCount();
int pageCount = (int) broadcastCount/DataStore.MAX_ITEM_IN_ONE_LIST
+ ((broadcastCount % DataStore.MAX_ITEM_IN_ONE_LIST != 0) ? 1 : 0);
List broadcastList = new ArrayList<>();
for (int i = 0; i < pageCount; i++) {
broadcastList.addAll(getDataStore().getBroadcastList(i*DataStore.MAX_ITEM_IN_ONE_LIST, DataStore.MAX_ITEM_IN_ONE_LIST,null,null,null,null));
}
StringBuilder insertQueryString = new StringBuilder();
insertQueryString.append("DELETE FROM stalker_db.ch_links;");
insertQueryString.append("DELETE FROM stalker_db.itv;");
String fqdn = getServerSettings().getServerName();
if (fqdn == null || fqdn.length() == 0) {
fqdn = getServerSettings().getHostAddress();
}
int number = 1;
for (Broadcast broadcast : broadcastList) {
String cmd = "ffmpeg http://"+ fqdn + ":"+serverSettings.getDefaultHttpPort()+"/"
+ getScope().getName() + "/streams/"+broadcast.getStreamId()+".m3u8";
insertQueryString.append("INSERT INTO stalker_db.itv(name, number, tv_genre_id, base_ch, cmd, languages)"
+ " VALUES ('"+broadcast.getName()+"' , "+ number +", 2, 1, '"+ cmd +"', '');");
insertQueryString.append("SET @last_id=LAST_INSERT_ID();"
+ "INSERT INTO stalker_db.ch_links(ch_id, url)"
+ " VALUES(@last_id, '"+ cmd +"');");
number++;
}
result = runStalkerImportQuery(insertQueryString.toString(), stalkerDBServer, stalkerDBUsername, stalkerDBPassword);
}
else {
message = "Portal DB info is missing";
errorId = 404;
}
return new Result(result, message, errorId);
}
private boolean runStalkerImportQuery(String query, String stalkerDBServer, String stalkerDBUsername, String stalkerDBPassword) {
boolean result = false;
try {
Process p = getProcess(query, stalkerDBServer, stalkerDBUsername, stalkerDBPassword);
if (p != null) {
InputStream is = p.getInputStream();
if (is != null) {
byte[] data = new byte[1024];
int length;
while ((length = is.read(data, 0, data.length)) != -1) {
if (logger.isInfoEnabled()) {
logger.info(new String(data, 0, length));
}
}
}
int exitWith = p.waitFor();
if (exitWith == 0) {
result = true;
}
}
} catch (IOException e) {
logger.error(ExceptionUtils.getStackTrace(e));
} catch (InterruptedException e) {
logger.error(ExceptionUtils.getStackTrace(e));
Thread.currentThread().interrupt();
}
return result;
}
private Process getProcess(String query, String stalkerDBServer, String stalkerDBUsername, String stalkerDBPassword) {
Process process = null;
String mysqlClientPath = getAppSettings().getMySqlClientPath();
if (processBuilderFactory != null) {
process = processBuilderFactory.make(mysqlClientPath,
"-h", stalkerDBServer,
"-u", stalkerDBUsername,
"-p"+stalkerDBPassword,
"-e", query);
}
else {
try {
process = new ProcessBuilder(
mysqlClientPath,
"-h", stalkerDBServer,
"-u", stalkerDBUsername,
"-p"+stalkerDBPassword,
"-e", query
).redirectErrorStream(true).start();
} catch (IOException e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
}
return process;
}
public Result importVoDsToStalker()
{
String stalkerDBServer = getAppSettings().getStalkerDBServer();
String stalkerDBUsername = getAppSettings().getStalkerDBUsername();
String stalkerDBPassword = getAppSettings().getStalkerDBPassword();
boolean result = false;
String message = "";
int errorId = -1;
if (stalkerDBServer != null && stalkerDBUsername != null && stalkerDBPassword != null) {
String vodFolderPath = getAppSettings().getVodFolder();
if (vodFolderPath != null && !vodFolderPath.isEmpty()) {
long totalVodNumber = getDataStore().getTotalVodNumber();
int pageCount = (int) totalVodNumber/DataStore.MAX_ITEM_IN_ONE_LIST
+ ((totalVodNumber % DataStore.MAX_ITEM_IN_ONE_LIST != 0) ? 1 : 0);
List vodList = new ArrayList<>();
for (int i = 0; i < pageCount; i++) {
vodList.addAll(getDataStore().getVodList(i*DataStore.MAX_ITEM_IN_ONE_LIST, DataStore.MAX_ITEM_IN_ONE_LIST, null, null, null, null));
}
String fqdn = getServerSettings().getServerName();
if (fqdn == null || fqdn.length() == 0) {
fqdn = getServerSettings().getHostAddress();
}
StringBuilder insertQueryString = new StringBuilder();
//delete all videos in stalker to import new ones
insertQueryString.append("DELETE FROM stalker_db.video_series_files;");
insertQueryString.append("DELETE FROM stalker_db.video;");
for (VoD vod : vodList) {
if (vod.getType().equals(VoD.USER_VOD)) {
insertQueryString.append("INSERT INTO stalker_db.video(name, o_name, protocol, category_id, cat_genre_id_1, status, cost, path, accessed) "
+ "values('"+ vod.getVodName() + "', '"+vod.getVodName()+"', '', 1, 1, 1, 0, '"+vod.getVodName()+"', 1);");
File vodFolder = new File(vodFolderPath);
int lastIndexOf = vod.getFilePath().lastIndexOf(vodFolder.getName());
String filePath = vod.getFilePath().substring(lastIndexOf);
String cmd = "ffmpeg http://"+ fqdn + ":"+serverSettings.getDefaultHttpPort()+"/"
+ getScope().getName() + "/streams/" + filePath;
insertQueryString.append("SET @last_id=LAST_INSERT_ID();");
insertQueryString.append("INSERT INTO stalker_db.video_series_files"
+ "(video_id, file_type, protocol, url, languages, quality, date_add, date_modify, status, accessed)"
+ "VALUES(@last_id, 'video', 'custom', '"+cmd+"', 'a:1:{i:0;s:2:\"en\";}', 5, NOW(), NOW(), 1, 1);");
}
}
result = runStalkerImportQuery(insertQueryString.toString(), stalkerDBServer, stalkerDBUsername, stalkerDBPassword );
}
else {
message = "No VoD folder specified";
errorId = 500;
}
}
else {
message = "Portal DB info is missing";
errorId = 404;
}
return new Result(result, message, errorId);
}
protected ProcessBuilderFactory getProcessBuilderFactory() {
return processBuilderFactory;
}
public void setProcessBuilderFactory(ProcessBuilderFactory processBuilderFactory) {
this.processBuilderFactory = processBuilderFactory;
}
public IWebRTCAdaptor getWebRTCAdaptor() {
IWebRTCAdaptor adaptor = null;
ApplicationContext appContext = getAppContext();
if (appContext != null && appContext.containsBean(IWebRTCAdaptor.BEAN_NAME)) {
Object webRTCAdaptorBean = appContext.getBean(IWebRTCAdaptor.BEAN_NAME);
if(webRTCAdaptorBean != null) {
adaptor = (IWebRTCAdaptor) webRTCAdaptorBean;
}
}
return adaptor;
}
public Result addIPCamera(Broadcast stream) {
Result connResult = new Result(false);
if(validateStreamURL(stream.getIpAddr())) {
logger.info("type {}", stream.getType());
connResult = connectToCamera(stream.getIpAddr(), stream.getUsername(), stream.getPassword());
if (connResult.isSuccess()) {
String authparam = stream.getUsername() + ":" + stream.getPassword() + "@";
String rtspURLWithAuth = RTSP + authparam + connResult.getMessage().substring(RTSP.length());
logger.info("rtsp url with auth: {}", rtspURLWithAuth);
stream.setStreamUrl(rtspURLWithAuth);
Date currentDate = new Date();
long unixTime = currentDate.getTime();
stream.setDate(unixTime);
Broadcast savedBroadcast = saveBroadcast(stream, IAntMediaStreamHandler.BROADCAST_STATUS_CREATED, getScope().getName(), getDataStore(), getAppSettings().getListenerHookURL(), getServerSettings(), 0);
connResult = getApplication().startStreaming(savedBroadcast);
//if IP Camera is not being started while adding, do not record it to datastore
if (!connResult.isSuccess())
{
getDataStore().delete(savedBroadcast.getStreamId());
}
}
}
else {
connResult.setMessage("IP camera addr is not valid: " + stream.getIpAddr());
}
return connResult;
}
public Result addStreamSource(Broadcast stream) {
Result result = new Result(false);
IStatsCollector monitor = (IStatsCollector) getAppContext().getBean(IStatsCollector.BEAN_NAME);
if(monitor.enoughResource())
{
if (stream.getType().equals(AntMediaApplicationAdapter.IP_CAMERA)) {
result = addIPCamera(stream);
}
else if (stream.getType().equals(AntMediaApplicationAdapter.STREAM_SOURCE) ) {
result = addSource(stream);
}
else{
result.setMessage("Auto start query needs an IP camera or stream source.");
}
}
else {
logger.error("Stream Fetcher can not be created due to high cpu load/limit: {}/{} ram free/minfree:{}/{}",
monitor.getCpuLoad(), monitor.getCpuLimit(), monitor.getFreeRam(), monitor.getMinFreeRamSize());
result.setMessage("Resource usage is high");
result.setErrorId(HIGH_CPU_ERROR);
}
return result;
}
public Result connectToCamera(String ipAddr, String username, String password) {
Result result = new Result(false);
OnvifCamera onvif = new OnvifCamera();
int connResult = onvif.connect(ipAddr, username, password);
if (connResult == 0) {
result.setSuccess(true);
//set RTSP URL. This message is directly used in saving stream url to the datastore
result.setMessage(onvif.getRTSPStreamURI());
}else {
//there is an error
//set error code and send it
result.setMessage("Could not connect to " + ipAddr + " result:" + connResult);
result.setErrorId(connResult);
logger.info("Cannot connect to ip camera:{}", ipAddr);
}
return result;
}
/**
* Parse the string to check it's a valid url
* It can parse protocol://username:[email protected]/stream format as well
* @param url
* @return
*/
protected static boolean validateStreamURL(String url) {
boolean ipAddrControl = false;
String[] ipAddrParts = null;
String serverAddr = url;
if(url != null && (url.startsWith(HTTP) ||
url.startsWith("https://") ||
url.startsWith("rtmp://") ||
url.startsWith("rtmps://") ||
url.startsWith(RTSP))) {
ipAddrParts = url.split("//");
serverAddr = ipAddrParts[1];
ipAddrControl=true;
}
if (serverAddr != null) {
if (serverAddr.contains("@")){
ipAddrParts = serverAddr.split("@");
serverAddr = ipAddrParts[1];
}
if (serverAddr.contains(":")){
ipAddrParts = serverAddr.split(":");
serverAddr = ipAddrParts[0];
}
if (serverAddr.contains("/")){
ipAddrParts = serverAddr.split("/");
serverAddr = ipAddrParts[0];
}
if (logger.isInfoEnabled()) {
logger.info("IP: {}", serverAddr.replaceAll("[\n|\r|\t]", "_"));
}
if(serverAddr.split("\\.").length == 4 && validateIPaddress(serverAddr)){
ipAddrControl = true;
}
}
return ipAddrControl;
}
protected static boolean validateIPaddress(String ipaddress) {
Pattern patternIP4 = Pattern.compile(IPV4_REGEX);
Pattern patternLoopBack = Pattern.compile(LOOPBACK_REGEX);
return patternIP4.matcher(ipaddress).matches() || patternLoopBack.matcher(ipaddress).matches() ;
}
public boolean checkStreamUrl (String url) {
boolean streamUrlControl = false;
String[] ipAddrParts = null;
String ipAddr = null;
if(url != null && (url.startsWith(HTTP) ||
url.startsWith("https://") ||
url.startsWith("rtmp://") ||
url.startsWith("rtmps://") ||
url.startsWith("rtsps://") ||
url.startsWith(RTSP) ||
url.startsWith("udp://") ||
url.startsWith("srt://")
)) {
streamUrlControl=true;
ipAddrParts = url.split("//");
ipAddr = ipAddrParts[1];
if (ipAddr.contains("@")){
ipAddrParts = ipAddr.split("@");
ipAddr = ipAddrParts[1];
}
if (ipAddr.contains(":")){
ipAddrParts = ipAddr.split(":");
ipAddr = ipAddrParts[0];
}
if (ipAddr.contains("/")){
ipAddrParts = ipAddr.split("/");
ipAddr = ipAddrParts[0];
}
}
return streamUrlControl;
}
protected Result addSource(Broadcast stream) {
Result result=new Result(false);
if(checkStreamUrl(stream.getStreamUrl())) {
//small improvement for user experience
boolean isVoD = stream.getStreamUrl().startsWith("http") && (stream.getStreamUrl().endsWith("mp4") || stream.getStreamUrl().endsWith("webm") || stream.getStreamUrl().endsWith("flv"));
if (isVoD) {
stream.setType(AntMediaApplicationAdapter.VOD);
}
Date currentDate = new Date();
long unixTime = currentDate.getTime();
stream.setDate(unixTime);
Broadcast savedBroadcast = saveBroadcast(stream, IAntMediaStreamHandler.BROADCAST_STATUS_CREATED, getScope().getName(), getDataStore(), getAppSettings().getListenerHookURL(), getServerSettings(), 0);
result = getApplication().startStreaming(savedBroadcast);
//if it's not started while adding, do not record it to datastore
if(!result.isSuccess()) {
getDataStore().delete(savedBroadcast.getStreamId());
result.setErrorId(FETCHER_NOT_STARTED_ERROR);
}
}
return result;
}
protected List getWebRTCClientStatsList(int offset, int size, String streamId) {
List list = new ArrayList<>();
IWebRTCAdaptor webRTCAdaptor = getWebRTCAdaptor();
if (webRTCAdaptor != null)
{
Collection webRTCClientStats = webRTCAdaptor.getWebRTCClientStats(streamId);
int t = 0;
int itemCount = 0;
if (size > MAX_ITEM_IN_ONE_LIST) {
size = MAX_ITEM_IN_ONE_LIST;
}
if (offset < 0) {
offset = 0;
}
for (WebRTCClientStats webrtcClientStat : webRTCClientStats) {
if (t < offset) {
t++;
continue;
}
list.add(webrtcClientStat);
itemCount++;
if (itemCount >= size ) {
return list;
}
}
}
return list;
}
protected Result deleteVoD(String id) {
boolean success = false;
String message = "";
ApplicationContext appContext = getAppContext();
if (appContext != null) {
File videoFile = null;
VoD voD = getDataStore().getVoD(id);
if (voD != null) {
try {
String filePath = String.format("webapps/%s/%s", getScope().getName(), voD.getFilePath());
videoFile = new File(filePath);
boolean result = Files.deleteIfExists(videoFile.toPath());
if (!result) {
logger.warn("File is not deleted because it does not exist {}", videoFile.getAbsolutePath());
}
String previewFilePath = voD.getPreviewFilePath();
if(previewFilePath != null){
File tmp = new File(previewFilePath);
boolean resultThumbnail = Files.deleteIfExists(tmp.toPath());
if (!resultThumbnail) {
logger.warn("Preview is not deleted because it does not exist {}", tmp.getAbsolutePath());
}
}
success = getDataStore().deleteVod(id);
if (success) {
message = "vod deleted";
}
String fileName = videoFile.getName();
int indexOfFileExtension = fileName.lastIndexOf(".");
String finalFileName = fileName.substring(0,indexOfFileExtension);
//delete preview file if exists
File previewFile = Muxer.getPreviewFile(getScope(), finalFileName, ".png");
Files.deleteIfExists(previewFile.toPath());
StorageClient storageClient = (StorageClient) appContext.getBean(StorageClient.BEAN_NAME);
storageClient.delete(getAppSettings().getS3StreamsFolderPath() + File.separator + fileName);
storageClient.delete(getAppSettings().getS3PreviewsFolderPath() + File.separator + finalFileName + ".png");
}
catch (Exception e) {
logger.error(ExceptionUtils.getStackTrace(e));
}
}
}
return new Result(success, message);
}
protected Result deleteVoDs(String[] vodIds)
{
Result result = new Result(false);
if(vodIds != null)
{
for (String id : vodIds)
{
result = deleteVoD(id);
if (!result.isSuccess())
{
id = id.replaceAll(REPLACE_CHARS_FOR_SECURITY, "_" );
logger.warn("VoD:{} cannot be deleted and breaking the loop", id);
break;
}
}
}
else
{
logger.warn("Requested deletion for VoD Ids is empty");
}
return result;
}
protected String getStreamsDirectory(String appScopeName) {
return String.format("%s/webapps/%s/%s", System.getProperty("red5.root"), appScopeName, "streams");
}
protected Result uploadVoDFile(String fileName, InputStream inputStream) {
boolean success = false;
String message = "";
String id= null;
String appScopeName = getScope().getName();
String fileExtension = FilenameUtils.getExtension(fileName);
try {
if ("mp4".equalsIgnoreCase(fileExtension) || "webm".equalsIgnoreCase(fileExtension)
|| "mov".equalsIgnoreCase(fileExtension) || "avi".equalsIgnoreCase(fileExtension)
|| "wmv".equalsIgnoreCase(fileExtension)) {
IStatsCollector statsCollector = (IStatsCollector) getAppContext().getBean(IStatsCollector.BEAN_NAME);
String vodUploadFinishScript = getAppSettings().getVodUploadFinishScript();
if (StringUtils.isNotBlank(vodUploadFinishScript) && !statsCollector.enoughResource())
{
logger.info("Not enough resource to upload VoD file");
message = "Not enough system resources available to upload and process VoD File";
}
else
{
File streamsDirectory = new File(
getStreamsDirectory(appScopeName));
// if the directory does not exist, create it
if (!streamsDirectory.exists()) {
streamsDirectory.mkdirs();
}
String vodId = RandomStringUtils.randomNumeric(24);
File savedFile = new File(String.format("%s/webapps/%s/%s", System.getProperty("red5.root"), appScopeName,
"streams/" + vodId + "." + fileExtension));
int read = 0;
byte[] bytes = new byte[2048];
try (OutputStream outpuStream = new FileOutputStream(savedFile))
{
while ((read = inputStream.read(bytes)) != -1) {
outpuStream.write(bytes, 0, read);
}
outpuStream.flush();
long fileSize = savedFile.length();
long unixTime = System.currentTimeMillis();
String path = savedFile.getPath();
String relativePath = AntMediaApplicationAdapter.getRelativePath(path);
VoD newVod = new VoD(fileName, "file", relativePath, fileName, unixTime, 0, Muxer.getDurationInMs(savedFile,fileName), fileSize,
VoD.UPLOADED_VOD, vodId, null);
if (StringUtils.isNotBlank(vodUploadFinishScript)) {
newVod.setProcessStatus(VoD.PROCESS_STATUS_INQUEUE);
}
id = getDataStore().addVod(newVod);
if(id != null) {
success = true;
message = id;
if (StringUtils.isNotBlank(vodUploadFinishScript))
{
startVoDScriptProcess(vodUploadFinishScript, savedFile, newVod, id);
}
}
}
}
}
else {
//this message has been used in the frontend(webpanel) pay attention
message = "notSupportedFileType";
}
}
catch (IOException iox) {
logger.error(iox.getMessage());
}
return new Result(success, id, message);
}
public void startVoDScriptProcess(String vodUploadFinishScript, File savedFile, VoD newVod, String vodId) {
new Thread() {
public void run()
{
long startTime = System.currentTimeMillis();
getDataStore().updateVoDProcessStatus(vodId, VoD.PROCESS_STATUS_PROCESSING);
String command = vodUploadFinishScript + " " + savedFile.getAbsolutePath();
try {
Process exec = getProcess(command);
int waitFor = exec.waitFor();
long endTime = System.currentTimeMillis();
long durationMs = endTime - startTime;
if (waitFor == 0)
{
logger.info("VoD file is processed successfully for Id:{} and name:{} in {}ms", vodId, newVod.getVodName(), durationMs);
getDataStore().updateVoDProcessStatus(vodId, VoD.PROCESS_STATUS_FINISHED);
}
else
{
logger.error("VoD file could not be processed for Id:{} and name:{} in {}ms", vodId, newVod.getVodName(), durationMs);
getDataStore().updateVoDProcessStatus(vodId, VoD.PROCESS_STATUS_FAILED);
}
} catch (IOException e) {
logger.error(ExceptionUtils.getStackTrace(e));
} catch (InterruptedException e) {
logger.error(ExceptionUtils.getStackTrace(e));
Thread.currentThread().interrupt();
}
}
}.start();
}
public Process getProcess(String command) throws IOException {
return Runtime.getRuntime().exec(command);
}
protected Result synchUserVodList() {
boolean result = false;
int errorId = -1;
String message = "";
String vodFolder = getAppSettings().getVodFolder();
logger.info("synch user vod list vod folder is {}", vodFolder);
if (vodFolder != null && vodFolder.length() > 0) {
result = getApplication().synchUserVoDFolder(null, vodFolder);
}
else {
errorId = 404;
message = "no VodD folder defined";
}
return new Result(result, message, errorId);
}
public MuxAdaptor getMuxAdaptor(String streamId)
{
AntMediaApplicationAdapter application = getApplication();
MuxAdaptor selectedMuxAdaptor = null;
if(application != null)
{
selectedMuxAdaptor = application.getMuxAdaptor(streamId);
}
return selectedMuxAdaptor;
}
@Nullable
protected Mp4Muxer getMp4Muxer(MuxAdaptor muxAdaptor) {
Mp4Muxer mp4Muxer = null;
for (Muxer muxer : muxAdaptor.getMuxerList()) {
if (muxer instanceof Mp4Muxer) {
mp4Muxer = (Mp4Muxer) muxer;
}
}
return mp4Muxer;
}
protected RecordMuxer startRecord(String streamId, RecordType recordType, int resolutionHeight) {
MuxAdaptor muxAdaptor = getMuxAdaptor(streamId);
if (muxAdaptor != null)
{
return muxAdaptor.startRecording(recordType, resolutionHeight);
}
else {
logger.info("No mux adaptor found for {} recordType:{} resolutionHeight:{}", streamId != null ?
streamId.replaceAll(REPLACE_CHARS_FOR_SECURITY, "_") : "null ",
recordType, resolutionHeight);
}
return null;
}
/**
*
* @param streamId
* @param recordType
* @param resolutionHeight
* @return
*/
protected @Nullable RecordMuxer stopRecord(String streamId, RecordType recordType, int resolutionHeight)
{
MuxAdaptor muxAdaptor = getMuxAdaptor(streamId);
if (muxAdaptor != null)
{
return muxAdaptor.stopRecording(recordType, resolutionHeight);
}
return null;
}
protected BroadcastStatistics getBroadcastStatistics(String id) {
int totalRTMPViewer = -1;
int totalWebRTCViewer = -1;
int totalHLSViewer = -1;
int totalDASHViewer = -1;
if (id != null)
{
IBroadcastScope broadcastScope = getScope().getBroadcastScope(id);
if (broadcastScope != null) {
totalRTMPViewer = broadcastScope.getConsumers().size();
}
Broadcast broadcast = getDataStore().get(id);
if (broadcast != null) {
totalHLSViewer = broadcast.getHlsViewerCount();
totalDASHViewer = broadcast.getDashViewerCount();
totalWebRTCViewer = broadcast.getWebRTCViewerCount();
}
}
return new BroadcastStatistics(totalRTMPViewer, totalHLSViewer, totalWebRTCViewer,totalDASHViewer);
}
protected AppBroadcastStatistics getBroadcastTotalStatistics() {
int totalWebRTCViewer = -1;
int totalHLSViewer = -1;
int totalDASHViewer = -1;
if (getAppContext().containsBean(HlsViewerStats.BEAN_NAME)) {
HlsViewerStats hlsViewerStats = (HlsViewerStats) getAppContext().getBean(HlsViewerStats.BEAN_NAME);
totalHLSViewer = hlsViewerStats.getTotalViewerCount();
}
if (getAppContext().containsBean(DashViewerStats.BEAN_NAME)) {
DashViewerStats dashViewerStats = (DashViewerStats) getAppContext().getBean(DashViewerStats.BEAN_NAME);
totalDASHViewer = dashViewerStats.getTotalViewerCount();
}
IWebRTCAdaptor webRTCAdaptor = getWebRTCAdaptor();
if(webRTCAdaptor != null) {
totalWebRTCViewer = webRTCAdaptor.getNumberOfTotalViewers();
}
int activeBroadcastCount = (int)getDataStore().getActiveBroadcastCount();
return new AppBroadcastStatistics(-1, totalHLSViewer, totalWebRTCViewer, totalDASHViewer, activeBroadcastCount);
}
protected Result getCameraErrorById(String streamId) {
Result result = new Result(false);
if (streamId != null) {
StreamFetcher camScheduler = getApplication().getStreamFetcherManager().getStreamFetcher(streamId);
if (camScheduler != null) {
result = camScheduler.getCameraError();
}
else {
result.setMessage("Camera is not found with streamId: " + streamId);
}
}
else {
result.setMessage("StreamId parameter is " + streamId + " Please use none null values");
}
return result;
}
public Result startStreamSource(String id)
{
Result result = new Result(false);
Broadcast broadcast = getDataStore().get(id);
if (broadcast != null)
{
if(broadcast.getStreamUrl() != null || Objects.equals(broadcast.getType(), AntMediaApplicationAdapter.PLAY_LIST))
{
result = getApplication().startStreaming(broadcast);
}
else if (Objects.equals(broadcast.getType(), AntMediaApplicationAdapter.IP_CAMERA))
{
//if streamURL is not defined before for IP Camera, connect to it again and define streamURL
result = connectToCamera(broadcast.getIpAddr(), broadcast.getUsername(), broadcast.getPassword());
if (result.isSuccess())
{
String authparam = broadcast.getUsername() + ":" + broadcast.getPassword() + "@";
String rtspURLWithAuth = RTSP + authparam + result.getMessage().substring(RTSP.length());
logger.info("rtsp url with auth: {}", rtspURLWithAuth);
broadcast.setStreamUrl(rtspURLWithAuth);
result = getApplication().startStreaming(broadcast);
}
}
else {
result.setMessage("Stream url is null and it's not an IP camera to get stream url for id:" + id);
}
}
else {
result.setMessage("No Stream Exists with id:"+id);
}
return result;
}
public Result playNextItem(String id, Integer index) {
Result result = new Result(false);
Broadcast broadcast = getDataStore().get(id);
if(broadcast == null) {
result.setMessage("There is no playlist found. Please check Stream id again");
return result;
}
else if (!AntMediaApplicationAdapter.PLAY_LIST.equals(broadcast.getType())) {
result.setMessage("This broadcast type is not playlist. This method is only available for playlists");
return result;
}
if(index == null) {
index = -1;
}
if (index < broadcast.getPlayListItemList().size())
{
StreamFetcher streamFetcher = getApplication().getStreamFetcherManager().getStreamFetcher(id);
if (streamFetcher != null)
{
IStreamFetcherListener streamFetcherListener = streamFetcher.getStreamFetcherListener();
//don't let the streamFetcherListener be called again because it already automatically plays the next item
streamFetcher.setStreamFetcherListener(null);
if (logger.isInfoEnabled()) {
logger.info("Switching to next item by REST method for playlist:{} and forwarding stream fetcher listener:{}", id.replaceAll(REPLACE_CHARS_FOR_SECURITY, "_"), streamFetcherListener.hashCode());
}
result = getApplication().getStreamFetcherManager().playItemInList(broadcast, streamFetcherListener, index);
}
else {
result.setMessage("No active playlist for id:" + id + ". Start the playlist first");
}
}
else {
result.setMessage("Index is out of the list. Please specify the correct index");
}
return result;
}
public Result stopStreaming(String id)
{
Result result = new Result(false);
Broadcast broadcast = getDataStore().get(id);
if(broadcast != null) {
result = getApplication().stopStreaming(broadcast);
}
return result;
}
protected String[] searchOnvifDevices() {
String localIP = null;
String[] list = null;
Enumeration interfaces = null;
try {
interfaces = NetworkInterface.getNetworkInterfaces();
} catch (SocketException e) {
// handle error
}
if (interfaces != null) {
while (interfaces.hasMoreElements()) {
NetworkInterface i = interfaces.nextElement();
Enumeration addresses = i.getInetAddresses();
while (addresses.hasMoreElements() && (localIP == null || localIP.isEmpty())) {
InetAddress address = addresses.nextElement();
if (!address.isLoopbackAddress() && address.isSiteLocalAddress()) {
localIP = address.getHostAddress();
}
}
}
logger.info("IP Address: {} " , localIP);
}
if (localIP != null) {
String[] ipAddrParts = localIP.split("\\.");
String ipAd = ipAddrParts[0] + "." + ipAddrParts[1] + "." + ipAddrParts[2] + ".";
ArrayList addressList = new ArrayList<>();
for (int i = 2; i < 255; i++) {
addressList.add(ipAd + i);
}
List onvifDevices = OnvifDiscovery.discoverOnvifDevices(true, addressList);
list = getIPArray(onvifDevices);
}
return list;
}
protected String[] getOnvifDeviceProfiles(String id) {
OnvifCamera camera = getApplication().getOnvifCamera(id);
return camera.getProfiles();
}
public String[] getIPArray(List onvifDevices) {
String[] list = null;
if (onvifDevices != null)
{
list = new String[onvifDevices.size()];
for (int i = 0; i < onvifDevices.size(); i++) {
list[i] = StringUtils.substringBetween(onvifDevices.get(i).toString(), HTTP, "/");
logger.info("IP Camera found: {}", onvifDevices.get(i));
}
}
return list;
}
protected boolean moveRelative(String id, float valueX, float valueY, float valueZoom) {
boolean result = false;
OnvifCamera camera = getApplication().getOnvifCamera(id);
if (camera != null) {
result = camera.moveRelative(valueX, valueY, valueZoom);
}
return result;
}
protected boolean moveAbsolute(String id, float valueX, float valueY, float valueZoom) {
boolean result = false;
OnvifCamera camera = getApplication().getOnvifCamera(id);
if (camera != null) {
result = camera.moveAbsolute(valueX, valueY, valueZoom);
}
return result;
}
protected boolean moveContinous(String id, float valueX, float valueY, float valueZoom) {
boolean result = false;
OnvifCamera camera = getApplication().getOnvifCamera(id);
if (camera != null) {
result = camera.moveContinous(valueX, valueY, valueZoom);
}
return result;
}
protected List getDetectionList(String id, int offset, int size) {
List list = null;
if (id != null) {
list = getDataStore().getDetectionList(id, offset, size);
}
if (list == null) {
//do not return null in rest service
list = new ArrayList<>();
}
return list;
}
protected ITokenService getTokenService()
{
ApplicationContext appContext = getAppContext();
if(appContext != null && appContext.containsBean(ITokenService.BeanName.TOKEN_SERVICE.toString())) {
return (ITokenService)appContext.getBean(ITokenService.BeanName.TOKEN_SERVICE.toString());
}
return null;
}
protected Object getToken (String streamId, long expireDate, String type, String roomId)
{
Token token = null;
String message = "Define Stream ID, Token Type and Expire Date (unix time)";
if(streamId != null && type != null && expireDate > 0) {
ITokenService tokenService = getTokenService();
if(tokenService != null)
{
token = tokenService.createToken(streamId, expireDate, type, roomId);
if(token != null)
{
if (getDataStore().saveToken(token)) {
//returns token only everything is OK
return token;
}
else {
message = "Cannot save token to the datastore";
}
}
else {
message = "Cannot create token. It can be a mock token service";
}
}
else {
message = "No token service in this app";
}
}
return new Result(false, message);
}
protected Object getJwtToken (String streamId, long expireDate, String type, String roomId)
{
Token token = null;
String message = "Define Stream ID, Token Type and Expire Date (unix time)";
if(streamId != null && type != null && expireDate > 0) {
ITokenService tokenService = getTokenService();
if(tokenService != null)
{
token = tokenService.createJwtToken(streamId, expireDate, type, roomId);
if(token != null)
{
return token;
}
else {
message = "Cannot create JWT token. The problem can be -> this is community edition or JWT stream key is not set or it's length is less than 32";
}
}
else {
message = "No token service in this app";
}
}
return new Result(false, message);
}
protected Token validateToken (Token token) {
Token validatedToken = null;
if(token.getTokenId() != null) {
validatedToken = getDataStore().validateToken(token);
}
return validatedToken;
}
protected Result revokeTokens (String streamId) {
Result result = new Result(false);
if(streamId != null) {
result.setSuccess(getDataStore().revokeTokens(streamId));
}
return result;
}
protected VoD getVoD(String id) {
VoD vod = null;
if (id != null) {
vod = getDataStore().getVoD(id);
}
if (vod == null) {
vod = new VoD();
}
return vod;
}
public static Version getSoftwareVersion() {
Version version = new Version();
version.setVersionName(AntMediaApplicationAdapter.class.getPackage().getImplementationVersion());
URL url = null;
Class clazz = RestServiceBase.class;
String className = clazz.getSimpleName() + ".class";
String classPath = clazz.getResource(className).toString();
String manifestPath = classPath.substring(0, classPath.lastIndexOf("!") + 1) +
"/META-INF/MANIFEST.MF";
try {
url = new URL(manifestPath);
} catch (MalformedURLException e) {
logger.error(e.getMessage());
}
Manifest manifest;
try {
if (url != null)
{
manifest = new Manifest(url.openStream());
version.setBuildNumber(manifest.getMainAttributes().getValue(RestServiceBase.BUILD_NUMBER));
}
else {
logger.error("url(META-INF/MANIFEST.MF) is null when getting software version");
}
} catch (IOException e) {
logger.error(e.getMessage());
}
version.setVersionType(isEnterprise() ? RestServiceBase.ENTERPRISE_EDITION : RestServiceBase.COMMUNITY_EDITION);
logger.info("Version Name {} Version Type {}", version.getVersionName(), version.getVersionType());
return version;
}
public static boolean isEnterprise() {
try {
Class.forName("io.antmedia.enterprise.adaptive.EncoderAdaptor");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
/**
* Get the active streams in the room
*
* @param roomId: It's the id of the room
* @param streamId: The id of the room to be extracted from the list. It's generally the publisher stream id in websocket communication
* @param store: Datastore object to run the query
*
* @return null if there is no room recorded in the database, returns map filled with the active streams. Key is the streamId, value is the name
*/
@Deprecated(forRemoval = true, since = "2.9.1")
public static Map getRoomInfoFromConference(Broadcast broadcastRoom, String streamId, DataStore store){
HashMap streamDetailsMap = null;
if (broadcastRoom != null)
{
streamDetailsMap = new HashMap<>();
List tempList = broadcastRoom.getSubTrackStreamIds();
if(tempList != null) {
for (String tmpStreamId : tempList)
{
Broadcast broadcast = store.get(tmpStreamId);
if (broadcast != null && broadcast.getStatus().equals(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING))
{
streamDetailsMap.put(tmpStreamId, broadcast.getName());
}
}
//remove the itself from the streamDetailsMap
streamDetailsMap.remove(streamId);
}
}
return streamDetailsMap;
}
public static void setResultSuccess(Result result, boolean success, String failMessage)
{
if (success) {
result.setSuccess(true);
}
else {
result.setSuccess(false);
result.setMessage(failMessage);
}
}
public static void logWarning(String message, String... arguments) {
if (logger.isWarnEnabled()) {
logger.warn(message , arguments);
}
}
public static Result addSubTrack(String id, String subTrackId, DataStore store)
{
Result result = new Result(false);
Broadcast subTrack = store.get(subTrackId);
Broadcast mainTrack = store.get(id);
String message = "";
if (subTrack != null && mainTrack != null)
{
int subtrackLimit = mainTrack.getSubtracksLimit();
List subTrackStreamIds = mainTrack.getSubTrackStreamIds();
if (subtrackLimit != -1 && store.getActiveSubtracksCount(id, null) >= subtrackLimit)
{
message = "Subtrack limit is reached for the main track:" + id;
logWarning("Subtrack limit is reached for the main track:{}", id.replaceAll(REPLACE_CHARS, "_"));
result.setMessage(message);
return result;
}
if (subTrackStreamIds == null) {
subTrackStreamIds = new ArrayList<>();
}
subTrack.setMainTrackStreamId(id);
//Update subtrack's main Track Id
BroadcastUpdate broadcastUpdate = new BroadcastUpdate();
broadcastUpdate.setMainTrackStreamId(id);
boolean success = store.updateBroadcastFields(subTrackId, broadcastUpdate);
if (success)
{
subTrackStreamIds.add(subTrackId);
broadcastUpdate = new BroadcastUpdate();
broadcastUpdate.setSubTrackStreamIds(subTrackStreamIds);
success = store.updateBroadcastFields(id, broadcastUpdate);
RestServiceBase.setResultSuccess(result, success, "Subtrack:" + subTrackId + " cannot be added to main track: " + id);
}
else
{
message = MAIN_TRACK_OF_THE_STREAM + subTrackId + " cannot be updated";
logWarning(MAIN_TRACK_OF_THE_STREAM +":{} cannot be updated to {}", subTrackId.replaceAll(REPLACE_CHARS, "_"), id.replaceAll(REPLACE_CHARS, "_"));
}
}
else
{
message = "There is no stream with id:" + subTrackId + " as subtrack or " + id + " as mainTrack";
logWarning("There is no stream with id:{} as subtrack or {} as mainTrack" , subTrackId.replaceAll(REPLACE_CHARS, "_"), id.replaceAll(REPLACE_CHARS, "_"));
}
result.setMessage(message);
return result;
}
public static Result removeSubTrack(String id, String subTrackId, DataStore store) {
Result result = new Result(false);
if (StringUtils.isNoneBlank(id, subTrackId))
{
subTrackId = subTrackId.replaceAll(REPLACE_CHARS, "_");
id = id.replaceAll(REPLACE_CHARS, "_");
boolean success = store.removeSubTrack(id, subTrackId);
if (success )
{
Broadcast subTrack = store.get(subTrackId);
if(subTrack != null && id.equals(subTrack.getMainTrackStreamId())) {
BroadcastUpdate broadcastUpdate = new BroadcastUpdate();
broadcastUpdate.setMainTrackStreamId("");
success = store.updateBroadcastFields(subTrackId, broadcastUpdate);
if (success)
{
RestServiceBase.setResultSuccess(result, success, "");
}
else
{
RestServiceBase.setResultSuccess(result, false, MAIN_TRACK_OF_THE_STREAM + subTrackId + " which is " + id +" cannot be removed");
logger.info(MAIN_TRACK_OF_THE_STREAM +" {} which is {} cannot be removed", subTrackId, id);
}
}
else {
RestServiceBase.setResultSuccess(result, false, MAIN_TRACK_OF_THE_STREAM + subTrackId + " which is " + id +" cannot be updated");
logger.info( MAIN_TRACK_OF_THE_STREAM +"{} which is {} not updated because either subtrack is null or its maintrack does not match with mainTrackId:{}", subTrackId, id, id);
}
}
else
{
RestServiceBase.setResultSuccess(result, false, "Subtrack(" + subTrackId + ") is not removed from mainTrack:" + id);
logger.info("Subtrack({}) is not removed from mainTrack:{}", subTrackId, id);
}
}
return result;
}
public static boolean isMainTrack(String streamId, DataStore store) {
boolean result = false;
if (streamId != null)
{
return store.hasSubtracks(streamId);
}
return result;
}
public static Result sendDataChannelMessage(String id, String message, AntMediaApplicationAdapter application, DataStore store)
{
// check if WebRTC data channels are supported in this edition
if(application != null && application.isDataChannelMessagingSupported()) {
// check if data channel is enabled in the settings
if(application.isDataChannelEnabled()) {
// check if stream with given stream id exists
if(application.doesWebRTCStreamExist(id) || RestServiceBase.isMainTrack(id, store)) {
// send the message through the application
boolean status = application.sendDataChannelMessage(id,message);
if(status) {
return new Result(true);
} else {
return new Result(false, "Operation not completed");
}
} else {
return new Result(false, "Requested WebRTC stream does not exist");
}
} else {
return new Result(false, "Data channels are not enabled");
}
} else {
return new Result(false, "Operation not supported in the Community Edition. Check the Enterprise version for more features.");
}
}
public static String logFailedOperation(boolean enableRecording,String streamId,RecordType type){
String id = streamId.replaceAll(REPLACE_CHARS, "_");
if (enableRecording)
{
logger.warn("{} recording could not be started for stream: {}", type,id);
}
else
{
logger.warn("{} recording could not be stopped for stream: {}",type, id);
}
return id;
}
public Result enableRecordMuxing(String streamId, boolean enableRecording, String type, int resolutionHeight)
{
boolean result = false;
String message = null;
String status = (enableRecording)?"started":"stopped";
String vodId = null;
RecordType recordType = null;
//type cannot be null
if (type.equals(RecordType.MP4.toString()))
{
recordType = RecordType.MP4;
}
else if (type.equals(RecordType.WEBM.toString()))
{
recordType = RecordType.WEBM;
}
if (streamId != null && recordType != null)
{
Broadcast broadcast = getDataStore().get(streamId);
if (broadcast != null)
{
if(!broadcast.getStatus().equals(IAntMediaStreamHandler.BROADCAST_STATUS_BROADCASTING))
{
if(recordType == RecordType.MP4) {
broadcast.setMp4Enabled(enableRecording ? RECORD_ENABLE : RECORD_DISABLE);
}
else {
broadcast.setWebMEnabled(enableRecording ? RECORD_ENABLE : RECORD_DISABLE);
}
result = true;
}
else {
boolean isAlreadyRecording = isAlreadyRecording(streamId, recordType, resolutionHeight);
//start recording and there is no active recording or stop recording and there is active recording
if (enableRecording != isAlreadyRecording)
{
result = true;
RecordMuxer muxer = null;
if (isInSameNodeInCluster(broadcast.getOriginAdress()))
{
if (enableRecording)
{
muxer = startRecord(streamId, recordType, resolutionHeight);
if (muxer != null) {
vodId = RandomStringUtils.randomAlphanumeric(24);
muxer.setVodId(vodId);
message = Long.toString(muxer.getCurrentVoDTimeStamp());
logger.warn("{} recording is {} for stream: {}", type,status,streamId);
}
}
else
{
muxer = stopRecord(streamId, recordType, resolutionHeight);
if (muxer != null) {
vodId = muxer.getVodId();
message = Long.toString(muxer.getCurrentVoDTimeStamp());
}
}
//Check process status result
if (muxer == null)
{
result = false;
logFailedOperation(enableRecording, streamId, recordType);
message= recordType +" recording couldn't be " + status;
}
}
else
{
message="Please send " + type + " recording request to " + broadcast.getOriginAdress() + " node or send request in a stopped status.";
result = false;
}
}
else {
if(enableRecording) {
message = type+" recording couldn't be started";
}
else {
message = type+" recording couldn't be stopped";
}
result = false;
}
}
// If record process works well then change record status in DB
if (result)
{
if (recordType == RecordType.WEBM)
{
result = getDataStore().setWebMMuxing(streamId, enableRecording ? RECORD_ENABLE : RECORD_DISABLE);
}
else if (recordType == RecordType.MP4)
{
result = getDataStore().setMp4Muxing(streamId, enableRecording ? RECORD_ENABLE : RECORD_DISABLE);
}
}
}
}
else
{
message = "No stream for this id: " + streamId + " or unexpected record type. Record type is "+ recordType;
}
return new Result(result, vodId, message);
}
public boolean isAlreadyRecording(String streamId, RecordType recordType, int resolutionHeight) {
MuxAdaptor muxAdaptor = getMuxAdaptor(streamId);
return muxAdaptor != null && muxAdaptor.isAlreadyRecording(recordType, resolutionHeight);
}
public Result importVoDs(String directory) {
return getApplication().importVoDFolder(directory);
}
public Result unlinksVoD(String directory) {
return getApplication().unlinksVoD(directory);
}
public static String replaceCharsForSecurity(String value) {
return value.replaceAll(REPLACE_CHARS_FOR_SECURITY, "_");
}
}