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

解决getUserMedia播放中视频切换deviceId后源未变更问题

切换正在播放的媒体流视频源问题

用例

通过下拉菜单切换可用视频源,保持视频处于播放状态。

需求

需要切换由navigator.mediaDevices.getUserMedia启动的正在播放的视频流源,尝试过关闭现有轨道、将srcObject设为null,再传入新的deviceId重新启动流,但未成功。

问题描述

切换下拉菜单后,视频元素关联的deviceId显示已更新,但实际视频源并未改变。


现有代码

JavaScript代码

$(document).ready(function() {
  var videoDeviceList = [];
  var selectedDeviceId = "";
  if (!navigator.mediaDevices.enumerateDevices) {
    console.log("enumerateDevices() not supported.");
  } else {
    // 枚举摄像头和麦克风设备
    navigator.mediaDevices.enumerateDevices()
      .then((devices) => {
        devices.forEach((device) => {
          if (device.kind === 'videoinput') {
            selectedDeviceId = selectedDeviceId === "" ? device.deviceId : selectedDeviceId;
            videoDeviceList.push({
              deviceId: device.deviceId,
              deviceLabel: device.label
            });
          }
        });
        videoDeviceList.forEach((item) => {
          $("#videoList").append("<option id='" + item.deviceId + "'>" + item.deviceLabel + "</option>");
        });

      })
      .catch((err) => {
        console.error(`${err.name}: ${err.message}`);
      });

  }
  $("#btn").click(function() {
    $(this).attr("disabled", true);
    startOrUpdateCameraFeed();
  });
  $('#videoList').change(function() {
    selectedDeviceId = $(this).find('option:selected').attr('id');
    console.log("selectedDeviceId: ", selectedDeviceId);
    startOrUpdateCameraFeed();
  });

  function startOrUpdateCameraFeed() {
    const video = document.getElementById('video');
    // 关闭所有正在运行的视频流
    if (window.videoStream !== null && window.videoStream !== undefined && window.videoStream.length > 0 &&
      video) {
      window.videoStream.forEach((stream) => {
        if (stream) {
          if ('getTracks' in stream && stream.getTracks().length > 0) {
            stream.getTracks().forEach((track) => stopTrack(track));
          }
          if ('getVideoTracks' in stream && stream.getVideoTracks().length > 0) {
            stream.getVideoTracks().forEach((track) => stopTrack(track));
          }
          if ('getAudioTracks' in stream && stream.getAudioTracks().length > 0) {
            stream.getAudioTracks().forEach((track) => stopTrack(track));
          }
        }
      });
      window.videoStream = [];
      video.srcObject = null;
    }
    // 使用新的摄像头设备ID启动流
    var constraints = {
      deviceId: {
        exact: selectedDeviceId
      }
    };
    console.log("constraints: ", constraints);
    navigator.mediaDevices
      .getUserMedia({
        video: true,
        video: {
          width: {
            ideal: 4096,
            max: 4096
          },
          height: {
            ideal: 2160,
            max: 2160
          },
          deviceId: constraints,
        }
      }).then(function(selfStream) {
        if (window.videoStream === null || window.videoStream === undefined) {
          window.videoStream = [];
        }
        if (video) {
          console.log('called video: ', window.videoStream);
          video.srcObject = selfStream;
          window.videoStream.push(selfStream);
        }
      })
      .catch(function(err0r) {
        console.log('Something went wrong regular before video!', err0r);
      });
  }
  function stopTrack (track){
    if (track && 'stop' in track) {
      track.stop();
    }
  };
});

HTML & CSS代码

<b>请授予网站访问摄像头列表的权限。</b><br/><br/>
切换摄像头源:
<select id="videoList">
  <option disabled>选择摄像头</option>
</select>
<div id="videoContainer">
  <video id="video" height="100%" width="100%" autoplay></video>
</div>

<button id="btn">
  启动初始视频流
</button>

<style>
video {
  height: 100%;
  width:100%;
  border: 1px solid red;
}
#videoContainer {
  height: 150px;
  width: 250px;
}
</style>

问题原因及修复方案

核心问题

调用getUserMedia时存在两处错误:

  1. 重复定义video参数:对象中重复的键会被后面的覆盖,但属于冗余写法;
  2. deviceId参数结构错误:你把constraints对象({deviceId: {exact: selectedDeviceId}})直接赋值给了video配置里的deviceId,导致浏览器无法正确识别目标设备。

修复后的核心代码

修改startOrUpdateCameraFeed函数,简化逻辑并修正参数结构:

function startOrUpdateCameraFeed() {
  const video = document.getElementById('video');
  // 关闭当前流的所有轨道
  if (window.videoStream) {
    window.videoStream.getTracks().forEach(track => track.stop());
    window.videoStream = null;
    video.srcObject = null;
  }
  
  // 正确构造视频约束
  const videoConstraints = {
    width: {
      ideal: 4096,
      max: 4096
    },
    height: {
      ideal: 2160,
      max: 2160
    },
    deviceId: { exact: selectedDeviceId }
  };

  navigator.mediaDevices.getUserMedia({ video: videoConstraints })
    .then(function(selfStream) {
      window.videoStream = selfStream;
      video.srcObject = selfStream;
    })
    .catch(function(error) {
      console.log('启动视频流出错:', error);
    });
}

其他优化点

  1. 简化轨道停止逻辑:直接通过stream.getTracks()获取所有轨道并停止,无需重复检查不同类型的轨道;
  2. 移除冗余数组:每次切换都会替换为新流,无需用数组维护多个流实例,单个变量存储当前流即可;
  3. 修正拼写错误:将err0r改为error,提升代码可读性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 21:25:37