【免费下载链接】myspoti
Native Android client for Spotify, built with Jetpack Compose
Myspoti 是一个用 Jetpack Compose 打造的 Spotify 原生 Android 客户端,它的源码采用经典的"接口契约—平台实现—宿主环境"三层架构::api定义纯 Kotlin 接口,:api-spotify基于官方 Web Player 给出完整实现,:host-cdp则把同一套实现接到桌面 Chrome 上跑真实集成测试。本文带你快速读懂这三个模块各自负责什么、它们又是如何协作的。
🧩 项目总览:一个 Gradle 多模块工程
打开 base/settings.gradle.kts,可以看到工程一共声明了 7 个模块:
| 模块 | 角色 | 依赖关系 |
|---|---|---|
:app | Jetpack Compose UI 层 | →:host-android、:extensions |
:host-android | Android WebView 宿主(BridgeHost的安卓实现) | →:api-spotify |
:host-cdp | 桌面 Chrome(CDP 协议)宿主 + 真机级集成测试 | →:api-spotify |
:api-spotify | 用官方 Web Player 实现:api的全部接口 | →:api |
:api | 纯 Kotlin 接口契约:数据模型 + 接口定义 | 无 Android、无 Spotify 细节 |
:extensions/:extensions-cli | 扩展沙箱运行时与桌面调试器 | — |
这张依赖表就是全文的骨架:箭头全部单向向下,UI 永远不直接碰 Spotify,Spotify 实现永远不知道自己跑在 WebView 还是 Chrome 里。
📐 第一层::api —— 只写契约,不写实现
:api是整个架构的地基。它的入口是一个总接口 MyspotiApi,聚合了 14 个领域:
interface MyspotiApi { val player: Player val search: Search val catalog: Catalog val library: Library val playlists: Playlists val home: Home val podcasts: Podcasts // ... devices、jam、blends、recents 等 }以 Player 为例,它把播放域拆成play / pause / seek / setShuffle / addToQueue / setVolume等一组suspend函数,并用StateFlow暴露当前状态。整个模块只有接口和模型,没有任何 Android API、没有 OkHttp、没有一行 Spotify 细节——这正是它敢叫"契约"的原因:UI 面对的是:api,而不是某个具体实现。
🌐 第二层::api-spotify —— 在 Web Player 之上搭一座桥
SpotifyApi 是MyspotiApi的实现类,它的设计精髓是双通道数据流:
- 读请求走 HTTP 直连:搜索、目录、主页等"只读"操作由
SpotifyHttp直接请求 Spotify 服务器(页面 web token + 本地铸发的 client token),快且可并行; - 写操作与播放走 Bridge:播放控制、收藏写入、Connect、Jam 都通过页面内部的官方服务完成,由注入到 open.spotify.com 的
player-bridge.js负责调用。
两个通道的交汇点是一个极小的接口 BridgeHost——它只要求宿主提供两样东西:向页面执行脚本(evaluate)和接收页面消息(messages)。请求/响应编解码则由 Bridge 完成。
此外,SpotifyApi.raw还保留了一个"逃生舱口":可以直接触达 Web Player 的全部 179 个 GraphQL 操作和 89 个页面服务,方便快速验证尚未类型化的能力(详见 api-spotify/README.md)。
🖥️ 第三层::host-cdp —— 让同一套代码跑在桌面 Chrome 上
这是最有意思的模块。:api-spotify只依赖抽象的BridgeHost,所以任何"能跑 Web Player 页面"的环境都可以当宿主:
- 手机上:WebPlayerHost 用一个 Android WebView 加载 open.spotify.com,实现
BridgeHost,并在shouldInterceptRequest里拦截广告音频; - 桌面上:CdpHost 通过 Chrome DevTools Protocol(CDP)WebSocket 连接启动调试端口的 Chrome,把
Runtime.evaluate映射成evaluate、把Runtime.addBinding映射成messages。
于是开发者可以不用真机就进行全链路测试,LiveApiTest 就是靠它驱动一个真实登录的 Web Player 跑完整 API 回归:
google-chrome --user-data-dir=<profile> --remote-debugging-port=9222 # 登录一次 ./gradlew :host-cdp:test -Pcdp=http://localhost:9222测试覆盖播放、搜索、目录、歌单、Jam 等全部领域,且每个用例都会还原自己修改的状态。
🔗 三模块协作:一条"播放"命令的完整链路
把三层拼起来,你点下播放按钮后发生的事情是:
:app(UI)调用MyspotiApi.player.play(...)——它只认识:api里的接口;:api-spotify的SpotifyPlayer通过Bridge生成一次编号调用,交给BridgeHost.evaluate;:host-cdp或:host-android把脚本送进页面(Chrome 走Runtime.evaluate,手机走 WebView),页面里的player-bridge.js调起官方PlayerAPI开始播放;- 页面把新的播放状态通过
MyspotiHost.postMessage推回宿主,Bridge收到state消息后更新StateFlow,UI 自动刷新。
整条链路里,:api保证 UI 与实现解耦,:api-spotify保证业务逻辑与运行环境解耦,:host-cdp则让"实现 + 真实 Spotify"可以在桌面上被反复验证——三者各司其职,构成了这个开源项目最值得学习的架构范例。
✅ 小结
:api:纯 Kotlin 契约层,UI 唯一依赖(base/api/);:api-spotify:契约的 Spotify 实现,HTTP 直连读 + Bridge 直写(SpotifyApi.kt);:host-cdp:桌面 CDP 宿主,把同一实现接到 Chrome 上跑真机级测试(CdpHost.kt)。
想深入更多细节,建议按顺序阅读 base/api-spotify/README.md 和 docs/api/CAPABILITIES.md,它们分别覆盖了 Bridge 消息协议与全量能力清单。
【免费下载链接】myspoti
Native Android client for Spotify, built with Jetpack Compose
相关推荐
深度解析MemLab架构:六大模块如何协同工作
深度解析MemLab架构:六大模块如何协同工作 MemLab是一个端到端的JavaScript内存分析框架,专门用于检测内存泄漏和优化内存使用。通过六大核心模块
开发工具测试CLIMCP 服务WhisperLiveKit 安装:5 步搞定 iOS 实时语音识别的完整教程
WhisperLiveKit 安装:5 步搞定 iOS 实时语音识别的完整教程 想给你的应用加上实时语音识别能力吗?WhisperLiveKit 是一个面向 i
人工智能语音模型推理服务本地部署MM-Interleaved 模型架构深度剖析:visual_tokenizer、mm_decoder、image_decoder 三大模块如何协同
MM Interleaved 模型架构深度剖析:visual_tokenizer、mm_decoder、image_decoder 三大模块如何协同 MM In
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考