HTML5 中将浏览器选项卡共享屏幕
在过去的几年里,我已经帮助几家不同的公司仅使用浏览器技术实现了类似屏幕共享的功能。 根据我的经验,仅在 Web 平台技术(即无插件)中实施 VNC 是一个难题。 有很多事情需要考虑,有很多挑战需要克服。 中继鼠标指针位置、转发击键以及以 60fps 实现完整的 24 位颜色重绘只是其中的几个问题。
捕获选项卡内容
如果我们消除传统屏幕共享的复杂性并专注于共享浏览器选项卡的内容,那么问题将大大简化为:
- 捕获当前状态下的可见选项卡
- 通过网络发送该帧
本质上,我们需要一种方法来对 DOM 进行快照并将其共享出去。
分享部分很简单。 Websockets 能够以不同的格式(字符串、JSON、二进制)发送数据。 快照部分是一个更难的问题。 像 html2canvas 这样的项目已经通过重新实现浏览器的渲染引擎来解决 HTML 屏幕捕获问题……在 JavaScript 中! 另一个例子是 Google Feedback ,尽管它不是开源的。 这些类型的项目 非常 酷,但速度也非常慢。 如果能达到 1fps 的吞吐量就已经很幸运了,更不用说令人垂涎的 60fps 了。
本文讨论了一些我最喜欢的“屏幕共享”选项卡的概念验证解决方案。
方法一:Mutation Observers + WebSocket
演示了一种镜像选项卡的方法 Rafael Weinstein 今年早些时候 。 他的技术使用 Mutation Observers 和 WebSocket。
本质上,演示者正在共享的选项卡会监视页面的更改,并使用 websocket 将差异发送给查看者。 当用户滚动页面或与页面交互时,观察者会获取这些更改并使用 Rafael 的 突变摘要库 将它们报告给查看者。 这样可以保持性能。 并不是每一帧都发送整个页面。
正如 Rafael 在视频中指出的那样,这只是概念验证。 不过,我认为这是将 Mutation Observers 等较新平台功能与 Websockets 等较旧平台功能相结合的巧妙方法。
方法 2:来自 HTMLDocument + 二进制 WebSocket 的 Blob
下一个方法是我最近突然想到的方法。 它类似于 Mutation Observers 方法,但它不是发送摘要差异,而是创建整个的 Blob 克隆 HTMLDocument
并通过二进制 websocket 发送它。 这是按设置进行的设置:
- 将页面上的所有 URL 重写为绝对 URL。 这可以防止静态图像和 CSS 资产包含损坏的链接。
- 克隆页面的文档元素:
document.documentElement.cloneNode(true);
- 使用 CSS 使克隆只读、不可选择并防止滚动
pointer-events: 'none';user-select:'none';overflow:hidden;
- 捕获页面的当前滚动位置并将它们添加为
data-*
副本上的属性。 - 创建一个
new Blob()
来自.outerHTML
的副本。
代码看起来像这样(我已经从完整源代码中进行了简化):
function screenshotPage() {
// 1. Rewrite current doc's imgs, css, and script URLs to be absolute before
// we duplicate. This ensures no broken links when viewing the duplicate.
urlsToAbsolute(document.images);
urlsToAbsolute(document.querySelectorAll("link[rel='stylesheet']"));
urlsToAbsolute(document.scripts);
// 2. Duplicate entire document tree.
var screenshot = document.documentElement.cloneNode(true);
// 3. Screenshot should be readyonly, no scrolling, and no selections.
screenshot.style.pointerEvents = 'none';
screenshot.style.overflow = 'hidden';
screenshot.style.userSelect = 'none'; // Note: need vendor prefixes
// 4. ... read on ...
// 5. Create a new .html file from the cloned content.
var blob = new Blob([screenshot.outerHTML], {type: 'text/html'});
// Open a popup to new file by creating a blob URL.
window.open(window.URL.createObjectURL(blob));
}
urlsToAbsolute()
包含简单的正则表达式,用于将相对/无架构 URL 重写为绝对 URL。 这是必要的,这样图像、css、字体和脚本在 blob URL 的上下文中查看时不会中断(例如,来自不同的来源)。
我做的最后一项调整是添加滚动支持。 当演示者滚动页面时,观众应该跟随。 为此,我将当前的 scrollX
和 scrollY
定位为 data-*
副本的属性 HTMLDocument
,在创建最终的 Blob 之前,会注入一些在页面加载时触发的 JS:
// 4. Preserve current x,y scroll position of this page. See addOnPageLoad().
screenshot.dataset.scrollX = window.scrollX;
screenshot.dataset.scrollY = window.scrollY;
// 4.5. When screenshot loads (e.g. in blob URL), scroll it to the same location
// of this page. Do this by appending a window.onDOMContentLoaded listener
// which pulls out the screenshot (dupe's) saved scrollX/Y state on the DOM.
var script = document.createElement('script');
script.textContent = '(' + addOnPageLoad_.toString() + ')();'; // self calling.
screenshot.querySelector('body').appendChild(script);
// NOTE: Not to be invoked directly. When the screenshot loads, scroll it
// to the same x,y location of original page.
function addOnPageLoad() {
window.addEventListener('DOMContentLoaded', function(e) {
var scrollX = document.documentElement.dataset.scrollX || 0;
var scrollY = document.documentElement.dataset.scrollY || 0;
window.scrollTo(scrollX, scrollY);
});
假装滚动给人的印象是我们已经截取了原始页面的一部分,而实际上,我们已经复制了整个内容,只是重新定位了它。
演示
下面的演示按钮截屏此页面并在新窗口中打开它。
注意:如果浏览器阻止弹出窗口,您可能需要取消阻止弹出窗口。
但是对于标签共享,我们需要不断地捕获标签并将其发送给查看者。 为此,我编写了一个小型 Node websocket 服务器、应用程序和小书签来演示该流程。
未来的改进
一种优化是不要在每一帧上复制整个文档。 这是一种浪费,而 Mutation Observer 示例在这方面做得很好。 另一个改进是处理相关的 CSS 背景图像 urlsToAbsolute()
. 这是当前脚本没有考虑的事情。
方法三:Chrome Extension API + Binary WebSocket
在 Google I/O 2012 上,我演示了另一种屏幕共享浏览器选项卡内容的方法。 然而,这是一个骗子。 它需要一个 Chrome 扩展 API:不是纯粹的 HTML5 魔法。
这个的 来源 也在 Github 上,但要点是:
- 将当前选项卡捕获为 .png dataURL。 Chrome 扩展有一个 API
chrome.tabs.captureVisibleTab()
. - 将 dataURL 转换为
Blob
. 看convertDataURIToBlob()
帮手。 - 通过设置使用二进制 websocket 将每个 Blob(帧)发送到查看器
socket.responseType='blob'
.
例子
下面是将当前选项卡截图为 png 并通过 websocket 发送框架的代码:
var IMG_MIMETYPE = 'images/jpeg'; // Update to image/webp when crbug.com/112957 is fixed.
var IMG_QUALITY = 80; // [0-100]
var SEND_INTERVAL = 250; // ms
var ws = new WebSocket('ws://...', 'dumby-protocol');
ws.binaryType = 'blob';
function captureAndSendTab() {
var opts = {format: IMG_MIMETYPE, quality: IMG_QUALITY};
chrome.tabs.captureVisibleTab(null, opts, function(dataUrl) {
// captureVisibleTab returns a dataURL. Decode it -> convert to blob -> send.
ws.send(convertDataURIToBlob(dataUrl, IMG_MIMETYPE));
});
}
var intervalId = setInterval(function() {
if (ws.bufferedAmount == 0) {
captureAndSendTab();
}
}, SEND_INTERVAL);
未来的改进
帧率对于这个来说出奇的好,但它可能会更好。 一项改进是消除将 dataURL 转换为 Blob 的开销。 很遗憾, chrome.tabs.captureVisibleTab()
只给了我们一个数据 URL。 如果它返回一个 Blob 或 Typed Array,我们可以直接通过 websocket 发送它,而不是自己转换成 Blob。 请 为crbug.com/32498 加注 星标以实现这一目标!
旁白:如果 FF( 错误 648610 )和 Chrome( crbug.com/67587 )实现,那将是致命的
<canvas>.toBlob()
. 我想为图像添加滤镜/效果,而不必引入 dataURL 的开销! 法郎有canvas.mozGetAsFile()
, 但它是非标准的。
方法四:WebRTC——真正的未来
最后但并非最不重要的!
浏览器中的屏幕共享未来将由 WebRTC 实现。 2012年8月14日,团队提出了 WebRTC Tab Content Capture API,用于共享标签页内容:
提议的 API 使选项卡输出能够被捕获为媒体流,并使用 WebRTC 进行传输。 还定义了支持 API 来通知和查询选项卡的捕获状态......此 API 支持一种特殊形式的截屏,但用户可以在其中共享选项卡的内容,而不是共享他们的整个桌面。
在这个人准备好之前,我们只剩下方法 1-3。
结论
因此,当今的网络技术可以实现浏览器选项卡共享,但是应该对这一声明持保留态度。 虽然简洁,但本文中的技术在某种程度上达不到出色的共享用户体验。 这一切都将随着 WebRTC 选项卡内容捕获 的努力而改变,但在它成为现实之前,我们只能使用浏览器插件或有限的解决方案,如本文所述。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论