沉沦博客

自定义 77°
69%
0%
文章

给一个几乎不放视频的博客,做了个视频播放器

摘要:博客终于支持视频了。为了这件事折腾了好几轮,最后干脆不用浏览器的播放器,自己写了一个。

·2352 字 ·约 8 分钟
目录(6 节)
  1. 这件事折腾了好几回
  2. 为什么选了最费劲的那条路
  3. 大概是怎么实现的
  4. 这么干的好处
  5. 这么干的坏处
  6. 想请看到这里的朋友帮个忙

先说结论:这个博客现在能放视频了。

听起来像个正经功能,但实际情况是,我一年也未必往文章里塞一段视频。给一个几乎没有视频的博客做视频播放器,大概相当于给一辆一年开两次的车装涡轮:大部分时间用不上,真用上一次,又觉得还挺值。

上面这段就是新功能的效果。十三秒,一片草原,一排铁丝网,没什么信息量,纯粹用来证明它真的能播。

这件事折腾了好几回

这个功能不是今天才想做的。前前后后试过几次,每次都是雄心勃勃地打开编辑器,灰头土脸地关掉浏览器。

问题从来不在电脑上。电脑上一切正常:海报、控件、进度条,怎么看怎么对。一到手机上就原形毕露——同一个页面,同一个视频,在你的浏览器里是海报加播放键,在手机自带浏览器里却自己动了起来,声音都出了,像是我没写清楚代码,又像是我写了太多。

中间试过的办法不少:把 preload 调成 none、把地址挪到 data-src、点播放才加载、灯箱里只摆海报……每一样单看都合理,凑在一起就像在跟一个听不懂人话的对象谈判。有几次我盯着屏幕想:到底是我在写代码,还是浏览器在教我做人。

最后这次算是想明白了:只要页面上还有一个真正的 <video>,手机浏览器就有办法接管它。它想什么时候加载、什么时候播放、用什么控件、全屏交给谁,都不归我管。preload="none" 管的是“预加载”,管不了“我就是要播”。

那就别用它了。

为什么选了最费劲的那条路

摆在面前其实有三条路:

  1. 继续调 <video>:改属性、改结构、赌浏览器的心情。成本低,但永远在赌。
  2. 把 <video> 缩成 1 像素、只让它解码,画面画到 canvas 上。相当于把引擎留在车里,自己重新做一个仪表盘。
  3. 连解码也拿回来:用 WebCodecs 自己解 MP4,画面和声音都自己调度。

第二条路其实够用了,改动也小。但我当时的想法很简单:既然仪表盘都自己做了,发动机也一起拆了吧。省得以后又在哪个浏览器里发现“引擎自己会走”。

顺手也考虑过 Video.js、Plyr 这类现成播放器,但它们换的只是皮,底下还是那个 <video>,手机浏览器照样能接管。要解决问题,得把地基换掉。

代价也很清楚:播放器变成了一个要自己维护的小内核。解封装、解码、音画同步、进度、暂停恢复,样样都得自己来。一个几乎不放视频的博客,拥有一个自己写的播放器内核,这事说出去多少有点荒唐。但至少以后出了问题,我知道该骂谁。

大概是怎么实现的

写得不算复杂,这里只挑关键的说。

第一步,只认 MP4 的目录。 服务端上传的视频统一转成 H.264/AAC 的 MP4,文件开头有一张“目录表”,记录每一帧数据在文件里的位置、时间戳、以及是不是关键帧。播放器先把这张表读出来,后面按表按块取数据,播到哪取到哪,不需要把整个文件先下下来。

第二步,解码交给 WebCodecs。 浏览器提供 VideoDecoder 和 AudioDecoder,把一段段压缩数据喂进去,出来就是画面帧和音频块。解码仍然是浏览器做的,但“什么时候解、解多少、什么时候播”由我决定。

第三步,画面画到 canvas,声音交给 WebAudio。 画面按时间逐帧画上去,音频块按时间排进音频时钟,再用音频时钟反过来驱动画面。控件是几颗按钮和一条进度条,样式跟站内其它地方保持一致。

