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

Android DOMException: 无法启动视频源(无活动流)问题求助

Angular切换摄像头时getUserMedia抛出DOMException: Could not start video source的解决方法

针对你遇到的指定deviceId调用getUserMedia时出现DOMException: Could not start video source的问题,结合你的代码情况,给出以下可行的修复方案:

1. 确保流和轨道完全停止后再启动新流

你的stopCurrentStream方法没有等待媒体轨道真正停止,只是同步调用了track.stop(),但硬件层面的资源释放可能存在延迟。修改该方法,等待所有轨道触发ended事件后再resolve:

private async stopCurrentStream(): Promise<void> {
  return new Promise<void>((resolve) => {
    this.video.nativeElement.pause();
    this.video.nativeElement.srcObject = null;
    this.video.nativeElement.src = null;

    if (!this.stream) {
      resolve();
      return;
    }

    const tracks = this.stream.getTracks();
    if (tracks.length === 0) {
      this.stream = null;
      resolve();
      return;
    }

    let completed = 0;
    const totalTracks = tracks.length;

    tracks.forEach((track: MediaStreamTrack) => {
      track.addEventListener('ended', () => {
        completed++;
        if (completed === totalTracks) {
          this.stream = null;
          resolve();
        }
      }, { once: true }); // 确保事件只触发一次
      track.stop();
      this.stream.removeTrack(track);
    });
  });
}

2. 严格控制停止与启动的时序

切换摄像头时,必须确保stopCurrentStream完全执行完成后,再启动新的视频流,必要时添加短暂延迟给硬件缓冲时间:

async switchCamera(targetDeviceId: string) {
  // 等待当前流完全停止
  await this.stopCurrentStream();
  // 添加100ms延迟,给摄像头硬件释放缓冲
  await new Promise(resolve => setTimeout(resolve, 100));
  // 启动目标摄像头
  await this.startVideo(targetDeviceId);
}

同时,把startVideo改成async/await写法,避免回调嵌套,也方便错误处理:

private async startVideo(deviceId: string = this.videoDevices[0]?.deviceId): Promise<void> {
  if (!deviceId) return;

  console.log('start vid', deviceId);
  try {
    const stream: MediaStream = await navigator.mediaDevices.getUserMedia({
      video: {
        deviceId: { exact: deviceId },
        // 可选:添加基础分辨率约束,避免设备兼容性问题
        width: { ideal: 1280 },
        height: { ideal: 720 }
      }
    });

    this.stream = stream;
    this.video.nativeElement.srcObject = stream;
    await this.video.nativeElement.play();
  } catch (err) {
    console.error('Error accessing camera:', err);
    // 可选:添加有限次数的重试逻辑,应对偶发的硬件占用问题
    let retryCount = 0;
    const retry = async () => {
      retryCount++;
      if (retryCount > 3) return;
      await new Promise(resolve => setTimeout(resolve, 200));
      await this.startVideo(deviceId);
    };
    await retry();
  }
}

3. 确保deviceId的有效性

每次切换摄像头前,重新获取最新的视频设备列表,避免使用过期或无效的deviceId:

async refreshVideoDevices() {
  const devices = await navigator.mediaDevices.enumerateDevices();
  this.videoDevices = devices.filter(device => device.kind === 'videoinput');
}

在初始化组件和触发摄像头切换前调用该方法,保证videoDevices中的deviceId是当前系统的有效设备ID。

关键说明

  • 移除deviceId能正常工作,是因为浏览器会自动选择可用的摄像头,而指定deviceId时,若目标摄像头资源未完全释放,就会抛出启动失败的异常。
  • 多次循环调用能成功,本质是偶然等到了硬件资源释放,通过上述方案可以主动确保资源释放完成,无需依赖随机重试。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 15:56:17