Android端Chrome用Twilio Video切换前后置摄像头时视频卡顿求助
解决方案:Android Chrome下Twilio Video摄像头切换修复
核心问题分析
你的代码逻辑顺序有误:先创建新视频轨道,再停止旧轨道。Android Chrome对摄像头资源的独占性更强,旧轨道未完全释放时,新轨道无法正常获取摄像头权限,导致卡顿/黑屏;而最小化浏览器会触发系统释放未使用的摄像头资源,所以再次打开后恢复正常。
修复步骤
- 调整操作顺序:先停止并取消发布旧轨道,彻底释放资源后,再创建新轨道
- 彻底清理DOM与轨道关联:使用
track.detach()移除旧轨道绑定的所有视频元素,避免残留资源 - 添加点击防抖:防止连续点击触发多次资源请求冲突
- 清理原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
相关产品推荐
相关产品推荐