第四步,兜底。 如果浏览器不支持 WebCodecs、编码不认识,或者运行中解码报错,就退回原生 <video>:页面只留一颗播放键,点了再交给浏览器自己的控件。能播就行,不追求好看。

现在的行为是:进页面只看到海报,一点流量都不花;点播放才开始取数据,先取文件开头那点目录,再按块边下边播;切页面会把解码器和音频收干净。灯箱和碎语小格子也都走同一套播放器。

顺便说一句,WebCodecs 不是每个浏览器内核都会开放,所以我也不敢保证每台设备都会走新播放器;不支持就退回原生 <video>,只留一颗播放键。这也正是下面想请大家帮忙测的原因。

这么干的好处

浏览器彻底管不着了。 页面上那个 <video> 再也不给它装可播的地址,它就没有东西可以接管。不会自己加载、不会自己出声、不会在视频上盖一层原生控件,全屏也归我自己处理。之前那些“这个浏览器这样、那个浏览器那样”的问题,一次性消失。

所有设备的播放表现终于一样了。 以前是同一份代码,在电脑上是海报加播放键,在手机上变成另一套东西。现在只要走的是新播放器,画面是 canvas 画的、控件是自己写的,换哪个浏览器看都长一个样——顶多性能有差别,长相不会再跑偏。

流量花得明明白白。 进页面只下海报,一点视频流量都不花;点了播放才去取数据,而且只取当下要播的那几块,不会把整段先拉下来。对偶尔看视频的读者来说,这个体验比“先偷偷缓冲三十秒”舒服。

以后想加什么自己说了算。 倍速、快捷键、进度记忆、画中画,只要我愿意写,不用看浏览器脸色。

出问题知道该骂谁。 以前是浏览器背锅,现在锅全在我这儿。听起来像缺点,但至少能改。

这么干的坏处

等于给自己养了个小项目。 解封装、音画同步、缓冲、异常处理,全得自己维护。写的时候觉得自己挺行,出了问题才发现是在给未来的自己挖坑。

只认普通 MP4。 分片 MP4、HLS、WebM、MOV 都不在支持范围。哪天想放长视频、做直播,这套东西就派不上用场了。

内存和电量都不太客气。 解码后的画面帧和缓存的分片要占内存(现在按块取,缓存上限十几 MB),画面靠逐帧画、声音靠 WebAudio 排期;低端机或者长视频上,CPU 和耗电大概率比原生播放器难看。目前这段十三秒的片子没什么感觉,真放十分钟就不好说了。

系统级能力基本没有。 锁屏控制、耳机线控、后台播放、投屏、字幕,这些原生播放器白送的东西,现在一样都没有。以后要不要补,看心情。

首帧还是比原生慢一点。 点播放要先取到目录和第一块数据才能出画,弱网的时候会先转一会儿圈。原生播放器能边下边播,我这里算半斤八两,但多了一层解析。

有视频的页面会多下一点脚本。 播放器脚本只在真的有视频的页面加载,大约 9KB;其它页面没有这份开销。即便如此,每次构建看到体积预算,我还是会反省一下。

这么一列,坏处比好处长,看着像劝退。但对我这个场景来说账是划算的:视频极少,稳定比功能多重要,能一次把手机上的怪毛病解决掉,多养一个小项目也认了。要是做成视频网站,这么干就是纯纯的想不开。

想请看到这里的朋友帮个忙

自己写播放器这件事,最怕的就是“我这儿好好的”。我手上只有一台手机、一个浏览器,测不出全部情况。

如果你方便,麻烦帮我试两件事:

  • 打开这篇文章,点一下上面那段视频,看看能不能正常播放、有没有声音、拖动进度条是否跟手、全屏正不正常。
  • 再去碎语页面,找那条带视频的碎语,展开配图,看看小格子里的播放键和灯箱是否正常(小格子现在也是同一套播放器)。

要是遇到黑屏、没声音、卡顿、控件错位、点了没反应,或者更离谱的情况,留言告诉我机型和浏览器就行。能复现的话,我会去修;不能复现的话,至少我知道有这么一回事。

如果一切正常,那这个功能大概会安安静静地待着,直到一年后我又想起它。

评论0