• 示例工程

【免费下载链接】WebRTC-Experiment

WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment
点击查看 免费下载

本文以 Firefox-Extensions/README.md 为主体,结合仓库内 enable-screen-capturing 等目录的源码,讲解如何通过 Firefox 扩展为 WebRTC 页面启用屏幕捕获能力:包括扩展的工作原理(preference 白名单机制)、页面与扩展的通信协议、如何把 arrayOfMyOwnDomains 替换为自有域名、如何用 jpm 构建 XPI 并提交到 Firefox Add-ons(AMO)商店,以及 Firefox 52 之后该方案的历史定位。

在 WebRTC 发展的早期,Firefox 的 getUserMedia 屏幕捕获 API 仅对少数被硬编码信任的域名开放,普通站点无法直接调用。本仓库的 Firefox-Extensions 子项目正是为解决这一问题而生:通过一个扩展把自定义域名写入 Firefox 的屏幕共享白名单,从而让 localhost127.0.0.1 以及你自己的 HTTPS 域名都能使用屏幕共享。读完本文,你将掌握该扩展的完整部署链路——从修改域名白名单、本地打包测试,到提交 AMO 审核发布,并理解其底层依赖的 media.getusermedia.screensharing.allowed_domains 机制。

历史背景:为什么需要这个扩展,又为何在 Firefox 52 后停更

仓库 Firefox-Extensions/README.md 开头就给出两个关键事实:

  • Firefox 52 起已原生支持屏幕共享,不再需要任何附加组件(add-on);
  • 本仓库自 2017 年 9 月 1 日起停止维护(discontinued)

因此,本文讲解的扩展属于"WebRTC 屏幕捕获历史解决方案":它服务于 Firefox 52 之前的浏览器版本(扩展 package.json 中声明 "firefox": ">=38.0a1",即至少需要 Firefox 38),以及 Firefox 47/48 前后那些仍受白名单限制的过渡版本。如果你在维护老版本 Firefox 环境、或需要复刻这段历史实现用于研究,本仓库依然是最完整的参考实现。

该扩展的目标非常明确,见 Firefox-Extensions/README.md

Enable screen capturing in Firefox for both localhost/127.0.0.1 and https://www.webrtc-experiment.com pages.

即:为本地回环地址(localhost127.0.0.1)与 HTTPS 页面同时开启屏幕捕获能力。

仓库结构:一套问题,三版实现

Firefox-Extensions 目录下存在三个演进版本:

目录技术形态说明
enable-screen-capturing/Add-on SDK(jpm)主推版本,README 中的部署步骤均针对它
enable-screen-capturing-old/旧式 bootstrap/XUL 扩展最早期实现,含 install.rdf 与预打包好的 enable-screen-capturing.xpi
enable-screen-capturing-old2/Add-on SDK(jpm)中间版本,支持网页动态申请域名授权

主版本的核心文件为 index.jscontent-script.jspackage.json,下文的分析均以它们为准。

工作原理:prefService 与屏幕共享白名单

Firefox 通过两组 preference 控制屏幕捕获权限,见 enable-screen-capturing/index.js

var prefService = require('sdk/preferences/service');

var configToReferListOfAllowedDomains = 'media.getusermedia.screensharing.allowed_domains';
var configToEnableScreenCapturing = 'media.getusermedia.screensharing.enabled';
  • media.getusermedia.screensharing.allowed_domains允许进行屏幕共享的域名列表(逗号分隔的字符串),只有出现在这个列表里的域名才能通过 getUserMedia 请求屏幕/窗口媒体;
  • media.getusermedia.screensharing.enabled:屏幕共享功能的总开关,源码注释说明该标志自 Firefox 37/38 起默认开启,扩展启动时显式把它置为 true
prefService.set(configToEnableScreenCapturing, true);

扩展的核心逻辑 addMyOwnDomains() 做的事情是:读取白名单 → 逐个检查自己的域名是否已存在 → 不存在则追加 → 写回 preference:

function addMyOwnDomains() {
    var existingDomains = prefService.get(configToReferListOfAllowedDomains).split(',');
    arrayOfMyOwnDomains.forEach(function(domain) {
        if (existingDomains.indexOf(domain) === -1) {
            existingDomains.push(domain);
        }
        // else { }
        else if (existingDomains.indexOf(domain) !== -1) {
            // Seems domain is already in the list.
            // Keep it when this addon is uninstalled.
            listOfSimilarAlreadyAllowedDomains.push(domain);
        }
    });
    prefService.set(configToReferListOfAllowedDomains, existingDomains.join(','));
}
addMyOwnDomains();

一个值得注意的细节:如果 localhost127.0.0.1 已被其他扩展加入白名单,扩展会把它记入 listOfSimilarAlreadyAllowedDomains,在卸载时不误删他人加入的域名——这正是 index.jsremoveMyDomainOnUnInstall() 的职责:

