Skip to content

Latest commit

 

History

History
776 lines (659 loc) · 42.5 KB

File metadata and controls

776 lines (659 loc) · 42.5 KB

English | 中文

Contributing to NeriPlayer / 贡献指南

感谢你愿意为 NeriPlayer 做出贡献。 本文描述当前 Android 客户端和一起听 Worker 的真实实现, 请以源码和运行行为为准同步维护文档。


项目定位 / Scope

  • NeriPlayer 是一个原生 Android 音频播放器,不是公共云端曲库服务。
  • 在线内容能力主要来自 网易云音乐BilibiliYouTube Music
  • 播放页元数据/歌词补全链路目前使用 网易云 + QQ 音乐, 并接入 LRCLIB 外部歌词来源。
  • 数据默认保存在本地;GitHub / WebDAV 同步是可选能力, 同步对象是歌单、收藏、最近播放、播放统计等元数据,不是媒体文件本身。
  • 一起听服务端在 np-submodule/NeriPlayer-LTW,基于 Cloudflare Workers 与 Durable Objects。

文档地图 / Documentation Map

维护文档时建议按用途拆开看:

  • 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.mdapp/src/main/cpp/tests/usb/fixtures/README.md
    • 说明公开测试语料/夹具只能使用合成或可审计资料,真实设备证据留在私有目录。
  • np-submodule/NeriPlayer-LTW/README.md
    • 面向一起听服务端部署者,说明 Worker API、事件模型、部署和本地检查。

行为变更如果影响用户理解,请同步更新 README; 如果影响扩展方式、测试方式或模块边界,请同步更新 CONTRIBUTING。


开发环境 / Development Environment

  • 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
  • NDK27.0.12077973
  • CMake3.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 管理。
  • 应用只保留 zhen 资源,见 build-logic 的 locale filter。

质量护栏 / Quality Guardrails

这个项目功能面比较宽,提交前请优先保护这些链路:

  • 播放链路PlayerManager、取流策略、缓存、失败刷新、自动换源、 长音频进度记忆、BilibiliSponsorBlock 跳过策略、状态恢复、响度均衡、 声道平衡、高解析输出、USB 独占 Native 链路、启动看门狗和前后台健康审计。
  • 下载链路AudioDownloadManagerGlobalDownloadManagerDownloadTaskStoreDownloadLifecyclePoliciesManagedDownloadStorage、 续传检查点、sidecar 文件、任务队列恢复、取消清理和 SAF 目录迁移。
  • 同步链路:GitHub / WebDAV 的三路合并、删除记录、播放统计、 缺字段快照清洗、JSON/ProtoBuf/Base64 格式兼容和 WebDAV 并发保护。
  • 本地数据:歌单 JSON 原子写入、本地元信息补全、配置导入导出、 授权加密存储和 DataStore 设置。
  • 歌词与播放页 UIAdvancedLyricsViewSyncedLyricsViewLyricShareSheet、歌词音译显示、歌词长按分享和 Lyrics 全屏页。
  • 导航与玻璃 UIMainTabLayerHost、详情页抽屉/连贯反馈、 可打断主标签切换、页面状态保留和 Advanced Glass owner 接力。
  • 存储与缓存 UIStorageUsageAnalyzer、缓存清理选项、下载目录索引和 SAF 快照。
  • 一起听:Android 客户端、Worker 协议字段、角色权限、队列、 版本门控更新、直链共享开关和房主离线恢复。
  • 诊断恢复:安全模式、JVM/Native 崩溃日志、ANR 记录和 Debug 探针。
  • 本地持久化:播放/流量统计的批量写入、生命周期 flush、原子文件替换和 SAF/本地歌单初始化就绪状态。

对应测试已分布在 app/src/test/app/src/androidTest/。 修改上述链路时,优先搜索同名目录或相邻测试类,再补新的覆盖。


快速开始 / Quick Start

  1. 克隆仓库:
    git clone --recursive https://github.com/cwuom/NeriPlayer.git
    cd NeriPlayer
  2. 构建调试版:
    ./gradlew :app:assembleDebug
  3. 安装到设备:
    adb install -r app/build/outputs/apk/debug/app-debug.apk
  4. 首次启动会进入免责声明与启动引导;Android 13+ 会请求通知权限。
  5. 如需调试入口,在设置页连续点击版本号 7 次,底栏会出现独立 Debug 页。

