- 示例工程
【免费下载链接】WebRTC-Experiment
WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!
导读
Screen-Capturing.js 是 WebRTC-Experiment 仓库中用于在网页端捕获屏幕/应用窗口 MediaStream 的前端脚本库,它依赖配套的 Chrome desktopCapture 扩展(Chrome-Extensions/Screen-Capturing.js/Screen-Capturing.js)。本指南将带你走通"下载并改造扩展 → 修改 manifest.json 白名单 → 在页面中引入脚本 → 调用各 API 捕获屏幕与系统音频"的完整链路,并结合仓库源码剖析底层 postMessage 消息协议与 chrome.desktopCapture 调用原理。读完本文,你可以在自己的 HTTPs 域上独立完成 Chrome 屏幕共享的集成与排错。
重要前提:该扩展自 2019 年起已停止维护,原作者明确声明"use at your own risk"。新项目请优先使用浏览器原生的
getDisplayMediaAPI,本文中的扩展方案仅适用于需要兼容旧版 Chrome(34+)或需要sourceId细粒度控制的遗留场景。
先看现代替代方案:getDisplayMedia
在深入扩展方案之前,务必先掌握现代浏览器提供的原生屏幕捕获 API。README 开篇即给出推荐的统一封装,它按能力降级依次尝试navigator.getDisplayMedia、navigator.mediaDevices.getDisplayMedia,最后才回退到扩展方案:
getScreenStream(function(screenStream) { video.srcObject = screenStream; }); function getScreenStream(callback) { if (navigator.getDisplayMedia) { navigator.getDisplayMedia({ video: true }).then(screenStream => { callback(screenStream); }); } else if (navigator.mediaDevices.getDisplayMedia) { navigator.mediaDevices.getDisplayMedia({ video: true }).then(screenStream => { callback(screenStream); }); } else { getScreenId(function(error, sourceId, screen_constraints) { navigator.mediaDevices.getUserMedia(screen_constraints).then(function(screenStream) { callback(screenStream); }); }); } }getDisplayMedia由浏览器直接弹窗让用户选择"整个屏幕 / 应用窗口 / 标签页",无需安装任何扩展、无需部署白名单,这是目前唯一推荐的方案。只有当用户浏览器不支持该 API 时,才需要走下文的扩展路径。
工作原理与消息协议(源码剖析)
Screen-Capturing.js 本身不直接调用chrome.desktopCapture,它通过postMessage 消息协议与扩展的 content-script 通信,扩展的 background-script 再调用底层 API 返回sourceId。整条链路由三个脚本协作完成:
| 脚本 | 角色 | 仓库路径 |
|---|---|---|
| Screen-Capturing.js | 网页侧 API 封装,发送/接收 postMessage | Chrome-Extensions/Screen-Capturing.js/Screen-Capturing.js |
| content-script.js | 网页与 background 之间的消息中转站 | Chrome-Extensions/desktopCapture/content-script.js |
| background-script.js | 调用chrome.desktopCapture.chooseDesktopMedia,返回 sourceId | Chrome-Extensions/desktopCapture/background-script.js |
网页侧:postMessage 的发送与接收
Screen-Capturing.js 在加载时注册message事件监听,并且只处理同源消息:
window.addEventListener('message', function(event) { if (event.origin != window.location.origin) { return; } onMessageCallback(event.data); });onMessageCallback处理三类响应(见 Screen-Capturing.js):
- 收到字符串
'PermissionDeniedError':用户点了"取消",直接以该字符串回调,便于调用方判断; - 收到
'rtcmulticonnection-extension-loaded':扩展通知自己已在页面中注入,脚本据此将内部状态chromeMediaSource置为'desktop'; - 收到携带
sourceId的对象:扩展共享了临时 sourceId,同时携带canRequestAudioTrack布尔值,表示本次选择的源是否允许捕获系统音频。
中转站:content-script
content-script.js 维护一个rtcmulticonnectionMessages白名单对象(包含'are-you-there'、'get-sourceId'、'audio-plus-tab'三个字符串),只放行白名单内的消息,避免与其他无关 postMessage 冲突:
- 网页发来
'are-you-there'时,content-script 直接回复'rtcmulticonnection-extension-loaded',让网页快速探测扩展存在性; - 网页发来
'get-sourceId'或'audio-plus-tab',或携带get-custom-sourceId数组的消息时,通过chrome.runtime.connect()建立的 Port 转发给 background-script; - background 返回的消息再通过
window.postMessage(message, '*')广播回网页。
底层:background-script 与 chooseDesktopMedia
background-script.js 在chrome.runtime.onConnect中监听 Port 消息,根据消息类型配置screenOptions并调用chrome.desktopCapture.chooseDesktopMedia(screenOptions, port.sender.tab, onAccessApproved):
'get-sourceId':使用默认选项['screen', 'window'],让用户选择整屏或某个应用窗口;'audio-plus-tab':将选项扩展为['screen', 'window', 'audio', 'tab'],即额外允许捕获标签页和系统音频;{ 'get-custom-sourceId': [...] }:使用调用方自定义的选项数组。
用户确认后,onAccessApproved(sourceId, opts)被回调:若sourceId为空(点了取消),回传'PermissionDeniedError';否则回传{ sourceId: sourceId, canRequestAudioTrack: !!opts.canRequestAudioTrack }。这个 sourceId 最终被写入 getUserMedia 的chromeMediaSourceId约束中,从而拿到屏幕 MediaStream。
第一步:下载并改造 desktopCapture 扩展
使用 Screen-Capturing.js 前,必须先准备扩展。仓库中的扩展源码位于 Chrome-Extensions/desktopCapture,你需要自行下载、改造并发布:
- 下载 desktopCapture 目录全部文件;
- 修改 manifest.json 中 content-scripts 的
matches白名单,把默认的"https://www.webrtc-experiment.com/*"替换为你自己的域名; - 通过
chrome://extensions/以"加载已解压的扩展程序"方式本地测试,或打包成 ZIP 上传 Google Web Store 发布。
manifest.json 关键配置
原仓库 manifest.json 的完整配置如下(version 3.7、manifest_version 2):
{ "name" : "Screen Capturing", "author": "Muaz Khan", "version" : "3.7", "manifest_version" : 2, "minimum_chrome_version": "34", "description" : "Capture full-screen or specific application's screen on any HTTPs domain!", "background": { "scripts": ["background-script.js"], "persistent": false }, "content_scripts": [ { "js": [ "content-script.js" ], "all_frames": true, "run_at": "document_end", "matches": ["https://www.webrtc-experiment.com/*"] }], "icons" : { "48" : "icon.png" }, "permissions": [ "desktopCapture" ], "web_accessible_resources": [ "icon.png" ] }你需要改动的核心只有matches,例如换成你的域名:
"matches": ["https://www.your-domain.com/*"]配置要点说明:
minimum_chrome_version: "34":chrome.desktopCaptureAPI 自 Chrome 34 起可用,这是扩展的最低兼容版本;permissions中的"desktopCapture":声明使用桌面捕获 API 的权限;all_frames: true:页面中所有 iframe 都会注入 content-script,保证getScreenId这类 iframe 嵌套方案也能工作;background.persistent: false:使用事件驱动(非持久)后台页;web_accessible_resources中的icon.png:网页侧通过chrome-extension://<id>/icon.png探测扩展安装状态时需要使用它(见下文getChromeExtensionStatus原理)。
第二步:在页面中引入 Screen-Capturing.js
扩展就绪后,在你的 HTTPs 页面中引入脚本(仓库同时提供 CDN 用法与本地 index.html 演示页):
<script src="https://www.webrtc-experiment.com/Screen-Capturing.js"></script> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>若希望离线使用,可直接拷贝仓库中的 Screen-Capturing.js 到你的站点;也可以使用 npm 包:
npm install webrtc-screen-capturing # node_modules/webrtc-screen-capturing/Screen-Capturing.js脚本同时支持 Chrome 与 Firefox:Firefox 无需扩展,getScreenConstraints会直接返回{ mozMediaSource: 'window', mediaSource: 'window' }约束(见 Screen-Capturing.js)。
API 全览与用法示例
getScreenConstraints
获取可直接传给navigator.mediaDevices.getUserMedia的屏幕捕获约束对象,内部自动完成扩展可用性检查、sourceId 获取与约束组装:
getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } if(screen_constraints.canRequestAudioTrack) { // 本次选择的源支持捕获系统扬声器音频 // getUserMedia({audio:screen_constraints}) } navigator.mediaDevices.getUserMedia({ video: screen_constraints }).then(function(stream) { var video = document.querySelector('video'); video.src = URL.createObjectURL(stream); video.play(); }).catch(function(error) { alert(JSON.stringify(error, null, '\t')); }); });从源码(Screen-Capturing.js)可以看到其内部组装的约束结构:
var screen_constraints = { mandatory: { chromeMediaSource: chromeMediaSource, // 'screen' 或 'desktop' maxWidth: screen.width > 1920 ? screen.width : 1920, maxHeight: screen.height > 1080 ? screen.height : 1080 }, optional: [] };其中chromeMediaSource默认是'screen';一旦探测到扩展存在,其值变为'desktop',并且脚本会请求扩展返回 sourceId 后写入screen_constraints.mandatory.chromeMediaSourceId。maxWidth/maxHeight以屏幕实际分辨率与 1920×1080 中较大者为准。
getScreenConstraintsWithAudio
与getScreenConstraints相同,但额外包含系统音频(扬声器)。实现上只是以captureSourceIdWithAudio=true调用getScreenConstraints,进而走getSourceIdWithAudio路径:
getScreenConstraintsWithAudio(function(error, screen_constraints) { if (error) { return alert(error); } navigator.mediaDevices.getUserMedia({ video: screen_constraints, audio: screen_constraints // 必须同时传 audio 这一行 }).then(function(stream) { var video = document.querySelector('video'); video.src = URL.createObjectURL(stream); video.play(); }).catch(function(error) { alert(JSON.stringify(error, null, '\t')); }); });演示页 index.html 中还给出了更稳妥的写法:先检查screen_constraints.canRequestAudioTrack === true,再决定是否把 audio 约束传出去:
navigator.mediaDevices.getUserMedia({ video: screen_constraints, audio: screen_constraints.canRequestAudioTrack ? screen_constraints : false }).then(...)getSourceId
直接向扩展索取sourceId(即chromeMediaSourceId),适合进阶用户自行组装约束:
getSourceId(function(sourceId, canRequestAudioTrack) { if(sourceId != 'PermissionDeniedError') { // 拿到 sourceId,组装自己的 getUserMedia 约束 } if(canRequestAudioTrack === true) { // 系统音频(扬声器)可用 } });注意其实现细节:若sourceId已缓存,会立即用缓存值回调(if(sourceId) return callback(sourceId);),避免重复弹出选择框;否则设置screenCallback并通过window.postMessage('get-sourceId', '*')向扩展请求。
getCustomSourceId
按需指定捕获来源类型,第一个参数必须是数组。支持的格式:
window:捕获指定应用窗口screen:捕获整个屏幕tab:捕获标签页audio:捕获系统音频
var our_own_choices = ['tab', 'audio']; getCustomSourceId(our_own_choices, function(sourceId, canRequestAudioTrack) { if(sourceId != 'PermissionDeniedError') { // 你的代码 } if(canRequestAudioTrack === true) { // 系统音频(扬声器)可用 } });底层通过window.postMessage({ 'get-custom-sourceId': arr }, '*')把数组传给扩展,background-script 收到后将其直接作为chooseDesktopMedia的选项。
getSourceIdWithAudio
getSourceId的"含系统音频"版本,与getScreenConstraintsWithAudio对应:
getSourceIdWithAudio(function(sourceId, canRequestAudioTrack) { if(sourceId != 'PermissionDeniedError') { // 你的代码 } if(canRequestAudioTrack === true) { // 系统音频(扬声器)可用 } });getChromeExtensionStatus
推荐使用的扩展状态检测方法,比isChromeExtensionAvailable更可靠。它在页面中创建一个指向chrome-extension://<id>/icon.png的<img>元素:onload说明扩展已安装,随后再通过 postMessage 握手判断是否启用;onerror说明未安装。可省略参数,此时使用默认扩展 IDajhifddimkapgcifgcodmmfdlknahffk;若使用自己发布的扩展,请传入自己的扩展 ID。
// 传你自己的扩展 ID;不传则使用默认 ID getChromeExtensionStatus('your-extension-id', function(status) { if(status == 'installed-enabled') { // 已安装且已启用 } if(status == 'installed-disabled') { // 已安装但被禁用 } if(status == 'not-installed') { // 未安装 } if(status == 'not-chrome') { // 非 Chrome 浏览器(Firefox 等) } });注意:在非 Chrome 浏览器(源码中用
typeof window.InstallTrigger判断 Firefox)中会直接回调'not-chrome'。
isChromeExtensionAvailable
较简化的存在性探测:向扩展发送'are-you-there',2 秒后仍未收到响应则判定不可用:
isChromeExtensionAvailable(function(isAvailable) { if(!isAvailable) alert('Chrome extension is either not installed or disabled.'); });官方文档建议优先使用getChromeExtensionStatus,因为它能区分"未安装"与"已禁用"两种状态。
常见问题:无法重复捕获屏幕?
屏幕捕获一次后再次调用 API 却不再弹窗?解决办法是先把sourceId置为null,再调用任意 API:
sourceId = null; // 关键一行 getScreenConstraints(function(error, screen_constraints) { if (error) { return alert(error); } navigator.mediaDevices.getUserMedia({ video: screen_constraints }).then(function(stream) { var video = document.querySelector('video'); video.src = URL.createObjectURL(stream); video.play(); }).catch(function(error) { alert(JSON.stringify(error, null, '\t')); }); });原因在源码中可见:getSourceId、getCustomSourceId、getSourceIdWithAudio都会检查全局变量sourceId是否已有缓存值,存在则直接复用而不再请求扩展;清空它即可让下一次调用重新触发chooseDesktopMedia选择框。同理,如果你的业务需要"每次都让用户重新选择",可以在回调前主动清理sourceId。
进阶替代方案:getScreenId.js(免发布扩展)
如果不想自己发布扩展,仓库还提供了 getScreenId.js 方案。它使用iframe 黑客技巧:页面中的 iframe 从https://www.webrtc-experiment.com/域加载,该域已在官方扩展白名单内,iframe 与扩展通过 postMessage 交换 sourceId,再转发回你的页面,从而让同一个官方扩展在任意 HTTPs 域可用。其完整调用方式与 API(getScreenId、getChromeExtensionStatus、自定义参数捕获音频/标签页等)见 getScreenId.js/README.md。
<script src="https://www.WebRTC-Experiment.com/getScreenId.js"></script> <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script> <video controls autoplay></video> <script> getScreenId(function (error, sourceId, screen_constraints) { navigator.mediaDevices.getUserMedia(screen_constraints).then(function (stream) { document.querySelector('video').src = URL.createObjectURL(stream); }).catch(function (error) { console.error(error); }); }); </script>该方案同样有局限:在 iframe 内使用时 postMessage 机制可能失效,官方建议此时改用 WebSocket 或外部服务器中转 sourceId;并且同样需要 HTTPs 环境。
本地运行演示
仓库为演示页提供了极简静态服务器(server.js),基于 Node.js 原生 http 模块,默认监听 9001 端口,仅做静态文件服务并屏蔽对 server.js 自身的访问:
node server.js # Server listening at http://localhost:9001技术栈与适用场景小结
| 方案 | 适用场景 | 关键限制 |
|---|---|---|
getDisplayMedia | 现代 Chrome/Edge/Firefox | 需用户手动选择源;不支持旧版 Chrome 34-70 等 |
| Screen-Capturing.js + 自发布扩展 | 需要固定扩展、细粒度 sourceId 控制的 HTTPs 站点 | 需改 manifest 白名单并发布;扩展已停止维护 |
| getScreenId.js + 官方扩展 | 不想发布扩展、任意 HTTPs 域快速验证 | 依赖官方扩展与 iframe 中转;iframe 内不可用 |
最后再次提醒:新项目请直接使用getDisplayMedia;Screen-Capturing.js 与 desktopCapture 扩展仅作为历史兼容方案保留。本文所有 API 行为均可对照仓库源码验证:网页侧封装见 Screen-Capturing.js,消息中转见 content-script.js,底层桌面捕获见 background-script.js,完整交互演示见 index.html。
- 示例工程
【免费下载链接】WebRTC-Experiment
WebRTC, WebRTC and WebRTC. Everything here is all about WebRTC!!
相关推荐
如何快速实现屏幕翻译?Screen Translator 完整使用指南
如何快速实现屏幕翻译?Screen Translator 完整使用指南 Screen Translator 是一款强大的开源屏幕翻译工具,集成了屏幕捕捉、OCR
桌面应用OCRElectron屏幕捕获:实现屏幕截图与录屏功能
Electron屏幕捕获:实现屏幕截图与录屏功能 在桌面应用开发中,屏幕捕获是一个常见且重要的功能需求。无论是用于远程协助、教学演示、游戏录制还是应用监控,屏幕
桌面应用跨平台前端屏幕翻译终极指南:Screen Translator完整使用教程
屏幕翻译终极指南:Screen Translator完整使用教程 Screen Translator是一款功能强大的开源屏幕翻译工具,它通过智能屏幕捕捉、精准O
桌面应用OCR
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考