function removeMyDomainOnUnInstall() {
    var externalDomains = [];
    prefService.get(configToReferListOfAllowedDomains).split(',').forEach(function(domain) {
        // Skip Others Domains
        if (arrayOfMyOwnDomains.indexOf(domain) === -1) {
            // if its NOT mine, keep it.
            externalDomains.push(domain);
        } else if (listOfSimilarAlreadyAllowedDomains.indexOf(domain) !== -1) {
            // seems that localhost/127.0.0.1 are already added by external users
            externalDomains.push(domain);
        }
    });
    prefService.set(configToReferListOfAllowedDomains, externalDomains.join(','));
}

卸载钩子通过 sdk/system/unload 注册,且注释明确提到"By AMO policy global preferences must be changed back to their original value"(按 AMO 政策,全局偏好必须在扩展卸载时恢复原值),这保证了扩展不会在 Firefox 中留下永久性的全局配置污染。

除了写白名单,扩展还通过 sdk/page-modPageMod 仅在匹配自己域名模式的页面注入内容脚本(contentScriptWhen: "start"),patternsOfMyDomains 同时支持通配符与正则表达式:

var patternsOfMyDomains = ['*.webrtc-experiment.com', '*.rtcmulticonnection.herokuapp.com', /https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?\/.*/];

页面与扩展的通信协议

检测协议:checkIfScreenCapturingEnabled

主版本扩展支持页面主动查询"我的域名是否已启用屏幕捕获"。页面侧发起请求(见 enable-screen-capturing/README.md):

// ask addon to check if screen capturing enabled for specific domains
window.postMessage({
    checkIfScreenCapturingEnabled: true
}, "*");

// watch addon's response
// addon will return "isScreenCapturingEnabled=true|false"
window.addEventListener("message", function(event) {
    if (event.source !== window) return;

    var addonMessage = event.data;

    if(!addonMessage || typeof addonMessage.isScreenCapturingEnabled === 'undefined') return;

    if(addonMessage.isScreenCapturingEnabled === true) {
        alert(JSON.stringify(addonMessage.domains) + '\n are enabled for screen capturing.');
    }
    else {
        alert(JSON.stringify(addonMessage.domains) + '\n are NOT enabled for screen capturing.');
    }
}, false);

请求协议(Request):

字段含义
checkIfScreenCapturingEnabledtrue请求扩展检查屏幕捕获是否已对相关域名启用

响应协议(Response):

字段含义
isScreenCapturingEnabledtrue 表示域名已启用屏幕捕获
domains已启用屏幕捕获的域名列表

在内容脚本侧(content-script.js),消息被桥接到扩展主进程:内容脚本先校验 event.source === document.defaultView(防止外部域名伪造消息,见源码中引用的 issue 11),然后通过 self.port.emit('is-screen-capturing-enabled') 把请求转给主进程的 PageMod.onAttach 处理器;主进程读取 preference、筛选出属于自己的已启用域名后,经 worker.port.emit('is-screen-capturing-enabled-response', ...) 返回,内容脚本再以 window.postMessage(response, '*') 交还给页面。

动态授权协议:enableScreenCapturing(旧版扩展)

中间版本 enable-screen-capturing-old2 支持更激进的用法:网页可以直接请求"永久"启用指定域名,但需要用户通过 window.confirm 确认。页面侧代码(见 enable-screen-capturing-old2/README.md):

// request addon to enable screen capturing for your domains
window.postMessage({
	enableScreenCapturing: true,
	domains: ["www.yourdomain.com", "yourdomain.com"]
}, "*");

// watch addon's response
// addon will return "enabledScreenCapturing=true" for success
// else "enabledScreenCapturing=false" for failure (i.e. user rejection)
window.addEventListener("message", function(event) {
	var addonMessage = event.data;

	if(!addonMessage || typeof addonMessage.enabledScreenCapturing === 'undefined') return;

    if(addonMessage.enabledScreenCapturing === true) {
    	// addonMessage.domains === [array-of-your-domains]
    	alert(JSON.stringify(addonMessage.domains) + ' are enabled for screen capturing.');
    }
    else {
    	// reason === 'user-rejected'
    	alert(addonMessage.reason);
    }
}, false);

该版本的内容脚本会把请求的域名以 JSON 形式拼进确认弹窗,用户确认后通过 self.port.emit('installation-confirmed', addonMessage.domains) 通知主进程(见 content-script.js),主进程随后把这些域名追加进 arrayOfMyOwnDomains 并重新调用 addMyOwnDomains();若用户拒绝,则返回 enabledScreenCapturing: falsereason: 'user-rejected'

客户端配合:getScreenId.js 中的 Firefox 分支

仓库的 getScreenId.js/getScreenId.js 展示了页面侧如何与"已启用白名单"的 Firefox 协同:当检测到 navigator.mozGetUserMedia 时,回调直接返回 sourceId === 'firefox' 和对应的屏幕约束:

// for Firefox:
// sourceId == 'firefox'
// screen_constraints = {...}
if (!!navigator.mozGetUserMedia) {
    callback(null, 'firefox', {
        video: {
            mozMediaSource: 'window',
            mediaSource: 'window'
        }
    });
    return;
}

