yusheng 2 weeks ago
parent
commit
7a2af14875

+ 1 - 0
package.json

@@ -34,6 +34,7 @@
     "element-tree-line": "^0.2.1",
     "element-ui": "2.15.14",
     "file-saver": "^2.0.5",
+    "flv.js": "^1.5.0",
     "github-markdown-css": "^5.1.0",
     "highlight.js": "9.18.5",
     "hls.js": "^1.5.16",

+ 226 - 67
src/views/components/video.vue

@@ -1,87 +1,246 @@
 <template>
+  <!-- <div v-loading="loading">
+
+  </div> -->
   <video
     :id="'video' + index"
     style="background-color: #333"
     class="video"
-    autoplay
     muted
+    playsinline
   ></video>
 </template>
 
 <script>
-import mpegts from 'mpegts.js';
-
-export default {
-  name: 'VideoPlayer',
-  
-  props: {
-    videoUrl: {
-      type: String,
-      default: '',
+  import mpegts from 'mpegts.js';
+
+  export default {
+    name: 'VideoPlayer',
+    props: {
+      videoUrl: {
+        type: String,
+        default: ''
+      },
+      index: {
+        type: Number,
+        default: 0
+      },
+      playDelaySeconds: {
+        type: Number,
+        default: 5 // 延迟2秒播放
+      }
     },
-    index: {
-      type: Number,
-      default: 0,
+    data() {
+      return {
+        player: null,
+        reconnectAttempts: 0,
+        maxReconnectAttempts: 5,
+        reconnectTimer: null,
+        isManualDestroy: false,
+        isPageUnloading: false, // 页面刷新/关闭标志
+        hasStarted: false, // 防止重复播放
+        config: {
+          liveBufferLatencyChasing: true,
+          liveBufferLatencyMinRemain: 0.5,
+          liveBufferLatencyMaxLatency: 1.5,
+          enableStashBuffer: false,
+          stashInitialSize: 128,
+          enableWorker: true,
+          lazyLoadMaxDuration: 3 * 60
+        }
+      };
     },
-  },
-  
-  data() {
-    return {
-      player: null,
-      config: {
-        liveBufferLatencyChasing: true,
-        liveBufferLatencyChasingOnPaused: true,
-        liveBufferLatencyMaxLatency: 1,
-        enableWorker: true,
-      }
-    };
-  },
-  
-  mounted() {
-    this.startPlay();
-  },
-  
-  beforeDestroy() {
-    this.stopPlay();
-  },
-  
-  methods: {
-    startPlay() {
-      if (mpegts.getFeatureList().mseLivePlayback) {
-        let videoElement = document.getElementById('video' + this.index);
-        this.player = mpegts.createPlayer(
-          {
-            type: 'flv',
-            isLive: true,
-            hasAudio: false,
-            url: this.videoUrl,
-          },
-          this.config
-        );
-        this.player.attachMediaElement(videoElement);
-        console.log(this.player,'this.player')
-        this.player.load();
-        setTimeout(()=>{
-            this.player.play();
-        },1000)
-      } else {
-        console.log('mpegts.js 不支持');
+    watch: {
+      videoUrl: {
+        handler(newUrl, oldUrl) {
+          if (newUrl && newUrl !== oldUrl) {
+            this.reconnectPlayer();
+          }
+        },
+        immediate: false
       }
     },
-    
-    stopPlay() {
-      if (this.player) {
-        this.player.destroy();
-        this.player = null;
+    mounted() {
+      window.addEventListener('beforeunload', this.handleBeforeUnload);
+      this.startPlay();
+    },
+    beforeDestroy() {
+      this.isPageUnloading = true;
+      this.isManualDestroy = true;
+      window.removeEventListener('beforeunload', this.handleBeforeUnload);
+      this.cleanReconnectTimer();
+      this.destroyPlayer();
+    },
+    methods: {
+      handleBeforeUnload() {
+        // 页面刷新/关闭时立即标记,阻止任何后续异步操作
+        this.isPageUnloading = true;
+        this.isManualDestroy = true;
+        this.cleanReconnectTimer();
+        this.destroyPlayer();
+      },
+
+      startPlay() {
+        if (this.isPageUnloading || this.isManualDestroy) return;
+        if (!mpegts.getFeatureList().mseLivePlayback) {
+          console.warn('当前浏览器不支持 mpegts.js 直播回放');
+          return;
+        }
+        const videoElement = document.getElementById('video' + this.index);
+        if (!videoElement) {
+          console.warn('video 元素未找到,延迟重试');
+          this.scheduleReconnect();
+          return;
+        }
+
+        try {
+          this.player = mpegts.createPlayer(
+            {
+              type: 'flv',
+              isLive: true,
+              hasAudio: false,
+              url: this.videoUrl,
+              cors: true
+            },
+            this.config
+          );
+
+          this.player.attachMediaElement(videoElement);
+          this.player.load();
+
+          // 媒体信息就绪后,延迟播放
+          this.player.on(mpegts.Events.MEDIA_INFO, () => {
+            if (this.isPageUnloading || this.isManualDestroy) return;
+            if (this.hasStarted) return;
+            this.hasStarted = true;
+            this.$emit('setLoading', true);
+
+            setTimeout(() => {
+              this.$emit('setLoading', false);
+
+              if (this.isPageUnloading || this.isManualDestroy || !this.player)
+                return;
+              this.player.play().catch((err) => {
+                if (err.name !== 'NotAllowedError') {
+                  this.scheduleReconnect();
+                }
+              });
+            }, this.playDelaySeconds * 1000);
+          });
+
+          // 兜底:如果 MEDIA_INFO 未触发,LOADING_COMPLETE 时也尝试延迟播放
+          this.player.on(mpegts.Events.LOADING_COMPLETE, () => {
+            if (this.isPageUnloading || this.isManualDestroy) return;
+            if (!this.hasStarted) {
+              console.warn('未收到 MEDIA_INFO,但仍尝试延迟播放');
+              this.hasStarted = true;
+              setTimeout(() => {
+                if (
+                  this.isPageUnloading ||
+                  this.isManualDestroy ||
+                  !this.player
+                )
+                  return;
+                this.player.play().catch((e) => console.warn);
+              }, this.playDelaySeconds * 1000);
+            }
+          });
+
+          // 错误处理
+          this.player.on(mpegts.Events.ERROR, (errorType, errorDetail) => {
+            if (this.isPageUnloading || this.isManualDestroy) return;
+            console.error('mpegts 错误:', errorType, errorDetail);
+            if (errorType === mpegts.ErrorTypes.NETWORK_ERROR) {
+              this.scheduleReconnect();
+            } else if (
+              errorType === mpegts.ErrorTypes.MEDIA_ERROR &&
+              this.reconnectAttempts === 0
+            ) {
+              this.scheduleReconnect(true);
+            }
+          });
+
+          // 缓冲统计,重置重连计数
+          this.player.on(mpegts.Events.STATISTICS_INFO, (stats) => {
+            if (this.isPageUnloading || this.isManualDestroy) return;
+            if (stats && stats.bufferLength > 0.5) {
+              this.reconnectAttempts = 0;
+            }
+          });
+        } catch (err) {
+          console.error('创建播放器异常:', err);
+          this.scheduleReconnect();
+        }
+      },
+
+      destroyPlayer() {
+        if (this.player) {
+          try {
+            this.player.pause();
+            this.player.unload();
+            this.player.detachMediaElement();
+            this.player.destroy();
+          } catch (e) {
+            // 刷新时可能已经销毁,静默处理
+          }
+          this.player = null;
+        }
+        // 手动清理 video 元素,彻底释放资源
+        const videoElement = document.getElementById('video' + this.index);
+        if (videoElement) {
+          videoElement.pause();
+          videoElement.src = '';
+          videoElement.load();
+          if (videoElement.srcObject) {
+            videoElement.srcObject = null;
+          }
+        }
+        this.hasStarted = false;
+      },
+
+      scheduleReconnect(onlyOnce = false) {
+        if (this.isPageUnloading || this.isManualDestroy) return;
+        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
+          console.error('已达最大重连次数,停止尝试');
+          return;
+        }
+        if (onlyOnce && this.reconnectAttempts > 0) return;
+
+        this.cleanReconnectTimer();
+        const delay = Math.min(
+          1000 * Math.pow(1.5, this.reconnectAttempts),
+          10000
+        );
+        console.log(
+          `${delay}ms 后尝试重连... (第${this.reconnectAttempts + 1}次)`
+        );
+        this.reconnectTimer = setTimeout(() => {
+          this.reconnectPlayer();
+        }, delay);
+      },
+
+      reconnectPlayer() {
+        if (this.isPageUnloading || this.isManualDestroy) return;
+        this.reconnectAttempts++;
+        console.log(`重连播放器,第${this.reconnectAttempts}次尝试`);
+        this.destroyPlayer();
+        this.startPlay();
+      },
+
+      cleanReconnectTimer() {
+        if (this.reconnectTimer) {
+          clearTimeout(this.reconnectTimer);
+          this.reconnectTimer = null;
+        }
       }
     }
-  }
-};
+  };
 </script>
 
 <style scoped lang="scss">
-.video {
-  height: 100%;
-  width: 100%;
-}
+  .video {
+    height: 100%;
+    width: 100%;
+    background-color: #333;
+  }
 </style>

+ 1 - 0
src/views/monitor/components/monitorList.vue

@@ -5,6 +5,7 @@
       :data="data"
       :props="defaultProps"
       @node-click="handleNodeClick"
+      highlight-current
       default-expand-all
     />
   </div>

+ 21 - 4
src/views/monitor/index.vue

@@ -23,7 +23,7 @@
             v-for="(page, pageIndex) in pageNum"
             :key="pageIndex"
           >
-            <div class="page">
+            <div class="page" v-loading="loading">
               <div
                 :style="itemStyle"
                 v-for="(video, videoIndex) in itemNum(pageIndex)"
@@ -47,11 +47,23 @@
                   }"
                   class="box-border videoItem"
                 >
-                  <myVideo
+                  <!-- <myVideo
                     :video-url="
                       videoList[pageIndex * layout * layout + videoIndex]['url']
                     "
                     :index="pageIndex * layout * layout + videoIndex"
+                  /> -->
+                  <myVideo
+                    :autoplay="true"
+                    :muted="true"
+                    :width="960"
+                    :height="540"
+                    :isLive="true"
+                    show-reload-btn
+                    @setLoading="setLoading"
+                    :video-url="
+                      videoList[pageIndex * layout * layout + videoIndex]['url']
+                    "
                   />
                   <div class="mask1">
                     <img
@@ -336,7 +348,7 @@
   import myVideo from '../components/video.vue';
   import * as realTime from '@/api/isp/ispRealtime/monitor/index';
   import * as deviceApi from '@/api/isp/deviceManage/robot';
-
+  // import FLVPlayer from "./FLVPlayer.vue";
   export default {
     name: 'MonitorPage',
     components: {
@@ -348,6 +360,7 @@
     data() {
       return {
         layout: 1,
+        loading: false,
         videoList: [],
         carouselIndex: 0,
         isShow: false,
@@ -402,6 +415,9 @@
     },
     mounted() {},
     methods: {
+      setLoading(loading) {
+        this.loading = loading;
+      },
       itemNum(pageIndex) {
         if (this.pageNum != 1 && pageIndex == this.pageNum - 1) {
           return this.count - this.layout * this.layout * (this.pageNum - 1);
@@ -567,7 +583,7 @@
           this.codeList.push(item.deviceCode);
         });
 
-        this.getClickId(list[0]);
+        // this.getClickId(list[0]);
       },
       async getClickId(device) {
         this.camera = device;
@@ -591,6 +607,7 @@
         await realTime.delStreamProxy(this.deviceCode);
         const res = await realTime.getCameraUrl([this.deviceCode]);
         this.videoList = res;
+        console.log(this.videoList);
       }
     }
   };