深入了解 WebRTC

我们最近在上一篇WebKit 博客文章中宣布了 Safari 11 在 High Sierra 和 iOS 11 上对 WebRTC 的支持。今天,我们想深入探讨我们实现的更多细节,并提供一些关于为您的网站添加 WebRTC 支持的提示。

使用 WebRTC 和媒体捕获的网站可以获取和广播非常个人化的信息。用户必须明确授予网站信任,并假定他们的图片和声音被适当使用。WebKit 要求网站满足特定条件才能使用这些技术并保护其用户的隐私。此外,Safari 会向用户显示何时使用了他们的捕获设备,并为用户提供控制网站访问其捕获设备的方式。对于在其应用程序中使用 WebKit 的开发者而言,任何 Web 视图中都可使用 RTCPeerConnectionRTCDataChannel,但目前摄像头和麦克风的访问权限仅限于 Safari。

开发菜单

Safari 技术预览版 34 提供了各种标志,通过 开发 > WebRTC 子菜单,让您更轻松地测试 WebRTC 网站或将 Safari 集成到您的持续集成系统中。

WebRTC Menu

下面我们将逐一介绍这些标志,并解释它们如何帮助您进行开发。

此外,WebKit 会将 WebRTC 状态记录到系统日志中,包括 SDP 提议和应答、ICE 候选者、WebRTC 统计信息以及传入和传出视频帧计数器。

媒体捕获的安全源策略

希望访问捕获设备的网站需要满足两个限制。

首先,请求摄像头和麦克风的文档需要来自 HTTPS 域。由于这在本地开发和测试时可能会很繁琐,您可以通过在 开发 > WebRTC 菜单中勾选“允许不安全站点进行媒体捕获”来绕过 HTTPS 限制。

其次,当子帧请求媒体捕获设备时,通往主帧的帧链需要来自相同的安全源。用户可能无法识别子帧与主帧相关的第三方源,因此此限制避免了混淆用户关于其授权的对象。

模拟捕获设备

开发 > WebRTC 菜单中,您可以选择“使用模拟捕获设备”来用模拟设备替换真实捕获设备。模拟设备会循环播放一段 bip-bop 音视频流,如下图所示。当用作输入流时,模拟设备可预测的数据使其易于评估流媒体播放的各个方面,包括同步、延迟和输入设备的选择。

Bip-Bop AV Mock Loop

模拟设备还可用于在持续集成系统中运行自动化测试。如果您正在使用并且想要避免 getUserMedia 的提示,请通过 Safari 的 偏好设置… > 网站 面板,将网站的摄像头和麦克风策略设置为允许

ICE 候选者限制

ICE 候选者在 WebRTC 连接的早期阶段进行交换,以识别两个对等方之间所有可能的网络路径。为此,WebKit 必须向网站暴露每个对等方的 ICE 候选者,以便它们可以共享。ICE 候选者会暴露 IP 地址,特别是主机 IP 地址可用于跟踪。

然而,在许多网络拓扑中,主机 ICE 候选者对于建立连接并非必要。服务器反射和 TURN ICE 候选者通常足以确保连接,无论它是否用于交换视频或任意数据。在没有访问捕获设备权限的情况下,WebKit 仅暴露服务器反射和 TURN ICE 候选者,这些候选者暴露的 IP 地址可能已被网站收集。当授予访问权限时,WebKit 将暴露主机 ICE 候选者,这最大化了连接成功的机会并提高了效率。我们做出此例外是因为我们相信用户通过授予对其捕获流的访问权限,表达了对网站的高度信任。

某些测试页面可能假定主机 ICE 候选者的可用性。要测试此项,请从 开发 > WebRTC 菜单中打开“禁用 ICE 候选者限制”,然后重新加载页面。

传统 WebRTC 和媒体流 API

通过 WebRTC 标准化过程,RTCPeerConnection API 在各个方面逐步改进。该 API 最初基于回调,后来变为完全基于 Promise。最初侧重于 MediaStream 的 API 转向了 MediaStreamTrack。得益于WebKit WebRTC 团队的上游努力,RTCPeerConnection API 与这两个重大变化保持了一致。

我们已在 Safari 技术预览版 34 中默认关闭了传统 WebRTC API,并计划在 macOS High Sierra 和 iOS 11 中发布的 Safari 11 不包含这些 API。保留传统 API 限制了我们更快推进 WebRTC 的能力。任何希望为 Safari 带来支持的网站可能需要进行其他调整,因此现在正是摆脱这些传统 API 的最佳时机。现有网站可能仍依赖这些传统 API,您可以通过在 开发 > WebRTC 菜单中打开“启用传统 WebRTC API”来查看。

更精确地说,以下 API 仅在传统 API 开关打开时可用,并附有更新建议

partial interface Navigator {
    // Switch to navigator.mediaDevices.getUserMedia
    void getUserMedia(MediaStreamConstraints constraints, NavigatorUserMediaSuccessCallback successCallback, NavigatorUserMediaErrorCallback errorCallback);
};

partial interface RTCPeerConnection {
    // Switch to getSenders, and look at RTCRtpSender.track
    sequence<MediaStream> getLocalStreams();
    // Switch to getReceivers, and look at RTCRtpReceiver.track
    sequence<MediaStream> getRemoteStreams();

    // Switch to getSenders/getReceivers
    MediaStream getStreamById(DOMString streamId);
    // Switch to addTrack
    void addStream(MediaStream stream);
    // Switch to removeTrack
    void removeStream(MediaStream stream);

