作者:Youenn Fablet,Jon Lee
原文链接:https://webkit.org/blog/7763/a-closer-look-into-webrtc/
原标题:A Closer Look Into WebRTC
我们最近宣布了High Sierra系统和iOS 11系统中的Safari 11支持WebRTC。今天,我们想要更详细地讲讲我们的实现,以及提供一些如何将WebRTC加入你的网站的建议。
使用WebRTC和媒体捕捉的网站能够获得并广播非常隐私的个人信息。用户必须非常的信任这个网站,认为网站会合规合理地使用他们的影像。WebKit要求网站必须达到指定的规定,以确保其用户隐私的安全。另外,Safari会在使用摄像头、麦克风等捕捉设备的时候提示用户,用户可以控制网站对这些捕捉设备权限。对于开发者来说,在他们的app中,RTCPeerConnection和RTCDataChannel在任何网页视图中都可以使用,但是Safari暂时还限制摄像头和麦克风的权限。
开发菜单
Safari技术预览版34版展示了各种flag可以让测试WebRTC网站的工作变得更简单,或者可以通过Develop > WebRTC子页面中将Safari整合到你的连续集成系统:
我们会在下文中一个一个分析这些flag,并且解释它们如何能在开发过程中对你起到帮助作用。
另外,WebKit会在系统日志中记录WebRTC数据,其中包含了SDP请求和应答,ICE候选,WebRTC数据,以及流入和流出的视频帧计数器。
媒体捕捉的安全来源政策
想要获取捕捉设备权限的网站必须要满足两个要求。
首先,文件要求摄像头和麦克风的请求必须是来于HTTPS域名的网站。因为在你进行本地开发和测试时,这项要求会成为一个负担,所以你可以通过在Develop > WebRTC菜单中勾选“Allow Media Capture on Insecure Sites”这一项以跳过HTTPS限制。
第二,当一个子帧请求使用媒体捕捉设备时,领导主帧的帧链也必须来自于从同一个安全来源。用户可能不会分辨出子帧的第三方来源与主帧的差别,所以这条要求可以避免用户在没弄清谁在请求的时候就授予权限。
模拟捕捉设备
在Develop > WebRTC菜单中,你可以选择“Use Mock Capture Devices”来使用一个模拟设备来替换实际的捕捉设备。像下图一样,模拟设备会循环一段bip-bop AV流。当用来做输入流的时候,模拟设备的可预测数据使其评估流媒体回放的表现变得更加简单。
在连续集成系统中,模拟对运行自动测试也十分的有用。如果你正在使用一个模拟设备并且想要避开getUserMedia弹出的提示,你可以通过Safari浏览器中的Preferences… > Websites面板,将摄像头和麦克风政策设定成“Allow”来实现。
ICE候选限制
在WebRTC连接的早期阶段会进行ICE候选项来确认两个对等端之间所有可能的网络通道。为了实现这一点,WebKit必须将每一个对等端的ICE候选项展示给网站,这样它们才能够进行交换。ICE候选项包括IP地址,并且需需要注意的是这些是主机IP地址,可以被用来做跟踪。
在多种网络拓扑结构中,主机ICE候选项不需要被用来进行连接。服务器反向以及TURN的ICE候选项通常已经足够用来保障连接,不管是用来交换视频或者水机数据的。在没有给捕捉设备授权的时候,WebKit只会展示服务器反向和TURN ICE候选项,展示网站已经获得的IP地址。当权授予权限之后,WebKit会展示主机ICE候选,将连接成功率最大化。
一些测试页面可能会假定主机ICE候选的可用性。为了测试这项,在Develop > WebRTC菜单中打开“Disable ICE Candidate Restrictions”选项,然后刷新网页。
过时的WebRTC和媒体流API
随着WebRTC标准的发展,RTCPeerConnection API也在各种方面都日益发展。最开始这个API是基于回调的,现在已经变成了完全基于承诺的。API最初聚焦于MediaStream,现在转为专注于MediaStreamTrack。
在STP 34中,我们默认将传统WebRTC API关掉,并且计划将Safari 11推到 macOS High Sierra和iOS 11上的时候去掉这些API。保留这些过时的API值会限制我们在WebRTC方面的发展速度。任何想要支持Safari的网站也需要做一些其他的调整,所以现在是一个摆脱这些过时API的好时机。现有的一些网站可能还依赖于这些传统API,你可以在Develop > WebRTC菜单中将“Enable Lagacy 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 getLocalStreams();
// Switch to getReceivers, and look at RTCRtpReceiver.track
sequence 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 createOffer(RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback, optional RTCOfferOptions options);
// Update to promise-only version of setLocalDescription
Promise setLocalDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
// Update to promise-only version of createAnswer
Promise createAnswer(RTCSessionDescriptionCallback successCallback, RTCPeerConnectionErrorCallback failureCallback);
// Update to promise-only version of setRemoteDescription
Promise setRemoteDescription(RTCSessionDescriptionInit description, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
// Update to promise-only version of addIceCandidate
Promise addIceCandidate((RTCIceCandidateInit or RTCIceCandidate) candidate, VoidFunction successCallback, RTCPeerConnectionErrorCallback failureCallback);
};
很多网站通过开源adapter.js项目来填补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;
获取捕捉流
Safari允许用户获得对某个网站上你捕捉设备权限的完全控制。
首先,在getUserMedia第一次被调用时,用户会被提示对网站使用捕捉设备进行授权。不像其他浏览器那样,Safari不会要求用户选择特定的设备,取而代之的是是对特定类型的所有设备进行授权,比如说所有摄像头或者麦克风。这可以减少需要多次授权时用户的烦躁感,并且可以防止用户养成看也不看就点“允许”的习惯。一个常见的例子是可以在iOS设备上切换前置和后置摄像头。getUserMedia中会返回一个满足要求的设备,随后getUserMedia调用同类型的设备就可以避免给用户再次弹出一个额外的提醒。如果你想要允许用户切换到不同的设备,一定要确保你给用户提供了一个UI来做这些。
第二,用户可以通过设置来决定每次都允许或者拒绝摄像头和麦克风的权限申请。
第三,一旦某个网站给一个设备创建了MediaStream,会在Safari UI和系统菜单栏上出现一个图标,表示正在有捕捉设备正在使用。用户可以点击这个图标来终止摄像头和麦克风的工作。这里WebKit会发送静默的音频和全黑的视频帧,而且你的网站可以通过检查MediaStreamTrack中是mute还是unmute来展示合适的UI。
最终,为了避免发生意外捕捉的情况,WebKit只允许一次只有一个标签页能够进行视频或者音频的捕捉。正在使用捕捉设备的标签页会看到他们的MediaStreamTrack被静音并且在新标签页获得权限之后,这个标签页会收到mute事件。
指纹
navigator.mediaDevices.enumerateDevices展示了可用的捕捉设备,甚至在权限没有被许可的情况下也可以向网站发出请求。对于那些有自定义摄像头和麦克风设置的用户来说,这可以加到用户指纹表面(fingerprinting surface)中。当权限没有被允许或者被明确拒绝的时候,WebKit会通过返回一个默认设备清单(可能与真实的设备完全没有关系)来避免暴露额外的信息。一旦权限被授予了,清单上的全部设备和他们的标签都会变成可用状态。
媒体捕捉和自动播放视频
在上一篇文章中我们已经讨论了macOS和iOS系统中视频自动播放政策的改变。我们调整了两个系统中的政策以适应WebRTC应用,WebRTC希望自动播放流入媒体流要包含音频。需要进行下面这些改变:
基于MediaStream的媒体在网页准备好捕捉的情况下会自动播放
基于MediaStream的媒体在网页准备好播放音频的时候回自动播放。依旧需要一个用户收拾来开启音频播放
WebRTC是一项可以有多种应用的强大技术,我们都知道越大的能力伴随着越大的责任。想要设计WebRTC应用就必须从一开始就有着清晰的思路。CPU,内存和网络的限制都会严重影响用户的体验度。这个问题应该得到网页引擎和网页应用的同时处理。在网页应用端,现在已经有很多机制可以用来解决这个问题:选用合适的视频分辨率和帧速率,选择正确的视频codec,使用CVO,在源处静音音轨,以及在客户端处监控WebRTC数据。