资讯动态

mpv JSON IPC 完全指南:用 Python 实现播放器自动化控制

发布时间:2026/10/13 3:44:41 来源:尧图企业网站定制
你有没有过这种想法mpv 正在放片子你想让另一个程序去告诉它“暂停一下”“切下一集”“把音量拉到 60%”或者反过来从它那儿拿当前播放进度、字幕状态、音轨信息手动敲键盘当然可以可一旦你想要的是程序化控制就绕不开 mpv 的 JSON IPC 接口。mpv 作为一款开源播放器除了命令行强、界面干净之外其实内置了一套 IPC 协议通过本地 socket 就能让外部进程跟它对话。这篇文章就是想把这块彻底讲透怎么打开 IPC、协议格式长什么样、用 Python 写控制脚本时会踩哪些坑以及最常见的排查思路。适合想给 mpv 写遥控器、做播放器联动、搞自动化通知或者干脆想在现有项目里塞一个“可编程播放器”的人。1. 先搞懂 JSON IPC 到底帮你解决了什么问题1.1 它不是遥控器是播放器的“外接大脑”很多人用过 mpv 的快捷键、命令行传参甚至写过 Lua 脚本。但 JSON IPC 的地位不太一样它让 mpv 变成可以被外部进程主动询问和控制的“服务端”。你启动时就给它一个 socket 文件路径之后任何程序只要往这个 socket 里写入一行 JSONmpv 就会执行对应的动作并且把结果或事件用 JSON 返回来。我举个具体场景。你正在看一部带多音轨的电影想根据房间环境自动切换音轨比如插上耳机就切到 5.1拔出耳机就切回立体声。这种需求靠手动按键很难受靠 mpv 的 Lua 脚本也能做但如果你想用自己熟悉的语言或者想跟现有的自动化流程打通 Lua 的边界就很明显了。而 JSON IPC 是语言无关的Python、Node.js、Go、Shell 都能通过 socket 跟它通信你只需要发一条set_property把aid改掉就行。再比如你想做“播放进度同步”。mpv 自己不会主动把进度写到文件但只要开启observe_property它就会在你指定的属性变化时推送事件外部程序就能实时记录当前播到第几秒、状态是播放还是暂停。这些都是单个命令行参数做不到的也是 JSON IPC 存在的真正价值。1.2 为什么不用其它控制方式三条路对比之后的选择mpv 控制方案其实有好几条路。除了 JSON IPC还有直接往进程的 stdin 写命令、编写 Lua 脚本、以及通过--osc这类界面脚本去间接控制。大多数时候最简单是直接传启动参数但那是一次性的没法在播放中途改变逻辑。Lua 脚本最“原生”能拿到不少内部状态但你不一定想为一个小功能去维护一套 Lua 代码而且它运行在 mpv 的进程里跟外部系统联动还得想办法。我直接给一个对比方便你按需求选方式启动时确定播放中可改从外部进程调用跨语言适合场景命令行参数是否否否固定行为、一次性播放stdin 命令否是受限是同一个进程内的简单驱动Lua 脚本是是否否深度定制 mpv 内部行为JSON IPC是是是是外部程序控制、联动、自动化我这个项目选择 JSON IPC核心原因就是“外部进程可控”。你可以在一个系统里把 mpv 当成“播放器组件”而不是“一个要人去操作的窗口”。典型场景包括给网页做个遥控器、对接智能家居的自动化开关、写一个直播推流时的自动切播客户端。所有这些都需要把播放器的状态暴露出来并且接收入外部指令JSON IPC 几乎是最直接的一条路。当然它也有代价。你需要自己管理 socket 连接、处理异步返回、区分事件和响应还得留意 JSON 编码类型。这些听起来复杂但本文后面会手把手带你解决。2. 准备阶段先让 mpv 把 IPC 服务开起来2.1 启动参数与配置文件里的两个关键选项让 mpv 开启 IPC 接口最核心的参数是--input-ipc-server。在 Linux 或 macOS 上我习惯给它指定一个本地 socket 文件路径比如mpv --input-ipc-server/tmp/mpv.sock video.mp4这样启动后mpv 就会在/tmp/mpv.sock上监听本地进程的连入。你可以在终端里试试启动后另开一个窗口用socat连上去socat - UNIX-CONNECT:/tmp/mpv.sock然后输入一行 JSON{command: [get_property, time-pos], request_id: 1}正常情况下你能收到类似这样的返回{request_id: 1, error: success, data: 12.34}Windows 上的写法不一样mpv 用的是命名管道路径而不是文件路径。你需要在启动时写成mpv --input-ipc-server\\.\pipe\mpv-pipe video.mp4此时在 Windows 上就不能直接用socat来测了得用支持命名管道的 Python 库或者其他工具。这一点细节我放到后面“多平台坑”那节再展开。如果你不想每次启动都敲这一长串参数可以把配置写在 mpv 的配置文件中。配置文件一般位于~/.config/mpv/mpv.confLinux/macOS或者%APPDATA%\mpv\mpv.confWindows你只需要加一行input-ipc-server/tmp/mpv.sock注意Windows 上在配置文件里写路径时反斜杠要留意转义问题我建议在配置里写成input-ipc-server\\.\pipe\mpv-pipe如果是从命令行传入则直接照原始写法就行。2.2 让 mpv 在无文件时也能保持存活一个很容易被忽略的点是如果 mpv 启动时没有带任何文件默认会在初始化后直接退出。这意味着即使你开了 IPCsocket 也可能一闪而过就消失了。这种情况在做“常驻服务”时会非常难受比如你想让 mpv 一直挂着等待指令却不想让它一开始就播放任何东西。解决的办法是加--idlempv --idle --input-ipc-server/tmp/mpv.sock加了--idle之后mpv 会进入空闲状态等待用户命令或 IPC 指令。之后你完全可以通过 IPC 发loadfile让它加载文件并播放。这个组合特别适合把 mpv 当后台服务用的场景。我在实际项目里的一个习惯是把 mpv 的日志输出到一个固定文件方便后面排查问题mpv --idle --input-ipc-server/tmp/mpv.sock --log-file/tmp/mpv.log这样一旦 IPC 命令不生效可以直接看日志是 socket 没监听到还是命令本身被拒了一目了然。3. 协议细节请求、响应、事件三件套必须理清3.1 每一行都是独立 JSONmpv 的 IPC 协议是基于“行”的文本协议。也就是说你发送给 socket 的内容必须是完整的、单行的 JSON以换行符结尾。我最初踩过一个坑用 Python 的json.dumps()生成字符串后直接sendall()出去结果 mpv 半天没反应。后来才发现我没有在末尾加\n。mpv 端是按行读取的没有换行符它会一直等。一个完整的请求长这样{command: [set_property, pause, true], request_id: 1}我解释一下字段的含义command是一个数组第一个元素是命令名后面的元素是参数。命令名可以是loadfile、set_property、get_property、observe_property等等。request_id是一个可选的整数或字符串。它主要用来标记请求因为 IPC 是异步的你发出请求后可能同时收到很多事件靠request_id才能把响应和请求对应上。当 mpv 处理了请求后它可能会返回{request_id: 1, error: success, data: null}如果请求失败error字段会变成具体的错误信息比如property not found同时data通常是null。注意并不是每个请求都会有响应。比如observe_property注册成功后回复是success但后面真正有用的信息都来自事件推送。3.2 监听事件mpv 会反过来主动推送除了请求-响应模式IPC 还有一类消息叫事件。事件是 mpv 主动推给客户端的不需要你请求。最常见的事件是property-change前提是你先注册观察。比如你想知道播放进度可以先发{command: [observe_property, 1, time-pos], request_id: 2}之后 mpv 每当时time-pos属性发生变化就会给你推来类似这样的消息{event: property-change, id: 1, name: time-pos, data: 15.7, request_id: 2}这时你要注意这条消息并不是你对request_id: 2的响应而是事件。它的标志是存在event字段。所以写客户端时一定要先判断消息是不是事件再决定按事件处理还是按响应处理。否则容易把进度更新数据当成 set 命令的返回错乱到怀疑人生。另外取消观察用unobserve_property{command: [unobserve_property, 1], request_id: 3}3.3 常用命令的地图先知道有哪些再按需选择mpv 的 IPC 命令非常多我挑几个日常用得最频繁的列一下后面实操都会用到命令作用示例loadfile加载文件并播放或加入队列[loadfile, /path/video.mp4, replace]set_property设置属性如暂停、音量[set_property, pause, true]get_property获取属性如当前进度[get_property, time-pos]observe_property订阅属性变化事件[observe_property, 1, pause]unobserve_property取消订阅[unobserve_property, 1]seek跳转进度[seek, 60, absolute]playlist-next下一曲[playlist-next]quit退出[quit]这里有个细节loadfile的第二个参数是文件路径第三个参数是加载方式常见的是replace表示替换当前文件append表示追加到播放队列。如果你想“先把文件加入队列但不立刻播放”用append就行。很多人在这里犯迷糊以为 loadfile 只是“打开文件”不知道还有个队列语义。属性名的命名也有讲究很多属性用连字符分隔比如time-pos、volume、pause、aid、sid、sub-visibility我建议你希望操作什么之前先查一下 mpv 属性列表或者用get_property实验一下确认没写错再集成到正式代码里。4. 实操用 Python 写一个 mpv IPC 控制客户端4.1 最核心的发送与接收封装我日常用的语言是 Python所以这里给你一套可以直接跑的封装。最底层的需求只有三个连上 socket、发 JSON、读 JSON。但实际写起来有几个细节很关键。第一个细节是发送时必须加换行符。第二个细节是接收时不能想当然地一口气读完因为 mpv 可能连续推送多条 JSON你每次recv()拿到的并不是完整的一行。最稳妥的做法是维护一个缓冲区按换行符拆分逐条解析 JSON。下面是一个最小实现import socket import json class MpvIpc: def __init__(self, socket_path): self.sock socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(socket_path) self.buffer b self.req_id 0 def _send(self, command, request_idNone): if request_id is None: self.req_id 1 request_id self.req_id payload {command: command, request_id: request_id} data json.dumps(payload).encode(utf-8) b\n self.sock.sendall(data) return request_id def _read_latest(self): # 简单读一次注意这只是示例完整版需要用循环分段读 data self.sock.recv(65536) self.buffer data lines self.buffer.split(b\n) self.buffer lines[-1] messages [] for line in lines[:-1]: if line.strip(): messages.append(json.loads(line.decode(utf-8))) return messages def set_property(self, name, value): return self._send([set_property, name, value]) def get_property(self, name): req_id self._send([get_property, name]) while True: messages self._read_latest() for msg in messages: if msg.get(request_id) req_id and event not in msg: return msg.get(data) def close(self): self.sock.close()这个封装里get_property用了最粗暴的等待循环只适合快速验证。真实项目里你一般会有自己的事件循环不会死等一个响应。4.2 监听事件做一个实时的播放进度监听器真正把 IPC 用到“工具”级别一定要会用事件驱动。我给你写一个监听time-pos的小脚本每隔一段时间就打印当前播放时间。为了让程序不因为阻塞读而卡死可以用select来判断 socket 是否可读。import socket import json import select class MpvEventMonitor: def __init__(self, socket_path): self.sock socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) self.sock.connect(socket_path) self.buffer b self.req_id 0 def send(self, command): self.req_id 1 data json.dumps({command: command, request_id: self.req_id}).encode(utf-8) b\n self.sock.sendall(data) def poll(self, timeout0.1): r, _, _ select.select([self.sock], [], [], timeout) if not r: return None data self.sock.recv(65536) if not data: return None self.buffer data results [] lines self.buffer.split(b\n) self.buffer lines[-1] for line in lines[:-1]: if line.strip(): results.append(json.loads(line.decode(utf-8))) return results monitor MpvEventMonitor(/tmp/mpv.sock) monitor.send([observe_property, 1, time-pos])] # 注册观察 monitor.send([observe_property, 2, pause]) # 再观察暂停状态 while True: messages monitor.poll() if messages: for msg in messages: if event in msg and msg.get(event) property-change: print(f{msg[name]}: {msg[data]})这段代码加了select让 socket 没有数据时不会一直傻等而是每 0.1 秒检查一次。这个模式很适合跟别的事件循环共存比如后面你想一边监听播放进度一边响应键盘输入或网络请求。4.3 一个真实场景根据外部信号自动暂停写到这里我给你做一个完整的小场景当某个条件满足时自动暂停 mpv条件恢复后继续播放。这个条件可以是文件变化、某个进程退出、或者外部传感器信号。为了不编造复杂依赖我用“文件存在与否”来模拟你把它替换成自己的触发源就行。逻辑很简单循环里先检查条件文件是否存在不存在就发暂停指令存在就发播放指令。同时每隔几秒刷新一次避免反复发同一条指令。import os import time mpv MpvIpc(/tmp/mpv.sock) condition_file /tmp/allow_play while True: pause_should not os.path.exists(condition_file) mpv.set_property(pause, pause_should) time.sleep(2)实际项目中你会用更优雅的触发方式比如用事件循环监听inotify事件。但这个例子足够说明核心件你只需要在正确的时间给 mpv 发一条set_property剩下的交给播放器。5. 常见问题排查我从实际踩坑里整理出来的速查表5.1 连不上 socket先从文件存在与权限查起最常遇到的第一类问题就是“连接被拒”或者“No such file or directory”。如果你在 Linux 下连接/tmp/mpv.sock时被提示No such file or directory先不要怀疑是 socket 协议问题而是先确认文件在不在ls -l /tmp/mpv.sock如果文件不存在多半是 mpv 没有用--input-ipc-server/tmp/mpv.sock启动或者启动时因为配置写错没生效。还有一个容易被忽略的点是你启动了两个 mpv后面那个实例可能因 socket 路径冲突干脆没有开启 IPC。我的经验是先用最简单的方式启动一次不要加载额外配置验证通了再慢慢加东西。如果文件存在但报Permission denied那就是权限问题了。socket 文件的属主和当前用户不一致或者 socket 目录的权限限制了访问。简单的临时方案是用chmod给 socket 文件加权限比如chmod 777 /tmp/mpv.sock但要注意这有安全风险仅限本地测试时使用。更合理的做法是用同一用户启动 mpv 和控制脚本。查看 mpv 是否真的在监听可以用ss -x | grep mpv能看到类似u_str LISTEN的输出说明监听正常。5.2 命令发了没反应八成都出在换行和 JSON 类型上第二种高频问题是“命令明明发了mpv 却什么都不返回”。我先说结论写 socket 时要确保发送的是完整的单行 JSON并且带换行符。像这样发是错的sock.sendall(b{command: [get_property, time-pos]})因为 mpv 在等一个换行你这条数据会一直留在缓冲区里。加上b\n就对了。还有一个我常犯的错是把 JSON 里的true/false当成字符串。比如设置暂停时# 错误写法 {command: [set_property, pause, true]} # 正确写法 {command: [set_property, pause, true]}在 JSON 协议里true是字符串true才是布尔值。mpv 的属性类型是严格匹配的你把pause设成字符串它干脆不执行。这个坑潜伏得特别深因为很多其它接口是可以自动转换类型的而 mpv 的 JSON IPC 不会帮你转。收到error: invalid parameter或者property not found时先核对属性名拼写和大小写。比如time-pos是连字符不是下划线pause是全小写volume也是全小写。命名稍微差一点就会返回 property not found。5.3 socket 文件残留与多实例冲突如果你曾经启动过 mpv然后非正常退出/tmp/mpv.sock这个文件可能还残留在那里。你再次启动 mpv 时它可能会提示无法绑定该地址或者干脆不监听。这种情况下先把旧的 socket 文件删掉rm -f /tmp/mpv.sock然后在启动 mpv。多实例冲突也很隐蔽。如果你同时开了几个 mpv其中两个用了同一个 socket 路径后启动的那个实例很可能不会绑定成功。我见过一种情况第一个 mpv 正常监听第二个 mpv 同样带了这个参数启动结果第二个的 IPC 根本没生效而我一直在尝试用脚本控制第二个实例怎么都连不通。最后查了半天才发现是我的启动脚本里 socket 路径写死了两个进程共用了一个路径。如果你确实要多实例管理建议给每个实例单独一个 socket 路径比如加上 PIDmpv --idle --input-ipc-server/tmp/mpv-$PPID.sock实际项目中可以用 pid 文件来管理路径谁启动谁负责清理。5.4 Windows 命名管道与 Linux 路径的差异Windows 用户如果直接把 Linux 那套 Python 代码拿过去大概率会撞墙。因为 Windows 上 mpv 使用的不是 Unix socket而是命名管道。Python 标准库里的socket.AF_UNIX在 Windows 并不可用。你需要用win32pipe也就是 pywin32 库。一个最小的 Windows 命名管道客户端大概是这个思路import win32pipe import win32file import json pipe_name r\\.\pipe\mpv-pipe pipe win32file.CreateFile( pipe_name, win32file.GENERIC_READ | win32file.GENERIC_WRITE, 0, None, win32file.OPEN_EXISTING, 0, None ) data json.dumps({command: [get_property, time-pos], request_id: 1}).encode(utf-8) b\n win32file.WriteFile(pipe, data) result win32file.ReadFile(pipe, 65536) print(result)如果你不想折腾 pywin32也可以考虑用 WSL 跑 Linux 版本但那要保证 WSL 里装了 mpv而且窗口显示会成为另一个复杂问题。我的建议是在纯 Windows 环境就直接上手命名管道代码量并不会多太多。还有一个细节Windows 命名管道的路径最好用原始字符串避免反斜杠被转义。比如r\\.\pipe\mpv-pipe。我曾经因为少写一个点把\\.\pipe写成了\.\pipe导致一直连接不上查资料才反应过来。6. 进阶姿势从“能控制”到“玩出花”6.1 给页面加一个遥控器把 IPC 暴露给内部的 HTTP 接口一个很自然的扩展是你不想在终端里敲命令而是想在浏览器里点按钮控制 mpv。做法就是自己写一个极其小的 HTTP 服务收到请求后转成 IPC 命令发给 mpv并把 mpv 返回的进度状态读回来。你可以用 Flask 或者 FastAPI 起一个本地服务监听127.0.0.1的某个端口。这样好处是你的手机在同一局域网想控制电脑上的播放器时也能通过这个 HTTP 接口发指令。需要注意安全不要把服务监听在0.0.0.0上否则局域网里任何设备都能控制你的播放器。至少要做个简单的 token 校验或者只绑定回环地址再通过 SSH 隧道访问。我实际做过的方案是把 mpv 的time-pos、pause、playlist都注册成observe_property然后用 WebSocket 推给前端页面。这样网页上能看到实时进度条不再需要轮询。整个链路就是浏览器 - WebSocket 服务 - JSON IPC - mpv。6.2 和播放列表联动做一个“剪贴板一键播放”工具还有一个我很喜欢的小工具监听系统剪贴板如果复制的是视频链接或本地文件路径几分钟后就自动交给 mpv 播放。实现思路很直接剪贴板监控程序发现新文本判断扩展名或 URL 特征然后通过 IPC 发loadfile给它。这种工具的难点不在于 IPC 调用本身而在于你自己要处理好反复触发的问题。我常遇到的坑是同一段文本复制两次结果 mpv 又加载了一次把正在看的电视剧切走了。解决办法是记录上一次处理过的内容哈希如果相同就跳过。这个思路同样适用于任何“事件源 mpv 联动”的场景。6.3 安全边界别让 IPC 裸奔最后必须提醒一句JSON IPC 的本地 socket 本身没有内置认证。任何能访问那个 socket 文件的用户都能控制你的 mpv。如果你的 socket 路径放在一个权限宽松的目录里比如很多人图方便放在/tmp那其他本地用户理论上也能连进去。我在生产环境里喜欢把 socket 放到当前用户目录下比如~/.config/mpv/socket.sock并手动限制目录权限。即便这样本机上的恶意进程谁也防不住但至少不会因为/tmp的共享权限而误暴露。如果以后 mpv 的 IPC 支持了 TCP 监听请记住不要轻易把它暴露到网络除非你自己在前面做了一层鉴权。用 JSON IPC 控制 mpv最大的价值不是“远程按暂停键”而是把播放器真正变成你系统里的一个可编程组件。你需要什么功能只需找到对应的命令或属性用自己熟悉的语言写一段 socket 客户端剩下的事情便都能自动化去完成。我自己在项目里用 IPC 做过播放进度同步、自动切音轨、外部通知、甚至一个简陋的“多人同步观影”客户端。踩得最多的坑永远是路径、权限和 JSON 类型这三样。现在我的习惯是连上之前先ls -l确认 socket 文件还在发出命令之后别急着读用request_id去对响应收到事件就先判断有没有event字段。这套流程跑顺之后mpv 就不再是“一个播放器”而是你工具箱里一块极其顺手的基础件。如果你正打算写自己的 mpv 控制脚本我建议你先用一个最小脚本走通协议再去叠加业务逻辑能省掉大量排查时间。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