最近女友使用百度云盘观看学习视频时, 觉得讲师语速太慢, 想调快速度, 然而调速是会员功能. 为了这么一个简单的功能而购买高昂的会员服务, 显然不值得(当然也可以把视频下载下来再用本地播放器倍速, 但又不够方便), 故我做了一番尝试, 最终通过技术手段达成了女友的功能要求. 本文仅做技术研究, 无它, 若有侵犯任何人的权益, 请通过关于页面中的联系方式联系我处理.

当代浏览器已经禁用了 Flash, 故在浏览器中播放视频只能使用 video 标签(若有安装浏览器插件, 则有例外情况, 在此不做讨论), 而调整速度使用 playbackRate API 即可, 原理上来说很简单.

第一次尝试

右键点击播放器, 使用浏览器的审查功能, 快速定位到了 video, 在开发工具的 Console 中执行代码 $0.playbackRate = 2, 视频播放速度加倍, 一切看似OK.

然而进一步完善时, 发现情况没这么简单. 在 Console 中执行代码 document.querySelector('video') 返回为null, 而非一个DOM节点. 通过查找 video 标签所在的父级标签, 发现了如下图所示的 #shadow-root(closed) 标记. 原来视频标签在 Shadow DOM 中, 被隔离开了, 使用JS API还是可以正常访问 Shadow DOM 中内容的.
shadowRoot

但意外情况又发生了, 访问Shadow DOM宿主元素的 shadowRoot 时, 又返回的了 null. 这时才留意到 #shadow-root(closed) 标记中的 (closed), 查阅资料后发现: closed 为 ShadowRoot 的 mode, 有 openclosed 两个值: open 表示 ShadowRoot 可以被JS访问, closed 则相反. 于是还需要进一步尝试.

第二次尝试

基于以上情况, 我便考虑如何将 closed 改为 open. ShadowRoot 的 mode 属性为只读属性, 无法直接修改, 于是猜测应当可以通过劫持原生API来实现. 再次阅资料后, 应证了我的猜想. 可以通过劫持 attachShadow 实现, 于是写下了如下代码进行劫持, 并借助浏览器插件 Tampermonkey 完善了一个用户脚本.

1
2
3
4
5
const originalAttachShadow = Element.prototype.attachShadow
Element.prototype.attachShadow = function (args) {
args.mode = 'open'
return originalAttachShadow.call(this, args)
}

写完用户脚本反复测试时又发现了新状况: 有时候倍速生效, 有时候又不生效 ):- . 进一步猜测是和网络加载速度有关系, 因为执行脚本用户脚本的时视频资源可能还在加载初始化中. 还要继续尝试.

第三次也是最后一次尝试

尝试中, 意外发现若将 Element.prototype.attachShadow = null, 网页不会使用Shadow DOM来创建视频标签了(即视频库做了特性检测, 有fallback方案), 此时可简单快速访问到 video 标签, 于是果断放弃了上述尝试, 最后也无缝完善了本功能.

这里还有两个小点要提一下:

  • 劫持 attachShadow 的代码应当在使用该API的代码之前执行, 所以应当在 Tampermonkey 的用户脚本设置中修改 Run At 为 document-start
  • 页面中一开始只有一个占位 video 标签做缓冲, 缓冲完之后才会创建播放视频的 video 标签, 可以通过监听父容器DOM节点的 DOMNodeInserted 事件来判断播放视频的标签是否创建

后记

在女友的建议下, 我还给播放器增加了倍速控制按钮, 和网页自带的播放器浑然天成. 也发现了网上有其他更简便的解决方案(均未亲自测验):

  1. 使用最新版火狐浏览器, 在火狐浏览器中右击视频, 即可调整播放速度, 此为浏览器自带功能, 使用方便
  2. 直接在浏览器中执行代码调用播放器使用的视频库 videojs 的API控制速度, 但对不懂代码的人来说, 使用不便且无法随意切换播放速度

我的思路从原生API出发, 更通用(有时候也会更繁琐), 不受浏览器(需要浏览器支持用户脚本, 现代浏览器均已支持)和使用的JS库限制.