顺雅(Msapi)开发状态
顺雅(Msapi)是一套可自托管的网页音乐播放器:服务端自己部署,前台一行 <script> 就能嵌进任何网站。本文是这个项目的完整状态说明——它是什么、能做什么、架构如何、经历了哪些版本、目前处于什么阶段、以及已知的边界在哪里。
当前版本 v1.6.9(2026-10-20),共发布 11 个版本,许可全文见仓库 LICENSE。v1.6.5 之后的一批改动——后台路由改为目录式入口、密钥删除级联清理、歌单配置导出导入、域名开关按用户独立、配置入库——已随 v1.6.9 打包发布,逐条见第六节。
一、项目是什么
它解决的是一个很具体的问题:想给自己的网站加一个好看、可控、不依赖第三方平台的音乐播放器。
市面上的选择大致两类:要么是纯前端组件(播放器有了,但歌单得自己维护),要么是第三方托管服务(省事,但内容与数据都在别人那里)。顺雅位于两者之间:
- 服务端自己部署:密钥、歌单、播放器配置都在你自己的服务器与数据库里。
- 前台一行接入:不需要改宿主站的构建流程,也不需要引入框架。
- 界面完全自绘:播放器界面全部运行在封闭的 Shadow DOM 内,不污染宿主页面的样式。
二、能做什么
2.1 播放器界面
- 两套内置皮肤,后台随时切换:
- 悬浮玻璃:可拖动的悬浮面板,支持边缘吸附与沉浸全屏。
- 玫瑰玻璃:常驻侧边抽屉,可收起为贴边把柄;封面环形播放进度、播放时顺时针旋转(暂停冻结角度不回正);歌曲列表从所点击的按钮位置放大展开;移动端为底部抽屉,可上下拖动并吸附半屏/全屏。
- 歌词:LRC 解析与逐行同步,窄屏下自动跑马灯,且只在换行时重排、匀速滚动。
- 音量与增强:音量滑杆呼出式交互,支持 1x / 2x / 3x 音量增强。
- 播放模式:顺序、单曲循环、随机。
- 不压住宿主导航:自动探测宿主固定在底部的导航栏/工具条高度,播放器与歌词框抬到它上方;探测要求真贴底且宽度、高度、可点子项都符合,悬浮球与 cookie 横幅不会被误判;后台可按域名改成固定值。
- 失败会说清原因:缺密钥、密钥无效、密钥被停用、请求太频繁、连不上服务器、域名未授权、密钥与域名不匹配、歌单为空……各有一条独立提示,不再一律显示同一句话。
- 对宿主页面克制:不动宿主的滚动条与全局样式;播放器区域内的右键菜单、文本选中与图片拖拽均已禁用,但宿主页面不受影响。
- 换页不掉链子:对 Swup / Pjax 这类无刷新换页框架做了去重,来回切页面不会出现两个播放器同时出声。
- 二次打开更快:可选接入 Redis,把上游解析结果(播放地址/歌词/封面)、歌单配置与播放状态缓存起来——同一首歌再次点开不再回源,换设备也能接着上次的位置。不接也不影响使用。
- 封面可从自有存储出图:把封面本地化到自己的对象存储后,播放器直接用自家存储的图片,不再受第三方图床可用性与防盗链的影响。
2.2 后台管理
- 密钥管理:为不同站点签发不同密钥,可给密钥绑定授权域名(拿错密钥访问会被直接拦住并说明原因),普通用户有创建数量限制;删除密钥会连带清掉名下歌单与歌曲,不留孤儿数据。
- 歌单配置导出导入:每个密钥的歌单可导出为 JSON(只含歌单数据,不含任何密钥信息),换站或重装时一键导回。
- 域名授权:按宿主主机名决定谁能加载播放器;授权与「自动登记新域名」两个开关按用户独立(互不影响),自动登记默认关闭,也能为每个域名单独设置歌词框与播放器的底部修正量。
- 歌单与歌曲:支持远程歌单快照、歌曲排序、封面缓存;同步与编辑保存时自动清掉上游已下架的歌曲。
- 播放器配置:皮肤选择、每个皮肤各自的初始位置(停靠边 + 垂直百分比);可指定一条密钥作为落地页演示播放器。
- 存储设置(“设置 → 储存”):对象存储、封面本地化的按歌单进度、Redis 播放器缓存(含连通性自检、TTL 与手动开关)三块。
- 账号与权限:超级管理员 / 管理员 / 普通用户三级,人员与设置仅管理员可见。
- 邮件模板:所有通知邮件均可自定义,含 HTML 与纯文本两份。
- 安装与升级:图形化安装器;数据结构统一到 SQL 单一来源,升级不需要手工改表;配置文件收敛为只放数据库连接,其余配置入库统一管理。
三、技术架构
v1.5.0 之后,播放器分成两层:
modules/
core/ 引擎:鉴权、状态、音频内核、界面契约、歌词、主题、底部避让(全站只有一份)
router/ 皮肤①:悬浮玻璃
rose/ 皮肤②:玫瑰玻璃
registry.php 清单装载器- 引擎与皮肤分离:播放能力全部收在
core/;皮肤只负责界面与交互。 - 皮肤由清单自述:每个皮肤一个目录 + 一份
skin.json,registry.php统一装载;后台的皮肤选择器读的就是同一份清单。 - 皮肤契约:皮肤需实现
loadCSS/createWidget/getHTML/_css四项,引擎通过data-mp属性绑定元素。 - 新增皮肤的成本:一个目录 + 一份清单 + 界面实现,不需要改动引擎与入口。
- 不依赖第三方 CDN:APlayer 1.10.1 只作音频内核,随包自托管(
assets/lib/aplayer/),界面样式全部自绘。 - 可选缓存层(v1.6.5):
admin/lib/redis.php一条缓存通道,装了 phpredis 走原生扩展、没装走纯 PHP RESP 实现,两者对外契约一致,上层不用改代码。 - 后台目录式路由(v1.6.9):后台页面从单文件改为
admin/pages/<action>/index.php目录入口,靠 nginx 内置的目录索引与自动补斜杠工作,零 rewrite / try_files 配置,任何主机都能直接部署。 - 配置入库(v1.6.9):站点 / 后台 / 接口配置与签名密钥的权威副本在数据库,按功能分组、后台可编辑;
config.php只留数据库连接这一段(库不可达时唯一的兜底),由统一入口合并读取。
四、接入方式
- 部署服务端,用图形化安装器完成建库与初始化。
- 在后台创建一个密钥(可选:给密钥绑定授权域名,这条密钥就只能在该域名下使用)。
- 把下面这行放进任意网站的
<body>里:
<script src="https://你的域名/api.php?key=你的密钥" defer></script>想让它出现得更快,可以在 <head> 里加一行预连接:
<link rel="preconnect" href="https://你的域名" crossorigin>密钥走查询参数而不是标签属性——因为标签属性服务端读不到。旧写法与旧入口已在 v1.5.0 下线。
五、全部版本时间线
| 版本 | 日期 | 主题 | 一句话 |
|---|---|---|---|
| v1.0.0 | 09-23 | 首个正式版 | 服务本体、安装器、鉴权与可嵌入的播放器 |
| v1.1.0 | 09-24 | 后台重构 | 单入口路由、邮件模板、封面缓存 |
| v1.2.0 | 09-24 | 换页修复 | 页面脚本外置加载 |
| v1.3.0 | 09-24 | 降低门槛 | 去掉激活校验,令牌自动签发 |
| v1.4.0 | 09-27 | 播放器重构 | 路由式模块化架构 |
| v1.4.2 | 10-01 | 结构统一 | SQL 单一来源、多实例、歌单快照 |
| v1.5.0 | 10-04 | 引擎与皮肤分离 | 新增玫瑰玻璃皮肤、按皮肤存位置 |
| v1.6.0 | 10-06 | 面向宿主站点 | 域名授权与自动登记、底部避让重做、失败原因分开提示 |
| v1.6.2 | 10-08 | 宿主站点收敛·第二回合 | 底栏探测重写、让出量分 PC 与移动端、域名授权可指定密钥、沉浸式歌单弹窗贴底 |
| v1.6.5 | 10-10 | 往数据层走 | Redis 播放器数据缓存、封面本地化、后台交互硬伤修复 |
| v1.6.9 | 10-20 | 结构与数据收口 | 后台目录式路由、密钥删除级联清理、歌单导出导入、域名开关按用户独立、配置入库 |
六、逐版本详解
v1.0.0(2026-09-23)首个正式版
把整件事的骨架立起来:音乐 API 服务本体(密钥鉴权、歌单与歌曲数据接口)、图形化安装器、JWT 令牌签发,以及一行 <script> 即可嵌入网站的播放器模块。
v1.1.0(2026-09-24)后台路由重构与邮件模板
- 后台单入口路由:所有页面走统一入口,页面之间切换不再整页刷新
- 新增邮件模板管理与封面缓存
- 修复切换页面后页面脚本未加载的问题;密钥改为掩码显示
v1.2.0(2026-09-24)页面脚本外置加载
页面脚本改为外置加载,彻底修复无刷新切换页面后脚本失效的问题,并修复由此连带的密钥页与导航问题。v1.1 与 v1.2 其实是连着修同一件事:v1.1 先补一手,v1.2 换成外置加载才算根治。
v1.3.0(2026-09-24)自动签发播放器令牌
移除账户激活与到期校验,令牌改由后台自动签发。接入成本从「部署 + 建密钥 + 配令牌」降到只需「部署 + 建密钥」。
v1.4.0(2026-09-27)播放器模块化架构
播放器拆分为路由式模块化架构,核心、皮肤、交互各自成模块按需加载。「皮肤」这个概念第一次出现在项目里——虽然那时还只有一套。
v1.4.2(2026-10-01)结构统一与多实例播放
- 安装与升级统一到 SQL 单一来源,不再依赖两套结构定义
- 播放器支持同一页面多个实例
- 后台新增歌曲排序与远程歌单快照
- 重写文档、补充界面截图;添加 MIT 许可证(后续版本已更新许可)
v1.5.0(2026-10-04)引擎与皮肤彻底分离
目前结构改动最大的一版:43 个文件、约 3200 行增删。
- 架构分层:播放能力收进
modules/core/,皮肤只画界面;皮肤由清单自述、统一装载。 - 新增「玫瑰玻璃」皮肤:侧边抽屉、封面环形进度与播放旋转、音量增强、歌曲列表弹层、移动端可拖动抽屉。
- 初始位置按皮肤分别保存:切换皮肤时各自记住自己的停靠边与垂直位置。
- 嵌入方式统一:只剩一行
?key=写法,旧入口与旧写法下线。 - 一批修复:换页框架下重复加载导致声音重叠;歌词跑马灯在移动端反复抽搐;切换皮肤时偶发的停靠方向判反导致播放器整屉飞出屏幕;后台登录后设置未恢复导致保存时覆盖数据库配置;后台皮肤卡片保存或换页后点不动。
- 清理:移除旧版单体皮肤、路由旧入口与旧转发壳,仓库不再留冗余文件。
v1.6.0(2026-10-06)面向宿主站点收敛
这一版几乎都在处理「播放器嵌在别人站上」的各种意外——不再是播放器自己能不能跑,而是它别给宿主站添乱。
- 底部避让重做:底部占用改由一个专门的探测模块负责,只认真正贴底的导航栏(高度、宽度、可点子项都要满足),悬浮球、横幅、吸底广告不再被当成底栏;差值要连续两次采样一致才生效,换页、转屏、DOM 变化都会重测,另有定时兜底。
- 失败原因分开说:缺密钥、密钥无效、密钥停用、限流、服务不可达、域名未授权、密钥与域名不匹配、歌单为空、歌单加载失败等各有独立提示,同一种提示不会叠加;启动失败时也不再补一条「后台还没有歌单」把真正原因盖住。
- 域名授权:新增域名页与「自动登记」,按宿主主机名放行或拦截(主机名精确匹配,不做 www 与子域合并);密钥也能绑定授权域名,在别的域名下用会被直接拦住并说明是哪条密钥。
- 后台细节:设置页拆成站点 / 邮件 / 安全 / 接口 / 存储五个子页;新建密钥改为弹窗,带名称与授权域名;提交表单与切换开关不再整页刷新,页内的测试播放器不会再被刷掉。
- 安全与清理:在线用户接口补管理员门槛,前端错误上报接口补限流与内容清洗并改写服务器日志;移除没有界面入口的调试开关与空占位文件。
v1.6.2(2026-10-08)宿主站点收敛,第二回合
v1.6.0 发布之后,真正接到站上又暴露出几个问题,这一版都在收拾它们——底栏探测的判定、让出量的计算范围、域名授权的粒度,以及移动端沉浸式歌单弹窗的一个定位错误。
- 底部避让的判定太严:v1.6.0 那套探测要求底栏「真贴底」(与视口底边只差几个像素)、高度 ≥40px、至少 3 个链接、嵌套不超过 2 层。拿 8 种真实底栏形态一测,只认出 3 种——矮一点的栏、纯
div拼的 tab、离底十几像素的悬浮胶囊全漏了。重写后这 8 种全部认出,没有底栏的页面依然不会误判;悬浮胶囊按「离底距离 + 自身高度」算真实占位,不再拿元素整体高度去估。 - 让位的只有歌词框:播放器本体与拖拽的边界夹取此前根本不看让出量,底栏一高,播放器照样被压住。现在初始位置、窗口缩放回退、拖拽吸附三处都吃这个值:实测底栏 56px,播放器最低点正好上移 56px,与底栏顶部的间隙保持不变。
- 让出量分成 PC 与移动端两套:后台域名页从「一个自动开关 + 两行修正」改成 PC / 移动端各一块,各有自动开关、歌词修正、播放器修正。客户端每 60 秒把实测到的底栏高度报回后台,直接显示成「最近探测」,可一键填进修正量。自动开着是「探测值 + 修正量 + 8px 保护带」,关掉就只按修正量让位。
- 域名授权可以指定密钥:一个账号签发了多条密钥时,可以只让某一条密钥使用某个域名,其余密钥不受影响;不指定就是该账号所有密钥共用(老数据保持原样)。权限同时收紧:超级管理员能改所有人的授权,管理员与普通用户只能改自己名下的。
- 移动端沉浸式歌单弹窗贴回屏幕底边:沉浸式是全屏遮罩,宿主底栏本来就被它盖住,弹窗却还按宿主底栏的高度往上让位,结果弹窗下面空出一条、露出宿主页的底栏。现在弹窗直接贴屏幕底边,底部内边距改用 iOS 安全区。
- 顺手修掉的两处:域名备注保存后被写成
0;普通用户拿别人那行的 id 提交保存,会在自己名下凭空多出一行。
v1.6.5(2026-10-10)播放器数据缓存与封面本地化
前几版都在收拾「嵌在别人站上」的问题,这一版换了方向——往自己的数据层走:一边让播放器不再重复回源,一边把封面从第三方图床搬到自己能管的地方。
- Redis 播放器数据缓存(新增):缓存三类数据——上游解析结果(播放地址、歌词、封面)、歌单配置 JSON、播放器状态(按 IP + 设备校验)。播放地址这类短时效数据用短 TTL,歌词与封面用长 TTL。实测同一首歌第二次播放从约 700ms 降到 8ms 量级。
- 缓存的取舍写死在设计里:手动开关优先级最高——关掉即完全不使用;开启时按连通性自动生效 / 自动停用,服务端恢复后自动继续,不需要人工干预;连不上只是跳过缓存(fail-open),绝不把错误抛给访客,也不会把 PHP 警告写进接口响应。
- 封面本地化(新增):封面按内容寻址存进自己的对象存储,同一张图跨歌单只存一份;删除歌单先释放索引,无人使用的图片进回收池、过了保留期才真正删除——删了又加回来能直接复用。后台可对整个歌单分批预热,也能一键迁移历史封面。
- 存储设置页:「设置 → 储存」现在有三块——对象存储、封面本地化进度(每个歌单一张卡片,本地还没有歌的会如实标注)、Redis 播放器缓存(含连通性自检与各项 TTL)。
- 后台交互的几处硬伤:表单提交改用隐藏字段承载「点了哪个按钮」,修掉「点保存没反应、配置像回弹」;校验失效时给出明确提示而不是一页报错;封面卡片只统计当前管理员名下的歌单,别人的歌单不再混进来。
- 播放器与后台重构:歌词、播放器与提示层继续拆分重写,沉浸模式与播放状态云同步进入常规能力。
- 数据层:增量同步增加「结构指纹」短路(稳态下不再逐条比对表结构);MySQL 与 SQLite 双驱动的结构同步、索引与列修补;新增配置项的自动播种。
- 清理:删掉 9 个失去入口的文件(两端各一套未使用的 skin / tokens 样式与 users 脚本、一个 extract 处理器、两份过时的设计文档,内容已并入代码与本文)。
v1.6.9(2026-10-20)后台路由与数据的结构收口
v1.6.5 之后的使用中暴露的几类问题——部署门槛、删数据不干净、配置两处存放互相打架——这一版集中收口,86 个文件、约 7400 行增删,是结构层面最大的一次整理。
- 后台路由改为目录式入口:后台页面从
admin/pages/<action>.php单文件改为admin/pages/<action>/index.php目录索引,靠 nginx 内置的目录索引与自动补斜杠工作——零 rewrite / try_files 配置,虚拟主机、宝塔、默认站点都能直接部署;判定链收进统一的 front.php / boot.php。顺带下线了在线歌厅(房间)功能与后台 service 层。 - 密钥删除改为级联清理:此前删除密钥只删密钥一行,名下的歌单与歌曲全部变成永远留在库里的孤儿数据(实测库里积累出 6 个孤儿歌单、44 首孤儿歌曲)。现在删除会连带清掉歌单与歌曲、释放无引用的歌曲封面、解绑域名授权(域名本身与授权状态保留——那是用户资产);删除前先做归属校验,确认框如实展示「将连带删除:N 个歌单、M 首歌」。
- 歌单配置导出与一键导入:每个密钥的歌单可导出为 JSON——只含歌单与歌曲数据,不含密钥、密钥名与任何归属信息,导出文件名带密钥名 + 用户名 + 时间戳;换站或重装时选文件导入,同名歌单自动跳过。入口在配置页每个密钥的卡片里。
- 同步时删除上游已下架的歌曲:歌单同步与编辑保存会把上游已经下架的歌从本地快照里删掉(本轮同步开始前就已缺失的才删,两轮确认防误删),保存后的提示会写清「新增 X · 删除 X · 下架 X」。
- 域名开关按用户独立:「启用域名授权」与「自动添加检测到的域名」从全站一份改为每个用户一套——A 用户关掉自动添加不再影响 B 用户,运行时按密钥主人取值;「自动添加」默认关闭(它会让任何主机名自动写进域名表并放行,缺省开等于新装即可被任意站白嫖);超管新增「用户开关总览」只读视图,能看清每个用户的生效值。
- 配置文件收敛为仅数据库连接:
config.php从「site / admin / api / 密钥全在文件」收敛为只留db一段——其余配置的权威副本入库(按功能分组、后台可编辑),由统一合并入口读取并兼容旧结构回落;安装器同步新结构,签名密钥与演示密钥物理隔离成独立配置行,整行 REPLACE 不会再把它们冲掉。 - 一批修复:安装向导守卫在深层路径下的连环 302(相对跳转逐层命中,实测叠出几十层 /install/install/…,改根绝对路径一次到位);封面本地化的统计范围漏掉别人的歌单(超管现在可见全部并带归属标注,批量入口补归属校验);SQL 模板中极验配置表缺失主键(新装会导致保存直接失败);恢复一个被误删的通用配置保存器。
七、八个阶段
- 09-23 → 09-24|两天打地基:四个版本压缩在两天内,从「能跑」到「能天天用」。
- 09-27|播放器第一次重构:开始有「皮肤」的概念,为后来的分层埋下伏笔。
- 10-01|结构收口:数据结构统一、补齐工程门面(文档与许可证)。
- 10-04|引擎与皮肤分离:加一套新皮肤不再需要复制播放逻辑,这是本轮最大的结构收益。
- 10-06|面向宿主站点:视角从「播放器自己」换到「宿主页面的规矩」——底部导航不能压、域名要说清能不能用、失败要让宿主知道原因。
- 10-08|把上一版的尾巴收完:判定、粒度、定位这三类问题逐个收口;同日仓库的 LICENSE 也从 MIT 版换成了新的许可全文。
- 10-10|往数据层走:播放器数据缓存与封面本地化落地——前者省掉重复回源,后者把封面搬到自己能管的存储上;顺带把后台交互的几处硬伤修掉。
- 10-20|结构与数据的收口:部署门槛降到零(目录式路由)、删数据删干净(级联清理)、配置只放一处(入库)——把前几版攒下的结构债一次还清。
八、说明文档的演进
文档跟着代码改了五次:v1.0.0 恢复并同步 README;v1.4.2 重写 README、补充界面截图并修正过期内容;v1.5.0 把「项目结构」章节改为「引擎 + 皮肤」两层,嵌入章节统一为一行 ?key= 写法,并清掉已删除文件的说明;v1.6.2 之后把「开源许可」章节改写为「许可协议」(徽章与项目结构注释同步),并修正技术栈里一句过期的 CDN 说明;v1.6.5 给 README 补了两节——「生产环境必需的 PHP 设置」(display_errors = 0、log_errors = 1、xdebug.mode = off)与「同页放多个播放器必须显式区分」,并删掉两份已被代码取代的设计文档。
目前仓库 README.md 覆盖:特性、界面预览、安装步骤、生产环境 PHP 设置、嵌入方式(含同页多实例)、皮肤机制、自定义参数、项目结构、API 端点与技术栈。
九、当前状态与已知边界
已经稳定可用的:服务端部署与升级、密钥与歌单管理、两套皮肤、歌词同步、多实例播放、响应式布局、邮件通知;v1.6.0 起,域名授权与宿主底部避让也进入常规能力;v1.6.2 起,底栏探测覆盖更多真实形态,让出量按 PC 与移动端分别校准;v1.6.5 起,可选的 Redis 播放器数据缓存与封面本地化也进入常规能力;v1.6.9 起,零配置后台路由、密钥级联清理、歌单配置导出导入与配置入库也进入常规能力。
已知边界(写出来是为了避免预期落差):
- 音乐内容依赖第三方接口:歌单、封面、歌词与音频来自第三方音乐平台。本站不存储、不转码、不分发任何音频文件,其可用性由第三方决定——对方调整或限流时,可能无法播放。
- 缓存与封面本地化都是可选项:不接 Redis 时行为与旧版完全一致(每次回源);封面本地化需要先配好对象存储。缓存服务不可用时播放器照常工作,只是不走缓存。
- 皮肤数量有限:目前两套。分层架构已经就位,但新皮肤仍需逐个实现。
- 底部避让不是万能的:宿主页面结构千奇百怪,自动探测按「像不像底栏」判断,遇到特殊布局可以在后台为那个域名直接填固定修正量。
- 移动端的部分交互仍在打磨:抽屉拖拽、弹层动画这类手势交互,在不同机型与浏览器上的手感还需要持续调整。
- 不提供音频下载与转存:这是设计选择,也是版权上的边界。
- 许可:以仓库根目录的
LICENSE为准。
十、相关链接
- 源码仓库
- LICENSE
- 全部版本与下载
- 各版本 Release 页:1.0.0 · 1.1.0 · 1.2.0 · 1.3.0 · 1.4.0 · 1.4.2 · 1.5.0 · 1.6.0 · 1.6.2 · 1.6.5 · 1.6.9
从只能自己用到别人也能一行接入,这个项目最花时间的其实不是写功能,而是把结构收拾干净——让下一套皮肤、下一个版本不用再复制一遍逻辑。v1.5.0 把结构做完了,v1.6.0 开始还债:把「嵌在别人站上」这件事一件件收敛好,v1.6.2 把这笔债又还掉一层。v1.6.5 把视线从「宿主页面」移回自己身上:缓存和存储,都是为了让播放器在被嵌进去之后依然跑得轻快、可控。到了 v1.6.9,债基本还清——路由零配置、删除不剩孤儿、配置只放一处,接下来可以把时间花在新东西上。





