局域网多设备音频 / 视频同步播放 · 把闲置设备拼成一套环绕声系统
sync-audio 让局域网内多台设备(电脑 / 手机 / iPad)同时播放同一段音频,节拍重音对齐即同步成功。除「所有设备放同一首歌」外,还支持把 5.1 / 7.1 声道的每个声道拆分到不同设备(左声道一个手机、低音炮一个音箱……),把家里的闲置设备拼成一套真正的物理环绕声系统。
无需安装任何 App,打开浏览器即可。
- 毫秒级同步:SNTP 时钟校准 + WebSocket 广播 + Web Audio 精确调度,重音对齐可听感验证
- 真实文件播放:mp3 / wav / mp4,主控浏览器端
ffmpeg.wasm解码,服务器零解码负担 - 多声道分配(5.1 / 7.1):为每台设备指定声道,把多台设备拼成物理环绕声
- 视频文件:只广播音轨,画面仅主控本机显示,与音频时间轴联动(播放 / 暂停 / 拖进度条同步)
- 主控自动切换:任何设备可接管主控,断线自动移交其他设备
- 手机不熄屏:HTTPS + Wake Lock(可选)
- 同步可观测:设备周期性上报速率 / 漂移,
analyze-log.js一键分析同步质量
环境要求:Node.js ≥ 14(仅依赖 ws);openssl 可选(用于自动生成 HTTPS 证书,Windows 装 Git 自带,Linux / macOS 一般自带)。
git clone https://github.com/wangk372/sync-audio.git
cd sync-audio
npm install # 仅安装 ws
node server.js启动后终端会打印所有访问地址(本机与局域网 IP,一般是 192.168.x.x)。
后台运行(推荐):Windows 用
.\run-test.ps1,类 Unix 用./run-test.sh,日志自动落盘到logs/。
| 设备 | 地址 | 说明 |
|---|---|---|
| 主控(主机) | http://localhost:8080 |
第一个打开的页面即成为主控,只有它能开始 / 暂停 |
| 子设备(手机 / iPad) | http://<主机IP>:8080 |
即连即用;明文 HTTP 无法自动保持屏幕常亮 |
| 子设备(要自动常亮) | https://<主机IP>:8443 |
需先装一次证书(见下文),播放时屏幕自动不熄 |
<主机IP>以启动日志为准,是主机的局域网 IP,不是子设备自己的 IP。
- 主控:主机浏览器打开
http://localhost:8080,页面显示「开始同步播放」即表示你是主控 - 子设备:手机 / iPad 打开
http://<主机IP>:8080,即连即用;iOS 需先点一下页面任意位置激活声音 - 主控点「开始同步播放」→ 所有设备同步出声(节拍重音对齐即成功)
- 主控点「暂停播放」→ 全部设备同步暂停
- 没声音?先点「本地自检音」:能听到 = 本机正常,问题在网络链路
- 主控点「选择音频 / 视频文件」选 mp3 / wav / mp4 等(首次需加载解码器约 30MB,之后秒载)
- 解码完成显示
文件名 · 时长 · N 声道 · 已解码就绪 - 点「开始同步播放」→ 所有设备同步播放该文件
- 未选文件时点「开始」= 内置节拍音,两种模式可自由切换
- 主控切换:任何设备点「我要成为主控」即可接管;主控断开会自动移交给其他设备
- 声道分配:主控可在「声道分配」卡片为每台设备指定听哪个声道(左 / 右 / 中置 / 低音炮 / 左环绕 / 右环绕……,选项数 = 当前文件声道数)。主控页面播放中显示音频进度条;主控切走 / 断流超 3 秒,服务器自动停止广播
- 视频文件:只抽取音轨广播(不解码画面),所有设备同步出声;画面仅主控本机显示,跟随音频时间轴自动对齐,播完停在最后一帧。其他设备只听声音,无画面
Web 浏览器无法在锁屏下继续播放(平台限制),策略是「播放时保持屏幕常亮」(Wake Lock):
- 手机浏览器打开
http://<主机IP>:8080/ca.pem下载证书 - 安卓:设置 → 安全 → 安装 CA 证书 iOS:安装描述文件 → 设置 → 通用 → 关于本机 → 证书信任设置 → 开启完全信任
- 之后访问
https://<主机IP>:8443→ 播放时自动常亮、停止自动恢复
HTTPS 证书为服务器启动时用 openssl 自动生成的自签证书,SAN 自动覆盖当前局域网 IP。
- 服务器自动写日志:
logs/run-<时间戳>.log(主控切换时自动新建文件,不会堆在一起) - 分析同步质量:
node analyze-log.js logs/run-<最新时间戳>.log
- 手动锁屏后声音被暂停:平台限制,无解;保持亮屏(Wake Lock 或调长自动锁屏)即可避免
- 无声:查服务器日志
[rep]行看各设备上报状态(audioCtx是否 running、队列是否在涨);iOS 需先点一下页面任意位置激活声音 - 主控刷新页面:仍是主控(身份自动恢复),日志不会因此新建
- 克隆后无法离线解码:
ffmpeg-core的 wasm 二进制(约 30MB)被.gitignore排除,未随仓库提交;首次运行会自动从 jsdelivr CDN 下载。如需完全离线,可手动把ffmpeg-core.js/ffmpeg-core.wasm放入public/vendor/ffmpeg/
flowchart LR
MC["主控浏览器<br/>ffmpeg.wasm 解码"] -- "上传 PCM 帧" --> SRV["服务器<br/>WebSocket 广播"]
SRV -- "广播音频帧 + 时间戳" --> SUB["子设备浏览器<br/>SNTP 校准 + Web Audio 调度"]
SUB -. "校准往返 syncReq / syncResp" .-> SRV
- 同步原理:服务器以自身时钟为基准,设备通过 SNTP 风格往返测量校准时钟偏移;音频帧时间戳指向「未来 + LEAD(默认 1200ms)」,各设备按本地时钟对齐播放,从根源消除累积漂移
- 协议:JSON 控制消息(
hello/welcome、syncReq/syncResp、start/stop/seek)+ 二进制音频帧 - 依赖极简:服务器端仅
ws;解码完全在浏览器端完成(ffmpeg.wasm)
sync-audio/
├── server.js # HTTP + HTTPS + WebSocket 服务器(广播、校准、主控管理、证书生成)
├── public/
│ ├── index.html # 浏览器端(主控 + 子设备同一页面,自动识别身份)
│ └── vendor/ffmpeg/ # ffmpeg.wasm 加载器(优先本地,缺失时回退 CDN)
├── analyze-log.js # 同步质量分析工具
├── test-client.js # 协议级验证脚本(node test-client.js)
├── run-test.ps1 / .sh # 后台启动脚本(Windows / 类 Unix)
├── logs/ # 运行时日志(git 忽略)
└── certs/ # 自动生成的 HTTPS 证书(git 忽略)