在Jitsi上用getDisplayMedia录制本地音频

事情的起因是这样的,我想往自己的Jitsi Meet实例中添加本地音频,但操作时发现结果并不是我想要的,所以我打算自己开发个简单的程序作为替代。在这过程中,我发现:

1. 用于屏幕截图的getDisplayMedia缺点多多;

2. 用于媒体录制的mediaRecorder也有诸多局限;

3. 在Jitsi Meet里添加HTML / JavaScript的操作非常简单;

下文是操作的具体细节和一些参考代码。点击这里可以查看我的成品。

在Jitsi上用getDisplayMedia录制本地音频

注:如果你想查看Mozilla的Jan-Ivar给出的就此评论和注意事项,请移步评论区。

问题凸显

几个月前,我构建了一个Jitsi Meet服务器。当时我想借其来更新我名为《两天内用WebRTC建立一家通信公司》的文章。当然,已经有很多讲解如何开发Jitsi Meet的文章或视频了,而且我也没有什么新鲜的或有趣的意见可分享了。但还有那么一项我迫切想改善的功能——录制。我经常做展示和录制会议,以供他人参考,为将来留存资料。表面上看,我的要求很简单:按需录制我的音频以及Jitsi Meet会话的音频和视频,将文件保存在本地。听起来操作很简单,实则不然。

Jitsi 云录制挑战

Jitsi中有一个用于记录的模块,叫Jibri。Jibri设置和配置的安装要比Jitsi Meet其他部件复杂。但如果你熟悉框架系统,几个小时甚至更少的时间就能把它安装好。Jibri加载了一个无头浏览器去充当呼叫的无声参与者,获取音频并将其保存到磁盘。这会占用大量资源,迫使我把服务器从5美元 / mo升级到了20美元 / mo。但它一次也只处理一条录音。如果你想一次性记录多条,可以设置启动多个Docker容器。但这样也越来越复杂了。除此之外,你还需要建立一种机制来在录制结束后,将文件传输到某个地方或设置Dropbox集成。我只想要一种能快速录制会话,之后进行共享的方法,但现行的功能把这个操作变得很复杂。是时候寻找一种更简单的方法了。

本地录音

另一种方法是仅在本地录制。其实本地记录会更安全,因为你不会把未加密的媒体留在服务器的某处。此外,因为你使用的是本地计算机来保存已经接收的媒体,而不是在云中添加新元素,所以也减少了资源消耗。 Jitsi实际上也有这么个选项,但它只能本地录制音频。而我需要把我看到的也记录下来,所以我开始尝试添加本地屏幕录像。

如何获得媒体

最傻瓜的操作是把一些音频放在一起进行Quicktime录制;只使用基于WebRTC的录制API或浏览器扩展程序之一也可以。对此我不做过多叙述,因为该操作并不对所有网络用户通用。我以前做过一个录音机,它可以重载createPeerConnection API,获取多个连接中的所有音轨,并将所有音频保存到文件中。实际上它可以和Jitsi Meet的多音频连接配合使用。我原本想设置一些可以同时录屏的canvas,并将其保存。但是我发现用getDisplayMedia操作会更简单。

getDisplayMedia

在这篇文章中,我向大家介绍了用于共享桌面媒体的getDisplayMedia API。 其优点是所有主流浏览器都应用了getDisplayMedia。 缺点是应用方式各不相同,且可能会影响用户体验。

注:下文提到的Edge指的是基于Chromium的新Edge。

测试代码

我原以为这是一个容易评估的API,但我想错了。为收集数据,我写了代码来调用getDisplayMedia和测试参数。大家可以在GitHub或我的网站上找到这些代码。

选择屏幕共享

录音器的选择也大有不同。Chrome和Edge允许在全显示窗口、应用程序窗口或浏览器标签中选择录音器。Firefox不允许用浏览器标签选择。而Safari对上述三种方式都不支持,只让你选择当前显示。

从下面这些图中你能看到各用户界面的差异。

Chrome

版本:84

可选项:全显示、窗口、标签

在Jitsi上用getDisplayMedia录制本地音频

Edge

版本:84

可选项:全显示、窗口、标签

在Jitsi上用getDisplayMedia录制本地音频

火狐(firefox)

版本:77

可选项:全显示、窗口

在Jitsi上用getDisplayMedia录制本地音频

Safari

版本:13.1

可选项:当前显示

在Jitsi上用getDisplayMedia录制本地音频

在Jitsi上用getDisplayMedia录制本地音频

Chrome和Edge在窗口边缘内侧会显示一个蓝色的高亮框,以表明标签页正在被共享。

displaySurface选择约束无效

getDisplayMedia API包含一个displaySurface选项,用于在桌面显示、窗口、应用程序或浏览器标签之间进行选择。而对我来说,我只想记录我的Jitsi Meet标签,所以我就想是否可以通过限制部分选项来简化用户界面。采用约束应该就可以了。

然而规范中说:

“用户代理必须让终端用户每次都从所有可选项中来选择共享哪个页面,严禁使用约束来限制选择。”

事实上,不像getUserMedia,“约束在 getDisplayMedia 中的作用与在 getUserMedia 中不同。它们不帮助发现,用户选择后才能应用它。(原文)

这意味着在实践中这些约束毫无意义。想进一步了解的话大家可以去看规范,但基本上这些约束不能起到效果,所以使用它们也没有什么意义。你不能在选择页面启用enumerateDevices(),也不能寻找设备变化事件。

(说句题外话:或许这诸多限制是Google Hangouts仍使用自己的扩展机制,不用getDisplayMedia的原因。)

分帧器

有了getUserMedia,你可以应用视频分辨率和帧率约束。视频分辨率只在捕获后调整视频的大小。如果想减少周期,你也可以降低帧率。事实上,有很多屏幕共享的WebRTC视频会议服务会根据共享的内容,八帧率降到10或更小,以减少占用CPU。

用户手势要求

火狐和Safari需要一个用户手势(比如点击按钮)才能访问getUserMedia。Chrome和Edge不需要。

把下面的代码粘贴到JavaScript控制台中,亲自体验一下吧!

https://webrtchacks.com/wp-content/uploads/2020/06/pasted-image-0-4.png

iFrame权限

在codepen中进行测试后,我发现iFrames也有限制。火狐和Safari如果没有特别允许权限就不能运行,他们的具体权限也不同。

火狐需要的权限代码是