PlayServiceImpl.java 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313
  1. package com.genersoft.iot.vmp.service.impl;
  2. import com.alibaba.fastjson2.JSON;
  3. import com.alibaba.fastjson2.JSONArray;
  4. import com.alibaba.fastjson2.JSONObject;
  5. import com.genersoft.iot.vmp.common.StreamInfo;
  6. import com.genersoft.iot.vmp.conf.DynamicTask;
  7. import com.genersoft.iot.vmp.conf.UserSetting;
  8. import com.genersoft.iot.vmp.conf.exception.ControllerException;
  9. import com.genersoft.iot.vmp.conf.exception.ServiceException;
  10. import com.genersoft.iot.vmp.conf.exception.SsrcTransactionNotFoundException;
  11. import com.genersoft.iot.vmp.gb28181.GBEventSubscribe;
  12. import com.genersoft.iot.vmp.gb28181.GBHookSubscribeFactory;
  13. import com.genersoft.iot.vmp.gb28181.HookSubscribeForKey;
  14. import com.genersoft.iot.vmp.gb28181.bean.*;
  15. import com.genersoft.iot.vmp.gb28181.event.SipSubscribe;
  16. import com.genersoft.iot.vmp.gb28181.session.SsrcConfig;
  17. import com.genersoft.iot.vmp.gb28181.session.VideoStreamSessionManager;
  18. import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder;
  19. import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage;
  20. import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander;
  21. import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommanderFroPlatform;
  22. import com.genersoft.iot.vmp.media.zlm.AssistRESTfulUtils;
  23. import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils;
  24. import com.genersoft.iot.vmp.media.zlm.ZlmHttpHookSubscribe;
  25. import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeFactory;
  26. import com.genersoft.iot.vmp.media.zlm.dto.HookSubscribeForStreamChange;
  27. import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem;
  28. import com.genersoft.iot.vmp.service.IDeviceService;
  29. import com.genersoft.iot.vmp.service.IMediaServerService;
  30. import com.genersoft.iot.vmp.service.IMediaService;
  31. import com.genersoft.iot.vmp.service.IPlayService;
  32. import com.genersoft.iot.vmp.service.bean.*;
  33. import com.genersoft.iot.vmp.storager.IRedisCatchStorage;
  34. import com.genersoft.iot.vmp.storager.IVideoManagerStorage;
  35. import com.genersoft.iot.vmp.utils.DateUtil;
  36. import com.genersoft.iot.vmp.utils.redis.RedisUtil;
  37. import com.genersoft.iot.vmp.vmanager.bean.ErrorCode;
  38. import com.genersoft.iot.vmp.vmanager.bean.ErrorHook;
  39. import com.genersoft.iot.vmp.vmanager.bean.StreamContent;
  40. import com.genersoft.iot.vmp.vmanager.bean.WVPResult;
  41. import gov.nist.javax.sip.message.SIPRequest;
  42. import gov.nist.javax.sip.message.SIPResponse;
  43. import org.slf4j.Logger;
  44. import org.slf4j.LoggerFactory;
  45. import org.springframework.beans.factory.annotation.Autowired;
  46. import org.springframework.beans.factory.annotation.Qualifier;
  47. import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
  48. import org.springframework.stereotype.Service;
  49. import org.springframework.util.ObjectUtils;
  50. import org.springframework.web.context.request.async.DeferredResult;
  51. import javax.sip.InvalidArgumentException;
  52. import javax.sip.ResponseEvent;
  53. import javax.sip.SipException;
  54. import java.math.BigDecimal;
  55. import java.math.RoundingMode;
  56. import java.text.ParseException;
  57. import java.util.EventObject;
  58. import java.util.List;
  59. import java.util.UUID;
  60. @SuppressWarnings(value = {"rawtypes", "unchecked"})
  61. @Service
  62. public class PlayServiceImpl implements IPlayService {
  63. private final static Logger logger = LoggerFactory.getLogger(PlayServiceImpl.class);
  64. @Autowired
  65. private IVideoManagerStorage storager;
  66. @Autowired
  67. private SIPCommander cmder;
  68. @Autowired
  69. private SIPCommanderFroPlatform sipCommanderFroPlatform;
  70. @Autowired
  71. private IRedisCatchStorage redisCatchStorage;
  72. @Autowired
  73. private DeferredResultHolder resultHolder;
  74. @Autowired
  75. private ZLMRESTfulUtils zlmresTfulUtils;
  76. @Autowired
  77. private AssistRESTfulUtils assistRESTfulUtils;
  78. @Autowired
  79. private IMediaService mediaService;
  80. @Autowired
  81. private IMediaServerService mediaServerService;
  82. @Autowired
  83. private VideoStreamSessionManager streamSession;
  84. @Autowired
  85. private IDeviceService deviceService;
  86. @Autowired
  87. private UserSetting userSetting;
  88. @Autowired
  89. private DynamicTask dynamicTask;
  90. @Autowired
  91. private ZlmHttpHookSubscribe subscribe;
  92. @Autowired
  93. private GBEventSubscribe GBHookSubscribe;
  94. @Qualifier("taskExecutor")
  95. @Autowired
  96. private ThreadPoolTaskExecutor taskExecutor;
  97. @Override
  98. public void play(MediaServerItem mediaServerItem, String deviceId,
  99. String channelId, int isUsePs,
  100. ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
  101. Runnable timeoutCallback) {
  102. String key = DeferredResultHolder.CALLBACK_CMD_PLAY + deviceId + channelId;
  103. WVPResult wvpResult = new WVPResult();
  104. RequestMessage msg = new RequestMessage();
  105. Device device = storager.queryVideoDevice(deviceId);
  106. StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
  107. msg.setKey(key);
  108. if (mediaServerItem == null) {
  109. logger.info("无法找到zlm服务");
  110. // 释放异步请求
  111. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  112. wvpResult.setMsg("点播失败, 无法找到zlm服务");
  113. msg.setData(wvpResult);
  114. resultHolder.invokeAllResult(msg);
  115. redisCatchStorage.stopPlay(streamInfo);
  116. return;
  117. }
  118. if (streamInfo != null) {
  119. logger.info("可以找到对应的stream");
  120. String streamId = streamInfo.getStream();
  121. if (streamId == null) {
  122. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  123. wvpResult.setMsg("点播失败, redis缓存streamId等于null");
  124. msg.setData(wvpResult);
  125. resultHolder.invokeAllResult(msg);
  126. redisCatchStorage.stopPlay(streamInfo);
  127. resultHolder.invokeAllResult(msg);
  128. return;
  129. }
  130. String mediaServerId = streamInfo.getMediaServerId();
  131. MediaServerItem mediaInfo = mediaServerService.getOne(mediaServerId);
  132. JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaInfo, streamId);
  133. // 判断rtpserver是否为null
  134. if (rtpInfo == null) {
  135. logger.info("rtpInfo为null");
  136. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  137. wvpResult.setMsg("点播失败,请重试 rtpInfo为null");
  138. msg.setData(wvpResult);
  139. redisCatchStorage.stopPlay(streamInfo);
  140. resultHolder.invokeAllResult(msg);
  141. return;
  142. }
  143. if (rtpInfo.getInteger("code") == 0) {
  144. if (rtpInfo.getBoolean("exist")) {
  145. int localPort = rtpInfo.getInteger("local_port");
  146. if (localPort == 0) {
  147. logger.warn("[点播],点播时发现rtpServer存在,但是尚未开始推流");
  148. // 此时说明rtpServer已经创建但是流还没有推上来
  149. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  150. wvpResult.setMsg("点播已经在进行中,请稍候重试");
  151. msg.setData(wvpResult);
  152. redisCatchStorage.stopPlay(streamInfo);
  153. resultHolder.invokeAllResult(msg);
  154. return;
  155. } else {
  156. wvpResult.setCode(ErrorCode.SUCCESS.getCode());
  157. wvpResult.setMsg(ErrorCode.SUCCESS.getMsg());
  158. wvpResult.setData(streamInfo);
  159. msg.setData(wvpResult);
  160. resultHolder.invokeAllResult(msg);
  161. if (hookEvent != null) {
  162. hookEvent.response(mediaServerItem, JSON.parseObject(JSON.toJSONString(streamInfo)));
  163. }
  164. }
  165. } else {
  166. redisCatchStorage.stopPlay(streamInfo);
  167. storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  168. streamInfo = null;
  169. }
  170. } else {
  171. logger.warn("获取rtpinfo失败");
  172. //zlm连接失败
  173. redisCatchStorage.stopPlay(streamInfo);
  174. storager.stopPlay(streamInfo.getDeviceID(), streamInfo.getChannelId());
  175. streamInfo = null;
  176. }
  177. }
  178. if (streamInfo == null) {
  179. String streamId = null;
  180. if (mediaServerItem.isRtpEnable()) {
  181. logger.info("test ----- {}",device.getDeviceId());
  182. logger.info("test ----- {}",channelId);
  183. streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  184. }
  185. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
  186. logger.info(JSONObject.toJSONString(ssrcInfo));
  187. if (ssrcInfo == null) {
  188. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  189. wvpResult.setMsg("开启收流失败");
  190. msg.setData(wvpResult);
  191. resultHolder.invokeAllResult(msg);
  192. return;
  193. }
  194. play(mediaServerItem, ssrcInfo, device, channelId, isUsePs,
  195. (mediaServerItemInUse, response) -> {
  196. if (hookEvent != null) {
  197. hookEvent.response(mediaServerItem, response);
  198. }
  199. }, event -> {
  200. // sip error错误
  201. logger.warn("sip 错误,点播失败");
  202. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  203. wvpResult.setMsg(String.format("点播失败, 错误码: %s, %s", event.statusCode, event.msg));
  204. msg.setData(wvpResult);
  205. resultHolder.invokeAllResult(msg);
  206. if (errorEvent != null) {
  207. errorEvent.response(event);
  208. }
  209. }, (code, msgStr) -> {
  210. // invite点播超时
  211. logger.warn("sip 点播超时");
  212. wvpResult.setCode(ErrorCode.ERROR100.getCode());
  213. if (code == 0) {
  214. wvpResult.setMsg("点播超时,请稍候重试");
  215. } else if (code == 1) {
  216. wvpResult.setMsg("收流超时,请稍候重试");
  217. }
  218. msg.setData(wvpResult);
  219. // 回复之前所有的点播请求
  220. resultHolder.invokeAllResult(msg);
  221. });
  222. }
  223. }
  224. @Override
  225. public void play(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,
  226. Device device, String channelId,
  227. int isUsePs,
  228. ZlmHttpHookSubscribe.Event hookEvent, SipSubscribe.Event errorEvent,
  229. InviteTimeOutCallback timeoutCallback) {
  230. logger.info("[点播开始] deviceId: {}, channelId: {},收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}", device.getDeviceId(), channelId, ssrcInfo.getPort(), device.getStreamMode(), ssrcInfo.getSsrc(), device.isSsrcCheck());
  231. // 超时处理
  232. String timeOutTaskKey = UUID.randomUUID().toString();
  233. dynamicTask.startDelay(timeOutTaskKey, () -> {
  234. // 执行超时任务时查询是否已经成功,成功了则不执行超时任务,防止超时任务取消失败的情况
  235. if (redisCatchStorage.queryPlayByDevice(device.getDeviceId(), channelId) == null) {
  236. logger.info("[点播超时] 收流超时 deviceId: {}, channelId: {},端口:{}, SSRC: {}", device.getDeviceId(), channelId, ssrcInfo.getPort(), ssrcInfo.getSsrc());
  237. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  238. try {
  239. cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  240. } catch (InvalidArgumentException | ParseException | SipException | SsrcTransactionNotFoundException e) {
  241. logger.error("[点播超时], 发送BYE失败 {}", e.getMessage());
  242. } finally {
  243. timeoutCallback.run(1, "收流超时");
  244. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  245. logger.info("点播超时, 关闭rtp通道");
  246. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  247. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  248. // 取消订阅消息监听
  249. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  250. subscribe.removeSubscribe(hookSubscribe);
  251. }
  252. }
  253. }, userSetting.getPlayTimeout());
  254. RequestMessage msg = new RequestMessage();
  255. msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
  256. //端口获取失败的ssrcInfo 没有必要发送点播指令
  257. if (ssrcInfo.getPort() <= 0) {
  258. logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
  259. dynamicTask.stop(timeOutTaskKey);
  260. // 释放ssrc
  261. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  262. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  263. msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "点播端口分配异常"));
  264. resultHolder.invokeAllResult(msg);
  265. return;
  266. }
  267. try {
  268. cmder.playStreamCmd(mediaServerItem, ssrcInfo, device, channelId, isUsePs,
  269. (MediaServerItem mediaServerItemInuse, JSONObject response) -> {
  270. logger.info("收到订阅消息: " + response.toJSONString());
  271. logger.info("停止超时任务: " + timeOutTaskKey);
  272. dynamicTask.stop(timeOutTaskKey);
  273. // hook响应
  274. onPublishHandlerForPlay(msg, mediaServerItemInuse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc());
  275. hookEvent.response(mediaServerItemInuse, response);
  276. logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
  277. String streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.flv", mediaServerItemInuse.getHttpPort(), "rtp", ssrcInfo.getStream());
  278. String path = "snap";
  279. String fileName = device.getDeviceId() + "_" + channelId + ".jpg";
  280. // 请求截图
  281. logger.info("[请求截图]: " + fileName);
  282. zlmresTfulUtils.getSnap(mediaServerItemInuse, streamUrl, 15, 1, path, fileName);
  283. },
  284. (okEvent) -> {
  285. ResponseEvent responseEvent = (ResponseEvent) okEvent.event;
  286. String contentString = new String(responseEvent.getResponse().getRawContent());
  287. // 获取ssrc
  288. int ssrcIndex = contentString.indexOf("y=");
  289. // 检查是否有y字段
  290. if (ssrcIndex >= 0) {
  291. //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  292. String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  293. // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  294. if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  295. return;
  296. }
  297. logger.info("[点播消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  298. if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
  299. logger.info("[点播消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  300. if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  301. // ssrc 不可用
  302. // 释放ssrc
  303. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  304. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  305. okEvent.msg = "下级自定义了ssrc,但是此ssrc不可用";
  306. okEvent.statusCode = 400;
  307. errorEvent.response(okEvent);
  308. return;
  309. }
  310. // 单端口模式streamId也有变化,需要重新设置监听
  311. if (!mediaServerItem.isRtpEnable()) {
  312. // 添加订阅
  313. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  314. subscribe.removeSubscribe(hookSubscribe);
  315. hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  316. subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  317. logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  318. dynamicTask.stop(timeOutTaskKey);
  319. // hook响应
  320. onPublishHandlerForPlay(msg, mediaServerItemInUse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc());
  321. hookEvent.response(mediaServerItemInUse, response);
  322. });
  323. }
  324. // 关闭rtp server
  325. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  326. // 重新开启ssrc server
  327. mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), false, ssrcInfo.getPort());
  328. }
  329. }
  330. }
  331. , (errEvent) -> {
  332. dynamicTask.stop(timeOutTaskKey);
  333. logger.info("[未知原因] 关闭推流 closeRTPServer");
  334. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  335. // 释放ssrc
  336. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  337. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  338. errorEvent.response(errEvent);
  339. });
  340. } catch (InvalidArgumentException | SipException | ParseException e) {
  341. logger.error("[命令发送失败] 点播消息: {}", e.getMessage());
  342. dynamicTask.stop(timeOutTaskKey);
  343. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  344. // 释放ssrc
  345. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  346. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  347. // 有什么大病一般的代码
  348. // SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  349. // eventResult.msg = "命令发送失败";
  350. EventObject eventObject = new EventObject("命令发送失败");
  351. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(eventObject);
  352. errorEvent.response(eventResult);
  353. //
  354. // RequestMessage msg = new RequestMessage();
  355. // msg.setKey(DeferredResultHolder.CALLBACK_CMD_PLAY + device.getDeviceId() + channelId);
  356. // msg.setData(WVPResult.fail(ErrorCode.ERR_Invite_fail, "点播命令发送失败: " + e.getMessage()));
  357. // resultHolder.invokeAllResult(msg);
  358. }
  359. }
  360. /**
  361. * 从缓存中获取流信息
  362. *
  363. * @param deviceId
  364. * @param channelId
  365. * @return
  366. */
  367. private StreamInfo getStreamInfo(String deviceId, String channelId, WVPResult wvpResult) {
  368. StreamInfo streamInfo = redisCatchStorage.queryPlayByDevice(deviceId, channelId);
  369. // 两种情况, 一种是已经在推流中了. 一种是还没有开始推流
  370. if (streamInfo == null) {
  371. // 未开始推流, 开启新推流
  372. return null;
  373. }
  374. // 能够读取到视频缓存
  375. String streamId = streamInfo.getStream();
  376. // 应该是不可能出现的异常情况,
  377. if (streamId == null) {
  378. logger.warn("[读取推流信息] 失败, redis缓存异常 streamId 为空");
  379. // 说明缓存的流信息有问题
  380. // 移除redis 缓存
  381. redisCatchStorage.stopPlay(streamInfo);
  382. redisCatchStorage.deleteDeviceStream(deviceId, channelId);
  383. storager.stopPlay(deviceId, channelId);
  384. return null;
  385. }
  386. String mediaServerId = streamInfo.getMediaServerId();
  387. // 获取视频推流通道对应的流媒体信息
  388. MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  389. JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(mediaServerItem, streamId);
  390. if (rtpInfo == null) {
  391. logger.warn("[读取推流信息] 失败, rtpInfo为null");
  392. redisCatchStorage.stopPlay(streamInfo);
  393. redisCatchStorage.deleteDeviceStream(deviceId, channelId);
  394. storager.stopPlay(deviceId, channelId);
  395. return null;
  396. }
  397. if (rtpInfo.getInteger("code") != 0) {
  398. redisCatchStorage.stopPlay(streamInfo);
  399. redisCatchStorage.deleteDeviceStream(deviceId, channelId);
  400. storager.stopPlay(deviceId, channelId);
  401. return null;
  402. }
  403. if (!rtpInfo.getBoolean("exist")) {
  404. // 说明流已经停止
  405. redisCatchStorage.stopPlay(streamInfo);
  406. redisCatchStorage.deleteDeviceStream(deviceId, channelId);
  407. storager.stopPlay(deviceId, channelId);
  408. return null;
  409. }
  410. int localPort = rtpInfo.getInteger("local_port");
  411. if (localPort == 0) {
  412. logger.warn("[读取推流信息] 失败, rtpServer存在,但是尚未开始推流");
  413. // 此时说明rtpServer已经创建但是流还没有推上来, 所以稍微等待一下重试即可
  414. // 使用 wvpResult 返回对应信息提示
  415. wvpResult.setCode(ErrorCode.ERROR_Retry.getCode());
  416. wvpResult.setMsg("点播已经在进行中,请稍后进行重试");
  417. }
  418. return streamInfo;
  419. }
  420. // 直接抛弃原有的播放 函数 创建新地播放函数, 重新梳理播放逻辑
  421. public void startPlay(RequestMessage requestMsg, MediaServerItem mediaServerItem, Device device, String channelId, int isUsePs) {
  422. // 获取缓存的流信息
  423. WVPResult wvpResult = new WVPResult();
  424. StreamInfo streamInfo = getStreamInfo(device.getDeviceId(), channelId, wvpResult);
  425. if (streamInfo != null) {
  426. logger.info("已经启用视频流 {}", wvpResult.toString());
  427. // 说明已经在推流中了
  428. if (wvpResult.getCode() != ErrorCode.ERROR_Retry.getCode()) {
  429. // 在获取流信息没有更改 wvpResult 时将返回值设置为成功
  430. logger.warn("{} 已经在推流, 并且无异常", device.getDeviceId(), channelId);
  431. // 数据类型转换, 懒得改前端了
  432. wvpResult = WVPResult.success(
  433. new StreamContent(streamInfo)
  434. );
  435. }
  436. resultHolder.invokeResult(requestMsg.setData(wvpResult));
  437. return;
  438. }
  439. // 说明还没有开始推流
  440. // if (!mediaServerItem.isRtpEnable()) {
  441. // logger.warn("设备应用的流媒体未启用");
  442. // resultHolder.invokeAllResult(
  443. // requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "设备应用的流媒体未启用"))
  444. // );
  445. // return;
  446. // }
  447. // 生成streamId
  448. String streamId = String.format("%s_%s", device.getDeviceId(), channelId);
  449. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(mediaServerItem, streamId, device.isSsrcCheck(), false);
  450. if (ssrcInfo == null) {
  451. logger.warn("开启收流失败");
  452. resultHolder.invokeAllResult(
  453. requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "开启收流失败"))
  454. );
  455. return;
  456. }
  457. // 尝试启用收流
  458. if (ssrcInfo.getPort() <= 0) {
  459. logger.info("[点播端口分配异常],deviceId={},channelId={},ssrcInfo={}", device.getDeviceId(), channelId, ssrcInfo);
  460. resultHolder.invokeAllResult(
  461. requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "点播端口分配异常, 请重试"))
  462. );
  463. return;
  464. }
  465. executePlayLive(requestMsg, mediaServerItem, ssrcInfo, device, channelId, isUsePs);
  466. }
  467. /**
  468. * 执行推流相关操作 发送命令
  469. *
  470. * @param requestMsg
  471. * @param mediaServerItem
  472. * @param device
  473. * @param channelId
  474. * @param isUsePs
  475. */
  476. private void executePlayLive(RequestMessage requestMsg, MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, Device device, String channelId, int isUsePs) {
  477. logger.info("[支持点播推流] 收流端口: {}, 收流模式:{}, SSRC: {}, SSRC校验:{}",
  478. ssrcInfo.getPort(), device.getStreamMode(),
  479. ssrcInfo.getSsrc(), device.isSsrcCheck());
  480. String stream = ssrcInfo.getStream();
  481. logger.info("{} 分配的ZLM为: {} [{}:{}]", stream, mediaServerItem.getId(), mediaServerItem.getIp(), ssrcInfo.getPort());
  482. // 构建errorHook 回调函数
  483. ErrorHook errorHook = wvpResult ->
  484. {
  485. logger.info("收到点播命令回调 {}", wvpResult);
  486. if (wvpResult.getCode() != ErrorCode.SUCCESS.getCode()) {
  487. logger.error("[发送点播命令] 失败 {}", wvpResult.getMsg());
  488. requestMsg.setData(wvpResult);
  489. stopPlay(device, channelId);
  490. // resultHolder.invokeAllResult(requestMsg);
  491. }
  492. // 监听zlm的流改变事件, 在流改变时直接回复 http 信息
  493. addHookSubscribeForStreamChange(requestMsg, mediaServerItem, device, channelId, ssrcInfo);
  494. };
  495. try {
  496. // 发送命令
  497. cmder.sendPlayCmd(mediaServerItem, ssrcInfo, device, channelId, isUsePs, errorHook);
  498. } catch (Exception e) {
  499. e.printStackTrace();
  500. logger.error("[发送点播命令] 失败 {}", e.getMessage());
  501. // 未知的错误, 打印错误堆栈
  502. requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "发送点播命令失败"));
  503. resultHolder.invokeAllResult(requestMsg);
  504. }
  505. }
  506. // 停止推流
  507. public void stopPlay(Device device, String channelId) {
  508. WVPResult wvpResult = new WVPResult();
  509. StreamInfo streamInfo = getStreamInfo(device.getDeviceId(), channelId, wvpResult);
  510. // 获取 流媒体
  511. // 发送bye给设备
  512. ErrorHook errorHook = wvpResult1 ->
  513. {
  514. logger.info("关闭推流回调 {}", wvpResult1);
  515. if (wvpResult1.getCode() != ErrorCode.SUCCESS.getCode()) {
  516. logger.error("[关闭推流回调] 失败 {}", wvpResult1.getMsg());
  517. return;
  518. }
  519. //
  520. if (streamInfo != null) {
  521. logger.info("找到正在推流的视频流 尝试停止 {}", wvpResult.toString());
  522. MediaServerItem mediaServerItem = mediaServerService.getOne(
  523. streamInfo.getMediaServerId()
  524. );
  525. // 获取ssrc
  526. logger.info("移除并关闭推流通道");
  527. redisCatchStorage.stopPlay(streamInfo);
  528. redisCatchStorage.deleteDeviceStream(device.getDeviceId(), channelId);
  529. mediaServerService.releaseSsrc(streamInfo.getMediaServerId(), streamInfo.getSsrc());
  530. mediaServerService.closeRTPServer(streamInfo.getMediaServerId(), streamInfo.getStream());
  531. } else {
  532. logger.info("在没有服务端缓存的情况下关闭推流通道");
  533. }
  534. };
  535. try {
  536. // 发送命令
  537. cmder.stopPlayCmd(device, channelId, errorHook);
  538. } catch (Exception e) {
  539. e.printStackTrace();
  540. logger.error("[关闭推流] 失败 {}", e.getMessage());
  541. // 未知的错误, 打印错误堆栈
  542. }
  543. }
  544. /**
  545. * 截取视频快照
  546. *
  547. * @param mediaServerItemInUse
  548. * @param deviceId
  549. * @param channelId
  550. * @param ssrcInfo
  551. */
  552. public void screenshot(MediaServerItem mediaServerItemInUse, String deviceId, String channelId, SSRCInfo ssrcInfo) {
  553. String streamUrl = String.format("http://127.0.0.1:%s/%s/%s.live.flv",
  554. mediaServerItemInUse.getHttpPort(), "rtp", ssrcInfo.getStream());
  555. String path = "snap";
  556. String fileName = deviceId + "_" + channelId + ".jpg";
  557. // 请求截图
  558. logger.info("[尝试截取视频快照]: " + fileName);
  559. zlmresTfulUtils.getSnap(mediaServerItemInUse, streamUrl, 15, 1, path, fileName);
  560. }
  561. public void openBroadcast(MediaServerItem mediaServerItem,
  562. Device device,
  563. int waitTime,
  564. BroadcastCallback callback) {
  565. logger.warn("[语音广播] 开语音广播 新");
  566. JSONObject errJson = new JSONObject();
  567. try {
  568. cmder.audioBroadcastCmd(device);
  569. } catch (InvalidArgumentException | SipException | ParseException e) {
  570. logger.error("[命令发送失败] 发送broadcast中 errorMsg: {}", e.getMessage());
  571. errJson.put("msg", "[命令发送失败] 无法发送broadcast消息");
  572. callback.run(2, errJson, null);
  573. }
  574. logger.warn("等待设备返回invite");
  575. HookSubscribeForKey broadcastForInviteHook = GBHookSubscribeFactory.on_broadcast_invite(device.getDeviceId());
  576. // 创建计时器,计时结束未收到invite则自动进行失败处理
  577. String timeOutTaskKey = UUID.randomUUID().toString();
  578. dynamicTask.startDelay(timeOutTaskKey, () -> {
  579. // todo 发送 bye 通知给设备?
  580. logger.warn("invite超时");
  581. errJson.put("msg", "等待设备语音invite信息超时");
  582. callback.run(1, errJson, null);
  583. }, waitTime);
  584. GBHookSubscribe.addInviteSubscribe(
  585. broadcastForInviteHook,
  586. (int code, JSONObject json, SIPRequest request) -> {
  587. // invite信息返回
  588. logger.info("[语音广播] 接收到设备invite信息___订阅事件触发 JSONDATA: {}", json.toJSONString());
  589. // 取消计时器
  590. dynamicTask.stop(timeOutTaskKey);
  591. callback.run(0, json, request);
  592. });
  593. }
  594. ;
  595. private void onPublishHandlerForPlay(
  596. RequestMessage requestMsg, MediaServerItem mediaServerItem,
  597. JSONObject response, String deviceId, String channelId, String ssrc) {
  598. StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
  599. if (streamInfo != null) {
  600. streamInfo.setBack(false);
  601. streamInfo.setSsrc(ssrc);
  602. DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
  603. if (deviceChannel != null) {
  604. deviceChannel.setStreamId(streamInfo.getStream());
  605. storager.startPlay(deviceId, channelId, streamInfo.getStream());
  606. }
  607. redisCatchStorage.startPlay(streamInfo);
  608. // 流信息转换
  609. requestMsg.setData(WVPResult.success(new StreamContent(streamInfo)));
  610. resultHolder.invokeAllResult(requestMsg);
  611. } else {
  612. logger.warn("设备预览API调用失败!");
  613. requestMsg.setData(WVPResult.fail(ErrorCode.ERROR100, "设备预览API调用失败!"));
  614. resultHolder.invokeAllResult(requestMsg);
  615. }
  616. }
  617. private void onPublishHandlerForPlayback(MediaServerItem mediaServerItem, JSONObject response, String deviceId, String channelId, String ssrc, PlayBackCallback playBackCallback) {
  618. StreamInfo streamInfo = onPublishHandler(mediaServerItem, response, deviceId, channelId);
  619. PlayBackResult<StreamInfo> playBackResult = new PlayBackResult<>();
  620. if (streamInfo != null) {
  621. streamInfo.setBack(true);
  622. streamInfo.setSsrc(ssrc);
  623. DeviceChannel deviceChannel = storager.queryChannel(deviceId, channelId);
  624. if (deviceChannel != null) {
  625. deviceChannel.setStreamId(streamInfo.getStream());
  626. storager.startPlay(deviceId, channelId, streamInfo.getStream());
  627. }
  628. redisCatchStorage.startPlay(streamInfo);
  629. playBackResult.setCode(ErrorCode.SUCCESS.getCode());
  630. playBackResult.setMsg(ErrorCode.SUCCESS.getMsg());
  631. playBackResult.setData(streamInfo);
  632. playBackCallback.call(playBackResult);
  633. } else {
  634. logger.warn("录像回放调用失败!");
  635. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  636. playBackResult.setMsg("录像回放调用失败!");
  637. playBackCallback.call(playBackResult);
  638. }
  639. }
  640. @Override
  641. public MediaServerItem getNewMediaServerItem(Device device) {
  642. if (device == null) {
  643. return null;
  644. }
  645. MediaServerItem mediaServerItem;
  646. if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) {
  647. mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
  648. } else {
  649. // 尝试获取device配置的zlm服务
  650. mediaServerItem = mediaServerService.getOne(device.getMediaServerId());
  651. // 如果默认zlm无法找到则随机分配一个zlm
  652. if(mediaServerItem == null){
  653. logger.warn("无法找到设备默认流媒体服务,即将使用默认流媒体服务");
  654. mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(null);
  655. }
  656. }
  657. if (mediaServerItem == null) {
  658. logger.warn("点播时未找到可使用的ZLM...");
  659. }
  660. return mediaServerItem;
  661. }
  662. @Override
  663. public MediaServerItem getNewMediaServerItemHasAssist(Device device) {
  664. if (device == null) {
  665. return null;
  666. }
  667. MediaServerItem mediaServerItem;
  668. if (ObjectUtils.isEmpty(device.getMediaServerId()) || "auto".equals(device.getMediaServerId())) {
  669. mediaServerItem = mediaServerService.getMediaServerForMinimumLoad(true);
  670. } else {
  671. mediaServerItem = mediaServerService.getOne(device.getMediaServerId());
  672. }
  673. if (mediaServerItem == null) {
  674. logger.warn("[获取可用的ZLM节点]未找到可使用的ZLM...");
  675. }
  676. return mediaServerItem;
  677. }
  678. @Override
  679. public void playBack(String deviceId, String channelId, String startTime,
  680. String endTime, InviteStreamCallback inviteStreamCallback,
  681. PlayBackCallback callback) {
  682. Device device = storager.queryVideoDevice(deviceId);
  683. if (device == null) {
  684. return;
  685. }
  686. MediaServerItem newMediaServerItem = getNewMediaServerItem(device);
  687. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, device.isSsrcCheck(), true);
  688. playBack(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, inviteStreamCallback, callback);
  689. }
  690. @Override
  691. public void playBack(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo,
  692. String deviceId, String channelId, String startTime,
  693. String endTime, InviteStreamCallback infoCallBack,
  694. PlayBackCallback playBackCallback) {
  695. if (mediaServerItem == null || ssrcInfo == null) {
  696. return;
  697. }
  698. Device device = storager.queryVideoDevice(deviceId);
  699. if (device == null) {
  700. throw new ControllerException(ErrorCode.ERROR100.getCode(), "设备: " + deviceId + "不存在");
  701. }
  702. PlayBackResult<StreamInfo> playBackResult = new PlayBackResult<>();
  703. String playBackTimeOutTaskKey = UUID.randomUUID().toString();
  704. dynamicTask.startDelay(playBackTimeOutTaskKey, () -> {
  705. logger.warn(String.format("设备回放超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  706. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  707. playBackResult.setMsg("回放超时");
  708. try {
  709. cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  710. } catch (InvalidArgumentException | ParseException | SipException e) {
  711. logger.error("[录像流]回放超时 发送BYE失败 {}", e.getMessage());
  712. } catch (SsrcTransactionNotFoundException e) {
  713. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  714. logger.info("回放超时")
  715. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  716. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  717. streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
  718. }
  719. // 回复之前所有的点播请求
  720. playBackCallback.call(playBackResult);
  721. }, userSetting.getPlayTimeout());
  722. SipSubscribe.Event errorEvent = event -> {
  723. dynamicTask.stop(playBackTimeOutTaskKey);
  724. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  725. playBackResult.setMsg(String.format("回放失败, 错误码: %s, %s", event.statusCode, event.msg));
  726. playBackResult.setEvent(event);
  727. playBackCallback.call(playBackResult);
  728. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  729. };
  730. InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
  731. logger.info("收到回放订阅消息: " + inviteStreamInfo.getResponse().toJSONString());
  732. dynamicTask.stop(playBackTimeOutTaskKey);
  733. StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  734. if (streamInfo == null) {
  735. logger.warn("设备回放API调用失败!");
  736. playBackResult.setCode(ErrorCode.ERROR100.getCode());
  737. playBackResult.setMsg("设备回放API调用失败!");
  738. playBackCallback.call(playBackResult);
  739. return;
  740. }
  741. redisCatchStorage.startPlayback(streamInfo, inviteStreamInfo.getCallId());
  742. playBackResult.setCode(ErrorCode.SUCCESS.getCode());
  743. playBackResult.setMsg(ErrorCode.SUCCESS.getMsg());
  744. playBackResult.setData(streamInfo);
  745. playBackResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
  746. playBackResult.setResponse(inviteStreamInfo.getResponse());
  747. playBackCallback.call(playBackResult);
  748. };
  749. try {
  750. cmder.playbackStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, infoCallBack,
  751. hookEvent, eventResult -> {
  752. if (eventResult.type == SipSubscribe.EventResultType.response) {
  753. ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
  754. String contentString = new String(responseEvent.getResponse().getRawContent());
  755. // 获取ssrc
  756. int ssrcIndex = contentString.indexOf("y=");
  757. // 检查是否有y字段
  758. if (ssrcIndex >= 0) {
  759. //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  760. String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  761. // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  762. if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  763. return;
  764. }
  765. logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  766. if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
  767. logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  768. if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  769. // ssrc 不可用
  770. // 释放ssrc
  771. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  772. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  773. eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
  774. eventResult.statusCode = 400;
  775. errorEvent.response(eventResult);
  776. return;
  777. }
  778. // 单端口模式streamId也有变化,需要重新设置监听
  779. if (!mediaServerItem.isRtpEnable()) {
  780. // 添加订阅
  781. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  782. subscribe.removeSubscribe(hookSubscribe);
  783. hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  784. subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  785. logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  786. dynamicTask.stop(playBackTimeOutTaskKey);
  787. // hook响应
  788. onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc(), playBackCallback);
  789. hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
  790. });
  791. }
  792. // 关闭rtp server
  793. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  794. // 重新开启ssrc server
  795. mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
  796. }
  797. }
  798. }
  799. }, errorEvent);
  800. } catch (InvalidArgumentException | SipException | ParseException e) {
  801. logger.error("[命令发送失败] 回放: {}", e.getMessage());
  802. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  803. eventResult.msg = "命令发送失败";
  804. errorEvent.response(eventResult);
  805. }
  806. }
  807. @Override
  808. public void download(String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback playBackCallback) {
  809. Device device = storager.queryVideoDevice(deviceId);
  810. if (device == null) {
  811. return;
  812. }
  813. MediaServerItem newMediaServerItem = getNewMediaServerItemHasAssist(device);
  814. if (newMediaServerItem == null) {
  815. PlayBackResult<StreamInfo> downloadResult = new PlayBackResult<>();
  816. downloadResult.setCode(ErrorCode.ERROR100.getCode());
  817. downloadResult.setMsg("未找到assist服务");
  818. playBackCallback.call(downloadResult);
  819. return;
  820. }
  821. SSRCInfo ssrcInfo = mediaServerService.openRTPServer(newMediaServerItem, null, device.isSsrcCheck(), true);
  822. download(newMediaServerItem, ssrcInfo, deviceId, channelId, startTime, endTime, downloadSpeed, infoCallBack, playBackCallback);
  823. }
  824. @Override
  825. public void download(MediaServerItem mediaServerItem, SSRCInfo ssrcInfo, String deviceId, String channelId, String startTime, String endTime, int downloadSpeed, InviteStreamCallback infoCallBack, PlayBackCallback hookCallBack) {
  826. if (mediaServerItem == null || ssrcInfo == null) {
  827. return;
  828. }
  829. Device device = storager.queryVideoDevice(deviceId);
  830. if (device == null) {
  831. throw new ControllerException(ErrorCode.ERROR400.getCode(), "设备:" + deviceId + "不存在");
  832. }
  833. PlayBackResult<StreamInfo> downloadResult = new PlayBackResult<>();
  834. String downLoadTimeOutTaskKey = UUID.randomUUID().toString();
  835. dynamicTask.startDelay(downLoadTimeOutTaskKey, () -> {
  836. logger.warn(String.format("录像下载请求超时,deviceId:%s ,channelId:%s", deviceId, channelId));
  837. downloadResult.setCode(ErrorCode.ERROR100.getCode());
  838. downloadResult.setMsg("录像下载请求超时");
  839. hookCallBack.call(downloadResult);
  840. // 点播超时回复BYE 同时释放ssrc以及此次点播的资源
  841. try {
  842. cmder.streamByeCmd(device, channelId, ssrcInfo.getStream(), null);
  843. } catch (InvalidArgumentException | ParseException | SipException e) {
  844. logger.error("[录像流]录像下载请求超时, 发送BYE失败 {}", e.getMessage());
  845. } catch (SsrcTransactionNotFoundException e) {
  846. logger.info("下载失败 SsrcTransactionNotFoundException");
  847. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  848. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  849. streamSession.remove(deviceId, channelId, ssrcInfo.getStream());
  850. }
  851. }, userSetting.getPlayTimeout());
  852. SipSubscribe.Event errorEvent = event -> {
  853. dynamicTask.stop(downLoadTimeOutTaskKey);
  854. downloadResult.setCode(ErrorCode.ERROR100.getCode());
  855. downloadResult.setMsg(String.format("录像下载失败, 错误码: %s, %s", event.statusCode, event.msg));
  856. downloadResult.setEvent(event);
  857. hookCallBack.call(downloadResult);
  858. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  859. };
  860. InviteStreamCallback hookEvent = (InviteStreamInfo inviteStreamInfo) -> {
  861. logger.info("收到订阅消息: " + inviteStreamInfo.getCallId());
  862. dynamicTask.stop(downLoadTimeOutTaskKey);
  863. StreamInfo streamInfo = onPublishHandler(inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  864. streamInfo.setStartTime(startTime);
  865. streamInfo.setEndTime(endTime);
  866. redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
  867. downloadResult.setCode(ErrorCode.SUCCESS.getCode());
  868. downloadResult.setMsg(ErrorCode.SUCCESS.getMsg());
  869. downloadResult.setData(streamInfo);
  870. downloadResult.setMediaServerItem(inviteStreamInfo.getMediaServerItem());
  871. downloadResult.setResponse(inviteStreamInfo.getResponse());
  872. hookCallBack.call(downloadResult);
  873. };
  874. try {
  875. cmder.downloadStreamCmd(mediaServerItem, ssrcInfo, device, channelId, startTime, endTime, downloadSpeed, infoCallBack,
  876. hookEvent, errorEvent, eventResult ->
  877. {
  878. if (eventResult.type == SipSubscribe.EventResultType.response) {
  879. ResponseEvent responseEvent = (ResponseEvent) eventResult.event;
  880. String contentString = new String(responseEvent.getResponse().getRawContent());
  881. // 获取ssrc
  882. int ssrcIndex = contentString.indexOf("y=");
  883. // 检查是否有y字段
  884. if (ssrcIndex >= 0) {
  885. //ssrc规定长度为10字节,不取余下长度以避免后续还有“f=”字段 TODO 后续对不规范的非10位ssrc兼容
  886. String ssrcInResponse = contentString.substring(ssrcIndex + 2, ssrcIndex + 12);
  887. // 查询到ssrc不一致且开启了ssrc校验则需要针对处理
  888. if (ssrcInfo.getSsrc().equals(ssrcInResponse)) {
  889. return;
  890. }
  891. logger.info("[回放消息] 收到invite 200, 发现下级自定义了ssrc: {}", ssrcInResponse);
  892. if (!mediaServerItem.isRtpEnable() || device.isSsrcCheck()) {
  893. logger.info("[回放消息] SSRC修正 {}->{}", ssrcInfo.getSsrc(), ssrcInResponse);
  894. if (!mediaServerItem.getSsrcConfig().checkSsrc(ssrcInResponse)) {
  895. // ssrc 不可用
  896. // 释放ssrc
  897. mediaServerService.releaseSsrc(mediaServerItem.getId(), ssrcInfo.getSsrc());
  898. streamSession.remove(device.getDeviceId(), channelId, ssrcInfo.getStream());
  899. eventResult.msg = "下级自定义了ssrc,但是此ssrc不可用";
  900. eventResult.statusCode = 400;
  901. errorEvent.response(eventResult);
  902. return;
  903. }
  904. // 单端口模式streamId也有变化,需要重新设置监听
  905. if (!mediaServerItem.isRtpEnable()) {
  906. // 添加订阅
  907. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", ssrcInfo.getStream(), true, "rtsp", mediaServerItem.getId());
  908. subscribe.removeSubscribe(hookSubscribe);
  909. hookSubscribe.getContent().put("stream", String.format("%08x", Integer.parseInt(ssrcInResponse)).toUpperCase());
  910. subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject response) -> {
  911. logger.info("[ZLM HOOK] ssrc修正后收到订阅消息: " + response.toJSONString());
  912. dynamicTask.stop(downLoadTimeOutTaskKey);
  913. // hook响应
  914. onPublishHandlerForPlayback(mediaServerItemInUse, response, device.getDeviceId(), channelId, ssrcInfo.getSsrc(), hookCallBack);
  915. hookEvent.call(new InviteStreamInfo(mediaServerItem, null, eventResult.callId, "rtp", ssrcInfo.getStream()));
  916. });
  917. }
  918. logger.info("下载完成");
  919. // 关闭rtp server
  920. mediaServerService.closeRTPServer(mediaServerItem.getId(), ssrcInfo.getStream());
  921. // 重新开启ssrc server
  922. mediaServerService.openRTPServer(mediaServerItem, ssrcInfo.getStream(), ssrcInResponse, device.isSsrcCheck(), true, ssrcInfo.getPort());
  923. }
  924. }
  925. }
  926. });
  927. } catch (InvalidArgumentException | SipException | ParseException e) {
  928. logger.error("[命令发送失败] 录像下载: {}", e.getMessage());
  929. SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  930. eventResult.msg = "命令发送失败";
  931. errorEvent.response(eventResult);
  932. }
  933. }
  934. @Override
  935. public StreamInfo getDownLoadInfo(String deviceId, String channelId, String stream) {
  936. StreamInfo streamInfo = redisCatchStorage.queryDownload(deviceId, channelId, stream, null);
  937. if (streamInfo != null) {
  938. if (streamInfo.getProgress() == 1) {
  939. return streamInfo;
  940. }
  941. // 获取当前已下载时长
  942. String mediaServerId = streamInfo.getMediaServerId();
  943. MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  944. if (mediaServerItem == null) {
  945. logger.warn("查询录像信息时发现节点已离线");
  946. return null;
  947. }
  948. if (mediaServerItem.getRecordAssistPort() > 0) {
  949. JSONObject jsonObject = assistRESTfulUtils.fileDuration(mediaServerItem, streamInfo.getApp(), streamInfo.getStream(), null);
  950. if (jsonObject == null) {
  951. throw new ControllerException(ErrorCode.ERROR100.getCode(), "连接Assist服务失败");
  952. }
  953. if (jsonObject.getInteger("code") == 0) {
  954. long duration = jsonObject.getLong("data");
  955. if (duration == 0) {
  956. streamInfo.setProgress(0);
  957. } else {
  958. String startTime = streamInfo.getStartTime();
  959. String endTime = streamInfo.getEndTime();
  960. long start = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime);
  961. long end = DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime);
  962. BigDecimal currentCount = new BigDecimal(duration / 1000);
  963. BigDecimal totalCount = new BigDecimal(end - start);
  964. BigDecimal divide = currentCount.divide(totalCount, 2, RoundingMode.HALF_UP);
  965. double process = divide.doubleValue();
  966. streamInfo.setProgress(process);
  967. }
  968. }
  969. }
  970. }
  971. return streamInfo;
  972. }
  973. @Override
  974. public void onPublishHandlerForDownload(InviteStreamInfo inviteStreamInfo, String deviceId, String channelId, String uuid) {
  975. RequestMessage msg = new RequestMessage();
  976. msg.setKey(DeferredResultHolder.CALLBACK_CMD_DOWNLOAD + deviceId + channelId);
  977. msg.setId(uuid);
  978. StreamInfo streamInfo = onPublishHandler(
  979. inviteStreamInfo.getMediaServerItem(), inviteStreamInfo.getResponse(), deviceId, channelId);
  980. if (streamInfo != null) {
  981. redisCatchStorage.startDownload(streamInfo, inviteStreamInfo.getCallId());
  982. msg.setData(JSON.toJSONString(streamInfo));
  983. resultHolder.invokeResult(msg);
  984. } else {
  985. logger.warn("设备预览API调用失败!");
  986. msg.setData(WVPResult.fail(ErrorCode.ERROR100.getCode(), "设备预览API调用失败!"));
  987. resultHolder.invokeResult(msg);
  988. }
  989. }
  990. public StreamInfo onPublishHandler(MediaServerItem mediaServerItem, JSONObject json, String deviceId, String channelId) {
  991. String streamId = json.getString("stream");
  992. JSONArray tracks = json.getJSONArray("tracks");
  993. StreamInfo streamInfo = mediaService.getStreamInfoByAppAndStream(mediaServerItem, "rtp", streamId, tracks, null);
  994. streamInfo.setDeviceID(deviceId);
  995. streamInfo.setChannelId(channelId);
  996. return streamInfo;
  997. }
  998. @Override
  999. public void zlmServerOffline(String mediaServerId) {
  1000. // 处理正在向上推流的上级平台
  1001. List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(null);
  1002. if (sendRtpItems.size() > 0) {
  1003. for (SendRtpItem sendRtpItem : sendRtpItems) {
  1004. if (sendRtpItem.getMediaServerId().equals(mediaServerId)) {
  1005. ParentPlatform platform = storager.queryParentPlatByServerGBId(sendRtpItem.getPlatformId());
  1006. try {
  1007. sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
  1008. } catch (SipException | InvalidArgumentException | ParseException e) {
  1009. logger.error("[命令发送失败] 国标级联 发送BYE: {}", e.getMessage());
  1010. }
  1011. }
  1012. }
  1013. }
  1014. // 处理正在观看的国标设备
  1015. List<SsrcTransaction> allSsrc = streamSession.getAllSsrc();
  1016. if (allSsrc.size() > 0) {
  1017. for (SsrcTransaction ssrcTransaction : allSsrc) {
  1018. if (ssrcTransaction.getMediaServerId().equals(mediaServerId)) {
  1019. Device device = deviceService.getDevice(ssrcTransaction.getDeviceId());
  1020. if (device == null) {
  1021. continue;
  1022. }
  1023. try {
  1024. cmder.streamByeCmd(device, ssrcTransaction.getChannelId(),
  1025. ssrcTransaction.getStream(), null);
  1026. } catch (InvalidArgumentException | ParseException | SipException |
  1027. SsrcTransactionNotFoundException e) {
  1028. logger.error("[zlm离线]为正在使用此zlm的设备, 发送BYE失败 {}", e.getMessage());
  1029. }
  1030. }
  1031. }
  1032. }
  1033. }
  1034. @Override
  1035. public void zlmServerOnline(String mediaServerId) {
  1036. // TODO 查找之前的点播,流如果不存在则给下级发送bye
  1037. // MediaServerItem mediaServerItem = mediaServerService.getOne(mediaServerId);
  1038. // zlmresTfulUtils.getMediaList(mediaServerItem, (mediaList ->{
  1039. // Integer code = mediaList.getInteger("code");
  1040. // if (code == 0) {
  1041. // JSONArray data = mediaList.getJSONArray("data");
  1042. // if (data == null || data.size() == 0) {
  1043. // zlmServerOffline(mediaServerId);
  1044. // }else {
  1045. // Map<String, JSONObject> mediaListMap = new HashMap<>();
  1046. // for (int i = 0; i < data.size(); i++) {
  1047. // JSONObject json = data.getJSONObject(i);
  1048. // String app = json.getString("app");
  1049. // if ("rtp".equals(app)) {
  1050. // String stream = json.getString("stream");
  1051. // if (mediaListMap.get(stream) != null) {
  1052. // continue;
  1053. // }
  1054. // mediaListMap.put(stream, json);
  1055. // // 处理正在观看的国标设备
  1056. // List<SsrcTransaction> ssrcTransactions = streamSession.getSsrcTransactionForAll(null, null, null, stream);
  1057. // if (ssrcTransactions.size() > 0) {
  1058. // for (SsrcTransaction ssrcTransaction : ssrcTransactions) {
  1059. // if(ssrcTransaction.getMediaServerId().equals(mediaServerId)) {
  1060. // cmder.streamByeCmd(ssrcTransaction.getDeviceId(), ssrcTransaction.getChannelId(),
  1061. // ssrcTransaction.getStream(), null);
  1062. // }
  1063. // }
  1064. // }
  1065. // }
  1066. // }
  1067. // if (mediaListMap.size() > 0 ) {
  1068. // // 处理正在向上推流的上级平台
  1069. // List<SendRtpItem> sendRtpItems = redisCatchStorage.querySendRTPServer(null);
  1070. // if (sendRtpItems.size() > 0) {
  1071. // for (SendRtpItem sendRtpItem : sendRtpItems) {
  1072. // if (sendRtpItem.getMediaServerId().equals(mediaServerId)) {
  1073. // if (mediaListMap.get(sendRtpItem.getStreamId()) == null) {
  1074. // ParentPlatform platform = storager.queryPlatformByServerGBId(sendRtpItem.getPlatformId());
  1075. // sipCommanderFroPlatform.streamByeCmd(platform, sendRtpItem.getCallId());
  1076. // }
  1077. // }
  1078. // }
  1079. // }
  1080. // }
  1081. // }
  1082. // }
  1083. // }));
  1084. }
  1085. @Override
  1086. public void pauseRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException {
  1087. String key = redisCatchStorage.queryPlaybackForKey(null, null, streamId, null);
  1088. StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
  1089. if (null == streamInfo) {
  1090. logger.warn("streamId不存在!");
  1091. throw new ServiceException("streamId不存在");
  1092. }
  1093. streamInfo.setPause(true);
  1094. RedisUtil.set(key, streamInfo);
  1095. MediaServerItem mediaServerItem = mediaServerService.getOne(streamInfo.getMediaServerId());
  1096. if (null == mediaServerItem) {
  1097. logger.warn("mediaServer 不存在!");
  1098. throw new ServiceException("mediaServer不存在");
  1099. }
  1100. // zlm 暂停RTP超时检查
  1101. JSONObject jsonObject = zlmresTfulUtils.pauseRtpCheck(mediaServerItem, streamId);
  1102. if (jsonObject == null || jsonObject.getInteger("code") != 0) {
  1103. throw new ServiceException("暂停RTP接收失败");
  1104. }
  1105. Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
  1106. cmder.playPauseCmd(device, streamInfo);
  1107. }
  1108. @Override
  1109. public void resumeRtp(String streamId) throws ServiceException, InvalidArgumentException, ParseException, SipException {
  1110. String key = redisCatchStorage.queryPlaybackForKey(null, null, streamId, null);
  1111. StreamInfo streamInfo = redisCatchStorage.queryPlayback(null, null, streamId, null);
  1112. if (null == streamInfo) {
  1113. logger.warn("streamId不存在!");
  1114. throw new ServiceException("streamId不存在");
  1115. }
  1116. streamInfo.setPause(false);
  1117. RedisUtil.set(key, streamInfo);
  1118. MediaServerItem mediaServerItem = mediaServerService.getOne(streamInfo.getMediaServerId());
  1119. if (null == mediaServerItem) {
  1120. logger.warn("mediaServer 不存在!");
  1121. throw new ServiceException("mediaServer不存在");
  1122. }
  1123. // zlm 暂停RTP超时检查
  1124. JSONObject jsonObject = zlmresTfulUtils.resumeRtpCheck(mediaServerItem, streamId);
  1125. if (jsonObject == null || jsonObject.getInteger("code") != 0) {
  1126. throw new ServiceException("继续RTP接收失败");
  1127. }
  1128. Device device = storager.queryVideoDevice(streamInfo.getDeviceID());
  1129. cmder.playResumeCmd(device, streamInfo);
  1130. }
  1131. public void broadcast(
  1132. MediaServerItem mediaServerItem,
  1133. Device device,
  1134. BroadcastItem broadcastItem,
  1135. int waitTime,
  1136. BroadcastCallback callback
  1137. ){
  1138. // 初始化waitTime值为15秒
  1139. if (waitTime == 0) {
  1140. waitTime = 15 * 1000;
  1141. }
  1142. logger.info("[语言广播] 开始返回invite200,并创建zlm服务 app:{} stream:{} ",
  1143. broadcastItem.getApp(), broadcastItem.getStream());
  1144. JSONObject errJson = new JSONObject();
  1145. try {
  1146. String steamId = "relay_" + broadcastItem.getStream();
  1147. String ssrcStr = broadcastItem.getSsrc();
  1148. // 更新
  1149. broadcastItem.setRecv_stream(steamId);
  1150. ssrcStr = ssrcStr.replaceAll("\\s*|\r|\n|\t", "");
  1151. SSRCInfo ssrcInfo = mediaServerService.startSendRtpServer(mediaServerItem,
  1152. broadcastItem.getApp(),
  1153. broadcastItem.getRecv_stream(),
  1154. broadcastItem.getStream(),
  1155. broadcastItem.getIpcIp(),
  1156. String.valueOf(broadcastItem.getIpcAudioPort()),
  1157. ssrcStr,
  1158. waitTime
  1159. );
  1160. broadcastItem.setSsrc(ssrcInfo.getSsrc());
  1161. if (ssrcInfo == null) {
  1162. logger.error("[zlm控制异常] 创建媒体流失败");
  1163. errJson.put("msg", "[zlm控制异常] 创建媒体流失败");
  1164. callback.run(2, errJson, null);
  1165. return;
  1166. }
  1167. // 回复invite 200 信息至设备
  1168. cmder.sendBoradcastInviteCmd(broadcastItem.getRequest(), mediaServerItem, ssrcInfo, device, null, null,
  1169. null,
  1170. null,
  1171. null);
  1172. callback.run(0, null, null);
  1173. } catch (InvalidArgumentException | SipException | ParseException e) {
  1174. logger.error("[下发audio拉流invite失败]", e);
  1175. logger.error("[zlm控制异常] 创建媒体流失败");
  1176. errJson.put("msg", "[zlm控制异常] 创建媒体流失败");
  1177. callback.run(2, errJson, null);
  1178. // SipSubscribe.EventResult eventResult = new SipSubscribe.EventResult(new CmdSendFailEvent(null));
  1179. // eventResult.msg = "命令发送失败";
  1180. // errorEvent.response(eventResult);
  1181. }
  1182. }
  1183. ;
  1184. private void addHookSubscribeForStreamChange(
  1185. RequestMessage requestMsg, MediaServerItem mediaServerItem,
  1186. Device device, String channelId, SSRCInfo ssrcInfo) {
  1187. String stream = ssrcInfo.getStream();
  1188. HookSubscribeForStreamChange hookSubscribe = HookSubscribeFactory.on_stream_changed("rtp", stream, true, "rtsp", mediaServerItem.getId());
  1189. subscribe.addSubscribe(hookSubscribe, (MediaServerItem mediaServerItemInUse, JSONObject json) -> {
  1190. // zlm 事件触发. 流改变事件
  1191. logger.info("[ZLM HOOK] 收到ZLM流 编号信息: {}", json.toJSONString());
  1192. logger.info("[点播成功] deviceId: {}, channelId: {}", device.getDeviceId(), channelId);
  1193. // 处理结果
  1194. onPublishHandlerForPlay(requestMsg, mediaServerItemInUse, json, device.getDeviceId(), channelId, ssrcInfo.getSsrc());
  1195. // 请求截图
  1196. screenshot(mediaServerItemInUse, device.getDeviceId(), channelId, ssrcInfo);
  1197. });
  1198. }
  1199. }