    // Listen to ontrack event
    attribute EventHandler onaddstream;

    // Update to promise-only version of createOffer
    Promise<void> createOffer(RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback, optional RTCOfferOptions options);
    // Update to promise-only version of setLocalDescription
    Promise<void> setLocalDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
    // Update to promise-only version of createAnswer
    Promise<void> createAnswer(RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback);
    // Update to promise-only version of setRemoteDescription
    Promise<void> setRemoteDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
    // Update to promise-only version of addIceCandidate
    Promise<void> addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
};

许多网站通过开源的 adapter.js 项目来 polyfill API 支持。更新到最新版本是弥补 API 差距的一种方式,但我们建议切换到规范中列出的 API。

以下是几个如何使用最新 API 的示例。典型的仅接收/类似网络研讨会的 WebRTC 呼叫可以这样完成

var pc = new RTCPeerConnection();
pc.addTransceiver('audio');
pc.addTransceiver('video');
var offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// send offer to the other party
...

而典型的音视频 WebRTC 呼叫可以这样完成

var stream = await navigator.mediaDevices.getUserMedia({audio: true, video: true});
var pc = new RTCPeerConnection();
var audioSender = pc.addTrack(stream.getAudioTracks()[0], stream);
var videoSender = pc.addTrack(stream.getVideoTracks()[0], stream);
var offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// send offer to the other party
...

基于 MediaStreamTrack 的 API 是有意义的,因为大部分处理都在此级别完成。假设捕获视频轨道的默认分辨率 640×480 不够好。继续上面的例子,动态更改它可以按如下方式完成

videoSender.track.applyConstraints({width: 1280, height: 720});

或者我们可能想静音视频但保持音频传输

videoSender.track.enabled = false;

哦等等,假设我们实际上想对当前视频轨道应用一些酷炫的滤镜效果,就像这个示例中一样。所有需要做的只是几个函数调用,而不需要任何 SDP 重新协商

videoSender.track.enabled = true;
renderWithEffects(video, canvas);
videoSender.replaceTrack(canvas.captureStream().getVideoTracks()[0]);

访问捕获流

Safari 允许用户完全控制网站对其捕获设备的访问。

首先,当首次调用 getUserMedia 时,系统会提示用户授予网站访问捕获设备的权限。然而,与其他浏览器不同,Safari 不要求用户选择特定设备;相反,提示会请求对特定类型的所有设备(如所有摄像头或麦克风)的访问权限。这减少了多次提示带来的疲劳,并可能避免训练用户总是点击“允许”。一个常见的案例是 iOS 设备在前后摄像头之间切换。getUserMedia 中解析的 Promise 会返回一个满足约束条件的设备,后续对相同设备类型调用 getUserMedia 将避免向用户呈现额外的提示。如果您希望允许用户切换到不同的设备,请务必提供相应的用户界面。

其次,用户可以通过 Safari 偏好设置决定始终允许或拒绝访问摄像头和麦克风。用户可以针对每个源进行此操作,甚至可以为所有网站设置通用策略。

第三,一旦网站为设备创建了 MediaStream,Safari UI 和系统菜单栏中就会出现图标,表明正在使用捕获设备。用户可以点击或轻触该图标以在流媒体传输中暂停摄像头和麦克风。此时 WebKit 将发送静音音频和黑色视频帧,您的网站可以通过监听 MediaStreamTrack 上的 muteunmute 事件来呈现适当的用户界面。

Active Capture Devices Icons

最后,为了避免意外捕获,WebKit 每次只允许一个标签页捕获视频或音频。当新标签页获得访问权限时,正在使用捕获设备的标签页将看到其 MediaStreamTrack 被静音并收到 mute 事件。

指纹识别

navigator.mediaDevices.enumerateDevices 暴露了可用的捕获设备列表,即使未授予访问这些设备的权限,网站也可以查询。对于具有自定义摄像头和麦克风设置的用户,这会增加用户的指纹识别面。当尚未请求访问权限或明确拒绝访问时,WebKit 通过返回一个可能与实际可用设备集不符的默认设备列表来避免暴露此附加信息。此外,根据规范,这些设备缺少 label。一旦授予访问权限,完整的设备列表及其标签将可用。

媒体捕获和自动播放视频

之前的文章中,我们讨论了 macOS 和 iOS 上视频自动播放策略的变化。我们调整了这两个平台上的策略,以适应 WebRTC 应用程序,在这些应用程序中,通常希望自动播放包含音频的传入媒体流。为了解决这些场景,同时保留当前自动播放规则的优势,已进行以下更改:

  • 如果网页正在捕获,则由 MediaStream 支持的媒体将自动播放。
  • 如果网页正在播放音频,则由 MediaStream 支持的媒体将自动播放。仍需要用户手势才能启动音频播放。

性能

WebRTC 是一个非常强大的功能,可以有许多应用。我们都知道,能力越大,责任越大。设计 WebRTC 应用程序需要从一开始就考虑效率。CPU、内存和网络都有局限性,会严重影响用户体验。这个问题应由 Web 引擎和 Web 应用程序共同解决。在 Web 应用程序端,已经有各种机制可用:选择正确的视频分辨率和帧率、选择正确的视频编解码器配置文件、使用 CVO、在源端静音轨道以及执行客户端 WebRTC 统计监控。

反馈

以上是我们对 WebRTC 和媒体捕获的深入探讨。我们随时欢迎您的反馈。请提交错误,发送邮件至 web-evangelist@apple.com,或在 Twitter 上@我们:@webkit