725 字
4 分钟
本地文件怎么喂给 video

存档里全是本地文件。要让它们在 Electron 窗口里像网页一样播放,比想象麻烦。

为什么不用 file://#

渲染进程里直接 <video src="file:///D:/archives/xxx.mp4">,会遇到两个问题:

一是路径。存档根目录是用户自己选的,可能在任意盘符,中文路径、空格、特殊字符都有。手拼 URL 很容易漏转义。

二是安全。现代 Electron 默认不允许渲染进程随便读本地文件,绕过限制要么放宽配置(等于开门),要么改架构。

自定义协议#

最后用了一个注册协议:social-archiver://asset/?path=<绝对路径>。

有两个必须先做的事:

  1. 在 app.whenReady() 之前调 registerSchemesAsPrivileged
  2. 两个标志都要带上:standard 和 stream

少任何一个,<video> 拿到的响应就不支持流式读取,拖动进度条直接失败。standard 让它有正常的 URL 语义,stream 让它支持 Range。

Range 是重点#

视频能拖进度条,靠的是 HTTP 206 和 Content-Range。

这里有个分界:100MB。

小于 100MB 的文件,直接把整个文件读进内存 Buffer,自己实现 Range 解析和切片。简单、可靠,反正内存扛得住。

超过 100MB 的走流式。流式下 Range 要自己算,而且要考虑边界:bytes=0-、bytes=-500、超出文件长度、多段 Range——这些都得处理,不然播放器会贴心地让你看到”无法播放”。

那个不该踩的坑#

有段时间用过这个写法:

const stream = Readable.toWeb(fs.createReadStream(filePath));
return new Response(stream, { headers: { 'Content-Type': 'video/mp4' } });

看起来很现代:Node 的 Stream 转成 Web Stream,直接喂给 Response。

结果:视频能播,但拖进度条就断。

原因是这个转换出来的流是”一次性的”——它已经按顺序把数据推出来了,再去要第二段拿不到。Range 请求本质是”重新给我 0 到 1000 字节”,不是”接着上次的地方继续”。

现在这条写在交接文档里,用加粗标着。

其他细节#

MIME 类型:.mov 一律标 video/mp4。Chromium 对 QuickTime 容器不认,标错就不播。

缓存:加了长期缓存和 ETag。同一个文件反复打开不重复读盘。

路径校验:协议处理里要 path.resolve 之后检查是否在存档根目录内。不然就是任意文件读取漏洞——social-archiver://asset/?path=C:/Windows/System32/config/SAM 这种请求本来能成。

播不了的那些#

有些实况图是 HEVC 编码的,而应用启动时禁用了 GPU(历史上 WebGL 崩过几次)。结果是 Chromium 里解不了 HEVC。

这不是下载失败,是渲染限制。

所以卡片上有个兜底:检测到播放失败就显示「打开实况」,走 shell.openPath 交给系统播放器。判断依据是文件存在、有 ftyp 头、但 <video> 报错。


本地文件协议这件事,最花时间的不是写代码,是搞清楚为什么明明能播却不能拖。

本地文件怎么喂给 video
https://mssworld.me/posts/local-file-protocol/
作者
m.s.s
发布于
2026-11-03
许可协议
CC BY-NC-SA 4.0