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

Chrome Extension MV3:同时捕获摄像头与屏幕的实现问题

Chrome扩展:同时捕获屏幕与摄像头的解决方案

问题1:能否从content-script发起请求,让权限弹窗显示扩展名称?

不行。content-script运行在宿主页面的上下文环境中,所有权限请求都会被浏览器关联到当前宿主网站,因此弹窗只会显示网站名称而非扩展名称。必须从扩展的独立上下文发起权限请求,比如Background Service Worker、Popup页面或Offscreen文档,这些环境的请求才会关联到扩展本身,弹窗显示扩展名称。

问题2:如何在offscreen.js中捕获视频流并展示给用户?

Offscreen文档有独立的DOM环境,你之前获取元素为null,核心原因是代码执行时机早于DOM加载完成。具体实现步骤:

  1. 确保DOM加载完成后执行代码
    在DOMContentLoaded事件回调中处理元素查询和流操作,避免DOM未就绪导致的元素获取失败:

    document.addEventListener('DOMContentLoaded', () => {
      // 此处执行DOM操作和流处理逻辑
    });
    
  2. 捕获并渲染流到Offscreen的DOM
    Offscreen文档本身不可见,但可以在其内部创建video、canvas元素处理流:

    // offscreen.js 示例
    document.addEventListener('DOMContentLoaded', async () => {
      const videoCamera = document.createElement('video');
      videoCamera.autoplay = true;
      videoCamera.muted = true; // 避免回声
    
      // 接收Background传来的摄像头轨道
      chrome.runtime.onMessage.addListener(async (msg) => {
        if (msg.type === 'LOAD_CAMERA_TRACK') {
          const cameraStream = new MediaStream([msg.track]);
          videoCamera.srcObject = cameraStream;
        }
      });
    });
    
  3. 将画面展示给用户
    Offscreen文档不可直接显示,需通过消息传递将画面数据传递到可见页面(如Popup、宿主页面的悬浮层):

    • 方式一:用Offscreen的canvas捕获合成后的画面,将实时帧转成Base64发送给Popup/Content-script渲染
    • 方式二:将MediaStream的轨道通过消息传递到Popup,绑定到Popup的video元素(需注意Chrome扩展中跨上下文传递流的限制,建议传递MediaStreamTrack而非完整流)

最优实现方案(参考Loom思路)

Loom的核心逻辑是将权限请求、流处理、画面展示拆分到不同扩展上下文,避免阻塞和权限混淆:

  1. 权限请求统一由Background发起
    Background属于扩展独立上下文,权限弹窗会显示扩展名称。同时获取屏幕流和摄像头流:

    // background.js
    async function startCapture() {
      try {
        // 请求屏幕捕获
        const screenStream = await navigator.mediaDevices.getDisplayMedia({
          video: true,
          audio: true
        });
        // 请求摄像头捕获
        const cameraStream = await navigator.mediaDevices.getUserMedia({
          video: { width: 320, height: 240 },
          audio: false // 避免音频冲突,复用屏幕流的音频
        });
    
        // 创建Offscreen文档处理流合成
        await chrome.offscreen.createDocument({
          url: 'offscreen.html',
          reasons: ['MEDIA_PROCESSING'],
          justification: 'Combine screen and camera streams for recording'
        });
    
        // 向Offscreen发送流轨道
        chrome.runtime.sendMessage({
          type: 'INIT_STREAMS',
          screenTrack: screenStream.getVideoTracks()[0],
          cameraTrack: cameraStream.getVideoTracks()[0],
          audioTrack: screenStream.getAudioTracks()[0]
        });
      } catch (err) {
        console.error('Capture failed:', err);
      }
    }
    
  2. Offscreen文档负责流合成
    在Offscreen中用canvas将摄像头画面叠加到屏幕流上,实现画中画效果:

    // offscreen.js
    document.addEventListener('DOMContentLoaded', async () => {
      const videoScreen = document.createElement('video');
      const videoCamera = document.createElement('video');
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d');
    
      videoScreen.autoplay = true;
      videoScreen.muted = true;
      videoCamera.autoplay = true;
      videoCamera.muted = true;
    
      chrome.runtime.onMessage.addListener(async (msg) => {
        if (msg.type === 'INIT_STREAMS') {
          // 初始化屏幕和摄像头流
          videoScreen.srcObject = new MediaStream([msg.screenTrack]);
          videoCamera.srcObject = new MediaStream([msg.cameraTrack]);
    
          // 等待视频元数据加载完成
          await Promise.all([
            new Promise(resolve => videoScreen.onloadedmetadata = resolve),
            new Promise(resolve => videoCamera.onloadedmetadata = resolve)
          ]);
    
          // 设置canvas尺寸匹配屏幕流
          canvas.width = videoScreen.videoWidth;
          canvas.height = videoScreen.videoHeight;
    
          // 循环绘制合成画面
          function renderCombinedFrame() {
            // 绘制屏幕画面
            ctx.drawImage(videoScreen, 0, 0, canvas.width, canvas.height);
            // 绘制摄像头画面(右上角固定位置)
            const camSize = { width: 200, height: 150 };
            ctx.drawImage(
              videoCamera,
              canvas.width - camSize.width - 20,
              20,
              camSize.width,
              camSize.height
            );
            requestAnimationFrame(renderCombinedFrame);
          }
          renderCombinedFrame();
    
          // 创建合成后的流(包含音频)
          const combinedStream = canvas.captureStream();
          combinedStream.addTrack(msg.audioTrack);
    
          // 可将合成流用于录制,或传递给Popup预览
          // 示例:发送合成流轨道给Popup
          chrome.runtime.sendMessage({
            type: 'COMBINED_STREAM_READY',
            track: combinedStream.getVideoTracks()[0],
            audioTrack: combinedStream.getAudioTracks()[0]
          });
        }
      });
    });
    
  3. Popup/Content-script负责展示与交互

    • Popup中创建video元素,接收Offscreen传来的合成流轨道,实现实时预览
    • Content-script可在宿主页面插入悬浮层,单独展示摄像头画面(如果不需要叠加到屏幕流)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 07:36:02