Parcourir la source

feat: 录像回放支持音频
1. 录像回放支持音频播放

kindring il y a 10 mois
Parent
commit
b8584aa6c7

+ 234 - 35
web_src/src/components/GBRecordDetail.vue

@@ -45,10 +45,33 @@
                   :message="videoError"
                   :hasAudio="hasAudio"
                   style="max-height: 100%"
+                  v-if="activePlayer === 'jessibuca'"
                   fluent autoplay live></player>
+          <rtc-player
+              v-if="activePlayer === 'webrtc'"
+              ref="webrtc"
+              @eventCallback="webrtcPlayEventHandle"
+              :videoUrl="videoUrl"
+              :error="videoError"
+              :message="videoError"
+              :hasAudio="hasAudio"
+          />
         </div>
         <div class="player-option-box">
           <div>
+            <el-radio-group
+                v-model="activePlayer"
+                size="medium"
+                @input="playerChangeHandle"
+            >
+              <el-radio-button
+                  v-for="(item,i) in playerList"
+                  :key="item.key + i"
+                  :label="item.key">
+                {{ item.text }}
+              </el-radio-button>
+            </el-radio-group>
+
             <el-button-group>
               <el-time-picker
                   size="mini"
@@ -118,14 +141,55 @@ import player from './common/jessibuca.vue'
 import moment from 'moment'
 import recordDownload from './dialog/recordDownload.vue'
 import handle from "@/until/handle";
