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

Chrome MV3录屏扩展getUserMedia报NotAllowedError: Invalid state错误

Chrome Manifest V3 屏幕录制扩展 Invalid state 错误解决方案

预期实现目标

  • 扩展popup页放置录制启动按钮
  • 点击按钮后后台启动录制逻辑,不干扰用户当前浏览
  • 在用户当前可见的活动标签页弹出桌面/窗口选择器
  • 录制进程常驻后台,普通浏览页面刷新、关闭时录制不中断

原有实现代码

document.querySelector('#startFromBackgroundPage')
    .addEventListener('click', function(event) {
      chrome.runtime.sendMessage(
          { event: 'open-bg-page' }, function(response) { console.log(response); });
    });

Background 逻辑

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.event === 'open-bg-page') {
    chrome.tabs.create({
      url: 'index.html',
      selected: false,
    });
  }

  if (message.event === 'record') {
    chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
      chrome.desktopCapture.chooseDesktopMedia(
          ["screen", "window"],
          tabs[0],
          function(id, options) {
            chrome.runtime.sendMessage(
              {'mediaId': id, options: options});
          });
    });
  }
});

录制页逻辑(index.html 配套 app.js)

// 页面加载后发消息请求唤起录制选择器
setTimeout(() => {
  chrome.runtime.sendMessage(
    { event: 'record' }, function(response) { console.log(response); });
}, 500);

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  console.log('on message', message);
  if (message.mediaId) {
    setTimeout(() => {
      onAccessApproved(message.mediaId, message.options);
    }, 100);
  }
});
// 根据返回的mediaId调用getUserMedia启动录制流
function onAccessApproved(id, options) {
  if (!id) {
    console.log('Access rejected.');
    return;
  }

  var audioConstraint = {
      mandatory: {
        chromeMediaSource: 'desktop',
        chromeMediaSourceId: id
      }
  };

  if (!options.canRequestAudioTrack)
    audioConstraint = false;

  navigator.getUserMedia({
    audio: audioConstraint,
    video: {
      mandatory: {
        chromeMediaSource: 'desktop',
        chromeMediaSourceId: id,
        maxWidth:screen.width,
        maxHeight:screen.height} }
  }, gotStream, getUserMediaError);
}

function getUserMediaError(error) {
  document.querySelector('#message').innerHTML = error;
  console.log('navigator.getUserMedia() error: ', error);
}

问题表现

运行代码抛出错误:NotAllowedError: Invalid state。
如果将background.js中chooseDesktopMedia传入的tabs[0]参数替换为sender.tab,功能可正常运行,但此时桌面选择器会在后台隐藏的录制页弹出,不符合交互要求。

错误原因

  1. chrome.desktopCapture.chooseDesktopMedia 有严格的调用上下文限制:必须由用户手势(如点击按钮)直接触发调用链路,异步延迟超过手势有效期(通常为1秒)、或者调用链路和用户手势的触发页不匹配,都会抛出Invalid state错误。原有实现中,点击popup按钮后先打开后台tab,等后台tab加载完成再发消息查活动tab、调用选择器,此时用户手势上下文已经失效,且查询到的活动tab没有参与调用链路,权限校验直接失败。
  2. Manifest V3 已禁用常驻后台页,用隐藏普通tab承载录制逻辑不可靠,浏览器会在资源紧张时挂起后台非激活标签页,导致录制中断。

修复方案

  1. 替换隐藏普通tab方案,使用Chrome官方为MV3提供的Offscreen API创建离屏文档承载录制逻辑,离屏文档不会被浏览器随意回收,可保证录制进程不中断。
  2. 调整调用链路:点击popup按钮时,同步获取当前活动标签页ID,直接在用户手势有效期内调用chooseDesktopMedia,不要通过多层异步消息传递丢失手势上下文。
  3. 拿到mediaId后定向发送给离屏文档初始化录制流,避免全局消息广播导致的上下文错乱。

核心修正代码

manifest.json 新增权限配置

{
  "permissions": ["offscreen", "desktopCapture", "tabs", "activeTab"],
  "manifest_version": 3
}

popup 按钮点击逻辑修正

document.querySelector('#startFromBackgroundPage')
    .addEventListener('click', async function(event) {
      // 点击时同步获取当前活动tab,保留用户手势上下文
      const [activeTab] = await chrome.tabs.query({ currentWindow: true, active: true });
      // 先创建离屏文档(已存在则跳过)
      const existingContexts = await chrome.runtime.getContexts({
        contextTypes: ['OFFSCREEN_DOCUMENT']
      });
      if (existingContexts.length === 0) {
        await chrome.offscreen.createDocument({
          url: 'index.html',
          reasons: ['USER_MEDIA'],
          justification: '后台屏幕录制'
        });
      }
      // 直接在手势有效期内调用选择器,目标为当前活动tab
      chrome.desktopCapture.chooseDesktopMedia(
        ["screen", "window"],
        activeTab,
        (id, options) => {
          if (!id) return;
          // 定向将mediaId发给离屏文档启动录制
          chrome.runtime.sendMessage({
            target: 'offscreen',
            mediaId: id,
            options: options
          });
        }
      );
    });

离屏文档(原index.html)逻辑修正,移除主动发消息请求录制的逻辑,只负责接收mediaId启动流

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
  if (message.target === 'offscreen' && message.mediaId) {
    onAccessApproved(message.mediaId, message.options);
  }
});

// 原有onAccessApproved、流处理逻辑保持不变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 01:45:43