苹果WebKit详细分析WebRTC实现
作者:Youenn Fablet,Jon Lee
原标题: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
Develop > WebRTC⼦页⾯中将Safari整合到你的连续集成系统:
Develop > WebR T C⼦页⾯
我们会在下⽂中⼀个⼀个分析这些flag,并且解释它们如何能在开发过程中对你起到帮助作⽤。
另外,WebKit会在系统⽇志中记录WebRTC数据,其中包含了SDP请求和应答,ICE候选,WebRTC数据,以及流⼊和流出的视频帧计数器。
媒体捕捉的安全来源政策
想要获取捕捉设备权限的⽹站必须要满⾜两个要求。
⾸先,⽂件要求摄像头和麦克风的请求必须是来于HTTPS域名的⽹站。因为在你进⾏本地开发和测试时,这项要求会成为⼀个负担,所以你可以Develop > WebRTC菜单中勾选“Allow Media Capture on Insecure Sites”这⼀项以跳过HTTPS限制。
通过在Develop > WebRTC
第⼆,当⼀个⼦帧请求使⽤媒体捕捉设备时,领导主帧的帧链也必须来⾃于从同⼀个安全来源。⽤户可能不会分辨出⼦帧的第三⽅来源与主帧的差别,所以这条要求可以避免⽤户在没弄清谁在请求的时候就授予权限。
模拟捕捉设备
在Develop > WebRTC
navigator标签Develop > WebRTC菜单中,你可以选择“Use Mock Capture Devices”来使⽤⼀个模拟设备来替换实际的捕捉设备。像下图⼀样,模拟设备会循环⼀段bip-bop AV流。当⽤来做输⼊流的时候,模拟设备的可预测数据使其评估流媒体回放的表现变得更加简单。
bip-bop模拟
在连续集成系统中,模拟对运⾏⾃动测试也⼗分的有⽤。如果你正在使⽤⼀个模拟设备并且想要避开getUserMedia弹出的提⽰,你可以通过Safari浏览器中的Preferences… > Websites⾯板,将摄像头和麦克风政策设定成“Allow”来实现。
候选限制
ICE候选限制
ICE
在WebRTC连接的早期阶段会进⾏ICE候选项来确认两个对等端之间所有可能的⽹络通道。为了实现这⼀点,WebKit必须将每⼀个对等端的ICE 候选项展⽰给⽹站,这样它们才能够进⾏交换。ICE候选项包括IP地址,并且需需要注意的是这些是主机IP地址,可以被⽤来做跟踪。
在多种⽹络拓扑结构中,主机ICE候选项不需要被⽤来进⾏连接。服务器反向以及TURN的ICE候选项通常已经⾜够⽤来保障连接,不管是⽤来交换视频或者⽔机数据的。在没有给捕捉设备授权的时候,WebKit只会展⽰服务器反向和TURN ICE候选项,展⽰⽹站已经获得的IP地址。当权授予权限之后,WebKit会展⽰主机ICE候选,将连接成功率最⼤化。
⼀些测试页⾯可能会假定主机ICE候选的可⽤性。为了测试这项,在Develop > WebRTC菜单中打开“D
isable ICE Candidate
Restrictions”选项,然后刷新⽹页。
过时的WebRTC和媒体流API
和媒体流API
过时的WebRTC
随着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 UserMedia
void getUserMedia(MediaStreamConstraints constraints, NavigatorUserMediaSuccessCallback
successCallback, NavigatorUserMediaErrorCallback errorCallback);
};
partial interface RTCPeerConnection {
// Switch to getSenders, and look ack
sequence getLocalStreams();
// Switch to getReceivers, and look ack
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 = ateOffer();
await pc.setLocalDescription(offer);
// send offer to the other party
...
典型的⾳频-视频WebRTC通话可以这样做:
var stream = UserMedia({audio: true, video: true});
var pc = new RTCPeerConnection();
var audioSender = pc.AudioTracks()[0], stream);
var videoSender = pc.VideoTracks()[0], stream);
var offer = ateOffer();
await pc.setLocalDescription(offer);
// send offer to the other party
...
基于MediaStreamTrack的API⼤部分处理⼯作在这个层⾯就已经完成了。举个例⼦,默认640×480分辨率的捕捉视频轨是并不好的。接着上⼀个例⼦,可以这样进⾏动态更改:
或者我们可以将视频静⾳,但是要保持⾳频⼀直运⾏:
获取捕捉流
Safari允许⽤户获得对某个⽹站上你捕捉设备权限的完全控制。
⾸先,在getUserMedia第⼀次被调⽤时,⽤户会被提⽰对⽹站使⽤捕捉设备进⾏授权。不像其他浏览器那样,Safari不会要求⽤户选择特定的设备,取⽽代之的是是对特定类型的所有设备进⾏授权,⽐如说所有摄像头或者麦克风。这可以减少需要多次授权时⽤户的烦躁感,并且可以防⽌⽤户养成看也不看就点“允许”的习惯。⼀个常见的例⼦是可以在iOS设备上切换前置和后置摄像头。getUserMedia中会返回⼀个满⾜要求的设备,随后getUserMedia调⽤同类型的设备就可以避免给⽤户再次弹出⼀个额外的提醒。如果你想要允许⽤户切换到不同的设备,⼀定要确保你给⽤户提供了⼀个UI来做这些。
第⼆,⽤户可以通过设置来决定每次都允许或者拒绝摄像头和麦克风的权限申请。
第三,⼀旦某个⽹站给⼀个设备创建了MediaStream,会在Safari UI和系统菜单栏上出现⼀个图标,表⽰正在有捕捉设备正在使⽤。⽤户可以点击这个图标来终⽌摄像头和麦克风的⼯作。这⾥WebKit会发送静默的⾳频和全⿊的视频帧,⽽且你的⽹站可以通过检查MediaStreamTrack中是mute还是unmute来展⽰合适的UI。
Safari UI和系统菜单栏
最终,为了避免发⽣意外捕捉的情况,WebKit只允许⼀次只有⼀个标签页能够进⾏视频或者⾳频的捕捉。正在使⽤捕捉设备的标签页会看到他们的MediaStreamTrack被静⾳并且在新标签页获得权限之后,这个标签页会收到mute事件。
指纹
候,WebKit会通过返回⼀个默认设备清单(可能与真实的设备完全没有关系)来避免暴露额外的信息。⼀旦权限被授予了,清单上的全部设备和他们的标签都会变成可⽤状态。
媒体捕捉和⾃动播放视频
在上⼀篇⽂章中我们已经讨论了macOS和iOS系统中视频⾃动播放政策的改变。我们调整了两个系统中的政策以适应WebRTC应⽤,WebRTC希望⾃动播放流⼊媒体流要包含⾳频。需要进⾏下⾯这些改变:
基于MediaStream的媒体在⽹页准备好捕捉的情况下会⾃动播放
基于MediaStream的媒体在⽹页准备好播放⾳频的时候回⾃动播放。依旧需要⼀个⽤户收拾来开启⾳频播放
WebRTC是⼀项可以有多种应⽤的强⼤技术,我们都知道越⼤的能⼒伴随着越⼤的责任。想要设计WebRTC应⽤就必须从⼀开始就有着清晰的思路。CPU,内存和⽹络的限制都会严重影响⽤户的体验度。这个问题应该得到⽹页引擎和⽹页应⽤的同时处理。在⽹页应⽤端,现在已经有很多机制可以⽤来解决这个问题:选⽤合适的视频分辨率和帧速率,选择正确的视频codec,使⽤CVO,在源处静⾳⾳轨,以及在客户端处监控WebRTC数据。