src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
web_src/src/components/dialog/devicePlayer.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
web_src/src/components/dialog/easyPlayer.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/ISIPCommander.java
@@ -103,7 +103,18 @@ * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss */ void playbackStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent); /** * 请求历史媒体下载 * * @param device 视频设备 * @param channelId 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param downloadSpeed 下载倍速参数 */ void downloadStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, String downloadSpeed, ZLMHttpHookSubscribe.Event event, SipSubscribe.Event errorEvent); /** * 视频流停止 * src/main/java/com/genersoft/iot/vmp/gb28181/transmit/cmd/impl/SIPCommander.java
@@ -555,7 +555,121 @@ } } /** * 请求历史媒体下载 * * @param device 视频设备 * @param channelId 预览通道 * @param startTime 开始时间,格式要求:yyyy-MM-dd HH:mm:ss * @param endTime 结束时间,格式要求:yyyy-MM-dd HH:mm:ss * @param downloadSpeed 下载倍速参数 */ @Override public void downloadStreamCmd(IMediaServerItem mediaServerItem,Device device, String channelId, String startTime, String endTime, String downloadSpeed, ZLMHttpHookSubscribe.Event event , SipSubscribe.Event errorEvent) { try { String ssrc = streamSession.createPlayBackSsrc(); String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); Integer mediaPort = null; // 使用动态udp端口 if (mediaServerItem.isRtpEnable()) { mediaPort = zlmrtpServerFactory.createRTPServer(mediaServerItem, streamId); }else { mediaPort = mediaServerItem.getRtpProxyPort(); } logger.info("{} 分配的ZLM为: {} [{}:{}]", streamId, mediaServerItem.getId(), mediaServerItem.getIp(), mediaPort); // 添加订阅 JSONObject subscribeKey = new JSONObject(); subscribeKey.put("app", "rtp"); subscribeKey.put("stream", streamId); subscribeKey.put("regist", true); subscribeKey.put("mediaServerId", mediaServerItem.getId()); logger.debug("录像回放添加订阅,订阅内容:" + subscribeKey.toString()); subscribe.addSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey, (IMediaServerItem mediaServerItemInUse, JSONObject json)->{ if (userSetup.isWaitTrack() && json.getJSONArray("tracks") == null) return; event.response(mediaServerItemInUse, json); subscribe.removeSubscribe(ZLMHttpHookSubscribe.HookType.on_stream_changed, subscribeKey); }); StringBuffer content = new StringBuffer(200); content.append("v=0\r\n"); content.append("o="+sipConfig.getSipId()+" 0 0 IN IP4 "+sipConfig.getSipIp()+"\r\n"); content.append("s=Download\r\n"); content.append("u="+channelId+":0\r\n"); content.append("c=IN IP4 "+mediaServerItem.getSdpIp()+"\r\n"); content.append("t="+DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(startTime)+" " +DateUtil.yyyy_MM_dd_HH_mm_ssToTimestamp(endTime) +"\r\n"); String streamMode = device.getStreamMode().toUpperCase(); if (userSetup.isSeniorSdp()) { if("TCP-PASSIVE".equals(streamMode)) { content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); }else if ("TCP-ACTIVE".equals(streamMode)) { content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 126 125 99 34 98 97\r\n"); }else if("UDP".equals(streamMode)) { content.append("m=video "+ mediaPort +" RTP/AVP 96 126 125 99 34 98 97\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=fmtp:126 profile-level-id=42e01e\r\n"); content.append("a=rtpmap:126 H264/90000\r\n"); content.append("a=rtpmap:125 H264S/90000\r\n"); content.append("a=fmtp:125 profile-level-id=42e01e\r\n"); content.append("a=rtpmap:99 MP4V-ES/90000\r\n"); content.append("a=fmtp:99 profile-level-id=3\r\n"); content.append("a=rtpmap:98 H264/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n"); if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } }else { if("TCP-PASSIVE".equals(streamMode)) { content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n"); }else if ("TCP-ACTIVE".equals(streamMode)) { content.append("m=video "+ mediaPort +" TCP/RTP/AVP 96 98 97\r\n"); }else if("UDP".equals(streamMode)) { content.append("m=video "+ mediaPort +" RTP/AVP 96 98 97\r\n"); } content.append("a=recvonly\r\n"); content.append("a=rtpmap:96 PS/90000\r\n"); content.append("a=rtpmap:98 H264/90000\r\n"); content.append("a=rtpmap:97 MPEG4/90000\r\n"); if("TCP-PASSIVE".equals(streamMode)){ // tcp被动模式 content.append("a=setup:passive\r\n"); content.append("a=connection:new\r\n"); }else if ("TCP-ACTIVE".equals(streamMode)) { // tcp主动模式 content.append("a=setup:active\r\n"); content.append("a=connection:new\r\n"); } } content.append("a=downloadspeed:" + downloadSpeed + "\r\n"); content.append("y="+ssrc+"\r\n");//ssrc String tm = Long.toString(System.currentTimeMillis()); CallIdHeader callIdHeader = device.getTransport().equals("TCP") ? tcpSipProvider.getNewCallId() : udpSipProvider.getNewCallId(); Request request = headerProvider.createPlaybackInviteRequest(device, channelId, content.toString(), null, "fromplybck" + tm, null, callIdHeader); ClientTransaction transaction = transmitRequest(device, request, errorEvent); streamSession.put(device.getDeviceId(), channelId, ssrc, streamId, transaction); } catch ( SipException | ParseException | InvalidArgumentException e) { e.printStackTrace(); } } /** * 视频流停止, 不使用回调 src/main/java/com/genersoft/iot/vmp/vmanager/gb28181/playback/DownloadController.java
New file @@ -0,0 +1,140 @@ package com.genersoft.iot.vmp.vmanager.gb28181.playback; import com.genersoft.iot.vmp.common.StreamInfo; import com.genersoft.iot.vmp.gb28181.transmit.callback.DeferredResultHolder; import com.genersoft.iot.vmp.gb28181.transmit.callback.RequestMessage; //import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import com.genersoft.iot.vmp.media.zlm.dto.IMediaServerItem; import com.genersoft.iot.vmp.media.zlm.dto.MediaServerItem; import com.genersoft.iot.vmp.storager.IRedisCatchStorage; import com.genersoft.iot.vmp.service.IPlayService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.CrossOrigin; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSONObject; import com.genersoft.iot.vmp.gb28181.bean.Device; import com.genersoft.iot.vmp.gb28181.transmit.cmd.impl.SIPCommander; import com.genersoft.iot.vmp.storager.IVideoManagerStorager; import org.springframework.web.context.request.async.DeferredResult; import javax.sip.message.Response; import java.util.UUID; @Api(tags = "历史媒体下载") @CrossOrigin @RestController @RequestMapping("/api/download") public class DownloadController { private final static Logger logger = LoggerFactory.getLogger(DownloadController.class); @Autowired private SIPCommander cmder; @Autowired private IVideoManagerStorager storager; @Autowired private IRedisCatchStorage redisCatchStorage; // @Autowired // private ZLMRESTfulUtils zlmresTfulUtils; @Autowired private IPlayService playService; @Autowired private DeferredResultHolder resultHolder; @ApiOperation("开始历史媒体下载") @ApiImplicitParams({ @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), @ApiImplicitParam(name = "startTime", value = "开始时间", dataTypeClass = String.class), @ApiImplicitParam(name = "endTime", value = "结束时间", dataTypeClass = String.class), @ApiImplicitParam(name = "downloadSpeed", value = "下载倍速", dataTypeClass = String.class), }) @GetMapping("/start/{deviceId}/{channelId}") public DeferredResult<ResponseEntity<String>> play(@PathVariable String deviceId, @PathVariable String channelId, String startTime, String endTime, String downloadSpeed) { if (logger.isDebugEnabled()) { logger.debug(String.format("历史媒体下载 API调用,deviceId:%s,channelId:%s,downloadSpeed:%s", deviceId, channelId, downloadSpeed)); } UUID uuid = UUID.randomUUID(); DeferredResult<ResponseEntity<String>> result = new DeferredResult<ResponseEntity<String>>(30000L); // 超时处理 result.onTimeout(()->{ logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId)); RequestMessage msg = new RequestMessage(); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setData("Timeout"); resultHolder.invokeResult(msg); }); Device device = storager.queryVideoDevice(deviceId); StreamInfo streamInfo = redisCatchStorage.queryPlaybackByDevice(deviceId, channelId); if (streamInfo != null) { // 停止之前的下载 cmder.streamByeCmd(deviceId, channelId); } resultHolder.put(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid, result); IMediaServerItem newMediaServerItem = playService.getNewMediaServerItem(device); if (newMediaServerItem == null) { logger.warn(String.format("设备下载响应超时,deviceId:%s ,channelId:%s", deviceId, channelId)); RequestMessage msg = new RequestMessage(); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setData("Timeout"); resultHolder.invokeResult(msg); return result; } cmder.downloadStreamCmd(newMediaServerItem, device, channelId, startTime, endTime, downloadSpeed, (IMediaServerItem mediaServerItem, JSONObject response) -> { logger.info("收到订阅消息: " + response.toJSONString()); playService.onPublishHandlerForPlayBack(mediaServerItem, response, deviceId, channelId, uuid.toString()); }, event -> { Response response = event.getResponse(); RequestMessage msg = new RequestMessage(); msg.setId(DeferredResultHolder.CALLBACK_CMD_PlAY + uuid); msg.setData(String.format("回放失败, 错误码: %s, %s", response.getStatusCode(), response.getReasonPhrase())); resultHolder.invokeResult(msg); }); return result; } @ApiOperation("停止历史媒体下载") @ApiImplicitParams({ @ApiImplicitParam(name = "deviceId", value = "设备ID", dataTypeClass = String.class), @ApiImplicitParam(name = "channelId", value = "通道ID", dataTypeClass = String.class), }) @GetMapping("/stop/{deviceId}/{channelId}") public ResponseEntity<String> playStop(@PathVariable String deviceId, @PathVariable String channelId) { cmder.streamByeCmd(deviceId, channelId); if (logger.isDebugEnabled()) { logger.debug(String.format("设备历史媒体下载停止 API调用,deviceId/channelId:%s/%s", deviceId, channelId)); } if (deviceId != null && channelId != null) { JSONObject json = new JSONObject(); json.put("deviceId", deviceId); json.put("channelId", channelId); return new ResponseEntity<String>(json.toString(), HttpStatus.OK); } else { logger.warn("设备历史媒体下载停止API调用失败!"); return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); } } } web_src/src/components/dialog/devicePlayer.vue
@@ -48,7 +48,10 @@ <el-table-column label="操作"> <template slot-scope="scope"> <el-button icon="el-icon-video-play" size="mini" @click="playRecord(scope.row)">播放</el-button> <el-button-group> <el-button icon="el-icon-video-play" size="mini" @click="playRecord(scope.row)">播放</el-button> <el-button icon="el-icon-download" size="mini" @click="downloadRecord(scope.row)">下载</el-button> </el-button-group> </template> </el-table-column> </el-table> @@ -444,6 +447,38 @@ if (callback) callback() }); }, downloadRecord: function (row) { let that = this; if (that.streamId != "") { that.stopDownloadRecord(function () { that.streamId = "", that.downloadRecord(row); }) } else { this.$axios({ method: 'get', url: '/api/download/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + row.startTime + '&endTime=' + row.endTime + '&downloadSpeed=4' }).then(function (res) { var streamInfo = res.data; that.app = streamInfo.app; that.streamId = streamInfo.streamId; that.mediaServerId = streamInfo.mediaServerId; that.videoUrl = that.getUrlByStreamInfo(streamInfo); that.recordPlay = true; }); } }, stopDownloadRecord: function (callback) { this.$refs.videoPlayer.pause(); this.videoUrl = ''; this.$axios({ method: 'get', url: '/api/download/stop/' + this.deviceId + "/" + this.channelId }).then(function (res) { if (callback) callback() }); }, ptzCamera: function (leftRight, upDown, zoom) { console.log('云台控制:' + leftRight + ' : ' + upDown + " : " + zoom); let that = this; web_src/src/components/dialog/easyPlayer.vue
@@ -60,8 +60,8 @@ min-width: 70px; } /* 隐藏logo */ /* .iconqingxiLOGO { .iconqingxiLOGO { display: none !important; } */ } </style>