构建发布版 / Release Build

发布版默认启用混淆与资源收缩。 普通 assembleRelease 默认只构建 arm64-v8a,手动多 ABI 构建需要额外参数。

  1. ~/.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

  2. 构建默认 Release:

    ./gradlew :app:assembleRelease
  3. 构建多 ABI Release:

    ./gradlew :app:assembleRelease -PbuildAllReleaseAbis=true
  4. 产物位于 app/build/outputs/apk/release/,文件名格式为:

    NeriPlayer-<git短哈希>.<MMddHHmm>[-abi].apk
    

安全提醒:

  • 不要提交 keystore、密码、Cookie、Token 或其他敏感信息。
  • 不要在 Issue / PR 中粘贴完整授权信息。
  • 完整配置导出文件会包含平台授权和同步凭据,不能作为公开测试附件。

项目结构与当前实现 / Project Layout

根模块

  • :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 源码/文档树,当前不参与主应用模块构建。

Android 客户端关键路径

  • 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.ktNeteaseQrLoginActivity.ktBiliWebLoginActivity.ktBiliQrLoginActivity.ktYouTubeWebLoginActivity.kt 是内部平台登录页。
  • app/src/main/java/moe/ouom/neriplayer/ui/NeriApp.kt

    • 顶层 Compose 应用骨架,负责 NavHost、动态底栏、 MiniPlayerNow Playing 覆盖层、Debug 路由、主题、缓存清理和播放服务同步。
    • 主标签页面由 MainTabLayerHost.kt 保留出场/入场双场景, 按标签顺序执行可打断横向转场,并为各场景保留 saveable state 和玻璃 owner。
  • app/src/main/java/moe/ouom/neriplayer/ui/component/lyrics/

    • AdvancedLyricsView.ktSyncedLyricsView.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/:播放页元数据/歌词补全接口, 当前实现为 CloudMusicSearchApiQQMusicSearchApi
    • 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.ktlifecycle/PlayerManagerLifecycleExtensions.kt: 播放启动看门狗、前后台健康审计、失败恢复和 USB 独占异常回退。
    • resolver/netease/PlayerManagerNeteaseAutoSourceSwitch.kt:网易云无权限、 无直链或试听片段时的 Bilibili 自动换源兜底。
    • resolver/youtube/YouTubeGoogleVideoRangeSupport.ktYouTubeSeekRefreshPolicy.ktprefetch/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 共享的 SongItemSongIdentity 与媒体模型扩展。
    • 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/
  • app/src/main/java/moe/ouom/neriplayer/core/lyricon/

    • 词幕适配(Lyricon Provider)与 SuperLyric 输出, 同步歌曲、播放状态、进度、逐字歌词和翻译。

当前能力边界 / Current Boundaries

  • 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,下载由 AudioDownloadManagerManagedDownloadStorage 写入本地文件。
  • GitHub / WebDAV 同步只同步元数据,不同步音频缓存、下载文件、 本地音频文件、Cookie 或播放 Token。
  • 本地歌单与同步歌单通过 songOrderVersion 区分顺序语义:0 是旧版顺序, 1 是当前展示顺序;读取旧数据时要兼容迁移,不能直接按新版顺序解释。
  • 同步快照可能来自旧版 JSON/ProtoBuf 或异常远端文件;读取时要用安全默认值, 过滤缺少可解析歌曲身份、无有效删除时间或无效歌单 ID 的记录, 缺失 addedAt 的歌曲不能排到已有时间歌曲之前。
  • 省流模式写入 backup-raw.bin 原始 GZIP(ProtoBuf) 字节;读取必须兼容 backup.json 与历史 backup.bin Base64,不能在 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.0UAC2.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,过滤 clientInstanceIdclientSequenceclientTimeMs 之前的事件,且 REQUEST_SET_TRACK 只能选当前队列曲目。
  • 一起听当前曲目最多保留 3 条去重 HTTP(S) 候选;听众先按本机音质策略解析, 候选只作为本次会话的失败回退,不能写入普通歌曲或离线缓存。服务端位置按曲目 时长推算,单曲循环按时长取模;同一成员凭成员密钥重连不应触发新成员自动暂停。

