mpv JSON IPC 协议完全解析:通过 Unix Socket 与命名管道远程控制播放器
【免费下载链接】mpv🎥 Command line media player项目地址: https://gitcode.com/GitHub_Trending/mp/mpv
本文以 mpv 官方 IPC 文档(DOCS/man/ipc.rst)为主体,系统讲解 mpv 的 JSON IPC 控制协议:如何用--input-ipc-server/--input-ipc-client建立连接、消息格式与request_id机制、异步命令、属性观察、UTF-8 边界问题与 JSON 扩展语法,并结合 input/ipc.c、input/ipc-unix.c、input/ipc-win.c 等源码实现,帮助读者既能快速上手 shell 控制,又能深入理解协议在 mpv 内部的落地方式。
1. IPC 是什么、不是什么
mpv 支持外部程序通过基于 JSON 的 IPC 协议来控制播放器:客户端连接到一个 socket(Unix 域 socket 或命名管道),向播放器发送命令、接收回复与事件。启用方式有两个选项:
--input-ipc-server=<path>:在指定路径(Unix socket 或命名管道)上监听,供任意多个客户端连接;--input-ipc-client=fd://<N>:不创建 socket,而是把继承的某个文件描述符当作一条已经accept()到的连接使用(Windows 下还支持handle://<N>)。
安全边界必须牢记:官方文档明确警告,IPC 不是一个安全的网络协议——没有认证、没有加密,且暴露了诸如run这类可执行任意系统命令的危险命令。它的设计用途就是本地控制播放器,地位等同于 MPlayer 时代的 slave 协议。因此任何跨网络或跨用户的暴露都不可接受。
两个选项的注册定义在 options/options.c(第 917–918 行)中:
{"input-ipc-server", OPT_STRING(ipc_path), .flags = M_OPT_FILE}, {"input-ipc-client", OPT_STRING(ipc_client)},注意input-ipc-server带有M_OPT_FILE标志,路径会经过mp_get_user_path()展开(见 input/ipc-unix.c 中mp_init_ipc()),因此支持~、$等展开规则。完整的选项说明(含 Linux 抽象命名空间@前缀、Windows 下\\.\pipe\前缀自动补全等行为)见 DOCS/man/options.rst 中--input-ipc-server/--input-ipc-client条目。
从 input/ipc-unix.c 的mp_init_ipc()可以印证文档中的行为细节:--input-ipc-client的取值必须以fd://开头并跟一个整数 FD,否则打印Invalid IPC client argument并放弃该特性。
2. 命令行实战:socat 与 Windows 命令提示符
2.1 Linux / Unix:socat
假设 mpv 以如下方式启动:
mpv file.mkv --input-ipc-server=/tmp/mpvsocket即可用 socat 发送 JSON 命令并读取回复(socat 在 stdin/stdout 与 mpv socket 连接之间搬运数据):
> echo '{ "command": ["get_property", "playback-time"] }' | socat - /tmp/mpvsocket {"data":190.482000,"error":"success"}也可以发送 input.conf 风格的纯文本命令:
> echo 'show-text ${playback-time}' | socat - /tmp/mpvsocket文本命令不会在 socket 上返回回复(该示例命令只是把播放时间显示到 OSD 上)。
想让 mpv 启动后不立即退出、可以持续被控制,可配合--idle选项启动(不加载文件)。
2.2 Windows:命名管道
Windows 上测试更困难:Cygwin/MSYS2 的 socat 端口不理解命名管道;echo只能发命令、收不到回复。假设:
mpv file.mkv --input-ipc-server=\\.\\pipe\\mpvsocket在命令提示符中发送命令:
echo show-text ${playback-time} >\\.\pipe\mpvsocket要像 Linux 那样同时双向读写,必须编写使用 overlapped file I/O 的外部程序(或 .NET 的NamedPipeClientStream之类的封装)。另外文档提到一个实用技巧:可以用 PuTTY 把该管道当作“串口”设备打开,无需写代码即可交互式测试。
从实现侧看,input/ipc-win.c 的ipc_thread()用CreateNamedPipeW创建管道,状态为PIPE_TYPE_MESSAGE | PIPE_READMODE_BYTE | PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS——即兼容消息模式与字节模式客户端。同时源码中create_restricted_sd()构造了 SDDL 安全描述符,只允许当前用户在当前(或更高)完整性级别的进程读写管道,这是对"无认证协议"在本地层面的最小兜底。
3. 协议规范:消息格式、回复与事件
3.1 编码与分帧
协议使用 RFC-8259 定义的 UTF-8 JSON,但有一个例外:不允许用\u转义序列构造代理对(surrogate pairs)。为避免冲突,代码点 U+0020 及以上的全部字符都应直接以 UTF-8 编码。极端情况下 mpv 可能输出损坏的 UTF-8(见第 6 节)。
- 每条命令、回复、事件之间以换行符
\n分隔; - 每条消息必须以
\n结尾,且消息内部不能出现\n——实际效果是:消息在发送前应压缩为单行(minified)JSON。
3.2 发送命令
客户端发送形如:
{ "command": ["command_name", "param1", "param2", ...] }其中command_name是命令名,参数必须是原生 JSON 值(整数、字符串、布尔值等)。
mpv 会回复命令是否执行成功,并附一个可能为null的命令返回数据字段:
{ "error": "success", "data": null }3.3 事件推送
mpv 也会主动向客户端推送事件:
{ "event": "event_name" }event_name是事件名,还可能携带事件专属的附加字段。
3.4 request_id:把回复和命令配对起来
由于事件可能在任意时刻发生,有时很难判断哪条回复对应哪条命令。命令可携带可选的request_id,它会原样拷贝进回复;mpv 不解释其含义,仅供请求方使用。要求是:request_id必须是整数(无小数、范围-2^63..2^63-1的数);其他类型目前会给出警告,未来版本将直接报错。
示例请求与回复:
{ "command": ["get_property", "time-pos"], "request_id": 100 }{ "error": "success", "data": 1.468135, "request_id": 100 }未指定request_id时,回复中会被置为 0。
源码印证:input/ipc.c 的json_execute_command()中,reqid_node存在时直接mpv_node_map_add(..., "request_id", reqid_node)原样回填,否则写入 int64 的 0;对非整数的request_id会打印"'request_id' must be an integer. Using other types is deprecated and will trigger an error in the future!",与文档描述逐字对应。
3.5 非 JSON 文本命令与注释
如果一行(跳过空白后)的首字符不是{,mpv 会把整行当作input.conf 风格的非 JSON 文本命令处理(等价于客户端 API 中的mpv_command_string())。以#开头的行和空行被忽略。
嵌入的 0 字节当前会终止当前行,但文档提醒:不要依赖这个行为。
对应源码在 input/ipc.c 的mp_ipc_consume_next_command():先json_skip_whitespace,然后line0[0] == '\0' || '#'跳过、'{'走json_execute_command()、其余走text_execute_command()(内部调用mpv_command_string(),且不产生回复)。
3.6 数据流时序
文档给出的时序约束在实现中可以直接验证(input/ipc-unix.c 的client_thread()):
- mpv 侧在执行命令、写回复期间不再服务该 socket:例如命令执行期间发生的事件,不会抢在回复之前写入 socket。这一点未来可能改变,唯一保证是"对 IPC 消息的回复按序发出";
- 由于 socket I/O 天然异步,你可能在收到上一条命令回复之前先读到不相关的事件消息——这些事件是 mpv 在读到你这条命令之前就已排队好的;
- 如果未来 mpv 侧改为非阻塞写/非阻塞执行,事件可能在任意时刻被发送;
- 还可以使用异步命令(见第 4 节),它们以任意顺序返回,且执行期间完全不阻塞 IPC 交互。
从源码结构看,client_thread()用poll()同时监听"播放器唤醒管"与客户端 socket:唤醒时批量mpv_wait_event取出事件并用mp_json_encode_event()编码写出;读入数据则累积到缓冲区,遇到\n才调用mp_ipc_consume_next_command()取出一整行命令处理。这解释了"回复按序、事件可能穿插"的行为来源。
4. 异步命令
任何命令都可以异步执行:行为与同步执行完全一致,只是不阻塞——执行期间可以继续发送其他命令,完成顺序任意。
控制字段是async:存在时必须是布尔值,缺省视为false。示例:发起
{ "command": ["screenshot"], "request_id": 123, "async": true }完成后收到:
{"request_id":123,"error":"success","data":null}三个要点:
- 按设计,不会收到"命令已启动"的确认。长耗时命令在发出后直到执行完毕才会有回复;
- 一些本质同步执行的命令若被标记
async,会表现为"立即完成的异步命令"; - 异步命令的取消能力在 libmpv API 中可用,但 IPC 协议尚未实现。
源码中,input/ipc.c 的json_execute_command()对async字段的处理是:存在且非布尔则报MPV_ERROR_INVALID_PARAMETER;为真时调用mpv_command_node_async(client, reqid, cmd_node)并置send_reply = false(即本次不立即写回复,等事件线程把COMMAND_REPLY事件编码写出)——这正是"没有启动确认、回复任意到达"机制的实现来源。
5. 命名参数命令与 IPC 专属命令
5.1 命名参数
若command字段是 JSON对象而非数组,则按命名参数解析(对应 C APImpv_command_node()文档中MPV_FORMAT_NODE_MAP的情形)。部分命令用命名参数可读性更好,少数生僻命令基本必须使用命名参数。目前只有"正规命令"(见List of Input Commands,即 DOCS/man/commands.rst 中列出的命令)支持命名参数。
5.2 IPC 协议额外的命令
除List of Input Commands中所有命令外,IPC 还支持以下命令(实现见 input/ipc.c):
| 命令 | 说明 | 示例 |
|---|---|---|
client_name | 返回客户端名字符串,形如ipc-N(N 为整数) | {"command": ["client_name"]} |
get_time_us | 返回 mpv 内部时间(微秒,系统时间加任意偏移) | — |
get_property | 读取属性,值放入回复的data字段 | {"command": ["get_property", "volume"]}→{"data": 50.0, "error": "success"} |
get_property_string | 同上,但data恒为字符串 | {"command": ["get_property_string", "volume"]}→{"data": "50.000000", "error": "success"} |
set_property | 设置属性 | {"command": ["set_property", "pause", true]}→{"error": "success"} |
set_property_string | set_property的别名,两者都接受原生值与字符串 | — |
observe_property | 观察属性变化,变化时产生property-change事件 | 见下 |
observe_property_string | 同上,但data恒为字符串 | — |
unobserve_property | 撤销观察,参数为观察时使用的数字 id | {"command": ["unobserve_property", 1]} |
request_log_messages | 开启 mpv 日志输出(以事件形式接收),参数为日志级别(同 C APImpv_request_log_messages) | — |
enable_event/disable_event | 启用/禁用指定事件,等价于 C APImpv_request_event();传字符串all表示全部事件 | — |
get_version | 返回该 mpv 实例客户端 API 的版本号 | — |
observe_property的完整交互示例:
{ "command": ["observe_property", 1, "volume"] } { "error": "success" } { "event": "property-change", "id": 1, "data": 52.0, "name": "volume" }警告(原文档原样强调):连接一旦断开,IPC 客户端在 mpv 内部被销毁,被观察的属性会随之注销。比如用分次调用 socat 发命令时就会这样,看起来就像"属性观察不生效"。必须保持 IPC 连接常开,观察才有效。
request_log_messages还附有一条重要告诫:日志输出是给人看的(主要用于调试),试图解析日志获取信息只会导致未来版本一升级就坏——需要信息时应该提 feature request,要求提供正规的返回该信息的事件。
关于enable_event/disable_event:默认大多数事件本来就是启用的,因此该命令实际使用价值有限。
6. UTF-8 边界问题
正常情况下所有字符串都是 UTF-8,但有时字符串会处于某种损坏编码(常见于文件标签;许多 Unix 系统上的文件名也不保证是 UTF-8)。这意味着 mpv 偶尔会发出非法 JSON。如果客户端解析器因此出问题,应当在送入 JSON 解析器之前,先对原始数据中的非法 UTF-8 序列做过滤替换。
mpv 承诺不会通过损坏的\u转义序列(包括代理对)去构造非法 UTF-8。
7. JSON 扩展语法
mpv 的 JSON 解析器支持以下非标准扩展(与 misc/json.c 头部注释一致):
- 数组或对象元素可以有尾随逗号;
- 对象语法除了
:之外还接受=; - 对象 key 可以不带引号,前提是首字符属于
A-Za-z_、其余字符只含A-Za-z0-9_; - 允许
\xAB形式的字节转义(AB 为两位十六进制数)。
示例:
{ objkey = "value\x0A" }等价于:
{ "objkey": "value\n" }这对手写命令时省却引号、以及表达控制字符非常便利,但注意这是单向便利:mpv 发出的消息始终是标准 JSON。
8. 不创建 server 的客户端连接方式:.run脚本
除了--input-ipc-server,还有一种匿名 IPC 连接方式,通过 mpv 的一个"伪脚本后端"启动外部进程实现:
- 在 mpv 脚本目录(即配置目录下的 scripts 目录,详见 mpv man 的
FILES章节)放入带.run扩展名的文件,或通过其他方式加载(见Script location)。这些脚本通过操作系统原生机制直接执行(就像在 shell 中运行一样),必须有正确的 shebang 并设置可执行位; - 执行时,IPC 连接所用的 socket 通过文件描述符继承传给子进程,并以特殊命令行参数
--mpv-ipc-fd=N指明 FD 编号(N 为数字); - 之后的行为与普通的
--input-ipc-serverIPC 连接完全一致。mpv 不尝试观察或与启动的脚本进程做其他交互; - Windows 上目前不可用。
源码印证:该后端注册在 player/scripting.c 中——mp_scripting_run结构体.name = "ipc"、.file_ext = "run";load_run()调用mp_ipc_start_anon_client()生成一对socketpair()(见 input/ipc-unix.c 对应函数),拼出--mpv-ipc-fd=%d参数后经mp_subprocess()以 detach 方式拉起子进程。而 input/ipc-win.c 中的mp_ipc_start_anon_client()直接返回false,与"Windows 上暂不可用"的文档表述一致。
这种模式的典型用法是:写一个带 shebang 的 Python/Shell 脚本放入 scripts 目录,脚本内以--mpv-ipc-fd=N指定的 FD 作为 stdin/stdout 通道与 mpv 双向通信,无需管理任何 socket 路径或权限。
9. 速查小结
- 启动:
mpv file.mkv --input-ipc-server=/tmp/mpvsocket(Unix)或--input-ipc-server=\\.\\pipe\\mpvsocket(Windows,缺\\.\pipe\前缀会自动补);--idle可让 mpv 常驻等待控制。 - 最小请求:
{"command": ["get_property", "playback-time"]}+ 换行;回复{"data":190.482000,"error":"success"}。 - 配对回复:始终带上整数
request_id,回复会原样带回。 - 长任务:加
"async": true,接受"无启动确认、回复乱序、不可取消"三件事。 - 属性订阅:
observe_property前记住"连接必须保持",否则观察随连接销毁而注销。 - 文本命令:不以
{开头的行按 input.conf 语法执行、无回复;#与空行忽略。 - 安全:仅限本机信任环境使用;
run命令暴露意味着任何能连上 socket 的本机进程都能执行任意命令。
参考路径汇总:协议文档 DOCS/man/ipc.rst;命令解析与回复构造 input/ipc.c;Unix 侧 socket 实现 input/ipc-unix.c;Windows 命名管道实现 input/ipc-win.c;JSON 扩展解析 misc/json.c;选项定义 options/options.c 与 DOCS/man/options.rst;.run脚本后端 player/scripting.c;命令清单 DOCS/man/commands.rst。
【免费下载链接】mpv🎥 Command line media player项目地址: https://gitcode.com/GitHub_Trending/mp/mpv
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考