Safari上使用WebRTC指南
盡管Apple在2017年的WWDC上宣布加入WebRTC支持,但仍然沒有看到Apple在支持WebRTC上更深入的舉動,尤其是其不只支持VP8更加強了這種擔憂。
文 / Chad Phillips
譯 / 元寶
原文:https://webrtchacks.com/guide-to-safari-webrtc/
自Apple首次向Safari添加WebRTC支持以來,已有一年多的時間了。鑒于WebRTC的差異和局限性,如何最好地開發Safari的WebRTC應用程序仍然存在許多問題。Chad是長期開源人員,也是FreeSWITCH產品的貢獻者。他自2015年以來一直參與WebRTC的開發工作。他最近推出了MoxieMeet,一個在線體驗活動的視頻會議平臺,在那里他擔任首席技術官,并為這篇文章將展示他許多見解。
?
Safari和WebRTC在野外。由Flickr用戶Curious Expeditions(CC BY-NC-SA 2.0)添加到Mountain Lion攻擊鹿 - 動物標本的照片
在2017年6月,蘋果成為最后一個發布WebRTC支持的主要供應商,為平臺的互操作性鋪平了(仍然坎坷的)道路。
然而,一年多以后,我對開發人員仍然缺乏可用于將WebRTC應用程序與Safari / iOS集成的指南感到驚訝。除了Webkit團隊的一些帖子之外,還有一些分散的StackOverflow問題,從WebRTC的Webkit bug報告中收集到的知識,以及這些網站上得的一些帖子,我真的沒有看到很多可用的支持。這篇文章試圖開始糾正這一差距。
我花了很多個月的努力將WebRTC集成到Safari中,用于非常復雜的視頻會議應用程序。我的大部分時間花在了iOS工作上,盡管下面的一些指針也適用于MacOS上的Safari。
這篇文章假設您在實施WebRTC方面有一定的經驗——這并不是初學者的方法,而是有經驗的開發人員指導他們平滑的將他們的應用程序與Safari / iOS集成的過程。在適當的情況下,我將指出Webkit bug跟蹤器中提交的相關問題,以便您可以將您的聲音添加到這些討論中,以及其他一些信息豐富的帖子中。?
為了在我的應用程序中聲明iOS支持,我做了大量探索,希望下面的知識將使您的旅程更加順暢!
首先是一些好消息
第一,好消息是:
蘋果目前的實施相當穩固
對于簡單的1-1音頻/視頻通話,集成非常簡單
讓我們來看看一些需求和問題所在。
一般準則和煩惱
使用當前的WebRTC規范
?
如果您是從頭開始構建應用程序,我建議使用當前的WebRTC API規范(它經歷了幾次迭代)。以下資源在這方面很棒:
https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API
https://github.com/webrtc/samples
對于那些運行具有較舊WebRTC實施的應用程序的人,我建議您盡可能升級到最新規范,因為iOS的下一個版本默認禁用舊版API。特別是,最好避免使用傳統的addStream API,這使得操作流中的軌道變得更加困難。
有關此問題的更多背景信息:https://blog.mozilla.org/webrtc/the-evolution-of-webrtc/
iPhone和iPad有獨特的規則 - 測試兩者
?
由于iPhone和iPad有不同的規則和限制,特別是在視頻方面,我強烈建議您在兩臺設備上測試您的應用程序。從iPhone開始全面工作可能更聰明,這似乎比iPad有更多限制。
更多背景信息:https://webkit.org/blog/6784/new-video-policies-for-ios
讓iOS瘋狂開始吧
您可能只需要將應用程序在iOS上運行即可。如果沒有,現在就出現了壞消息:iOS實現有一些相當令人抓狂的錯誤/限制,特別是在多方會議電話等更復雜的情況下。
iOS上的其他瀏覽器缺少WebRTC集成
WebRTC API尚未向使用WKWebView的IOS瀏覽器公開。實際上,這意味著您的基于Web的WebRTC應用程序僅適用于iOS上的Safari,而不適用于用戶可能安裝的任何其他瀏覽器(例如Chrome),也不適用于Safari的“應用程序內”版本。
為避免用戶混淆,如果他們嘗試在除Safari之外的其他瀏覽器/環境中打開您的應用,您可能希望包含一些有用的用戶錯誤消息。
相關問題:
https://bugs.webkit.org/show_bug.cgi?id=183201
https://bugs.chromium.org/p/chromium/issues/detail?id=752458
沒有beforeunload事件,請使用pagehide
根據這個Safari事件文檔,不推薦使用“unload”事件,并且已在Safari中完全刪除了 “beforeunload”事件。因此,如果您正在使用這些事件,例如,為了處理調用清理,您將需要重構代碼,以在Safari上使用 “pagehide”事件。
/**
?*?iOS?doesn't?support?beforeunload,?use?pagehide?instead.
?*?NOTE:?I?tried?doing?this?detection?via?examining?the?window?object
?*???????for?onbeforeunload/onpagehide,?but?they?both?exist?in?iOS,?even
?*???????though?beforeunload?is?never?fired.
?*/
var?iOS?=?['iPad',?'iPhone',?'iPod'].indexOf(navigator.platform)?>=?0;
var?eventName?=?iOS???'pagehide'?:?'beforeunload';
window.addEventListener(eventName,?function?(event)?{
??//?Do?the?work...
});
來源:
https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe
獲取和播放媒體
playsinline屬性
第一步是將所需的“playsinline”屬性添加 到您的視頻標簽,這允許視頻開始在iOS上播放。所以這:
????????<video?id="video-tag"?autoplay></video>
變成這樣:
??<video?id="video-tag"?autoplay?playsinline></video>
“playsinline”最初只是iOS上Safari的一項要求,但現在你可能需要在某些情況下在Chrome中使用它 - 請參閱https://github.com/webrtc/samples/issues/929
自動播放規則
接下來,您需要了解有關自動播放音頻/視頻的Webkit WebRTC規則。主要規則是:
如果網頁已經捕獲,MediaStream支持的媒體將自動播放。
如果網頁已播放音頻,MediaStream支持的媒體將自動播放
需要用戶手勢來啟動任何音頻回放 - WebRTC或其他。
這對于視頻通話的常見用例來說是個好消息,因為您很可能已經獲得用戶使用麥克風/攝像頭的許可,這符合第一條規則。請注意,這些規則與MacOS和iOS的基本自動播放規則一起使用,因此也很好地了解它們。
相關的webkit帖子:
https://webkit.org/blog/7763/a-closer-look-into-webrtc
https://webkit.org/blog/7734/auto-play-policy-changes-for-macos
https://webkit.org/blog/6784/new-video-policies-for-ios
沒有低/有限的視頻分辨率
測試常見的視頻分辨率和Safari / iOS中的結果
在WebRTC兼容的瀏覽器中訪問https://jsfiddle.net/thehunmonkgroup/kmgebrfz/15/(或webrtcHack的WebRTC-Camera-Resolution項目),可以快速分析測試設備/瀏覽器支持的常用分辨率組合。您會注意到在MacOS和iOS上的Safari中,沒有任何可用的低視頻分辨率,例如行業標準QQVGA或160×120像素。這些小分辨率對于提供縮略圖大小的視頻非常有用 - 例如,想想Google Hangouts調用中的用戶幻燈片。
現在,您可以發送對等連接中最低可用原始分辨率的任何內容,并讓接收器的瀏覽器縮小視頻,但是對于在網格/ SFU場景中具有較低速度的互聯網的用戶,您將面臨使下載帶寬飽和的風險。
我通過限制發送視頻的比特率來解決這個問題,這是一個相當快速和低端的妥協辦法。另一個需要更多工作的解決方案是在將應用程序中的視頻流傳遞給對等連接之前對其進行縮減,盡管這會導致客戶端的設備花費一些CPU周期。
示例代碼:
https://webrtc.github.io/samples/src/content/peerconnection/bandwidth/
新的getUserMedia()請求會終止現有的流跟蹤
Apple的WebRTC實現僅允許一次捕獲一個getUserMedia
如果您的應用程序從多個“getUserMedia()”請求中獲取媒體流,則可能會出現iOS問題。從我的測試中,這個問題可以總結如下:如果“getUserMedia()”請求在先前請求的媒體類型“getUserMedia()”,先前請求媒體軌道的“靜音” 屬性設置為true,并沒有以編程方式取消靜音。數據仍然會通過對等連接發送,但對于軌道靜音的另一方來說沒什么用處!此限制是iOS上當前預期的行為。
我能夠通過以下方式成功解決它:
在我的應用程序生命周期的早期抓取全局音頻/視頻流
使用MediaStream。clone(),MediaStream。addTrack(),MediaStream。removeTrack() 用于從全局流創建/操作其他流,而無需再次調用getUserMedia()。
/**
?*?Illustrates?how?to?clone?and?manipulate?MediaStream?objects.
?*/
function?makeAudioOnlyStreamFromExistingStream(stream)?{
??var?audioStream?=?stream.clone();
??var?videoTracks?=?audioStream.getVideoTracks();
??for?(var?i?=?0,?len?=?videoTracks.length;?i?<?len;?i++)?{
????audioStream.removeTrack(videoTracks[i]);
??}
??console.log('created?audio?only?stream,?original?stream?tracks:?',?stream.getTracks());
??console.log('created?audio?only?stream,?new?stream?tracks:?',?audioStream.getTracks());
??return?audioStream;
}
function?makeVideoOnlyStreamFromExistingStream(stream)?{
??var?videoStream?=?stream.clone();
??var?audioTracks?=?videoStream.getAudioTracks();
??for?(var?i?=?0,?len?=?audioTracks.length;?i?<?len;?i++)?{
????videoStream.removeTrack(audioTracks[i]);
??}
??console.log('created?video?only?stream,?original?stream?tracks:?',?stream.getTracks());
??console.log('created?video?only?stream,?new?stream?tracks:?',?videoStream.getTracks());
??return?videoStream;
}
function?handleSuccess(stream)?{
??var?audioOnlyStream?=?makeAudioOnlyStreamFromExistingStream(stream);
??var?videoOnlyStream?=?makeVideoOnlyStreamFromExistingStream(stream);
??//?Do?stuff?with?all?the?streams...
}
function?handleError(error)?{
??console.error('getUserMedia()?error:?',?error);
}
var?constraints?=?{
??audio:?true,
??video:?true,
};
navigator.mediaDevices.getUserMedia(constraints).
????then(handleSuccess).catch(handleError);
來源:https://gist.github.com/thehunmonkgroup/2c3be48a751f6b306f473d14eaa796a0
有關更多內容,請參閱:https://developer.mozilla.org/en-US/docs/Web/API/MediaStream 和
https://bugs.webkit.org/show_bug.cgi?id = 179363
管理媒體設備
媒體設備ID在頁面重新加載時更改
許多應用程序包括支持用戶選擇音頻/視頻設備。這最終歸結為將“deviceId”作為約束傳遞給“getUserMedia()”。
不幸的是,作為開發人員,作為Webkit安全協議的一部分,在每個新頁面加載時為所有設備生成隨機“deviceId”。這意味著,與其他平臺不同,您不能簡單地將用戶選定的“deviceId”填充到持久存儲中以供將來重用。
我發現這個問題的最簡潔的解決方法是:
存放兩個設備“deviceId” 和設備。 用戶選擇的設備的標簽
對于最終將“deviceId”傳遞給“getUserMedia()”的任何代碼工作流:
嘗試使用保存的“deviceId”
如果失敗,請再次枚舉設備,并嘗試 ?從保存的設備標簽中查找“deviceId”。
相關說明:Webkit通過僅在用戶授予設備訪問權限后公開用戶的實際可用設備來進一步防止指紋識別。實際上,這意味著您需要在 ?調用“enumerateDevices()”之前進行 “getUserMedia()” 調用 。
**
?*?Illustrates?how?to?handle?getting?the?correct?deviceId?for
?*?a?user's?stored?preference,?while?accounting?for?Safari's
?*?security?protocol?of?serving?a?random?deviceId?per?page?load.
?*/
//?These?would?be?pulled?from?some?persistent?storage...
var?storedVideoDeviceId?=?'1234';
var?storedVideoDeviceLabel?=?'Front?camera';
function?getDeviceId(devices)?{
??var?videoDeviceId;
??//?Try?matching?by?ID?first.
??for?(var?i?=?0;?i?<?devices.length;?++i)?{
????var?device?=?devices[i];
????console.log(device.kind?+?":?"?+?device.label?+?"?id?=?"?+?device.deviceId);
????if?(deviceInfo.kind?===?'videoinput')?{
??????if?(device.deviceId?==?storedVideoDeviceId)?{
????????videoDeviceId?=?device.deviceId;
????????break;
??????}
????}
??}
??if?(!videoDeviceId)?{
????//?Next?try?matching?by?label.
????for?(var?i?=?0;?i?<?devices.length;?++i)?{
??????var?device?=?devices[i];
??????if?(deviceInfo.kind?===?'videoinput')?{
????????if?(device.label?==?storedVideoDeviceLabel)?{
??????????videoDeviceId?=?device.deviceId;
??????????break;
????????}
??????}
????}
????//?Sensible?default.
????if?(!videoDeviceId)?{
??????videoDeviceId?=?devices[0].deviceId;
????}
??}
??//?Now,?the?discovered?deviceId?can?be?used?in?getUserMedia()?requests.
??var?constraints?=?{
????audio:?true,
????video:?{
??????deviceId:?{
????????exact:?videoDeviceId,
??????},
????},
??};
??navigator.mediaDevices.getUserMedia(constraints).
????then(function(stream)?{
??????//?Do?something?with?the?stream...
????}).catch(function(error)?{
??????console.error('getUserMedia()?error:?',?error);
????});
}
function?handleSuccess(stream)?{
??stream.getTracks().forEach(function(track)?{
????track.stop();
??});
??navigator.mediaDevices.enumerateDevices().
????then(getDeviceId).catch(function(error)?{
??????console.error('enumerateDevices()?error:?',?error);
????});
}
//?Safari?requires?the?user?to?grant?device?access?before?providing
//?all?necessary?device?info,?so?do?that?first.
var?constraints?=?{
??audio:?true,
??video:?true,
};
navigator.mediaDevices.getUserMedia(constraints).
??then(handleSuccess).catch(function(error)?{
????console.error('getUserMedia()?error:?',?error);
??});
來源:
https://gist.github.com/thehunmonkgroup/197983bc111677c496bbcc502daeec56
相關問題:
https://bugs.webkit.org/show_bug.cgi?id = 179220
相關文章:
https://webkit.org/blog/7763/a-closer-look-into-webrtc
揚聲器選擇不受支持
Webkit尚不支持“HTMLMediaElement.setSinkId()”,這是用于將音頻輸出分配給特定設備的API方法。如果您的應用程序包含對此的支持,則需要確保它可以處理缺少基礎API支持的情況。
/**
?*?Illustrates?methods?for?testing?for?the?existence?of?support
?*?for?setting?a?speaker?device.
?*/
//?Check?for?the?setSinkId()?method?on?HTMLMediaElement.
if?(setSinkId?in?HTMLMediaElement.prototype)?{
??//?Do?the?work.
}
//?...or...
//?Check?for?the?sinkId?property?on?an?HTMLMediaElement?instance.
if?(typeof?element.sinkId?!==?'undefined')?{
??//?Do?the?work.
}
來源:
https://gist.github.com/thehunmonkgroup/1e687259167e3a48a55cd0f3260deb70
相關問題:
https://bugs.webkit.org/show_bug.cgi?id = 179415
PeerConnections和Calling
當心,沒有VP8支持
雖然W3C規范明確規定要實施對VP8視頻編解碼器(以及H.264編解碼器)的支持,但蘋果迄今為止選擇不支持它。遺憾的是,這不是技術問題,因為libwebrtc包含VP8支持,而Webkit主動禁用它。
所以在這個時候,我在各種場景中實現最佳互操作性的建議是:
多方MCU - 確保H.264是受支持的編解碼器
多方SFU - 使用H.264
多方網格和點對點 - 祈禱每個人都可以協商一個共同的編解碼器
我說最好的互操作,因為雖然這會讓你走很遠的路,但它不會一帆風順。例如,Chrome for Android尚不支持軟件H.264編碼。在我的測試中,許多(但不是全部)Android手機都采用硬件H.264編碼,但那些缺少硬件編碼的手機在Chrome中不能用于Android。
相關錯誤報告:
https://bugs.webkit.org/show_bug.cgi?id=167257
https://bugs.webkit.org/show_bug.cgi?id=173141
https://bugs.chromium.org/p/chromium/issues/detail?id=719023
僅發送/接收流
如前所述,iOS不支持舊版WebRTC API。但是,并非所有瀏覽器實現都完全支持當前規范。在撰寫本文時,一個很好的事例是創建一個僅發送音頻/視頻對等連接。iOS不支持舊版 RTCPeerConnection.createOffer()選項offerToReceiveAudio /offerToReceiveVideo,以及當前穩定Chrome不支持RTCRtpTransceiver 默認規格。
其他更深奧的錯誤和限制
當然,你可以點擊的其他一些極端案例似乎有點超出了這篇文章的范圍。但是,一個優秀的資源應該是Webkit問題隊列,你可以只針對與WebRTC相關的問題進行過濾:
https://bugs.webkit.org/buglist.cgi?component = WebRTC&list_id = 4034671&product = WebKit&resolution = -
請記住,Webkit / Apple的實現還很年輕
它仍然缺少一些功能(如上面提到的揚聲器選擇),而且在我的測試中,它的穩定性不如GoogleChrome中更成熟的實現。
還有一些主要的錯誤- 捕獲音頻在iOS 12 Beta發布周期的大部分時間內完全被破壞(謝天謝地,他們最終修復了Beta 8)。
蘋果對WebRTC作為平臺的長期承諾尚不清楚,特別是因為除了基本支持之外,他們還沒有發布有關它的更多信息。例如,前面提到的缺乏VP8的支持對于他們遵守W3C規范的意圖是令人不安的。
在考慮瀏覽器原生實現與本地應用程序時,這些是值得考慮的事情。目前,我持謹慎樂觀的態度,并希望他們對WebRTC的支持將繼續下去,并擴展到iOS上的其他非Safari瀏覽器。
總結
以上是生活随笔為你收集整理的Safari上使用WebRTC指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 宋利:许多高手并未参加MSU评测
- 下一篇: 张霖峰:AV1和VVC的格局将在2023