本文件为 AI 代理(Claude、GPT、Copilot 等)提供完整的项目上下文、架构说明与编码规范,以确保代码修改与项目风格保持一致。
Kanade (奏) 是一款面向 Android 平台的高性能、现代化音乐播放器,专注于极致播放体验、沉浸式 UI 设计与高度可扩展性。
- 组织: Parallel-SEKAI
- 包名:
org.parallel_sekai.kanade - 目标平台: Android 8.0 (API 26) ~ Android 16 (API 36)
- 开源协议: Apache License 2.0
- 统一播放: 本地 MediaStore 音轨 + 通过 KSS 脚本扩展的外部在线音源
- 高级歌词: 标准 LRC、增强型 LRC(逐字)、Apple 风格 TTML,支持平滑过渡与分享
- Kanade 脚本系统 (KSS): 基于 QuickJS 的 JavaScript 插件引擎,允许开发者编写脚本接入第三方音乐平台
- 现代 UI/UX: 纯 Jetpack Compose 构建,动态渐变(Palette API)、沉浸式播放器过渡、延迟解析优化
| 模块 | 技术选型 |
|---|---|
| 编程语言 | Kotlin (JVM 17), JavaScript (ES2015+ via QuickJS) |
| 界面框架 | Jetpack Compose (Material 3, BOM 2025.12.00) |
| 音频引擎 | Android Media3 (ExoPlayer 1.9.0 + MediaSession) |
| 架构模式 | MVI (Model-View-Intent) + Repository Pattern |
| 脚本引擎 | app.cash.quickjs:quickjs-android:0.9.2 |
| 图像加载 | Coil 2.7.0 + Palette KTX 1.0.0 |
| 数据持久化 | Jetpack DataStore Preferences 1.1.3 |
| 网络层 | OkHttp 4.12.0 |
| JSON 序列化 | kotlinx.serialization 1.7.3 |
| 导航 | Navigation Compose 2.9.6 |
| 歌词广播 | Lyric-Getter-Api 6.0.0, SuperLyricApi 2.4 |
| 代码格式 | Spotless 7.0.2 + ktlint 1.5.0 |
# 构建 Debug APK
./gradlew :app:assembleDebug
# 构建 Release APK
./gradlew :app:assembleRelease
# 代码格式化(提交前必须执行)
./gradlew spotlessApply
# 运行 Lint
./gradlew :app:lint
# CI 脚本 (lint + assembleRelease)
./ci.sh- GitHub Actions (
.github/workflows/android_build.yml): JDK 17 →spotlessCheck→assembleDebug→ Upload APK - pre-commit.ci: 自动修复格式问题、每周更新 hooks
- 本地 pre-commit: trailing-whitespace、end-of-file-fixer、check-yaml、check-added-large-files、ktlint --android
| 配置项 | 值 |
|---|---|
compileSdk |
36 |
minSdk |
26 |
targetSdk |
36 |
jvmTarget |
17 |
versionCode |
1 |
versionName |
1.0 |
isMinifyEnabled (release) |
false |
kanade/
├── app/ # 唯一模块
│ ├── build.gradle.kts
│ └── src/main/java/org/parallel_sekai/kanade/
│ ├── MainActivity.kt # 入口、导航定义、手动依赖注入
│ ├── data/
│ │ ├── model/ # 不可变数据类 (MusicModel, LyricData, ArtistModel...)
│ │ ├── parser/ # 歌词解析器 (LRC/TTML)
│ │ ├── repository/ # 领域逻辑 & 状态管理
│ │ │ ├── PlaybackRepository.kt # Media3 控制桥接,播放状态持久化
│ │ │ └── SettingsRepository.kt # DataStore 用户偏好
│ │ ├── script/ # KSS 脚本系统
│ │ │ ├── ScriptEngine.kt # QuickJS 运行时封装
│ │ │ ├── HostBridge.kt # Kotlin→JS 桥接 (HTTP, Crypto, Logging)
│ │ │ ├── ScriptManager.kt # 脚本生命周期管理
│ │ │ ├── ScriptModels.kt # 脚本相关数据模型
│ │ │ └── ScriptMusicSource.kt # 脚本驱动的音源适配器
│ │ ├── source/ # 数据源抽象
│ │ │ ├── IMusicSource.kt # 核心音源接口
│ │ │ ├── SourceManager.kt # 聚合管理器 (单例)
│ │ │ └── local/
│ │ │ └── LocalMusicSource.kt # MediaStore 本地音源
│ │ └── utils/ # 工具类
│ │ ├── CacheManager.kt
│ │ ├── UrlCacheManager.kt
│ │ ├── LyricGetterManager.kt
│ │ ├── LyricSplitter.kt
│ │ └── LyricImageUtils.kt
│ ├── service/
│ │ └── KanadePlaybackService.kt # Media3 前台播放服务
│ └── ui/
│ ├── adaptive/
│ │ └── AdaptiveLayout.kt # 自适应布局 (手机/平板/桌面)
│ ├── preview/ # Compose Preview 辅助
│ ├── screens/
│ │ ├── player/ # 播放器 (Contract/ViewModel/Components/SongInfo)
│ │ ├── library/ # 本地库 + 首页浏览
│ │ ├── search/ # 多源防抖搜索
│ │ ├── artist/ # 艺术家页面
│ │ ├── settings/ # 设置 (KSS/UI/歌词/缓存)
│ │ └── more/ # "更多"页面
│ └── theme/
│ ├── Theme.kt # Material 3 主题 (支持动态颜色)
│ ├── Color.kt
│ ├── Type.kt
│ └── Dimens.kt # 原子化间距变量
├── build.gradle.kts # 根构建文件 (Spotless 配置)
├── settings.gradle.kts
├── gradle/libs.versions.toml # 版本目录
├── ci.sh # CI 脚本
├── .editorconfig # 代码风格规则
├── .pre-commit-config.yaml # Pre-commit hooks 配置
└── .github/workflows/android_build.yml # GitHub Actions
每个页面严格遵循 MVI 契约,定义在同一包下的 *Contract.kt 文件中:
State → 不可变的 UI 快照 (data class)
Intent → 用户操作 (sealed interface)
Effect → 一次性事件如 Toast/Snackbar (sealed interface)
数据流:
[Compose UI] --Intent--> [ViewModel] --更新--> [MutableStateFlow<State>]
↓
[Compose UI 重组]
[ViewModel] --Effect--> [MutableSharedFlow] --> [Snackbar/Toast]
文件命名约定:
XxxContract.kt— 包含XxxState,XxxIntent,XxxEffectXxxViewModel.kt— 处理 Intent,更新 State,发出 EffectXxxScreen.kt— Compose UI 实现
PlaybackRepository— 播放控制的唯一真相源,桥接 ViewModel ↔ MediaControllerSettingsRepository— 用户偏好的 DataStore 管理器
IMusicSource— 统一音源接口LocalMusicSource— 本地 MediaStore 实现ScriptMusicSource— 脚本驱动的远程源实现SourceManager— 单例管理器,聚合本地和脚本音源
无 DI 框架。所有依赖在 MainActivity.onCreate() 中手动构造并传递。
| 类型 | 风格 | 示例 |
|---|---|---|
| 类/接口 | PascalCase | PlayerViewModel, IMusicSource |
| 函数/变量 | camelCase | handleIntent(), currentSong |
| 常量 | SCREAMING_SNAKE_CASE | EXTRA_EXPAND_PLAYER |
| 包名 | 全小写 | org.parallel_sekai.kanade |
| 规则 | 值 |
|---|---|
| 缩进 | 4 空格 |
| 最大行长 | 120 字符 |
| 字符集 | UTF-8 |
| 行尾 | LF |
| 末尾换行 | 必须 |
package-name— 允许非标准包名function-naming— 允许 Composable 函数使用大写开头no-wildcard-imports— 允许通配符导入property-naming— 允许非标准属性名value-parameter-comment— 允许行内注释backing-property-naming— 允许非标准 backing property 名
- 提交前必须运行
./gradlew spotlessApply - 使用
MaterialTheme.colorScheme获取颜色,禁止硬编码色值 - 使用
Dimens对象管理间距,禁止硬编码像素值 - UI 必须无状态,仅响应 StateFlow
- 使用
sealed interface(非sealed class)定义 Intent/Effect
- ViewModel 中使用
MutableStateFlow,对外暴露为StateFlow - UI 层只订阅状态,不直接操作
- Effect 使用
MutableSharedFlow,UI 通过LaunchedEffect收集
- 禁止 UI 代码直接操作
MediaController或ExoPlayer - 所有播放操作必须通过
PlaybackRepository
- 所有宿主桥接调用 (HTTP, Crypto) 必须在
ScriptEngine的专用executor线程执行 - QuickJS 不是线程安全的,严禁跨线程共享 JS 运行时
- UI 使用
PlayerEffect.ShowError/ShowMessage反馈 - Repository 捕获异常并返回安全默认值 (如
emptyList()) - 不要让异常传播到 UI 层导致崩溃
- 使用 Kotlin Coroutines + Flow
- ViewModel 使用
viewModelScope - Repository 使用
suspend函数 - 播放服务使用
ProcessLifecycleOwner.get().lifecycleScope
- 路由定义在
sealed class Screen中 - 参数路由使用
createRoute()方法 - 顶级导航使用
popUpTo(findStartDestination)+saveState+restoreState - 自适应布局: 宽屏 (≥840dp) 使用
NavigationRail,窄屏使用NavigationBar
- 使用
remember缓存计算结果 - 使用
LaunchedEffect处理副作用 - 使用
collectAsState()收集 StateFlow - 预览函数放在
ui/preview/目录
KSS 脚本是 .js 文件,由 Manifest(JSDoc 注释)+ 逻辑(全局函数)组成。
/**
* @kanade_script
* {
* "id": "unique_id",
* "name": "Display Name",
* "version": "1.0.0",
* "author": "Author",
* "description": "Optional",
* "configs": [
* { "key": "api_key", "label": "API Key", "type": "string", "default": "" }
* ]
* }
*/| 函数 | 说明 | 返回值 |
|---|---|---|
init(config) |
初始化,接收用户配置 JS 对象 | void |
getHomeList() |
首页/发现区 | MusicItem[] |
search(query, page) |
搜索 | MusicItem[] |
getMediaUrl(id) |
解析流媒体 URL | StreamInfo |
getLyrics(id) |
(可选) 获取歌词 | LRC/TTML 字符串 |
getMusicListByIds(ids) |
(可选) 批量获取详情 | MusicItem[] |
// MusicItem
{ id: "string", title: "string", artist: "string", album: "string?", cover: "string?", duration: number? }
// StreamInfo
{ url: "string", format: "mp3"|"flac"|"m4a", headers: { "Key": "Value" } }http.get(url, options),http.post(url, body, options)— headers, responseType ("text"/"hex"), contentTypecrypto.md5(text),crypto.aesEncrypt(text, key, mode, padding),crypto.aesDecrypt(hex, key, mode, padding)console.log/info/debug/warn/error(...)→ Android Logcat (Tag:KanadeScript)
- 沙箱: 无
process,require,eval - 超时: 单次函数执行 ≤ 15 秒
- 内存: 每个脚本实例 64MB
- 网络: 默认仅 HTTPS
外部歌曲使用 kanade://resolve?source_id=...&original_id=... URI 包装脚本 ID。KanadePlaybackService 中的 ResolvingDataSource 在歌曲即将播放时才调用 getMediaUrl() 获取真实流地址,避免提前请求。
- 格式检测:
LyricParserFactory自动识别 LRC/TTML - 同步:
PlayerViewModel观察progressFlow,200ms 节流更新当前行 - 广播: 通过
LyricGetterManager推送至系统级歌词 API - 分享:
LyricImageUtils生成歌词卡片,通过FileProvider+Intent.ACTION_SEND分享
歌曲切换时 PlayerViewModel 使用 Palette 提取封面主色调,存入 PlayerState.gradientColors,播放器背景平滑过渡。
CacheManager: 管理音频缓存,支持清理和大小限制 (Settings UI 可配)UrlCacheManager: 永久缓存脚本音源的已解析 URL,避免重复请求- Coil: 内存缓存 25%,磁盘缓存 2%,忽略服务器缓存头
| 工具 | 命令 | 说明 |
|---|---|---|
| Spotless + ktlint | ./gradlew spotlessApply |
格式化 .kt 和 .gradle.kts |
| Spotless Check | ./gradlew spotlessCheck |
CI 中检查格式 |
| Android Lint | ./gradlew :app:lint |
静态分析 |
| Pre-commit | 自动触发 | trailing-whitespace, end-of-file, check-yaml, ktlint |
- 单元测试:
app/src/test/(JUnit 4) - 仪器测试:
app/src/androidTest/(AndroidX JUnit + Espresso + Compose UI Test) - 当前仅有模板测试文件,尚无自定义测试
| 语言 | 资源目录 |
|---|---|
| 英文 (默认) | res/values/ |
| 简体中文 | res/values-zh/ |
| 繁体中文 | res/values-zh-rTW/ |
| 日语 | res/values-ja/ |
| 权限 | 用途 |
|---|---|
FOREGROUND_SERVICE + FOREGROUND_SERVICE_MEDIA_PLAYBACK |
后台播放 |
READ_MEDIA_AUDIO (API 33+) / READ_EXTERNAL_STORAGE (API <33) |
读取本地音频 |
POST_NOTIFICATIONS (API 33+) |
播放通知 |
WAKE_LOCK |
防止播放时休眠 |
INTERNET |
脚本音源网络请求 |
| 文件 | 说明 |
|---|---|
GEMINI.md |
项目完整文档(含 AI 上下文指令) |
KSS_DEVELOPER_GUIDE.md |
KSS 脚本开发指南 (英文) |
SCRIPTS.md |
KSS 脚本系统技术规范 (中文) |
NETEASE.md |
网易云 EAPI 对接文档 |
home.md |
NetEaseCloudMusicApiEnhanced 完整接口文档 |
PLAN.md |
开发计划 |
UI.md |
UI 架构说明 |