感受 <video> 和 <canvas>

因为本地会有很多音视频资源, 想要在局域网内的多台设备上看. 如果实现这个目的, 那最简单的办法应该是起个 HTTP 服务器, 比如 Chrome Web Server 之类的, 不管是浏览器直接看还是下载, 问题都不大. 正好在看 Angular, 于是想要做个浏览器端的播放器, 配合一个文件服务器, 于是这篇内容记录了验证思路的整个过程.

感受 <video><canvas>

要点

  • <video> 离屏播放, 路由随意变播放不中断
  • context.drawImage() 将视频画面绘制在 <canvas>
  • window.requestAnimationFrame() 保证流畅绘制
  • element.requestFullscreen() 实现全屏
  • video.requestPictureInPicture() 实现画中画, 当然这个是 Chrome 的私货

流水账

本来可以选择 Emby 之类的媒体服务器, 还能自动下载海报字幕等等. 但是官方客户端需要另外付费, 而第三方工具, 比如 VLC Player 对相关协议的支持并不怎么好.

基本的逻辑就是请求当前目录的文件列表, 点击时播放当前文件及之后的文件. 之前说过 <video> 可以拿来播放音频, 从而额外支持 WebVTT 的字幕/歌词. 于是直接选了 video 元素, 开始拿音乐文件做实验, 还像个正经音乐播放器那样考虑了播放顺序的支持, 比如顺序播放/单曲循环.

起初做的话是直接通过 ElementRef 获取播放器组件中的 <video>, 然后所有的逻辑都围绕这个 video 元素. 但是在这个状态下的一个问题是路由切换, 毕竟直接用 video 的话, 路由切走音乐就停掉了. 理论上需要把 video 元素放在最外一层, 通过 service 暴露方法给需要的组件. 不过音频嘛, 不塞进页面里也没关系.

通过 video.service 暴露了一堆方法给播放器组件来间接控制 <video> 的状态, 并且通过 <video> 的事件更新 currentTime, 播放百分比和 paused 等状态.

在这个时候拿 Golang 写了个简单的文件服务器, 对于当前路径是目录的情况返回目录文件列表的 JSON, 会包含需要的属性, 顺便过滤掉不需要的文件. 这里本来想直接基于 http.FileServer 稍微改改, 然后魔改的时候遇到无限重定向的问题看了下源码的实现发现并不能这么玩 …

因为是用 <video> 实现的, MIME Type 服务端也有返回, 所以直接点击视频路径不会影响播放, 声音还是听得到的. 是时候解决画面了, 要把 <video> 请回来呢? 不是还有 <canvas> 嘛, ctx.drawImage(video, ...) 是可以直接把视频画在 <canvas> 上. 这里还能对画面做些处理加点特技.

用 video.ontimeupdate 事件, 间隔太久会一卡一卡的? 不是还有 window.requestAnimationFrame() 呐, 本来就是为了动画而准备的. 只要 video 有画面, canvas 也有画面.

进度条和其它控件可以自己做, 全屏, 全屏怎么办? 本来以为要自己折腾, 不过正好发现了 element.requestfullscreen() 方法. 这个方法要求 video 元素在页面内…嘛, 还有 <canvas> 嘛, 事实上这个方法所有 DOM 元素都有, 完全可以把某个 div 之类的东西全屏.

值得一提的是, <video> 还有 video.requestPictureInPicture() 方法, 可以开个小窗画中画. <canvas> 是没有这个, 但是 <video> 使用画中画的话也不要求元素在页面内, 所以不是什么大问题.

验证完了
验证完了

上图是目前的状态, 剩下的东西慢慢填.


做了一些改动, 重写了一个播放器组件:

新的播放器
新的播放器
全屏模式下
全屏模式下