本文做者: IMWeb團隊 原文連接javascript
騰訊課堂是一款經過線上的直播與點播向用戶提供在線教育服務的產品,從 2014 年成立至今,已累計存儲了 250 萬個視頻,共 600 TB,累計時長 150 萬小時。以前一直採用的是騰訊視頻的方案,但使用的是 MP4 格式,用戶拿到了播放連接以後很容易盜版,因此趁着上雲的潮流,咱們將視頻點播遷移到了騰訊雲 - 雲點播上,本文主要會講一講咱們總體的方案、Web 接入的方法和遇到的一些問題。html
視頻點播分爲視頻上傳和視頻播放兩個部分,下面的表格整理了上雲先後的部分數據對比:前端
騰訊視頻 | 騰訊雲 | |
---|---|---|
Web 視頻上傳成功率 | 92% | 99.5% |
視頻轉碼速度(兩小時左右的視頻) | > 60 分鐘 | < 20 分鐘 |
播放成功率 - PC | 99% | 98.7% |
播放成功率 - H5 | 97% | 97.1% |
能夠看出來上傳成功率和視頻轉碼速度有了極大的提高,PC 和 H5 側的播放成功率雲和騰訊視頻基本持平。java
考慮到存量視頻較多,無法短期內所有從騰訊視頻遷移至騰訊雲,同時遷移過程當中用戶可能繼續使用老的方式向騰訊視頻上傳,因此整個點播上雲分爲兩期進行:git
視頻上傳總體方案如上圖所示,主要涉及三塊:github
上面三塊中最重要也最容易出問題的是"調用 SDK 上傳"這一部分,直接決定了上傳成功率,但也很容易受用戶網絡情況的影響,須要重點關注,建議記錄詳細的用戶日誌以便進行問題定位與排查。web
另外,其實上述流程圖與騰訊雲文檔給出的客戶端上傳指引略微有點差異,主要在於第 4 步通知業務後臺上傳完成這裏,官方文檔中是雲後臺來通知,咱們實際採用的方式是 Web 側來通知,從而避免出現 Web 側調後臺接口出錯提示用戶上傳失敗後,雲後臺又通知業務後臺保存相關數據的狀況。小程序
在之前使用騰訊視頻的方案時,出於種種考慮,咱們並未對視頻作加密處理,致使有些課程被他人惡意盜錄。目前上雲以後,咱們使用的是加密 HLS 的方案,經過雲提供的 Key 防盜鏈 和 DRM(數字版權管理)方案,咱們對視頻作了加密處理,就算被拿到了視頻地址,也沒法進行盜錄,進一步打擊了惡意行爲,保護了老師的版權。後端
用戶瀏覽器在播放視頻時主要流程如上圖所示,其中依靠第 1 步獲取 Token 和第 3 步獲取 DK 進行版權的保護,他們的做用分別爲:瀏覽器
視頻上傳主要依賴雲提供的 vod-js-sdk-v6,用 TypeScript 編寫,具備較爲完善的的測試用例,代碼質量很高 👍 其底層依賴的是 cos-js-sdk-v5,也是由騰訊雲提供的對象存儲能力。
接入 SDK 的方法很簡單,只涉及兩方面:
upload
函數上傳視頻。import TCVod from 'vod-js-sdk-v6';
// 用簽名函數觸發
const uploader = new TCVod({
getSignature,
});
// 向業務後臺獲取簽名
function getSignature() {
return fetch('FAKE_CGI_URL').then((result) => {
return result.sign;
})
}
// 調用 SDK 上傳
function uploadVideo(videoFile) {
const upVideo = uploader.upload({ videoFile });
upVideo.on('video_progress', (info) => {
// 此處獲取上傳進度
// 例如上傳百分比、上傳速度等
});
upVideo.done().then((result) => {
// 此處獲取上傳結果
// 例如 fileId、CDN 源文件地址等
}).catch((error) => {
// 上傳失敗
});
}
uploadVideo(fileA);
uploadVideo(fileB);
複製代碼
雖然上傳的 SDK 用起來很簡單,但在咱們灰度的過程當中,仍是遇到了一些問題,於是強烈建議在代碼中加入詳細的上報日誌,例如上面的 DEMO 中能夠加入的日誌信息包括:獲取簽名的開始、成功與失敗,文件上傳的開始、成功與失敗等。
1. 默認只開啓了重慶存儲區
上線後咱們發現視頻上傳的連接均是 xxx.cos.ap-chongqing.myqcloud.com
的形式,這看起來不太對呀,怎麼都往 chongqing(重慶區)上傳了呢?難道不支持就近上傳的能力嗎?後來咱們聯繫雲的同事得知,因爲視頻雲的底層依賴的是騰訊雲的對象存儲(COS),因此具體往哪傳,怎麼傳比較快是由 COS 保證的,須要在雲控制檯開啓相關配置。
2. SDK 上傳部分報錯
上傳初期進行灰度時發現上傳成功率爲 97%,距離預期的 99% 還存在必定距離,經過雙方的合做排查,最終發現主要是由兩個問題引發的:
目前在最新版的 vod-js-sdk-v6
中上述問題均已解決,上傳成功率在全量後也在 99.5% 以上。
前面已經簡單提過了視頻播放流程,咱們這裏再來詳細說明一下。
點播播放其實很簡單,簡單來講就是下面這個流程:
第一步: 獲取m3u8
地址
第二步:調用播放器播放
就是這麼簡單。
這時候咱們發現一個問題,有了m3u8
地址,全部人都能播放了。這個m3u8
地址能夠肆無忌憚的傳播,任何人拿到連接均可以播放,就沒有付費課的概念了。因而咱們開始引入前面提到的第一個技術,咱們稱之爲Key 防盜鏈 。防盜鏈參數是動態變化的,引入以後咱們的流程就變成了:
加了防盜鏈以後,缺乏防盜鏈參數的連接就無法播放了。就算帶防盜鏈參數的m3u8
地址傳播出去,由於有時效性,這個連接過一陣子也會失效。
這時候,聰明的小夥伴應該又發現了另一個問題,假設在防盜鏈參數失效以前把m3u8
文件下載下來,同樣是能夠拿來傳播的。
要解決這個問題,咱們能夠簡單來看下m3u8
的格式。
簡單的說,m3u8
是一個遵循某種格式的文本文件,裏面是一些TS
分片的索引,經過這些索引就能夠找到全部的視頻分片。
回到咱們加密的主題,若是是每個TS
分片作加密,是否是就算把m3u8
下載下來,也無法播放了呢?HLS
的普通 AES 加密技術正是這樣作的。引入了HLS
普通加密以後,整個流程就變成了這樣:
爲了簡單起見,咱們忽略了COS
CDN
這一塊的圖示。解釋一下上圖:
首先是加密,要加密就要要密鑰。這時候就引入了KMS
,咱們暫時不關心KMS
內部實現,簡單認爲作了就是提供密鑰的工做。騰訊雲收到了業務後臺發起的視頻加密請求以後,就會從KMS
獲取對應的加密密鑰,對文件進行加密處理。這就是上圖藍色字的部分。
而後是解密,業務前端在拿到m3u8
的內容的時候,發現須要解密TS
的,因此須要解密密鑰,因而就會請求業務後臺去得到解密密鑰。業務後臺怎麼認爲請求是合法的呢?固然是要有用戶的身份信息(cookie)。騰訊雲提供了兩種方式,具體能夠看HLS 普通加密 。上圖示例便是第一種方案,用例子來解釋一下。咱們看一個 m3u8 地址示例:
https://1258712167.vod2.myqcloud.com/fb8e6c92vodtranscq1258712167/c896adc25285890789334843878/drm/voddrm.token.dWluPTt2b2RfdHlwZT0yO2NpZD00MDY4NDQ7dGVybV9pZD0xMDA0ODUxNzc7cHNrZXk9O2V4dD0=.v.f3071.m3u8?t=5d2f1647&exper=0&us=7776585111527298975&sign=195ed8bcbc08bb5e40f4823c49e71696
這裏的dWluPTt2b2RfdHlwZT0yO2NpZD00MDY4NDQ7dGVybV9pZD0xMDA0ODUxNzc7cHNrZXk9O2V4dD0=
便是須要帶給業務後臺的鑑權token
。再看看這個文件的內容:
m3u8
格式裏用EXT-X-KEY
值用於解密,上圖的cgi-bin/qcloud/get_dk
便是咱們圖示裏的第 5 步,攜帶身份信息,向業務後臺獲取解密密鑰。得到解密密鑰以後,就能夠對TS
文件解密而且播放啦~
瞭解了流程以後,代碼其實就很簡單了。
首先:獲取 m3u8
地址,並拼接上 token
。
async getM3U8List(fileId: string) {
const { termId, onError } = this.props;
try {
// 獲取防盜鏈參數,對應流程圖裏第2步
const urlParams = await getUrlToken({
termId,
fileId,
});
// 獲取 m3u8 地址,對應流程圖裏第3步
const videoInfo = await getPlayInfo(fileId, urlParams);
// 獲取拼接了 token 以後的 m3u8 地址
const m3u8List = getPlayListWithToken(videoInfo, {
termId,
});
return m3u8List;
} catch (e) {
onError(e);
}
}
複製代碼
其次,調用播放器,這裏能夠參考超級播放器 或者 tcplayerlite。文檔比較詳細,這裏就不贅述了。咱們播放完整流程圖裏的第 4 步則是由播放器發起的,第 5 步由瀏覽器本身發起的。
關於監控,播放目前是使用內部 monitor
+ tdw
+ badjs
上報作監控的。
monitor
用於告警和數據累積量的查看。
tdw
用於報表、日報、週報的生成。
badjs
則用於出現了播放失敗等狀況時的排查。
小程序端有兩個問題須要解決:
咱們先來看一下小程序組件騰訊雲視頻播放的一個基本流程:
- 課堂這邊是開啓了防盜鏈和HLS加密的,因此上述的判斷流程都走綠色的路徑;
- tokenObj 是防盜鏈的token,裏面包括: 播放地址的過時時間戳、試看時長、連接標識、防盜鏈簽名。參考Key 防盜鏈;
- drmToken 是m3u8獲取解密密鑰須要用到的鑑權token,具體規則由先後端在業務層約定加密規則。參考QueryString 傳遞身份認證信息;
<cloud-player-video />
組件內部的播放仍是用的小程序的<video />
組件,只是提供了經過參數獲取真正播放地址的功能;- 目前
<cloud-player-video /\>
是咱們本身研發的組件,還在持續迭代優化中,後續會加入倍速切換,清晰度切換等播放器經常使用功能;
<cloud-player-video />
;課堂小程序中獲取 tokenObj 、 drmToken ,因爲這兩個參數的獲取方式是業務決定的,內部流程就不贅述了,貼一下的步驟代碼:
getCloudUrlToken(params)
.then(tokenObj => {
const drmToken = getDrmToken({ term_id: termId });
this.setData({
fileId,
appId: '1258712167', // pro
drmToken,
tokenObj,
});
})
.catch(({ err_code, err_msg }) => {
// 降級播放
this.init(this.properties.playInfo, null, true);
});
複製代碼
而後將四個關鍵參數傳遞給組件,以下:
<cloud-player-video player-id="course-video-player{{r}}" file-id="{{fileId}}" app-id="{{appId}}" token-obj="{{tokenObj}}" drm-token="{{drmToken}}" safety poster="{{poster && tools.renderUrl(poster)}}" bindplay="onPlay" bindpause="onPause" binderror="onVideoError" bindended="onEnded" bindmedianotsup="onMediaNotSup"![](http://imweb-io-1251594266.cos.ap-guangzhou.myqcloud.com/b645c306e5a3695be09104cfdb27183a.png) ></cloud-player-video>
複製代碼
而後是 <cloud-player-video />
組件內部的一些關鍵方法,getPlayInfo是根據 appid 、 tokenObj 、 fileId 獲取原始 m3u8 播放地址的方法;formatUrlWithToken是爲 m3u8 地址附加drmToken的方法:
// 獲取視頻播放地址的方法
getPlayInfo() {
const {
fileId,
appId,
safety,
tokenObj: {
t,
us,
sign,
exper = 0,
},
} = this.properties;
// 當前版本默認獲取playInfo的地址
let url = `https://playvideo.qcloud.com/getplayinfo/v2/${appId}/${fileId}`;
// 若是開啓了防盜鏈,將防盜鏈信息加到querystring裏面
if (safety) {
url += `?t=${t}&us=${us}&sign=${sign}&exper=${exper}`;
}
return request({ url });
}
// 附加drmToken的方法
formatUrlWithToken(m3u8 = '', drmToken) {
const reg = /(\/drm\/)/g;
let tokenUrl = m3u8.replace(/http:/, 'https:');
tokenUrl = tokenUrl.replace(reg, `$1voddrm.token.${drmToken}.`);
return tokenUrl;
}
複製代碼
雖然在上雲的過程當中遇到了一些問題,但都能順利地解決,並且最後的產品數據與用戶體驗都比以前有了提高,但願愈來愈多業務能積極地擁抱雲的時代!