You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue中WebSocket流页面刷新异常与SourceBuffer错误处理

WebSocket视频流Electron应用问题修复方案

问题1:页面刷新后无流传输仍接收Socket缓冲区数组

原因

页面刷新后,前端旧WebSocket连接断开,但后端未收到停止流的通知,仍持续推送视频帧;同时前端刷新后streaming_client_id、media_source被清空,但新Socket连接仍会接收后端推送的帧数据,导致无效处理。

修复方案

  • 页面卸载前主动通知后端停止流:在组件卸载或页面刷新前,向WebSocket发送停止指令,让后端终止推送。
  • 接收视频帧时增加双重校验:同时检查media_source和streaming_client_id是否有效,避免无效数据处理。
  • 清理Socket监听:组件卸载时移除所有Socket事件监听,防止内存泄漏。

代码修改示例

// 组件mounted钩子中添加页面刷新监听
mounted() {
  window.addEventListener('beforeunload', () => {
    if (this.socket && this.streaming_client_id) {
      this.socket.emit("stop-streaming", { id: this.streaming_client_id });
    }
  });
}

// 组件beforeUnmount钩子中清理资源
beforeUnmount() {
  if (this.socket && this.streaming_client_id) {
    this.socket.emit("stop-streaming", { id: this.streaming_client_id });
  }
  // 移除所有Socket监听
  this.socket.off("start-streaming");
  this.socket.off("video-frame");
  this.socket.off("stop-streaming");
  window.removeEventListener('beforeunload', this.handleBeforeUnload);
}

// 修改video-frame监听逻辑
this.socket.on('video-frame', (data) => {
  // 增加streaming_client_id有效性校验
  if(!this.media_source || !this.streaming_client_id) return;
  
  // ...原有统计计算逻辑
  
  const arrayU8 = new Uint8Array(data);
  
  // 检查MediaSource和SourceBuffer状态
  if (this.media_source.readyState === 'open' && this.media_source.sourceBuffers.length > 0) {
    try {
      this.media_source.sourceBuffers[0].appendBuffer(arrayU8);
    } catch (e) {
      console.error('Append buffer failed:', e);
    }
  } else {
    console.log('Media source or SourceBuffer not ready');
  }
});

问题2:打开模态框时MediaSource/SourceBuffer报错

原因

  • 模态框关闭时未完整清理MediaSource资源,导致旧SourceBuffer已被移除但仍被引用;
  • 重复创建MediaSource或重复设置video.src,引发MediaSource状态异常;
  • 模态框打开时,sourceopen事件未触发完成就尝试操作SourceBuffer。

修复方案

  • 模态框关闭时完整清理资源:终止流、移除SourceBuffer、释放URL对象、重置相关引用;
  • 保存SourceBuffer独立引用,避免直接通过sourceBuffers[0]访问,操作前检查有效性;
  • 等待sourceopen事件触发后再处理视频帧,防止提前操作;
  • 初始化MediaSource前先清理旧的无效资源。

代码修改示例

// 新增模态框关闭时的清理方法
closeStreamModal() {
  this.is_stream_modal_open = false;
  if (this.remote_video) {
    this.remote_video.pause();
    URL.revokeObjectURL(this.remote_video.src); // 释放URL对象
    this.remote_video.src = '';
  }
  if (this.media_source) {
    try {
      if (this.media_source.readyState === 'open') {
        this.media_source.endOfStream();
      }
      // 移除所有SourceBuffer
      while (this.media_source.sourceBuffers.length > 0) {
        this.media_source.removeSourceBuffer(this.media_source.sourceBuffers[0]);
      }
    } catch (e) {
      console.error('Cleanup MediaSource failed:', e);
    }
    this.media_source = null;
    this.sourceBuffer = null; // 新增SourceBuffer独立引用
  }
}

// 修改initMediaSource方法
initMediaSource() {
  // 先清理旧的无效资源
  if (this.media_source) {
    try {
      if (this.media_source.readyState === 'open') {
        this.media_source.endOfStream();
      }
      while (this.media_source.sourceBuffers.length > 0) {
        this.media_source.removeSourceBuffer(this.media_source.sourceBuffers[0]);
      }
    } catch (e) {
      console.error('Cleanup old MediaSource failed:', e);
    }
    this.media_source = null;
    this.sourceBuffer = null;
  }

  this.media_source = new MediaSource();
  this.media_source.addEventListener('sourceopen', () => {
    console.log('MediaSource sourceopen');
    try {
      this.sourceBuffer = this.media_source.addSourceBuffer('video/webm; codecs="vp8"');
      console.log('SourceBuffer created', this.sourceBuffer);

      this.sourceBuffer.addEventListener('updateend', () => {
        console.log('SourceBuffer updateend');
        if (this.remote_video && this.remote_video.paused) {
          this.remote_video.play().catch(e => console.error('Play failed:', e));
        }
      });

      this.sourceBuffer.addEventListener('error', (event) => {
        console.error('SourceBuffer error:', event);
      });

      // 监听SourceBuffer被移除事件
      this.sourceBuffer.addEventListener('abort', () => {
        console.log('SourceBuffer aborted');
        this.sourceBuffer = null;
      });
    } catch (e) {
      console.error('Create SourceBuffer failed:', e);
    }
  });

  this.media_source.addEventListener('sourceclose', () => {
    console.log('MediaSource sourceclose');
    this.sourceBuffer = null;
    this.media_source = null;
  });

  if(this.remote_video) {
    URL.revokeObjectURL(this.remote_video.src); // 先释放旧URL
    this.remote_video.src = URL.createObjectURL(this.media_source);
  }
}

// 修改video-frame中的appendBuffer逻辑
this.socket.on('video-frame', (data) => {
  if(!this.media_source || !this.streaming_client_id || !this.sourceBuffer) return;
  
  // ...统计计算逻辑
  
  const arrayU8 = new Uint8Array(data);
  
  // 检查SourceBuffer是否正在更新
  if (this.media_source.readyState === 'open' && !this.sourceBuffer.updating) {
    try {
      this.sourceBuffer.appendBuffer(arrayU8);
    } catch (e) {
      console.error('Append buffer failed:', e);
      // 若报错为SourceBuffer已移除,重置引用
      if (e.message.includes('removed from the parent media source')) {
        this.sourceBuffer = null;
      }
    }
  }
});

内容的提问来源于stack exchange,提问作者Himanshu Rahi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 07:20:28