Prechádzať zdrojové kódy

hls 播放加载出时间却无法播放问题修复

dedsudiyu 1 týždeň pred
rodič
commit
de3cb5d5f9

+ 73 - 24
src/views/safetyEducationExaminationNew/components/videoComponent.vue

@@ -1,9 +1,8 @@
 <template>
   <div class="video-component">
-    <!-- 注意:移除了 :src="url",因为 src 将由 JS 动态绑定 -->
     <video
       ref="videoRef"
-      autoplay controls muted
+      controls
       preload="metadata"
       class="video-player"
       @timeupdate="onTimeUpdate"
@@ -11,6 +10,7 @@
       @pause="saveProgress"
       @ended="onEnded"
     ></video>
+    <div v-if="videoError" class="video-error-text">视频加载失败,请检查网络或联系管理员</div>
   </div>
 </template>
 
@@ -34,7 +34,10 @@
     data() {
       return {
         _saveTimer: null,
-        hls: null, // 新增:存储 hls 实例
+        _stallTimer: null,
+        _lastTime: 0,
+        hls: null,
+        videoError: false,
       };
     },
     computed: {
@@ -43,57 +46,98 @@
         return `${STORAGE_KEY_PREFIX}${this.userId}_${safeUrl}`;
       },
     },
+    watch: {
+      url(newVal) {
+        if (newVal) {
+          this._stopStallDetector();
+          this.$nextTick(() => {
+            this.initPlayer();
+          });
+        }
+      },
+    },
     mounted() {
       this.initPlayer();
     },
     methods: {
-      // 初始化播放器
       initPlayer() {
         const video = this.$refs.videoRef;
         if (!video) return;
 
-        // 1. 优先检测浏览器是否原生支持 HLS (如 Safari, iOS)
+        if (this.hls) {
+          this.hls.destroy();
+          this.hls = null;
+        }
+
+        this.$set(this, 'videoError', false);
+
+        // iOS / Safari 原生支持 HLS
         if (video.canPlayType('application/vnd.apple.mpegurl')) {
           video.src = this.url;
         }
-        // 2. 检测是否支持 hls.js (如 Firefox, Chrome)
+        // Chrome / Firefox,用 hls.js 解析
         else if (Hls.isSupported()) {
-          this.hls = new Hls();
+          this.hls = new Hls({
+            maxBufferLength: 60,
+            maxMaxBufferLength: 120,
+            maxBufferSize: 60 * 1000 * 1000,
+            lowLatencyMode: false,
+            enableWorker: true,
+          });
           this.hls.loadSource(this.url);
           this.hls.attachMedia(video);
 
-          // 监听 HLS 解析完成事件
           this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
-            console.log('HLS 视频源解析完成,准备播放');
+            video.play().catch(() => {});
+            this._startStallDetector(video);
           });
 
-          // 监听错误事件
           this.hls.on(Hls.Events.ERROR, (event, data) => {
             if (data.fatal) {
               switch (data.type) {
                 case Hls.ErrorTypes.NETWORK_ERROR:
-                  console.error('网络错误,尝试恢复...');
                   this.hls.startLoad();
                   break;
                 case Hls.ErrorTypes.MEDIA_ERROR:
-                  console.error('媒体解码错误,尝试恢复...');
                   this.hls.recoverMediaError();
                   break;
                 default:
-                  console.error('发生不可恢复的致命错误');
                   this.hls.destroy();
+                  this.hls = null;
+                  this.$set(this, 'videoError', true);
                   break;
               }
             }
           });
         }
-        // 3. 都不支持
+        // X5 / 老旧 WebView 降级
         else {
-          console.error('当前浏览器不支持 HLS 视频播放');
+          video.src = this.url;
+        }
+      },
+
+      // 卡死检测:有缓冲但 currentTime 不动时,跳过 0.1 秒解除卡死
+      _startStallDetector(video) {
+        this._stopStallDetector();
+        this._lastTime = video.currentTime;
+        this._stallTimer = setInterval(() => {
+          if (video.paused || video.ended) return;
+          const bufferedEnd = video.buffered.length ? video.buffered.end(video.buffered.length - 1) : 0;
+          const hasBuffer = bufferedEnd - video.currentTime > 1;
+          if (hasBuffer && video.currentTime === this._lastTime) {
+            video.currentTime += 0.1;
+          }
+          this._lastTime = video.currentTime;
+        }, 1000);
+      },
+
+      _stopStallDetector() {
+        if (this._stallTimer) {
+          clearInterval(this._stallTimer);
+          this._stallTimer = null;
         }
       },
 
-      // 读取上次保存的时间点
       getSavedTime() {
         try {
           const val = localStorage.getItem(this.storageKey);
@@ -103,7 +147,6 @@
         }
       },
 
-      // 视频元数据加载完成后跳转到上次位置
       onLoadedMetadata() {
         const saved = this.getSavedTime();
         const video = this.$refs.videoRef;
@@ -112,7 +155,6 @@
         }
       },
 
-      // timeupdate 节流处理
       onTimeUpdate() {
         if (this._saveTimer) return;
         this._saveTimer = setTimeout(() => {
@@ -121,7 +163,6 @@
         }, 5000);
       },
 
-      // 实际写入 localStorage
       saveProgress() {
         const video = this.$refs.videoRef;
         if (!video) return;
@@ -132,8 +173,8 @@
         }
       },
 
-      // 播放结束时清除记录
       onEnded() {
+        this._stopStallDetector();
         try {
           localStorage.removeItem(this.storageKey);
         } catch {
@@ -142,15 +183,12 @@
       },
     },
     beforeDestroy() {
-      // 1. 清除保存进度的定时器
+      this._stopStallDetector();
       if (this._saveTimer) {
         clearTimeout(this._saveTimer);
         this._saveTimer = null;
       }
-      // 2. 销毁前立即保存一次进度
       this.saveProgress();
-
-      // 3. 【关键】销毁 hls 实例,释放内存并断开网络连接
       if (this.hls) {
         this.hls.destroy();
         this.hls = null;
@@ -162,6 +200,7 @@
 <style scoped lang="scss">
   .video-component {
     width: 100%;
+    position: relative;
 
     .video-player {
       width: 1458px;
@@ -170,5 +209,15 @@
       background: #000;
       border-radius: 6px;
     }
+
+    .video-error-text {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      color: #ccc;
+      font-size: 14px;
+      text-align: center;
+    }
   }
 </style>