现代简洁的本地视频播放器。优先适配 Windows,架构上按跨平台设计,可直接向 macOS / Linux 移植。
支持 MP4 / MKV / AVI / MOV / FLV / WMV / WEBM / TS / M3U8 等主流格式,具备播放列表管理、倍速播放、完整快捷键体系,并针对播放流畅性与资源占用做了专门优化。
Chromium 只能解码 MP4/MOV/WEBM 等少数容器,MKV、AVI、FLV、WMV、RMVB、TS 直接丢给 <video> 是播不了的。很多同类项目在这里妥协,只支持浏览器原生格式。
Starlight 的做法是在 Electron 与播放器之间加了一层 媒体管线(Media Pipeline):用本地 ffmpeg 把不兼容的容器实时转成 HLS 分片,交给 hls.js 播放。三种代价递增的策略,按需选用、失败自动降级:
| 策略 | 适用场景 | 开销 | 首帧延迟 |
|---|---|---|---|
direct 原生直连 |
MP4/MOV/WEBM,视频 H.264/VP8/VP9/AV1,音频 AAC/MP3/Opus/Vorbis/FLAC/PCM(无音轨同样直连) | 零额外 CPU | 即时 |
hls-copy 无损重封装 |
MKV/AVI/FLV/TS,视频 H.264 + 音频 AAC/MP3 | 仅换封装,速度 50~200× | 1~2 秒 |
hls-remux 视频复制 + 音频转码 |
视频 H.264,音频为 AC3/DTS/FLAC 等 | 中等 | 2~5 秒 |
hls-transcode 全量转码 |
H.265 / VP9 / MPEG-2 等不兼容编码 | 较高,优先硬件加速 | 视片长 |
关键点:只有真正需要时才付出转码代价。绝大多数 MKV(H.264 + AAC 压制)走 hls-copy,几百毫秒就能开始播放,画质零损失。
播放过程中若某个策略失败(编码嗅探不准、文件损坏、显卡驱动异常),引擎会沿降级阶梯自动换下一种方式,并给出提示,无需用户干预。
- Node.js ≥ 18
- ffmpeg / ffprobe(播放 MKV/AVI/FLV 等格式必需)
# Windows
winget install Gyan.FFmpeg
# 或 choco install ffmpeg
# macOS
brew install ffmpeg
# Linux
sudo apt install ffmpeg没装 ffmpeg 也能用——MP4/MOV/WEBM 依旧正常播放,只是 MKV/AVI/FLV 会提示需要解码环境。 打包发布时可以把 ffmpeg 二进制放进
resources/bin/<platform>-<arch>/,应用会优先使用它,用户无需自行安装。
npm install # 会自动把 hls.js 拷贝到渲染层 vendor 目录
npm start # 启动应用
npm run dev # 带开发者工具npm run demo生成 demo/starlight-player-demo.html,双击即可在浏览器中查看界面与交互。
(该 Demo 走纯浏览器路径,只能播放浏览器原生格式与网络 HLS;完整格式支持需运行桌面端。)
npm test # 管线自检:策略决策 / HLS 产出 / Range 服务 / 字幕转换(不依赖 Electron)
npm run verify # 播放自检:真实启动应用,验证画面与字幕确实渲染出来
npm run lint # ESLint(核心规则 no-undef,专门防「调用了未定义的标识符」)
npm run capture # Electron 离屏截图验证 UI 渲染,产物在 docs/preview/
npm run check:pack # 打包就绪度:require 解析 / 资源引用 / files 覆盖 / 图标齐全
npm run check:arch # 架构约定:平台判断收口 / 渲染层隔离 / 媒体管线独立性
npm run check:theme # 主题令牌:变量名对齐 + 两套主题的 WCAG 对比度
npm run measure # 实测资源占用:空闲 / 直连播放 / 重封装播放三场景对比
npm run measure:speed # 实测高倍速:1×/2×/4× 逐档采样推进速率 / 丢帧 / 缓冲
npm run verify:install # 安装包端到端:静默安装 → 快捷方式 → 卸载登记 → 启动播放 → 静默卸载七个检查各有分工,别只跑一个:
-
npm test断言「管线产出合法」——用真实 ffmpeg 生成 7 种编码组合的样本, 检查策略决策、HLS 清单与分片、Range 请求、非法 token 与目录穿越拦截、 字幕发现与 WebVTT 转换、音轨与章节解析、按平台的 ffmpeg 安装指引, 以及设置/续播记录的越界值清洗、目录截断提示与自检临时目录清理,共 126 项。不依赖 Electron。 -
npm run verify断言「画面真的出来了」——启动真实应用(无头模式, 窗口不显示、不抢焦点),让 6 种场景各走一遍完整的 IPC → 媒体服务 → ffmpeg → hls.js →<video>链路,回读解码状态并截图。判定标准是
videoWidth/videoHeight非零 且 时间轴推进 且 无mediaError。 只看currentTime是不够的——有声音无画面时时间轴照样走,这正是最容易漏掉的失败模式。 -
npm run check:pack断言「打包后能启动」——打包最常见的翻车不是构建失败, 而是构建成功却一启动就白屏。该脚本静态走查所有相对require、index.html的资源引用、build.files覆盖度与图标齐全性。 -
npm run verify:install断言「用户拿到安装包后能装上、能启动、能卸干净」—— 分发前最后一道闸。它会静默安装到临时目录,校验开始菜单与桌面快捷方式、 「程序和功能」里的卸载登记项,然后启动已安装的程序跑一遍真实播放, 最后静默卸载并确认安装目录、快捷方式、注册表都清理干净。这一步在开发机上永远暴露不出来——开发机一直是
npm start跑源码。 脚本会先清理上一轮的历史安装,可反复运行。 -
npm run check:theme断言「浅色主题没有漏样式」——两件事都靠算而不是靠看:一是变量名对齐:浅色主题必须覆盖深色主题的全部配色变量。少覆盖一个, 那个组件就会在浅色下沿用深色值,表现为局部没换过来,而这正是主题切换最容易漏的坑。 二是对比度:按 WCAG 计算文字/背景配对,正文要求 ≥ 4.5:1、控件态 ≥ 3:1, 半透明令牌先与背景合成再计算(直接拿 rgba 算会得到错误结果)。
该检查顺带修掉了深色主题原有的两处不达标:三级灰在白底/深底上的对比度不足, 以及白字压在强调色填充按钮上只有 3.5:1。
-
npm run check:arch断言「架构约定没有被绕过」——把只写在文档里、 靠人记着的规则变成可执行的检查。约定一旦只存在于文档,就会慢慢被绕过, 而这类漂移往往到跨平台构建时才暴露。五项:- 平台判断收口:
process.platform只允许出现在已登记的文件中 (窗口装饰、菜单、ffmpeg 定位、进程终止),每项都写明理由。 业务逻辑里出现它,就说明平台差异泄漏了。 - 渲染层隔离:
src/renderer/不得出现require/module.exports/process.*/__dirname—— 一切能力经preload.js白名单暴露。 - 媒体管线独立性:
media-server/hls-jobs/media-probe不得require('electron'),它们要能在 CLI 与单测中独立运行。 - 配置注入:
ffmpeg-locate的查找优先级里写了「用户配置」最优先, 但它不依赖 store,必须由主进程显式注入 —— 断言这一步没有被漏掉。 (曾经漏过:文档写了优先级、设置界面也有输入框,实际却没人注入, 用户填的自定义路径静默失效。) - 渲染层代次约定:
teardown()只释放资源、不得改动loadToken; 作废在途加载要用cancelPending();playIndex必须先认领代次再 teardown。 (曾经在 teardown 里自增,逼得降级重试把代次「写回去」, 并发时覆盖更新的加载 → 快速切换文件弹出「无法播放该文件:已取消」的误报。)
清单里登记了但实际已不再使用的文件也会报错,避免清单本身腐化。
- 平台判断收口:
-
npm run measure:speed断言「高倍速播放确实流畅」—— 1×/2×/4× 逐档采样, 核心指标是实际推进速率(媒体时间增量 ÷ 墙钟时间,应等于设定倍速; 明显偏低说明媒体时钟被音频渲染或缓冲饿死拖慢),外加丢帧率、waiting 次数与缓冲余量。 每档倍速都走应用自身的setSpeed路径,因此测的是真实播放链路而非裸<video>。
无 GPU 的环境(CI / 容器 / 远程会话)里 Electron 的 GPU 进程会直接 FATAL 退出,
npm run verify会识别出这个特征并自动改用--no-sandbox --disable-gpu --in-process-gpu重试。 若需强制指定其他开关,仍可通过环境变量覆盖:STARLIGHT_EXTRA_FLAGS="--no-sandbox --disable-gpu --in-process-gpu" npm run verify
npm run clean # 清理构建产物与缓存(release/ 等),保证全新构建
npm run dist:win # Windows → NSIS 安装包 + 便携版
npm run dist:win:fresh # 清理 + 打包,一条命令完成
npm run dist:portable # 仅 Windows 便携版,不依赖 electron-builder 的 PowerShell 子流程
npm run dist:linux # Linux → AppImage + deb(需要联网下载 appimage / fpm 工具链)
npm run dist:linux:portable # Linux → tar.gz,不依赖额外工具链
npm run dist:mac # macOS → DMG(x64 + arm64,只能在 macOS 上构建)
npm run icons # 重新生成三平台图标(产物需入库以保证打包可复现)
dist:portable是 Windows 的退路:它只用 Node API +@electron/asar+yazl产出便携目录与 ZIP,完全不经过 electron-builder。受限环境(企业策略 / CI 容器 禁止 PowerShell 子进程)里dist:win跑不通时用它。 代价是不产 NSIS 安装器、不注册文件关联。
dist:linux:portable的定位不同:Linux 侧的障碍不是 PowerShell,而是 AppImage / deb 需要额外下载 appimage / fpm 工具链。它只取dir目标再打成 tar.gz, 解压即用。两者别混为一谈。
| 宿主 → 目标 | 能否构建 | 说明 |
|---|---|---|
| Windows → Windows | ✅ | 主战场 |
| Windows → Linux | ✅ | node scripts/package-win.mjs --linux dir --out <目录> 实测可产出完整目录包 |
| Windows → macOS | ❌ | electron-builder 直接拒绝:Build for macOS is supported only on macOS |
| Linux/macOS → Windows | 未实测 | 理论上可行,但 Windows 目标通常仍需在 Windows 上产出安装包 |
因此 macOS 的打包配置只能在 macOS 上验证。在 Windows 上误跑 npm run dist:mac
只会得到上面那句报错——不是配置问题,是工具链的硬限制。
scripts/package-win.mjs 是通用包装脚本,名字里的 "win" 只是默认平台:
--linux / --mac 都会原样透传给 electron-builder。
Windows 包随附 ffmpeg,Linux 包不随附,这是刻意的:
- 本项目的 ffmpeg 定位优先级是
用户配置 > 环境变量 > 随包 > 系统 PATH, Linux 上几乎总能从系统 PATH 找到; - 发行版仓库里的 ffmpeg 通常比任何随包方案都新,随包反而容易过时;
- 随包一个旧版本会造成「Windows 能播、Linux 不能」这类极难排查的支持负担 —— 应用的策略链与探测逻辑是针对较新 ffmpeg 调优的。
缺少 ffmpeg 时应用会明确提示,并给出当前平台的安装命令
(见 src/shared/constants.js 的 ffmpegInstallHint)。要随附的话,
把二进制放进 resources/bin/linux-<arch>/ 即可,构建时会自动带上并通过平台/架构校验。
chrome-sandbox需要 setuid root。 Chromium 沙箱依赖该权限位,而用户以普通身份 解压 tar.gz 时它不会生效(只有 root 解压才能还原)。此时应用会报沙箱错误, 需要手动执行一次sudo chown root:root chrome-sandbox && sudo chmod 4755 chrome-sandbox, 或改用 AppImage / deb 安装包(安装时自动处理)。 打包脚本会把这段说明写进归档内的README-LINUX.txt。
打包产物不含 node_modules。 渲染层通过 <script src="vendor/hls.min.js"> 加载 hls.js,
src/ 中没有任何 require() 第三方包的代码,因此生产依赖为空,包体只包含应用代码与
随包分发的 ffmpeg(若放置于 resources/bin/<platform>-<arch>/)。
图标由 scripts/make-icons.mjs 纯 Node 生成(手写 PNG 编码 + ICO/ICNS 打包,
不依赖任何图像库),一次产出 Windows / macOS / Linux 三平台所需的全部格式。
scripts/package-win.mjs 做两件事,都源于实机踩到的坑:
- 压制 npm 的 warning。electron-builder 收集依赖树时会执行
npm list --production --json;npm 10 对该写法先输出一行npm warn config production Use --omit=dev instead.,把 JSON 污染掉, 于是打包报No JSON content found in output并中断。 脚本用NPM_CONFIG_LOGLEVEL=error消除这一行。 - 跨平台地设置环境变量。Windows 上 npm script 由 cmd.exe 执行,
不支持
VAR=value cmd这种 POSIX 内联写法,所以必须用 Node 派发。
另外还有一个无法用脚本解决的环境前提,见下节。
electron-builder 在 Windows 上收集依赖树时,会把 npm 调用包进
powershell.exe -EncodedCommand(规避 CVE-2024-27980 的命令注入)。
而本机若执行策略是 Restricted,npm 会被解析到 Node 自带的 npm.ps1,
直接报「因为在此系统上禁止运行脚本」,依赖树收集拿到空输出,
最终同样表现为 No JSON content found in output。
排查结论(都已实测):
npm在 PowerShell 里优先解析.ps1,即使 PATH 里排在前面的目录只有npm.cmd也会被.ps1抢走NPM_CONFIG_LOGLEVEL、PSExecutionPolicyPreference环境变量、补全PATHEXT都无效
因此需要放开当前用户的执行策略:
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
# 仅当前用户、无需管理员;想还原:Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Undefined判断是不是这个问题:
powershell.exe -NoProfile -Command "npm list --omit=dev --json", 若报「禁止运行脚本」即是。注意该报错藏在 stderr 的 CLIXML 里, electron-builder 只会报出No JSON content found in output,容易被误判成 npm 配置问题。
一条命令产出可双击运行的安装程序:
npm run dist:win # 构建 NSIS 安装包 + 便携版
npm run verify:install # 验证:安装 → 快捷方式 → 卸载登记 → 启动播放 → 卸载依赖:npm install 时已装好(electron + electron-builder,均为 devDependencies)。
首次构建需联网拉取 electron 与 NSIS 工具链,缓存到 %LOCALAPPDATA%\electron-builder\Cache;
离线环境可用 ELECTRON_BUILDER_CACHE 预置。机器上不需要装 WiX、Visual Studio 或任何签名工具。
产物(默认输出到 release/;若该目录被杀软占用删不掉,可加
--out dist 换一个目录,验证脚本会同时搜索这两个位置):
| 文件 | 说明 |
|---|---|
Starlight-Player-Setup-1.0.0.exe |
NSIS 安装程序(约 158 MB),双击运行 |
Starlight-Player-Portable-1.0.0.exe |
便携版单文件(免安装,直接运行) |
*.exe.blockmap |
增量更新用的块映射,分发时可不带 |
安装后:默认装到 %LOCALAPPDATA%\Programs\Starlight Player(每用户安装,不需要管理员权限),
自动创建开始菜单与桌面快捷方式,并在「程序和功能」中登记卸载项(含版本、发布者、图标)。
运行时无需用户配置任何环境——ffmpeg / ffprobe 已随包内置于
<安装目录>\resources\bin\,Node 与 Chromium 由 Electron 自带,
src/ 中也没有任何第三方 require(),因此打包后不含 node_modules。
应用名 / 版本 / 图标等信息全部集中在 package.json,不需要改构建脚本:
| 想改什么 | 改哪里 |
|---|---|
| 应用显示名(窗口标题、快捷方式、卸载列表) | productName |
| 版本号(文件名、卸载列表、安装包属性) | version(遵循 semver) |
| 卸载项唯一标识(升级时判断是否同一应用) | build.appId |
| 图标 | build.win.icon → resources/icons/icon.ico(用 npm run icons 重新生成) |
| 快捷方式名称、是否建桌面快捷方式、装完是否启动 | build.nsis.shortcutName / createDesktopShortcut / runAfterFinish |
| 安装范围(每用户 / 每台机器) | build.nsis.perMachine |
| 随包二进制(ffmpeg 等) | build.extraResources,源目录 resources/bin/<platform>-<arch>/ |
| 文件关联(双击视频用本应用打开) | build.fileAssociations |
换版本号后旧的
release/产物不会自动清理,分发前建议确认目录里没有同名的旧安装包。
| 现象 | 原因与解决 |
|---|---|
No JSON content found in output |
有两种来源:① npm 的更新提示 / --production 告警污染了 npm list 的 JSON,已由 scripts/package-win.mjs 通过 NPM_CONFIG_LOGLEVEL=error 解决;② 执行策略为 Restricted,见上文「Windows 构建环境前提」 |
打包报 EBUSY / 旧产物删不掉 |
release/ 里的 app.asar 常被杀软实时监控占用,进程列表里查不到持有者。npm run clean 会先改名探测再删:改不动立即报告并跳过(不会卡住),改得动才进入删除。仍删不掉时用 node scripts/package-win.mjs --win --out dist-<时间戳> 换一个输出目录。注意 --out 若不以 dist- 开头会被告警——.gitignore 只忽略 release/ 与 dist-*/,别的名字有误提交风险 |
configuration.linux.desktop should be null |
electron-builder 26 起 linux.desktop 改为 { entry, desktopActions } 结构,MIME 类型改用 linux.mimeTypes |
| 交叉构建的包里混入了其他平台的二进制 | extraResources 里的 ${platform} 宏展开的是宿主平台而非目标平台(见 app-builder-lib/out/util/macroExpander.js,case "platform": return process.platform;目标平台对应 ${os})。因此必须按平台分别写死路径。package-win.mjs 会按二进制文件头校验平台与架构(scripts/lib/binary-format.mjs)并拦下这种包 |
Build for macOS is supported only on macOS |
electron-builder 的硬限制,macOS 目标无法交叉构建。Linux 目标可以(--linux dir 实测通过) |
| Windows 打包报 PowerShell 相关错误 | electron-builder 在 Windows 上会把 npm 调用包进 powershell.exe -EncodedCommand(规避 CVE-2024-27980),受限环境下需确保可派生 PowerShell |
| 首次打包卡在下载 | 需要联网拉取 electron 与 NSIS 工具链;离线环境可预置 ELECTRON_BUILDER_CACHE |
下载 Electron 报 Response code 502 |
网络/代理抖动。可改用镜像:ELECTRON_MIRROR=https://registry.npmmirror.com/-/binary/electron/ npm run dist:linux:portable。已缓存且校验和匹配时不会重新下载 |
| Linux 包解压后无法运行 | 大概率是丢了可执行位。本项目的 tar 写入器(scripts/lib/tar.mjs)会显式写入权限位,并用系统 tar 独立回读校验;若自行用 tar 打包,Windows 上产出的归档会把所有文件记成 0644 |
- 播放 / 暂停、上一集 / 下一集、拖拽定位
- 进度条:缓冲区间可视化、悬停时间预览、拖拽预览
- 音量滑块 + 静音,音量随鼠标悬停展开
- 倍速 0.25× ~ 4×(10 档预设),高倍速全程流畅不丢帧(见下)
- 画面比例:适应 / 铺满 / 拉伸 / 原始
- 全屏、画中画、迷你模式(置顶小窗)、截图保存
- 续播记忆:同一文件下次打开自动从上次位置继续(距结尾 10 秒内视为看完,不再续播)
- 任务栏 / Dock 进度条同步
高倍速卡顿的根源有三个,分别处理:
- 音频时间拉伸(WSOLA)跟不上。
preservesPitch的变调保持在 2× 以上 开销陡增且质量劣化,音频渲染一旦落后还会反过来拖慢媒体时钟 —— 表现就是画面一顿一顿。 超过 2× 后自动改用简单重采样(音调变高但开销小),1×/2× 听感不变。 - 音频渲染本身成为瓶颈。达到 4× 自动静音,把音频从关键路径上彻底摘掉; 回落到 4× 以下恢复原本偏好,期间按 M 手动取消静音也会被尊重(OSD 有提示)。
- HLS 缓冲被「播放列表快照」锁死。hls.js 对 EVENT 播放列表的重载极其保守,
它能看到的分片被首帧时抓到的快照限死,缓冲顶到快照末尾就饿死 —— 与
maxBufferLength设多大无关。两处修复: 媒体服务对清单响应发送no-store(分片仍长缓存,分片写出后不可变); ffmpeg 作业退出时(清单已完整)强制loadSource刷新一次,hls.js 转为 VOD 语义后立即把缓冲填满。此外缓冲目标按当前倍速等比放大 —— 40s 媒体缓冲在 4× 下墙钟只剩 10s,不放大就会周期性 waiting。
验证方式:npm run measure:speed 在 1×/2×/4× 逐档采样实际推进速率(应等于
设定倍速)、丢帧率、waiting 次数与缓冲余量,走应用自身的 setSpeed 路径而非裸改
playbackRate。优化前后对比见下表(沙箱无 GPU、软件渲染,绝对值偏保守):
| 场景 | 档位 | 优化前实际速率 | 优化后实际速率 | 优化前丢帧率 | 优化后丢帧率 |
|---|---|---|---|---|---|
| MP4 直连 | 4× | 3.98 | 3.99 | 42.8% | 2.8% |
| MKV 重封装 | 2× | 0.92(已饿死) | 1.99 | 1.4% | 1.0% |
| MKV 重封装 | 4× | 0.00(完全停滞) | 3.97 | - | 2.7% |
转码(hls-transcode)路径的吞吐受 ffmpeg 编码速度限制,本身达不到 4× 实时, 高倍速下仍会周期性等待 —— 这是转码策略的固有约束,不是回放层问题。
- 深色 / 浅色两套主题,一键切换:标题栏右侧图标循环「跟随系统 → 浅色 → 深色」,或走菜单「视图 → 主题」
- 跟随系统偏好:
prefers-color-scheme变化时即时响应,无需重启 - 选择持久化:模式写入设置文件,重启后按模式恢复,首帧就是正确主题
- 窗口底色、Windows 标题栏叠加层、原生对话框与滚动条一并跟随,不出现「内容切了外壳没切」
实现要点,两处最容易踩的坑:
首帧不能闪。IPC 是异步的,等主进程回话时首帧已经画完——深色用户会看到一帧白屏。 因此主进程把持久化的模式放进 URL 查询串,
theme-init.js在<head>里同步读取并 打上data-theme,窗口底色与标题栏也按同一主题创建。注意 CSP 是script-src 'self', 这个脚本必须是外部文件,内联会被拦掉。视频叠层不跟随主题。控制栏、字幕、OSD 压在实际画面上,画面内容不可控, 浅色主题下它们必须保持「浅字深底」。因此令牌分三组: 尺寸与动效、视频叠层(
--stage-*,两套主题共用同一取值)、配色(唯一被覆盖的一组)。 切换主题只改配色,叠层不受影响。
- 音轨切换:MKV 双语版(原声 + 配音)可自由选择音频流,切换时保持播放位置与播放状态
- 章节:解析容器章节元数据,在进度条上渲染分隔标记,悬停显示章节名,支持快速跳转
实现要点:Chromium 的
<video>只能播放容器里的默认音轨,无法选择其他音频流。 因此切换音轨必须由 ffmpeg 重新映射(-map 0:a:<n>)并换封装,播放策略会自动从direct升级到 HLS 路径。文件只有单条音轨时不会做这个降级。
- 外挂字幕自动识别:与视频同目录、同主文件名的
.srt/.ass/.ssa/.vtt自动加载 - 多语言后缀:
movie.zh-CN.srt/movie.en.srt会作为多个轨道列出,语言标记按 BCP-47 归一化 - 内嵌字幕轨:MKV 等容器内的字幕流可直接选择(抽取为 WebVTT 后渲染)
- 时间轴偏移:±0.1s / ±1s 微调,解决字幕与片源不同步——这是实际观影中最常遇到的问题
- 字号四档:小 / 中 / 大 / 特大,随窗口尺寸自适应
- 自绘渲染层:多重描边 + 投影,保证在亮暗画面上都清晰可读;字幕随控制栏显隐自动升降
- 打开文件、打开文件夹(递归扫描,最多 4 层 / 3000 个文件)
- 拖拽文件或文件夹到窗口任意位置导入
- 拖拽条目调整顺序
- 关键词筛选
- 循环模式(关 / 列表循环 / 单个循环)、随机播放
- 列表持久化,下次启动自动恢复
- 时长懒加载(后台串行 ffprobe,不阻塞界面)
| 按键 | 功能 | 按键 | 功能 |
|---|---|---|---|
Space / K |
播放 / 暂停 | F |
全屏 |
← → |
快退 / 快进 5s | Esc |
退出全屏 / 关闭弹层 |
Shift+← → |
快退 / 快进 30s | [ ] |
减速 / 加速 |
J / L |
后退 / 前进 10s | Shift+[ ] |
恢复 1.0× |
↑ ↓ |
音量 ±5% | C |
截图 |
M |
静音 | A |
画面比例 |
V |
显示 / 隐藏字幕 | G |
切换到下一条字幕轨 |
Z / X |
字幕延迟 ∓0.1s | Shift+Z X |
字幕延迟 ∓1s |
B |
音轨切换菜单 | , . |
上一章 / 下一章 |
N / P |
下一个 / 上一个 | Ctrl+O |
打开文件 |
0–9 |
跳转到 0%~90% | Ctrl+Shift+O |
打开文件夹 |
Home / End |
片头 / 片尾 | Ctrl+U |
打开网络地址 |
R |
循环模式 | Ctrl+B |
显示 / 隐藏播放列表 |
H |
随机播放 | Ctrl+Shift+V |
字幕开关 |
Tab |
显示 / 隐藏播放列表 | Ctrl+Shift+L |
打开字幕文件 |
? |
快捷键一览 | Ctrl+← → |
上一章 / 下一章 |
播放器长时间挂机很容易吃满 CPU 和内存,这里做了几件事:
| 优化 | 说明 |
|---|---|
| 策略优选 | 能直连就直连,能重封装就不转码。避免为了一致性把一切都转码。 |
| 边转边播 | HLS 播放列表使用 event 类型,ffmpeg 产出首个分片即可开播,首帧延迟与转码总时长解耦。 |
| 硬件加速 + 自动回退 | 转码默认尝试 -hwaccel auto(D3D11VA / VideoToolbox / VAAPI),失败自动去掉重试,兼顾性能与兼容性。 |
| 限制回退缓存 | hls.js backBufferLength: 30、maxBufferSize: 60MB,长片播放内存占用保持平稳。 |
| 后台暂停 | 窗口最小化 / 隐藏时暂停播放,避免无意义的解码开销。 |
| 进程清理 | 切换文件、关闭窗口、应用退出时立即 kill ffmpeg 进程(Windows 用 taskkill /T 连同子进程),并清理临时分片。 |
| 懒探测 | 播放列表时长串行后台探测,一次只跑一个 ffprobe。 |
| 按需重绘 | 进度更新走 requestAnimationFrame;列表时长刷新只改文本节点,不重绘整个列表。 |
| 原子持久化 | 状态写入使用「临时文件 + rename」,避免异常退出损坏配置。 |
Starlight/
├─ src/
│ ├─ main/ # 主进程(Node 侧)
│ │ ├─ main.js # 生命周期、窗口、IPC 路由
│ │ ├─ menu.js # 应用菜单与带修饰键的快捷键
│ │ ├─ preload.js # contextBridge 白名单桥
│ │ ├─ media-server.js # 本地回环 HTTP 流服务(Range / HLS / 目录映射)
│ │ ├─ media-probe.js # ffprobe 探测(策略决策在 shared/constants.js)
│ │ ├─ hls-jobs.js # ffmpeg 后台作业管理
│ │ ├─ subtitles.js # 字幕发现与 WebVTT 抽取
│ │ ├─ scan-dir.js # 目录递归扫描(黑名单 / 层级上限 / 截断)
│ │ ├─ proc.js # 结束进程树(各平台杀法不同)
│ │ ├─ ffmpeg-locate.js # ffmpeg/ffprobe 跨平台定位
│ │ └─ store.js # 设置 / 播放列表 / 续播持久化
│ ├─ renderer/ # 渲染进程(浏览器侧)
│ │ ├─ index.html # 界面结构
│ │ ├─ theme-init.js # 首帧前同步应用主题(防闪烁)
│ │ ├─ styles.css # 视觉系统(三组 CSS 变量驱动,双主题)
│ │ ├─ app.js # 播放引擎 + UI 逻辑
│ │ └─ vendor/hls.min.js # hls.js(由 postinstall 拷贝)
│ └─ shared/
│ └─ constants.js # 主进程与渲染层共用的格式能力表 / 策略决策
├─ scripts/
│ ├─ vendor.mjs # 拷贝 hls.js
│ ├─ build-demo.mjs # 生成单文件 Demo
│ └─ check-theme.mjs # 主题令牌对齐与对比度校验
├─ docs/ARCHITECTURE.md # 架构与跨平台移植说明
└─ resources/bin/ # 随包分发的 ffmpeg(可选)
架构上已经把平台差异收敛到三个边界,移植时不需要改动业务代码:
ffmpeg-locate.js— 二进制定位。新增平台只需补充「查找路径表」。main.js的窗口配置 — 标题栏样式、交通灯位置、菜单角色按process.platform分支。preload.js— 渲染层唯一的外部依赖,保证渲染层零 Node API。
详细说明、待办项(macOS 公证、Linux MPRIS、libmpv 后端)见 docs/ARCHITECTURE.md。
- HLS 重封装的分片会写入系统临时目录,长片(>2 小时)转码会占用相应磁盘空间,退出应用时自动清理。
- 转码模式下的拖动 受限于已转换完成的区间;无损重封装模式下,ffmpeg 通常数秒内完成,可全片随意拖动。
- 图形字幕(PGS / VobSub)不支持。这类字幕是图片而非文本,无法转成 WebVTT;需要 OCR 或图形渲染后端(见 libmpv 后端规划)。
- 浏览器 Demo 版本无法播放 MKV/AVI/FLV,也不支持字幕转换 —— 这两者都依赖 Node 侧的 ffmpeg,是架构决定的,不是实现缺陷。
MIT