也就是说,一旦域名被扩展写入白名单,Firefox 便会放行以 mozMediaSource: 'window'(或 'screen')为约束的 getUserMedia 请求,页面拿到 MediaStream 后即可用于 RTCPeerConnection 推流。这与本扩展的用途形成完整闭环:扩展负责开白名单,页面负责发起捕获请求

自定义你自己的域名:修改 arrayOfMyOwnDomains

主版本 README 给出的部署第一步是修改域名白名单:

  1. 打开 enable-screen-capturing/index.js
  2. 定位到第 7 行附近的 arrayOfMyOwnDomains 数组(在旧版 old2 中位于第 11 行,见 enable-screen-capturing-old2/index.js);
  3. 把数组替换为你自己的域名列表。
// replace your own domains with below array
var arrayOfMyOwnDomains = ['webrtc-experiment.com', 'www.webrtc-experiment.com', 'localhost', '127.0.0.1'];

实践建议:

  • 必须同时列出裸域名与 www 子域(如 webrtc-experiment.comwww.webrtc-experiment.com),因为白名单按精确域名匹配;
  • localhost127.0.0.1 通常应保留,便于本地联调;
  • 若你的站点包含多个子域,请逐一加入,或参考 patternsOfMyDomains 中的 *.webrtc-experiment.com 通配写法设计匹配模式;
  • 修改后记得同步检查 package.json 中的 titledescription 等元数据,它们会展示在 AMO 商店页面上。

本地构建与打包:jpm 工作流

该扩展基于 Mozilla Add-on SDK,打包工具是 jpm。README 中给出的完整构建命令如下:

[sudo] npm install jpm --global

jpm run -b nightly 		# test in Firefox Nightly without making the XPI

jpm xpi					# it will create xpi file

逐步说明:

  1. npm install jpm --global:全局安装 jpm(部分系统需要 sudo);
  2. jpm run -b nightly:在 Firefox Nightly 中直接运行扩展做冒烟测试,无需先生成 XPI 文件,适合迭代调试;
  3. jpm xpi:在 enable-screen-capturing/ 目录内执行,生成可提交的 XPI 安装包。

package.json 是 jpm 的清单文件,其中 "main": "index.js" 声明扩展入口,"engines": { "firefox": ">=38.0a1" } 声明兼容的 Firefox 版本范围,"permissions": { "unsafe-content-script": true } 允许内容脚本访问页面作用域(便于调用 window.postMessage/window.confirm 等页面 API)。

提交到 AMO:发布你的扩展

按照 README 的部署清单,发布到 Firefox Add-ons 商店需要五步:

  1. 注册开发者账号:在 addons.mozilla.org 注册(users/register 页面);
  2. 使用唯一的扩展名:修改 package.json 中的 name/title 字段,避免与商店已有扩展冲突;
  3. 加入自己的域名:修改 index.jsarrayOfMyOwnDomains
  4. 生成 XPI:执行上述 jpm xpi 命令打包;
  5. 提交 XPI 审核:在 AMO 开发者后台的提交页面按流程上传,选择合法的浏览器版本(README 建议选择 Firefox 38 到 Firefox 45 区间),然后提交给 Mozilla 人工审核(review)。

README 特别提醒:提交审核是"hard/tough step"(最困难的一步),需要仔细阅读并遵循 AMO 的每一步指引;审核周期通常为 2~3 小时,通过后扩展才会对公众可见。提交前请务必确认扩展遵守 AMO 政策——尤其是前文提到的"卸载时必须把全局 preference 恢复原值"这一条,仓库源码已经内置了相应实现(sdk/system/unload 钩子)。

版本限制与适用前提

由于仓库已于 2017 年停更,且 Firefox 52 起原生支持屏幕共享,请务必明确本方案的历史适用性:

  • 扩展声明支持 Firefox >= 38.0a1
  • README 源码注释推测 media.getusermedia.screensharing.enabled 在 47~48 版本附近可能被移除,但这一推断属作者当时的前瞻性判断,不代表已发生的版本事实;
  • 当前主流 Firefox 请直接使用 navigator.mediaDevices.getDisplayMedia() 进行屏幕共享,无需任何扩展。

配套测试

主版本在 enable-screen-capturing/test/test-index.js 提供了基于 SDK sdk/test 的最小单元测试骨架,包含同步与异步两种用例模式,可作为自测扩展入口是否正常加载的参考。

License

本子项目(Firefox-Extensions)以 MIT 许可证 发布,版权归 Muaz Khan 所有。仓库根目录同样采用 MIT 许可(见 LICENSE),你在其基础上二次开发、重新打包发布自己的域名白名单版本是允许的,但请在衍生作品中保留原作者版权声明。

  • 示例工程

【免费下载链接】WebRTC-Experiment

WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!

项目地址: https://gitcode.com/gh_mirrors/we/WebRTC-Experiment
点击查看 免费下载
Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