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

WebRTC切换摄像头后远端无法接收视频问题求助

摄像头切换后远端流未更新的问题解决

我正在实现摄像头开关切换功能,切换后本地PeerConnection的流显示已更新,但远端客户端的流没有变化,stream事件未触发,添加onaddstream事件也无效。相关代码如下:

Call Peer

const callPeer = (myPeer, peerId, myVideoStream, addToCalls = true) => {
    const call = myPeer.call(peerId, myVideoStream);
    if (addToCalls) {
        let existingCall = myPeer.calls.find((existingCall) => existingCall.peer == peerId);
        if (existingCall)
            myPeer.calls.splice(myPeer.calls.indexOf(existingCall), 1);
        myPeer.calls.push(call);
        call.on('stream', peerVideoStream => {
            let userVideoElement = $("video[data-peer='" + peerId + "']")[0];
            userVideoElement.srcObject = peerVideoStream;
        });
    }
}

Answer Call

const answerCall = (call, addToCalls = true) => {
    call.answer(myVideoStream);
    if (addToCalls) {
        let existingCall = myPeer.calls.find((existingCall) => existingCall.peer == call.peer);
        if (existingCall)
            myPeer.calls.splice(myPeer.calls.indexOf(existingCall), 1);
        myPeer.calls.push(call);
    }
    call.on('stream', userVideoStream => {
        let userVideoElement = $("video[data-peer='" + call.peer + "']")[0];
        userVideoElement.srcObject = userVideoStream;
    });
}

Toggle Camera

const toggleCamera = async (isOpened, peerId, isMuted) => {
    let oldStream = myVideoStream;
    myVideoStream = await navigator.mediaDevices.getUserMedia({
        video: !isOpened,
        audio: true
    });
    myPeer.calls.forEach((call) => {
        call.peerConnection.removeStream(oldStream);
        call.peerConnection.addStream(myVideoStream);
    });
    oldStream.getTracks().forEach((track) => {
        track.stop();
    });
    let myVideoElement = document.querySelector("video[data-peer='" + peerId + "']");
    myVideoElement.srcObject = myVideoStream;
    myVideoElement.muted = true;
    if (isOpened)
        $("#video").removeClass("bg-primary").attr("data-video", "closed");
    else
        $("#video").addClass("bg-primary").attr("data-video", "opened");
    ws.send(JSON.stringify({
        type: isOpened ? "video-off" : "video-on"
    }));
    if (isMuted)
        myVideoStream.getAudioTracks()[0].enabled = false;
};

解决方法

1. 改用轨道级别的状态切换(推荐)

不要重新获取整个媒体流,直接操作现有流的视频轨道启用/禁用状态,这是WebRTC官方推荐的方式,远端会自动同步轨道状态:

修改后的toggleCamera函数:

const toggleCamera = (isOpened, peerId, isMuted) => {
    // 直接操作现有流的视频轨道
    const videoTrack = myVideoStream.getVideoTracks()[0];
    videoTrack.enabled = !isOpened;

    // 更新本地视频显示(其实这一步可以省略,因为轨道状态变化会自动同步到video元素)
    let myVideoElement = document.querySelector("video[data-peer='" + peerId + "']");
    myVideoElement.srcObject = myVideoStream;

    // 更新UI状态
    if (isOpened)
        $("#video").removeClass("bg-primary").attr("data-video", "closed");
    else
        $("#video").addClass("bg-primary").attr("data-video", "opened");
    
    ws.send(JSON.stringify({
        type: isOpened ? "video-off" : "video-on"
    }));
    
    // 处理音频静音
    if (isMuted)
        myVideoStream.getAudioTracks()[0].enabled = false;
};

2. 远端监听轨道状态变化

远端不要只监听stream事件,改用track事件监听轨道的状态变化:

// 在callPeer的回调中替换原stream事件监听
call.on('track', (track) => {
    let userVideoElement = $("video[data-peer='" + peerId + "']")[0];
    // 确保视频元素有可用的MediaStream
    if (!userVideoElement.srcObject) {
        userVideoElement.srcObject = new MediaStream();
    }
    userVideoElement.srcObject.addTrack(track);

    // 监听轨道启用/禁用状态
    track.onenabled = () => {
        // 摄像头开启时的处理
    };
    track.ondisabled = () => {
        // 摄像头关闭时的处理
    };
});

// AnswerCall中同理替换stream事件为track事件

为什么原方法无效?

  • removeStream和addStream属于旧版WebRTC API,已被废弃,这类操作不会自动触发远端的媒体更新事件,需要手动发起会话协商(createOffer + setLocalDescription),流程复杂且易出错。
  • 直接操作轨道的enabled属性,WebRTC会自动同步状态到远端,无需额外的会话协商,效率更高且更稳定。

内容的提问来源于stack exchange,提问作者Sezer Can Kaynar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:11:05