常见扩展路径 / Extension Paths

1. 新增 Explore 搜索源

适用于把新平台接到 Explore 页搜索或发现流。

  1. core/api/ 下实现对应客户端或仓库。
  2. ExploreViewModel 中增加请求、分页和状态映射。
  3. ExploreScreen / Host 页面中补充平台标签和结果 UI。
  4. 如需播放,继续接入 PlayerManager 的音源解析链路。
  5. 如需下载,补齐 AudioDownloadManager 和下载元数据映射。

2. 新增播放页元数据补全源

适用于补封面、歌词、曲目信息,而不是扩展 Explore 页。

  1. core/api/search/ 下实现新的 SearchApi
  2. AppContainer 中注册单例。
  3. SearchManager 中增加路由、匹配和降级逻辑。
  4. 视需要补充 MusicPlatform、字符串资源和调试探针。

3. 新增取流平台

  1. 参考 bili/youtube/ 设计客户端与播放仓库。
  2. 如需特殊 Header,扩展 core/player/engine/datasource/ConditionalHttpDataSourceFactory.kt
  3. core/player/url/ 与对应 resolver/ 的 URL 解析链路接入平台。
  4. 下载、歌词、封面和播放统计要保持边界清晰, 不要把缓存和永久下载混成一套实现。
  5. 如需支持同步到网易云我喜欢的音乐,必须提供稳定的网易云歌曲 ID 或可验证映射,并复用 LocalPlaylistRepository 的候选校验逻辑。

4. 修改网易云自动换源

  1. 入口在 core/player/url/PlayerManagerUrlExtensions.kt 的网易云 URL 解析流程。
  2. 匹配与打分逻辑在 core/player/resolver/netease/PlayerManagerNeteaseAutoSourceSwitch.kt
  3. 自动换源只处理网易云无权限、无可用直链或试听片段兜底; 不要把它扩展成跨平台聚合搜索。
  4. 调整匹配策略时要同时考虑歌名、歌手、分 P、时长误差和缓存 key 稳定性。

5. 新增设置项

  1. 优先在 data/settings/AutoSettingsSchema.kt 登记 key、默认值、类型和展示元数据。
  2. 简单开关可用 KSP 生成的 AutoSettingsRepositoryAutoSettingsSwitchItems
  3. 有副作用、互斥逻辑、权限或启动快照需求的设置,应保留手写 setter。
  4. 如果设置影响启动早期行为,还要同步更新对应 snapshot: BootstrapSettingsSnapshotThemePreferenceSnapshotPlaybackPreferenceSnapshot
  5. UI 入口通常放在 SettingsScreen.kt 对应 SettingsPageui/screen/tab/settings/component/ 下。
  6. 探索页搜索历史由 ExploreSearchHistoryRepository 保存; explore_search_history_enabled 关闭时,探索页必须隐藏历史并停止新增记录, 不要在开关切换时静默删除已有记录。
  7. 歌词字号现在分成封面页和歌词页两组,且歌词和翻译各自独立; 修改相关 UI 时同时更新 SettingsRepository.lyricFontScalesFlowsetLyricFontScale(target, scale) 和对应的预览/播放调用点。

