// Architecture
架构概览
Mio Music 是一个 Cargo 工作区:界面、核心规则、列表契约与插件宿主分别放在不同的 crate 里,桌面端与移动端共享同一套核心。
工作区
| 位置 | 职责 |
|---|---|
crates/mio-app | 桌面应用本体。基于 GPUI Kit 的界面、窗口与托盘、快捷键与媒体键、桌面歌词、DLNA、更新器、旧版数据导入等平台相关部分。 |
crates/mio-core | 共享的应用规则。播放引擎与音效、音乐库(SQLite)、下载、备份与恢复、歌词与封面、已安装列表插件的管理。桌面与移动端只负责展示和平台集成,业务规则在这里。 |
crates/mio-music-sdk | 音乐列表契约与路由:MusicListProvider trait、能力声明、歌曲 / 歌单 / 歌手 / 发现等数据类型,以及歌词、评论、封面的解码与校验。 |
crates/mio-plugin | 插件宿主。基于 QuickJS(rquickjs)的受限运行时、列表插件加载与适配、音源插件与洛雪脚本的转换器。 |
crates/mio-tokens | 界面 token(明暗模式配色等),由提取器从源项目生成。 |
crates/mio-mobile-bridge | 面向移动端的 flutter_rust_bridge 接口,复用 mio-core。 |
apps/mobile | Flutter 移动端工程(Android / iOS 共用)。GPUI 只用于桌面端。 |
plugins/ | 子模块 mio-music-plugins,内置列表插件的脚本。 |
tools/ | 打包脚本、源项目静态快照提取器、黄金图对照工具与 Subsonic 测试夹具。 |
docs/parity/ | 与源项目的对照清单与实施进度。 |
website/ | 本官网与文档,纯静态,不参与软件打包。 |
在线内容的数据流
应用本身不带任何在线来源。每一个来源都是某个已启用的列表插件注册进来的 provider:
界面 (mio-app)
└─ mio_music_sdk::search_source(source, keyword, page, limit)
├─ 校验参数(关键词 ≤1024 字节,page ≥ 1,limit 1..=30)
├─ 按来源 ID 找到已注册的 provider,并检查其 search 能力
└─ ListProvider::search (mio-plugin)
└─ 新建 QuickJS 运行时 → 执行脚本 → 调用 listPlugin.search(source, {...})
└─ 脚本通过 cerumusic.request 发起受限的 HTTP 请求
← JSON 返回值反序列化为 SearchResult,宿主覆盖 source、截断到 500 条
几个有意为之的设计:
- 没有回退:来源缺失或能力缺失时直接报错,从不退回到内置适配器。
- 宿主盖章:宿主会把每首歌、每个结果的
source改写成本次调用的来源,插件无法冒充别的来源。 - 无状态运行时:每次调用都新建 JS 运行时与上下文,脚本顶层变量不会在调用之间保留;需要保留的状态通过
cerumusic.storage持久化。 - 有界:单次调用 30 秒、请求次数、响应体大小、内存与脚本体积都有上限,详见 运行时 API。
播放
播放地址有两条来路:来源所属的列表插件自己提供(musicUrl),或交给当前启用的音源插件按来源与音质解析。选择哪条由插件卡片上「由本插件提供播放」开关决定。解析出的地址只允许不含用户信息的 HTTP / HTTPS。随后由 mio-core 里基于 Rodio、cpal 与 Symphonia 的引擎解码和输出,并提供 Gapless、Crossfade、EQ 与 FFT 频谱。
检查
cargo fmt --all -- --check
cargo test --workspace --locked
cargo check --workspace --locked
CI 在 macOS、Windows 与 Linux 上执行同样的三条命令,见 .github/workflows/check.yml。官网与文档位于 website/,不在工作区内,也不属于 Rust 构建。
延伸阅读
- 插件开发者文档:宿主对插件的全部约定。