企鵝輔導是騰訊推出的針對小學、初中、高中課外的在線學習服務平臺。近期,企鵝輔導爲了方便用戶更加快速便捷地上課,新增了移動端H5的上課形式。本文主要介紹了H5「實時直播」和「點播」的選型,以及開發過程當中遇到的問題和解決方案。html
企鵝輔導的上課功能是產品較爲核心的功能,而原有上課方式只有如下兩種:APP上課/PC瀏覽器上課。react
如今多了一種上課方式和載體 —— 移動端H5上課。它的特色在於:分享連接便可進入指定課程指定節,易於傳播。無需求下載app,進入連接報名便可上課。主要用於快速體驗上課的場景ios
H5上課頁入口git
1.非APP環境下,原先H5課程詳情頁、支付成功頁,均提供入口進入H5上課頁,(目前入口灰度)。對於買了課的學生在不下載APP的狀況下,快捷進入輔導上課頁,當即體驗上課。github
2.經過羣裏指定分享連接,連接帶有課程ID和節ID了,便可自動進入指定節的直播課堂,或查看節的回放內容。節ID缺省狀況下,也會自動定位最近一節直播課。web
H5上課頁功能需求chrome
1.上課直播: 經過騰訊雲WebRTC直播(優先方案)以及hls直播(降級方案)兩種方式完成。 2.點播回放: 經過騰訊雲TcPlayer超級點播的進行hls播放。 3.討論互動: 基於impush,學生能發送和接收文案+表情。其他互動場景經過toast提示。 4.課程大綱: 查看課程每節的內容及時間,可在同一頁面直接切換節進行直播/回放。瀏覽器
其它需求 在PC Web 上課的業務邏輯基礎上,還有如下需求: 一、根據不一樣狀態展現不一樣封面。 二、橫豎屏檢測及播放、切換全屏播放。 三、網絡類型檢測及提示,wifi => 4G流量 暫停並提示等。 四、引導跳轉:①購買檢查引導跳轉購課。②引導APP更好體驗上課。緩存
WebRTC直播 降級hls直播bash
因爲上課直播的同時,還有討論區模塊以及其餘交互模塊(如舉手,答題等)。若是音視頻存在延遲學生用戶可以明顯感知,體驗會大打折扣。所以PlanA採用低延時的直播方案——WebRTC,時延能在1秒內。
但因爲WebRTC在移動端兼容性並不夠好,尤爲在蘋果機型上,須要IOS版本11.1.2以上才逐漸支持。所以須要一個兼容性覆蓋面廣的降級直播方案做爲PlanB,保證能兜底直播。
降級直播方案採用HLS協議直播方式,它在移動端H5有良好普遍的兼容性。但時延較高達到20秒以上。所以降級直播時,討論區的消息也順延相應的時間長度,且禁止發言功能。經過只讓學生單方面的接收互動消息,而不能發送消息的方式,來屏蔽低延時的感知。
這裏爲何不選用延遲比hls更少的其餘直播方式做爲降級方案呢? 緣由在於即使其餘直播方式延時更少(如flv可達到5-7秒),可是降級方案優先考慮兼容性,而再也不考慮延遲。即使延遲5-7秒仍會被感知,也須要禁言等單向信息流的方式了抹掉延遲感,延遲多少反而沒那麼重要。因此採用移動端支持性更好的HLS。
點播回放
點播方案採用騰訊雲點播服務,web側使用加密 HLS 的點播方案。因爲以前的在PC上經驗,使用了騰訊雲的超級點播播放器,目前這塊相比直播更加穩定。因爲防盜須要對加密點播資源解密,主要的工做就是token身份認證換取解密密鑰等鑑權處理。
直播接入 webrtc使用的是在線教育webrtc-live-player,依賴騰訊雲WebRTC SDK,須要支持到3.4.1以上(緣由IOS新版本已經支持unifiedPlan)。直播播放器接入,比較簡單。肯定容器dom,指定相關業務上報模塊。而後實例化,執行preconnect。
const opts = {
// 一些上報等配置項
};
this.webrtcPlayer = new WebRTCLivePlayer(opts);
this.preconnect();
複製代碼
這裏的preconnect,只是先創建webrtc的websocket信令鏈接,因爲webtc的usersig可能不會第一時間下發。預先建立信令鏈接,能夠節省信令建連的耗時。
當usersig準備好後,執行init,異步建立播放器成功後能夠設置播放器寬高。最後執行connet進入webrtc的房間成功後,交換SDP以及candidate創建ICE鏈接,等待視頻流後就能夠播放了。
this.webrtcPlayer.init(sig, roomId).then(() => {
// 設置播放器寬高
this.webrtcPlayer.setContainerStyle({
width: xxx,
height: xxx,
});
this.webrtcPlayer.connect();
});
複製代碼
固然接入webrtc播放器,實際業務層還有更多的事情要作,涉及流更新處理,視頻寬高變化更新佈局,重連機制,以及上報處理等。
降級Hls直播接入 基於TCPlayerLite,傳入m3u8混流地址,實例化播放器便可。初始化參數請參考:cloud.tencent.com/document/pr…。
TCPlayerLite 採用 H5<video>
和 Flash 相結合的方式來進行視頻播放,根據不一樣的播放環境,播放器會選擇默認最合適的播放方案。 在移動端須要使用m3u8,因爲咱們獲取混流地址業務接口提供rtmp,flv和hls地址,所以在移動端H5下傳入options地址中,要把rtmp和flv去掉只傳m3u8地址。
const player = new TcPlayer('tcplayer_container', {
"m3u8": "http://2157.liveplay.myqcloud.com/2157_358535a.m3u8",//舉例
"autoplay" : false,(備註:true只對大部分 PC 平臺生效)
"width":"100%",
"height":"100%",
"live":true,
"x5_player":true
});
複製代碼
爲了更好理解直播的步驟,本身梳理了直播的流程和架構圖。
騰訊雲TCPlayer點播接入
點播資源做爲學生用戶付費獲取的資源,天然不可能憑藉一個m3u8地址就能播放,必須是有加密的。所以點播的接入,除了須要點播資源fileId以外,還須要用戶的身份信息校驗用於視頻解密。
加密HLS請參考:cloud.tencent.com/document/pr… 裏面提供了兩個視頻播放方案。簡單來講都是用cookie生成token,經過業務的DK接口結果獲取解密密鑰DK。
方案1:經過 QueryString 傳遞身份認證token信息。
http://example.vod2.myqcloud.com/path/to/a/voddrm.token.ABC123.video.m3u8複製代碼
方案2:播放器在訪問 EXT-X-KEY 標籤所標識的 URL 時會帶上 Cookie。
但這裏有坑點:PC上咱們採用了方案2,同域的業務dk接口帶上cookie是沒有問題,可是在安卓H5下怎麼也播不了。抓包發現,在安卓下明明是同域的接口卻沒法攜帶任何cookie。查迴文檔才發現這麼一句。
看來咱們在移動端H5上咱們沒法採用和PC同樣簡單的方案二,可是方案一根據cookie生成token外,還要在播放地址參數,而咱們本來只有filedId和dk接口,而並不是用m3u8地址進行播放。 通過請教課堂的同事,原來Tcplayer提供一個文檔中沒有的參數HLStoken來傳遞token。
而這裏的token,在播放器請求/cgi-bin/qcloud/get_dk時攜帶token,這樣安卓瀏覽器上沒辦法攜帶cookie也不要緊。
this.player = new window.TCPlaye(this.elIDId, {
fileID, // 請傳入須要播放的視頻filID 必須
appID // 請傳入點播帳號的appID 必須
autoplay: false,
plugins: {
HLSToken: {
token, //根據cookie生成的token,這個參數文檔中並無。
},
},
});
複製代碼
因爲移動端H5除了系統及版本(IOS和安卓)各不相同、瀏覽器環境(微信、手Q和自帶瀏覽器)也比PC複雜不少。加之WebRTC在移動端兼容性問題,從ios11.1.2到最新的12.4表現也存在差異。所以特地把一些踩過的主要坑作下總結。
error
、timeupdate
、load
、loadedmetadata
、loadeddata
、progress
、fullscreen
、play
、playing
、pause
、ended
、seeking
、seeked
、resize
、volumechange
解決方法:部分機器爲觸發播放後,才觸發loadmeatadata和loaddata。調整首幀時間爲可播放時間。<video controls >
設爲false,本身用dom實現控制欄,兩個控制欄必備按鍵爲:靜音鍵,設置video.muted=true/false便可,另外一個全屏鍵,參考下面全屏方案以及requestfullscreen API 相關內容。至於網頁全屏怎麼實現,這裏最簡單的方式:進入網頁全屏時,video設爲fixed,調整z-index值,control欄同理佔滿。在橫豎屏下處理稍有不一樣,對比屏幕和video的寬高比例,以寬或高設置爲100%,另外一邊按比例計算便可,最終目的是達到contain的填充效果。
支持屏幕全屏的接口有兩種,一種稱爲 Fullscreen API,經過 Fullscreen API 進入屏幕全屏後的特色是,進入全屏後仍然能夠看到由 HTML CSS 組成的播放器界面。另外一種接口爲 webkitEnterFullScreen,該接口只能做用於 video 標籤,一般用於移動端不支持 Fullscreen API 的狀況,經過該接口全屏後,播放器界面爲系統自帶的界面。
在實際業務中,對於全屏優先執行video.webkitEnterFullscreen();
(蘋果只支持這個API),當執行不成功則再執行video.webkitRequestFullScreen();
。
這裏我使用了第三方檢測代碼:github.com/shrekshrek/… 這斷代碼除了能檢測橫豎屏外,還能檢測重力及水平儀角度等,而且作了多終端兼容處理。
使用方法很簡單:初始化監聽,當dir值發生變化即爲橫豎屏變化,隨即更新佈局便可。
const _orienter = new window.Orienter();
this.props.updateOrientation(_orienter.direction); //先判斷一次當前橫豎屏
_orienter.onOrient = function (obj) {
if (this.dir !== obj.dir) {
this.dir = obj.dir;
setTimeout(() => {
//延遲200ms更新佈局
this.props.updateOrientation(obj.dir);
}, 200);
}
};
//開始監聽橫豎屏
_orienter.on();
複製代碼
注意:這裏有設置200毫秒延時,緣由是當橫豎屏變化以後,取到的屏幕寬高仍然是「變化前」的屏幕寬高,當即更新佈局會有問題。所以延遲200毫秒再更新佈局。
當拿到需求和交互視覺稿後,會發現一節課的有多種的封面,且封面可能在一個頁面中隨時間發生來回變化。這一切都源於上課狀態的複雜,判斷的維度有三個:1.當前時間。2.老師端是否上課。3.是否有視頻流。 所以如何控制封面的變化,主要是維護和控制課堂狀態的store值,而後映射封面便可。
封面狀態設計:採用分級封面,狀態映射來控制。組封面覆蓋在子封面之上。 主封面組件:負責時間維度及CGI能夠判斷的大的課程狀態,單向流程,不會切換。 子封面組件:負責播放狀態的控制,根據老師上課狀態、流狀態控制,非單向流程,可能會來回切換。
在PC瀏覽器上課,通常都是wifi和有線上網,可是在移動端瀏覽器看直播和點播,除了wifi可能會消耗流量,需求是須要可以檢測當前的網絡狀態(流量模式/wifi模式)進行判斷。從而給用戶響應的提示,避免用戶在不知情的狀況下消耗流量。
經過瀏覽器自己navigator.connection.type
確實能夠判斷瀏覽器,但只有chrome支持移動端,其餘瀏覽器幾乎都不可用。
所以判斷網絡類型,仍是要依賴於app提供的能力,基於兩大app內瀏覽器,手Q和微信都有提供JS API判斷網絡類型,下面是代碼實現。
return new Promise((resolve, reject) => {
if (window.WeixinJSBridge) {
//微信檢測networktype
window.WeixinJSBridge.invoke('getNetworkType', {}, (e) => {
resolve({
isNetWorkFlow:
e.err_msg !== 'network_type:wifi' &&
e.err_msg !== 'network_type:fail',
isNetworkBroken: e.err_msg === 'network_type:fail',
});
});
}
if (window.mqq) {
//手Q檢測networktype
window.mqq.device.getNetworkInfo((res) => {
resolve({
isNetWorkFlow: res.type === 2 || res.type === 3 || res.type === 4,
isNetworkBroken: res.type === 0,
});
});
} else {
reject();
}
});
複製代碼
經過每3秒輪訊回調,除了判斷網絡類型是否爲流量isNetWorkFlow
,還判斷當前網絡isNetworkBroken
是否斷開。最後根據回調結果基於不一樣的提示。
在PC web項目中,每一個上課頁只對應一節課,連接的參數對應每節課的信息和當前課堂狀態。
https://fudao.qq.com/pc/webclass.html?term_id=2000010153&course_id=113536&lesson=0&lesson_id=93561&status=2&sub_termid=0複製代碼
course_id
和term_id
: 課程id和班級id lesson_id
: 節id status
:課堂狀態 0未開始、1直播中、2回放、三、生成回放中 sub_termid
:小班id
在PC瀏覽器,從課程任務頁跳到上課頁,連接上的query參數在上個頁面已經肯定,能夠當即拿到status參數(由時間計算),這樣有個好處就是,頁面加載能當即根據課堂狀態加載播放器和相應樣式,不須要等待CGI請求;而壞處是切換課程是要從新加載刷新頁面。
因爲H5上課頁包含了「課程大綱」,至關於把本來PC上的「課程任務頁」和「上課頁」集中在一個頁面,產品但願能達到如下要求: 1. 課程切換不刷新頁面。 2. 當連接上沒有lesson_id,能定位到最近的未開始的一節課。 3. 切換節後,分享的連接是能定位到當前節。
以上三點就是實現起來比較困難,緣由在於本來代碼中,有至關的多地方是從連接上一次性獲取參數,而且因爲分享機制,也須要在不刷新頁面修改連接參數,而且從新獲取連接參數,從新加載播放器以及字幕組件。實現一個switchLesson方法,思路以下: