
这篇教程解决什么问题
你有一个网站,想做到:用户粘贴一个网易云/QQ音乐的分享链接,站内直接出现一个播放器,点了就能听,不用跳到网易云/QQ音乐的站外页面。
本文给出一套经过生产验证的完整方案:前端用 APlayer + MetingJS,后端自建 Meting-API 解析歌曲元数据和真实播放地址。全文面向会写代码、能自己部署 Docker 服务的站长(或者站长的 Agent),照着做可以直接落地。
技术栈
| 组件 | 作用 | 仓库 |
|---|---|---|
| APlayer | 播放器 UI(播放/暂停/进度条/歌词这些界面) | github.com/MoePlayer/APlayer |
| MetingJS | 浏览器端自定义元素 <meting-js>,负责调后端 API 拿歌曲数据,喂给 APlayer | github.com/metowolf/MetingJS |
| Meting-API | 自建的后端服务,代理网易云/QQ音乐等平台的接口,输出统一格式 | github.com/metowolf/Meting-API |
三者分工很清楚:Meting-API 负责"去各大音乐平台把数据抓出来",MetingJS 负责"把数据喂给播放器",APlayer 负责"播放器长什么样"。你只需要自己部署 Meting-API,前两个直接用 CDN 引入即可,不用自己写。
第一步:部署 Meting-API
Docker 部署
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 里,项目作者本人排查用户反馈时问的第一句话就是"你的服务器是不是在大陆外",得到了确认。
解决办法:
- 如果你有大陆的云服务器(阿里云/腾讯云/华为云等国内节点),直接把 Meting-API 部署在上面,问题自动解决。
- 如果你的主站服务器在海外,但手头有一台大陆的内网机器(比如家里/公司的电脑),可以用 frp 把 Meting-API 跑在那台机器上,再从主站反代过去——frp 只是解决"外部怎么访问到这台没有公网 IP 的内网机器",不影响这台机器自己的出站网络环境是大陆 IP 这个事实,所以依然能解决地域限制问题。
- 不要尝试伪造
X-Real-IP等请求头来"骗"网易云以为自己在大陆——这类方法在实测中不稳定(对一些网络环境有效,对另一些完全无效),而且本质是在绕过平台的访问控制,不建议。
第二步:配置反向代理
把 Meting-API 挂到你主站域名下的一个子路径,比如 https://your-domain.com/meting,这样前端调用时不用关心真实后端地址,也方便做访问控制。
Nginx/OpenResty 示例:
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 这个路径:
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,不用自己打包):
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.css">
<script src="https://cdn.jsdelivr.net/npm/aplayer/dist/APlayer.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/meting@2/dist/Meting.min.js"></script>
然后在需要出现播放器的地方放一个自定义标签:
<meting-js
server="netease"
type="song"
id="5257138"
api="https://your-domain.com/meting/api?server=:server&type=:type&id=:id&auth=:auth&r=:r"
theme="#e64831"
mutex="true">
</meting-js>
server:平台,netease(网易云)/tencent(QQ音乐)/kugou(酷狗)等type:资源类型,song(单曲)/playlist(歌单)/album(专辑)id:该平台上这个资源的 IDapi:关键参数,指向你自己部署的 Meting-API,:server/:type/:id/:auth/:r是占位符,MetingJS 会自动替换,照抄这个模板改域名即可mutex="true":同一页面有多个播放器时,播放一个会自动暂停其他的
不传 mini="true" 就是长条样式,适合列表页;想要紧凑一点可以加上。
第四步(进阶):从用户粘贴的分享链接自动识别
如果你希望用户发内容时直接粘贴一个分享链接(而不是手动填 server/type/id 三个字段),MetingJS 自带一个 auto 属性可以做这件事:
<meting-js auto="https://music.163.com/song?id=5257138"></meting-js>
但这里有个坑:MetingJS 内置的识别正则比较老,网易云的常见链接格式基本能认,但 QQ 音乐认不出来——它只认 y.qq.com/.../song/xxx.html 这种旧格式,而现在 QQ 音乐主流分享链接长这样:y.qq.com/n/ryqq/songDetail/xxx,带 songDetail 的新格式匹配不上,会直接识别失败。
如果你的用户群体会分享 QQ 音乐链接,建议自己写一个更宽松的解析函数,不依赖 auto 属性,直接解析出 server/type/id 后手动传给 <meting-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 的音乐分类——卡片列表和详情页都是直接内嵌播放器的长条样式,用的就是本文这套技术栈。
暂无评论
