Media Source Extensions

今天在 Apple 官网看 Apple Events 2021 的视频时,觉得分辨率太低啦!想到了查看一下可不可以更换分辨率。

然后就发现它的文件是分开存的,视频用 mp4 文件存储(只有画面没有声音),声音也是用 mp4 文件存的,没有画面。

然后我就好奇了,看它前端是怎么结合使用的。

扒了 dom 结构,发现竟然只有一个 video 元素,没有其他任何的 audio 元素!

咦?这和我以往学到的东西不一样啊,然后继续研究。

看到它的 video 标签上,src 使用的是一个 blob 链接,嘿嘿,这个我就熟悉了!

这个是通过 URL.createObjectURL 函数生成的,只能在当前会话中使用的二进制对象地址,见 URL.createObjectURL MDN

什么数据被 createObjectURL 处理了?

我想知道它的数据来源是怎样的,那就要知道 “URL.createObjectURL 到底处理了什么数据”

那怎么办呢?

  1. 打断点看数据:也就是看看传入的参数有什么
  2. 用 chrome 调试工具替换 (Sources -> Overrides):随意更改源文件并替换加载,可以为所欲为
  3. 劫持 createObjectURL 方法:简单易行,但只限于一些特殊场景使用

但是我的电脑太慢了,主要是 CPU 主频低(2.6GHz),查看脚本文件 “hls.js” 要卡顿好一会儿(怀念以前的黑苹果台式机)。所以,我选择了第三个方法。

怎么劫持呢?其实很简单,就是我们新建一个函数来替代 createObjectURL 方法即可,像下面这样:

1
2
3
4
5
6
7
8
9
window.realCreateObjectURL = URL.createObjectURL
URL.createObjectURL = function(...args){
// debugger // 打断点
// console.log(...args) // 输出数据
// window.MY_DATA ? window.MY_DATA.push(args) : (window.MY_DATA = [args]) // 保存数据以便稍后操作

// 调用真实 createObjectURL 函数
return window.realCreateObjectURL(...args)
}

劫持方法简单易行,但他也有一个最致命的点:需要在页面脚本加载前插入文档中并运行!

所以我们用了熟悉的 油猴 TamperMonkey chrome 插件,新建一个插件并把上面的劫持代码放进去就大功告成。

结果是,我们发现,createObjectURL 一共被调用了 3 次,参数分别为:

1
[MediaSource, Blob, Blob]

通过解码,发现后面两个 Blob 都是 js 代码,第一个就是我们要寻找的目标啦,去 Google 一下 MediaSource 是什么

原来这一种新的、更强大的 Web 接口,他可以让我们更方便地用 JavaScript 去控制媒体的获取、传递播放。

简单来说

  • HTML 的 video 通过一个数据输入进行播放;
  • 媒体数据可能是多个源合成的:音频和视频、分段数据、加密的流数据等等

那 video 的单一输入与多源的媒体数据之间,就需要一个中间适配器去整合多源的数据然后喂给 video 或者 audio 等(其实正如 MediaSource 的名称,媒体数据他都能搞定,音频视频图片…)

每种数据源用 SourceBuffer 来包装,也是正如其名。我们只需要把 ajax 获取的媒体数据,转换为 arrayBuffer,然后喂给 SourceBuffer

再将单个或多个 SourceBuffer 喂给单个 MediaSource,最后把这个单一的 MediaSource 喂给 video,就大功告成啦!

试一试

好了,大致思想确定了,就可以开始看实际的代码了!

我使用了 frag bunny 的 mp4 文件来做示范,参考大佬的一篇文章 Javascrit中使用MediaSource播放加密视频,将 mp4 文件剥离为音频和视频,并且将音频和视频都拆分为更小的文件块,便于网络上增量加载。

代码在这里,可运行 media-source-extensions

继续看发布会

诶,我在干嘛呢?咋就开了这么多标签??

最感兴趣的可能就是 M1 的 iPad 了,iPad OS 有点拖后腿了啊,希望能用上本地 VS Code 而不是在线版!