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

WEBRTC基于Socket切换输入设备后远端无法同步视频的实现咨询

问题根因

切换本地视频输入设备后仅更新了本地预览的媒体流,未同步替换RTCPeerConnection中的视频发送轨道,也未触发对应的SDP重协商流程,导致远端始终接收的是最初的旧视频流。

修复实现示例

1. Meeting类核心修改代码

class Meeting {
  constructor() {
    this.localStream = null;
    this.peerConnections = new Map(); // 存储所有远端的RTCPeerConnection实例,key为远端用户id
    this.socket = null; // Socket信令实例
    this.isNegotiating = new Map(); // 防止协商嵌套,key为远端用户id
  }

  // 原有初始化、建连、信令处理逻辑保留即可

  /**
   * 切换视频输入设备
   * @param {string} deviceId 目标摄像头设备id
   */
  async switchVideoDevice(deviceId) {
    try {
      // 获取新的视频流,音频可复用原有轨避免麦克风重启
      const newStream = await navigator.mediaDevices.getUserMedia({
        video: { deviceId: { exact: deviceId } },
        audio: false
      });
      // 复用旧流的音频轨
      this.localStream.getAudioTracks().forEach(track => {
        newStream.addTrack(track);
      });

      // 停止旧视频轨释放设备资源
      this.localStream.getVideoTracks().forEach(track => track.stop());

      // 更新本地预览
      document.getElementById('local-video').srcObject = newStream;
      
      const newVideoTrack = newStream.getVideoTracks()[0];
      // 遍历所有peerConnection替换发送轨
      for (const [userId, pc] of this.peerConnections.entries()) {
        const videoSender = pc.getSenders().find(s => s.track?.kind === 'video');
        if (videoSender) {
          // 直接替换轨道,大部分场景下无需重协商即可生效
          await videoSender.replaceTrack(newVideoTrack);
          // 兼容旧浏览器/自定义流属性场景,触发重协商
          this.triggerNegotiation(userId, pc);
        }
      }

      // 更新本地存储的流实例
      this.localStream = newStream;
    } catch (err) {
      console.error('切换视频设备失败:', err);
    }
  }

  /**
   * 触发SDP重协商
   * @param {string} userId 远端用户id
   * @param {RTCPeerConnection} pc 对应的peerConnection实例
   */
  async triggerNegotiation(userId, pc) {
    if (this.isNegotiating.get(userId)) return;
    this.isNegotiating.set(userId, true);
    try {
      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);
      // 通过Socket发送Offer到远端
      this.socket.emit('webrtc_offer', {
        targetUserId: userId,
        offer: pc.localDescription
      });
    } catch (err) {
      console.error('发起协商失败:', err);
    } finally {
      this.isNegotiating.set(userId, false);
    }
  }
}

2. 配套逻辑说明

  • 信令层不需要新增类型,对端收到webrtc_offer后执行原有建连时的setRemoteDescription、createAnswer、回发answer逻辑即可
  • 远端无需修改流渲染逻辑,WebRTC底层会自动同步新轨道数据,原有渲染video元素会自动更新画面
  • 若使用addTransceiver预先创建收发器的实现,替换轨道时对应对应transceiver的sender即可

内容的提问来源于stack exchange,提问作者Özgür Can Karagöz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.07 02:09:01