如何应对Chrome 112版本中的getDisplayMedia问题?

5,977 阅读6分钟

前言

前几天chrome发布了112版本,原本可以通过getDisplayMedia正确的获取当前标签页的内容,在此版本中却出现了内容挤压、概率性出现滚动条问题。

经过一番思考后,我想到了两个处理方案,本文就跟大家分享下我的思路,欢迎各位感兴趣的开发者阅读本文。

定位问题

当我在浏览器中调试修复截图插件的其他bug时,发现截取的内容出现了滚动条,这让我感到非常诧异。难道是我在解决某个问题的同时引发了更多问题。于是,我尝试回退代码版本,但问题仍然存在,这使我陷入了沉思,开始求助别人、在搜索引擎寻找解决方案。

经过一番折腾后,我没有得到任何答案。就在这时,有一个开发者在给我提了issue,他也遇到了同样的问题。

他提供了浏览器版本号,这让我恍然大悟,不久前我升级了浏览器版本并且版本号跟他的一样,同为112版本。

为了验证是否为浏览器升级所带来的bug,我在虚拟机中安装了110版本的chrome,发现这个问题确实不存在。难不成是Google改了API?我带着这个疑问翻阅了getDisplayMedia的API文档,并没有发现有什么特殊说明。

事情陷入了僵局,我又陷入了沉思。突然间,我想起来之前有个网友说Google自家也有产品用到了这个API,找他要来了网址。

果然,他们产品也出现了问题。

截取后的内容出现了挤压和滚动条

解决方案

Chrome在下个版本的更新中可能会解决此问题,也有可能不会解决。这是个未知数,我还是得想想其他办法来处理处理这个问题。

隐藏滚动条并填充挤压区域

最简单粗暴的办法就是在调用getDisplayMedia方法之前将页面的宽、高分别设为100vw100vh,给html和body元素设置overflow:hidden。部分代码如下所示:

// 设置页面宽高并隐藏滚动条
document.documentElement.classList.add("hidden-screen-shot-scroll");
document.body.classList.add("hidden-screen-shot-scroll");
.hidden-screen-shot-scroll {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
}

这样设置后,又出现了新的问题,因为截图容器的高度为body区域未挤压时的正确高度,webrtc的共享弹窗会把页面内容整体挤下去。

getDisplayMedia的回调里拿到的屏幕流数据是挤压后的,底部会缺少一部分,这部分内容正好是共享弹窗的高度。当用户想截取这部分的内容时,会发现拿到的是透明的。

为了解决这个问题,我想到了将这一部分用其他颜色填充,告诉用户这部分不是截图内容,部分代码如下所示:

if (
    this.hiddenScrollBar.state &&
    diffHeight > 0 &&
    this.hiddenScrollBar.fillState
) {
    // 填充容器的剩余部分
    imgContext.beginPath();
    let fillWidth = containerWidth;
    let fillHeight = diffHeight;
    if (this.hiddenScrollBar.fillWidth > 0) {
        fillWidth = this.hiddenScrollBar.fillWidth;
    }
    if (this.hiddenScrollBar.fillHeight > 0) {
        fillHeight = this.hiddenScrollBar.fillHeight;
    }
    imgContext.rect(0, fixHeight, fillWidth, fillHeight);
    imgContext.fillStyle = this.hiddenScrollBar.color;
    imgContext.fill();
}

使用窗口截图模式

通过上个章节的分析,我们发现使用标签页截图时出现挤压的根本原因在于共享弹窗会出现在页面的顶部。那么,有没有什么办法能做到不让这个共享弹窗出现在最顶部呢?

带着这个疑问,我在chrome的开发文档中找到了displaySurface选项,将这个值设为window,它的共享弹窗就不会出现了。部分代码如下所示:

let mediaWidth = window.screen.width * this.dpr;
let mediaHeight = window.screen.height * this.dpr;
navigator.mediaDevices.getDisplayMedia({
    audio: false,
    video: {
        width: mediaWidth,
        height: mediaHeight,
        displaySurface: "window"
    }
});

网页标题栏会出现录制图标

但是,这样子做我们拿到的是整个浏览器窗口的截图,我们想要的是body区域的截图内容。 因此,还需要对截图内容做进一步的裁剪处理,我们可以知道浏览器窗口的宽高、body区域的宽高。那么,用浏览器窗口的宽高减去body区域的宽高,我们就拿到了body区域的裁剪坐标。部分代码如下所示:

  /**
   * 从窗口数据流中截取页面body内容
   * @param videoWidth 窗口宽度
   * @param videoHeight 窗口高度
   * @param containerWidth body内容宽度
   * @param containerHeight body内容高度
   * @private
   */
  private getWindowContentData(
    videoWidth: number,
    videoHeight: number,
    containerWidth: number,
    containerHeight: number
  ) {
    const videoCanvas = document.createElement("canvas");
    videoCanvas.width = videoWidth;
    videoCanvas.height = videoHeight;
    const videoContext = getCanvas2dCtx(videoCanvas, videoWidth, videoHeight);
    if (videoContext) {
      videoContext.drawImage(this.videoController, 0, 0);
      const startX = 0;
      const startY = videoHeight - containerHeight;
      const width = containerWidth;
      const height = videoHeight - startY;
      // 获取裁剪框区域图片信息;
      return videoContext.getImageData(
        startX * this.dpr,
        startY * this.dpr,
        width * this.dpr,
        height * this.dpr
      );
    }
    return null;
  }

思考与分析

使用当前标签页进行截图相对而言用户体验是最好的,但是因为chrome 112版本的bug会造成页面内容挤压导致截取到的内容不完整,因此只能采用其他方案来解决此问题了。窗口截图模式和隐藏滚动条都可以解决这个问题,但是他们都有缺点:

  • 窗口截图模式可以完美的获取屏幕截图,但是用户授权时会出现其他的应用程序选项,用户体验会差一些。
  • 隐藏滚动条方案还是采用标签页截图,但是会造成内容挤压,底部出现空白。

这两种方案都是不完美的,希望Chrome能在后续的版本更新中修复此问题。

窗口模式截图我有找过能否让授权列表中只包含当前窗口选项,我在Chrome的开发文档中找到了Google对此情况的解释 Google为了防止开发者乱搞,不会提供此选项。但是我觉得即使你提供了限制选项,开发者也没有办法乱搞吧?毕竟授权窗口已经弹出了,用户大多数时候只是想分享当前打开的网页。如果选项列表里出现太多窗口,反而会让用户下意识的思考下:我到底该选哪个呢?

项目地址

本文所列举的代码完整内容请移步:

写在最后

至此,文章就分享完毕了。

我是神奇的程序员,一位前端开发工程师。

如果你对我感兴趣,请移步我的个人网站,进一步了解。

  • 文中如有错误,欢迎在评论区指正,如果这篇文章帮到了你,欢迎点赞和关注😊
  • 本文首发于神奇的程序员公众号,未经许可禁止转载💌