Azure Media Player 趟坑记

前言

最近参与了一个手机 App 项目,用 Cordova + AngularJS 实现的 Hybrid App。里面有一块视频播放的功能,用到了微软的 Azure Media Service (别问我为啥要用 Azure 的视频服务,客户指定要的😂)。作为手机端,用的就是 Azure 官方提供的一个播放器 Azure Media Player
写了一周多,大大小小踩了不少坑,终于整合到项目里,特此记录一下,也纪念一下趟坑的日子。

简要使用说明

虽然 Azure Media Player(以下简称 AMP)的在线文档的使用其实很简单,不过还是概括说一下。
引入 script 和 css 文件自然不必说,核心的代码其实就三步:

  1. 在 HTML 里声明 <video> tag
  2. 在 Javascript 里使用 amp() 初始化 AMP 实例
  3. 在 Javascript 里使用 AMP 的 src() 方法指定视频流的地址

官方也提供了各种使用场景下的 Sample,不过这里还是要吐个槽:光有效果页面没有示例代码叫哪门子 Sample 啊。。后来为了研究参数使用就用了好久。。

坑 1: 光有声音没有画面

稍微读了 AMP 的文档之后,就动手把 AMP 引入了项目的代码工程里。本来以为可以轻轻松松一次性运行成功,结果页面上只有一个大大的圆圈,里面有个三角形的播放图标(见下图),点了也没有任何效果。但此时还有声音播放出来。

于是打开 Chrome 的开发者工具,在 Console 里看到了如下的消息。

1
Uncaught SyntaxError: Failed to execute 'add' on 'DOMTokenList': The token provided must not be empty.

在 Google 上搜了一下,发现这个错误很可能是因为页面还没有完全渲染好,导致 DOM 操作失败,从而出了这个错误。
于是解决方法是将 AMP 的初始化相关逻辑,放到 devicereadydocument.ready 事件收到之后。

这个问题严格来说不是 AMP 的坑,毕竟 AngularJS + Cordova 的项目里,等待 deviceready 是一件非常重要的事情。(也导致使用 Cordova 的应用启动速度很慢。。)不过因为国内连 Azure 比较慢,AMP 在后台下载视频片段时经常出错,所以之前一直找错了方向,以为是网络问题导致加载不完全。

坑 2: 播放完毕无法重放

AMP 可以正常播放之后,本来以为搞定了,但是无意中在一段视频播放完毕之后,又点一下播放键,结果没有重新播放。。一开始以为是要重新下载数据,但等了一会没有任何反应。捣鼓了一会儿,无意中又发现,如果拖动一下进度条,就可以正常播放下去了。

仔细一想,是不是要播放完毕后重置播放进度,于是给 ended 事件加了一个 callback,重新设置 currentTime 为 0。然而再测试仍然没有解决。
没有什么思路,只好给所有事件都加上 callback,将 Play rate、current time 等参数都打出来看看。再测试,并没有发现任何参数异常,却发现一个很奇怪的事情,就是点击播放之后,出发了 playing 事件之后,立刻又收到一个 paused 事件,就好像是播放器在不断 Pause 自己一样。。

于是继续问 Google,用「loop」关键字搜索,无果。。这就看出 AMP 应该没啥人用了,搜来搜去都是官方的那几篇文档。。
排除了各种可能性之后,剩下最不可能的也就成了可能,「这不会是 AMP 的 Bug 吧」,这么想着开始查 AMP 的 Known Issues。一看之下,果然。。。

  • Cannot automatically playback same source again after presentation ended.
    • To replay a source after it has ended, it is required to set the source again.

这就意味着,每次播放完毕之后,需要重新通过 src() 方法加载视频源,效率嘛,这个就先不要考虑了。。

坑 3: iOS 上无法播放

终于在 Android 上调试好了,由于 App 还要在 iOS 上发布(这也是使用 Cordova 的最主要原因),所以免不了在 iOS 上也跑了下。
本来以为是走个过场,没想到 iOS 上居然来了个惊喜。。无法播放,直接死给你看。。

于是继续查文档,文档提到 AMP 是可以有好几个核心实现(Tech Order )的备选的,还贴心的给了一个对比表格
根据这个文档,Safari 上应该已经可以使用默认的「azureHtml5JS」才对,但是不管是使用默认,还是在 AMP 的参数里手动指定 techOrder 参数,都没有任何结果。之后再看看 Known Issues,嗯。。于是瞬间找到原因。。

  • AES and restricted token content does not playback using iOS and older Android devices.
    • In order to achieve this scenario, a proxy must be added to your service.

嗯,,微软爸爸是我们不应该用 AES 加密。。😓

那么这个 Proxy 是怎么回事呢?还好 AMP 的文档中也给出了一篇解释的文章。基本上,就是说 iOS 不鸟微软这套(MSE),需要通过一个 Proxy 带着 Token 代替 iOS 去 Azure 的 Key Delivery Server 上请求一下 Key。文章里还很贴心的给出了 Proxy 的 Sample 代码(尼玛是 C# 写的。。)和在线的 Proxy Demo
同时,AMP 还给了一个在线的 Player 代码生成页面,通过这个页面,将带 Proxy 的地址配进来,可以生成出 AMP 的使用代码。

过程是曲折的,结果是胜利的。最后总算还是是搞定了 iOS 上的播放,几个注意点如下。

  • src 需要指定为 Proxy 地址,通过 URL 参数将实际视频的 URL 和 AES Token 传过去,例如 proxy?url=<实际视频地址>&webtoken=<AES Token> 的形式
  • type 不是默认的 application/vnd.ms-sstr+xml,而需要设置为 application/vnd.apple.mpegurl
  • streamingFormats 需要手动指定为 ["HLS-V4", "HLS-V3"]
  • 虽然 token 是作为参数传给 Proxy 了,但 protectionInfo 还是要指定为 [{"type": "AES"}]

总结

总体来说 AMP 的使用并没有什么技术含量,但是官方文档主要还是偏向于纯 Web 上使用,并没有太多关注移动端的应用,除了 Cordova 上的使用,其实 Android/iOS 原生如何使用的说明也是缺失的,而这些恰恰是现在的主流使用场景。

而这次除了上面说的三个大坑,其它小坑也趟了不少,当然一部分是因为我对 AngularJS 和 Javascript 并不太熟悉导致的,不过 AMP 的文档还是符合我一贯对 M$ 的印象:把 API 列举一下就完事,并没有各种场景下的使用方法以及 Best Practise 的说明解释。
其实本来想给本篇 Blog 起名叫「Azure Media Player:那些官方没有告诉你的坑」,但是仔细想想,人家文档里虽然语焉不详,但也不能说只字未提。。

回想起几年前做过一个基于 Visual Studio DDEX 框架的项目也是如此,一篇文档轻描淡写的把主要概念说了一下,然后就推出来一堆 API Reference,至于这些 API 应该在什么情况下用,应该用什么样的顺序调用,抱歉这就是考验开发人员智商的时间了。。
所以 M$ 的东西啊。。还是少碰为妙。