Merge branch 'refs/heads/master' into develop-add-api-key

This commit is contained in:
leesam
2024-04-10 20:49:44 +08:00
124 changed files with 4940 additions and 3762 deletions

View File

@@ -1,8 +1,7 @@
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.github.pagehelper.PageInfo;
@@ -18,22 +17,17 @@ public interface ICloudRecordService {
/**
* 分页回去云端录像列表
*/
PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems);
/**
* 根据hook消息增加一条记录
*/
void addRecord(OnRecordMp4HookParam param);
PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems);
/**
* 获取所有的日期
*/
List<String> getDateList(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems);
List<String> getDateList(String app, String stream, int year, int month, List<MediaServer> mediaServerItems);
/**
* 添加合并任务
*/
String addTask(String app, String stream, MediaServerItem mediaServerItem, String startTime,
String addTask(String app, String stream, MediaServer mediaServerItem, String startTime,
String endTime, String callId, String remoteHost, boolean filterMediaServer);

View File

@@ -1,100 +0,0 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.media.zlm.ZLMServerConfig;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.vmanager.bean.RecordFile;
import java.util.List;
/**
* 媒体服务节点
*/
public interface IMediaServerService {
List<MediaServerItem> getAll();
List<MediaServerItem> getAllFromDatabase();
List<MediaServerItem> getAllOnline();
MediaServerItem getOne(String generalMediaServerId);
void syncCatchFromDatabase();
/**
* 新的节点加入
* @param zlmServerConfig
* @return
*/
void zlmServerOnline(ZLMServerConfig zlmServerConfig);
/**
* 节点离线
* @param mediaServerId
* @return
*/
void zlmServerOffline(String mediaServerId);
MediaServerItem getMediaServerForMinimumLoad(Boolean hasAssist);
void setZLMConfig(MediaServerItem mediaServerItem, boolean restart);
void updateVmServer(List<MediaServerItem> mediaServerItemList);
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode);
SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto);
void closeRTPServer(MediaServerItem mediaServerItem, String streamId);
void closeRTPServer(MediaServerItem mediaServerItem, String streamId, CommonCallback<Boolean> callback);
Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc);
void closeRTPServer(String mediaServerId, String streamId);
void clearRTPServer(MediaServerItem mediaServerItem);
void update(MediaServerItem mediaSerItem);
void addCount(String mediaServerId);
void removeCount(String mediaServerId);
void releaseSsrc(String mediaServerItemId, String ssrc);
void clearMediaServerForOnline();
void add(MediaServerItem mediaSerItem);
int addToDatabase(MediaServerItem mediaSerItem);
int updateToDatabase(MediaServerItem mediaSerItem);
void resetOnlineServerItem(MediaServerItem serverItem);
MediaServerItem checkMediaServer(String ip, int port, String secret);
boolean checkMediaRecordServer(String ip, int port);
void delete(String id);
void deleteDb(String id);
MediaServerItem getDefaultMediaServer();
void updateMediaServerKeepalive(String mediaServerId, ServerKeepaliveData data);
/**
* 获取负载信息
* @return
*/
MediaServerLoad getLoad(MediaServerItem mediaServerItem);
List<MediaServerItem> getAllWithAssistPort();
}

View File

@@ -1,8 +1,7 @@
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson2.JSONArray;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.bean.ResultForOnPublish;
import com.genersoft.iot.vmp.media.bean.MediaServer;
/**
* 媒体信息业务
@@ -10,35 +9,11 @@ import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
public interface IMediaService {
/**
* 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在
* @param app
* @param stream
* @return
* 播放鉴权
*/
StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId,String addr, boolean authority);
boolean authenticatePlay(String app, String stream, String callId);
ResultForOnPublish authenticatePublish(MediaServer mediaServer, String app, String stream, String params);
/**
* 根据应用名和流ID获取播放地址, 通过zlm接口检查是否存在, 返回的ip使用远程访问ip适用与zlm与wvp在一台主机的情况
* @param app
* @param stream
* @return
*/
StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, boolean authority);
/**
* 根据应用名和流ID获取播放地址, 只是地址拼接
* @param app
* @param stream
* @return
*/
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaServerItem, String app, String stream, Object tracks, String callId);
/**
* 根据应用名和流ID获取播放地址, 只是地址拼接返回的ip使用远程访问ip适用与zlm与wvp在一台主机的情况
* @param app
* @param stream
* @return
*/
StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay);
boolean closeStreamOnNoneReader(String mediaServerId, String app, String stream, String schema);
}

View File

@@ -3,8 +3,8 @@ package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.InviteTimeOutCallback;
import com.genersoft.iot.vmp.gb28181.bean.SipTransactionInfo;
import com.github.pagehelper.PageInfo;
@@ -73,13 +73,13 @@ public interface IPlatformService {
* @param errorEvent 信令错误事件
* @param timeoutCallback 超时事件
*/
void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
void broadcastInvite(ParentPlatform platform, String channelId, MediaServer mediaServerItem, HookSubscribe.Event hookEvent,
SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException;
/**
* 语音喊话回复BYE
*/
void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream,boolean sendBye, MediaServerItem mediaServerItem);
void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream,boolean sendBye, MediaServer mediaServerItem);
void addSimulatedSubscribeInfo(ParentPlatform parentPlatform);
}

View File

