企鵝輔導H5直播點播實踐

前言

企鵝輔導是騰訊推出的針對小學、初中、高中課外的在線學習服務平臺。近期,企鵝輔導爲了方便用戶更加快速便捷地上課,新增了移動端H5的上課形式。本文主要介紹了H5「實時直播」和「點播」的選型,以及開發過程當中遇到的問題和解決方案。html

需求背景

企鵝輔導的上課功能是產品較爲核心的功能,而原有上課方式只有如下兩種:APP上課/PC瀏覽器上課。react

  • APP上課: 環境要求不高,拿起手機app便可體驗,首次上課的須要先下載App。
  • PC瀏覽器上課: 登入網址便可上課,可是上課環境要求必須在電腦前。

如今多了一種上課方式和載體 —— 移動端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音視頻踩坑點

因爲移動端H5除了系統及版本(IOS和安卓)各不相同、瀏覽器環境(微信、手Q和自帶瀏覽器)也比PC複雜不少。加之WebRTC在移動端兼容性問題,從ios11.1.2到最新的12.4表現也存在差異。所以特地把一些踩過的主要坑作下總結。

  1. IOS 12.3開始須要支持unifiedplan 問題緣由:WebRTC SDP標準 個瀏覽器廠商逐步採用unifiedplan,而蘋果下plan-b沒法使用。 解決方案:升級你的騰訊雲WebRTC SDK至3.4.1,這部分修改了本來默認plan-b爲unifiedplan,且補充unifiedplan路徑走到的對應RTCUtils的API。
  2. WebRTC 在IOS非safari下,存在2分鐘斷流。 問題緣由:IOS非safari的app(微信,手Q)的瀏覽器內核,沒法獲取PeerConnection的質量。關鍵在於獲取質量的既沒有成功的回調,也沒有失敗的回調。致使沒有2s上報質量,而後就會斷流。 解決方法:判斷ua爲IOS非safari的瀏覽器,強制補充失敗的回調。
  3. video標籤屬性的兼容性問題。 解決方法:H5下須要使用playsinline屬性,不然會自動全屏播放。屬性須要加-webkit前綴,x5前綴。autoplay可能無效,須要用戶操做事件觸發播放。遇到video標籤屬性都要case by case處理。部分兼容性可參考docs.qq.com/sheet/DTGp4…
  4. videoEVent觸發機制問題。 問題緣由: H5 視頻播放標準在各個平臺終端的實現不一致性,事件的觸發方式和結果會有差別。developer.mozilla.org/en-US/docs/…errortimeupdateloadloadedmetadataloadeddataprogressfullscreenplayplayingpauseendedseekingseekedresizevolumechange 解決方法:部分機器爲觸發播放後,才觸發loadmeatadata和loaddata。調整首幀時間爲可播放時間。
  5. IOS非unfiedplan,WebRTC更新流時沒法給video從新設置流。 解決方法:IOS12.3如下版本,當畫中畫從有到無,再從無到有,從新創建WebRTC鏈接。
  6. X5內核tcplayer點播,系統全屏須要屏蔽下載按鈕。 解決方法:找tcplayer同事,想x5內核團隊提供域名,便可屏蔽。
  7. video系統自帶control欄。 解決方法:<video controls >設爲false,本身用dom實現控制欄,兩個控制欄必備按鍵爲:靜音鍵,設置video.muted=true/false便可,另外一個全屏鍵,參考下面全屏方案以及requestfullscreen API 相關內容。
  8. WebRTC系統全屏在X5內核的內部實現路徑和MSE video不一致。 問題緣由:詢問X5內核同事,目前webrtc系統全屏和普通video的系統全屏,內部路徑不同。致使WebRTC系統全屏不完美,點擊返回鍵會退出頁面。關於總體全屏方案下面說明。

橫豎屏檢測及全屏方案

概念定義:屏幕全屏(系統全屏)or網頁全屏(僞全屏)

  • 屏幕全屏:是指在屏幕範圍內全屏,全屏後只有視頻畫面內容,看不到瀏覽器的地址欄等界面,這種全屏須要瀏覽器提供接口支持。
  • 網頁全屏:是指在網頁顯示區域範圍內全屏,全屏後仍能夠看到瀏覽器的地址欄等界面,一般狀況下網頁全屏是爲了應對瀏覽器不支持系統全屏而實現相似全屏的一種方式,因此又稱僞全屏。該全屏方式由 CSS 實現。

至於網頁全屏怎麼實現,這裏最簡單的方式:進入網頁全屏時,video設爲fixed,調整z-index值,control欄同理佔滿。在橫豎屏下處理稍有不一樣,對比屏幕和video的寬高比例,以寬或高設置爲100%,另外一邊按比例計算便可,最終目的是達到contain的填充效果。

requestFullScreen API

支持屏幕全屏的接口有兩種,一種稱爲 Fullscreen API,經過 Fullscreen API 進入屏幕全屏後的特色是,進入全屏後仍然能夠看到由 HTML CSS 組成的播放器界面。另外一種接口爲 webkitEnterFullScreen,該接口只能做用於 video 標籤,一般用於移動端不支持 Fullscreen API 的狀況,經過該接口全屏後,播放器界面爲系統自帶的界面。

在實際業務中,對於全屏優先執行video.webkitEnterFullscreen();(蘋果只支持這個API),當執行不成功則再執行video.webkitRequestFullScreen();

橫豎屏判斷,orientation檢測

這裏我使用了第三方檢測代碼: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是否斷開。最後根據回調結果基於不一樣的提示。

上課頁連接query參數控制及課程節切換

PC上課頁連接query參數

在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_idterm_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方法,思路以下:

1.利用history.replaceState修改連接參數。
2.將已經掛載在全局的播放器銷燬,好比tcplayer.dispose()。
3.經過disaptch觸發CustomEvent,在頁面根位置接收事件。
4.重置部分store的值、從新執行"獲取服務器時間","獲取課程信息"(設置緩存),"檢查購買"等方法。(只須要_重置影響節切換_的值和執行方法)
5.卸載相關的react組件,willunmount中須要①取消時間監聽②銷燬定時器③銷燬播放器實例和討論區實例
6.從新加載渲染局部react組件。
相關文章
相關標籤/搜索