如何为 mpv 编写第一个 JavaScript 脚本?JS 与 Lua 环境的关键差异
【免费下载链接】mpv🎥 Command line media player项目地址: https://gitcode.com/GitHub_Trending/mp/mpv
mpv 的脚本文档以 Lua 为主体,但 DOCS/man/javascript.rst 明确说明:JavaScript 支持与 Lua 支持"几乎完全一致",API 细节与通用脚本编写都参照 Lua 文档,只有少数加载方式、错误处理与语言特性上的差异。本文按照这条文档指引走一遍完整流程:确认当前 mpv 编译了 JavaScript 后端、把第一个.js脚本放到正确的位置、验证它确实被加载并生效,最后列出 JS 与 Lua 环境之间必须知道的关键差异。
先确认这个 mpv 带 JavaScript 后端
JavaScript 后端是可选的构建特性。在 meson.options 中它定义为javascript选项,类型是feature,默认值auto;meson.build 中会查找 MuJS 依赖,版本要求不低于 1.0.0,找不到则该特性为否。构建配置完成后,mpv 的构建摘要(summary)里会打印javascript: yes/no一项,用它核对当前构建是否启用。
运行时也有直接的症状:把脚本交给 mpv 加载时,若扩展名无法匹配到任何已编译的脚本后端,控制台会打印一条错误——按 DOCS/man/lua.rst "Script location" 一节的说法,要么是扩展名拼错了,要么是对应后端没有编译进你的 mpv。源码中的错误串为Can't load unknown script: <文件>(见 player/scripting.c)。对.js文件看到这条错误,就说明这个 mpv 缺 JavaScript 后端,需要先解决编译问题,后面所有步骤都以此为前提。
mpv 如何找到并加载 JS 脚本
javascript.rst 的第一条规则:脚本文件带.js扩展名时,mpv 会尝试把它作为 JavaScript 加载;除此之外,文档中所有 Lua 的脚本选项、脚本目录、加载方式同样适用于 JavaScript 文件。具体有:
- 自动加载:把脚本放进 mpv 配置目录的
scripts子目录,通常是~/.config/mpv/scripts/; - 命令行指定:通过
--script选项传入脚本路径; - 扩展名为
.disable的条目总是被忽略; - 目录也可以代表一个脚本:mpv 会在其中加载
main.js。若目录里同时存在main.lua和main.js,只会加载其中一个,具体是哪个取决于 mpv 内部实现且可能随时变化; - 脚本名由去掉扩展名、把所有非字母数字字符替换为
_得到(文档以my-tools.lua→my_tools为例,JS 文件同理),运行时可用mp.get_script_name()取到。
写第一个 JS 脚本
直接采用官方文档的例子:播放器被暂停时退出全屏模式。把下面内容保存为一个.js文件(文件名可以任意,本文以~/.config/mpv/scripts/unfull.js为例;末尾额外加了一行print,方便加载时确认):
function on_pause_change(name, value) { if (value == true) mp.set_property("fullscreen", "no"); } mp.observe_property("pause", "bool", on_pause_change); print("unfull.js loaded");这段代码里有两个知识点:
mp、mp.msg等模块不需要require。mp、mp.utils、mp.msg、mp.options、mp.input在 JS 环境中是预加载的,可直接使用——这正是与 Lua 的第一个差异。- 生命周期:脚本主体在启动时先执行一遍,随后 mpv 进入事件循环,你注册的观察者(如
mp.observe_property)由事件循环驱动。lua.rst 特别提醒:脚本与播放器初始化并行启动,顶层执行时部分属性可能还没有有意义的值,因此不要在脚本顶层直接读这些属性,而应在mp.observe_property或事件处理器中读取。
随后像平时一样启动 mpv 即可(video.mp4替换成你要播放的媒体文件;也可用mpv --script <.js 文件路径> video.mp4显式加载该脚本):
mpv video.mp4验证脚本真的运行了
- 加载成功:终端打印
unfull.js loaded。print是mp.msg.info的别名;按 lua.rst 对日志级别的说明,默认情况下除v、debug、trace外的消息都会显示,所以info级别可见。 - 行为符合文档示例:播放中进入全屏,然后暂停,播放器退出全屏。
- 加载失败:控制台打印错误(前文的
Can't load unknown script一类)。脚本加载失败不会阻止 mpv 继续启动,只是该脚本不生效。
脚本内部如果抛出 JavaScript 错误,可以用try { ... } catch(e) { ... }捕获;当错误是用Error(...)构造器创建时,e.stack提供堆栈跟踪,便于定位。
JS 与 Lua 环境的关键差异
除下述差异外,两侧 API 一致:javascript.rst "Scripting APIs - identical to Lua" 一节列出的mp.command、mp.commandv、mp.command_native、mp.get_property/mp.set_property家族、mp.register_event、mp.observe_property、mp.add_key_binding、mp.add_hook、mp.osd_message、mp.options.read_options、mp.input.get等函数在 JS 中签名与语义相同,细节以 Lua 文档为准。
| 方面 | Lua | JavaScript |
|---|---|---|
| 模块加载 | 需要require "mp.msg"等 | 五个模块预加载,无需任何 setup |
| 错误表示 | 返回nil,或value, error两个值 | 返回undefined;错误原因经mp.last_error()获取(仅部分函数提供,文档中标注(LE)) |
| 一次性定时器 | mp.add_timeout(seconds, fn) | id = setTimeout(fn, ms)(注意 Lua 用秒、JS 用毫秒) |
| 周期定时器 | mp.add_periodic_timer(seconds, fn) | id = setInterval(fn, ms) |
| JSON | mp.utils.parse_json/mp.utils.format_json | JSON.parse/JSON.stringify |
| 语言 | Lua | ECMAScript 5 |
两条转换规则:
- 标准 JS API 优先。
setTimeout、JSON.stringify直接可用;反向地,mp.add_timeout和mp.utils.format_json在 JS 环境中不存在。 - 语言级别是 ES5,脚本后端是 MuJS(一个兼容的极简 ES5 解释器)。例如
String.substring有实现,而常见但非标准的String.substr没有。写新语法前先确认 MuJS 的语言特性支持。
JS 环境还有几处 Lua 文档没有的机制:
dump:与print类似,但会递归展开对象和数组;mp.last_error():在更新了 last error 的 API 调用后,返回空字符串表示成功、非空字符串表示失败原因;- 文件函数:
mp.utils.read_file(fname [,max])、mp.utils.write_file(fname, str)、mp.utils.append_file(fname, str)。它们只接受文本内容,出错时抛出异常;write_file/append_file的路径必须以file://开头(防止参数写错的简单保护),例如mp.utils.write_file("file://~/abc.txt", "hello world"); - CommonJS
require(id):id总是按追加.js处理;以./或../开头的 id 相对发起require的脚本解析,其余 id 先按绝对路径(如/x/y、~/x)尝试,再按mp.module_paths数组顺序搜索全局模块。由于缺少fs、process等 node.js 核心模块,大多数 node.js 模块无法运行——这套机制是为共享 mpv 脚本设计的,不是 node.js 的替代品; init.js:mpv 为每个脚本初始化 JS 环境之后、加载脚本之前,会运行 mpv 配置目录根部的init.js,可用它统一更新所有脚本的环境(如mp.module_paths.push("/foo"))。注意:用--no-config启动时该文件被忽略;更新搜索路径要用push而不是整体重新赋值,否则会清掉已有的搜索路径。
边界与限制
- mpv 中的 JavaScript没有标准库:与 mpv 之外的一切交互都局限在可用 API 上,通常经由
mp.utils; - 语言停留在 ES5;MuJS 未实现的标准特性不可用;
mp.utils.file_info(path)与 Lua 一致,不展开~~/foo这类 meta 路径,其他 JS 文件函数会展开;- 定时器永远异步回调:
setTimeout(fn)在返回之前绝不会调用fn,回调发生在本次事件循环迭代末尾或之后的迭代,因此setTimeout(fn)也可以当作一次性的 idle 观察者使用。
第一个脚本跑通后,API 细节可以继续查 Lua 文档(JS 复用同一套 API);想看print、dump、定时器与require的默认实现,可以直接读 player/javascript/defaults.js。
【免费下载链接】mpv🎥 Command line media player项目地址: https://gitcode.com/GitHub_Trending/mp/mpv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考