@@ -7,8 +7,8 @@ import com.genersoft.iot.vmp.gb28181.bean.Device;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.bean.DeviceChannel;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.vmanager.bean.AudioBroadcastResult;
@@ -26,20 +26,20 @@ import java.util.Map;
*/
public interface IPlayService {
void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channelId,
void play(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channelId,
ErrorCallback<Object> callback);
SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback);
SSRCInfo play(MediaServer mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback);
StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId);
StreamInfo onPublishHandlerForPlay(MediaServer mediaServerItem, MediaInfo mediaInfo, String deviceId, String channelId);
MediaServerItem getNewMediaServerItem(Device device);
MediaServer getNewMediaServerItem(Device device);
void playBack(String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
void playBack(MediaServer mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, ErrorCallback<Object> callback);
void zlmServerOffline(String mediaServerId);
void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback);
void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback);
void download(MediaServer mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback);
StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream);
@@ -47,7 +47,7 @@ public interface IPlayService {
AudioBroadcastResult audioBroadcast(Device device, String channelId, Boolean broadcastMode);
boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
boolean audioBroadcastCmd(Device device, String channelId, MediaServer mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException;
boolean audioBroadcastInUse(Device device, String channelId);
@@ -59,10 +59,9 @@ public interface IPlayService {
void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader);
void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo,
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader);
void startSendRtpStreamFailHand(SendRtpItem sendRtpItem,ParentPlatform platform, CallIdHeader callIdHeader);
void talkCmd(Device device, String channelId, MediaServerItem mediaServerItem, String stream, AudioBroadcastEvent event);
void talkCmd(Device device, String channelId, MediaServer mediaServerItem, String stream, AudioBroadcastEvent event);
void stopTalk(Device device, String channelId, Boolean streamIsReady);

View File

@@ -1,13 +1,15 @@
package com.genersoft.iot.vmp.service;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.GeneralCallback;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import java.util.Map;
public interface IStreamProxyService {
/**
@@ -18,17 +20,19 @@ public interface IStreamProxyService {
/**
* 添加视频代理到zlm
*
* @param param
* @return
*/
JSONObject addStreamProxyToZlm(StreamProxyItem param);
WVPResult<String> addStreamProxyToZlm(StreamProxyItem param);
/**
* 从zlm移除视频代理
*
* @param param
* @return
*/
JSONObject removeStreamProxyFromZlm(StreamProxyItem param);
Boolean removeStreamProxyFromZlm(StreamProxyItem param);
/**
* 分页查询
@@ -73,9 +77,10 @@ public interface IStreamProxyService {
/**
* 获取ffmpeg.cmd模板
*
* @return
*/
JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem);
Map<String, String> getFFmpegCMDs(MediaServer mediaServerItem);
/**
* 根据app与stream获取streamProxy

View File

@@ -1,9 +1,8 @@
package com.genersoft.iot.vmp.service;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.service.bean.StreamPushItemFromRedis;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
import com.github.pagehelper.PageInfo;
@@ -16,8 +15,6 @@ import java.util.Map;
*/
public interface IStreamPushService {
List<StreamPushItem> handleJSON(String json, MediaServerItem mediaServerItem);
/**
* 将应用名和流ID加入国标关联
* @param stream

View File

@@ -1,5 +1,6 @@
package com.genersoft.iot.vmp.service.bean;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
/**
@@ -76,18 +77,18 @@ public class CloudRecordItem {
*/
private long timeLen;
public static CloudRecordItem getInstance(OnRecordMp4HookParam param) {
public static CloudRecordItem getInstance(MediaRecordMp4Event param) {
CloudRecordItem cloudRecordItem = new CloudRecordItem();
cloudRecordItem.setApp(param.getApp());
cloudRecordItem.setStream(param.getStream());
cloudRecordItem.setStartTime(param.getStart_time()*1000);
cloudRecordItem.setFileName(param.getFile_name());
cloudRecordItem.setFolder(param.getFolder());
cloudRecordItem.setFileSize(param.getFile_size());
cloudRecordItem.setFilePath(param.getFile_path());
cloudRecordItem.setMediaServerId(param.getMediaServerId());
cloudRecordItem.setTimeLen((long) param.getTime_len() * 1000);
cloudRecordItem.setEndTime((param.getStart_time() + (long)param.getTime_len()) * 1000);
cloudRecordItem.setStartTime(param.getRecordInfo().getStartTime()*1000);
cloudRecordItem.setFileName(param.getRecordInfo().getFileName());
cloudRecordItem.setFolder(param.getRecordInfo().getFolder());
cloudRecordItem.setFileSize(param.getRecordInfo().getFileSize());
cloudRecordItem.setFilePath(param.getRecordInfo().getFilePath());
cloudRecordItem.setMediaServerId(param.getMediaServer().getId());
cloudRecordItem.setTimeLen((long) param.getRecordInfo().getTimeLen() * 1000);
cloudRecordItem.setEndTime((param.getRecordInfo().getStartTime() + (long)param.getRecordInfo().getTimeLen()) * 1000);
return cloudRecordItem;
}

View File

@@ -2,7 +2,7 @@ package com.genersoft.iot.vmp.service.bean;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import java.util.EventObject;
@@ -15,7 +15,7 @@ public class PlayBackResult<T> {
private String msg;
private T data;
private MediaServerItem mediaServerItem;
private MediaServer mediaServerItem;
private JSONObject response;
private SipSubscribe.EventResult<EventObject> event;
@@ -35,11 +35,11 @@ public class PlayBackResult<T> {
this.data = data;
}
public MediaServerItem getMediaServerItem() {
public MediaServer getMediaServerItem() {
return mediaServerItem;
}
public void setMediaServerItem(MediaServerItem mediaServerItem) {
public void setMediaServerItem(MediaServer mediaServerItem) {
this.mediaServerItem = mediaServerItem;
}

View File

@@ -1,7 +1,7 @@
package com.genersoft.iot.vmp.service.bean;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.bean.MediaServer;
/**
* redis消息下级回复推送信息
@@ -11,7 +11,7 @@ public class ResponseSendItemMsg {
private SendRtpItem sendRtpItem;
private MediaServerItem mediaServerItem;
private MediaServer mediaServerItem;
public SendRtpItem getSendRtpItem() {
return sendRtpItem;
@@ -21,11 +21,11 @@ public class ResponseSendItemMsg {
this.sendRtpItem = sendRtpItem;
}
public MediaServerItem getMediaServerItem() {
public MediaServer getMediaServerItem() {
return mediaServerItem;
}
public void setMediaServerItem(MediaServerItem mediaServerItem) {
public void setMediaServerItem(MediaServer mediaServerItem) {
this.mediaServerItem = mediaServerItem;
}
}

View File

@@ -5,12 +5,12 @@ import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.media.event.media.MediaRecordMp4Event;
import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.service.ICloudRecordService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.CloudRecordItem;
import com.genersoft.iot.vmp.service.bean.DownloadFileInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -24,6 +24,8 @@ import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.*;
@@ -51,7 +53,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
private VideoStreamSessionManager streamSession;
@Override
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServerItem> mediaServerItems) {
public PageInfo<CloudRecordItem> getList(int page, int count, String query, String app, String stream, String startTime, String endTime, List<MediaServer> mediaServerItems) {
// 开始时间和结束时间在数据库中都是以秒为单位的
Long startTimeStamp = null;
Long endTimeStamp = null;
@@ -76,7 +78,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
@Override
public List<String> getDateList(String app, String stream, int year, int month, List<MediaServerItem> mediaServerItems) {
public List<String> getDateList(String app, String stream, int year, int month, List<MediaServer> mediaServerItems) {
LocalDate startDate = LocalDate.of(year, month, 1);
LocalDate endDate;
if (month == 12) {
@@ -99,19 +101,20 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
return new ArrayList<>(resultSet);
}
@Override
public void addRecord(OnRecordMp4HookParam param) {
CloudRecordItem cloudRecordItem = CloudRecordItem.getInstance(param);
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(param.getApp(), param.getStream());
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaRecordMp4Event event) {
CloudRecordItem cloudRecordItem = CloudRecordItem.getInstance(event);
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(event.getApp(), event.getStream());
if (streamAuthorityInfo != null) {
cloudRecordItem.setCallId(streamAuthorityInfo.getCallId());
}
logger.info("[添加录像记录] {}/{} 文件大小:{}, 时长: {}", param.getApp(), param.getStream(), param.getFile_size(),param.getTime_len());
logger.info("[添加录像记录] {}/{} 内容:{}", event.getApp(), event.getStream(), event.getRecordInfo());
cloudRecordServiceMapper.add(cloudRecordItem);
}
@Override
public String addTask(String app, String stream, MediaServerItem mediaServerItem, String startTime, String endTime,
public String addTask(String app, String stream, MediaServer mediaServerItem, String startTime, String endTime,
String callId, String remoteHost, boolean filterMediaServer) {
// 参数校验
assert app != null;
@@ -128,7 +131,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
endTimeStamp = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
}
List<MediaServerItem> mediaServers = new ArrayList<>();
List<MediaServer> mediaServers = new ArrayList<>();
mediaServers.add(mediaServerItem);
// 检索相关的录像文件
List<String> filePathList = cloudRecordServiceMapper.queryRecordFilePathList(app, stream, startTimeStamp,
@@ -146,7 +149,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
@Override
public JSONArray queryTask(String app, String stream, String callId, String taskId, String mediaServerId,
Boolean isEnd, String scheme) {
MediaServerItem mediaServerItem = null;
MediaServer mediaServerItem = null;
if (mediaServerId == null) {
mediaServerItem = mediaServerService.getDefaultMediaServer();
}else {
@@ -183,10 +186,10 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
}
List<MediaServerItem> mediaServerItems;
List<MediaServer> mediaServerItems;
if (!ObjectUtils.isEmpty(mediaServerId)) {
mediaServerItems = new ArrayList<>();
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到流媒体: " + mediaServerId);
}
@@ -229,7 +232,7 @@ public class CloudRecordServiceImpl implements ICloudRecordService {
throw new ControllerException(ErrorCode.ERROR400.getCode(), "资源不存在");
}
String filePath = recordItem.getFilePath();
MediaServerItem mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId());
MediaServer mediaServerItem = mediaServerService.getOne(recordItem.getMediaServerId());
return CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
}
}

View File

@@ -14,12 +14,11 @@ import com.genersoft.iot.vmp.gb28181.task.impl.MobilePositionSubscribeTask;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.event.request.impl.message.response.cmd.CatalogResponseMessageHandler;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.IDeviceChannelService;
import com.genersoft.iot.vmp.service.IDeviceService;
import com.genersoft.iot.vmp.service.IInviteStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.DeviceChannelMapper;
import com.genersoft.iot.vmp.storager.dao.DeviceMapper;
@@ -54,6 +53,7 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired
private SIPCommander cmder;
@Autowired
private DynamicTask dynamicTask;
@@ -102,9 +102,6 @@ public class DeviceServiceImpl implements IDeviceService {
@Autowired
private AudioBroadcastManager audioBroadcastManager;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Override
public void online(Device device, SipTransactionInfo sipTransactionInfo) {
logger.info("[设备上线] deviceId{}->{}:{}", device.getDeviceId(), device.getIp(), device.getPort());
@@ -244,12 +241,8 @@ public class DeviceServiceImpl implements IDeviceService {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
if (sendRtpItem != null) {
redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
zlmresTfulUtils.stopSendRtp(mediaInfo, param);
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
mediaServerService.stopSendRtp(mediaInfo, sendRtpItem.getApp(), sendRtpItem.getStream(), null);
}
audioBroadcastManager.del(deviceId, audioBroadcastCatch.getChannelId());

View File

@@ -6,13 +6,18 @@ import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.InviteSessionStatus;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.service.IInviteStreamService;
import com.genersoft.iot.vmp.service.bean.ErrorCallback;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -31,6 +36,35 @@ public class InviteStreamServiceImpl implements IInviteStreamService {
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private IVideoManagerStorage storage;
/**
* 流到来的处理
*/
@Async("taskExecutor")
@org.springframework.context.event.EventListener
public void onApplicationEvent(MediaArrivalEvent event) {
// if ("rtsp".equals(event.getSchema()) && "rtp".equals(event.getApp())) {
//
// }
}
/**
* 流离开的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaDepartureEvent event) {
if ("rtsp".equals(event.getSchema()) && "rtp".equals(event.getApp())) {
InviteInfo inviteInfo = getInviteInfoByStream(null, event.getStream());
if (inviteInfo != null && (inviteInfo.getType() == InviteSessionType.PLAY || inviteInfo.getType() == InviteSessionType.PLAYBACK)) {
removeInviteInfo(inviteInfo);
storage.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
}
}
}
@Override
public void updateInviteInfo(InviteInfo inviteInfo) {
if (inviteInfo == null || (inviteInfo.getDeviceId() == null || inviteInfo.getChannelId() == null)) {

View File

@@ -1,756 +0,0 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.common.CommonCallback;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.media.zlm.*;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.ServerKeepaliveData;
import com.genersoft.iot.vmp.service.IInviteStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.MediaServerLoad;
import com.genersoft.iot.vmp.service.bean.SSRCInfo;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.MediaServerMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.utils.JsonUtil;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.RecordFile;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import java.io.File;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
/**
* 媒体服务器节点管理
*/
@Service
@DS("master")
public class MediaServerServiceImpl implements IMediaServerService {
private final static Logger logger = LoggerFactory.getLogger(MediaServerServiceImpl.class);
private final String zlmKeepaliveKeyPrefix = "zlm-keepalive_";
@Autowired
private SipConfig sipConfig;
@Autowired
private SSRCFactory ssrcFactory;
@Value("${server.ssl.enabled:false}")
private boolean sslEnabled;
@Value("${server.port}")
private Integer serverPort;
@Autowired
private UserSetting userSetting;
@Autowired
private SendRtpPortManager sendRtpPortManager;
@Autowired
private AssistRESTfulUtils assistRESTfulUtils;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired
private MediaServerMapper mediaServerMapper;
@Autowired
private DataSourceTransactionManager dataSourceTransactionManager;
@Autowired
private TransactionDefinition transactionDefinition;
@Autowired
private ZLMServerFactory zlmServerFactory;
@Autowired
private EventPublisher publisher;
@Autowired
private DynamicTask dynamicTask;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IInviteStreamService inviteStreamService;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
/**
* 初始化
*/
@Override
public void updateVmServer(List<MediaServerItem> mediaServerItemList) {
logger.info("[zlm] 缓存初始化 ");
for (MediaServerItem mediaServerItem : mediaServerItemList) {
if (ObjectUtils.isEmpty(mediaServerItem.getId())) {
continue;
}
// 更新
if (!ssrcFactory.hasMediaServerSSRC(mediaServerItem.getId())) {
ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null);
}
// 查询redis是否存在此mediaServer
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId();
Boolean hasKey = redisTemplate.hasKey(key);
if (hasKey != null && ! hasKey) {
redisTemplate.opsForValue().set(key, mediaServerItem);
}
}
}
@Override
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String presetSsrc, boolean ssrcCheck,
boolean isPlayback, Integer port, Boolean onlyAuto, Boolean reUsePort, Integer tcpMode) {
if (mediaServerItem == null || mediaServerItem.getId() == null) {
logger.info("[openRTPServer] 失败, mediaServerItem == null || mediaServerItem.getId() == null");
return null;
}
// 获取mediaServer可用的ssrc
String ssrc;
if (presetSsrc != null) {
ssrc = presetSsrc;
}else {
if (isPlayback) {
ssrc = ssrcFactory.getPlayBackSsrc(mediaServerItem.getId());
}else {
ssrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
}
}
if (streamId == null) {
streamId = String.format("%08x", Long.parseLong(ssrc)).toUpperCase();
}
if (ssrcCheck && tcpMode > 0) {
// 目前zlm不支持 tcp模式更新ssrc暂时关闭ssrc校验
logger.warn("[openRTPServer] 平台对接时下级可能自定义ssrc但是tcp模式zlm收流目前无法更新ssrc可能收流超时此时请使用udp收流或者关闭ssrc校验");
}
int rtpServerPort;
if (mediaServerItem.isRtpEnable()) {
rtpServerPort = zlmServerFactory.createRTPServer(mediaServerItem, streamId, ssrcCheck ? Long.parseLong(ssrc) : 0, port, onlyAuto, reUsePort, tcpMode);
} else {
rtpServerPort = mediaServerItem.getRtpProxyPort();
}
return new SSRCInfo(rtpServerPort, ssrc, streamId);
}
@Override
public SSRCInfo openRTPServer(MediaServerItem mediaServerItem, String streamId, String ssrc, boolean ssrcCheck, boolean isPlayback, Integer port, Boolean onlyAuto) {
return openRTPServer(mediaServerItem, streamId, ssrc, ssrcCheck, isPlayback, port, onlyAuto, null, 0);
}
@Override
public void closeRTPServer(MediaServerItem mediaServerItem, String streamId) {
if (mediaServerItem == null) {
return;
}
zlmServerFactory.closeRtpServer(mediaServerItem, streamId);
}
@Override
public void closeRTPServer(MediaServerItem mediaServerItem, String streamId, CommonCallback<Boolean> callback) {
if (mediaServerItem == null) {
callback.run(false);
return;
}
zlmServerFactory.closeRtpServer(mediaServerItem, streamId, callback);
}
@Override
public void closeRTPServer(String mediaServerId, String streamId) {
MediaServerItem mediaServerItem = this.getOne(mediaServerId);
if (mediaServerItem != null && mediaServerItem.isRtpEnable()) {
closeRTPServer(mediaServerItem, streamId);
}
zlmresTfulUtils.closeStreams(mediaServerItem, "rtp", streamId);
}
@Override
public Boolean updateRtpServerSSRC(MediaServerItem mediaServerItem, String streamId, String ssrc) {
return zlmServerFactory.updateRtpServerSSRC(mediaServerItem, streamId, ssrc);
}
@Override
public void releaseSsrc(String mediaServerItemId, String ssrc) {
MediaServerItem mediaServerItem = getOne(mediaServerItemId);
if (mediaServerItem == null || ssrc == null) {
return;
}
ssrcFactory.releaseSsrc(mediaServerItemId, ssrc);
}
/**
* zlm 重启后重置他的推流信息, TODO 给正在使用的设备发送停止命令
*/
@Override
public void clearRTPServer(MediaServerItem mediaServerItem) {
ssrcFactory.reset(mediaServerItem.getId());
}
@Override
public void update(MediaServerItem mediaSerItem) {
mediaServerMapper.update(mediaSerItem);
MediaServerItem mediaServerItemInRedis = getOne(mediaSerItem.getId());
MediaServerItem mediaServerItemInDataBase = mediaServerMapper.queryOne(mediaSerItem.getId());
if (mediaServerItemInRedis == null || !ssrcFactory.hasMediaServerSSRC(mediaSerItem.getId())) {
ssrcFactory.initMediaServerSSRC(mediaServerItemInDataBase.getId(),null);
}
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItemInDataBase.getId();
redisTemplate.opsForValue().set(key, mediaServerItemInDataBase);
}
@Override
public List<MediaServerItem> getAll() {
List<MediaServerItem> result = new ArrayList<>();
List<Object> mediaServerKeys = RedisUtil.scan(redisTemplate, String.format("%S*", VideoManagerConstants.MEDIA_SERVER_PREFIX+ userSetting.getServerId() + "_" ));
String onlineKey = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
for (Object mediaServerKey : mediaServerKeys) {
String key = (String) mediaServerKey;
MediaServerItem mediaServerItem = JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
if (Objects.isNull(mediaServerItem)) {
continue;
}
// 检查状态
Double aDouble = redisTemplate.opsForZSet().score(onlineKey, mediaServerItem.getId());
if (aDouble != null) {
mediaServerItem.setStatus(true);
}
result.add(mediaServerItem);
}
result.sort((serverItem1, serverItem2)->{
int sortResult = 0;
LocalDateTime localDateTime1 = LocalDateTime.parse(serverItem1.getCreateTime(), DateUtil.formatter);
LocalDateTime localDateTime2 = LocalDateTime.parse(serverItem2.getCreateTime(), DateUtil.formatter);
sortResult = localDateTime1.compareTo(localDateTime2);
return sortResult;
});
return result;
}
@Override
public List<MediaServerItem> getAllFromDatabase() {
return mediaServerMapper.queryAll();
}
@Override
public List<MediaServerItem> getAllOnline() {
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
Set<Object> mediaServerIdSet = redisTemplate.opsForZSet().reverseRange(key, 0, -1);
List<MediaServerItem> result = new ArrayList<>();
if (mediaServerIdSet != null && mediaServerIdSet.size() > 0) {
for (Object mediaServerId : mediaServerIdSet) {
String mediaServerIdStr = (String) mediaServerId;
String serverKey = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerIdStr;
result.add((MediaServerItem) redisTemplate.opsForValue().get(serverKey));
}
}
Collections.reverse(result);
return result;
}
/**
* 获取单个zlm服务器
* @param mediaServerId 服务id
* @return MediaServerItem
*/
@Override
public MediaServerItem getOne(String mediaServerId) {
if (mediaServerId == null) {
return null;
}
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerId;
return JsonUtil.redisJsonToObject(redisTemplate, key, MediaServerItem.class);
}
@Override
public MediaServerItem getDefaultMediaServer() {
return mediaServerMapper.queryDefault();
}
@Override
public void clearMediaServerForOnline() {
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
redisTemplate.delete(key);
}
@Override
public void add(MediaServerItem mediaServerItem) {
mediaServerItem.setCreateTime(DateUtil.getNow());
mediaServerItem.setUpdateTime(DateUtil.getNow());
mediaServerItem.setHookAliveInterval(30f);
JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
if (responseJSON != null) {
JSONArray data = responseJSON.getJSONArray("data");
if (data != null && data.size() > 0) {
ZLMServerConfig zlmServerConfig= JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
if (mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId()) != null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"保存失败媒体服务ID [ " + zlmServerConfig.getGeneralMediaServerId() + " ] 已存在,请修改媒体服务器配置");
}
mediaServerItem.setId(zlmServerConfig.getGeneralMediaServerId());
zlmServerConfig.setIp(mediaServerItem.getIp());
mediaServerMapper.add(mediaServerItem);
zlmServerOnline(zlmServerConfig);
}else {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"连接失败");
}
}else {
throw new ControllerException(ErrorCode.ERROR100.getCode(),"连接失败");
}
}
@Override
public int addToDatabase(MediaServerItem mediaSerItem) {
return mediaServerMapper.add(mediaSerItem);
}
@Override
public int updateToDatabase(MediaServerItem mediaSerItem) {
int result = 0;
if (mediaSerItem.isDefaultServer()) {
TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);
int delResult = mediaServerMapper.delDefault();
if (delResult == 0) {
logger.error("移除数据库默认zlm节点失败");
//事务回滚
dataSourceTransactionManager.rollback(transactionStatus);
return 0;
}
result = mediaServerMapper.add(mediaSerItem);
dataSourceTransactionManager.commit(transactionStatus); //手动提交
}else {
result = mediaServerMapper.update(mediaSerItem);
}
return result;
}
/**
* 处理zlm上线
* @param zlmServerConfig zlm上线携带的参数
*/
@Override
public void zlmServerOnline(ZLMServerConfig zlmServerConfig) {
MediaServerItem serverItem = mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId());
if (serverItem == null) {
logger.warn("[未注册的zlm] 拒接接入:{}来自{}{}", zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(),zlmServerConfig.getHttpPort() );
logger.warn("请检查ZLM的<general.mediaServerId>配置是否与WVP的<media.id>一致");
return;
}else {
logger.info("[ZLM] 正在连接 : {} -> {}:{}",
zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
}
serverItem.setHookAliveInterval(zlmServerConfig.getHookAliveInterval());
if (serverItem.getHttpPort() == 0) {
serverItem.setHttpPort(zlmServerConfig.getHttpPort());
}
if (serverItem.getHttpSSlPort() == 0) {
serverItem.setHttpSSlPort(zlmServerConfig.getHttpSSLport());
}
if (serverItem.getRtmpPort() == 0) {
serverItem.setRtmpPort(zlmServerConfig.getRtmpPort());
}
if (serverItem.getRtmpSSlPort() == 0) {
serverItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
}
if (serverItem.getRtspPort() == 0) {
serverItem.setRtspPort(zlmServerConfig.getRtspPort());
}
if (serverItem.getRtspSSLPort() == 0) {
serverItem.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
}
if (serverItem.getRtpProxyPort() == 0) {
serverItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
}
serverItem.setStatus(true);
if (ObjectUtils.isEmpty(serverItem.getId())) {
logger.warn("[未注册的zlm] serverItem缺少ID 无法接入:{}{}", zlmServerConfig.getIp(),zlmServerConfig.getHttpPort() );
return;
}
mediaServerMapper.update(serverItem);
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + zlmServerConfig.getGeneralMediaServerId();
if (!ssrcFactory.hasMediaServerSSRC(serverItem.getId())) {
ssrcFactory.initMediaServerSSRC(zlmServerConfig.getGeneralMediaServerId(), null);
}
redisTemplate.opsForValue().set(key, serverItem);
resetOnlineServerItem(serverItem);
if (serverItem.isAutoConfig()) {
setZLMConfig(serverItem, "0".equals(zlmServerConfig.getHookEnable()));
}
final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + serverItem.getId();
dynamicTask.stop(zlmKeepaliveKey);
dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(serverItem), (serverItem.getHookAliveInterval().intValue() + 5) * 1000);
publisher.zlmOnlineEventPublish(serverItem.getId());
logger.info("[ZLM] 连接成功 {} - {}:{} ",
zlmServerConfig.getGeneralMediaServerId(), zlmServerConfig.getIp(), zlmServerConfig.getHttpPort());
}
class KeepAliveTimeoutRunnable implements Runnable{
private MediaServerItem serverItem;
public KeepAliveTimeoutRunnable(MediaServerItem serverItem) {
this.serverItem = serverItem;
}
@Override
public void run() {
logger.info("[zlm心跳到期]" + serverItem.getId());
// 发起http请求验证zlm是否确实无法连接如果确实无法连接则发送离线事件否则不作处理
JSONObject mediaServerConfig = zlmresTfulUtils.getMediaServerConfig(serverItem);
if (mediaServerConfig != null && mediaServerConfig.getInteger("code") == 0) {
logger.info("[zlm心跳到期]{}验证后zlm仍在线恢复心跳信息,请检查zlm是否可以正常向wvp发送心跳", serverItem.getId());
// 添加zlm信息
updateMediaServerKeepalive(serverItem.getId(), null);
}else {
publisher.zlmOfflineEventPublish(serverItem.getId());
}
}
}
@Override
public void zlmServerOffline(String mediaServerId) {
delete(mediaServerId);
final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerId;
dynamicTask.stop(zlmKeepaliveKey);
}
@Override
public void resetOnlineServerItem(MediaServerItem serverItem) {
// 更新缓存
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
// 使用zset的分数作为当前并发量 默认值设置为0
if (redisTemplate.opsForZSet().score(key, serverItem.getId()) == null) { // 不存在则设置默认值 已存在则重置
redisTemplate.opsForZSet().add(key, serverItem.getId(), 0L);
// 查询服务流数量
zlmresTfulUtils.getMediaList(serverItem, null, null, "rtsp",(mediaList ->{
Integer code = mediaList.getInteger("code");
if (code == 0) {
JSONArray data = mediaList.getJSONArray("data");
if (data != null) {
redisTemplate.opsForZSet().add(key, serverItem.getId(), data.size());
}
}
}));
}else {
clearRTPServer(serverItem);
}
}
@Override
public void addCount(String mediaServerId) {
if (mediaServerId == null) {
return;
}
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
redisTemplate.opsForZSet().incrementScore(key, mediaServerId, 1);
}
@Override
public void removeCount(String mediaServerId) {
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
redisTemplate.opsForZSet().incrementScore(key, mediaServerId, - 1);
}
/**
* 获取负载最低的节点
* @return MediaServerItem
*/
@Override
public MediaServerItem getMediaServerForMinimumLoad(Boolean hasAssist) {
String key = VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId();
Long size = redisTemplate.opsForZSet().zCard(key);
if (size == null || size == 0) {
logger.info("获取负载最低的节点时无在线节点");
return null;
}
// 获取分数最低的,及并发最低的
Set<Object> objects = redisTemplate.opsForZSet().range(key, 0, -1);
ArrayList<Object> mediaServerObjectS = new ArrayList<>(objects);
MediaServerItem mediaServerItem = null;
if (hasAssist == null) {
String mediaServerId = (String)mediaServerObjectS.get(0);
mediaServerItem = getOne(mediaServerId);
}else if (hasAssist) {
for (Object mediaServerObject : mediaServerObjectS) {
String mediaServerId = (String)mediaServerObject;
MediaServerItem serverItem = getOne(mediaServerId);
if (serverItem.getRecordAssistPort() > 0) {
mediaServerItem = serverItem;
break;
}
}
}else if (!hasAssist) {
for (Object mediaServerObject : mediaServerObjectS) {
String mediaServerId = (String)mediaServerObject;
MediaServerItem serverItem = getOne(mediaServerId);
if (serverItem.getRecordAssistPort() == 0) {
mediaServerItem = serverItem;
break;
}
}
}
return mediaServerItem;
}
/**
* 对zlm服务器进行基础配置
* @param mediaServerItem 服务ID
* @param restart 是否重启zlm
*/
@Override
public void setZLMConfig(MediaServerItem mediaServerItem, boolean restart) {
logger.info("[ZLM] 正在设置 {} -> {}:{}",
mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
String protocol = sslEnabled ? "https" : "http";
String hookPrefix = String.format("%s://%s:%s/index/hook", protocol, mediaServerItem.getHookIp(), serverPort);
Map<String, Object> param = new HashMap<>();
param.put("api.secret",mediaServerItem.getSecret()); // -profile:v Baseline
if (mediaServerItem.getRtspPort() != 0) {
param.put("ffmpeg.snap", "%s -rtsp_transport tcp -i %s -y -f mjpeg -frames:v 1 %s");
}
param.put("hook.enable","1");
param.put("hook.on_flow_report","");
param.put("hook.on_play",String.format("%s/on_play", hookPrefix));
param.put("hook.on_http_access","");
param.put("hook.on_publish", String.format("%s/on_publish", hookPrefix));
param.put("hook.on_record_ts","");
param.put("hook.on_rtsp_auth","");
param.put("hook.on_rtsp_realm","");
param.put("hook.on_server_started",String.format("%s/on_server_started", hookPrefix));
param.put("hook.on_shell_login","");
param.put("hook.on_stream_changed",String.format("%s/on_stream_changed", hookPrefix));
param.put("hook.on_stream_none_reader",String.format("%s/on_stream_none_reader", hookPrefix));
param.put("hook.on_stream_not_found",String.format("%s/on_stream_not_found", hookPrefix));
param.put("hook.on_server_keepalive",String.format("%s/on_server_keepalive", hookPrefix));
param.put("hook.on_send_rtp_stopped",String.format("%s/on_send_rtp_stopped", hookPrefix));
param.put("hook.on_rtp_server_timeout",String.format("%s/on_rtp_server_timeout", hookPrefix));
param.put("hook.on_record_mp4",String.format("%s/on_record_mp4", hookPrefix));
param.put("hook.timeoutSec","20");
// 推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
// 置0关闭此特性(推流断开会导致立即断开播放器)
// 此参数不应大于播放器超时时间
// 优化此消息以更快的收到流注销事件
param.put("protocol.continue_push_ms", "3000" );
// 最多等待未初始化的Track时间单位毫秒超时之后会忽略未初始化的Track, 设置此选项优化那些音频错误的不规范流,
// 等zlm支持给每个rtpServer设置关闭音频的时候可以不设置此选项
if (mediaServerItem.isRtpEnable() && !ObjectUtils.isEmpty(mediaServerItem.getRtpPortRange())) {
param.put("rtp_proxy.port_range", mediaServerItem.getRtpPortRange().replace(",", "-"));
}
if (!ObjectUtils.isEmpty(mediaServerItem.getRecordPath())) {
File recordPathFile = new File(mediaServerItem.getRecordPath());
param.put("protocol.mp4_save_path", recordPathFile.getParentFile().getPath());
param.put("protocol.downloadRoot", recordPathFile.getParentFile().getPath());
param.put("record.appName", recordPathFile.getName());
}
JSONObject responseJSON = zlmresTfulUtils.setServerConfig(mediaServerItem, param);
if (responseJSON != null && responseJSON.getInteger("code") == 0) {
if (restart) {
logger.info("[ZLM] 设置成功,开始重启以保证配置生效 {} -> {}:{}",
mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
zlmresTfulUtils.restartServer(mediaServerItem);
}else {
logger.info("[ZLM] 设置成功 {} -> {}:{}",
mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
}
}else {
logger.info("[ZLM] 设置zlm失败 {} -> {}:{}",
mediaServerItem.getId(), mediaServerItem.getIp(), mediaServerItem.getHttpPort());
}
}
@Override
public MediaServerItem checkMediaServer(String ip, int port, String secret) {
if (mediaServerMapper.queryOneByHostAndPort(ip, port) != null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "此连接已存在");
}
MediaServerItem mediaServerItem = new MediaServerItem();
mediaServerItem.setIp(ip);
mediaServerItem.setHttpPort(port);
mediaServerItem.setSecret(secret);
JSONObject responseJSON = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
if (responseJSON == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接失败");
}
JSONArray data = responseJSON.getJSONArray("data");
ZLMServerConfig zlmServerConfig = JSON.parseObject(JSON.toJSONString(data.get(0)), ZLMServerConfig.class);
if (zlmServerConfig == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "读取配置失败");
}
if (mediaServerMapper.queryOne(zlmServerConfig.getGeneralMediaServerId()) != null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "媒体服务ID [" + zlmServerConfig.getGeneralMediaServerId() + " ] 已存在,请修改媒体服务器配置");
}
mediaServerItem.setHttpSSlPort(zlmServerConfig.getHttpPort());
mediaServerItem.setRtmpPort(zlmServerConfig.getRtmpPort());
mediaServerItem.setRtmpSSlPort(zlmServerConfig.getRtmpSslPort());
mediaServerItem.setRtspPort(zlmServerConfig.getRtspPort());
mediaServerItem.setRtspSSLPort(zlmServerConfig.getRtspSSlport());
mediaServerItem.setRtpProxyPort(zlmServerConfig.getRtpProxyPort());
mediaServerItem.setStreamIp(ip);
mediaServerItem.setHookIp(sipConfig.getIp().split(",")[0]);
mediaServerItem.setSdpIp(ip);
return mediaServerItem;
}
@Override
public boolean checkMediaRecordServer(String ip, int port) {
boolean result = false;
OkHttpClient client = new OkHttpClient();
String url = String.format("http://%s:%s/index/api/record", ip, port);
Request request = new Request.Builder()
.get()
.url(url)
.build();
try {
Response response = client.newCall(request).execute();
if (response != null) {
result = true;
}
} catch (Exception e) {}
return result;
}
@Override
public void delete(String id) {
redisTemplate.opsForZSet().remove(VideoManagerConstants.MEDIA_SERVERS_ONLINE_PREFIX + userSetting.getServerId(), id);
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + id;
redisTemplate.delete(key);
}
@Override
public void deleteDb(String id){
//同步删除数据库中的数据
mediaServerMapper.delOne(id);
}
@Override
public void updateMediaServerKeepalive(String mediaServerId, ServerKeepaliveData data) {
MediaServerItem mediaServerItem = getOne(mediaServerId);
if (mediaServerItem == null) {
// 缓存不存在,从数据库查询,如果数据库不存在则是错误的
mediaServerItem = getOneFromDatabase(mediaServerId);
if (mediaServerItem == null) {
logger.warn("[更新ZLM 保活信息] 流媒体{}尚未加入使用,请检查节点中是否含有此流媒体 ", mediaServerId);
return;
}
// zlm连接重试
logger.warn("[更新ZLM 保活信息]尝试链接zml id {}", mediaServerId);
ssrcFactory.initMediaServerSSRC(mediaServerItem.getId(), null);
String key = VideoManagerConstants.MEDIA_SERVER_PREFIX + userSetting.getServerId() + "_" + mediaServerItem.getId();
redisTemplate.opsForValue().set(key, mediaServerItem);
resetOnlineServerItem(mediaServerItem);
clearRTPServer(mediaServerItem);
}
final String zlmKeepaliveKey = zlmKeepaliveKeyPrefix + mediaServerItem.getId();
dynamicTask.stop(zlmKeepaliveKey);
dynamicTask.startDelay(zlmKeepaliveKey, new KeepAliveTimeoutRunnable(mediaServerItem), (mediaServerItem.getHookAliveInterval().intValue() + 5) * 1000);
}
private MediaServerItem getOneFromDatabase(String mediaServerId) {
return mediaServerMapper.queryOne(mediaServerId);
}
@Override
public void syncCatchFromDatabase() {
List<MediaServerItem> allInCatch = getAll();
List<MediaServerItem> allInDatabase = mediaServerMapper.queryAll();
Map<String, MediaServerItem> mediaServerItemMap = new HashMap<>();
for (MediaServerItem mediaServerItem : allInDatabase) {
mediaServerItemMap.put(mediaServerItem.getId(), mediaServerItem);
}
for (MediaServerItem mediaServerItem : allInCatch) {
if (!mediaServerItemMap.containsKey(mediaServerItem.getId())) {
delete(mediaServerItem.getId());
}
}
}
@Override
public MediaServerLoad getLoad(MediaServerItem mediaServerItem) {
MediaServerLoad result = new MediaServerLoad();
result.setId(mediaServerItem.getId());
result.setPush(redisCatchStorage.getPushStreamCount(mediaServerItem.getId()));
result.setProxy(redisCatchStorage.getProxyStreamCount(mediaServerItem.getId()));
result.setGbReceive(inviteStreamService.getStreamInfoCount(mediaServerItem.getId()));
result.setGbSend(redisCatchStorage.getGbSendCount(mediaServerItem.getId()));
return result;
}
@Override
public List<MediaServerItem> getAllWithAssistPort() {
return mediaServerMapper.queryAllWithAssistPort();
}
}

