虽然工期紧张,还是忍不住想跟大家分享一下我在搭建新宁皓的视频播放服务的痛苦经历,现在想想,简单的像是在夏天里吃根冰棍。
宁皓网现在用的视频播放服务就是直接把压缩好的视频上传到服务器上,我在阿里云买了一块 NAS 存储服务,这样多台服务器可以共用这块存储,也就是都可以在同一个地方读取视频文件。这次重构我打算试一下视频点播服务,虽然咱们是个小网站,不过还是会有一些在国外的用户,我希望可以用内容分发服务更快速的把视频内容交给大家。
这回让我痛苦不堪的就是在学习阿里云的视频点播服务的时候,总体来说,阿里云的开发文档还是值得肯定的,只要花些时间就可以在文档里找到自己想要的东西,即使找不到阿里云的工程师们也能即时帮我们解决掉任何的疑问。
阿里云的文档表达的比较官方与正式,对我这种习惯白话的人来讲有点不太适应,再加上文档里掺杂着一些陌生的技术词汇,还有就是复杂的流程图,这不能怪阿里云画的不好,我真的是看不懂流程图,看着看着就迷了路。
这个视频点播服务还是有些复杂的,文档里出现的一些东西也比较容易混淆,有时候难以判断在文档里提到的东西到底是服务本身提供的,还是需要我们开发者自己要做的。客户这个词在阿里云的文档里指的应该是我们,但是在我们心里客户指的是我们应用的用户,或者客户端的应用。
我用了整整一周的时间才熟悉这个服务,实际上我们只需要在了解它是怎么工作的,就知道怎么用它了。文档内容虽然详尽,但是无从下手,本想从头读到尾,但始终没能成功,还是会跳来跳去,这就是痛苦的根源。每篇文档内容都比较独立,很难从中找出工作流程。解决这种问题的方法就是能干点啥就先干点啥,做到一定程度的时候,视线就会慢慢变得清晰了。
停止废话,下面分享点干货。
开通服务
我们有一堆录好的视频,需要一种快速、安全、便捷的方法把这些视频交给我们的用户那里,这就可以选择使用这种视频点播服务,它提供了存储,处理,分发视频的功能。确定要使用这种服务,可以买一些相关的资源包,价格会便宜些,比如流量包,存储包,转码包等等。
在自己的阿里云帐户开通这个视频点播服务,按要求设置存储空间,视频点播服务会把上传的视频存储到指定的地方,国内好像只能选择存储在上海,我们不用担心存储空间的问题,用多少就交多少钱。再设置一下加速域名(视频播放地址里的主机名),比如我设置的是 play.ninghao.co,这样在视频地址里就会使用这个主机名。
访问密钥
阿里云允许我们设置多个子帐户,然后可以给每个子帐户设置单独的权限,这里我们可以创建一个子帐户,然后给它分配使用视频点播服务的权限。在每个子帐户下面都可以申请一对访问密钥,在我们的应用里可以通过这对密钥验证身份,这样就可以使用开发工具包提供的功能调用视频点播服务的接口了。
开发工具
要使用视频点播服务提供的功能,可以先在我们的应用里准备一个开发工具包,根据应用的程序语言选择使用对应的开发工具包(SDK),我用的是 Node.js,可以在 NPM 找到对应的软件包,把它安装在应用里就行了(希望阿里云的工程师能给 Node.js 版本的 SDK 添加类型定义)。
也就是对于 Node.js 来说,这个阿里云的 SDK 就是一个 Package(软件包),用这个软件包我们可以创建一个 RPCClient,RPC 指的应该是 Remote Procedure Call,就是可以远程调用一些功能,这里就是调用阿里云的服务相关的功能,Client 是客户端的意思。用这个东西我们可以调用阿里云的视频点播服务的接口,比如可以给应用的客户端申请上传文件用的凭证,提交处理视频的动作,调取视频信息,获取视频地址等等。
在 Node.js 这里,这个 SDK 包的名字叫 @alicloud/pop-core(让我想起了爆米花),一开始我以为这是专门针对视频点播服务用的开发工具,后来发现它是阿里云所有服务的开发工具,不过在使用其它的阿里云服务的时候,可能还得安装其它的包。
示例:
this.client = new RPCClient({ accessKeyId, accessKeySecret, endpoint, apiVersion, });
服务接口
视频点播服务提供了很多服务接口,通过这些接口可以去做不同的事情,比如提交视频转码任务,获取视频地址与相关信息等等。也就是在我们的应用里要去做什么事情,就要找到对应的接口,然后用之前在应用里准备好的那个阿里云的 RPCClient,调用这个接口。不同的接口在调用它的时候支持使用不同的参数,调用接口返回的数据也是不一样的。这些都需要查阅相关的接口文档。
示例:
const response = await this.aliyunVodService.client.request( AliyunVodApi.GetPlayInfo, command.params, {}, );
上传视频
要往视频点播服务里面上传视频,我们要在应用的客户端那里集成上传用的 SDK ,比如客户端应用如果是一个前端应用,就需要集成一下 JavaScript 版本的 SDK。在客户端上传视频文件之前,我们需要在应用的服务端这里,调用 CreateUploadVideo 接口获取到视频的上传凭证还有地址,把它们交给客户端应用,这样才能上传视频。
事件处理
在视频点播服务这里发生的一些事情可以通知我们的服务端应用,比如当上传了新的视频文件,视频截图成功,转码成功,视频信息变更等等,这些都会触发对应的事件,发生这些事情以后视频点播服务可以把这些事情以及相关的数据发给我们的服务端应用。
也就是我们可以在服务端应用这里需要定义一个处理视频点播服务事件用的接口,阿里云会请求使用这个接口,把视频点播服务里发生的事情告诉给我们的应用。在这个接口需要验证一下包含在请求头部里的签名数据,确定请求是不是真的是从阿里云那里发过来的,然后根据请求数据里的事件类型,可以确定如何处理不同的事情。
除了这种通知方法,阿里云还支持使用消息队列服务的方式,也就是发生的事情会推送到指定的阿里云的消息队列服务那里,我们的服务端应用可以读取这个消息服务里的消息再去做不同的处理,用这种方法可能更稳定,不过得开通 MNS 服务,还得花钱。
处理视频
上传到阿里云视频点播服务上的视频可以做不同的处理,比如打水印,转码,截图等等。通过相关接口或者直接在阿里云的控制台那里可以配置这些处理方式,然后再通过相关接口可以对指定的视频文件做不同的处理。上传的视频会自动生成一些截图,默认不会对视频做转码处理。
对视频做编码处理或者想生成几种不同的尺寸,可以提前定义好转码模板,每个模板里都可以添加几种不同的处理,每个处理可以设置视频编码的相关参数,比如封装格式,编码格式,码率,尺寸等等。上传视频申请上传凭证与地址的时候可以设置使用某个处理模板,这样上传的视频会自动被处理,或者也可以在上传视频完成以后,通过调用 SubmitTranscodeJobs(提交媒体转码作业) 接口对指定的视频提交对应的处理。
我选择的是 hls 这种视频封装格式,它会把视频切成很多小块(.ts 文件),每小块视频默认的时长是 10 秒,最终生成的是一个 m3u8 格式的文本文件,在这个文本文件里会列出视频包含的所有的切片地址。注意视频的封装格式与编码格式是两回事,比如 mp4 与 m3u8 这两种封装格式的视频,使用的可以是同一种编码格式,比如 H.264。视频的封装格式只是决定了播放器如何播放这个视频,影响视频大小的是编码格式。
视频播放
根据上传视频以后生成的视频 ID,调用视频点播服务的 GetPlayInfo 接口可以获取到视频的播放地址,调用接口会返回视频标题,时长,还有一组播放用的地址。
搭建新宁皓的流媒体服务的时候,我单独创建了一个数据表存储视频相关数据,在服务端应用这里设计阿里云视频点播服务的事件处理接口的时候,我就会把收到的数据存储在这个数据表里。在这个表格里会存储视频 ID,视频地址还有截图等等。这样我可以直接通过服务端接口把视频相关数据发给我的客户端,就不需要再去问阿里云要这个地址了。
示例:
https://play.ninghao.co/53ddb52b0a5d40c782ea9c0d710f63f7/78d093c1c409c04c86be0bda68cad91c-hd-encrypt-stream.m3u8
安全播放
知道视频地址以后默认可以任意播放这个视频,在宁皓网只有订阅会员或者有权限的会员才能播放视频,所以需要一种安全的方法播放视频。阿里云提供了几种方法保护视频资源,最简单的就是通过加密字符串的方式(HTTP 鉴权)。
启用这种方式以后,在视频地址里会包含过期时间,还有加密字符串,这样生成的视频地址只有在规定的时间内才有效,过期以后就不能通过这个地址继续播放视频了,需要重新生成新的视频地址。
这样我们在服务端应用这里,可以根据客户端的请求判断具体是哪个用户,检查了用户权限以后可以给客户端生成一个在规定时间内有效的视频地址。
示例:
http://play.ninghao.co/53ddb52b0a5d40c782ea9c0d710f63f7/78d093c1c409c04c86be0bda68cad91c-hd-encrypt-stream.m3u8?auth_key=16247949863600-0-0-7fe17dce88e95451e6a11466f9d5bde1
在视频地址里会包含一个 auth_key 查询符,它的值是由几个部分组成的,在文档里可以找到生成这个 auth_key 的方法。就是按一定的顺序排列出一个字符串(地址,过期时间,随机字符,用户 ID,密钥),然后用 md5 的方法加密一下。生成这个加密字符串会用到一个密钥,这个密钥我们要告诉给点播服务,它在验证视频地址的时候会用到这个密钥。
视频加密
如果选择 hls 这种视频封装格式,可以对视频内容进行加密,这样即使把视频文件下载到本地,没有密钥也没法播放。阿里云提供了私有加密还有标准加密。使用私有加密的话只能使用阿里云提供的播放器,使用标准加密,可以在任何支持 hls 的播放器上播放加密的视频。
要使用 hls 标准加密,首先得添加一个转码模板,在里面添加一些用 hls 封装,并且启用了加密功能的处理。上传视频的时候用不转码的转码模板上传,上传完成以后再提交带加密的转码模板处理视频,在提交这个处理的时候,要带着加密视频需要用的密钥。
这里可以配合使用阿里云的 KMS 服务,就是在提交加密转码之前,先调用阿里云的 KMS 服务生成密钥,提交加密转码任务的时候带着这个密钥,还要带着解密接口的地址,视频播放器在播放加密视频的时候会请求这个接口地址获取到解密视频数据用的密钥。一定要注意这里要在 KMS 服务那里授权给 VOD(视频点播服务)使用 KMS 服务(我的两小时生命就花在这儿了)。
示例(m3u8 文件内容):
#EXT-X-KEY:METHOD=AES-128,URI="https://sandbox.ninghao.net/aliyun/kms/decrypt?CipherText=YTU4MWVmODctMzE2OS00ZDlmLTllMWUtZDQ0MmRmMzZiMzYxSDdmSUhxb3FxL2k2TDRWY1dHdFFTWHFTaENLb1VtOVlBQUFBQUFBQUFBQUsrbXlYM01lbklpV1UwT2U1SkozRndrTkUrWVcxREVNS0paMUE1bkw0enBMREpjYkc2VzBt"
加密以后,在生成的 m3u8 文件里面会有 #EXT-X-KEY:METHOD=AES-128 ,URI 的值就是获取解密用的密钥的地址。我们在服务端应用里设计的解密接口这里,通过地址里的 CipherText 这个查询符的值,可以调用阿里云的 KMS 服务得到对应的密钥(Plaintext),再把这个密钥响应给客户端,这样客户端这里的播放器就可以播放加密的视频了。
解密接口示例:
/** * HLS 视频播放解密 */ @Get('decrypt') async decrypt(@Query() query: any, @Res() res: Response) { const response = await this.commandBus.execute( new DecryptCommand({ CiphertextBlob: query.CipherText }), ); res.end(Buffer.from(response.Plaintext, 'base64')); }
调用阿里云的 KMS 服务的解密接口以后,得到了 Plaintext,用 base64 解码后再把它响应给客户端(这里用了我的另外两小时生命)。
播放器
使用阿里云的私有加密只能用阿里云提供的播放器播放视频,除此以外就没有限制了,我们可以任意选择自己喜欢的视频播放器。因为我用的是 hls 封装视频,所以要选择支持这种封装格式的播放器,比如 Video.js,不用额外的插件就可以直接播放 hls 视频,我暂时选择的是 Plyr,这是一款比较简单的播放器,要播放 hls 视频需要配合使用 hls.js。另外 Vime 看起来也是不错的播放器,不过因为在 Vue 里面遇到了编译问题,所以没有测试成功。
服务架构
设计应用界面太花时间了,我直接用的是这次训练营里做的那个项目的前端界面。因为比较赶时间,所以也没有实施文件上传,打算直接通过点播服务的控制台上传视频。我提前准备好了视频内容数据,并且把它们导入到了数据库,然后通过点播服务的控制台上传视频,成功以后它会发消息告诉给我的服务端应用,服务端收到消息以后,根据消息的类型可以做出不同的处理,比如收到视频上传成功的消息以后,可以提交一个加密转码任务,等转码完成再次收到消息以后,可以把转码之后的视频地址存储在应用的数据仓库里。
训练营的同学
训练营的同学们再等上几天哈,等我把视频全部上传并处理好以后,做个简单的视频列表与播放功能就把网站发布出来,先让大家看上视频。作为补偿,这次咱就不限制播放了(实现这个功能还得花很长时间啊 ~ ),等上线以后可以一口气看完所有视频,让大伙爽个够。
评论
不错,对要做视频服务的同学还是很有指导意义的。剩下的只能去看阿里云那绕死人的文档了
3 年 1个月 以前