一个共享播放状态的 PWA 音乐播放器:人在页面里使用迷你播放器和完整播放页,AI 通过 MCP 搜歌、播放、排队和控制同一个房间。
The human-facing PWA and the AI-facing MCP server control the same music room.
这个公开版从真实家庭 PWA 的现役播放器中独立抽出,保留了核心体验:
- 固定在页面底部的迷你播放器:封面、进度、上/下一首、播放暂停、快速队列
- 正式播放页:大封面、同步歌词、拖动进度、循环、随机、曲库搜索、队列与红心
- 浏览器自动播放被拦截时,给出明确的一次点击恢复卡片
- PWA manifest、离线壳与三段可自由分发的原创合成演示音频
- 七个 stdio MCP 工具:读取、搜索、立即播放、排队、暂停、继续、下一首
- Cookie 即用的网易云适配器;不填 Cookie 时自动回到零框架本地演示源
仓库不包含家庭 App、聊天页、聊天记录、私有音乐账号、生产域名、Cookie、签名媒体地址或任何凭据。
把仓库交给安装 Agent 时,只需让它执行:
npm install
cp .env.example .env然后把你从已登录 music.163.com 浏览器会话复制出的完整 Cookie header 写进本地 .env:
MUSIC_SOURCE=netease
NETEASE_COOKIE="MUSIC_U=...; __csrf=...; ..."再启动:
npm run demo打开 http://127.0.0.1:8788,点“打开播放器”→右上角音乐库,就能搜索账号可播放的真实歌曲;MCP 同时使用同一曲库和播放房间。
.env 已被 Git 忽略。Cookie 只存在 Node 音源进程里,不返回给 PWA 或 MCP;限时音频地址也不进入播放状态、工具结果或持久化文件,而是由浏览器播放时通过同源解析端点按需取得。
如果播放接口提示 NetEase login expired,说明 Cookie 已失效:替换 .env 里的 Cookie 后重启进程即可。
需要 Node.js 20+。
npm install
npm run demo不创建 .env,或设置 MUSIC_SOURCE=demo,再打开 http://127.0.0.1:8788。默认停在第一首演示曲,点迷你播放器即可展开完整页面。
另开一个终端启动 MCP:
MUSIC_RELAY_URL=http://127.0.0.1:8788 npm run mcp此时页面和 MCP 会共享同一个内存播放房间。模型调用 music_play 后,打开着的 PWA 会在下一次轮询中收到新曲目;若浏览器的自动播放策略要求手势,页面会出现“点一下发出声音”的恢复卡片。
Claude Desktop、Claude Code 或其他支持 stdio MCP 的客户端可使用:
{
"mcpServers": {
"music-player": {
"command": "node",
"args": ["/absolute/path/to/Freq/src/mcp-server.js"],
"env": {
"MUSIC_RELAY_URL": "http://127.0.0.1:8788"
}
}
}
}如果 Relay 开启了 token,再加入:
"MUSIC_TOKEN": "your-relay-token"工具语义:
music_state():读取当前曲目、播放状态和队列music_search(query):只搜索,不改播放状态music_play(query):立即播放唯一匹配的曲目music_queue(query):把唯一匹配的曲目加入队列music_pause():暂停music_resume():继续播放;浏览器仍可能要求一次用户手势music_next():播放队列下一首;队列为空时顺序进入下一首演示曲
模糊查询命中多首时,MCP 会返回候选并要求模型说得更具体,不会擅自挑第一首。
公开版没有把播放器绑死在 demo 首页里:
web/index.html 迷你播放器与完整播放页的语义化结构
web/player.css 独立播放器视觉、响应式布局与 Reduced Motion
web/player.js 音频生命周期、轮询、搜索、队列、歌词与红心
web/manifest.webmanifest PWA 安装信息
web/sw.js 离线壳和演示音频缓存
要嵌进已有 PWA,可把 index.html 中的 [data-music-player] 节点及其完整播放页移入你的页面,并引入:
<link rel="stylesheet" href="/player.css">
<script type="module" src="/player.js"></script>组件只依赖下方 API 合同,不依赖聊天 App、前端框架或特定音乐平台。红心默认存在浏览器 localStorage;共享队列与当前播放状态存在 Relay。
现有后端不必使用 demo server,只要实现五个请求:
GET /api/state
GET /api/tracks?q=<query>
GET /api/tracks/<track-id>/lyrics
POST /api/queue
POST /api/control
搜索返回:
{
"tracks": [
{
"id": "blue-hour",
"title": "Blue Hour",
"artist": "Open Signals",
"duration_sec": 24,
"audio_url": "/audio/blue-hour.mp3",
"palette": ["#7695ad", "#c19caf"]
}
]
}立即播放或排队:
POST /api/queue
Content-Type: application/json
{ "track_id": "blue-hour", "play_now": true }控制请求:
POST /api/control
Content-Type: application/json
{ "action": "pause", "position_sec": 8.25 }action 支持 play、pause、seek、next、previous、ended。响应都返回最新播放快照。
网易云适配器在 src/netease-source.js,演示适配器在 src/music-source.js。要接其他平台,实现同样的 initialTracks/search/track/lyrics/resolve 五个方法即可。
audio_url 应保持为同源解析路径;若供应商返回签名地址,只在 resolve() 当次请求里使用,不要把它写入日志、MCP 工具返回或持久化存储。
生产场景通常会把 demo 的内存 MusicRoom 换成数据库或现有播放服务,同时保持 API 合同不变。播放器本身不要求网易云、Spotify、Apple Music 或任何特定供应商。
HOST=127.0.0.1 \
PORT=8788 \
MUSIC_TOKEN=choose-a-token \
npm run demo- 默认只监听
127.0.0.1 - 设置
MUSIC_TOKEN后,MCP 使用 Bearer token;demo 首页会写入同值的 HttpOnly/SameSite cookie,浏览器仍可直接运行 - 播放房间在 demo 进程内存中,重启后恢复到第一首暂停状态
npm test
npm run check聚焦测试覆盖播放房间状态转换、立即播放后把被打断曲目放回队首、PWA 静态资源和音频可用、token 的浏览器/MCP 两条路径、Cookie 不下传的网易云搜索/解析边界,以及 stdio MCP 工具声明。
它诞生于一个家庭 AI 伴侣项目:迷你播放器常驻聊天页,完整页面承载一起听、歌词、队列和模型点歌。
共创人:小cc、桑尼(Sunnymilk / Sunny)。
公开版由 词词 发起并授权;Sunnymilk(Sunny,家里的 Codex) 从现役实现中抽取、去除家庭耦合、补齐通用 MCP 与独立 demo。
Co-created by CC and Sunnymilk (Sunny). Open-source edition initiated by Cici and extracted/generalized by Sunnymilk (Sunny, the household Codex).
代码使用 MIT License。web/audio/ 下三段原创合成演示音频另以 CC0 1.0 释出,方便 fork 直接保留演示。