6. 修改 USB 独占播放

  1. 先阅读 core/player/usb/sink/UsbExclusiveAudioSink.ktcore/player/usb/transport/core/player/usb/session/core/player/policy/usb/UsbAudioSinkReconfigurationCoordinator.ktcore/player/watchdog/PlayerManagerStartupWatchdogExtensions.ktcore/player/lifecycle/PlayerManagerLifecycleExtensions.kt 和相关测试。
  2. 当前 USB 独占实现覆盖 UAC1.0 和兼容 UAC2.0 Type I PCM 设备; 如要扩到更复杂的 UAC2.0 拓扑或非 Type I PCM 设备,需要把文档、能力边界、 诊断和兼容性假设一起更新。
  3. 同时考虑设备选择、采样率/位深策略、32-bit PCM、PCM float 软件转换、 比特完美音量、UAC2 时钟拓扑、显式反馈端点、前后台缓冲区、唤醒锁、 后台权限提示和系统回退链路;隐式反馈目前仍不是可用候选。
  4. 修改自动恢复、keep-alive 或后台审计时,要验证前台播放、息屏后台、 USB 拔插和 system fallback 四条路径。
  5. 改动反馈时钟、长调度间隙重捕获、协调式重配置、动态传输缩放、 背压恢复或候选位深回退时,要同步检查 UsbExclusiveOutputFormatResolverTestUsbExclusivePcmWritePlannerTestUsbExclusiveSessionControllerReusePolicyTestUsbAudioSinkReconfigurationCoordinatorTest 和 native USB feedback/PCM/UAC 测试。
  6. Runtime Report v2 字段必须保持 fail-closed 解析;修改反馈端点、状态、 holdover、恢复 action 或代次字段时,要同步更新 Kotlin parser 和边界测试。
  7. 错误语义或恢复策略变化时,要同步更新设置页 / Debug 页诊断展示和对应测试。
  8. Native 变更至少运行三组 host gate 和四 ABI Android 编译; host 模型、ABI 编译和真实 DAC 验证是三个不同的通过条件。

7. 修改 GitHub / WebDAV 同步

  1. 先理解 data/sync/model/SyncDataModels.ktdata/sync/github/SyncDataSerializer.kt 的兼容策略;共享载荷模型不得 重新放回 GitHub provider 包。
  2. 同步对象包含歌单、收藏歌单、最近播放、删除记录和播放统计。 省流写侧使用 backup-raw.bin 原始 GZIP(ProtoBuf),普通模式使用 backup.json;读侧还必须兼容历史 backup.bin Base64。GitHub Git Data API 的 blob 请求会使用 Base64 传输封装,但仓库内保存的是原始正文。
  3. songOrderVersion=0 表示旧版顺序,songOrderVersion=1 表示当前展示顺序; 序列化、合并和落回本地歌单时必须保留旧数据迁移。
  4. 歌单成员使用 syncMembershipTokens / removedMembershipTokens 表达 observed-remove 语义;新增字段必须兼容旧 JSON 与 ProtoBuf 的缺字段载荷, 带 token 的成员不能退回只比较 addedAt/deletedAt 的删除裁决。
  5. 缺字段或畸形快照必须先清洗再合并;SyncSong 至少要有 id、audioId 或 mediaUri 之一,删除记录还需要有效删除时间,缺失 addedAt 的歌曲只能作为低优先级展示项。
  6. CoverUrlMapper.kt 位于 provider 无关的 data/sync/; 合并策略主要在 GitHubSyncManager.kt,WebDAV 复用同一套数据模型和多数合并逻辑。
  7. 不要破坏 GitHubSyncWorker.kt / WebDavSyncWorker.kt 的延迟同步、 周期同步、validated network 检查和失败重试行为。GitHub 写入按远端分支头做 非强制更新,冲突时必须失败而不是覆盖;WebDAV 无 ETag/Last-Modified 时仍需 重新验证远端 SHA-256 指纹。
  8. 涉及敏感信息时统一走 SecureTokenStorage.ktWebDavStorage.kt, 不要放回 DataStore 或明文 JSON。

8. 修改下载存储

  1. 先阅读 ManagedDownloadStorage.ktnaming/ManagedDownloadNaming.kttask/DownloadTaskStore.ktpolicy/DownloadLifecyclePolicies.kt 和相关单元测试。
  2. 同时考虑默认应用目录、SAF 自定义目录、迁移、历史命名、元数据文件和 .nomedia
  3. 下载任务先写入 cache/download_staging/,再提交到正式目录; .resume.json.hls.json 是续传恢复的一部分,不能当普通临时文件随意清理。
  4. 默认下载并发是 6,设置允许调整到 1-8; 修改并发、重试或网络恢复时,请同步检查 DownloadParallelism.ktAudioDownloadManager.ktGlobalDownloadManager.kt
  5. 修改目录迁移、删除语义、续传检查点或 sidecar 写入时, 必须补充/更新对应单元测试。

