# 自建音乐播放:APlayer + MetingJS + Meting-API 部署教程 > 站长向:粘贴网易云/QQ音乐分享链接,站内直接出播放器。含关键的地域限制坑和解决办法。 ## 这篇教程解决什么问题 你有一个网站,想做到:**用户粘贴一个网易云/QQ音乐的分享链接,站内直接出现一个播放器,点了就能听**,不用跳到网易云/QQ音乐的站外页面。 本文给出一套经过生产验证的完整方案:前端用 **APlayer + MetingJS**,后端自建 **Meting-API** 解析歌曲元数据和真实播放地址。全文面向会写代码、能自己部署 Docker 服务的站长(或者站长的 Agent),照着做可以直接落地。 --- ## 技术栈 | 组件 | 作用 | 仓库 | |---|---|---| | **APlayer** | 播放器 UI(播放/暂停/进度条/歌词这些界面) | github.com/MoePlayer/APlayer | | **MetingJS** | 浏览器端自定义元素 ``,负责调后端 API 拿歌曲数据,喂给 APlayer | github.com/metowolf/MetingJS | | **Meting-API** | 自建的后端服务,代理网易云/QQ音乐等平台的接口,输出统一格式 | github.com/metowolf/Meting-API | 三者分工很清楚:Meting-API 负责"去各大音乐平台把数据抓出来",MetingJS 负责"把数据喂给播放器",APlayer 负责"播放器长什么样"。你只需要自己部署 Meting-API,前两个直接用 CDN 引入即可,不用自己写。 --- ## 第一步:部署 Meting-API ### Docker 部署 ```bash docker run -d \ --name meting-api \ --restart unless-stopped \ -p 127.0.0.1:39311:80 \ -e METING_URL=https://your-domain.com/meting \ -e METING_TOKEN=<自己生成一个随机字符串> \ ghcr.io/metowolf/meting-api:latest ``` 关键环境变量: - `METING_URL`:这个服务对外能访问到的公网地址(**不要带 `/api` 后缀**,服务内部会自动拼),后面生成歌词/封面/播放地址这些回调链接时要用。填错了播放会直接失败。 - `METING_TOKEN`:HMAC 签名密钥,用来给播放地址等敏感接口做鉴权,随便生成一串随机字符串即可,不需要记住,只是内部用。 建议只绑定 `127.0.0.1`,不要让这个端口直接暴露公网——下面会说明原因。 ### 关键坑:网易云播放地址的地域限制 **这是最容易踩的坑,务必先看完再部署。** 网易云音乐获取真实播放地址(`type=url`)的接口对服务器所在地域有限制:**境外服务器大概率拿不到真实播放地址**,哪怕歌曲搜索、封面图、歌曲详情这些接口都正常,播放地址就是空的。这不是配置问题,是网易云自己的策略——在 [metowolf/Meting 的 issue #15](https://github.com/metowolf/Meting/issues/15) 里,项目作者本人排查用户反馈时问的第一句话就是"你的服务器是不是在大陆外",得到了确认。 **解决办法**: - 如果你有大陆的云服务器(阿里云/腾讯云/华为云等国内节点),直接把 Meting-API 部署在上面,问题自动解决。 - 如果你的主站服务器在海外,但手头有一台大陆的内网机器(比如家里/公司的电脑),可以用 **frp** 把 Meting-API 跑在那台机器上,再从主站反代过去——frp 只是解决"外部怎么访问到这台没有公网 IP 的内网机器",不影响这台机器自己的出站网络环境是大陆 IP 这个事实,所以依然能解决地域限制问题。 - 不要尝试伪造 `X-Real-IP` 等请求头来"骗"网易云以为自己在大陆——这类方法在实测中不稳定(对一些网络环境有效,对另一些完全无效),而且本质是在绕过平台的访问控制,不建议。 --- ## 第二步:配置反向代理 把 Meting-API 挂到你主站域名下的一个子路径,比如 `https://your-domain.com/meting`,这样前端调用时不用关心真实后端地址,也方便做访问控制。 Nginx/OpenResty 示例: ```nginx location /meting/ { proxy_pass http://127.0.0.1:39311/; proxy_set_header Host $host; } ``` **⚠️ 另一个坑**:如果你用的是某些面板自带的"反向代理"可视化功能(比如 1Panel),不一定和上面这种 Nginx `location` + 结尾斜杠的行为一致——有些面板的反代是把匹配到的路径**原样**转发给后端,不会自动裁剪掉 `/meting` 这段前缀。实测结果是:前端请求 `/meting/api`,面板转发给后端的路径还是 `/meting/api`,但 Meting-API 内部默认只认 `/api`,于是永远 404。 **怎么判断自己中招没有**:部署完用 `curl https://your-domain.com/meting/api?server=netease&type=search&id=test`,如果返回的是 Meting-API 自己的 "Not Found"(通常是纯文本 `Not Found`,带 `x-request-id` 这类响应头),而不是 Nginx/OpenResty 的默认 404 页面,说明请求确实到了 Meting-API,只是路径没对上。 **怎么修**:给容器加一个环境变量 `HTTP_PREFIX=/meting`,让 Meting-API 内部也认 `/meting/api` 这个路径: ```bash docker run -d \ --name meting-api \ --restart unless-stopped \ -p 127.0.0.1:39311:80 \ -e HTTP_PREFIX=/meting \ -e METING_URL=https://your-domain.com/meting \ -e METING_TOKEN=<随机字符串> \ ghcr.io/metowolf/meting-api:latest ``` --- ## 第三步:前端接入 在页面里引入三个文件(CDN,不用自己打包): ```html ``` 然后在需要出现播放器的地方放一个自定义标签: ```html ``` - `server`:平台,`netease`(网易云)/ `tencent`(QQ音乐)/ `kugou`(酷狗)等 - `type`:资源类型,`song`(单曲)/ `playlist`(歌单)/ `album`(专辑) - `id`:该平台上这个资源的 ID - `api`:**关键参数**,指向你自己部署的 Meting-API,`:server`/`:type`/`:id`/`:auth`/`:r` 是占位符,MetingJS 会自动替换,照抄这个模板改域名即可 - `mutex="true"`:同一页面有多个播放器时,播放一个会自动暂停其他的 不传 `mini="true"` 就是长条样式,适合列表页;想要紧凑一点可以加上。 --- ## 第四步(进阶):从用户粘贴的分享链接自动识别 如果你希望用户发内容时直接粘贴一个分享链接(而不是手动填 server/type/id 三个字段),MetingJS 自带一个 `auto` 属性可以做这件事: ```html ``` **但这里有个坑**:MetingJS 内置的识别正则比较老,网易云的常见链接格式基本能认,但 **QQ 音乐认不出来**——它只认 `y.qq.com/.../song/xxx.html` 这种旧格式,而现在 QQ 音乐主流分享链接长这样:`y.qq.com/n/ryqq/songDetail/xxx`,带 `songDetail` 的新格式匹配不上,会直接识别失败。 如果你的用户群体会分享 QQ 音乐链接,建议自己写一个更宽松的解析函数,不依赖 `auto` 属性,直接解析出 server/type/id 后手动传给 ``: ```js function parseMusicShareUrl(url) { if (!url) return null; let m = url.match(/music\.163\.com.*?playlist.*?[?&]id=(\d+)/); if (m) return { server: "netease", type: "playlist", id: m[1] }; m = url.match(/music\.163\.com.*?album.*?[?&]id=(\d+)/); if (m) return { server: "netease", type: "album", id: m[1] }; m = url.match(/music\.163\.com.*?song.*?[?&]id=(\d+)/); if (m) return { server: "netease", type: "song", id: m[1] }; // 同时兼容新旧两种 QQ 音乐链接格式 m = url.match(/y\.qq\.com.*?songDetail\/(\w+)/); if (m) return { server: "tencent", type: "song", id: m[1] }; m = url.match(/y\.qq\.com.*?\/song\/(\w+)\.html/); if (m) return { server: "tencent", type: "song", id: m[1] }; m = url.match(/y\.qq\.com.*?playlist\/(\w+)/); if (m) return { server: "tencent", type: "playlist", id: m[1] }; return null; // 解析失败时,建议回退成普通外链按钮,而不是报错 } ``` 这个函数没覆盖短链接(比如网易云的 `163cn.tv/xxx`)和手机端分享链接,如果需要支持,得在服务端发一次 HTTP 请求跟踪重定向,拿到跳转后的真实链接再解析——这个复杂度通常没必要,遇到解析失败的情况,直接保留原始链接做普通外链按钮(跳转到网易云/QQ音乐原页面)就是一个合理的兜底,不影响基本可用性。 --- ## 安全提醒 Meting-API 的 `search`/`song`/`album`/`artist`/`playlist` 这几个接口是**完全不需要鉴权**的(只有获取播放地址/歌词/封面这几个敏感操作才需要 `auth` 签名)。如果把这个服务直接暴露公网、不做任何访问限制,等于把自己的服务器变成了一个免费公共代理,任何人都能拿去用——流量一大,很可能导致这个来之不易的"大陆IP能正常获取数据"的身份被网易云/QQ音乐风控盯上,功能可能直接失效。 建议: - Meting-API 容器只绑定 `127.0.0.1`,不直接对公网暴露端口 - 反向代理这一层加访问控制——如果只有你自己的站点会调用,可以在防火墙/安全组层面限制来源 IP;如果做不到精确限流,至少配置好限流规则,避免被批量调用薅走大陆 IP 的访问额度 --- ## 效果可以参考 这套方案已经在生产环境验证,可以直接体验效果:[octohz.com 的音乐分类](https://octohz.com/Entertainment/music)——卡片列表和详情页都是直接内嵌播放器的长条样式,用的就是本文这套技术栈。 --- **分类**:教程 **标签**:com · meting · Meting **作者**:子龙 **链接**:https://octohz.com/p/2187