Skip to main content
Glama
gobly2333

pwa-music-player-mcp

by gobly2333
README.md
# Freq · 人和 AI 共用的播放器

一个共享播放状态的 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、签名媒体地址或任何凭据。

## Cookie 即用(真实网易云)

把仓库交给安装 Agent 时,只需让它执行:

```bash
npm install
cp .env.example .env
```

然后把你从已登录 `music.163.com` 浏览器会话复制出的完整 Cookie header 写进本地 `.env`:

```dotenv
MUSIC_SOURCE=netease
NETEASE_COOKIE="MUSIC_U=...; __csrf=...; ..."
```

再启动:

```bash
npm run demo
```

打开 <http://127.0.0.1:8788>,点“打开播放器”→右上角音乐库,就能搜索账号可播放的真实歌曲;MCP 同时使用同一曲库和播放房间。

`.env` 已被 Git 忽略。Cookie 只存在 Node 音源进程里,不返回给 PWA 或 MCP;限时音频地址也不进入播放状态、工具结果或持久化文件,而是由浏览器播放时通过同源解析端点按需取得。

如果播放接口提示 `NetEase login expired`,说明 Cookie 已失效:替换 `.env` 里的 Cookie 后重启进程即可。

## 无 Cookie 演示模式

需要 Node.js 20+。

```bash
npm install
npm run demo
```

不创建 `.env`,或设置 `MUSIC_SOURCE=demo`,再打开 <http://127.0.0.1:8788>。默认停在第一首演示曲,点迷你播放器即可展开完整页面。

另开一个终端启动 MCP:

```bash
MUSIC_RELAY_URL=http://127.0.0.1:8788 npm run mcp
```

此时页面和 MCP 会共享同一个内存播放房间。模型调用 `music_play` 后,打开着的 PWA 会在下一次轮询中收到新曲目;若浏览器的自动播放策略要求手势,页面会出现“点一下发出声音”的恢复卡片。

## 接入 MCP 客户端

Claude Desktop、Claude Code 或其他支持 stdio MCP 的客户端可使用:

```json
{
  "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,再加入:

```json
"MUSIC_TOKEN": "your-relay-token"
```

工具语义:

- `music_state()`:读取当前曲目、播放状态和队列
- `music_search(query)`:只搜索,不改播放状态
- `music_play(query)`:立即播放唯一匹配的曲目
- `music_queue(query)`:把唯一匹配的曲目加入队列
- `music_pause()`:暂停
- `music_resume()`:继续播放;浏览器仍可能要求一次用户手势
- `music_next()`:播放队列下一首;队列为空时顺序进入下一首演示曲

模糊查询命中多首时,MCP 会返回候选并要求模型说得更具体,不会擅自挑第一首。

## PWA 结构

公开版没有把播放器绑死在 demo 首页里:

```text
web/index.html             迷你播放器与完整播放页的语义化结构
web/player.css             独立播放器视觉、响应式布局与 Reduced Motion
web/player.js              音频生命周期、轮询、搜索、队列、歌词与红心
web/manifest.webmanifest   PWA 安装信息
web/sw.js                  离线壳和演示音频缓存
```

要嵌进已有 PWA,可把 `index.html` 中的 `[data-music-player]` 节点及其完整播放页移入你的页面,并引入:

```html
<link rel="stylesheet" href="/player.css">
<script type="module" src="/player.js"></script>
```

组件只依赖下方 API 合同,不依赖聊天 App、前端框架或特定音乐平台。红心默认存在浏览器 `localStorage`;共享队列与当前播放状态存在 Relay。

## Relay API 合同

现有后端不必使用 demo server,只要实现五个请求:

```text
GET  /api/state
GET  /api/tracks?q=<query>
GET  /api/tracks/<track-id>/lyrics
POST /api/queue
POST /api/control
```

搜索返回:

```json
{
  "tracks": [
    {
      "id": "blue-hour",
      "title": "Blue Hour",
      "artist": "Open Signals",
      "duration_sec": 24,
      "audio_url": "/audio/blue-hour.mp3",
      "palette": ["#7695ad", "#c19caf"]
    }
  ]
}
```

立即播放或排队:

```http
POST /api/queue
Content-Type: application/json

{ "track_id": "blue-hour", "play_now": true }
```

控制请求:

```http
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/netease-source.js),演示适配器在 [`src/music-source.js`](src/music-source.js)。要接其他平台,实现同样的 `initialTracks/search/track/lyrics/resolve` 五个方法即可。

`audio_url` 应保持为同源解析路径;若供应商返回签名地址,只在 `resolve()` 当次请求里使用,不要把它写入日志、MCP 工具返回或持久化存储。

生产场景通常会把 demo 的内存 `MusicRoom` 换成数据库或现有播放服务,同时保持 API 合同不变。播放器本身不要求网易云、Spotify、Apple Music 或任何特定供应商。

## 本地 Relay 配置

```bash
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 进程内存中,重启后恢复到第一首暂停状态

## 测试

```bash
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)**.

## License

代码使用 MIT License。`web/audio/` 下三段原创合成演示音频另以 CC0 1.0 释出,方便 fork 直接保留演示。