View File

@@ -1,109 +1,331 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.bean.ResultForOnPublish;
import com.genersoft.iot.vmp.media.zlm.ZLMMediaListManager;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IMediaService;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.OtherPsSendInfo;
import com.genersoft.iot.vmp.vmanager.bean.OtherRtpSendInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class MediaServiceImpl implements IMediaService {
private final static Logger logger = LoggerFactory.getLogger(MediaServiceImpl.class);
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private IStreamProxyService streamProxyService;
@Autowired
private UserSetting userSetting;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Autowired
private IUserService userService;
@Autowired
private IInviteStreamService inviteStreamService;
@Autowired
private VideoStreamSessionManager sessionManager;
@Autowired
private IVideoManagerStorage storager;
@Autowired
private IMediaServerService mediaServerService;
private ZLMMediaListManager zlmMediaListManager;
@Autowired
private MediaConfig mediaConfig;
private IDeviceService deviceService;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
private ISIPCommanderForPlatform commanderForPlatform;
@Autowired
private ISIPCommander commander;
@Override
public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String callId) {
return getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, null, callId, true);
public boolean authenticatePlay(String app, String stream, String callId) {
if (app == null || stream == null) {
return false;
}
if ("rtp".equals(app)) {
return true;
}
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
if (streamAuthorityInfo == null || streamAuthorityInfo.getCallId() == null) {
return true;
}
return streamAuthorityInfo.getCallId().equals(callId);
}
@Override
public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, String addr, boolean authority) {
StreamInfo streamInfo = null;
if (mediaServerId == null) {
mediaServerId = mediaConfig.getId();
}
MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
if (mediaInfo == null) {
return null;
}
String calld = null;
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(app, stream);
if (streamAuthorityInfo != null) {
calld = streamAuthorityInfo.getCallId();
}
JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaInfo, app, stream);
if (mediaList != null) {
if (mediaList.getInteger("code") == 0) {
JSONArray data = mediaList.getJSONArray("data");
if (data == null) {
return null;
public ResultForOnPublish authenticatePublish(MediaServer mediaServer, String app, String stream, String params) {
// 推流鉴权的处理
if (!"rtp".equals(app)) {
StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, stream);
if (streamProxyItem != null) {
ResultForOnPublish result = new ResultForOnPublish();
result.setEnable_audio(streamProxyItem.isEnableAudio());
result.setEnable_mp4(streamProxyItem.isEnableMp4());
return result;
}
if (userSetting.getPushAuthority()) {
// 对于推流进行鉴权
Map<String, String> paramMap = urlParamToMap(params);
// 推流鉴权
if (params == null) {
logger.info("推流鉴权失败: 缺少必要参数sign=md5(user表的pushKey)");
throw new ControllerException(ErrorCode.ERROR401.getCode(), "Unauthorized");
}
JSONObject mediaJSON = data.getJSONObject(0);
JSONArray tracks = mediaJSON.getJSONArray("tracks");
if (authority) {
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr, calld, true);
}else {
streamInfo = getStreamInfoByAppAndStream(mediaInfo, app, stream, tracks, addr,null, true);
String sign = paramMap.get("sign");
if (sign == null) {
logger.info("推流鉴权失败: 缺少必要参数sign=md5(user表的pushKey)");
throw new ControllerException(ErrorCode.ERROR401.getCode(), "Unauthorized");
}
// 推流自定义播放鉴权码
String callId = paramMap.get("callId");
// 鉴权配置
boolean hasAuthority = userService.checkPushAuthority(callId, sign);
if (!hasAuthority) {
logger.info("推流鉴权失败: sign 无权限: callId={}. sign={}", callId, sign);
throw new ControllerException(ErrorCode.ERROR401.getCode(), "Unauthorized");
}
StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(app, stream, mediaServer.getId());
streamAuthorityInfo.setCallId(callId);
streamAuthorityInfo.setSign(sign);
// 鉴权通过
redisCatchStorage.updateStreamAuthorityInfo(app, stream, streamAuthorityInfo);
}
} else {
zlmMediaListManager.sendStreamEvent(app, stream, mediaServer.getId());
}
ResultForOnPublish result = new ResultForOnPublish();
result.setEnable_audio(true);
// 是否录像
if ("rtp".equals(app)) {
result.setEnable_mp4(userSetting.getRecordSip());
} else {
result.setEnable_mp4(userSetting.isRecordPushLive());
}
// 国标流
if ("rtp".equals(app)) {
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, stream);
// 单端口模式下修改流 ID
if (!mediaServer.isRtpEnable() && inviteInfo == null) {
String ssrc = String.format("%010d", Long.parseLong(stream, 16));
inviteInfo = inviteStreamService.getInviteInfoBySSRC(ssrc);
if (inviteInfo != null) {
result.setStream_replace(inviteInfo.getStream());
logger.info("[ZLM HOOK]推流鉴权 stream: {} 替换为 {}", stream, inviteInfo.getStream());
stream = inviteInfo.getStream();
}
}
// 设置音频信息及录制信息
List<SsrcTransaction> ssrcTransactionForAll = sessionManager.getSsrcTransactionForAll(null, null, null, stream);
if (ssrcTransactionForAll != null && ssrcTransactionForAll.size() == 1) {
// 为录制国标模拟一个鉴权信息, 方便后续写入录像文件时使用
StreamAuthorityInfo streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(app, stream, mediaServer.getId());
streamAuthorityInfo.setApp(app);
streamAuthorityInfo.setStream(ssrcTransactionForAll.get(0).getStream());
streamAuthorityInfo.setCallId(ssrcTransactionForAll.get(0).getSipTransactionInfo().getCallId());
redisCatchStorage.updateStreamAuthorityInfo(app, ssrcTransactionForAll.get(0).getStream(), streamAuthorityInfo);
String deviceId = ssrcTransactionForAll.get(0).getDeviceId();
String channelId = ssrcTransactionForAll.get(0).getChannelId();
DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
if (deviceChannel != null) {
result.setEnable_audio(deviceChannel.isHasAudio());
}
// 如果是录像下载就设置视频间隔十秒
if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.DOWNLOAD) {
// 获取录像的总时长,然后设置为这个视频的时长
InviteInfo inviteInfoForDownload = inviteStreamService.getInviteInfo(InviteSessionType.DOWNLOAD, deviceId, channelId, stream);
if (inviteInfoForDownload != null && inviteInfoForDownload.getStreamInfo() != null) {
String startTime = inviteInfoForDownload.getStreamInfo().getStartTime();
String endTime = inviteInfoForDownload.getStreamInfo().getEndTime();
long difference = DateUtil.getDifference(startTime, endTime) / 1000;
result.setMp4_max_second((int) difference);
result.setEnable_mp4(true);
// 设置为2保证得到的mp4的时长是正常的
result.setModify_stamp(2);
}
}
// 如果是talk对讲则默认获取声音
if (ssrcTransactionForAll.get(0).getType() == InviteSessionType.TALK) {
result.setEnable_audio(true);
}
}
} else if (app.equals("broadcast")) {
result.setEnable_audio(true);
} else if (app.equals("talk")) {
result.setEnable_audio(true);
}
if (app.equalsIgnoreCase("rtp")) {
String receiveKey = VideoManagerConstants.WVP_OTHER_RECEIVE_RTP_INFO + userSetting.getServerId() + "_" + stream;
OtherRtpSendInfo otherRtpSendInfo = (OtherRtpSendInfo) redisTemplate.opsForValue().get(receiveKey);
String receiveKeyForPS = VideoManagerConstants.WVP_OTHER_RECEIVE_PS_INFO + userSetting.getServerId() + "_" + stream;
OtherPsSendInfo otherPsSendInfo = (OtherPsSendInfo) redisTemplate.opsForValue().get(receiveKeyForPS);
if (otherRtpSendInfo != null || otherPsSendInfo != null) {
result.setEnable_mp4(true);
}
}
return result;
}
private Map<String, String> urlParamToMap(String params) {
HashMap<String, String> map = new HashMap<>();
if (ObjectUtils.isEmpty(params)) {
return map;
}
String[] paramsArray = params.split("&");
if (paramsArray.length == 0) {
return map;
}
for (String param : paramsArray) {
String[] paramArray = param.split("=");
if (paramArray.length == 2) {
map.put(paramArray[0], paramArray[1]);
}
}
return map;
}
@Override
public boolean closeStreamOnNoneReader(String mediaServerId, String app, String stream, String schema) {
boolean result = false;
// 国标类型的流
if ("rtp".equals(app)) {
result = userSetting.getStreamOnDemand();
// 国标流, 点播/录像回放/录像下载
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(null, stream);
// 点播
if (inviteInfo != null) {
// 录像下载
if (inviteInfo.getType() == InviteSessionType.DOWNLOAD) {
return false;
}
// 收到无人观看说明流也没有在往上级推送
if (redisCatchStorage.isChannelSendingRTP(inviteInfo.getChannelId())) {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByChannelId(
inviteInfo.getChannelId());
if (!sendRtpItems.isEmpty()) {
for (SendRtpItem sendRtpItem : sendRtpItems) {
ParentPlatform parentPlatform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
try {
commanderForPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
}
redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
sendRtpItem.getCallId(), sendRtpItem.getStream());
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}
}
}
}
Device device = deviceService.getDevice(inviteInfo.getDeviceId());
if (device != null) {
try {
// 多查询一次防止已经被处理了
InviteInfo info = inviteStreamService.getInviteInfo(inviteInfo.getType(),
inviteInfo.getDeviceId(), inviteInfo.getChannelId(), inviteInfo.getStream());
if (info != null) {
commander.streamByeCmd(device, inviteInfo.getChannelId(),
inviteInfo.getStream(), null);
} else {
logger.info("[无人观看] 未找到设备的点播信息: {} 流:{}", inviteInfo.getDeviceId(), stream);
}
} catch (InvalidArgumentException | ParseException | SipException |
SsrcTransactionNotFoundException e) {
logger.error("[无人观看]点播, 发送BYE失败 {}", e.getMessage());
}
} else {
logger.info("[无人观看] 未找到设备: {},流:{}", inviteInfo.getDeviceId(), stream);
}
inviteStreamService.removeInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId(),
inviteInfo.getChannelId(), inviteInfo.getStream());
storager.stopPlay(inviteInfo.getDeviceId(), inviteInfo.getChannelId());
return result;
}
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(null, null, stream, null);
if (sendRtpItem != null && "talk".equals(sendRtpItem.getApp())) {
return false;
}
} else if ("talk".equals(app) || "broadcast".equals(app)) {
return false;
} else {
// 非国标流 推流/拉流代理
// 拉流代理
StreamProxyItem streamProxyItem = streamProxyService.getStreamProxyByAppAndStream(app, stream);
if (streamProxyItem != null) {
if (streamProxyItem.isEnableRemoveNoneReader()) {
// 无人观看自动移除
result = true;
streamProxyService.del(app, stream);
String url = streamProxyItem.getUrl() != null ? streamProxyItem.getUrl() : streamProxyItem.getSrcUrl();
logger.info("[{}/{}]<-[{}] 拉流代理无人观看已经移除", app, stream, url);
} else if (streamProxyItem.isEnableDisableNoneReader()) {
// 无人观看停用
result = true;
// 修改数据
streamProxyService.stop(app, stream);
} else {
// 无人观看不做处理
result = false;
}
}
}
return streamInfo;
}
@Override
public StreamInfo getStreamInfoByAppAndStreamWithCheck(String app, String stream, String mediaServerId, boolean authority) {
return getStreamInfoByAppAndStreamWithCheck(app, stream, mediaServerId, null, authority);
}
@Override
public StreamInfo getStreamInfoByAppAndStream(MediaServerItem mediaInfo, String app, String stream, Object tracks, String addr, String callId, boolean isPlay) {
StreamInfo streamInfoResult = new StreamInfo();
streamInfoResult.setStream(stream);
streamInfoResult.setApp(app);
if (addr == null) {
addr = mediaInfo.getStreamIp();
}
streamInfoResult.setIp(addr);
streamInfoResult.setMediaServerId(mediaInfo.getId());
String callIdParam = ObjectUtils.isEmpty(callId)?"":"?callId=" + callId;
streamInfoResult.setRtmp(addr, mediaInfo.getRtmpPort(),mediaInfo.getRtmpSSlPort(), app, stream, callIdParam);
streamInfoResult.setRtsp(addr, mediaInfo.getRtspPort(),mediaInfo.getRtspSSLPort(), app, stream, callIdParam);
streamInfoResult.setFlv(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
streamInfoResult.setFmp4(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
streamInfoResult.setHls(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
streamInfoResult.setTs(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam);
streamInfoResult.setRtc(addr, mediaInfo.getHttpPort(),mediaInfo.getHttpSSlPort(), app, stream, callIdParam, isPlay);
streamInfoResult.setTracks(tracks);
return streamInfoResult;
return result;
}
}

View File

@@ -1,10 +1,7 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.common.InviteInfo;
import com.genersoft.iot.vmp.common.InviteSessionStatus;
import com.genersoft.iot.vmp.common.InviteSessionType;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.common.*;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
@@ -12,46 +9,38 @@ import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
import com.genersoft.iot.vmp.gb28181.session.SSRCFactory;
import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.event.hook.HookData;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.mediaServer.MediaSendRtpStoppedEvent;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.IInviteStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IPlatformService;
import com.genersoft.iot.vmp.service.IPlayService;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.dao.*;
import com.genersoft.iot.vmp.storager.dao.GbStreamMapper;
import com.genersoft.iot.vmp.storager.dao.ParentPlatformMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.sdp.*;
import javax.sip.InvalidArgumentException;
import javax.sip.ResponseEvent;
import javax.sip.PeerUnavailableException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
/**
@@ -81,7 +70,7 @@ public class PlatformServiceImpl implements IPlatformService {
private IMediaServerService mediaServerService;
@Autowired
private SIPCommanderFroPlatform commanderForPlatform;
private ISIPCommanderForPlatform commanderForPlatform;
@Autowired
private DynamicTask dynamicTask;
@@ -99,20 +88,67 @@ public class PlatformServiceImpl implements IPlatformService {
private UserSetting userSetting;
@Autowired
private ZlmHttpHookSubscribe subscribe;
private HookSubscribe subscribe;
@Autowired
private VideoStreamSessionManager streamSession;
@Autowired
private IPlayService playService;
@Autowired
private IInviteStreamService inviteStreamService;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
/**
* 流离开的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaDepartureEvent event) {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(event.getStream());
if (!sendRtpItems.isEmpty()) {
for (SendRtpItem sendRtpItem : sendRtpItems) {
if (sendRtpItem != null && sendRtpItem.getApp().equals(event.getApp())) {
String platformId = sendRtpItem.getPlatformId();
ParentPlatform platform = platformMapper.getParentPlatByServerGBId(platformId);
try {
if (platform != null) {
commanderForPlatform.streamByeCmd(platform, sendRtpItem);
redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(),
sendRtpItem.getCallId(), sendRtpItem.getStream());
}
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
}
}
}
}
}
/**
* 发流停止
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaSendRtpStoppedEvent event) {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(event.getStream());
if (sendRtpItems != null && !sendRtpItems.isEmpty()) {
for (SendRtpItem sendRtpItem : sendRtpItems) {
ParentPlatform parentPlatform = platformMapper.getParentPlatByServerGBId(sendRtpItem.getPlatformId());
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
try {
commanderForPlatform.streamByeCmd(parentPlatform, sendRtpItem.getCallId());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
}
redisCatchStorage.deleteSendRTPServer(parentPlatform.getServerGBId(), sendRtpItem.getChannelId(),
sendRtpItem.getCallId(), sendRtpItem.getStream());
}
}
}
@Override
@@ -400,7 +436,7 @@ public class PlatformServiceImpl implements IPlatformService {
for (SendRtpItem sendRtpItem : sendRtpItems) {
ssrcFactory.releaseSsrc(sendRtpItem.getMediaServerId(), sendRtpItem.getSsrc());
redisCatchStorage.deleteSendRTPServer(platformId, sendRtpItem.getChannelId(), null, null);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Map<String, Object> param = new HashMap<>(3);
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
@@ -463,7 +499,7 @@ public class PlatformServiceImpl implements IPlatformService {
}
@Override
public void broadcastInvite(ParentPlatform platform, String channelId, MediaServerItem mediaServerItem, ZlmHttpHookSubscribe.Event hookEvent,
public void broadcastInvite(ParentPlatform platform, String channelId, MediaServer mediaServerItem, HookSubscribe.Event hookEvent,
SipSubscribe.Event errorEvent, InviteTimeOutCallback timeoutCallback) throws InvalidArgumentException, ParseException, SipException {
if (mediaServerItem == null) {
@@ -474,19 +510,19 @@ public class PlatformServiceImpl implements IPlatformService {
if (inviteInfoForOld != null && inviteInfoForOld.getStreamInfo() != null) {
// 如果zlm不存在这个流则删除数据即可
MediaServerItem mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfoForOld.getStreamInfo().getMediaServerId());
MediaServer mediaServerItemForStreamInfo = mediaServerService.getOne(inviteInfoForOld.getStreamInfo().getMediaServerId());
if (mediaServerItemForStreamInfo != null) {
Boolean ready = zlmServerFactory.isStreamReady(mediaServerItemForStreamInfo, inviteInfoForOld.getStreamInfo().getApp(), inviteInfoForOld.getStreamInfo().getStream());
Boolean ready = mediaServerService.isStreamReady(mediaServerItemForStreamInfo, inviteInfoForOld.getStreamInfo().getApp(), inviteInfoForOld.getStreamInfo().getStream());
if (!ready) {
// 错误存在于redis中的数据
inviteStreamService.removeInviteInfo(inviteInfoForOld);
}else {
// 流确实尚在推流,直接回调结果
OnStreamChangedHookParam hookParam = new OnStreamChangedHookParam();
hookParam.setApp(inviteInfoForOld.getStreamInfo().getApp());
hookParam.setStream(inviteInfoForOld.getStreamInfo().getStream());
hookEvent.response(mediaServerItemForStreamInfo, hookParam);
HookData hookData = new HookData();
hookData.setApp(inviteInfoForOld.getStreamInfo().getApp());
hookData.setStream(inviteInfoForOld.getStreamInfo().getStream());
hookData.setMediaServer(mediaServerItemForStreamInfo);
hookEvent.response(hookData);
return;
}
}
@@ -506,7 +542,7 @@ public class PlatformServiceImpl implements IPlatformService {
} else {
tcpMode = 0;
}
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true, false, tcpMode);
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, null, ssrcCheck, false, null, true, false, false, tcpMode);
if (ssrcInfo == null || ssrcInfo.getPort() < 0) {
logger.info("[国标级联] 发起语音喊话 开启端口监听失败, platform: {}, channel {}", platform.getServerGBId(), channelId);
SipSubscribe.EventResult<Object> eventResult = new SipSubscribe.EventResult<>();
@@ -544,14 +580,14 @@ public class PlatformServiceImpl implements IPlatformService {
}
}
}, userSetting.getPlayTimeout());
commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (mediaServerItemForInvite, hookParam)->{
commanderForPlatform.broadcastInviteCmd(platform, channelId, mediaServerItem, ssrcInfo, (hookData)->{
logger.info("[国标级联] 发起语音喊话 收到上级推流 deviceId: {}, channelId: {}", platform.getServerGBId(), channelId);
dynamicTask.stop(timeOutTaskKey);
// hook响应
playService.onPublishHandlerForPlay(mediaServerItemForInvite, hookParam, platform.getServerGBId(), channelId);
playService.onPublishHandlerForPlay(hookData.getMediaServer(), hookData.getMediaInfo(), platform.getServerGBId(), channelId);
// 收到流
if (hookEvent != null) {
hookEvent.response(mediaServerItem, hookParam);
hookEvent.response(hookData);
}
}, event -> {
@@ -604,13 +640,12 @@ public class PlatformServiceImpl implements IPlatformService {
});
}
private void inviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, int tcpMode, boolean ssrcCheck, MediaServerItem mediaServerItem,
private void inviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, int tcpMode, boolean ssrcCheck, MediaServer mediaServerItem,
ParentPlatform platform, String channelId, String timeOutTaskKey, ErrorCallback<Object> callback,
InviteInfo inviteInfo, InviteSessionType inviteSessionType){
inviteInfo.setStatus(InviteSessionStatus.ok);
ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
String contentString = new String(responseEvent.getResponse().getRawContent());
System.out.println(1111);
System.out.println(contentString);
String ssrcInResponse = SipUtils.getSsrcFromSdp(contentString);
// 兼容回复的消息中缺少ssrc(y字段)的情况
@@ -709,7 +744,7 @@ public class PlatformServiceImpl implements IPlatformService {
private void tcpActiveHandler(ParentPlatform platform, String channelId, String contentString,
MediaServerItem mediaServerItem, int tcpMode, boolean ssrcCheck,
MediaServer mediaServerItem, int tcpMode, boolean ssrcCheck,
String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback<Object> callback){
if (tcpMode != 2) {
return;
@@ -737,8 +772,8 @@ public class PlatformServiceImpl implements IPlatformService {
}
logger.info("[TCP主动连接对方] serverGbId: {}, channelId: {}, 连接对方的地址:{}:{}, SSRC: {}, SSRC校验{}",
platform.getServerGBId(), channelId, sdp.getConnection().getAddress(), port, ssrcInfo.getSsrc(), ssrcCheck);
JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
logger.info("[TCP主动连接对方] 结果: {}", jsonObject);
Boolean result = mediaServerService.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
logger.info("[TCP主动连接对方] 结果: {}", result);
} catch (SdpException e) {
logger.error("[TCP主动连接对方] serverGbId: {}, channelId: {}, 解析200OK的SDP信息失败", platform.getServerGBId(), channelId, e);
dynamicTask.stop(timeOutTaskKey);
@@ -757,7 +792,7 @@ public class PlatformServiceImpl implements IPlatformService {
}
@Override
public void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream, boolean sendBye, MediaServerItem mediaServerItem) {
public void stopBroadcast(ParentPlatform platform, DeviceChannel channel, String stream, boolean sendBye, MediaServer mediaServerItem) {
try {
if (sendBye) {

View File

@@ -1,11 +1,9 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.common.*;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.SipConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.conf.exception.ServiceException;
@@ -18,17 +16,18 @@ import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommander;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.gb28181.utils.SipUtils;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.bean.RecordInfo;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.SendRtpPortManager;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForRecordMp4;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.HookParam;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnRecordMp4HookParam;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.service.*;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.service.redisMsg.RedisGbPlayMsgListener;
@@ -44,9 +43,8 @@ import gov.nist.javax.sip.message.SIPResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;
@@ -86,24 +84,15 @@ public class PlayServiceImpl implements IPlayService {
@Autowired
private IRedisCatchStorage redisCatchStorage;
@Autowired
private ZLMServerFactory zlmServerFactory;
@Autowired
private IInviteStreamService inviteStreamService;
@Autowired
private ZlmHttpHookSubscribe subscribe;
private HookSubscribe subscribe;
@Autowired
private SendRtpPortManager sendRtpPortManager;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired
private IMediaService mediaService;
@Autowired
private IMediaServerService mediaServerService;
@@ -116,35 +105,165 @@ public class PlayServiceImpl implements IPlayService {
@Autowired
private IDeviceChannelService channelService;
@Autowired
private SipConfig sipConfig;
@Autowired
private DynamicTask dynamicTask;
@Autowired
private ISIPCommanderForPlatform commanderForPlatform;
@Qualifier("taskExecutor")
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@Autowired
private RedisGbPlayMsgListener redisGbPlayMsgListener;
@Autowired
private ZlmHttpHookSubscribe hookSubscribe;
@Autowired
private SSRCFactory ssrcFactory;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
/**
* 流到来的处理
*/
@Async("taskExecutor")
@org.springframework.context.event.EventListener
public void onApplicationEvent(MediaArrivalEvent event) {
if ("broadcast".equals(event.getApp())) {
if (event.getStream().indexOf("_") > 0) {
String[] streamArray = event.getStream().split("_");
if (streamArray.length == 2) {
String deviceId = streamArray[0];
String channelId = streamArray[1];
Device device = deviceService.getDevice(deviceId);
if (device == null) {
logger.info("[语音对讲/喊话] 未找到设备:{}", deviceId);
return;
}
if ("broadcast".equals(event.getApp())) {
if (audioBroadcastManager.exit(deviceId, channelId)) {
stopAudioBroadcast(deviceId, channelId);
}
// 开启语音对讲通道
try {
audioBroadcastCmd(device, channelId, event.getMediaServer(),
event.getApp(), event.getStream(), 60, false, (msg) -> {
logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
});
} catch (InvalidArgumentException | ParseException | SipException e) {
logger.error("[命令发送失败] 语音对讲: {}", e.getMessage());
}
}else if ("talk".equals(event.getApp())) {
// 开启语音对讲通道
talkCmd(device, channelId, event.getMediaServer(), event.getStream(), (msg) -> {
logger.info("[语音对讲] 通道建立成功, device: {}, channel: {}", deviceId, channelId);
});
}
}
}
}
}
/**
* 流离开的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaDepartureEvent event) {
List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServerByStream(event.getStream());
if (!sendRtpItems.isEmpty()) {
for (SendRtpItem sendRtpItem : sendRtpItems) {
if (sendRtpItem != null && sendRtpItem.getApp().equals(event.getApp())) {
String platformId = sendRtpItem.getPlatformId();
Device device = deviceService.getDevice(platformId);
try {
if (device != null) {
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), event.getStream(), sendRtpItem.getCallId());
if (sendRtpItem.getPlayType().equals(InviteStreamType.BROADCAST)
|| sendRtpItem.getPlayType().equals(InviteStreamType.TALK)) {
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
if (audioBroadcastCatch != null) {
// 来自上级平台的停止对讲
logger.info("[停止对讲] 来自上级,平台:{}, 通道:{}", sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
audioBroadcastManager.del(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
}
}
}
} catch (SipException | InvalidArgumentException | ParseException |
SsrcTransactionNotFoundException e) {
logger.error("[命令发送失败] 发送BYE: {}", e.getMessage());
}
}
}
}
if ("broadcast".equals(event.getApp()) || "talk".equals(event.getApp())) {
if (event.getStream().indexOf("_") > 0) {
String[] streamArray = event.getStream().split("_");
if (streamArray.length == 2) {
String deviceId = streamArray[0];
String channelId = streamArray[1];
Device device = deviceService.getDevice(deviceId);
if (device == null) {
logger.info("[语音对讲/喊话] 未找到设备:{}", deviceId);
return;
}
if ("broadcast".equals(event.getApp())) {
stopAudioBroadcast(deviceId, channelId);
}else if ("talk".equals(event.getApp())) {
stopTalk(device, channelId, false);
}
}
}
}
}
/**
* 流未找到的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaNotFoundEvent event) {
if (!"rtp".equals(event.getApp())) {
return;
}
String[] s = event.getStream().split("_");
if ((s.length != 2 && s.length != 4)) {
return;
}
String deviceId = s[0];
String channelId = s[1];
Device device = redisCatchStorage.getDevice(deviceId);
if (device == null || !device.isOnLine()) {
return;
}
DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
if (deviceChannel == null) {
return;
}
if (s.length == 2) {
logger.info("[ZLM HOOK] 预览流未找到, 发起自动点播:{}->{}->{}/{}", event.getMediaServer().getId(), event.getSchema(), event.getApp(), event.getStream());
play(event.getMediaServer(), deviceId, channelId, null, null);
} else if (s.length == 4) {
// 此时为录像回放, 录像回放格式为> 设备ID_通道ID_开始时间_结束时间
String startTimeStr = s[2];
String endTimeStr = s[3];
if (startTimeStr == null || endTimeStr == null || startTimeStr.length() != 14 || endTimeStr.length() != 14) {
return;
}
String startTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(startTimeStr);
String endTime = DateUtil.urlToyyyy_MM_dd_HH_mm_ss(endTimeStr);
logger.info("[ZLM HOOK] 回放流未找到, 发起自动点播:{}->{}->{}/{}-{}-{}",
event.getMediaServer().getId(), event.getSchema(),
event.getApp(), event.getStream(),
startTime, endTime
);
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(event.getMediaServer(), event.getStream(), null,
device.isSsrcCheck(), true, 0, false, !deviceChannel.isHasAudio(), false, device.getStreamModeForParam());
playBack(event.getMediaServer(), ssrcInfo, deviceId, channelId, startTime, endTime, null);
}
}
@Override
public SSRCInfo play(MediaServerItem mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
public SSRCInfo play(MediaServer mediaServerItem, String deviceId, String channelId, String ssrc, ErrorCallback<Object> callback) {
if (mediaServerItem == null) {
logger.warn("[点播] 未找到可用的zlm deviceId: {},channelId:{}", deviceId, channelId);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到可用的zlm");
@@ -179,9 +298,8 @@ public class PlayServiceImpl implements IPlayService {
return inviteInfo.getSsrcInfo();
}
String mediaServerId = streamInfo.getMediaServerId();
MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
Boolean ready = zlmServerFactory.isStreamReady(mediaInfo, "rtp", streamId);
MediaServer mediaInfo = mediaServerService.getOne(mediaServerId);
Boolean ready = mediaServerService.isStreamReady(mediaInfo, "rtp", streamId);
if (ready != null && ready) {
callback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), streamInfo);
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
@@ -198,8 +316,8 @@ public class PlayServiceImpl implements IPlayService {
}
}
}
String streamId = String.format("%s_%s", device.getDeviceId(), channelId);;
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, false, device.getStreamModeForParam());
String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, ssrc, device.isSsrcCheck(), false, 0, false, !channel.isHasAudio(), false, device.getStreamModeForParam());
if (ssrcInfo == null) {
callback.run(InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getCode(), InviteErrorCode.ERROR_FOR_RESOURCE_EXHAUSTION.getMsg(), null);
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channelId, null,
@@ -212,8 +330,8 @@ public class PlayServiceImpl implements IPlayService {
return ssrcInfo;
}
private void talk(MediaServerItem mediaServerItem, Device device, String channelId, String stream,
ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
private void talk(MediaServer mediaServerItem, Device device, String channelId, String stream,
HookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
Runnable timeoutCallback, AudioBroadcastEvent audioEvent) {
String playSsrc = ssrcFactory.getPlaySsrc(mediaServerItem.getId());
@@ -269,38 +387,25 @@ public class PlayServiceImpl implements IPlayService {
}
}, userSetting.getPlayTimeout());
Map<String, Object> param = new HashMap<>(12);
param.put("vhost","__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
param.put("ssrc", sendRtpItem.getSsrc());
param.put("src_port", sendRtpItem.getLocalPort());
param.put("pt", sendRtpItem.getPt());
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
param.put("is_udp", sendRtpItem.isTcp() ? "0" : "1");
param.put("recv_stream_id", sendRtpItem.getReceiveStream());
param.put("close_delay_ms", userSetting.getPlayTimeout() * 1000);
zlmServerFactory.startSendRtpPassive(mediaServerItem, param, jsonObject -> {
if (jsonObject == null || jsonObject.getInteger("code") != 0 ) {
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
logger.info("[语音对讲]失败 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
audioEvent.call("失败, " + jsonObject.getString("msg"));
// 查看是否已经建立了通道存在则发送bye
stopTalk(device, channelId);
}
});
try {
mediaServerService.startSendRtpPassive(mediaServerItem, null, sendRtpItem, userSetting.getPlayTimeout() * 1000);
}catch (ControllerException e) {
mediaServerService.releaseSsrc(mediaServerItem.getId(), sendRtpItem.getSsrc());
logger.info("[语音对讲]失败 deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
audioEvent.call("失败, " + e.getMessage());
// 查看是否已经建立了通道存在则发送bye
stopTalk(device, channelId);
}
// 查看设备是否已经在推流
try {
cmder.talkStreamCmd(mediaServerItem, sendRtpItem, device, channelId, callId, (mediaServerItemInuse, hookParam) -> {
logger.info("[语音对讲] 流已生成, 开始推流: " + hookParam);
cmder.talkStreamCmd(mediaServerItem, sendRtpItem, device, channelId, callId, (hookData) -> {
logger.info("[语音对讲] 流已生成, 开始推流: " + hookData);
dynamicTask.stop(timeOutTaskKey);
// TODO 暂不做处理
}, (mediaServerItemInuse, hookParam) -> {
logger.info("[语音对讲] 设备开始推流: " + hookParam);
}, (hookData) -> {
logger.info("[语音对讲] 设备开始推流: " + hookData);
dynamicTask.stop(timeOutTaskKey);
}, (event) -> {
@@ -355,7 +460,7 @@ public class PlayServiceImpl implements IPlayService {
@Override
public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
public void play(MediaServer mediaServerItem, SSRCInfo ssrcInfo, Device device, DeviceChannel channel,
ErrorCallback<Object> callback) {
if (mediaServerItem == null || ssrcInfo == null) {
@@ -410,8 +515,7 @@ public class PlayServiceImpl implements IPlayService {
streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream());
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
// 取消订阅消息监听
HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
subscribe.removeSubscribe(hookSubscribe);
subscribe.removeSubscribe(Hook.getInstance(HookType.on_media_arrival, "rtp", ssrcInfo.getStream(), mediaServerItem.getId()));
}
}else {
logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},码流:{},端口:{}, SSRC: {}",
@@ -426,11 +530,11 @@ public class PlayServiceImpl implements IPlayService {
}, userSetting.getPlayTimeout());
try {
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channel, (mediaServerItemInuse, hookParam ) -> {
logger.info("收到订阅消息: " + hookParam);
cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channel, (hookData ) -> {
logger.info("收到订阅消息: " + hookData);
dynamicTask.stop(timeOutTaskKey);
// hook响应
StreamInfo streamInfo = onPublishHandlerForPlay(mediaServerItemInuse, hookParam, device.getDeviceId(), channel.getChannelId());
StreamInfo streamInfo = onPublishHandlerForPlay(hookData.getMediaServer(), hookData.getMediaInfo(), device.getDeviceId(), channel.getChannelId());
if (streamInfo == null){
callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getMsg(), null);
@@ -446,7 +550,7 @@ public class PlayServiceImpl implements IPlayService {
streamInfo);
logger.info("[点播成功] deviceId: {}, channelId:{}, 码流类型:{}", device.getDeviceId(), channel.getChannelId(),
channel.getStreamIdentification());
snapOnPlay(mediaServerItemInuse, device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream());
snapOnPlay(hookData.getMediaServer(), device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream());
}, (eventResult) -> {
// 处理收到200ok后的TCP主动连接以及SSRC不一致的问题
InviteOKHandler(eventResult, ssrcInfo, mediaServerItem, device, channel.getChannelId(),
@@ -460,8 +564,7 @@ public class PlayServiceImpl implements IPlayService {
streamSession.remove(device.getDeviceId(), channel.getChannelId(), ssrcInfo.getStream());
callback.run(InviteErrorCode.ERROR_FOR_SIGNALLING_ERROR.getCode(),
String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
callback.run(event.statusCode, event.msg, null);
inviteStreamService.call(InviteSessionType.PLAY, device.getDeviceId(), channel.getChannelId(), null,
InviteErrorCode.ERROR_FOR_RESET_SSRC.getCode(),
String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg), null);
@@ -489,7 +592,7 @@ public class PlayServiceImpl implements IPlayService {
}
private void tcpActiveHandler(Device device, String channelId, String contentString,
MediaServerItem mediaServerItem,
MediaServer mediaServerItem,
String timeOutTaskKey, SSRCInfo ssrcInfo, ErrorCallback<Object> callback){
if (!device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE")) {
return;
@@ -516,9 +619,9 @@ public class PlayServiceImpl implements IPlayService {
}
}
logger.info("[TCP主动连接对方] deviceId: {}, channelId: {}, 连接对方的地址:{}:{}, 收流模式:{}, SSRC: {}, SSRC校验{}", device.getDeviceId(), channelId, sdp.getConnection().getAddress(), port, device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
JSONObject jsonObject = zlmresTfulUtils.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
logger.info("[TCP主动连接对方] 结果: {}" , jsonObject);
if (jsonObject.getInteger("code") != 0) {
Boolean result = mediaServerService.connectRtpServer(mediaServerItem, sdp.getConnection().getAddress(), port, ssrcInfo.getStream());
logger.info("[TCP主动连接对方] 结果: {}" , result);
if (!result) {
// 主动连接失败,结束流程, 清理数据
dynamicTask.stop(timeOutTaskKey);
mediaServerService.closeRTPServer(mediaServerItem, ssrcInfo.getStream());
@@ -558,7 +661,7 @@ public class PlayServiceImpl implements IPlayService {
* @param channelId 通道 ID
* @param stream ssrc
*/
private void snapOnPlay(MediaServerItem mediaServerItemInuse, String deviceId, String channelId, String stream) {
private void snapOnPlay(MediaServer mediaServerItemInuse, String deviceId, String channelId, String stream) {
String streamUrl;
if (mediaServerItemInuse.getRtspPort() != 0) {
streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", stream);
@@ -569,14 +672,13 @@ public class PlayServiceImpl implements IPlayService {
String fileName = deviceId + "_" + channelId + ".jpg";
// 请求截图
logger.info("[请求截图]: " + fileName);
zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
mediaServerService.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
}
public StreamInfo onPublishHandlerForPlay(MediaServerItem mediaServerItem, HookParam hookParam, String deviceId, String channelId) {
public StreamInfo onPublishHandlerForPlay(MediaServer mediaServerItem, MediaInfo mediaInfo, String deviceId, String channelId) {
StreamInfo streamInfo = null;
Device device = redisCatchStorage.getDevice(deviceId);
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam)hookParam;
streamInfo = onPublishHandler(mediaServerItem, streamChangedHookParam, deviceId, channelId);
streamInfo = onPublishHandler(mediaServerItem, mediaInfo, deviceId, channelId);
if (streamInfo != null) {
DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
if (deviceChannel != null) {
@@ -594,9 +696,8 @@ public class PlayServiceImpl implements IPlayService {
}
private StreamInfo onPublishHandlerForPlayback(MediaServerItem mediaServerItem, HookParam param, String deviceId, String channelId, String startTime, String endTime) {
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam) param;
StreamInfo streamInfo = onPublishHandler(mediaServerItem, streamChangedHookParam, deviceId, channelId);
private StreamInfo onPublishHandlerForPlayback(MediaServer mediaServerItem, MediaInfo mediaInfo, String deviceId, String channelId, String startTime, String endTime) {
StreamInfo streamInfo = onPublishHandler(mediaServerItem, mediaInfo, deviceId, channelId);
if (streamInfo != null) {
streamInfo.setStartTime(startTime);
streamInfo.setEndTime(endTime);
@@ -605,7 +706,7 @@ public class PlayServiceImpl implements IPlayService {
deviceChannel.setStreamId(streamInfo.getStream());
storager.startPlay(deviceId, channelId, streamInfo.getStream());
}
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, ((OnStreamChangedHookParam) param).getStream());
InviteInfo inviteInfo = inviteStreamService.getInviteInfoByStream(InviteSessionType.PLAYBACK, mediaInfo.getStream());
if (inviteInfo != null) {
inviteInfo.setStatus(InviteSessionStatus.ok);
@@ -618,11 +719,11 @@ public class PlayServiceImpl implements IPlayService {
}
@Override
public MediaServerItem getNewMediaServerItem(Device device) {
public MediaServer getNewMediaServerItem(Device device) {
if (device == null) {
return null;
}
MediaServerItem mediaServerItem;
MediaServer mediaServerItem;
if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) {
mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
} else {
@@ -643,7 +744,13 @@ public class PlayServiceImpl implements IPlayService {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到设备:" + deviceId);
}
MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
DeviceChannel channel = channelService.getOne(deviceId, channelId);
if (channel == null) {
logger.warn("[录像回放] 未找到通道 deviceId: {},channelId:{}", deviceId, channelId);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "未找到通道:" + channelId);
}
MediaServer newMediaServerItem = getNewMediaServerItem(device);
if (device.getStreamMode().equalsIgnoreCase("TCP-ACTIVE") && ! newMediaServerItem.isRtpEnable()) {
logger.warn("[录像回放] 单端口收流时不支持TCP主动方式收流 deviceId: {},channelId:{}", deviceId, channelId);
throw new ControllerException(ErrorCode.ERROR100.getCode(), "单端口收流时不支持TCP主动方式收流");
@@ -655,12 +762,12 @@ public class PlayServiceImpl implements IPlayService {
.replace(":", "")
.replace(" ", "");
String stream = deviceId + "_" + channelId + "_" + startTimeStr + "_" + endTimeTimeStr;
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, stream, null, device.isSsrcCheck(), true, 0, false, !channel.isHasAudio(), false, device.getStreamModeForParam());
playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, callback);
}
@Override
public void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,
public void playBack(MediaServer mediaServerItem, SSRCInfo ssrcInfo,
String deviceId, String channelId, String startTime,
String endTime, ErrorCallback<Object> callback) {
if (mediaServerItem == null || ssrcInfo == null) {
@@ -711,10 +818,10 @@ public class PlayServiceImpl implements IPlayService {
inviteStreamService.removeInviteInfo(inviteInfo);
};
ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, hookParam) -> {
logger.info("收到回放订阅消息: " + hookParam);
HookSubscribe.Event hookEvent = (hookData) -> {
logger.info("收到回放订阅消息: " + hookData);
dynamicTask.stop(playBackTimeOutTaskKey);
StreamInfo streamInfo = onPublishHandlerForPlayback(mediaServerItemInuse, hookParam, deviceId, channelId, startTime, endTime);
StreamInfo streamInfo = onPublishHandlerForPlayback(hookData.getMediaServer(), hookData.getMediaInfo(), deviceId, channelId, startTime, endTime);
if (streamInfo == null) {
logger.warn("设备回放API调用失败");
callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
@@ -744,7 +851,7 @@ public class PlayServiceImpl implements IPlayService {
}
private void InviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, MediaServerItem mediaServerItem,
private void InviteOKHandler(SipSubscribe.EventResult eventResult, SSRCInfo ssrcInfo, MediaServer mediaServerItem,
Device device, String channelId, String timeOutTaskKey, ErrorCallback<Object> callback,
InviteInfo inviteInfo, InviteSessionType inviteSessionType){
inviteInfo.setStatus(InviteSessionStatus.ok);
@@ -840,7 +947,11 @@ public class PlayServiceImpl implements IPlayService {
if (device == null) {
return;
}
MediaServerItem newMediaServerItem = this.getNewMediaServerItem(device);
DeviceChannel channel = channelService.getOne(deviceId, channelId);
if (channel == null) {
return;
}
MediaServer newMediaServerItem = this.getNewMediaServerItem(device);
if (newMediaServerItem == null) {
callback.run(InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getCode(),
InviteErrorCode.ERROR_FOR_ASSIST_NOT_READY.getMsg(),
@@ -848,13 +959,13 @@ public class PlayServiceImpl implements IPlayService {
return;
}
// 录像下载不使用固定流地址,固定流地址会导致如果开始时间与结束时间一致时文件错误的叠加在一起
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,false, device.getStreamModeForParam());
SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, null, device.isSsrcCheck(), true, 0, false,!channel.isHasAudio(), false, device.getStreamModeForParam());
download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, callback);
}
@Override
public void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) {
public void download(MediaServer mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, ErrorCallback<Object> callback) {
if (mediaServerItem == null || ssrcInfo == null) {
callback.run(InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getCode(),
InviteErrorCode.ERROR_FOR_PARAMETER_ERROR.getMsg(),
@@ -900,10 +1011,10 @@ public class PlayServiceImpl implements IPlayService {
streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
inviteStreamService.removeInviteInfo(inviteInfo);
};
ZlmHttpHookSubscribe.Event hookEvent = (mediaServerItemInuse, hookParam) -> {
logger.info("[录像下载]收到订阅消息: " + hookParam);
HookSubscribe.Event hookEvent = (hookData) -> {
logger.info("[录像下载]收到订阅消息: " + hookData);
dynamicTask.stop(downLoadTimeOutTaskKey);
StreamInfo streamInfo = onPublishHandlerForDownload(mediaServerItemInuse, hookParam, deviceId, channelId, startTime, endTime);
StreamInfo streamInfo = onPublishHandlerForDownload(hookData.getMediaServer(), hookData.getMediaInfo(), deviceId, channelId, startTime, endTime);
if (streamInfo == null) {
logger.warn("[录像下载] 获取流地址信息失败");
callback.run(InviteErrorCode.ERROR_FOR_STREAM_PARSING_EXCEPTIONS.getCode(),
@@ -921,26 +1032,24 @@ public class PlayServiceImpl implements IPlayService {
downLoadTimeOutTaskKey, callback, inviteInfo, InviteSessionType.DOWNLOAD);
// 注册录像回调事件,录像下载结束后写入下载地址
ZlmHttpHookSubscribe.Event hookEventForRecord = (mediaServerItemInuse, hookParam) -> {
HookSubscribe.Event hookEventForRecord = (hookData) -> {
logger.info("[录像下载] 收到录像写入磁盘消息: {}/{}-{}",
inviteInfo.getDeviceId(), inviteInfo.getChannelId(), ssrcInfo.getStream());
logger.info("[录像下载] 收到录像写入磁盘消息内容: " + hookParam);
OnRecordMp4HookParam recordMp4HookParam = (OnRecordMp4HookParam)hookParam;
String filePath = recordMp4HookParam.getFile_path();
logger.info("[录像下载] 收到录像写入磁盘消息内容: " + hookData);
RecordInfo recordInfo = hookData.getRecordInfo();
String filePath = recordInfo.getFilePath();
DownloadFileInfo downloadFileInfo = CloudRecordUtils.getDownloadFilePath(mediaServerItem, filePath);
InviteInfo inviteInfoForNew = inviteStreamService.getInviteInfo(inviteInfo.getType(), inviteInfo.getDeviceId()
, inviteInfo.getChannelId(), inviteInfo.getStream());
inviteInfoForNew.getStreamInfo().setDownLoadFilePath(downloadFileInfo);
inviteStreamService.updateInviteInfo(inviteInfoForNew);
};
HookSubscribeForRecordMp4 hookSubscribe = HookSubscribeFactory.on_record_mp4(
mediaServerItem.getId(), "rtp", ssrcInfo.getStream());
Hook hook = Hook.getInstance(HookType.on_record_mp4, "rtp", ssrcInfo.getStream(), mediaServerItem.getId());
// 设置过期时间,下载失败时自动处理订阅数据
// long difference = DateUtil.getDifference(startTime, endTime)/1000;
// Instant expiresInstant = Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(difference * 2));
// hookSubscribe.setExpires(expiresInstant);
subscribe.addSubscribe(hookSubscribe, hookEventForRecord);
subscribe.addSubscribe(hook, hookEventForRecord);
});
} catch (InvalidArgumentException | SipException | ParseException e) {
logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
@@ -967,7 +1076,7 @@ public class PlayServiceImpl implements IPlayService {
// 获取当前已下载时长
String mediaServerId = inviteInfo.getStreamInfo().getMediaServerId();
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem == null) {
logger.warn("[获取下载进度] 查询录像信息时发现节点不存在");
return null;
@@ -978,30 +1087,13 @@ public class PlayServiceImpl implements IPlayService {
logger.warn("[获取下载进度] 下载已结束");
return null;
}
JSONObject mediaListJson= zlmresTfulUtils.getMediaList(mediaServerItem, "rtp", stream);
if (mediaListJson == null) {
logger.warn("[获取下载进度] 从zlm查询进度失败");
String app = "rtp";
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServerItem, app, stream);
if (mediaInfo == null) {
logger.warn("[获取下载进度] 查询进度失败, 节点Id {} {}/{}", mediaServerId, app, stream);
return null;
}
if (mediaListJson.getInteger("code") != 0) {
logger.warn("[获取下载进度] 从zlm查询进度出现错误 {}", mediaListJson.getString("msg"));
return null;
}
JSONArray data = mediaListJson.getJSONArray("data");
if (data == null) {
logger.warn("[获取下载进度] 从zlm查询进度时未返回数据");
return null;
}
JSONObject mediaJSON = data.getJSONObject(0);
JSONArray tracks = mediaJSON.getJSONArray("tracks");
if (tracks.isEmpty()) {
logger.warn("[获取下载进度] 从zlm查询进度时未返回数据");
return null;
}
JSONObject jsonObject = tracks.getJSONObject(0);
long duration = jsonObject.getLongValue("duration");
if (duration == 0) {
if (mediaInfo.getDuration() == 0) {
inviteInfo.getStreamInfo().setProgress(0);
} else {
String startTime = inviteInfo.getStreamInfo().getStartTime();
@@ -1010,7 +1102,7 @@ public class PlayServiceImpl implements IPlayService {
long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
BigDecimal currentCount = new BigDecimal(duration);
BigDecimal currentCount = new BigDecimal(mediaInfo.getDuration());
BigDecimal totalCount = new BigDecimal((end - start) * 1000);
BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
double process = divide.doubleValue();
@@ -1023,9 +1115,8 @@ public class PlayServiceImpl implements IPlayService {
return inviteInfo.getStreamInfo();
}
private StreamInfo onPublishHandlerForDownload(MediaServerItem mediaServerItemInuse, HookParam hookParam, String deviceId, String channelId, String startTime, String endTime) {
OnStreamChangedHookParam streamChangedHookParam = (OnStreamChangedHookParam) hookParam;
StreamInfo streamInfo = onPublishHandler(mediaServerItemInuse, streamChangedHookParam, deviceId, channelId);
private StreamInfo onPublishHandlerForDownload(MediaServer mediaServerItemInuse, MediaInfo mediaInfo, String deviceId, String channelId, String startTime, String endTime) {
StreamInfo streamInfo = onPublishHandler(mediaServerItemInuse, mediaInfo, deviceId, channelId);
if (streamInfo != null) {
streamInfo.setProgress(0);
streamInfo.setStartTime(startTime);
@@ -1042,8 +1133,8 @@ public class PlayServiceImpl implements IPlayService {
}
public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, OnStreamChangedHookParam hookParam, String deviceId, String channelId) {
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", hookParam.getStream(), hookParam.getTracks(), null);
public StreamInfo onPublishHandler(MediaServer mediaServerItem, MediaInfo mediaInfo, String deviceId, String channelId) {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", mediaInfo.getStream(), mediaInfo, null);
streamInfo.setDeviceID(deviceId);
streamInfo.setChannelId(channelId);
return streamInfo;
@@ -1099,7 +1190,7 @@ public class PlayServiceImpl implements IPlayService {
logger.warn("开启语音广播的时候未找到通道: {}", channelId);
return null;
}
MediaServerItem mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
MediaServer mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
if (broadcastMode == null) {
broadcastMode = true;
}
@@ -1108,13 +1199,13 @@ public class PlayServiceImpl implements IPlayService {
AudioBroadcastResult audioBroadcastResult = new AudioBroadcastResult();
audioBroadcastResult.setApp(app);
audioBroadcastResult.setStream(stream);
audioBroadcastResult.setStreamInfo(new StreamContent(mediaService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false)));
audioBroadcastResult.setStreamInfo(new StreamContent(mediaServerService.getStreamInfoByAppAndStream(mediaServerItem, app, stream, null, null, null, false)));
audioBroadcastResult.setCodec("G.711");
return audioBroadcastResult;
}
@Override
public boolean audioBroadcastCmd(Device device, String channelId, MediaServerItem mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
public boolean audioBroadcastCmd(Device device, String channelId, MediaServer mediaServerItem, String app, String stream, int timeout, boolean isFromPlatform, AudioBroadcastEvent event) throws InvalidArgumentException, ParseException, SipException {
if (device == null || channelId == null) {
return false;
}
@@ -1130,7 +1221,7 @@ public class PlayServiceImpl implements IPlayService {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
// 查询流是否存在,不存在则认为是异常状态
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream());
Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, sendRtpItem.getApp(), sendRtpItem.getStream());
if (streamReady) {
logger.warn("语音广播已经开启: {}", channelId);
event.call("语音广播已经开启");
@@ -1140,18 +1231,6 @@ public class PlayServiceImpl implements IPlayService {
}
}
}
// SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
// if (sendRtpItem != null) {
// MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
// Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
// if (streamReady) {
// logger.warn("[语音对讲] 进行中: {}", channelId);
// event.call("语音对讲进行中");
// return false;
// } else {
// stopTalk(device, channelId);
// }
// }
// 发送通知
cmder.audioBroadcastCmd(device, channelId, eventResultForOk -> {
@@ -1182,8 +1261,8 @@ public class PlayServiceImpl implements IPlayService {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
// 查询流是否存在,不存在则认为是异常状态
MediaServerItem mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStream());
MediaServer mediaServerServiceOne = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Boolean streamReady = mediaServerService.isStreamReady(mediaServerServiceOne, sendRtpItem.getApp(), sendRtpItem.getStream());
if (streamReady) {
logger.warn("语音广播通道使用中: {}", channelId);
return true;
@@ -1212,12 +1291,8 @@ public class PlayServiceImpl implements IPlayService {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(deviceId, audioBroadcastCatch.getChannelId(), null, null);
if (sendRtpItem != null) {
redisCatchStorage.deleteSendRTPServer(deviceId, sendRtpItem.getChannelId(), null, null);
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
zlmresTfulUtils.stopSendRtp(mediaInfo, param);
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
mediaServerService.stopSendRtp(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream(), null);
try {
cmder.streamByeCmdForDeviceInvite(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
} catch (InvalidArgumentException | ParseException | SipException |
@@ -1293,7 +1368,7 @@ public class PlayServiceImpl implements IPlayService {
}
inviteInfo.getStreamInfo().setPause(true);
inviteStreamService.updateInviteInfo(inviteInfo);
MediaServerItem mediaServerItem = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId());
MediaServer mediaServerItem = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId());
if (null == mediaServerItem) {
logger.warn("mediaServer 不存在!");
throw new ServiceException("mediaServer不存在");
@@ -1304,8 +1379,8 @@ public class PlayServiceImpl implements IPlayService {
if (!mediaServerItem.isRtpEnable()) {
streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase();
}
JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamKey);
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
Boolean result = mediaServerService.pauseRtpCheck(mediaServerItem, streamKey);
if (!result) {
throw new ServiceException("暂停RTP接收失败");
}
Device device = storager.queryVideoDevice(inviteInfo.getDeviceId());
@@ -1321,7 +1396,7 @@ public class PlayServiceImpl implements IPlayService {
}
inviteInfo.getStreamInfo().setPause(false);
inviteStreamService.updateInviteInfo(inviteInfo);
MediaServerItem mediaServerItem = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId());
MediaServer mediaServerItem = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId());
if (null == mediaServerItem) {
logger.warn("mediaServer 不存在!");
throw new ServiceException("mediaServer不存在");
@@ -1332,8 +1407,8 @@ public class PlayServiceImpl implements IPlayService {
if (!mediaServerItem.isRtpEnable()) {
streamKey = Long.toHexString(Long.parseLong(inviteInfo.getSsrcInfo().getSsrc())).toUpperCase();
}
JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamKey);
if (jsonObject == null || jsonObject.getInteger("code") != 0) {
boolean result = mediaServerService.resumeRtpCheck(mediaServerItem, streamKey);
if (!result) {
throw new ServiceException("继续RTP接收失败");
}
Device device = storager.queryVideoDevice(inviteInfo.getDeviceId());
@@ -1343,24 +1418,7 @@ public class PlayServiceImpl implements IPlayService {
@Override
public void startPushStream(SendRtpItem sendRtpItem, SIPResponse sipResponse, ParentPlatform platform, CallIdHeader callIdHeader) {
// 开始发流
String is_Udp = sendRtpItem.isTcp() ? "0" : "1";
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
logger.info("[开始推流] rtp/{}, 目标={}:{}SSRC={}, RTCP={}", sendRtpItem.getStream(),
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isRtcp());
Map<String, Object> param = new HashMap<>(12);
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
param.put("ssrc", sendRtpItem.getSsrc());
param.put("src_port", sendRtpItem.getLocalPort());
param.put("pt", sendRtpItem.getPt());
param.put("use_ps", sendRtpItem.isUsePs() ? "1" : "0");
param.put("only_audio", sendRtpItem.isOnlyAudio() ? "1" : "0");
param.put("is_udp", is_Udp);
if (!sendRtpItem.isTcp()) {
// udp模式下开启rtcp保活
param.put("udp_rtcp_timeout", sendRtpItem.isRtcp() ? "1" : "0");
}
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaInfo == null) {
RequestPushStreamMsg requestPushStreamMsg = RequestPushStreamMsg.getInstance(
@@ -1368,80 +1426,55 @@ public class PlayServiceImpl implements IPlayService {
sendRtpItem.getIp(), sendRtpItem.getPort(), sendRtpItem.getSsrc(), sendRtpItem.isTcp(),
sendRtpItem.getLocalPort(), sendRtpItem.getPt(), sendRtpItem.isUsePs(), sendRtpItem.isOnlyAudio());
redisGbPlayMsgListener.sendMsgForStartSendRtpStream(sendRtpItem.getServerId(), requestPushStreamMsg, json -> {
startSendRtpStreamHand(sendRtpItem, platform, json, param, callIdHeader);
startSendRtpStreamFailHand(sendRtpItem, platform, callIdHeader);
});
} else {
// 如果是严格模式,需要关闭端口占用
JSONObject startSendRtpStreamResult = null;
if (sendRtpItem.getLocalPort() != 0) {
try {
if (sendRtpItem.isTcpActive()) {
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
mediaServerService.startSendRtpPassive(mediaInfo, platform, sendRtpItem, null);
} else {
param.put("dst_url", sendRtpItem.getIp());
param.put("dst_port", sendRtpItem.getPort());
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
}
} else {
if (sendRtpItem.isTcpActive()) {
startSendRtpStreamResult = zlmServerFactory.startSendRtpPassive(mediaInfo, param);
} else {
param.put("dst_url", sendRtpItem.getIp());
param.put("dst_port", sendRtpItem.getPort());
startSendRtpStreamResult = zlmServerFactory.startSendRtpStream(mediaInfo, param);
mediaServerService.startSendRtpStream(mediaInfo, platform, sendRtpItem);
}
}catch (ControllerException e) {
logger.error("RTP推流失败: {}", e.getMessage());
startSendRtpStreamFailHand(sendRtpItem, platform, callIdHeader);
return;
}
if (startSendRtpStreamResult != null) {
startSendRtpStreamHand(sendRtpItem, platform, startSendRtpStreamResult, param, callIdHeader);
}
logger.info("RTP推流成功[ {}/{} ]{}, ", sendRtpItem.getApp(), sendRtpItem.getStream(),
sendRtpItem.isTcpActive()?"被动发流": sendRtpItem.getIp() + ":" + sendRtpItem.getPort());
}
}
@Override
public void startSendRtpStreamHand(SendRtpItem sendRtpItem, Object correlationInfo,
JSONObject jsonObject, Map<String, Object> param, CallIdHeader callIdHeader) {
if (jsonObject == null) {
logger.error("RTP推流失败: 请检查ZLM服务");
} else if (jsonObject.getInteger("code") == 0) {
logger.info("调用ZLM推流接口, 结果: {}", jsonObject);
logger.info("RTP推流成功[ {}/{} ]{}->{}, ", param.get("app"), param.get("stream"), jsonObject.getString("local_port"),
sendRtpItem.isTcpActive()?"被动发流": param.get("dst_url") + ":" + param.get("dst_port"));
if (sendRtpItem.getPlayType() == InviteStreamType.PUSH && correlationInfo instanceof ParentPlatform) {
ParentPlatform platform = (ParentPlatform)correlationInfo;
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0, sendRtpItem.getApp(), sendRtpItem.getStream(),
sendRtpItem.getChannelId(), platform.getServerGBId(), platform.getName(), userSetting.getServerId(),
sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(platform.getId());
redisCatchStorage.sendPlatformStartPlayMsg(messageForPushChannel);
public void startSendRtpStreamFailHand(SendRtpItem sendRtpItem, ParentPlatform platform, CallIdHeader callIdHeader) {
if (sendRtpItem.isOnlyAudio()) {
Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
if (audioBroadcastCatch != null) {
try {
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
} catch (SipException | ParseException | InvalidArgumentException |
SsrcTransactionNotFoundException exception) {
logger.error("[命令发送失败] 停止语音对讲: {}", exception.getMessage());
}
}
} else {
logger.error("RTP推流失败: {}, 参数:{}", jsonObject.getString("msg"), JSONObject.toJSONString(param));
if (sendRtpItem.isOnlyAudio()) {
Device device = deviceService.getDevice(sendRtpItem.getDeviceId());
AudioBroadcastCatch audioBroadcastCatch = audioBroadcastManager.get(sendRtpItem.getDeviceId(), sendRtpItem.getChannelId());
if (audioBroadcastCatch != null) {
try {
cmder.streamByeCmd(device, sendRtpItem.getChannelId(), audioBroadcastCatch.getSipTransactionInfo(), null);
} catch (SipException | ParseException | InvalidArgumentException |
SsrcTransactionNotFoundException e) {
logger.error("[命令发送失败] 停止语音对讲: {}", e.getMessage());
}
}
} else {
if (platform != null) {
// 向上级平台
if (correlationInfo instanceof ParentPlatform) {
try {
ParentPlatform parentPlatform = (ParentPlatform)correlationInfo;
commanderForPlatform.streamByeCmd(parentPlatform, callIdHeader.getCallId());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
}
try {
commanderForPlatform.streamByeCmd(platform, callIdHeader.getCallId());
} catch (SipException | InvalidArgumentException | ParseException e) {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
}
}
}
}
@Override
public void talkCmd(Device device, String channelId, MediaServerItem mediaServerItem, String stream, AudioBroadcastEvent event) {
public void talkCmd(Device device, String channelId, MediaServer mediaServerItem, String stream, AudioBroadcastEvent event) {
if (device == null || channelId == null) {
return;
}
@@ -1458,8 +1491,8 @@ public class PlayServiceImpl implements IPlayService {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, null, null);
if (sendRtpItem != null && sendRtpItem.isOnlyAudio()) {
// 查询流是否存在,不存在则认为是异常状态
MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Boolean streamReady = mediaServerService.isStreamReady(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream());
if (streamReady) {
logger.warn("[语音对讲] 正在语音广播,无法开启语音通话: {}", channelId);
event.call("正在语音广播");
@@ -1472,8 +1505,8 @@ public class PlayServiceImpl implements IPlayService {
SendRtpItem sendRtpItem = redisCatchStorage.querySendRTPServer(device.getDeviceId(), channelId, stream, null);
if (sendRtpItem != null) {
MediaServerItem mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServer, "rtp", sendRtpItem.getReceiveStream());
MediaServer mediaServer = mediaServerService.getOne(sendRtpItem.getMediaServerId());
Boolean streamReady = mediaServerService.isStreamReady(mediaServer, "rtp", sendRtpItem.getReceiveStream());
if (streamReady) {
logger.warn("[语音对讲] 进行中: {}", channelId);
event.call("语音对讲进行中");
@@ -1483,7 +1516,7 @@ public class PlayServiceImpl implements IPlayService {
}
}
talk(mediaServerItem, device, channelId, stream, (mediaServerItem1, hookParam) -> {
talk(mediaServerItem, device, channelId, stream, (hookData) -> {
logger.info("[语音对讲] 收到设备发来的流");
}, eventResult -> {
logger.warn("[语音对讲] 失败,{}/{}, 错误码 {} {}", device.getDeviceId(), channelId, eventResult.statusCode, eventResult.msg);
@@ -1517,15 +1550,10 @@ public class PlayServiceImpl implements IPlayService {
return;
}
MediaServerItem mediaServer = mediaServerService.getOne(mediaServerId);
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (streamIsReady == null || streamIsReady) {
Map<String, Object> param = new HashMap<>();
param.put("vhost", "__defaultVhost__");
param.put("app", sendRtpItem.getApp());
param.put("stream", sendRtpItem.getStream());
param.put("ssrc", sendRtpItem.getSsrc());
zlmServerFactory.stopSendRtpStream(mediaServer, param);
mediaServerService.stopSendRtp(mediaServer, sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getSsrc());
}
ssrcFactory.releaseSsrc(mediaServerId, sendRtpItem.getSsrc());
@@ -1552,7 +1580,7 @@ public class PlayServiceImpl implements IPlayService {
if (inviteInfo != null) {
if (inviteInfo.getStreamInfo() != null) {
// 已存在线直接截图
MediaServerItem mediaServerItemInuse = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId());
MediaServer mediaServerItemInuse = mediaServerService.getOne(inviteInfo.getStreamInfo().getMediaServerId());
String streamUrl;
if (mediaServerItemInuse.getRtspPort() != 0) {
streamUrl = String.format("rtsp://127.0.0.1:%s/%s/%s", mediaServerItemInuse.getRtspPort(), "rtp", inviteInfo.getStreamInfo().getStream());
@@ -1562,7 +1590,7 @@ public class PlayServiceImpl implements IPlayService {
String path = "snap";
// 请求截图
logger.info("[请求截图]: " + fileName);
zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
mediaServerService.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
File snapFile = new File(path + File.separator + fileName);
if (snapFile.exists()) {
errorCallback.run(InviteErrorCode.SUCCESS.getCode(), InviteErrorCode.SUCCESS.getMsg(), snapFile.getAbsoluteFile());
@@ -1573,7 +1601,7 @@ public class PlayServiceImpl implements IPlayService {
}
}
MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
MediaServer newMediaServerItem = getNewMediaServerItem(device);
play(newMediaServerItem, deviceId, channelId, null, (code, msg, data)->{
if (code == InviteErrorCode.SUCCESS.getCode()) {
InviteInfo inviteInfoForPlay = inviteStreamService.getInviteInfoByDeviceAndChannel(InviteSessionType.PLAY, deviceId, channelId);

View File

@@ -1,6 +1,5 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.common.GeneralCallback;
@@ -9,17 +8,19 @@ import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.conf.exception.ControllerException;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.event.media.MediaNotFoundEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.StreamProxyItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IMediaService;
import com.genersoft.iot.vmp.service.IStreamProxyService;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
@@ -29,11 +30,14 @@ import com.genersoft.iot.vmp.storager.dao.StreamProxyMapper;
import com.genersoft.iot.vmp.utils.DateUtil;
import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
import com.genersoft.iot.vmp.vmanager.bean.ResourceBaseInfo;
import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
@@ -60,12 +64,6 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
@Autowired
private IVideoManagerStorage videoManagerStorager;
@Autowired
private IMediaService mediaService;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired
private ZLMServerFactory zlmServerFactory;
@@ -94,7 +92,7 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
private IMediaServerService mediaServerService;
@Autowired
private ZlmHttpHookSubscribe hookSubscribe;
private HookSubscribe hookSubscribe;
@Autowired
private DynamicTask dynamicTask;
@@ -105,31 +103,62 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
@Autowired
TransactionDefinition transactionDefinition;
/**
* 流到来的处理
*/
@Async("taskExecutor")
@org.springframework.context.event.EventListener
public void onApplicationEvent(MediaArrivalEvent event) {
if ("rtsp".equals(event.getSchema())) {
updateStatus(true, event.getApp(), event.getStream());
}
}
/**
* 流离开的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaDepartureEvent event) {
if ("rtsp".equals(event.getSchema())) {
updateStatus(false, event.getApp(), event.getStream());
}
}
/**
* 流离开的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaNotFoundEvent event) {
if ("rtp".equals(event.getApp())) {
return;
}
// 拉流代理
StreamProxyItem streamProxyByAppAndStream = getStreamProxyByAppAndStream(event.getApp(), event.getStream());
if (streamProxyByAppAndStream != null && streamProxyByAppAndStream.isEnableDisableNoneReader()) {
start(event.getApp(), event.getStream());
}
}
@Override
public void save(StreamProxyItem param, GeneralCallback<StreamInfo> callback) {
MediaServerItem mediaInfo;
MediaServer mediaServer;
if (ObjectUtils.isEmpty(param.getMediaServerId()) || "auto".equals(param.getMediaServerId())){
mediaInfo = mediaServerService.getMediaServerForMinimumLoad(null);
mediaServer = mediaServerService.getMediaServerForMinimumLoad(null);
}else {
mediaInfo = mediaServerService.getOne(param.getMediaServerId());
mediaServer = mediaServerService.getOne(param.getMediaServerId());
}
if (mediaInfo == null) {
if (mediaServer == null) {
logger.warn("保存代理未找到在线的ZLM...");
throw new ControllerException(ErrorCode.ERROR100.getCode(), "保存代理未找到在线的ZLM");
}
String dstUrl;
if ("ffmpeg".equalsIgnoreCase(param.getType())) {
JSONObject jsonObject = zlmresTfulUtils.getMediaServerConfig(mediaInfo);
if (jsonObject.getInteger("code") != 0) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "获取流媒体配置失败");
}
JSONArray dataArray = jsonObject.getJSONArray("data");
JSONObject mediaServerConfig = dataArray.getJSONObject(0);
if (ObjectUtils.isEmpty(param.getFfmpegCmdKey())) {
param.setFfmpegCmdKey("ffmpeg.cmd");
}
String ffmpegCmd = mediaServerConfig.getString(param.getFfmpegCmdKey());
String ffmpegCmd = mediaServerService.getFfmpegCmd(mediaServer, param.getFfmpegCmdKey());
if (ffmpegCmd == null) {
throw new ControllerException(ErrorCode.ERROR100.getCode(), "ffmpeg拉流代理无法获取ffmpeg cmd");
}
@@ -140,25 +169,25 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
int port;
String schemaForUri;
if (schema.equalsIgnoreCase("rtsp")) {
port = mediaInfo.getRtspPort();
port = mediaServer.getRtspPort();
schemaForUri = schema;
}else if (schema.equalsIgnoreCase("flv")) {
port = mediaInfo.getRtmpPort();
port = mediaServer.getRtmpPort();
schemaForUri = schema;
}else {
port = mediaInfo.getRtmpPort();
port = mediaServer.getRtmpPort();
schemaForUri = schema;
}
dstUrl = String.format("%s://%s:%s/%s/%s", schemaForUri, "127.0.0.1", port, param.getApp(),
param.getStream());
}else {
dstUrl = String.format("rtsp://%s:%s/%s/%s", "127.0.0.1", mediaInfo.getRtspPort(), param.getApp(),
dstUrl = String.format("rtsp://%s:%s/%s/%s", "127.0.0.1", mediaServer.getRtspPort(), param.getApp(),
param.getStream());
}
param.setDstUrl(dstUrl);
logger.info("[拉流代理] 输出地址为:{}", dstUrl);
param.setMediaServerId(mediaInfo.getId());
param.setMediaServerId(mediaServer.getId());
boolean saveResult;
// 更新
if (videoManagerStorager.queryStreamProxy(param.getApp(), param.getStream()) != null) {
@@ -170,17 +199,17 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
callback.run(ErrorCode.ERROR100.getCode(), "保存失败", null);
return;
}
HookSubscribeForStreamChange hookSubscribeForStreamChange = HookSubscribeFactory.on_stream_changed(param.getApp(), param.getStream(), true, "rtsp", mediaInfo.getId());
hookSubscribe.addSubscribe(hookSubscribeForStreamChange, (mediaServerItem, response) -> {
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
mediaInfo, param.getApp(), param.getStream(), null, null);
Hook hook = Hook.getInstance(HookType.on_media_arrival, param.getApp(), param.getStream(), mediaServer.getId());
hookSubscribe.addSubscribe(hook, (hookData) -> {
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(
mediaServer, param.getApp(), param.getStream(), null, null);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
});
if (param.isEnable()) {
String talkKey = UUID.randomUUID().toString();
String delayTalkKey = UUID.randomUUID().toString();
dynamicTask.startDelay(delayTalkKey, ()->{
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStreamWithCheck(param.getApp(), param.getStream(), mediaInfo.getId(), false);
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStreamWithCheck(param.getApp(), param.getStream(), mediaServer.getId(), false);
if (streamInfo != null) {
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}else {
@@ -188,12 +217,12 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
callback.run(ErrorCode.ERROR100.getCode(), "超时", null);
}
}, 7000);
JSONObject jsonObject = addStreamProxyToZlm(param);
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
hookSubscribe.removeSubscribe(hookSubscribeForStreamChange);
WVPResult<String> result = addStreamProxyToZlm(param);
if (result != null && result.getCode() == 0) {
hookSubscribe.removeSubscribe(hook);
dynamicTask.stop(talkKey);
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
mediaInfo, param.getApp(), param.getStream(), null, null);
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(
mediaServer, param.getApp(), param.getStream(), null, null);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}else {
param.setEnable(false);
@@ -203,16 +232,16 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}else {
updateStreamProxy(param);
}
if (jsonObject == null){
if (result == null){
callback.run(ErrorCode.ERROR100.getCode(), "记录已保存,启用失败", null);
}else {
callback.run(ErrorCode.ERROR100.getCode(), jsonObject.getString("msg"), null);
callback.run(ErrorCode.ERROR100.getCode(), result.getMsg(), null);
}
}
}
else{
StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(
mediaInfo, param.getApp(), param.getStream(), null, null);
StreamInfo streamInfo = mediaServerService.getStreamInfoByAppAndStream(
mediaServer, param.getApp(), param.getStream(), null, null);
callback.run(ErrorCode.SUCCESS.getCode(), ErrorCode.SUCCESS.getMsg(), streamInfo);
}
}
@@ -308,38 +337,34 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}
@Override
public JSONObject addStreamProxyToZlm(StreamProxyItem param) {
JSONObject result = null;
MediaServerItem mediaServerItem = null;
public WVPResult<String> addStreamProxyToZlm(StreamProxyItem param) {
WVPResult<String> result = null;
MediaServer mediaServer = null;
if (param.getMediaServerId() == null) {
logger.warn("添加代理时MediaServerId 为null");
return null;
}else {
mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
mediaServer = mediaServerService.getOne(param.getMediaServerId());
}
if (mediaServerItem == null) {
if (mediaServer == null) {
return null;
}
if (zlmServerFactory.isStreamReady(mediaServerItem, param.getApp(), param.getStream())) {
zlmresTfulUtils.closeStreams(mediaServerItem, param.getApp(), param.getStream());
if (mediaServerService.isStreamReady(mediaServer, param.getApp(), param.getStream())) {
mediaServerService.closeStreams(mediaServer, param.getApp(), param.getStream());
}
String msgResult;
if ("ffmpeg".equalsIgnoreCase(param.getType())){
result = zlmresTfulUtils.addFFmpegSource(mediaServerItem, param.getSrcUrl().trim(), param.getDstUrl(),
param.getTimeoutMs() + "", param.isEnableAudio(), param.isEnableMp4(),
result = mediaServerService.addFFmpegSource(mediaServer, param.getSrcUrl().trim(), param.getDstUrl(),
param.getTimeoutMs(), param.isEnableAudio(), param.isEnableMp4(),
param.getFfmpegCmdKey());
}else {
result = zlmresTfulUtils.addStreamProxy(mediaServerItem, param.getApp(), param.getStream(), param.getUrl().trim(),
result = mediaServerService.addStreamProxy(mediaServer, param.getApp(), param.getStream(), param.getUrl().trim(),
param.isEnableAudio(), param.isEnableMp4(), param.getRtpType());
}
if (result != null && result.getInteger("code") == 0) {
JSONObject data = result.getJSONObject("data");
if (data == null) {
logger.warn("[获取拉流代理的结果数据Data] 失败: {}", result );
return result;
}
String key = data.getString("key");
if (result != null && result.getCode() == 0) {
String key = result.getData();
if (key == null) {
logger.warn("[获取拉流代理的结果数据Data中的KEY] 失败: {}", result );
logger.warn("[获取拉流代理的结果数据Data] 失败: {}", result );
return result;
}
param.setStreamKey(key);
@@ -349,16 +374,23 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}
@Override
public JSONObject removeStreamProxyFromZlm(StreamProxyItem param) {
public Boolean removeStreamProxyFromZlm(StreamProxyItem param) {
if (param ==null) {
return null;
}
MediaServerItem mediaServerItem = mediaServerService.getOne(param.getMediaServerId());
JSONObject result = null;
MediaServer mediaServer = mediaServerService.getOne(param.getMediaServerId());
if (mediaServer == null) {
return null;
}
List<StreamInfo> mediaList = mediaServerService.getMediaList(mediaServer, param.getApp(), param.getStream(), null);
if (mediaList == null || mediaList.isEmpty()) {
return true;
}
Boolean result = false;
if ("ffmpeg".equalsIgnoreCase(param.getType())){
result = zlmresTfulUtils.delFFmpegSource(mediaServerItem, param.getStreamKey());
result = mediaServerService.delFFmpegSource(mediaServer, param.getStreamKey());
}else {
result = zlmresTfulUtils.delStreamProxy(mediaServerItem, param.getStreamKey());
result = mediaServerService.delStreamProxy(mediaServer, param.getStreamKey());
}
return result;
}
@@ -379,8 +411,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
gbStreamMapper.del(app, stream);
videoManagerStorager.deleteStreamProxy(app, stream);
redisCatchStorage.removeStream(streamProxyItem.getMediaServerId(), "PULL", app, stream);
JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyItem);
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
Boolean result = removeStreamProxyFromZlm(streamProxyItem);
if (result != null && result) {
logger.info("[移除代理] 代理: {}/{}, 从zlm移除成功", app, stream);
}else {
logger.info("[移除代理] 代理: {}/{}, 从zlm移除失败", app, stream);
@@ -393,16 +425,16 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
boolean result = false;
StreamProxyItem streamProxy = videoManagerStorager.queryStreamProxy(app, stream);
if (streamProxy != null && !streamProxy.isEnable() ) {
JSONObject jsonObject = addStreamProxyToZlm(streamProxy);
if (jsonObject == null) {
WVPResult<String> wvpResult = addStreamProxyToZlm(streamProxy);
if (wvpResult == null) {
return false;
}
if (jsonObject.getInteger("code") == 0) {
if (wvpResult.getCode() == 0) {
result = true;
streamProxy.setEnable(true);
updateStreamProxy(streamProxy);
}else {
logger.info("启用代理失败: {}/{}->{}({})", app, stream, jsonObject.getString("msg"),
logger.info("启用代理失败: {}/{}->{}({})", app, stream, wvpResult.getMsg(),
streamProxy.getSrcUrl() == null? streamProxy.getUrl():streamProxy.getSrcUrl());
}
} else if (streamProxy != null && streamProxy.isEnable()) {
@@ -416,8 +448,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
boolean result = false;
StreamProxyItem streamProxyDto = videoManagerStorager.queryStreamProxy(app, stream);
if (streamProxyDto != null && streamProxyDto.isEnable()) {
JSONObject jsonObject = removeStreamProxyFromZlm(streamProxyDto);
if (jsonObject != null && jsonObject.getInteger("code") == 0) {
Boolean removed = removeStreamProxyFromZlm(streamProxyDto);
if (removed != null && removed) {
streamProxyDto.setEnable(false);
result = updateStreamProxy(streamProxyDto);
}
@@ -426,20 +458,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}
@Override
public JSONObject getFFmpegCMDs(MediaServerItem mediaServerItem) {
JSONObject result = new JSONObject();
JSONObject mediaServerConfigResuly = zlmresTfulUtils.getMediaServerConfig(mediaServerItem);
if (mediaServerConfigResuly != null && mediaServerConfigResuly.getInteger("code") == 0
&& mediaServerConfigResuly.getJSONArray("data").size() > 0){
JSONObject mediaServerConfig = mediaServerConfigResuly.getJSONArray("data").getJSONObject(0);
for (String key : mediaServerConfig.keySet()) {
if (key.startsWith("ffmpeg.cmd")){
result.put(key, mediaServerConfig.getString(key));
}
}
}
return result;
public Map<String, String> getFFmpegCMDs(MediaServer mediaServer) {
return mediaServerService.getFFmpegCMDs(mediaServer);
}
@@ -465,8 +485,8 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
mediaServerId, true);
for (StreamProxyItem streamProxyDto : streamProxyListForEnable) {
logger.info("恢复流代理," + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
JSONObject jsonObject = addStreamProxyToZlm(streamProxyDto);
if (jsonObject == null) {
WVPResult<String> wvpResult = addStreamProxyToZlm(streamProxyDto);
if (wvpResult == null) {
// 设置为离线
logger.info("恢复流代理失败" + streamProxyDto.getApp() + "/" + streamProxyDto.getStream());
updateStatus(false, streamProxyDto.getApp(), streamProxyDto.getStream());
@@ -516,44 +536,30 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
}
private void syncPullStream(String mediaServerId){
MediaServerItem mediaServer = mediaServerService.getOne(mediaServerId);
MediaServer mediaServer = mediaServerService.getOne(mediaServerId);
if (mediaServer != null) {
List<OnStreamChangedHookParam> allPullStream = redisCatchStorage.getStreams(mediaServerId, "PULL");
if (allPullStream.size() > 0) {
zlmresTfulUtils.getMediaList(mediaServer, jsonObject->{
Map<String, StreamInfo> stringStreamInfoMap = new HashMap<>();
if (jsonObject.getInteger("code") == 0) {
JSONArray data = jsonObject.getJSONArray("data");
if(data != null && data.size() > 0) {
for (int i = 0; i < data.size(); i++) {
JSONObject streamJSONObj = data.getJSONObject(i);
if ("rtsp".equals(streamJSONObj.getString("schema"))) {
StreamInfo streamInfo = new StreamInfo();
String app = streamJSONObj.getString("app");
String stream = streamJSONObj.getString("stream");
streamInfo.setApp(app);
streamInfo.setStream(stream);
stringStreamInfoMap.put(app+stream, streamInfo);
}
}
if (!allPullStream.isEmpty()) {
List<StreamInfo> mediaList = mediaServerService.getMediaList(mediaServer, null, null, null);
Map<String, StreamInfo> stringStreamInfoMap = new HashMap<>();
if (mediaList != null && !mediaList.isEmpty()) {
for (StreamInfo streamInfo : mediaList) {
stringStreamInfoMap.put(streamInfo.getApp() + streamInfo.getStream(), streamInfo);
}
}
if (stringStreamInfoMap.isEmpty()) {
redisCatchStorage.removeStream(mediaServerId, "PULL");
}else {
for (String key : stringStreamInfoMap.keySet()) {
StreamInfo streamInfo = stringStreamInfoMap.get(key);
if (stringStreamInfoMap.get(streamInfo.getApp() + streamInfo.getStream()) == null) {
redisCatchStorage.removeStream(mediaServerId, "PULL", streamInfo.getApp(),
streamInfo.getStream());
}
}
if (stringStreamInfoMap.size() == 0) {
redisCatchStorage.removeStream(mediaServerId, "PULL");
}else {
for (String key : stringStreamInfoMap.keySet()) {
StreamInfo streamInfo = stringStreamInfoMap.get(key);
if (stringStreamInfoMap.get(streamInfo.getApp() + streamInfo.getStream()) == null) {
redisCatchStorage.removeStream(mediaServerId, "PULL", streamInfo.getApp(),
streamInfo.getStream());
}
}
}
});
}
}
}
}
@Override
@@ -569,13 +575,13 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
@Scheduled(cron = "* 0/10 * * * ?")
public void asyncCheckStreamProxyStatus() {
List<MediaServerItem> all = mediaServerService.getAllOnline();
List<MediaServer> all = mediaServerService.getAllOnline();
if (CollectionUtils.isEmpty(all)){
return;
}
Map<String, MediaServerItem> serverItemMap = all.stream().collect(Collectors.toMap(MediaServerItem::getId, Function.identity(), (m1, m2) -> m1));
Map<String, MediaServer> serverItemMap = all.stream().collect(Collectors.toMap(MediaServer::getId, Function.identity(), (m1, m2) -> m1));
List<StreamProxyItem> list = videoManagerStorager.getStreamProxyListForEnable(true);
@@ -585,15 +591,14 @@ public class StreamProxyServiceImpl implements IStreamProxyService {
for (StreamProxyItem streamProxyItem : list) {
MediaServerItem mediaServerItem = serverItemMap.get(streamProxyItem.getMediaServerId());
MediaServer mediaServerItem = serverItemMap.get(streamProxyItem.getMediaServerId());
// TODO 支持其他 schema
JSONObject mediaInfo = zlmresTfulUtils.isMediaOnline(mediaServerItem, streamProxyItem.getApp(), streamProxyItem.getStream(), "rtsp");
MediaInfo mediaInfo = mediaServerService.getMediaInfo(mediaServerItem, streamProxyItem.getApp(), streamProxyItem.getStream());
if (mediaInfo == null){
streamProxyItem.setStatus(false);
} else {
if (mediaInfo.getInteger("code") == 0 && mediaInfo.getBoolean("online")) {
if (mediaInfo.getOnline() != null && mediaInfo.getOnline()) {
streamProxyItem.setStatus(true);
} else {
streamProxyItem.setStatus(false);

View File

@@ -1,21 +1,25 @@
package com.genersoft.iot.vmp.service.impl;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.TypeReference;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.genersoft.iot.vmp.common.StreamInfo;
import com.genersoft.iot.vmp.conf.MediaConfig;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.*;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.PlatformCatalog;
import com.genersoft.iot.vmp.gb28181.event.EventPublisher;
import com.genersoft.iot.vmp.gb28181.event.subscribe.catalog.CatalogEvent;
import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
import com.genersoft.iot.vmp.media.zlm.dto.*;
import com.genersoft.iot.vmp.media.bean.MediaInfo;
import com.genersoft.iot.vmp.media.event.media.MediaArrivalEvent;
import com.genersoft.iot.vmp.media.event.media.MediaDepartureEvent;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.zlm.dto.StreamAuthorityInfo;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OnStreamChangedHookParam;
import com.genersoft.iot.vmp.media.zlm.dto.hook.OriginType;
import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.service.bean.StreamPushItemFromRedis;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
@@ -27,7 +31,9 @@ import com.github.pagehelper.PageInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
@@ -66,9 +72,6 @@ public class StreamPushServiceImpl implements IStreamPushService {
@Autowired
private EventPublisher eventPublisher;
@Autowired
private ZLMRESTfulUtils zlmresTfulUtils;
@Autowired
private IRedisCatchStorage redisCatchStorage;
@@ -87,33 +90,125 @@ public class StreamPushServiceImpl implements IStreamPushService {
@Autowired
private MediaConfig mediaConfig;
@Override
public List<StreamPushItem> handleJSON(String jsonData, MediaServerItem mediaServerItem) {
if (jsonData == null) {
return null;
/**
* 流到来的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaArrivalEvent event) {
MediaInfo mediaInfo = event.getMediaInfo();
if (mediaInfo == null) {
return;
}
if (mediaInfo.getOriginType() != OriginType.RTMP_PUSH.ordinal()
&& mediaInfo.getOriginType() != OriginType.RTSP_PUSH.ordinal()
&& mediaInfo.getOriginType() != OriginType.RTC_PUSH.ordinal()) {
return;
}
StreamAuthorityInfo streamAuthorityInfo = redisCatchStorage.getStreamAuthorityInfo(event.getApp(), event.getStream());
if (streamAuthorityInfo == null) {
streamAuthorityInfo = StreamAuthorityInfo.getInstanceByHook(event);
} else {
streamAuthorityInfo.setOriginType(mediaInfo.getOriginType());
}
redisCatchStorage.updateStreamAuthorityInfo(event.getApp(), event.getStream(), streamAuthorityInfo);
StreamPushItem transform = StreamPushItem.getInstance(event, userSetting.getServerId());
transform.setPushIng(true);
transform.setUpdateTime(DateUtil.getNow());
transform.setPushTime(DateUtil.getNow());
transform.setSelf(true);
StreamPushItem pushInDb = getPush(event.getApp(), event.getStream());
if (pushInDb == null) {
transform.setCreateTime(DateUtil.getNow());
streamPushMapper.add(transform);
}else {
streamPushMapper.update(transform);
gbStreamMapper.updateMediaServer(event.getApp(), event.getStream(), event.getMediaServer().getId());
}
// TODO 相关的事件自行管理不需要写入ZLMMediaListManager
// ChannelOnlineEvent channelOnlineEventLister = getChannelOnlineEventLister(transform.getApp(), transform.getStream());
// if ( channelOnlineEventLister != null) {
// try {
// channelOnlineEventLister.run(transform.getApp(), transform.getStream(), transform.getServerId());;
// } catch (ParseException e) {
// logger.error("addPush: ", e);
// }
// removedChannelOnlineEventLister(transform.getApp(), transform.getStream());
// }
// 冗余数据,自己系统中自用
redisCatchStorage.addPushListItem(event.getApp(), event.getStream(), event);
// 发送流变化redis消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetting.getServerId());
jsonObject.put("app", event.getApp());
jsonObject.put("stream", event.getStream());
jsonObject.put("register", true);
jsonObject.put("mediaServerId", event.getMediaServer().getId());
redisCatchStorage.sendStreamChangeMsg(OriginType.values()[event.getMediaInfo().getOriginType()].getType(), jsonObject);
}
/**
* 流离开的处理
*/
@Async("taskExecutor")
@EventListener
public void onApplicationEvent(MediaDepartureEvent event) {
// 兼容流注销时类型从redis记录获取
OnStreamChangedHookParam onStreamChangedHookParam = redisCatchStorage.getStreamInfo(
event.getApp(), event.getStream(), event.getMediaServer().getId());
if (onStreamChangedHookParam != null) {
String type = OriginType.values()[onStreamChangedHookParam.getOriginType()].getType();
redisCatchStorage.removeStream(event.getMediaServer().getId(), type, event.getApp(), event.getStream());
if ("PUSH".equalsIgnoreCase(type)) {
// 冗余数据,自己系统中自用
redisCatchStorage.removePushListItem(event.getApp(), event.getStream(), event.getMediaServer().getId());
}
if (type != null) {
// 发送流变化redis消息
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetting.getServerId());
jsonObject.put("app", event.getApp());
jsonObject.put("stream", event.getStream());
jsonObject.put("register", false);
jsonObject.put("mediaServerId", event.getMediaServer().getId());
redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
}
}
GbStream gbStream = gbStreamMapper.selectOne(event.getApp(), event.getStream());
if (gbStream != null) {
if (userSetting.isUsePushingAsStatus()) {
streamPushMapper.updatePushStatus(event.getApp(), event.getStream(), false);
eventPublisher.catalogEventPublishForStream(null, gbStream, CatalogEvent.OFF);
}
}else {
streamPushMapper.del(event.getApp(), event.getStream());
}
}
private List<StreamPushItem> handleJSON(List<StreamInfo> streamInfoList) {
if (streamInfoList == null || streamInfoList.isEmpty()) {
return null;
}
Map<String, StreamPushItem> result = new HashMap<>();
List<OnStreamChangedHookParam> onStreamChangedHookParams = JSON.parseObject(jsonData, new TypeReference<List<OnStreamChangedHookParam>>() {});
for (OnStreamChangedHookParam item : onStreamChangedHookParams) {
for (StreamInfo streamInfo : streamInfoList) {
// 不保存国标推理以及拉流代理的流
if (item.getOriginType() == OriginType.RTSP_PUSH.ordinal()
|| item.getOriginType() == OriginType.RTMP_PUSH.ordinal()
|| item.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
String key = item.getApp() + "_" + item.getStream();
if (streamInfo.getOriginType() == OriginType.RTSP_PUSH.ordinal()
|| streamInfo.getOriginType() == OriginType.RTMP_PUSH.ordinal()
|| streamInfo.getOriginType() == OriginType.RTC_PUSH.ordinal() ) {
String key = streamInfo.getApp() + "_" + streamInfo.getStream();
StreamPushItem streamPushItem = result.get(key);
if (streamPushItem == null) {
streamPushItem = transform(item);
streamPushItem = streamPushItem.getInstance(streamInfo);
result.put(key, streamPushItem);
}
}
}
return new ArrayList<>(result.values());
}
@Override
public StreamPushItem transform(OnStreamChangedHookParam item) {
StreamPushItem streamPushItem = new StreamPushItem();
@@ -122,7 +217,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
streamPushItem.setStream(item.getStream());
streamPushItem.setAliveSecond(item.getAliveSecond());
streamPushItem.setOriginSock(item.getOriginSock());
streamPushItem.setTotalReaderCount(item.getTotalReaderCount());
streamPushItem.setTotalReaderCount(item.getTotalReaderCount() + "");
streamPushItem.setOriginType(item.getOriginType());
streamPushItem.setOriginTypeStr(item.getOriginTypeStr());
streamPushItem.setOriginUrl(item.getOriginUrl());
@@ -164,15 +259,10 @@ public class StreamPushServiceImpl implements IStreamPushService {
gbStreamService.sendCatalogMsg(stream, CatalogEvent.DEL);
platformGbStreamMapper.delByAppAndStream(stream.getApp(), stream.getStream());
int del = gbStreamMapper.del(stream.getApp(), stream.getStream());
MediaServerItem mediaInfo = mediaServerService.getOne(stream.getMediaServerId());
JSONObject mediaList = zlmresTfulUtils.getMediaList(mediaInfo, stream.getApp(), stream.getStream());
if (mediaList != null) {
if (mediaList.getInteger("code") == 0) {
JSONArray data = mediaList.getJSONArray("data");
if (data == null) {
streamPushMapper.del(stream.getApp(), stream.getStream());
}
}
MediaServer mediaInfo = mediaServerService.getOne(stream.getMediaServerId());
List<StreamInfo> mediaList = mediaServerService.getMediaList(mediaInfo, stream.getApp(), stream.getStream(), null);
if (mediaList != null && mediaList.isEmpty()) {
streamPushMapper.del(stream.getApp(), stream.getStream());
}
return del > 0;
}
@@ -195,8 +285,8 @@ public class StreamPushServiceImpl implements IStreamPushService {
gbStreamMapper.del(app, streamId);
int delStream = streamPushMapper.del(app, streamId);
if (delStream > 0) {
MediaServerItem mediaServerItem = mediaServerService.getOne(streamPushItem.getMediaServerId());
zlmresTfulUtils.closeStreams(mediaServerItem,app, streamId);
MediaServer mediaServerItem = mediaServerService.getOne(streamPushItem.getMediaServerId());
mediaServerService.closeStreams(mediaServerItem,app, streamId);
}
return true;
}
@@ -204,7 +294,7 @@ public class StreamPushServiceImpl implements IStreamPushService {
@Override
public void zlmServerOnline(String mediaServerId) {
// 同步zlm推流信息
MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
MediaServer mediaServerItem = mediaServerService.getOne(mediaServerId);
if (mediaServerItem == null) {
return;
}
@@ -232,71 +322,61 @@ public class StreamPushServiceImpl implements IStreamPushService {
for (StreamAuthorityInfo streamAuthorityInfo : allStreamAuthorityInfo) {
streamAuthorityInfoInfoMap.put(streamAuthorityInfo.getApp() + streamAuthorityInfo.getStream(), streamAuthorityInfo);
}
zlmresTfulUtils.getMediaList(mediaServerItem, (mediaList ->{
if (mediaList == null) {
return;
List<StreamInfo> mediaList = mediaServerService.getMediaList(mediaServerItem, null, null, null);
if (mediaList == null) {
return;
}
List<StreamPushItem> streamPushItems = handleJSON(mediaList);
if (streamPushItems != null) {
for (StreamPushItem streamPushItem : streamPushItems) {
pushItemMap.remove(streamPushItem.getApp() + streamPushItem.getStream());
streamInfoPushItemMap.remove(streamPushItem.getApp() + streamPushItem.getStream());
streamAuthorityInfoInfoMap.remove(streamPushItem.getApp() + streamPushItem.getStream());
}
String dataStr = mediaList.getString("data");
Integer code = mediaList.getInteger("code");
List<StreamPushItem> streamPushItems = null;
if (code == 0 ) {
if (dataStr != null) {
streamPushItems = handleJSON(dataStr, mediaServerItem);
}
}
if (streamPushItems != null) {
for (StreamPushItem streamPushItem : streamPushItems) {
pushItemMap.remove(streamPushItem.getApp() + streamPushItem.getStream());
streamInfoPushItemMap.remove(streamPushItem.getApp() + streamPushItem.getStream());
streamAuthorityInfoInfoMap.remove(streamPushItem.getApp() + streamPushItem.getStream());
}
}
List<StreamPushItem> offlinePushItems = new ArrayList<>(pushItemMap.values());
if (offlinePushItems.size() > 0) {
String type = "PUSH";
int runLimit = 300;
if (offlinePushItems.size() > runLimit) {
for (int i = 0; i < offlinePushItems.size(); i += runLimit) {
int toIndex = i + runLimit;
if (i + runLimit > offlinePushItems.size()) {
toIndex = offlinePushItems.size();
}
List<StreamPushItem> streamPushItemsSub = offlinePushItems.subList(i, toIndex);
streamPushMapper.delAll(streamPushItemsSub);
}
List<StreamPushItem> offlinePushItems = new ArrayList<>(pushItemMap.values());
if (offlinePushItems.size() > 0) {
String type = "PUSH";
int runLimit = 300;
if (offlinePushItems.size() > runLimit) {
for (int i = 0; i < offlinePushItems.size(); i += runLimit) {
int toIndex = i + runLimit;
if (i + runLimit > offlinePushItems.size()) {
toIndex = offlinePushItems.size();
}
}else {
streamPushMapper.delAll(offlinePushItems);
}
}
Collection<OnStreamChangedHookParam> offlineOnStreamChangedHookParamList = streamInfoPushItemMap.values();
if (offlineOnStreamChangedHookParamList.size() > 0) {
String type = "PUSH";
for (OnStreamChangedHookParam offlineOnStreamChangedHookParam : offlineOnStreamChangedHookParamList) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetting.getServerId());
jsonObject.put("app", offlineOnStreamChangedHookParam.getApp());
jsonObject.put("stream", offlineOnStreamChangedHookParam.getStream());
jsonObject.put("register", false);
jsonObject.put("mediaServerId", mediaServerId);
redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
// 移除redis内流的信息
redisCatchStorage.removeStream(mediaServerItem.getId(), "PUSH", offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream());
// 冗余数据,自己系统中自用
redisCatchStorage.removePushListItem(offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream(), mediaServerItem.getId());
List<StreamPushItem> streamPushItemsSub = offlinePushItems.subList(i, toIndex);
streamPushMapper.delAll(streamPushItemsSub);
}
}else {
streamPushMapper.delAll(offlinePushItems);
}
Collection<StreamAuthorityInfo> streamAuthorityInfos = streamAuthorityInfoInfoMap.values();
if (streamAuthorityInfos.size() > 0) {
for (StreamAuthorityInfo streamAuthorityInfo : streamAuthorityInfos) {
// 移除redis内流的信息
redisCatchStorage.removeStreamAuthorityInfo(streamAuthorityInfo.getApp(), streamAuthorityInfo.getStream());
}
}
Collection<OnStreamChangedHookParam> offlineOnStreamChangedHookParamList = streamInfoPushItemMap.values();
if (offlineOnStreamChangedHookParamList.size() > 0) {
String type = "PUSH";
for (OnStreamChangedHookParam offlineOnStreamChangedHookParam : offlineOnStreamChangedHookParamList) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("serverId", userSetting.getServerId());
jsonObject.put("app", offlineOnStreamChangedHookParam.getApp());
jsonObject.put("stream", offlineOnStreamChangedHookParam.getStream());
jsonObject.put("register", false);
jsonObject.put("mediaServerId", mediaServerId);
redisCatchStorage.sendStreamChangeMsg(type, jsonObject);
// 移除redis内流的信息
redisCatchStorage.removeStream(mediaServerItem.getId(), "PUSH", offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream());
// 冗余数据,自己系统中自用
redisCatchStorage.removePushListItem(offlineOnStreamChangedHookParam.getApp(), offlineOnStreamChangedHookParam.getStream(), mediaServerItem.getId());
}
}));
}
Collection<StreamAuthorityInfo> streamAuthorityInfos = streamAuthorityInfoInfoMap.values();
if (streamAuthorityInfos.size() > 0) {
for (StreamAuthorityInfo streamAuthorityInfo : streamAuthorityInfos) {
// 移除redis内流的信息
redisCatchStorage.removeStreamAuthorityInfo(streamAuthorityInfo.getApp(), streamAuthorityInfo.getStream());
}
}
}
@Override
@@ -470,8 +550,8 @@ public class StreamPushServiceImpl implements IStreamPushService {
int delStream = streamPushMapper.delAllForGbStream(gbStreams);
if (delStream > 0) {
for (GbStream gbStream : gbStreams) {
MediaServerItem mediaServerItem = mediaServerService.getOne(gbStream.getMediaServerId());
zlmresTfulUtils.closeStreams(mediaServerItem, gbStream.getApp(), gbStream.getStream());
MediaServer mediaServerItem = mediaServerService.getOne(gbStream.getMediaServerId());
mediaServerService.closeStreams(mediaServerItem, gbStream.getApp(), gbStream.getStream());
}
}

View File

@@ -6,12 +6,12 @@ import com.genersoft.iot.vmp.common.VideoManagerConstants;
import com.genersoft.iot.vmp.conf.DynamicTask;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.media.event.hook.Hook;
import com.genersoft.iot.vmp.media.event.hook.HookType;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.media.event.hook.HookSubscribe;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.bean.*;
import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
import com.genersoft.iot.vmp.utils.redis.RedisUtil;
@@ -61,9 +61,9 @@ public class RedisGbPlayMsgListener implements MessageListener {
*/
public static final int ERROR_CODE_TIMEOUT = -3;
private Map<String, PlayMsgCallback> callbacks = new ConcurrentHashMap<>();
private Map<String, PlayMsgCallbackForStartSendRtpStream> callbacksForStartSendRtpStream = new ConcurrentHashMap<>();
private Map<String, PlayMsgErrorCallback> callbacksForError = new ConcurrentHashMap<>();
private final Map<String, PlayMsgCallback> callbacks = new ConcurrentHashMap<>();
private final Map<String, PlayMsgCallbackForStartSendRtpStream> callbacksForStartSendRtpStream = new ConcurrentHashMap<>();
private final Map<String, PlayMsgErrorCallback> callbacksForError = new ConcurrentHashMap<>();
@Autowired
private UserSetting userSetting;
@@ -87,9 +87,9 @@ public class RedisGbPlayMsgListener implements MessageListener {
@Autowired
private ZlmHttpHookSubscribe subscribe;
private HookSubscribe subscribe;
private ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
private final ConcurrentLinkedQueue<Message> taskQueue = new ConcurrentLinkedQueue<>();
@Qualifier("taskExecutor")
@Autowired
@@ -219,7 +219,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
* 处理收到的请求推流的请求
*/
private void requestPushStreamMsgHand(RequestPushStreamMsg requestPushStreamMsg, String fromId, String serial) {
MediaServerItem mediaInfo = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId());
MediaServer mediaInfo = mediaServerService.getOne(requestPushStreamMsg.getMediaServerId());
if (mediaInfo == null) {
// TODO 回复错误
return;
@@ -258,7 +258,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
* 处理收到的请求sendItem的请求
*/
private void requestSendItemMsgHand(RequestSendItemMsg content, String toId, String serial) {
MediaServerItem mediaServerItem = mediaServerService.getOne(content.getMediaServerId());
MediaServer mediaServerItem = mediaServerService.getOne(content.getMediaServerId());
if (mediaServerItem == null) {
logger.info("[回复推流信息] 流媒体{}不存在 ", content.getMediaServerId());
@@ -274,7 +274,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
return;
}
// 确定流是否在线
Boolean streamReady = zlmServerFactory.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
Boolean streamReady = mediaServerService.isStreamReady(mediaServerItem, content.getApp(), content.getStream());
if (streamReady != null && streamReady) {
logger.info("[回复推流信息] {}/{}", content.getApp(), content.getStream());
responseSendItem(mediaServerItem, content, toId, serial);
@@ -297,9 +297,8 @@ public class RedisGbPlayMsgListener implements MessageListener {
}, userSetting.getPlatformPlayTimeout());
// 添加订阅
HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed(content.getApp(), content.getStream(), true, "rtsp", mediaServerItem.getId());
subscribe.addSubscribe(hookSubscribe, (mediaServerItemInUse, hookParam)->{
Hook hook = Hook.getInstance(HookType.on_media_arrival, content.getApp(), content.getStream(), content.getMediaServerId());
subscribe.addSubscribe(hook, (hookData)->{
dynamicTask.stop(taskKey);
responseSendItem(mediaServerItem, content, toId, serial);
});
@@ -317,7 +316,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
/**
* 将获取到的sendItem发送出去
*/
private void responseSendItem(MediaServerItem mediaServerItem, RequestSendItemMsg content, String toId, String serial) {
private void responseSendItem(MediaServer mediaServerItem, RequestSendItemMsg content, String toId, String serial) {
SendRtpItem sendRtpItem = zlmServerFactory.createSendRtpItem(mediaServerItem, content.getIp(),
content.getPort(), content.getSsrc(), content.getPlatformId(),
content.getApp(), content.getStream(), content.getChannelId(),
@@ -449,7 +448,7 @@ public class RedisGbPlayMsgListener implements MessageListener {
logger.info("[REDIS 执行其他平台的请求停止推流] 失败: sendRtpItem为NULL");
return;
}
MediaServerItem mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
if (mediaInfo == null) {
// TODO 回复错误
return;

View File

@@ -2,12 +2,14 @@ package com.genersoft.iot.vmp.service.redisMsg;
import com.alibaba.fastjson2.JSON;
import com.genersoft.iot.vmp.conf.UserSetting;
import com.genersoft.iot.vmp.gb28181.bean.InviteStreamType;
import com.genersoft.iot.vmp.gb28181.bean.ParentPlatform;
import com.genersoft.iot.vmp.gb28181.bean.SendRtpItem;
import com.genersoft.iot.vmp.gb28181.transmit.cmd.ISIPCommanderForPlatform;
import com.genersoft.iot.vmp.media.bean.MediaServer;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.media.zlm.ZLMServerFactory;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannel;
import com.genersoft.iot.vmp.service.bean.MessageForPushChannelResponse;
@@ -23,6 +25,7 @@ import org.springframework.stereotype.Component;
import javax.sip.InvalidArgumentException;
import javax.sip.SipException;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -83,6 +86,26 @@ public class RedisPushStreamCloseResponseListener implements MessageListener {
logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
}
}
if (push.isSelf()) {
// 停止向上级推流
String streamId = sendRtpItem.getStream();
Map<String, Object> param = new HashMap<>();
param.put("vhost","__defaultVhost__");
param.put("app",sendRtpItem.getApp());
param.put("stream",streamId);
param.put("ssrc",sendRtpItem.getSsrc());
logger.info("[REDIS消息-推流结束] 停止向上级推流:{}", streamId);
MediaServer mediaInfo = mediaServerService.getOne(sendRtpItem.getMediaServerId());
redisCatchStorage.deleteSendRTPServer(sendRtpItem.getPlatformId(), sendRtpItem.getChannelId(), sendRtpItem.getCallId(), sendRtpItem.getStream());
zlmServerFactory.stopSendRtpStream(mediaInfo, param);
if (InviteStreamType.PUSH == sendRtpItem.getPlayType()) {
MessageForPushChannel messageForPushChannel = MessageForPushChannel.getInstance(0,
sendRtpItem.getApp(), sendRtpItem.getStream(), sendRtpItem.getChannelId(),
sendRtpItem.getPlatformId(), parentPlatform.getName(), userSetting.getServerId(), sendRtpItem.getMediaServerId());
messageForPushChannel.setPlatFormIndex(parentPlatform.getId());
redisCatchStorage.sendPlatformStopPlayMsg(messageForPushChannel);
}
}
}
}
}

View File

@@ -5,7 +5,7 @@ import com.alibaba.fastjson2.JSONObject;
import com.genersoft.iot.vmp.gb28181.bean.GbStream;
import com.genersoft.iot.vmp.media.zlm.dto.StreamPushItem;
import com.genersoft.iot.vmp.service.IGbStreamService;
import com.genersoft.iot.vmp.service.IMediaServerService;
import com.genersoft.iot.vmp.media.service.IMediaServerService;
import com.genersoft.iot.vmp.service.IStreamPushService;
import com.genersoft.iot.vmp.utils.DateUtil;
import org.slf4j.Logger;