mpv JSON IPC 完全指南:用 Python 实现播放器外部控制

发布时间:2026/10/13 18:20:50
mpv JSON IPC 完全指南:用 Python 实现播放器外部控制 mpv 这播放器老用户都知道它的好启动快、内存占用低、可定制性强一款没有花哨界面的开源播放器却藏着不少硬核功能。其中最容易被忽略、也最值得玩透的一个能力就是它的 JSON IPC 接口。简单说你可以让任何外部程序通过一个本地 socket 或管道用 JSON 格式的命令直接控制 mpv 的播放行为比如暂停、跳转、加载文件、改音量甚至监听它正在播放什么、进度走到哪。这篇文章就是要把这个接口讲透从一个最简单的 socket 连接开始到用 Python 写一个能收发命令、订阅事件的控制客户端最后再给出我实际踩过的坑。适合想给播放器加自动脚本、做桌面工具、或者单纯想被“万物互联”加持的播放器深度玩家。选这个方向的原因很直接mpv 的命令行参数虽然强大但只能启动时控制Lua 脚本虽然能内嵌但总不能为了改个音量就重启进程。JSON IPC 恰好补上了这块空白它让 mpv 变成了一个可以被任意语言、任意进程随时调用的服务端而这种“播放器作为服务”的思路在自动化场景里几乎无处不能。接下来我从设计思路、协议细节、实操代码到排查清单把这套玩法完整拆给你。1. 项目概览为什么选择 JSON IPC 控制 mpv1.1 mpv 的控制方式对比接触 mpv 的人大概率用过这三种控制方式命令行启动参数、内部 Lua 脚本、外部事件注入。命令行参数适合“启动前定好一切”比如--volume30开场音量多少或者--loop-playlistinf无限循环Lua 脚本适合在播放器内部实现逻辑比如自动跳过片头、读取外部配置文件但这两者都有一个共同限制交互是单向的、静态的或者深埋在播放器进程内部外面很难插一手。JSON IPC 走的是另一条路mpv 以服务模式监听一个本地 Unix socket 或者 Windows 命名管道外部程序设计好 JSON 格式的请求发给它它执行后再把结果返回给外部程序。整个过程是双向的、动态的、独立于进程的。比如你的 mpv 在笔记本上播放用一个手机上的控制器脚本去切换它的下一集这在技术上完全可行只要你的本地网络能访问到那个 socket。对比一下更直观命令行参数适合一次性预设无法在播放中动态修改也很难获取当前状态。Lua 脚本可编程能力强但只能“内嵌”外部程序想通信只能再额外开端口。JSON IPC语言无关进程无关既能发命令又能读属性还能收事件是双向异构控制的最佳解。这也是我后来所有 mpv 自动化都优先走 IPC 的原因。它不把逻辑限制在某个脚本运行时里而是给整个系统开了一扇标准化的门。1.2 JSON IPC 的核心应用场景实际干活的时候什么场景才值得搭一套 JSON IPC 客户端我自己的经验是一旦你想让“正在播的东西”和你“身边的设备”联动起来IPC 就香得不行。最典型的例子是播放状态同步你的播放器在客厅电脑上放片卧室里有个小脚本每隔几秒读一次当前时间、标题、是否暂停然后把信息推送到手机或桌面小组件。如果不走 IPC你只能靠抓窗口标题或读播放器日志既不稳定又拿不到精确进度。另一个高频场景是“定时或条件触发控制”。我写过一个小脚本晚上十二点之后如果还在播放就自动把音量降到 10%并弹一个通知提醒该睡了。这种逻辑放在 Lua 里也能写但用外部 Python 脚本写起来更舒服而且它能同时读取系统时间、传感器状态等多种信息不局限于播放器本身。还有一个很实用的方向批量文件转码前的预览检查。我处理视频素材时经常要用脚本自动打开一批文件、记录每段素材的片长和关键帧时间点然后自动关闭。这用 JSON IPC 做起来就是遍历播放列表、逐条读属性的事手动操作得累死。如果你有做类似“视频自动打点”或“播放状态监控”的需求这个接口基本是必需品。1.3 需要准备的基本环境开始之前先明确环境要求。mpv 版本最好在 0.30 及以上因为 IPC 接口在早期版本里功能不完整老版本可能缺属性或者事件类型。系统方面Linux 和 macOS 直接用 Unix socket配置文件里写input-ipc-server/tmp/mpv.sock就能生成一个 socket 文件Windows 需要走命名管道参数略不同但协议内容完全一致。你还需要一个能发送 JSON 数据的工具。交互调试阶段用socat或ncat最方便因为可以手动敲命令正式开发阶段建议用 Python自带的socket和json模块就够不需要装额外依赖。如果你后续要处理更复杂的并发比如同时监听多个属性变化可以考虑用asyncio重写连接但核心逻辑还是一样。另外别忘了权限问题。Unix socket 文件默认权限取决于进程 umask如果你用服务方式管理 mpv一定要确认外部脚本有读写权限。我最早就是在写脚本时忘了 socket 文件属于另一个用户结果连接一直失败排查了半小时才发现是权限。2. 核心机制与基础配置先让 mpv 开口说话2.1 协议本质基于 JSON 的请求、响应与事件mpv 的 JSON IPC 从协议层面看并不是严格意义上的 JSON-RPC而是一套自己定制的“JSON 换行流”。每条消息都是一个独立的 JSON 对象对象之间用换行符\n分隔。发送方发出的请求格式大致长这样{command: [set_property, pause, true], request_id: 100}command一个数组第一个元素是命令名后面的元素是参数。mpv 的所有控制操作本质上都收敛到command这个字段。request_id可选但强烈建议带上。它是一个由调用方自定义的数字或字符串用来辨识哪条响应对应哪条请求尤其是你连续发送多条命令时没有它就不知道返回的是哪一个结果。mpv 返回的响应也放在同一个连接里格式通常是这样{request_id: 100, error: success, data: true}error用来表示命令是否执行成功只有成功才是successdata是额外返回值比如读取属性时返回属性值。除了这种方式mpv 还会主动推送事件也就是event型消息常见的有{event: file-loaded, id: 1}事件消息同样以换行分隔和响应消息混在同一个数据流里。这意味着你的客户端不能只读一条响应就完事必须持续解析这个流把响应和事件分流处理。关键点在于mpv 的消息流是“行分隔”的不是“长度前缀”的。你读取 socket 时不能简单认为一次recv拿到的就是一整条 JSON必须做缓冲区的行分割。这也是最容易踩的坑之一后面我专门会讲。2.2 启动 mpv 并开启 IPC 通道要让 mpv 监听 IPC最直接的方法是启动时加参数mpv --input-ipc-server/tmp/mpv.sock video.mp4这条命令会把 mpv 变成一个 IPC 服务端同时它在界面上照常播放。你也可以把这个参数写进 mpv 的配置文件这样以后每次启动都会自动创建 socket。Linux 用户一般放在~/.config/mpv/mpv.conf内容是input-ipc-server/tmp/mpv.sockWindows 用户则把路径换成命名管道格式input-ipc-server\\.\pipe\mpv-pipe注意Windows 的命名管道不需要预先创建mpv 启动时会自动建立。相比之下Unix socket 在/tmp下看起来就是一个普通文件但它不能像普通文件一样直接读写必须通过 socket 接口访问。这里有个小细节如果 socket 文件已经存在mpv 有可能拒绝启动提示地址已占用。一般发生在你上次异常退出、socket 文件残留的时候。解决办法很简单手动删除残留文件再启动即可或者写个启动脚本先清理。我自己的习惯是只在使用自动化脚本时才临时加--input-ipc-server平时播放器裸启不给系统留一个常驻服务。如果你的场景是长期常驻播放器就把它写进配置如果是录像机一样按需拉起那用命令行参数更干净。2.3 用命令行工具直接验证通道通道建好之后最快速的验证方式是使用socat。假设 socket 路径是/tmp/mpv.sock执行socat - /tmp/mpv.sock然后在终端里发送一行 JSON{command: [get_property, time-pos], request_id: 1}socat 会把请求原样送到 mpv也会把 mpv 的响应原样打回终端。你很快能看到类似这样的返回{request_id: 1, error: success, data: 12.345000}如果系统没有socatncat也可以做类似的事或者你用 Python 一行连上再手动发数据。这个验证环节非常重要它能帮你确认三件事socket 确实存在、权限没问题、协议语法理解正确。我第一次测试时随便发了一个不带request_id的请求mpv 也会正常执行但没有标识符后面连续发多条就完全对不上谁是谁。所以就算是在命令行手测也尽量保持带request_id的习惯。好习惯能帮你省去后期大量排查时间。你还可以利用这个通道测试看事件推送。比如先开一个终端用 socat 连着然后在第二个终端里用快捷键或者send-volatile之类的动作来触发 mpv 状态变化第一个终端会立刻收到property-change事件直观感受一下“主动推送”是怎么回事。2.4 常用命令与属性速查要熟练使用 JSON IPC下面这张速查表能帮你覆盖 90% 日常控制需求。常用命令我整理成了表格方便你写脚本时对照命令示例参数作用set_property[set_property, pause, true]设置属性暂停/播放、音量、静音、倍速都走它get_property[get_property, path]读取当前属性比如文件路径、时间位置、播放状态observe_property[observe_property, time-pos, 1]订阅属性变化后续 mpv 会持续推送变化事件unobserve_property[unobserve_property, 1]取消指定 id 的属性订阅loadfile[loadfile, /path/to/file.mp4]加载并播放新文件playlist-next[playlist-next]跳到播放列表下一项playlist-prev[playlist-prev]上一项seek[seek, 10, relative]跳转进度单位秒支持相对、绝对等模式quit[quit]让 mpv 退出get_time_ms[get_time_ms]获取 mpv 内部时间常用于性能测试常用属性里最值得记住的是这些pause布尔值暂停状态。time-pos数字当前播放位置单位秒。duration数字当前文件总时长单位秒。media-title字符串显示标题。path字符串当前媒体文件路径。volume数字音量百分比。mute布尔值静音状态。speed数字播放倍速。eof-reached布尔值是否已到结尾和自动联动相关。理解属性的方式很简单属性就是 mpv 内部变量的对外窗口你用get_property读它用set_property写它用observe_property监听它。几乎所有的播放器状态都能在这套属性体系里找到对应项也让 IPC 控制变得非常统一。3. 实操演练用 Python 驱动 mpv3.1 搭建一个可复用的 MpvClient到这一步才是真正要用代码解决问题的时刻。一个良好的 mpv 控制客户端至少要做三件事正确连接 socket、正确分帧解析消息、允许上下层业务读取响应和事件。我写了一个最典型的单线程客户端骨架不需要第三方库直接跑import socket import json class MpvClient: def __init__(self, socket_path): self.sock socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(socket_path) self.buffer b self.request_id 0 def send_command(self, command, request_idNone): if request_id is None: self.request_id 1 request_id self.request_id payload json.dumps({ command: command, request_id: request_id }) \n self.sock.sendall(payload.encode(utf-8)) return request_id def read_messages(self): while True: chunk self.sock.recv(65536) if not chunk: break self.buffer chunk while b\n in self.buffer: line, self.buffer self.buffer.split(b\n, 1) if line.strip(): try: yield json.loads(line) except json.JSONDecodeError: continue这个类最核心的设计就是read_messages。它永远在循环里recv数据同时把收到的字节塞进self.buffer然后按换行符把完整的 JSON 一条条切出来。这样即使一次recv拿到半条消息或者一次拿到了好几条消息也能保证每条 JSON 都完整处理。如果你用的是 Windows连接方式要换成命名管道。Python 标准库的socket模块不支持 Windows 命名管道最好用win32pipe或者调用 mpv 官方推荐的第三方库。不过为了演示主流程我以 Linux 的 Unix socket 为准Windows 用户重点理解协议逻辑即可换管道的实现并不难。3.2 发送命令并处理请求 ID有了客户端类第一步当然是发一个读取命令试试。比如我想获取当前播放时长client MpvClient(/tmp/mpv.sock) client.send_command([get_property, duration]) for msg in client.read_messages(): if request_id in msg: if msg.get(request_id) 1: duration msg.get(data) print(当前时长:, duration) break这段代码有两个要点。第一send_command返回的request_id必须保存下来否则你不知道哪条响应是你刚才那条。第二读取循环里不光有响应还有事件所以判断依据是消息里是否存在request_id字段。事件消息通常用event字段不会带request_id两者很好区分。如果你想连续发送多条命令比如同时获取“路径、标题、时间、时长”可以一次性把它们全部发出去然后再进入读取循环按request_id分别归档。这样做的好处是减少等待时间网络交互变成一次突发传输不过要注意 mpv 会按请求顺序返回响应但顺序不一定和发送顺序完全一致所以还是必须靠request_id对号入座。实际调的时候我会维护一个字典把request_id映射到“我要用这个数据做什么”。每条响应回来就查字典执行对应的处理函数。这样代码结构清晰也不会漏掉响应。3.3 订阅属性变化observe_property静态读取属性是基础操作但很多实用场景更希望“发生变化时主动通知我”。比如你写一个屏幕上的迷你进度条想让它跟着播放进度平滑移动总不能隔 0.1 秒就轮询一次time-pos那样太浪费资源。正确的做法是订阅这个属性。用 IPC 订阅属性的命令是{command: [observe_property, time-pos, 1], request_id: 2}第三个参数是一个整数表示属性值的格式通常填1表示原生格式。订阅成功之后mpv 并不会马上给你数据而是在属性值变化时推送一条事件消息{event: property-change, name: time-pos, data: 12.34, id: 2}你只需要在事件循环里判断event property-change然后用name字段区分是哪个属性变了再用data字段拿到新值。这在写交互工具时非常舒服比如做一个歌词跟随脚本订阅time-pos每次值一跳就去查一下当前应该显示的歌词行。顺带提醒一下订阅的第三个参数格式和 mpv 版本有关。有的版本用native字符串也能生效但为了兼容性我习惯用数字1。如果你在旧版本上发现订阅了没反应先看看手册里这个参数还认不认。如果你只关心某个属性变化一次那观察一次之后及时unobserve_property取消订阅即可。长时间不取消而又频繁变化的属性比如time-pos事件流会非常多如果处理不及时socket 缓冲区可能积压导致其他消息延迟。3.4 实战自动标记播放进度并输出日志把上面的基础操作组合起来就能做一个非常实用的小工具播放时每 5 秒记录一次当前进度并写入日志文件如果播放器暂停或者播放结束也自动记录一条状态。思路是订阅两个属性pause和time-pos。不过time-pos每秒会变动很多次如果每条变化都写日志会产生大量数据。我实际的做法是在本地维护一个变量只有当前时间距离上次写入超过 5 秒时才真正写日志。这样既利用了订阅的实时性又不会把日志刷爆。代码如下import time import json def run(socket_path, log_path): client MpvClient(socket_path) client.send_command([observe_property, pause, 1]) client.send_command([observe_property, time-pos, 1]) last_log_time 0 last_pos 0 with open(log_path, a, encodingutf-8) as f: for msg in client.read_messages(): if msg.get(event) property-change: name msg.get(name) value msg.get(data) if name pause: state paused if value else playing f.write(f[{time.strftime(%Y-%m-%d %H:%M:%S)}] {state}\n) f.flush() elif name time-pos: last_pos value now time.time() if now - last_log_time 5: f.write(f[{time.strftime(%Y-%m-%d %H:%M:%S)}] pos{value:.2f}\n) f.flush() last_log_time now这个小工具运行后你完全可以把它放在后台一边看视频一遍生成自己的“播放时间轴”。更进一步你可以在日志里加入duration属性结合总长算出百分比做成一个自动打卡工具。对我来说这类“状态自动跟着播放器走”的能力才是 IPC 真正的价值所在。4. 常见问题与排查技巧4.1 socket 连不上怎么办连接失败是新手的第一道坎原因集中在三种mpv 没有真正开启 IPC、socket 路径错误、权限不够。先确认 mpv 是否带有--input-ipc-server参数启动并检查 socket 文件是否存在ls -l /tmp/mpv.sock sudo socat - /tmp/mpv.sock如果 ls 看不到文件说明 mpv 没监听成功或者路径不对。这时去 mpv 的日志里找启动信息常用--msg-levelinputdebug来查看。权限问题则表现为文件存在但非当前用户连接被拒绝。我遇到过一个诡异的情况socket 文件明明存在但程序连接时异常退出。后来发现是文件权限只有拥有者可读写而脚本运行在另一个用户下。如果你用 home assistant 或 cron 跑脚本这点特别容易踩。解决办法是在启动脚本里显式chmod 666 /tmp/mpv.sock或者用同一用户执行。4.2 消息边界问题这是最常见的协议性错误。有人直接用recv()拿一次数据就尝试json.loads结果经常报“JSON 解码失败”因为一条消息可能被 TCP 拆成两个包或者多条消息被合在一个包里。要解决这个问题必须引入缓冲区按换行符切分完整行。前面的MpvClient.read_messages就是标准的行分割方案。核心思路是每次recv之后把数据追加到缓冲区然后循环找\n找到就说明有一条完整消息。还有一点你发送给 mpv 的请求结尾也必须带\n。不用换行符的话mpv 会一直干等对话框看起来像是没有响应实际上是你自己没有把消息边界发清楚。4.3 收到错误响应但排查不到原因当你的命令返回error不是success时常见的错误值有property not found、disabled、command unavailab。先检查命令名拼写mpv 命令大小写敏感get_property和get_Property完全不同。属性名也一样很多新手把timestamp写成time-stamp自然查不到。另一种情况是参数类型不对。set_property的布尔值必须是 JSON 的true/false不能是字符串true。如果 mpv 需要数值你传了字符串它可能报类型错误或者静默忽略。最有效的排查办法是在 socat 里手动敲一条相同命令这样能快速区分是客户端代码的问题还是命令语法的问题。另外个别属性只在特定条件下可用。比如读取duration时如果当前流还没解析完返回可能是一个无效值。从 pop 层看不是命令写错了而是条件不满足。遇到这种情况可以先订阅file-loaded事件等它触发后再去读取文件相关属性。4.4 线程模型与并发控制如果你要在同一个连接里同时处理“发送多个请求”和“持续读取事件流”就要注意并发模型。最常见的错误是主线程先发一个命令然后阻塞着等待响应结果其他线程发来的命令排不上队或者响应到达后没有合适的线程去收集。我的建议是保持一个专用接收线程负责读取和分发所有消息发送命令则提供统一的方法从任意线程调用都行。如果不需要并发那就用单线程轮询读取简单稳定。关键是不要在同一线程里先用一个循环读若干条消息再去发下一条命令这样容易漏掉期间推送的事件。在 Python 里如果想在接收线程中安全地把request_id和响应关联起来可以维护一个Future字典发送时创建一个Future放进字典接收线程匹配到响应后调用set_result请求方等待future.result()。这套模式能很好地解决并发问题。4.5 Windows 下的特殊留意点Windows 上使用 JSON IPC 有两个容易踩的坑。第一个是 socket 类型不同前面提到过要用命名管道而非 Unix socketAF_UNIX在 Windows 上并不可靠建议直接用pywin32的win32pipe包。第二个是路径转义问题命名管道路径在 JSON 配置或命令行里反斜杠需要处理例如写成\\.\pipe\mpv-pipe在 Python 字符串里要小心双反斜杠。Windows 下测试时我建议先打开 mpv同时确保它以管理员权限运行某些系统配置下非管理员进程访问命名管道也可能失败。如果遇到奇怪的问题先用 mpv 自带的控制台日志调试多用msg-levelinputdebug看具体收发数据能省下很多猜测时间。5. 进阶玩法与避坑建议5.1 用 JSON IPC 写一个秒控浮层一个很有趣的进阶玩法是写一个轻量浮层面板通过 JSON IPC 实现对 mpv 的“秒控”。这个浮层可以用 Python 的 tkinter 做也可以用任何桌面技术栈。面板上放几个按钮暂停/播放、前进十秒、后退十秒、音量加减、倍速切换每次点击其实都是向 mpv 发送一个 JSON 命令。我做过一个简化版核心就是在按钮回调里直接调用client.send_command。因为 IPC 响应速度很快本地 socket 交互的延迟几乎可以忽略实际体感比按键盘快捷键还顺手。而且因为它是独立的进程不依赖播放器窗口焦点即使你正在全屏看视频浮层也能正常操作这在某些多显示器场景下特别舒服。如果你想让它更酷一点甚至可以读取当前播放进度在浮层上画一条实时进度条。这只需要订阅time-pos和duration然后把回调实时更新到 UI 控件完全不需要轮询。5.2 安全策略IPC 通道要收好把控制通道建起来之后不能不管安全。尤其要注意IPC socket 是本地文件不是网络端口但只要你的系统上有其他用户进程能访问它就有可能被滥用。默认情况下socket 文件可能被创建成0644权限这意味着其他用户也能连接并控制你的播放器比如强制改音量、切走你的视频。我的建议是要么启动 mpv 的用户和脚本用户完全一致要么将 socket 路径放在只有自己可访问的目录下并使用配置强制权限。在启动命令里加一个包装脚本先umask 077再启动 mpv能有效避免权限问题。另外不要把对外暴露的服务直接接到这个 socket 上。如果你想远程控制建议通过一个带鉴权的中转服务转发命令而不要直接把 IPC socket 暴露到公网。虽然它通常是本地可控的但安全意识还是要刻在骨子里。5.3 版本兼容与文档查阅习惯mpv 的 JSON IPC 接口虽然在核心部分很稳定但属性、事件、命令会随着版本迭代增加或调整。我的经验是遇到“命令存在但行为不符合预期”时第一时间查对应当前版本的文档而不是到网上找旧帖子硬套。mpv 官方手册里对每个命令和属性都有准确描述适合作为真事实源。更实用的一点是开发时要用--idleyes模式。如果你只想启动一个空播放器专门等待外部指令可以用mpv --idle --input-ipc-server/tmp/mpv.sock这样 mpv 一开始不加载任何文件纯当一个可控的空服务一直挂着之后你随时通过 IPC 给它加载文件。这在做自动测试、批量处理、多任务协调时非常有用。还有一个小习惯我每次写 IPC 客户端都会先定义一个“发送日志”函数把每次发出的命令和时间记录下来。日志能帮你快速定位是客户端没发出去还是 mpv 不接受。这个习惯帮我少走了太多弯路。最后再分享一个我觉得最有价值的玩法把 JSON IPC 和系统的自动化事件配合。比如当某个视频文件播放结束时mpv 会推送end-file事件你读到这个事件后可以自动向计划任务发送“下一个文件”的信号形成一套完整的自动追剧或自动轮播流程。我试过把这个能力和手机上的通知中心对接效果非常顺手。巧的是用熟了之后你会发现这套接口其实已经成了自己操作播放器时最顺手的“遥控器”不管是本地按键映射还是脚本批量控制都绕不开它的灵活和稳定。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询