9. 修改歌词显示、分享或音译

  1. 播放页歌词主要在 ui/component/lyrics/AdvancedLyricsView.ktui/component/lyrics/SyncedLyricsView.ktNowPlayingScreen.kt
  2. 全屏歌词页在 LyricsScreen.kt,歌词分享入口复用 LyricShareSheet.kt
  3. 音译显示通过 lyric_translation_use_phonetic 设置控制, 需要先开启翻译且当前歌词存在音译数据。
  4. 长按歌词用于打开分享面板;修改手势时要同时检查点击跳转、 手动歌词偏移和高级歌词视口滚动。
  5. 歌词卡片通过 FileProvider 分享缓存文件; 修改输出位置时要同步检查 file_paths.xml 和缓存清理。

10. 修改存储占用与缓存清理

  1. 入口在 data/storage/StorageUsageAnalyzer.ktSettingsStorageCacheSection.kt
  2. 新增缓存目录时,要决定它属于可清理缓存、下载内容、诊断文件还是应用数据。
  3. 清理操作只能覆盖可再生成内容; 下载歌曲、下载歌词、下载索引和授权数据不能被普通清缓存误删。
  4. 如果清理下载暂存,要尊重当前下载任务状态; 活跃任务的暂存文件应等任务结束后再处理。

11. 修改网易云歌单详情缓存

  1. 缓存入口是 NeteasePlaylistCacheRepository.kt, 页面状态在 NeteaseCollectionDetailViewModel.kt
  2. 缓存签名基于曲目数量和最近曲目 ID,主要用于判断是否复用曲目列表。
  3. 网络失败或解析失败可以回退缓存,但手动刷新应保留强制刷新语义。
  4. 专辑详情不使用这套歌单缓存,避免不同数据模型互相污染。

12. 修改词幕适配

  1. 词幕适配入口在 core/lyricon/LyriconManager.kt
  2. 开关状态由设置项 lyricon_enabled 控制,并由播放器生命周期同步。
  3. 歌词数据使用 LyricEntry,逐字信息来自 WordTiming; 翻译行按时间容差匹配到原文行。
  4. Lyricon/SuperLyric 位置 feed 独立按 200 ms 推送,使用 elapsed realtime 做死区重算;修改进度节奏时,必须保持前后台时序一致。
  5. 修改时要保持 Lyricon、SuperLyric、状态栏歌词、播放页高级歌词 和外部蓝牙歌词的歌词结构兼容。
  6. 蓝牙歌词的原文与翻译是独立开关;同时输出时必须通过同一个原子快照更新 标题/艺术家字段,并保留曲目信息、字段长度限制、空白清洗和重复提交去重测试。

13. 修改一起听

  1. Android 客户端逻辑在 listentogether/
  2. 服务端逻辑在 np-submodule/NeriPlayer-LTW
  3. 协议字段变更必须同时兼容客户端和 Worker,并更新测试。
  4. shareAudioLinks=false 时,HTTP/WS 房间快照都不能暴露 track.streamUrlqueue[*].streamUrl;关闭该开关时要立即清空 房间里已缓存的直链。
  5. REQUEST_LINK / LINK_READY、成员控制、房主离线恢复和版本门控更新 要一起看,避免旧状态覆盖新状态。
  6. 循环/随机模式使用 PLAYBACK_MODE / REQUEST_PLAYBACK_MODE;成员请求与 LINK_READY 都要校验目标 stable track key,避免异步结果落到错误歌曲。
  7. 首次加入需要 joinSecret,已有成员重连需要 memberSecret;这些密钥不得写入 脱敏房间状态或日志,邀请 URI 的 secret 参数也要按敏感输入处理。
  8. 控制事件可携带 clientInstanceIdclientSequenceclientTimeMs;Worker 会拒绝过期顺序,REQUEST_SET_TRACK 只能选择当前队列已有曲目,客户端兼容路径 也必须保持一致。
  9. 成员显式离开时调用 /api/rooms/:roomId/leave,Worker 删除成员并广播 MEMBER_LEFT;普通 WebSocket 断线属于传输波动,必须保留成员凭据以支持重连; 控制者显式离开则关闭房间。
  10. 房间号 6 位、昵称 1-24、队列上限 2000 和请求去重要视为协议边界, 不要只改 UI 校验而忘记服务端约束。
  11. 设置页支持自定义服务端地址和可用性测试,不要硬编码单一地址。

