diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java index 0c450a741..b9c05e45d 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMHttpHookListener.java @@ -128,29 +128,29 @@ public class ZLMHttpHookListener { } String app = json.getString("app"); String streamId = json.getString("id"); + if ("rtp".equals(app)) { + String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16)); + StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc); + if ("rtp".equals(app) && streamInfoForPlay != null ) { + MediaServerConfig mediaInfo = storager.getMediaInfo(); + streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId)); + streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId)); + storager.startPlay(streamInfoForPlay); + } - - String ssrc = new DecimalFormat("0000000000").format(Integer.parseInt(streamId, 16)); - StreamInfo streamInfoForPlay = storager.queryPlayBySSRC(ssrc); - if ("rtp".equals(app) && streamInfoForPlay != null ) { - MediaServerConfig mediaInfo = storager.getMediaInfo(); - streamInfoForPlay.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); - streamInfoForPlay.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); - streamInfoForPlay.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId)); - streamInfoForPlay.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); - streamInfoForPlay.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId)); - storager.startPlay(streamInfoForPlay); - } - - StreamInfo streamInfoForPlayBack = storager.queryPlaybackBySSRC(ssrc); - if ("rtp".equals(app) && streamInfoForPlayBack != null ) { - MediaServerConfig mediaInfo = storager.getMediaInfo(); - streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); - streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); - streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId)); - streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); - streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId)); - storager.startPlayback(streamInfoForPlayBack); + StreamInfo streamInfoForPlayBack = storager.queryPlaybackBySSRC(ssrc); + if ("rtp".equals(app) && streamInfoForPlayBack != null ) { + MediaServerConfig mediaInfo = storager.getMediaInfo(); + streamInfoForPlayBack.setFlv(String.format("http://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + streamInfoForPlayBack.setWs_flv(String.format("ws://%s:%s/rtp/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + streamInfoForPlayBack.setRtmp(String.format("rtmp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtmpPort(), streamId)); + streamInfoForPlayBack.setHls(String.format("http://%s:%s/rtp/%s/hls.m3u8", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + streamInfoForPlayBack.setRtsp(String.format("rtsp://%s:%s/rtp/%s", mediaInfo.getWanIp(), mediaInfo.getRtspPort(), streamId)); + storager.startPlayback(streamInfoForPlayBack); + } } // TODO Auto-generated method stub diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java index 86f05da43..1e38bdc74 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRESTfulUtils.java @@ -89,6 +89,22 @@ public class ZLMRESTfulUtils { return sendPost("getRtpInfo",param); } + public JSONObject addFFmpegSource(String src_url, String dst_url, String timeout_ms){ + System.out.println(src_url); + System.out.println(dst_url); + Map<String, Object> param = new HashMap<>(); + param.put("src_url", src_url); + param.put("dst_url", dst_url); + param.put("timeout_ms", timeout_ms); + return sendPost("addFFmpegSource",param); + } + + public JSONObject delFFmpegSource(String key){ + Map<String, Object> param = new HashMap<>(); + param.put("key", key); + return sendPost("delFFmpegSource",param); + } + public JSONObject getMediaServerConfig(){ return sendPost("getServerConfig",null); } diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java index fc519dae2..e7cfdb219 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMRunner.java @@ -101,7 +101,8 @@ public class ZLMRunner implements CommandLineRunner { String hookPrex = String.format("http://%s:%s/index/hook", hookIP, serverPort); Map<String, Object> param = new HashMap<>(); - param.put("secret",mediaSecret); + param.put("api.secret",mediaSecret); // -profile:v Baseline + param.put("ffmpeg.cmd","%s -fflags nobuffer -rtsp_transport tcp -i %s -c:a aac -strict -2 -ar 44100 -ab 48k -c:v libx264 -f flv %s"); param.put("hook.enable","1"); param.put("hook.on_flow_report",""); param.put("hook.on_play",""); diff --git a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java index f44e7df7c..8195b6567 100644 --- a/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java +++ b/src/main/java/com/genersoft/iot/vmp/media/zlm/ZLMUtils.java @@ -29,7 +29,7 @@ public class ZLMUtils { param.put("enable_tcp", 1); param.put("stream_id", streamId); JSONObject jsonObject = zlmresTfulUtils.openRtpServer(param); - if (jsonObject.getInteger("code") == 0) { + if (jsonObject != null && jsonObject.getInteger("code") == 0) { return newPort; } else { return getNewRTPPort(ssrc); diff --git a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java index a01748e2a..300ffc3d8 100644 --- a/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java +++ b/src/main/java/com/genersoft/iot/vmp/vmanager/play/PlayController.java @@ -3,6 +3,7 @@ package com.genersoft.iot.vmp.vmanager.play; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.genersoft.iot.vmp.common.StreamInfo; +import com.genersoft.iot.vmp.conf.MediaServerConfig; import com.genersoft.iot.vmp.media.zlm.ZLMRESTfulUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,7 +47,7 @@ public class PlayController { Integer getEncoding) { if (getEncoding == null) getEncoding = 0; - getEncoding = closeWaitRTPInfo ? 0: getEncoding; + getEncoding = closeWaitRTPInfo ? 0 : getEncoding; Device device = storager.queryVideoDevice(deviceId); StreamInfo streamInfo = storager.queryPlayByDevice(deviceId, channelId); @@ -149,5 +150,73 @@ public class PlayController { return new ResponseEntity<String>(HttpStatus.INTERNAL_SERVER_ERROR); } } + + /** + * 将不是h264的视频通过ffmpeg 转码为h264 + aac + * @param ssrc + * @return + */ + @PostMapping("/play/{ssrc}/convert") + public ResponseEntity<String> playConvert(@PathVariable String ssrc) { + StreamInfo streamInfo = storager.queryPlayBySSRC(ssrc); + if (streamInfo == null) { + logger.warn("视频转码API调用失败!, 视频流已经停止!"); + return new ResponseEntity<String>("未找到视频流信息, 视频流可能已经停止", HttpStatus.OK); + } + String streamId = String.format("%08x", Integer.parseInt(ssrc)).toUpperCase(); + JSONObject rtpInfo = zlmresTfulUtils.getRtpInfo(streamId); + if (!rtpInfo.getBoolean("exist")) { + logger.warn("视频转码API调用失败!, 视频流已停止推流!"); + return new ResponseEntity<String>("推流信息在流媒体中不存在, 视频流可能已停止推流", HttpStatus.OK); + } else { + MediaServerConfig mediaInfo = storager.getMediaInfo(); + String dstUrl = String.format("rtmp://%s:%s/convert/%s", "127.0.0.1", mediaInfo.getRtmpPort(), + streamId ); + JSONObject jsonObject = zlmresTfulUtils.addFFmpegSource(streamInfo.getRtsp(), dstUrl, "1000000"); + System.out.println(jsonObject); + JSONObject result = new JSONObject(); + if (jsonObject != null && jsonObject.getInteger("code") == 0) { + result.put("code", 0); + JSONObject data = jsonObject.getJSONObject("data"); + if (data != null) { + result.put("key", data.getString("key")); + result.put("rtmp", dstUrl); + result.put("flv", String.format("http://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + result.put("ws_flv", String.format("ws://%s:%s/convert/%s.flv", mediaInfo.getWanIp(), mediaInfo.getHttpPort(), streamId)); + } + }else { + result.put("code", 1); + result.put("msg", "cover fail"); + } + return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK); + } + } + + /** + * 结束转码 + * @param key + * @return + */ + @PostMapping("/play/convert/stop/{key}") + public ResponseEntity<String> playConvertStop(@PathVariable String key) { + + JSONObject jsonObject = zlmresTfulUtils.delFFmpegSource(key); + System.out.println(jsonObject); + JSONObject result = new JSONObject(); + if (jsonObject != null && jsonObject.getInteger("code") == 0) { + result.put("code", 0); + JSONObject data = jsonObject.getJSONObject("data"); + if (data != null && data.getBoolean("flag")) { + result.put("code", "0"); + result.put("msg", "success"); + }else { + + } + }else { + result.put("code", 1); + result.put("msg", "delFFmpegSource fail"); + } + return new ResponseEntity<String>( result.toJSONString(), HttpStatus.OK); + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 49bd03f96..463548a29 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -44,8 +44,11 @@ media: #zlm服务器的ip与http端口, 重点: 这是http端口 wanIp: port: 80 secret: 035c73f7-bb6b-4889-a715-d9eb2d1925cc - streamNoneReaderDelayMS: 1800000 # 无人观看多久自动关闭流 - closeWaitRTPInfo: false # 强制关闭等待收到流编码信息后在返回, 设为true可以快速打开播放窗口, 设为false保证返回后流就可以播放 + streamNoneReaderDelayMS: 600000 # 无人观看多久自动关闭流 + # 关闭等待收到流编码信息后在返回, + # 设为false可以获得更好的兼容性,保证返回后流就可以播放, + # 设为true可以快速打开播放窗口,可以获得更好的体验 + closeWaitRTPInfo: true rtp: # 启用udp多端口模式 enable: true udpPortRange: 30000,30500 # 端口范围 diff --git a/web_src/src/components/gb28181/devicePlayer.vue b/web_src/src/components/gb28181/devicePlayer.vue index 780d03e5f..c87d0c497 100644 --- a/web_src/src/components/gb28181/devicePlayer.vue +++ b/web_src/src/components/gb28181/devicePlayer.vue @@ -1,7 +1,7 @@ <template> -<div id="devicePlayer"> +<div id="devicePlayer" v-loading="isLoging"> <el-dialog title="视频播放" top="0" :close-on-click-modal="false" :visible.sync="showVideoDialog" :destroy-on-close="true" @close="close()"> - <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> + <LivePlayer v-if="showVideoDialog" ref="videoPlayer" :videoUrl="videoUrl" :error="videoError" :message="videoError" :hasaudio="hasaudio" fluent autoplay live></LivePlayer> <div id="shared" style="text-align: right; margin-top: 1rem;"> <el-tabs v-model="tabActiveName"> <el-tab-pane label="实时视频" name="media"> @@ -26,20 +26,6 @@ <!--{"code":0,"data":{"paths":["22-29-30.mp4"],"rootPath":"/home/kkkkk/Documents/ZLMediaKit/release/linux/Debug/www/record/hls/kkkkk/2020-05-11/"}}--> <el-tab-pane label="录像查询" name="record"> <el-date-picker size="mini" v-model="videoHistory.date" type="date" value-format="yyyy-MM-dd" placeholder="日期" @change="queryRecords()"></el-date-picker> - <!-- <el-slider style="margin: 0 1rem 1rem 1rem;"--> - <!-- v-model="timeVal"--> - <!-- :min="timeMin"--> - <!-- :max="timeMax"--> - <!-- :step="5"--> - <!-- :marks="getTimeMakrs()"--> - <!-- :format-tooltip="formatTooltip">--> - <!-- </el-slider>--> - <!-- <range-slider :min="timeMin"--> - <!-- :max="timeMax"--> - <!-- :step="5"></range-slider>--> - - <!-- <el-date-picker v-model="videoHistory.endTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="结束时间"--> - <!-- @change="recordList()"></el-date-picker>--> <el-table :data="videoHistory.searchHistoryResult" height="150" v-loading="recordsLoading"> <el-table-column label="名称" prop="name"></el-table-column> <el-table-column label="文件" prop="filePath"></el-table-column> @@ -143,41 +129,16 @@ export default { date: '', searchHistoryResult: [] //媒体流历史记录搜索结果 }, - timeMakrs: { - // 0 : "0:00", - // // 60 : "1:00", - // 120 : "2:00", - // // 180 : "3:00", - // 240 : "4:00", - // // 300 : "5:00", - // 360 : "6:00", - // // 420 : "7:00", - // 480 : "8:00", - // 540 : "9:00", - 600: "10:00", - // 660 : "11:00", - 720: "12:00", - // 780 : "13:00", - 840: "14:00", - // 900 : "15:00", - 960: "16:00", - // 1020 : "17:00", - 1080: "18:00", - // 1140 : "19:00", - // 1200 : "20:00", - // // 1260 : "21:00", - // 1320 : "22:00", - // // 1380 : "23:00", - // 1440 : "24:00" - }, showVideoDialog: false, ssrc: '', + convertKey: '', deviceId: '', channelId: '', tabActiveName: 'media', hasaudio: false, loadingRecords: false, recordsLoading: false, + isLoging: false, timeVal: 0, timeMin: 0, timeMax: 1440, @@ -200,6 +161,7 @@ export default { this.$refs.videoPlayer.pause(); } + switch (tab) { case "media": this.play(param.streamInfo, param.hasAudio) @@ -217,33 +179,97 @@ export default { timeAxisSelTime: function (val) { console.log(val) }, - getTimeMakrs() { - return this.timeMakrs; - }, play: function (streamInfo, hasAudio) { this.hasaudio = hasAudio; - // 根据媒体流信息二次判断 - var realHasAudio = false; - if (!!streamInfo.tracks && streamInfo.tracks.length > 0 && hasAudio) { + var that = this; + that.isLoging = false; + if (!!streamInfo.tracks && streamInfo.tracks.length > 0 ) { for (let i = 0; i < streamInfo.tracks.length; i++) { - if (streamInfo.tracks[i].codec_type == 1 && streamInfo.tracks[i].codec_id_name == "CodecAAC") { // 判断为AAC音频 - realHasAudio = true; - } + if (streamInfo.tracks[i].codec_type == 0 && streamInfo.tracks[i].codec_id_name != "CodecH264") { // 判断为H265视频 + that.coverPlay(streamInfo, streamInfo.tracks[i].codec_id_name, ()=>{ + that.close(); + return; + }) + }else if (streamInfo.tracks[i].codec_type == 1 && streamInfo.tracks[i].codec_id_name != "CodecAAC") { + that.coverPlay(streamInfo, streamInfo.tracks[i].codec_id_name, ()=>{ + that.playFromStreamInfo(false. streamInfo) + }) + }else if (streamInfo.tracks[i].codec_type == 1 && streamInfo.tracks[i].codec_id_name == "CodecAAC") { + that.playFromStreamInfo(true, streamInfo) + }else { + that.playFromStreamInfo(false, streamInfo) + } } + }else { + that.playFromStreamInfo(false, streamInfo) } - this.hasaudio = realHasAudio && this.hasaudio; - this.ssrc = streamInfo.ssrc; - // this.$refs.videoPlayer.hasaudio = hasAudio; - // this.videoUrl = streamInfo.flv + "?" + new Date().getTime(); - this.videoUrl = streamInfo.ws_flv; - this.showVideoDialog = true; - console.log(this.ssrc); + }, + coverPlay: function (streamInfo, codec_id_name, catchcallback) { + var that = this; + + that.$confirm(codec_id_name + ' 编码格式不支持播放, 是否转码播放?', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + type: 'warning' + }).then(() => { + that.isLoging = true; + that.$axios({ + method: 'post', + url: '/api/play/' + streamInfo.ssrc + '/convert' + }).then(function (res) { + if (res.data.code == 0) { + streamInfo.ws_flv = res.data.ws_flv; + that.convertKey = res.data.key; + setTimeout(()=>{ + that.isLoging = false; + that.playFromStreamInfo(false, streamInfo); + }, 2000) + } else { + that.isLoging = false; + that.$message({ + showClose: true, + message: '转码失败', + type: 'error' + }); + } + }).catch(function (e) { + that.$message({ + showClose: true, + message: '播放错误', + type: 'error' + }); + }); + }).catch(function (e) { + if (catchcallback)catchcallback() + }); + }, + playFromStreamInfo: function (realHasAudio, streamInfo) { + this.videoUrl = streamInfo.ws_flv; + this.showVideoDialog = true; + this.hasaudio = realHasAudio && this.hasaudio; + this.ssrc = streamInfo.ssrc; + console.log(this.ssrc); }, close: function () { console.log('关闭视频'); - this.$refs.videoPlayer.pause(); + if (!this.$refs.videoPlayer){ + this.$refs.videoPlayer.pause(); + } this.videoUrl = ''; this.showVideoDialog = false; + if (this.convertKey != '') { + this.$axios({ + method: 'post', + url: '/api/play/convert/stop/' + this.convertKey + }).then(function (res) { + if (res.data.code == 0) { + console.log(res.data.msg) + }else { + console.error(res.data.msg) + } + }).catch(function (e) {}); + } + this.convertKey = '' }, copySharedInfo: function (data) { console.log('复制内容:' + data);