感谢你愿意为 NeriPlayer 做出贡献。 本文描述当前 Android 客户端和一起听 Worker 的真实实现, 请以源码和运行行为为准同步维护文档。
- NeriPlayer 是一个原生 Android 音频播放器,不是公共云端曲库服务。
- 在线内容能力主要来自 网易云音乐、Bilibili 与 YouTube Music。
- 播放页元数据/歌词补全链路目前使用 网易云 + QQ 音乐, 并接入 LRCLIB 外部歌词来源。
- 数据默认保存在本地;GitHub / WebDAV 同步是可选能力, 同步对象是歌单、收藏、最近播放、播放统计等元数据,不是媒体文件本身。
- 一起听服务端在
np-submodule/NeriPlayer-LTW,基于 Cloudflare Workers 与 Durable Objects。
维护文档时建议按用途拆开看:
README.md/README_EN.md- 面向用户和新贡献者,说明项目定位、能力边界、安装构建、同步与隐私。
CONTRIBUTING.md/CONTRIBUTING_EN.md- 面向开发者,说明真实模块边界、扩展路径、测试和提交要求。
app/src/main/cpp/README.md- 说明 NeriPlayer 自有 Native 源码的替代授权范围、第三方排除项和 外部贡献所需的显式双授权声明。
app/src/main/cpp/tests/usb/config/host-gate-contract.md- 说明公开 Native USB host 门禁、CI 覆盖与真实设备验证边界。
app/src/main/cpp/tests/usb/corpus/README.md与app/src/main/cpp/tests/usb/fixtures/README.md- 说明公开测试语料/夹具只能使用合成或可审计资料,真实设备证据留在私有目录。
np-submodule/NeriPlayer-LTW/README.md- 面向一起听服务端部署者,说明 Worker API、事件模型、部署和本地检查。
行为变更如果影响用户理解,请同步更新 README; 如果影响扩展方式、测试方式或模块边界,请同步更新 CONTRIBUTING。
- Android Studio:最新稳定版
- JDK:17
- Kotlin:2.4.10,JVM target 17
- AGP:9.2.1
- Gradle:9.4.1
- compileSdk / targetSdk / minSdk:37 / 36 / 28
- NDK:
27.0.12077973 - CMake:
3.28.0+ - Node.js:20,用于一起听 Worker 检查
- 版本名格式:
<git短哈希>.<MMddHHmm> - Release APK 文件名:
NeriPlayer-<versionName>[-abi].apk
补充说明:
- 仓库依赖 Git 子模块,首次克隆请使用
--recursive,或手动执行git submodule update --init --recursive。 - 构建脚本会读取 Git 短提交生成版本名,本地请确保已安装 Git。
- 依赖版本由
gradle/libs.versions.toml与各模块build.gradle.kts管理。 - 应用只保留
zh与en资源,见build-logic的 locale filter。
这个项目功能面比较宽,提交前请优先保护这些链路:
- 播放链路:
PlayerManager、取流策略、缓存、失败刷新、自动换源、 长音频进度记忆、BilibiliSponsorBlock 跳过策略、状态恢复、响度均衡、 声道平衡、高解析输出、USB 独占 Native 链路、启动看门狗和前后台健康审计。 - 下载链路:
AudioDownloadManager、GlobalDownloadManager、DownloadTaskStore、DownloadLifecyclePolicies、ManagedDownloadStorage、 续传检查点、sidecar 文件、任务队列恢复、取消清理和 SAF 目录迁移。 - 同步链路:GitHub / WebDAV 的三路合并、删除记录、播放统计、 缺字段快照清洗、JSON/ProtoBuf/Base64 格式兼容和 WebDAV 并发保护。
- 本地数据:歌单 JSON 原子写入、本地元信息补全、配置导入导出、 授权加密存储和 DataStore 设置。
- 歌词与播放页 UI:
AdvancedLyricsView、SyncedLyricsView、LyricShareSheet、歌词音译显示、歌词长按分享和 Lyrics 全屏页。 - 导航与玻璃 UI:
MainTabLayerHost、详情页抽屉/连贯反馈、 可打断主标签切换、页面状态保留和 Advanced Glass owner 接力。 - 存储与缓存 UI:
StorageUsageAnalyzer、缓存清理选项、下载目录索引和 SAF 快照。 - 一起听:Android 客户端、Worker 协议字段、角色权限、队列、 版本门控更新、直链共享开关和房主离线恢复。
- 诊断恢复:安全模式、JVM/Native 崩溃日志、ANR 记录和 Debug 探针。
- 本地持久化:播放/流量统计的批量写入、生命周期 flush、原子文件替换和 SAF/本地歌单初始化就绪状态。
对应测试已分布在 app/src/test/ 与 app/src/androidTest/。
修改上述链路时,优先搜索同名目录或相邻测试类,再补新的覆盖。
- 克隆仓库:
git clone --recursive https://github.com/cwuom/NeriPlayer.git cd NeriPlayer - 构建调试版:
./gradlew :app:assembleDebug
- 安装到设备:
adb install -r app/build/outputs/apk/debug/app-debug.apk
- 首次启动会进入免责声明与启动引导;Android 13+ 会请求通知权限。
- 如需调试入口,在设置页连续点击版本号 7 次,底栏会出现独立
Debug页。
发布版默认启用混淆与资源收缩。
普通 assembleRelease 默认只构建 arm64-v8a,手动多 ABI 构建需要额外参数。
-
在
~/.gradle/gradle.properties、项目 Gradle properties 或命令行-P中提供签名信息:KEYSTORE_FILE=/absolute/path/to/neri.jks KEYSTORE_PASSWORD=your_store_password KEY_ALIAS=key0 KEY_PASSWORD=your_key_password
如果
KEYSTORE_FILE使用相对路径,会按app/模块目录解析。 当前 Release 构建不会回退到 debug signing config; Android Studio / IntelliJ 本地构建会自动允许未签名 Release 通过打包校验, 便于 IDE 里的Build APK/assemble使用。命令行和 CI 仍然默认要求 可用 keystore;GitHub PR 会自动构建未签名 Release 做打包校验,其他 CI/PR 环境可以显式传入-PallowUnsignedRelease=true。 -
构建默认 Release:
./gradlew :app:assembleRelease
-
构建多 ABI Release:
./gradlew :app:assembleRelease -PbuildAllReleaseAbis=true
-
产物位于
app/build/outputs/apk/release/,文件名格式为:NeriPlayer-<git短哈希>.<MMddHHmm>[-abi].apk
安全提醒:
- 不要提交 keystore、密码、Cookie、Token 或其他敏感信息。
- 不要在 Issue / PR 中粘贴完整授权信息。
- 完整配置导出文件会包含平台授权和同步凭据,不能作为公开测试附件。
:app- Android 主应用。
:ksp-annotations/:ksp-processor- 设置项 schema、key、备份白名单和设置 UI 元数据的 KSP 生成链路。
:accompanist-lyrics-core/:accompanist-lyrics-ui- 歌词解析和 Compose 歌词 UI 子模块。
build-logic- Android/Kotlin/Compose convention plugins。
buildSrc- 保留的辅助 Gradle 构建逻辑模块。
np-submodule/NeriPlayer-LTW- 一起听 Cloudflare Workers 服务端。
np-submodule/miuix- 仓库内附带的上游 Miuix 源码/文档树,当前不参与主应用模块构建。
-
app/src/main/java/moe/ouom/neriplayer/NeriPlayerApplication.kt- 应用初始化入口,负责语言、异常处理、
AppContainer、 Lyricon、全局下载管理和共享图片加载器。
- 应用初始化入口,负责语言、异常处理、
-
app/src/main/java/moe/ouom/neriplayer/activity/MainActivity.kt是唯一对外入口,负责安全模式、启动流程、免责声明、 启动引导、外部音频导入、一起听深链和顶层 Compose 宿主。- 平台登录 Activity 位于
activity/auth/,并在独立次进程中运行;activity/sync/保存 Activity 侧的同步告警状态。 NeteaseWebLoginActivity.kt、NeteaseQrLoginActivity.kt、BiliWebLoginActivity.kt、BiliQrLoginActivity.kt与YouTubeWebLoginActivity.kt是内部平台登录页。
-
app/src/main/java/moe/ouom/neriplayer/ui/NeriApp.kt- 顶层 Compose 应用骨架,负责
NavHost、动态底栏、MiniPlayer、Now Playing覆盖层、Debug 路由、主题、缓存清理和播放服务同步。 - 主标签页面由
MainTabLayerHost.kt保留出场/入场双场景, 按标签顺序执行可打断横向转场,并为各场景保留 saveable state 和玻璃 owner。
- 顶层 Compose 应用骨架,负责
-
app/src/main/java/moe/ouom/neriplayer/ui/component/lyrics/AdvancedLyricsView.kt与SyncedLyricsView.kt负责高级歌词排版、 逐字/逐词高亮、翻译/音译显示、点击跳转和长按回调。LyricShareSheet.kt负责歌词行选择、复制、歌曲分享和歌词卡片生成。- 旧
AppleMusicLyric名称只存在于ui/component/LyricsCompatibility.kt的@Deprecated包装中,新代码统一使用SyncedLyricsView。
-
app/src/main/java/moe/ouom/neriplayer/ui/component/playback/NeriMiniPlayer.kt负责底部迷你播放器、播放暂停和横向滑动切歌; 播放音效与睡眠定时器面板也在该目录。ui/component/根目录中的同名文件主要是旧包兼容入口,新增实现应放入lyrics/、playback/、download/、navigation/等职责子包。
-
app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/LibraryScreen.kt负责媒体库顶层分类,本地内容可在歌单/歌手之间切换, 收藏页可展示歌单和已关注艺术家。LocalArtistLibraryGrid.kt展示本地艺术家网格、空状态和艺术家卡片。
-
app/src/main/java/moe/ouom/neriplayer/ui/screen/playlist/LocalArtistDetailScreen.kt展示本地艺术家详情,支持播放全部、 多选、导出歌单和批量下载可在线解析的歌曲。
-
app/src/main/java/moe/ouom/neriplayer/ui/screen/artist/- 网易云艺术家详情页,展示艺术家信息、热门歌曲、分页专辑和关注状态。
-
app/src/main/java/moe/ouom/neriplayer/ui/viewmodel/artist/- 网易云艺术家摘要、JSON 解析和详情页状态管理。
-
app/src/main/java/moe/ouom/neriplayer/ui/onboarding/- 首次启动引导,覆盖语言、平台账号、GitHub 同步和个性化设置。
-
app/src/main/java/moe/ouom/neriplayer/core/api/netease/:网易云接口、加密和账号能力。bili/:Bilibili 搜索、二维码登录、收藏夹、合集、播放信息和音频拉流。youtube/:YouTube Music 客户端(NewPipe Extractor)、 首页/歌单/搜索/播放、PoToken 和 JS Challenge 支持。search/:播放页元数据/歌词补全接口, 当前实现为CloudMusicSearchApi与QQMusicSearchApi。lyrics/:外部歌词来源,当前实现为LrcLibClient。
-
app/src/main/java/moe/ouom/neriplayer/core/player/PlayerManager.kt:Media3 ExoPlayer 的统一管理层, 负责音源解析、播放队列、缓存、状态恢复、失败重试和播放策略。service/AudioPlayerService.kt:前台播放服务、媒体通知、MediaSession 和媒体按钮。download/AudioDownloadManager.kt:下载核心链路;同目录的DownloadParallelism.kt定义并发边界。effects/PlaybackEffectsController.kt:倍速、音调、响度增强和均衡器。engine/:Media3 音频处理器,包括响度均衡、声道平衡和高解析输出相关处理。playback/PlaybackStatsTracker.kt:播放统计采集;播放命令与队列推进也在playback/PlayerManagerPlaybackExtensions.kt。timer/SleepTimerManager.kt:睡眠定时器。engine/datasource/ConditionalHttpDataSourceFactory.kt:为特定域名动态附加 Header。watchdog/PlayerManagerStartupWatchdogExtensions.kt、lifecycle/PlayerManagerLifecycleExtensions.kt: 播放启动看门狗、前后台健康审计、失败恢复和 USB 独占异常回退。resolver/netease/PlayerManagerNeteaseAutoSourceSwitch.kt:网易云无权限、 无直链或试听片段时的 Bilibili 自动换源兜底。resolver/youtube/YouTubeGoogleVideoRangeSupport.kt、YouTubeSeekRefreshPolicy.kt、prefetch/YouTubePrefetchRunner.kt:YouTube Music 播放兼容策略。metadata/:歌词、元数据、外部蓝牙歌词等播放页数据处理。model/:播放器专用状态模型;跨数据层共享的歌曲模型不在此处。usb/:按device/、path/、session/、sink/、system/与transport/拆分 USB 独占会话、Native 桥、运行态快照和恢复控制, 当前实现覆盖 UAC1.0 和兼容 UAC2.0 Type I PCM 设备, 并已经包含 32-bit PCM、PCM float 软件转换、UAC2 显式反馈、 协调式 AudioSink 重配置、动态传输缩放和背压卡顿恢复。
-
app/src/main/java/moe/ouom/neriplayer/core/download/GlobalDownloadManager.kt维护全局下载任务与本地已下载列表。ManagedDownloadStorage.kt是应用目录/SAF 目录的外观入口;具体实现已拆到storage/commit/、delete/、lookup/、migration/、recovery/、snapshot/、tree/与working/等子包。task/DownloadTaskStore.kt持久化下载任务、状态、进度和 attemptId。policy/DownloadLifecyclePolicies.kt集中封装下载恢复、取消清理和快速结算策略。naming/ManagedDownloadNaming.kt管理下载文件名模板和历史命名兼容。metadata/DownloadedAudioTagWriter.kt写入音频标签;catalog/管理已下载歌曲目录模型。
-
app/src/main/java/moe/ouom/neriplayer/core/startup/- 启动阶段与决策已按
app/、crash/、download/、logging/、permission/、player/、safemode/、sync/和theme/拆分;MainActivity只负责协调这些组件与 UI 生命周期。
- 启动阶段与决策已按
-
app/src/main/java/moe/ouom/neriplayer/data/model/:跨播放器、歌单、下载、同步、一起听和 UI 共享的SongItem、SongIdentity与媒体模型扩展。settings/:DataStore设置、KSP schema、启动快照、主题快照和播放偏好快照。auth/:网易云、Bilibili、YouTube 的 Cookie / Auth 本地存储与校验。platform/netease/:网易云平台侧缓存,当前包含歌单详情本地缓存。storage/:存储占用分析、缓存分组和额外缓存清理。local/playlist/:本地歌单 JSON 原子写入、系统歌单兼容、 后台元信息补全和本地艺术家聚合模型。local/audioimport/、local/media/:本地音频导入、快速扫描、 后台元信息补全、封面回退和分享。playlist/favorite/、playlist/usage/:收藏歌单、收藏艺术家和首页继续播放数据。history/、stats/:最近播放、播放统计和日/周/月/年/总计周期聚合。backup/:本地歌单 JSON 备份、导入与差异分析。config/:完整配置导入/导出。sync/model/:GitHub 与 WebDAV 共用的同步载荷和冲突模型。sync/:provider 无关的协调、偏好和封面映射。sync/github/:GitHub 传输、三路合并、序列化、省流模式和安全存储。sync/webdav/:WebDAV 同步、远端配置、Worker 和 WebDAV API。
-
app/src/main/java/moe/ouom/neriplayer/listentogether/protocol/定义房间、事件与传输模型,network/负责 HTTP/WebSocket 与重连,playback/负责队列、权威直链和进度同步,control/、session/、invite/、mapping/、validation/分别承载控制、会话策略、邀请、模型映射和输入边界。- 根目录保留
ListenTogetherSessionManager.kt与少量兼容入口;新增协议逻辑 不应继续堆入根包。
-
app/src/main/cpp/- Native 崩溃处理位于
crash/;USB 实现按usb/exclusive/、usb/feedback/、usb/iso/、usb/pcm/、usb/uac1/、usb/uac2/拆分,对应 host 测试位于tests/usb/。
- Native 崩溃处理位于
-
app/src/main/java/moe/ouom/neriplayer/core/lyricon/- 词幕适配(Lyricon Provider)与 SuperLyric 输出, 同步歌曲、播放状态、进度、逐字歌词和翻译。
Explore是网易精选歌单 + YouTube Music 歌单 + 网易/Bilibili/YouTube Music 按平台独立搜索,不是混合聚合搜索。Home在中文默认模式下主要展示本地继续播放与网易推荐; 国际化模式下优先展示 YouTube Music 首页货架。Library中 QQ 音乐入口仍为占位,不代表完整平台接入。- 本地艺术家分类来自本地已导入/已保存歌曲的展示艺术家聚合, 不是在线艺术家资料库。
- 网易云艺术家详情依赖网易云 artist 元数据和接口; 关注状态会保存到本地收藏分类。
Bilibili已支持搜索、收藏夹和音频播放/下载,但不是完整视频发现流或评论区。YouTube Music已支持登录、匿名播放、首页/歌单浏览、详情、搜索、播放与下载; 有效身份 Cookie 会保留并支持轮换,取流会复用 bootstrap/player.js/PoToken 与挑战 结果缓存,签名或直链失败时可回退 EJS/HLS, 不能把缓存命中或本地单测当作真实账号/网络稳定性证明;长音频大幅 Seek 会走 更短的启动恢复窗口,仍需验证真实网络下的体验。- 状态栏歌词依赖厂商私有能力,当前仅适用于部分支持设备。
RuntimeShader流体/音频响应背景只在 Android 13+ 启用;封面模糊与高级模糊 只在 Android 12+ 启用,修改动效时必须保留低版本降级路径。- 歌词音译显示依赖平台返回的音译歌词或内嵌逐字歌词的 phonetic 字段; 当前没有音译数据时,不应强行合成或展示空的第二行。
- 歌词分享会通过
FileProvider分享缓存目录中的歌词卡片文件; 这类分享产物属于可清理缓存,不是用户下载内容。 - Lyricon/SuperLyric 的位置 feed 独立按 200 ms 推送并使用 elapsed realtime 锚点; 修改播放器进度刷新间隔时,必须同时检查前后台歌词时序和 SuperLyric 对齐。
- 网易云播放会在当前音质不可用时自动尝试更低音质; 无权限、无直链或仅返回试听片段时,可按设置自动匹配 Bilibili 或本地音频兜底。
- 网易云歌单详情缓存只服务歌单详情页快速展示和失败回退; 专辑详情仍保持实时刷新,避免和歌单缓存混用。
- 本地「我喜欢的音乐」支持将可识别的网易云歌曲同步到网易云我喜欢的音乐; 该能力依赖网易云登录态,并会跳过不支持或已存在的歌曲。
- 下载使用共享
OkHttpClient写入应用目录或 SAF 目录, 不是系统DownloadManager;当前已支持自动断点续传与启动恢复。 - 下载任务队列、取消记录和 attemptId 都参与恢复判断; 修改恢复流程时要避免旧请求把新请求的任务状态清掉。
- 续传按传输类型分别处理:
- 直链下载通过工作文件大小 +
Range头续传 googlevideo显式分块下载按字节偏移续传- HLS 下载通过
.hls.json检查点按 segment 恢复
- 直链下载通过工作文件大小 +
- 工作文件位于
cache/download_staging/,并额外保存.resume.json恢复元数据;应用启动和网络恢复后会尝试自动找回未完成下载。 - 手动取消会回滚半成品并删除工作文件;只有网络策略暂停与可恢复错误重试 才会保留断点。
- 音频本体完整后,标签或 SAF 可写句柄失败也按无标签完成并保留已落盘音频, 不应为标签重试而删除音频。
- 应用私有下载目录通常比 SAF 自定义目录更快; SAF 快照和索引用于减少遍历;空目录扫描不能覆盖已有索引,且不能假设 SAF 操作成本 和普通文件系统一致。
- 分享受控目录中的本地音频时优先直接暴露可读 URI;无法直接分享的 content URI 才复制到缓存 staging,分享暂存属于可清理缓存。
- 存储清理只能删除可再生成缓存、下载暂存和分享暂存; 不要通过“清缓存”删除用户主动下载的音频、歌词和封面。
- 流媒体缓存与下载是两套能力:
缓存使用
SimpleCache,下载由AudioDownloadManager与ManagedDownloadStorage写入本地文件。 - GitHub / WebDAV 同步只同步元数据,不同步音频缓存、下载文件、 本地音频文件、Cookie 或播放 Token。
- 本地歌单与同步歌单通过
songOrderVersion区分顺序语义:0是旧版顺序,1是当前展示顺序;读取旧数据时要兼容迁移,不能直接按新版顺序解释。 - 同步快照可能来自旧版 JSON/ProtoBuf 或异常远端文件;读取时要用安全默认值,
过滤缺少可解析歌曲身份、无有效删除时间或无效歌单 ID 的记录,
缺失
addedAt的歌曲不能排到已有时间歌曲之前。 - 省流模式写入
backup-raw.bin原始GZIP(ProtoBuf)字节;读取必须兼容backup.json与历史backup.binBase64,不能在 Android 与 Desktop 尚未同时 支持 read-both 前单独切换格式。JSON、压缩正文和解压后正文上限分别为 8 MiB、 12 MiB 和 16 MiB。 - GitHub 同步通过 Git Data API 的 blob/tree/commit 写入仓库,分支 ref 使用非强制更新; blob 请求中的 Base64 只是 API 传输封装。读取优先使用 raw 内容,不能再描述为 已废弃的资产清单协议。
- WebDAV 优先使用 ETag/Last-Modified 条件写入;没有条件令牌时只能在远端 SHA-256 指纹与已读快照一致的情况下回退无条件写入,否则必须按并发冲突失败。
- 播放统计与流量统计采用延迟批量写入;播放统计在播放器/Activity 关键生命周期 flush,流量累积器在请求或下载尝试结束时 flush,播放每日桶按保留窗口和数量上限裁剪。
- 平台 Cookie / 鉴权信息、GitHub Token、WebDAV 密码使用
Android Keystore + EncryptedSharedPreferences加密保存。 DataStore只承担常规设置与非敏感状态,不承载平台登录凭据。- 长音频进度记忆只针对不少于 15 分钟的内容;小于 5 秒的位置不保存,
距离结尾 30 秒以内清零,显式播放位置优先,持久化字段为
resumePositionMs。 这不是第三方平台播放历史。 - BilibiliSponsorBlock 默认关闭,只发送当前 BV 号的 SHA-256 前缀并在本地执行跳转; 一起听期间保持关闭,不能把公开接口结果写入同步或房间状态。
- 32-bit 高解析系统输出会优先保留普通系统输出的高精度管线,并旁路响度均衡、 声道平衡、音频可视化和应用内倍速等处理;改动时必须同时确认设置文案和测试。
- USB 独占依赖兼容 UAC1.0 或 UAC2.0 Type I PCM 的 DAC、 前台服务、唤醒锁和系统后台策略; 设置页的后台权限提示不是装饰,改动相关逻辑时要同时考虑息屏场景。
- USB 设置还包含比特完美音量模式;启用后软件增益保持 0 dB,音量由 DAC 硬件控制, 不能把它与普通系统音量或应用内响度处理混为一谈。
- 前后台 USB runtime report 若返回
native_refresh_deferred,播放器只在有限次数内 延迟重试;其他无效报告仍按 fail-closed 处理。 - 本地扫描结果可能先用快速元数据返回,再由后台任务补全歌曲名、歌手、 专辑和封面;不要假设首次扫描结果已经是最终形态。
- 一起听在
shareAudioLinks=false时,房间快照和队列不应暴露streamUrl; 关闭该开关时还要立即清空已缓存直链,REQUEST_LINK也应直接拒绝。 - 一起听循环/随机模式通过
PLAYBACK_MODE/REQUEST_PLAYBACK_MODE同步; 成员控制必须校验目标 stable track key,过滤clientInstanceId、clientSequence、clientTimeMs之前的事件,且REQUEST_SET_TRACK只能选当前队列曲目。 - 一起听当前曲目最多保留 3 条去重 HTTP(S) 候选;听众先按本机音质策略解析, 候选只作为本次会话的失败回退,不能写入普通歌曲或离线缓存。服务端位置按曲目 时长推算,单曲循环按时长取模;同一成员凭成员密钥重连不应触发新成员自动暂停。
适用于把新平台接到 Explore 页搜索或发现流。
- 在
core/api/下实现对应客户端或仓库。 - 在
ExploreViewModel中增加请求、分页和状态映射。 - 在
ExploreScreen/ Host 页面中补充平台标签和结果 UI。 - 如需播放,继续接入
PlayerManager的音源解析链路。 - 如需下载,补齐
AudioDownloadManager和下载元数据映射。
适用于补封面、歌词、曲目信息,而不是扩展 Explore 页。
- 在
core/api/search/下实现新的SearchApi。 - 在
AppContainer中注册单例。 - 在
SearchManager中增加路由、匹配和降级逻辑。 - 视需要补充
MusicPlatform、字符串资源和调试探针。
- 参考
bili/或youtube/设计客户端与播放仓库。 - 如需特殊 Header,扩展
core/player/engine/datasource/ConditionalHttpDataSourceFactory.kt。 - 在
core/player/url/与对应resolver/的 URL 解析链路接入平台。 - 下载、歌词、封面和播放统计要保持边界清晰, 不要把缓存和永久下载混成一套实现。
- 如需支持同步到网易云我喜欢的音乐,必须提供稳定的网易云歌曲 ID
或可验证映射,并复用
LocalPlaylistRepository的候选校验逻辑。
- 入口在
core/player/url/PlayerManagerUrlExtensions.kt的网易云 URL 解析流程。 - 匹配与打分逻辑在
core/player/resolver/netease/PlayerManagerNeteaseAutoSourceSwitch.kt。 - 自动换源只处理网易云无权限、无可用直链或试听片段兜底; 不要把它扩展成跨平台聚合搜索。
- 调整匹配策略时要同时考虑歌名、歌手、分 P、时长误差和缓存 key 稳定性。
- 优先在
data/settings/AutoSettingsSchema.kt登记 key、默认值、类型和展示元数据。 - 简单开关可用 KSP 生成的
AutoSettingsRepository和AutoSettingsSwitchItems。 - 有副作用、互斥逻辑、权限或启动快照需求的设置,应保留手写 setter。
- 如果设置影响启动早期行为,还要同步更新对应 snapshot:
BootstrapSettingsSnapshot、ThemePreferenceSnapshot或PlaybackPreferenceSnapshot。 - UI 入口通常放在
SettingsScreen.kt对应SettingsPage或ui/screen/tab/settings/component/下。 - 探索页搜索历史由
ExploreSearchHistoryRepository保存;explore_search_history_enabled关闭时,探索页必须隐藏历史并停止新增记录, 不要在开关切换时静默删除已有记录。 - 歌词字号现在分成封面页和歌词页两组,且歌词和翻译各自独立;
修改相关 UI 时同时更新
SettingsRepository.lyricFontScalesFlow、setLyricFontScale(target, scale)和对应的预览/播放调用点。
- 先阅读
core/player/usb/sink/UsbExclusiveAudioSink.kt、core/player/usb/transport/、core/player/usb/session/、core/player/policy/usb/UsbAudioSinkReconfigurationCoordinator.kt、core/player/watchdog/PlayerManagerStartupWatchdogExtensions.kt、core/player/lifecycle/PlayerManagerLifecycleExtensions.kt和相关测试。 - 当前 USB 独占实现覆盖 UAC1.0 和兼容 UAC2.0 Type I PCM 设备; 如要扩到更复杂的 UAC2.0 拓扑或非 Type I PCM 设备,需要把文档、能力边界、 诊断和兼容性假设一起更新。
- 同时考虑设备选择、采样率/位深策略、32-bit PCM、PCM float 软件转换、 比特完美音量、UAC2 时钟拓扑、显式反馈端点、前后台缓冲区、唤醒锁、 后台权限提示和系统回退链路;隐式反馈目前仍不是可用候选。
- 修改自动恢复、keep-alive 或后台审计时,要验证前台播放、息屏后台、 USB 拔插和 system fallback 四条路径。
- 改动反馈时钟、长调度间隙重捕获、协调式重配置、动态传输缩放、
背压恢复或候选位深回退时,要同步检查
UsbExclusiveOutputFormatResolverTest、UsbExclusivePcmWritePlannerTest、UsbExclusiveSessionControllerReusePolicyTest、UsbAudioSinkReconfigurationCoordinatorTest和 native USB feedback/PCM/UAC 测试。 - Runtime Report v2 字段必须保持 fail-closed 解析;修改反馈端点、状态、 holdover、恢复 action 或代次字段时,要同步更新 Kotlin parser 和边界测试。
- 错误语义或恢复策略变化时,要同步更新设置页 / Debug 页诊断展示和对应测试。
- Native 变更至少运行三组 host gate 和四 ABI Android 编译; host 模型、ABI 编译和真实 DAC 验证是三个不同的通过条件。
- 先理解
data/sync/model/SyncDataModels.kt与data/sync/github/SyncDataSerializer.kt的兼容策略;共享载荷模型不得 重新放回 GitHub provider 包。 - 同步对象包含歌单、收藏歌单、最近播放、删除记录和播放统计。
省流写侧使用
backup-raw.bin原始GZIP(ProtoBuf),普通模式使用backup.json;读侧还必须兼容历史backup.binBase64。GitHub Git Data API 的 blob 请求会使用 Base64 传输封装,但仓库内保存的是原始正文。 songOrderVersion=0表示旧版顺序,songOrderVersion=1表示当前展示顺序; 序列化、合并和落回本地歌单时必须保留旧数据迁移。- 歌单成员使用
syncMembershipTokens/removedMembershipTokens表达 observed-remove 语义;新增字段必须兼容旧 JSON 与 ProtoBuf 的缺字段载荷, 带 token 的成员不能退回只比较addedAt/deletedAt的删除裁决。 - 缺字段或畸形快照必须先清洗再合并;
SyncSong至少要有 id、audioId 或 mediaUri 之一,删除记录还需要有效删除时间,缺失addedAt的歌曲只能作为低优先级展示项。 CoverUrlMapper.kt位于 provider 无关的data/sync/; 合并策略主要在GitHubSyncManager.kt,WebDAV 复用同一套数据模型和多数合并逻辑。- 不要破坏
GitHubSyncWorker.kt/WebDavSyncWorker.kt的延迟同步、 周期同步、validated network 检查和失败重试行为。GitHub 写入按远端分支头做 非强制更新,冲突时必须失败而不是覆盖;WebDAV 无 ETag/Last-Modified 时仍需 重新验证远端 SHA-256 指纹。 - 涉及敏感信息时统一走
SecureTokenStorage.kt或WebDavStorage.kt, 不要放回DataStore或明文 JSON。
- 先阅读
ManagedDownloadStorage.kt、naming/ManagedDownloadNaming.kt、task/DownloadTaskStore.kt、policy/DownloadLifecyclePolicies.kt和相关单元测试。 - 同时考虑默认应用目录、SAF 自定义目录、迁移、历史命名、元数据文件和
.nomedia。 - 下载任务先写入
cache/download_staging/,再提交到正式目录;.resume.json与.hls.json是续传恢复的一部分,不能当普通临时文件随意清理。 - 默认下载并发是 6,设置允许调整到 1-8;
修改并发、重试或网络恢复时,请同步检查
DownloadParallelism.kt、AudioDownloadManager.kt和GlobalDownloadManager.kt。 - 修改目录迁移、删除语义、续传检查点或 sidecar 写入时, 必须补充/更新对应单元测试。
- 播放页歌词主要在
ui/component/lyrics/AdvancedLyricsView.kt、ui/component/lyrics/SyncedLyricsView.kt和NowPlayingScreen.kt。 - 全屏歌词页在
LyricsScreen.kt,歌词分享入口复用LyricShareSheet.kt。 - 音译显示通过
lyric_translation_use_phonetic设置控制, 需要先开启翻译且当前歌词存在音译数据。 - 长按歌词用于打开分享面板;修改手势时要同时检查点击跳转、 手动歌词偏移和高级歌词视口滚动。
- 歌词卡片通过
FileProvider分享缓存文件; 修改输出位置时要同步检查file_paths.xml和缓存清理。
- 入口在
data/storage/StorageUsageAnalyzer.kt和SettingsStorageCacheSection.kt。 - 新增缓存目录时,要决定它属于可清理缓存、下载内容、诊断文件还是应用数据。
- 清理操作只能覆盖可再生成内容; 下载歌曲、下载歌词、下载索引和授权数据不能被普通清缓存误删。
- 如果清理下载暂存,要尊重当前下载任务状态; 活跃任务的暂存文件应等任务结束后再处理。
- 缓存入口是
NeteasePlaylistCacheRepository.kt, 页面状态在NeteaseCollectionDetailViewModel.kt。 - 缓存签名基于曲目数量和最近曲目 ID,主要用于判断是否复用曲目列表。
- 网络失败或解析失败可以回退缓存,但手动刷新应保留强制刷新语义。
- 专辑详情不使用这套歌单缓存,避免不同数据模型互相污染。
- 词幕适配入口在
core/lyricon/LyriconManager.kt。 - 开关状态由设置项
lyricon_enabled控制,并由播放器生命周期同步。 - 歌词数据使用
LyricEntry,逐字信息来自WordTiming; 翻译行按时间容差匹配到原文行。 - Lyricon/SuperLyric 位置 feed 独立按 200 ms 推送,使用 elapsed realtime 做死区重算;修改进度节奏时,必须保持前后台时序一致。
- 修改时要保持 Lyricon、SuperLyric、状态栏歌词、播放页高级歌词 和外部蓝牙歌词的歌词结构兼容。
- 蓝牙歌词的原文与翻译是独立开关;同时输出时必须通过同一个原子快照更新 标题/艺术家字段,并保留曲目信息、字段长度限制、空白清洗和重复提交去重测试。
- Android 客户端逻辑在
listentogether/。 - 服务端逻辑在
np-submodule/NeriPlayer-LTW。 - 协议字段变更必须同时兼容客户端和 Worker,并更新测试。
shareAudioLinks=false时,HTTP/WS 房间快照都不能暴露track.streamUrl与queue[*].streamUrl;关闭该开关时要立即清空 房间里已缓存的直链。REQUEST_LINK/LINK_READY、成员控制、房主离线恢复和版本门控更新 要一起看,避免旧状态覆盖新状态。- 循环/随机模式使用
PLAYBACK_MODE/REQUEST_PLAYBACK_MODE;成员请求与LINK_READY都要校验目标 stable track key,避免异步结果落到错误歌曲。 - 首次加入需要
joinSecret,已有成员重连需要memberSecret;这些密钥不得写入 脱敏房间状态或日志,邀请 URI 的secret参数也要按敏感输入处理。 - 控制事件可携带
clientInstanceId、clientSequence和clientTimeMs;Worker 会拒绝过期顺序,REQUEST_SET_TRACK只能选择当前队列已有曲目,客户端兼容路径 也必须保持一致。 - 成员显式离开时调用
/api/rooms/:roomId/leave,Worker 删除成员并广播MEMBER_LEFT;普通 WebSocket 断线属于传输波动,必须保留成员凭据以支持重连; 控制者显式离开则关闭房间。 - 房间号 6 位、昵称 1-24、队列上限 2000 和请求去重要视为协议边界, 不要只改 UI 校验而忘记服务端约束。
- 设置页支持自定义服务端地址和可用性测试,不要硬编码单一地址。
- 主标签生产路径由
NeriApp.kt与MainTabLayerHost.kt共同承载; 标签顺序与方向统一通过resolveMainTabTransitionDirection决定。 - 转场期间必须同时保留出场和入场 scene,并通过
SaveableStateHolder保留各标签状态;快速反向或连续点击不能先清空旧 scene。 - 每个 scene 都有独立
MainTabGlassOwner。修改 Advanced Glass 时, 要保证只有当前可见 owner 参与合成,详情页 handoff 仍由导航层接管。 coherent_feedback_enabled默认关闭:详情页使用抽屉式前景上升和背景轻微下沉; 开启后才使用背景与详情页同步接力的连贯反馈。- 修改动效时要覆盖正向、反向、中断、重复请求、状态恢复和启动首帧;
几何测试不得把尚未布局的
Rect(0, 0, 0, 0)scene 当成真实重叠。 - 至少同步检查
NeriAppMainTabTransitionPolicyTest、AdvancedGlassNavigationTransitionTest、NeriAppNavigationTransitionTest和HostNavigationTransitionGeometryTest。
- 开发者模式开启方式:设置页连续点击版本号 7 次。
- 开启后底栏会出现独立
Debug页。 - 普通文件日志仅在开发者模式开启时启用。
- 崩溃日志由
ExceptionHandler/NativeCrashHandler独立落盘,不依赖开发者模式。 - Debug 页包含 YouTube、Bili、Netease、Search、Listen Together 探针, 以及普通日志和崩溃日志查看器。
常用命令:
adb logcat | findstr NeriPlayerLinux / macOS 可改用:
adb logcat | grep NeriPlayer提交前建议至少完成以下检查:
- 能成功构建调试版:
./gradlew :app:assembleDebug
- 单元测试:
./gradlew :app:testDebugUnitTest
- 如修改登录态、取流链路或回归风险较高的集成行为,可按需执行 smoke test:
./gradlew :app:testDebugUnitTest -DrunNeteaseSmoke=true ./gradlew :app:testDebugUnitTest \ -DrunYouTubePlaybackSmoke=true \ -DyoutubeSmokeVideoId=<id> \ [-DyoutubeSmokeForceRefresh=true] \ [-DyoutubeSmokeCookieFile=/absolute/path/to/cookies.json]
- 如修改资源、UI、导航、设置、同步或存储逻辑,建议执行:
./gradlew :app:lintDebug
- 如涉及 Compose UI、权限、Activity 或登录流程,建议在设备/模拟器上执行:
./gradlew :app:connectedDebugAndroidTest
- 如修改 Native USB,实现需对齐独立 Android Native CI:
host gate 会固定执行同一组 CTest;Android 编译还要确认
for profile in release-werror-asserts asan-ubsan tsan; do tools_pub/usb-async-lab host-test \ --manifest app/src/main/cpp/tests/usb/config/run-manifest.example.yaml \ --profile "$profile" done ./gradlew :app:externalNativeBuildDebug \ --no-daemon \ --warning-mode all \ --stacktrace
arm64-v8a、armeabi-v7a、x86、x86_64均生成非空lib_neri.so。 - 如修改一起听 Worker:
这里的
npm ci --prefix np-submodule/NeriPlayer-LTW npm run check --prefix np-submodule/NeriPlayer-LTW
npm run check会依次执行node --check、协议测试和wrangler deploy --dry-run;协议或房间状态改动还需要实际验证 create/join/ws 流程。 - 新增单元测试放到
app/src/test/; 新增设备或 Compose UI 测试放到app/src/androidTest/。 - 行为变更涉及 README、设置文案、用户流程或同步格式时,请同步更新文档。
当前已有测试覆盖的重点包括:
- YouTube 登录、Cookie 轮换、匿名会话、挑战解析、PoToken、取流、Range/Seek 策略与预取 (含长音频大幅 Seek 的快速恢复)
- 网易云歌词、本地 smoke test、自动换源和播放响应解析
- USB 独占 keep-alive、启动看门狗、前后台恢复、32-bit/float 输出、 UAC2 显式反馈、长调度间隙重捕获、协调式重配置、Runtime Report v2、 背压恢复、延迟 runtime 刷新重试、比特完美音量和音频焦点策略
- 主标签双 scene 转场、快速反向切换、抽屉/连贯详情反馈、玻璃 owner 隔离 和未布局 scene 几何过滤
- 下载元数据、命名、目录迁移、快照缓存、
.nomedia、删除语义和启动恢复 - 启动阶段、通知权限、播放服务启动、历史记录与安全模式恢复规划
- 本地扫描、元信息补全、封面回退、系统歌单去重和歌单顺序稳定性
- GitHub/WebDAV 同步序列化、缺字段快照清洗、旧歌单顺序迁移、删除策略、 播放统计合并、WebDAV 并发回退、原子文件写入和上传重试
- 长音频进度阈值、显式位置优先、BilibiliSponsorBlock 本地跳转与一起听禁用策略
- 一起听地址校验、版本门控、循环/随机模式、stable track key 目标校验、 播放同步规划、候选直链回退、邀请/成员密钥、显式离开/重连、事件排序、Session 控制/取消与协议兼容
- 歌词视图、逐词时间、外部蓝牙歌词、播放音效和播放策略
- 配置备份、设置生成、安全守卫、崩溃日志文件和安全模式相关逻辑
PR 建议包含:
- 变更动机
- 关键实现点
- 风险与兼容性影响
- 测试方式
- 如涉及 UI,附截图或录屏
不要提交:
- APK、签名文件、IDE 本地配置
- 缓存、日志、临时构建产物
- 授权 Cookie、Token、完整配置备份、个人数据
Commit 信息建议遵循 Conventional Commits,
例如 feat: ...、fix: ...、docs: ...。
- 项目仅供学习与研究使用,请勿用于非法用途。
- 本项目使用 GPL-3.0 协议。
- 提交贡献即表示你同意至少以 GPL-3.0 分发你的修改。
app/src/main/cpp/README.md中的替代授权只覆盖明确列出的 NeriPlayer 自有 Native 源码,不覆盖第三方代码或其他仓库内容。- Native PR 本身不代表授予替代授权;若贡献者同意双授权,必须在 PR、 commit 或版权持有人接受的其他可审计记录中加入该 README 提供的声明。
- 未提供显式双授权的外部 Native 贡献仍可按 GPL-3.0 接受, 但不进入“满足署名条件即可闭源使用”的例外范围。
- Issues:缺陷、功能建议、讨论
- README.md:功能与使用说明
- CODE_OF_CONDUCT.md:社区行为准则
如你准备提交较大的结构性改动,建议先开 Issue 对齐方向。