Screen Capture API简介-在浏览器中扫描QR码

引言


在本文中,我们猜测我们将讨论Screen Capture API。 该API诞生于2014年,很难将其命名为新API,但浏览器支持仍然很薄弱。 但是,它可以用于个人项目或这种支持不是很重要的地方。


一些链接可帮助您入门:



如果与演示程序的链接断开(或者您懒得去那里)-这是完成的演示程序的外观:



让我们开始吧。


动机


最近,我想到了一个在其工作中使用QR码的Web应用程序的想法。 而且,尽管它们通常便于发送(例如,现实世界中的长链接),您可以在其中将手机对准它们,但在台式机上却有点复杂。 如果QR码在您需要阅读的同一设备的屏幕上,则您需要弄乱识别服务或从电话中识别它,然后将数据传输回PC。 不方便。


有些产品(例如1Password)针对这种情况提供了一种有趣的解决方案。 如果您需要通过QR码设置帐户,他们会打开一个半透明窗口,您可以将其中的代码拖到图像上,并自动识别该图像。 看起来是这样的:



如果我们可以为我们的应用程序实现类似的东西,那将是理想的。 但可能无法在浏览器中运行...


见面-getDisplayMedia


好吧,差不多。 屏幕捕获API及其唯一的getDisplayMedia方法将为getDisplayMediagetDisplayMedia就像getUserMedia一样,仅适用于设备屏幕,而不适用于其相机。 不幸的是,如上所述,浏览器支持远不及访问相机。 根据MDN的说法,它可以在Firefox,Chrome,Edge(尽管放在错误的位置-在navigator ,而不是在navigator.mediaDevices )中使用+ Edge Mobile和Android版Opera。


预期中的两大移动浏览器旁边有很多很好奇的选择。


API本身非常简单。 它的工作原理与getUserMedia相同,但是允许您从已定义的显示表面之一捕获视频流:


  • 监视器 (整个屏幕),
  • 从某个应用程序的一个或多个窗口中,
  • 浏览器 ,或者从特定文档。 在Chrome中,此文档是一个单独的标签,但在FF中则没有此类选项。

浏览器API,它使您可以浏览浏览器之外的东西……听起来很熟悉,通常会带来一些麻烦,但是在这种情况下,它可能非常方便。 您可以从其他窗口捕获图片,例如,实时识别和翻译文本,例如Google Translate Camera。 好吧,可能还有更多有趣的用途。


我们收集


因此,我们弄清楚了API提供给我们的功能。 接下来是什么?


然后,我们需要将此视频流替换为可以处理的图像。 为此,我们使用<video><canvas>元素和其他一些JS。


该过程的特写看起来像这样:


  • 直接流到<video> ;
  • 以一定的频率在<canvas>绘制<video>的内容;
  • 使用getImageData 2D上下文方法从<canvas>收集ImageData对象。

由于流水线这么长,整个过程听起来有些奇怪,但是这种方法非常流行,并且用于从getUserMedia网络摄像头捕获数据。


忽略所有不相关的内容,以启动流并从中拉出帧,我们需要以下代码:


 async function run() { const video = document.createElement('video'); const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); const displayMediaOptions = { video: { cursor: "never" }, audio: false } video.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions); const videoTrack = video.srcObject.getVideoTracks()[0]; const { height, width } = videoTrack.getSettings(); context.drawImage(video, 0, 0, width, height); return context.getImageData(0, 0, width, height); } await run(); 

如上所述,首先我们创建<video><canvas>元素,并向画布请求2D上下文( CanvasRenderingContext2D )。


然后我们定义流量限制/条件 。 与相机中的流不同,它们很少。 我们说我们不想看到光标,并且我们不需要音频。 尽管在撰写本文时,任何人仍不支持音频捕获。


之后,我们将接收到的MediaStream类型的流挂接到<video>元素。 请注意, getDisplayMedia返回一个Promise。


最后,从流上接收到的数据中,我们记得视频的分辨率,以便正确地将其绘制到画布上,绘制帧并从ImageData拉出ImageData对象。


为了充分利用,您很可能希望循环处理帧而不是一次。 例如,当您等待所需的图像出现在框架中时。 这里需要说几句话。


当涉及到“以恒定循环处理DOM中的某些内容”时,想到的第一件事很可能是requestAnimationFrame 。 但是,在我们的情况下,将无法使用它。 事实是,当选项卡停止活动时,浏览器将暂停rAF循环处理。 在我们的情况下,这时我们将要处理图像。


在这方面,我们将使用旧的setInterval代替rAF。 但是他的处境并不顺利。 在非活动标签中,回调操作之间的间隔至少为1秒 。 尽管如此,这对我们来说已经足够了。


最后,当我们到达框架时,我们可以根据需要对其进行处理。 出于本演示的目的,我们将使用jsQR库。 这非常简单:输入接受ImageData ,即图像的宽度和高度。 如果接收到的图像具有QR码,您将获得一个带有已识别数据的JS对象。
让我们用几行代码来补充前面的示例:


 const imageData = await run(); const code = jsQR(imageData.data, streamWidth, streamHeight); 

做完了!


NPM


我认为可以将本示例后面的主要代码打包到一个npm库中,并在最初使用时节省一些时间以备后用。 该库非常简单,在这个阶段它只接受将ImageData发送到的回调,另外一个参数是发送数据的频率。 您需要自己进行所有处理。 我会考虑扩展功能是否有意义。


该库称为stream-displayNPM | Github


它的使用减少为字面上的三行代码和一个回调:


 const callback = imageData => {...} // do whatever with those images const capture = new StreamDisplay(callback); // specify where the ImageData will go await capture.startCapture(); // when ready capture.stopCapture(); // when done 

演示可以在这里看到。 还有一个CodePen版本可用于快速实验。 这两个示例都使用上面的NPM包。


关于测试的一点


将这些代码打包到库中后,我不得不考虑如何对其进行测试。 我绝对不想拖动50MB的无头Chrome浏览器在其中运行一些小测试。 尽管为所有组件编写存根的想法似乎很痛苦,但最终我还是这样做了。
选择了tape作为测试跑步者。 这是我最终不得不模拟的:


  • document对象和DOM元素。 为此,我选择了jsdom
  • 一些缺少实现的jsdom方法: HTMLMediaElement#playHTMLCanvasElement#getContextnavigator.mediaDevices#getDisplayMedia ;
  • 时间。 为此,我使用了useFakeTimers库的useFakeTimersuseFakeTimers称为lolex 。 它将其替换设置为setIntervalrequestAnimationFrame和许多其他随时间运行的函数,还允许您控制此假时间的流向。 但请注意:jsdom在其初始化过程中的某个位置会占用时间,如果您先打开sinon,一切都会冻结。

我还将sinon用于所有需要监视的功能存根。 其余部分由空的JS函数实现。


当然,您可以自由选择已经熟悉的工具。 但是,我希望这个清单可以让您提前准备,因为现在您知道您要处理的内容了。


最终结果可以在库存储库中看到。 看起来不太漂亮,但是可以用。


结论


事实证明,该解决方案并不像本文开头提到的透明窗口那样优雅,但是也许有一天网络会出现。 我们只能希望,当浏览器学习通过其窗口进行查看时,这些功能将由我们严格控制。 同时,请记住,当您在Chrome中浏览屏幕时-可以对其进行解析,记录等。 因此,不要随意翻唱!


我希望本文之后的人能够自己学到新的技巧。 如果您有其他用途的想法,请在评论中写下。 很快见。

Source: https://habr.com/ru/post/zh-CN460825/


All Articles