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

Android端Chrome用Twilio Video切换前后置摄像头时视频卡顿求助

解决方案:Android Chrome下Twilio Video摄像头切换修复

核心问题分析

你的代码逻辑顺序有误:先创建新视频轨道,再停止旧轨道。Android Chrome对摄像头资源的独占性更强,旧轨道未完全释放时,新轨道无法正常获取摄像头权限,导致卡顿/黑屏;而最小化浏览器会触发系统释放未使用的摄像头资源,所以再次打开后恢复正常。

修复步骤

  1. 调整操作顺序:先停止并取消发布旧轨道,彻底释放资源后,再创建新轨道
  2. 彻底清理DOM与轨道关联:使用track.detach()移除旧轨道绑定的所有视频元素,避免残留资源
  3. 添加点击防抖:防止连续点击触发多次资源请求冲突
  4. 清理原MediaStream中的旧轨道:避免原stream持有无效轨道导致资源泄漏

修改后的代码片段

Plugin.prototype.checkDevices = function () {
    var that = this;
    var constraints = {
        audio: true,
        video: true
    };
    // 添加防抖标记,防止连续点击触发冲突
    var isSwitching = false;

    navigator.mediaDevices.getUserMedia(constraints).then(function (stream) {
        that.userStream = stream;
        var nstream = null;
        if (that.options.auto_recording === 4) {
            const tracks = stream.getAudioTracks();
            nstream = new MediaStream();
            nstream.addTrack(tracks[0]);
        } else {
            nstream = stream;
        }
        if (!that.setUpRTCRecorder('own', that.getVideoFrom(false), false, nstream, false)) {
            that.setUpStatRecording();
        }
        navigator.mediaDevices.enumerateDevices().then(function (mediaDevices) {
            var count = 1;
            const select = document.getElementById('video-switch-select');
            that.cameraSelected = 0;
            that.cameras = []; // 重置数组,避免重复添加设备
            mediaDevices.forEach(mediaDevice => {
                if (mediaDevice.kind === 'videoinput') {
                    count++;
                    that.cameras.push({
                        id: mediaDevice.deviceId,
                        label: mediaDevice.label || `Camara ${count}`
                    });
                }
            });
            if (that.cameras.length > 1) {
                // 先解绑原有点击事件,避免重复绑定
                $(".video-switch-button").off('click').on('click', function () {
                    if (isSwitching) return;
                    isSwitching = true;

                    that.cameraSelected++;
                    if (that.cameraSelected >= that.cameras.length) {
                        that.cameraSelected = 0;
                    }
                    console.log("Switching to camera", that.cameraSelected);
                    var toswitch = that.cameras[that.cameraSelected];

                    // 第一步:处理旧轨道,彻底释放资源
                    const publications = Array.from(that.activeRoom.localParticipant.videoTracks.values());
                    publications.forEach(publication => {
                        const track = publication.track;
                        console.log("Unpublishing and stopping track", track);
                        // 取消发布轨道
                        that.activeRoom.localParticipant.unpublishTrack(track);
                        // 从原MediaStream移除轨道
                        if (that.userStream) {
                            that.userStream.removeTrack(track.mediaStreamTrack);
                        }
                        // 解绑并移除所有关联的DOM元素
                        track.detach().forEach(el => el.remove());
                        // 停止轨道
                        track.stop();
                    });

                    // 第二步:清理本地视频容器
                    $('#local-media').empty();

                    // 第三步:创建并发布新轨道
                    Twilio.Video.createLocalVideoTrack({
                        deviceId: {exact: toswitch.id}
                    }).then(function (localVideoTrack) {
                        console.log("Adding track", localVideoTrack);
                        // 将新轨道添加到原MediaStream(如果需要)
                        if (that.userStream) {
                            that.userStream.addTrack(localVideoTrack.mediaStreamTrack);
                        }
                        // 发布新轨道
                        that.activeRoom.localParticipant.publishTrack(localVideoTrack);
                        // 添加到DOM并应用样式
                        const videoEl = localVideoTrack.attach();
                        document.getElementById('local-media').appendChild(videoEl);
                        if (that.chatOn) {
                            $(videoEl).addClass('local-media-oncall-video-chat');
                            $('.user-placeholder').addClass('placeholder-oncall-video-chat');
                        } else {
                            $(videoEl).addClass('local-media-oncall-video');
                            $('.user-placeholder').addClass('placeholder-oncall-video');
                        }
                        isSwitching = false; // 重置防抖标记
                    }).catch(error => {
                        console.error("Error al crear la nueva pista de video:", error);
                        alert("Error al cambiar de cámara: " + error.message);
                        isSwitching = false; // 重置防抖标记
                    });
                });
            }
        });
        that.initCB();
    }).catch(function (error) {
        alert('getUserMedia() error: ', error.message);
    });
};

关键修改说明

  • 操作顺序反转:先完成旧轨道的停止、取消发布、DOM解绑流程,确保摄像头资源完全释放后再创建新轨道,解决Android Chrome的资源独占冲突
  • 防抖机制:isSwitching标记防止短时间内多次触发切换,避免资源竞争导致的异常
  • 彻底资源清理:使用track.detach()移除所有关联的视频元素,并从原MediaStream中移除旧轨道,避免资源泄漏
  • 设备数组重置:每次枚举设备时清空that.cameras,避免重复添加同一设备

内容的提问来源于stack exchange,提问作者Iván León Capote

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 14:51:04