+import RtcPlayer from "@components/dialog/rtcPlayer.vue";
+import mpegTsVideo from "@components/common/mpegtsVideo.vue";
+import h265WebJessibuca from "@components/common/h265webJessibuca.vue";
+import H265WebJs from "@components/common/h265webJs.vue";
+import {webrtcEvent} from "@/map/eventMap";
+import {strFill} from "@/until/str";
+
+const playList = [
+  {
+    key: "jessibuca",
+    text: "265 jessibuca播放器",
+    description: "用于播放265格式视频",
+    support: ['h264', 'h265'],
+    audioSupport: ['aac'],
+    disable: false
+  },
+  {
+    key: "webrtc",
+    text: "webrtc 播放器",
+    description: "延迟较低的播放器,需要浏览器支持",
+    support: ['h264'],
+    audioSupport: ['opus', 'pcma', 'pcmu'],
+    disable: false
+  },
+
+
+];
+const playerProtocol = {
+  webrtc: ["rtc", "rtcs"],
+  jessibuca: ["ws_flv", "wss_flv"],
+};
+let playTimeSliderMarks = {};
+for (let i = 0 ; i < 24 ; i++)
+{
+  playTimeSliderMarks[i * 3600] = `${strFill(i, 2)}:00`;
+}
 
 export default {
   name: 'app',
   components: {
+    H265WebJs, h265WebJessibuca, mpegTsVideo,
+    RtcPlayer,
     uiHeader, player, recordDownload
   },
   data() {
     return {
+      activePlayer: '',
+      playerList: playList,
+      fps: 15,
       deviceId: this.$route.params.deviceId,
       channelId: this.$route.params.channelId,
       recordsLoading: false,
@@ -159,33 +223,7 @@ export default {
       timeRange: null,
       startTime: null,
       endTime: null,
-      playTimeSliderMarks: {
-        0: "00:00",
-        3600: "01:00",
-        7200: "02:00",
-        10800: "03:00",
-        14400: "04:00",
-        18000: "05:00",
-        21600: "06:00",
-        25200: "07:00",
-        28800: "08:00",
-        32400: "09:00",
-        36000: "10:00",
-        39600: "11:00",
-        43200: "12:00",
-        46800: "13:00",
-        50400: "14:00",
-        54000: "15:00",
-        57600: "16:00",
-        61200: "17:00",
-        64800: "18:00",
-        68400: "19:00",
-        72000: "20:00",
-        75600: "21:00",
-        79200: "22:00",
-        82800: "23:00",
-        86400: "24:00",
-      },
+      playTimeSliderMarks: playTimeSliderMarks,
       extList: [],
       filterExt: null,
     };
@@ -209,6 +247,9 @@ export default {
     this.playerStyle["height"] = this.winHeight + "px";
     this.chooseDate = moment().format('YYYY-MM-DD')
     this.dateChange();
+    if(!this.activePlayer){
+      this.activePlayer = this.playerList[0].key;
+    }
   },
   beforeDestroy() {
     console.log('准备销毁页面')
@@ -228,7 +269,7 @@ export default {
 
       this.setTime(this.chooseDate + " 00:00:00", this.chooseDate + " 23:59:59");
       this.recordsLoading = true;
-      this.detailFiles = [];
+      // this.detailFiles = [];
       let url = '/api/gb_record/query/'
       url += `${this.deviceId}/${this.channelId}`;
       url += `?startTime=${this.startTime}`;
@@ -318,14 +359,19 @@ export default {
           method: 'get',
           url: '/api/playback/start/' + this.deviceId + '/' + this.channelId + '?startTime=' + this.startTime + '&endTime=' +
               this.endTime
-        }).then((res) => {
+        }).then(async (res) => {
           if (res.data.code === 0) {
             this.streamInfo = res.data.data;
+            await this.queryMediaInfo(true);
+            console.log(`streamInfo`);
+            console.log(this.streamInfo);
             this.app = this.streamInfo.app;
             this.streamId = this.streamInfo.stream;
             this.mediaServerId = this.streamInfo.mediaServerId;
             this.ssrc = this.streamInfo.ssrc;
-            this.videoUrl = this.getUrlByStreamInfo();
+            // 根据tracks 选择播放器
+
+            this.videoUrl = this.getUrlByStreamInfo(this.streamInfo);
           } else {
             this.$message({
               showClose: true,
@@ -426,14 +472,30 @@ export default {
       let differenceTime = timeForFile[0].getTime() - new Date(this.chooseDate + " 00:00:00").getTime()
       return parseFloat((differenceTime - this.sliderMIn * 1000) / ((this.sliderMax - this.sliderMIn) * 1000)) * 100;
     },
-    getUrlByStreamInfo() {
+    getUrlByStreamInfo(streamInfo) {
+      if (this.enableDebug) {
+        console.log("获取基础拉流地址");
+        console.log(streamInfo);
+      }
+      if (!streamInfo) {
+        return '';
+      }
+      if(!this.activePlayer){
+        this.activePlayer = this.playerList[0].key;
+      }
+      let playerData = playerProtocol[this.activePlayer];
       if (location.protocol === "https:") {
-        this.videoUrl = this.streamInfo["wss_flv"]
+        this.videoUrl = streamInfo[playerData[1]]
       } else {
-        this.videoUrl = this.streamInfo["ws_flv"]
+        this.videoUrl = streamInfo[playerData[0]]
       }
-      return this.videoUrl;
+      if (this.enableDebug) {
+        console.log(`播放url: ${this.videoUrl}`);
+      }
+      console.log(`播放url: ${this.videoUrl}`);
+      console.log(this.videoUrl)
 
+      return this.videoUrl;
     },
     timePickerChange: function (val) {
       this.setTime(val[0], val[1])
@@ -504,7 +566,144 @@ export default {
         this.detailFiles = this.detailFiles;
       }
 
-    }
+    },
+    findPlayer(videoType, audioType)
+    {
+      // 先尝试寻找到所有支持对应协议的播放器
+      let arr = this.playerList.filter(item => {
+        return item.support.includes(videoType)
+      })
+      console.log(arr);
+      console.log(arr);
+      console.log(arr);
+      console.log(arr);
+      // 从中找到同时支持
+      let res_player = arr.find(item => {
+        // 判断播放器是否同时支持 视频与音频
+        return item.audioSupport.includes(audioType);
+      })
+      if (!res_player)
+      {
+        res_player = arr[0]
+      }
+
+      return res_player;
+    },
+    async queryMediaInfo(isFirst) {
+      let info = this.streamInfo
+      let url = `/zlm/${info.mediaServerId}/index/api/getMediaInfo`
+      url += `?vhost=__defaultVhost__`
+      url += `&schema=rtsp`
+      url += `&app=${info.app}`
+      url += `&stream=${info.stream}`
+      url += this.shareCode ? `&shareCode=${this.shareCode}` : ''
+      let [err, res] = await handle(this.$axios.axios({
+        method: 'get',
+        url: url
+      }));
+
+      if (err) {
+        console.error(err);
+        this.$message.warning("流媒体信息获取失败")
+        return;
+      }
+      console.log(res);
+      if (res.data.code !== 0) {
+        this.tracksNotLoaded = true;
+        this.$message({
+          showClose: true,
+          message: '获取编码信息失败,',
+          type: 'warning'
+        });
+        return;
+      }
+      if (res.data.tracks) {
+        this.tracks = res.data.tracks;
+        if (this.tracks) {
+          console.log(`---------tracks----------`)
+          // 根据我们摄像头默认fps值来进行配置播放器
+          let fps = 15;
+          let isH265 = false;
+          let isPCMAudio = false;
+          for (const tracksElement of this.tracks) {
+            console.log(tracksElement);
+            if (tracksElement.fps) {
+              fps = tracksElement.fps;
+            }
+
+            console.log(`${tracksElement.codec_id_name}:code ${tracksElement.codec_id_name ? 'is h265' : 'not h265'}`)
+            if (tracksElement.codec_id_name && tracksElement.codec_id_name.includes("265")) {
+              isH265 = true;
+            }
+
+            if (tracksElement.codec_id_name && tracksElement.codec_id_name.includes("PCM"))
+            {
+              isPCMAudio = true;
+              // console.log("audio")
+            }
+          }
+          this.fps = fps;
+          if (isFirst) {
+            // g711 264
+            // g711 265
+            let _Player = this.findPlayer(isH265?'h265':'h264',
+                isPCMAudio?'pcma':'aac');
+            if (_Player) {
+              console.log(`切换为${_Player.key}播放器`);
+              this.activePlayer = _Player.key;
+            } else {
+              console.log("使用默认播放器");
+              this.activePlayer = playList[0].key;
+            }
+          }
+        }
+      } else {
+        console.log('没有编码信息');
+      }
+    },
+    playerChangeHandle() {
+      console.log('切换播放器');
+      this.$nextTick(() => {
+        // todo 切换url
+        this.videoUrl = this.getUrlByStreamInfo(this.streamInfo);
+      })
+    },
+    webrtcPlayEventHandle(type, e) {
+      // 添加同类型防抖
+      if (this.enableDebug) {
+        console.log('playEventHandle');
+        console.log(type, e);
+      }
+      if (this.playEventHandleTimer) {
+        clearTimeout(this.playEventHandleTimer);
+      }
+      this.playEventHandleTimer = setTimeout(() => {
+        this.webrtcEventExecute(type, e);
+      }, 300);
+    },
+    webrtcEventExecute(type, e) {
+      // 防抖,防止多次触发
+      if (type === webrtcEvent.apiFail.code) {
+        this.$notify.error({
+          title: 'ZLM连接失败',
+          dangerouslyUseHTMLString: true,
+          message: `<span>连接zlm服务失败${e.message}</span> <br/>
+                        <a href="${this.videoUrl}" target="_blank"><span>手动访问</span></a>`,
+          duration: 0
+        });
+        this.videoError = true;
+        this.videoUrl = '';
+
+      } else if (type === webrtcEvent.played.code) {
+        this.$message.success('播放成功');
+      } else if (type === webrtcEvent.sdpFail.code) {
+        console.log(e);
+        this.$notify.error({
+          title: 'sdp 交互失败',
+          duration: 4500
+        })
+      }
+    },
   }
 };
 </script>

+ 16 - 6
web_src/src/components/com/livePlayBox.vue

@@ -140,12 +140,22 @@ export default {
       }
       this.$message.success('复制播放地址成功');
     },
-    loadH265Player() {
-      let h265Player = playList.find(item => {
-        return item.support.includes('h265')
-      });
-      return h265Player;
+    findPlayer(videoType, audioType)
+    {
+      return playList.find(item => {
+        // 判断播放器是否同时支持 视频与音频
+        if (item.support.includes(videoType) &&
+            item.support.includes(audioType))
+        {
+          return true;
+        } else if (item.support.includes(videoType))
+        {
+          return true;
+        }
+
+      })
     },
+
     play(isFirst) {
 
       if (!this.videoUrl) {
@@ -250,7 +260,7 @@ export default {
           this.fps = fps;
           if (isFirst) {
             if (isH265) {
-              let h265Player = this.loadH265Player();
+              let h265Player = this.findPlayer();
               console.log("265视频流,切换为默认播放器");
               this.activePlayer = h265Player.key;
             } else {

+ 10 - 0
web_src/src/until/str.js

@@ -0,0 +1,10 @@
+/**
+ * 字符串补位
+ * @param str
+ * @param len
+ * @param char
+ * @return {string}
+ */
+export function strFill(str, len, char = '0') {
+  return (Array(len).join(char) + str).slice(-len)
+}

+ 2 - 2
web_src/vue.config.js

@@ -6,8 +6,8 @@ function resolve(dir) {
     return path.join(__dirname, dir);
 }
 
-const baseUrl = "https://127.0.0.1:19200"
-// const baseUrl = "https://hofuniot.cn:29072"
+// const baseUrl = "https://127.0.0.1:19200"
+const baseUrl = "https://hofuniot.cn:29072"
 // const c =
 module.exports = {
     devServer: {