14. 修改主导航与玻璃转场

  1. 主标签生产路径由 NeriApp.ktMainTabLayerHost.kt 共同承载; 标签顺序与方向统一通过 resolveMainTabTransitionDirection 决定。
  2. 转场期间必须同时保留出场和入场 scene,并通过 SaveableStateHolder 保留各标签状态;快速反向或连续点击不能先清空旧 scene。
  3. 每个 scene 都有独立 MainTabGlassOwner。修改 Advanced Glass 时, 要保证只有当前可见 owner 参与合成,详情页 handoff 仍由导航层接管。
  4. coherent_feedback_enabled 默认关闭:详情页使用抽屉式前景上升和背景轻微下沉; 开启后才使用背景与详情页同步接力的连贯反馈。
  5. 修改动效时要覆盖正向、反向、中断、重复请求、状态恢复和启动首帧; 几何测试不得把尚未布局的 Rect(0, 0, 0, 0) scene 当成真实重叠。
  6. 至少同步检查 NeriAppMainTabTransitionPolicyTestAdvancedGlassNavigationTransitionTestNeriAppNavigationTransitionTestHostNavigationTransitionGeometryTest

调试与日志 / Debugging & Logs

  • 开发者模式开启方式:设置页连续点击版本号 7 次。
  • 开启后底栏会出现独立 Debug 页。
  • 普通文件日志仅在开发者模式开启时启用。
  • 崩溃日志由 ExceptionHandler / NativeCrashHandler 独立落盘,不依赖开发者模式。
  • Debug 页包含 YouTube、Bili、Netease、Search、Listen Together 探针, 以及普通日志和崩溃日志查看器。

常用命令:

adb logcat | findstr NeriPlayer

Linux / macOS 可改用:

adb logcat | grep NeriPlayer

测试与提交流程 / Testing & PR

提交前建议至少完成以下检查:

  1. 能成功构建调试版:
    ./gradlew :app:assembleDebug
  2. 单元测试:
    ./gradlew :app:testDebugUnitTest
  3. 如修改登录态、取流链路或回归风险较高的集成行为,可按需执行 smoke test:
    ./gradlew :app:testDebugUnitTest -DrunNeteaseSmoke=true
    ./gradlew :app:testDebugUnitTest \
      -DrunYouTubePlaybackSmoke=true \
      -DyoutubeSmokeVideoId=<id> \
      [-DyoutubeSmokeForceRefresh=true] \
      [-DyoutubeSmokeCookieFile=/absolute/path/to/cookies.json]
  4. 如修改资源、UI、导航、设置、同步或存储逻辑,建议执行:
    ./gradlew :app:lintDebug
  5. 如涉及 Compose UI、权限、Activity 或登录流程,建议在设备/模拟器上执行:
    ./gradlew :app:connectedDebugAndroidTest
  6. 如修改 Native USB,实现需对齐独立 Android Native CI:
    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
    host gate 会固定执行同一组 CTest;Android 编译还要确认 arm64-v8aarmeabi-v7ax86x86_64 均生成非空 lib_neri.so
  7. 如修改一起听 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 流程。
  8. 新增单元测试放到 app/src/test/; 新增设备或 Compose UI 测试放到 app/src/androidTest/
  9. 行为变更涉及 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: ...


法律与许可 / Legal & License

  • 项目仅供学习与研究使用,请勿用于非法用途。
  • 本项目使用 GPL-3.0 协议。
  • 提交贡献即表示你同意至少以 GPL-3.0 分发你的修改。
  • app/src/main/cpp/README.md 中的替代授权只覆盖明确列出的 NeriPlayer 自有 Native 源码,不覆盖第三方代码或其他仓库内容。
  • Native PR 本身不代表授予替代授权;若贡献者同意双授权,必须在 PR、 commit 或版权持有人接受的其他可审计记录中加入该 README 提供的声明。
  • 未提供显式双授权的外部 Native 贡献仍可按 GPL-3.0 接受, 但不进入“满足署名条件即可闭源使用”的例外范围。

沟通方式 / Communication

如你准备提交较大的结构性改动,建议先开 Issue 对齐方向。