本文做者: 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" ></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;
}
复制代码
虽然在上云的过程当中遇到了一些问题,但都能顺利地解决,并且最后的产品数据与用户体验都比以前有了提高,但愿愈来愈多业务能积极地拥抱云的时代!