Skip to content

新功能(歌词):添加歌词源偏好设置 - #417

Merged
cwuom merged 9 commits into
cwuom:masterfrom
BCMOJANG:feat/lyric-source-preference
Sep 26, 2026
Merged

cwuom merged 9 commits into
cwuom:masterfrom
BCMOJANG:feat/lyric-source-preference

Conversation

@BCMOJANG

@BCMOJANG BCMOJANG commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

描述 / Description

为歌词补全链路增加「歌词源」设置,让用户能表达两种此前无法表达的偏好:

  1. 默认歌词源:指定播放时优先使用哪个来源(自动 / 网易云 / 酷狗 / QQ 音乐 / LRCLIB / AMLL TTML),找不到时仍按原有逻辑回退。
  2. 优先使用逐词歌词:同一首歌存在多个候选时,把「是否带词级时间轴」作为排序的一级条件,而不是只依赖综合匹配分。

两个设置的默认值都与改造前完全一致(跟随平台 + 优先逐词),因此不改变现有用户的行为。

类型 / Type

  • Bug 修复 / Bug Fix
  • 新功能 / New Feature
  • 文档更新 / Documentation Update
  • 其他(请描述)/ Other (please describe):

修复或解决的问题 / Issues Fixed or Closed by This PR

背景讨论见 #412(歌词源相关),本 PR 是该讨论中「歌词源」方向的具体实现。
未声称修复该 issue 中关于平台侧逐词歌词覆盖率的讨论。

关键实现点

  • data/settings/LyricSourcePreference.kt(新增):来源枚举 + LyricSourcePreferencePolicy(存储值归一化、旧值别名、非法值回退)。
  • AutoSettingsSchema.kt:新增 @AutoSettingsSection(order = 66) lyricSource,登记 prefer_word_timed_lyrics(Boolean,默认 true)与 default_lyric_source(String,默认 automatic,带 normalizer)。
  • EditableLyricMatchPolicy.kt:RankedEditableLyricMatch 增加 hasWordTiming,比较器改为先按词级时间分层、再比较原有匹配分。
  • PlayerLyricsProvider.kt:新增 resolvePreferredLyricSourceEntries(...),按偏好重排候选;defaultLyricSource 贯穿 getYouTubeMusicLyrics 与 loadDurationMatchedExternalLyrics。
  • PlayerManagerLifecycleExtensions.kt:监听两个偏好变化,清理 ytMusicLyricsCache、neteaseLyricsCache 与 AMLL 缓存,并触发外部蓝牙歌词重新同步,保证切换后立即生效。
  • UI:新增独立设置页 SettingsPage.LyricSource 与 SettingsLyricSourceSection,同步补齐中英文字符串与 SettingsSearchIndex 搜索关键词。
  • README.md / README_EN.md 已同步「歌词来源」章节。

风险与兼容性

  • 默认 automatic + prefer_word_timed_lyrics = true 与改造前行为一致,老用户升级后无感知变化。
  • default_lyric_source 通过 normalizer 兼容历史/别名取值,非法值回退到 automatic;配置导入导出与启动快照(PlaybackPreferenceSnapshot、ConfigSettingsSanitizer)已同步。
  • 偏好变化时清空歌词缓存,避免旧候选继续命中。
  • 未改动播放解析、下载、同步等链路。

测试方式

  • ./gradlew :app:testDebugUnitTest :app:lintDebug(3227 个单元测试 + lint 通过)
  • ./gradlew :app:connectedDebugAndroidTest(模拟器 connected 测试通过)
  • 新增 EditableLyricMatchPolicyTest 用例覆盖词级时间分层与默认排序回退
  • AutoSettingsGeneratedTest 已登记新增的 lyricSource section

清单 / Checklist

  • 我已阅读并遵循贡献指南 / I have read and followed the contribution guidelines
  • 我已在本地测试这些更改 / I have tested these changes locally
  • 我已更新相关文档或注释(如适用) / I have updated relevant documentation or comments (if applicable)
  • 我确认本次更改没有破坏任何原有逻辑 / I confirm this change does not break existing behavior
  • 我确认本次更改保持向下兼容(如适用) / I confirm this change remains backward compatible (if applicable)
  • 我已运行必要的构建、测试或静态检查 / I have run the required build, tests, or static checks
  • 我确认没有提交密钥、Token 或其他敏感信息 / I confirm no secrets, tokens, or other sensitive information were committed
  • 涉及 UI 变更时,我已补充截图或录屏(如适用) / I have added screenshots or recordings for UI changes (if applicable)

其他信息 / Additional Information

新增设置入口位于「设置 → 歌词源」,包含两个设置项:

  • 优先使用逐词歌词(开关,默认开)
  • 默认歌词源(可选:自动(跟随平台)/ 网易云 / 酷狗 / QQ 音乐 / LRCLIB / AMLL TTML)
Screenshot_2026-09-13-01-37-58-031_moe ouom neriplayes Screenshot_2026-09-13-01-37-56-194_moe ouom neriplayes Screenshot_2026-09-13-01-37-54-317_moe ouom neriplayes Screenshot_2026-09-13-01-37-31-266_moe ouom neriplayes

新增歌词偏好设置,支持自动、网易云、酷狗、QQ 音乐、LRCLIB 和 AMLL TTML。选择具体来源时,应用会优先尝试该来源;未找到符合条件的歌词时,继续使用原有歌词加载流程。偏好来源匹配要求候选歌词与歌曲时长兼容。

开启“优先使用逐词歌词”后,逐词歌词候选优先于普通歌词;关闭后,应用不再为逐词时间轴覆盖已有歌词,也不再使用 AMLL 补充逐词歌词。设置默认值为自动来源和开启逐词优先。歌词来源选项已并入歌词设置。

新增测试覆盖逐词候选排序、来源偏好尝试条件、时长匹配、首选来源歌词状态及设置搜索定位。未提供这些测试的运行结果。

- add LyricSourcePreference and a dedicated settings page for choosing
  the default lyric source (automatic / NetEase / Kugou / QQ Music /
  LRCLIB / AMLL TTML), falling back silently when a source has no match
- add a "prefer word-timed lyrics" toggle and thread it through the
  matcher, playback lyric resolution and the lyrics editor sheet
- promote word-timed candidates into their own ranking tier when
  preferred, and always report hasWordTiming independently of the flag
- evict lyric caches when any lyric source setting changes
- persist both settings in the playback preference snapshot and
  sanitize the stored source value
Adding @AutoSettingsSection(order = 66) object lyricSource made
AutoSettingsMetadata.sections return 14 entries, which broke the
hardcoded 13-entry assertion in
AutoSettingsGeneratedTest.generatedMetadataCoversSectionsAndCustomSettings.

lyricSource (order 66) sorts between lyrics (65) and network (70).
Also assert the new section constant in
generatedSectionConstantsCoverSettingsScopes.
@coderabbitai

coderabbitai Bot commented Sep 12, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

新增逐词歌词偏好和默认歌词来源设置,并将偏好保存到设置存储。歌词匹配会记录逐词时间轴状态,并在启用偏好时调整候选排序。歌词提供器可尝试指定来源,并将结果用于主歌词、翻译和音译查询。播放界面响应偏好变化、显示逐词标记;设置页面新增来源选择和逐词偏好选项。

Priority: ➖ Normal

Merge Risk: 🔵 Low · up to bad8d

With a preferred source selected, lyrics already available for a song may disappear while the app searches online. This is a bounded playback issue to fix or explicitly accept before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to bad8d

A selected lyric source can be queried before existing per-track lyric decisions are applied. This may disclose song metadata to the selected provider or show lyrics for a track whose downloaded lyrics were cleared. Preference changes may also leave an older lyric result visible briefly. The exposure is limited to this app’s lyric flow; no new service credentials or privileged access were identified.

Retained concerns

  • Medium · security · inferred: For a non-local track with no stored lyric, selecting a provider can initiate a lookup before an explicitly cleared downloaded lyric is handled. The same early lookup also precedes existing YouTube translation suppression and platform-specific branches, expanding when song metadata can reach the selected external provider.
  • Low · reliability · inferred: An in-flight automatic YouTube lookup can write to the song-keyed cache after preference-triggered eviction; same-song Bluetooth publication likewise has no preference-revision check. Cancellation and song-identity checks reduce, but do not establish the absence of, cross-revision publication.
Security review details

Security Blast Radius

  • inferred — The identifiable exposure is song title, artist, album, and duration from a non-local track to a user-selected lyric source. The inspected path does not establish access to other users, stores, environments, or privileged credentials.

Security Findings and Attack Paths

  • inferred — For a non-local song without a stored lyric but with a cleared downloaded lyric, the preferred lookup can run before the clear produces an empty result. Remote-supplied track metadata can thereby reach the selected provider where the prior local decision would have stopped this lyric path.

Trust Boundaries and Controls

  • observed — The source setting is required for the early path; local songs and an explicitly empty stored lyric are excluded. Source membership, candidate sanitization, confidence filtering, and title/artist/duration selection remain in place.

Resilience and Maintainability Implications

  • observed — Bluetooth resynchronization cancels the prior job, checks cancellation after loading, and verifies current-song identity before publishing. It does not compare the preference revision for a same-song replacement.

Hardening Proposals

  • proposed — Apply per-track clear and translation-suppression decisions before any preferred external lookup, and use a captured preference generation when committing cache entries or externally published same-song lyrics.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 76 functions across 22 files. (9 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了本次变更的主要内容:新增歌词源偏好设置,并与歌词相关功能一致。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 2.63% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 76 functions across 22 files. (9 skipped: 9 unsupported.)


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)
app/src/main/java/moe/ouom/neriplayer/core/api/lyrics/EditableLyricsMatcher.kt (1)

97-100: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

让 lowConfidenceMatches 使用统一结果比较器排序。

当 preferWordTimed 为 true 时,当前排序不会比较 hasWordTiming;matchLyrics 还会先拼接 rankedMatches,因此逐词候选可能排在逐行候选之后。请在去重后使用 editableLyricMatchResultComparator,并传入 normalizedRequest.preferWordTimed、editableLyricMatchSourcePriority 和来源兜底排序。这样可以保留现有的置信度、分数、时长及来源排序规则,同时确保逐词候选优先。

🟡 Other comments (2)
app/src/main/java/moe/ouom/neriplayer/core/player/metadata/PlayerLyricsProvider.kt-1028-1041 (1)

1028-1041: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

让 defaultLyricSource 作用于 YouTube Music 曲目

LyricSourcePreference 定义为播放期间优先使用的歌词来源,但 YouTube Music 分支在调用 getYouTubeMusicLyrics 后立即返回,完全跳过该设置。请将 defaultLyricSource 传入该分支,并处理包括 AmllTtml 在内的可选来源;否则用户选择的来源对 YouTube Music 曲目无效。

app/src/main/java/moe/ouom/neriplayer/core/player/metadata/PlayerLyricsProvider.kt-1102-1106 (1)

1102-1106: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

保留 matchHighConfidenceLyricsForSource 的排序结果

resolvePreferredLyricSourceEntries 已将 preferWordTimed 传给 matcher。matcher 会用 editableLyricMatchResultComparator 将逐词歌词置于前面,但这里的 automaticExternalLyricMatchComparator() 会再次排序,且不检查 hasWordTiming。启用逐词偏好时,firstOrNull() 可能因此选择逐行歌词。删除此处的二次排序,直接使用 matcher 返回的顺序。

🧹 Nitpick comments (1)
app/src/main/java/moe/ouom/neriplayer/core/player/metadata/PlayerLyricsProvider.kt (1)

1074-1120: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

为默认歌词源匹配结果增加缓存。

当曲目没有本地、已存储或已下载歌词,且使用非网易云默认源时,getLyrics 每次都会调用 matchHighConfidenceLyricsForSource。该方法随后会重复执行远程搜索和歌词详情请求。NowPlayingScreen 和歌词编辑器的重复调用会增加网络流量,并延迟歌词显示。

现有 neteaseLyricsCache 和 ytMusicLyricsCache 不覆盖此路径。请新增专用缓存,缓存键至少包含曲目标识、歌名、歌手、专用来源和 preferWordTimed,并将其加入 clearLyricsCaches 的失效流程。


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: QUIET

Plan: Advanced

Run ID: c1fe85a2-184a-4ab3-8ab7-37a406dff2a6

📥 Commits

Reviewing files that changed from the base of the PR and between ac7bdea and 4d34829.

⛔ Files ignored due to path filters (2)
  • README.md is excluded by !**/*.md
  • README_EN.md is excluded by !**/*.md
📒 Files selected for processing (24)
  • app/src/main/java/moe/ouom/neriplayer/core/api/lyrics/EditableLyricMatchPolicy.kt
  • app/src/main/java/moe/ouom/neriplayer/core/api/lyrics/EditableLyricsMatcher.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/PlayerManager.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/lifecycle/PlayerManagerLifecycleExtensions.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/metadata/PlayerLyricsProvider.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerPersistenceExtensions.kt
  • app/src/main/java/moe/ouom/neriplayer/data/config/ConfigSettingsSanitizer.kt
  • app/src/main/java/moe/ouom/neriplayer/data/settings/AutoSettingsSchema.kt
  • app/src/main/java/moe/ouom/neriplayer/data/settings/LyricSourcePreference.kt
  • app/src/main/java/moe/ouom/neriplayer/data/settings/PlaybackPreferenceSnapshot.kt
  • app/src/main/java/moe/ouom/neriplayer/data/settings/SettingsRepository.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/NowPlayingScreen.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/SettingsScreen.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/settings/component/SettingsLyricSourceSection.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/settings/page/SettingsPage.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/settings/page/SettingsSearchIndex.kt
  • app/src/main/res/values-en/strings_lyrics.xml
  • app/src/main/res/values-en/strings_settings_general.xml
  • app/src/main/res/values-zh/strings_lyrics.xml
  • app/src/main/res/values-zh/strings_settings_general.xml
  • app/src/main/res/values/strings_lyrics.xml
  • app/src/main/res/values/strings_settings_general.xml
  • app/src/test/java/moe/ouom/neriplayer/core/api/lyrics/EditableLyricMatchPolicyTest.kt
  • app/src/test/java/moe/ouom/neriplayer/data/settings/AutoSettingsGeneratedTest.kt

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

@cwuom

cwuom commented Sep 13, 2026 •

Copy link
Copy Markdown
Owner
image 歌词源和歌词设置的图标重叠了 我更倾向于把歌词源合并到歌词设置里 在歌词来源这个卡片的上面新增一个卡片 同时在歌词设置选项里重命名原先就有的歌词来源这个带有歧义的标题(可以改成歌词服务) 歌词源作为新卡片的标题在这个场景下可以变成来源偏好

@BCMOJANG

Copy link
Copy Markdown
Contributor Author

我甚至觉得能直接合并到歌词设置里面

@cwuom

cwuom commented Sep 13, 2026

Copy link
Copy Markdown
Owner

我甚至觉得能直接合并到歌词设置里面

我的意思就是合并到歌词设置里

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Quiet mode is enabled, so only the most important comments were posted inline. Other review comments are grouped below.

🟡 Other comments (1)
app/src/main/java/moe/ouom/neriplayer/ui/screen/NowPlayingScreen.kt-3226-3239 (1)

3226-3239: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

在偏好歌词匹配完成前保留已有歌词。

启用 shouldLoadPreferredLyrics 时,immediateLyricsState 和快速阶段都会发布空歌词。后台任务先等待 getPreferredLyricSourceResult,只有该调用完成后才进入本地或平台歌词回退流程。网络请求未立即结束时,已有本地歌词会保持不可见。

先发布本地或已存歌词。偏好来源命中后再替换;同时为偏好来源匹配设置有界超时,并在超时后执行回退。


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: cwuom/NeriPlayer/.coderabbit.yaml

Review profile: QUIET

Plan: Advanced

Run ID: 02daaf61-7c0b-487f-a99c-22dd18085d99

📥 Commits

Reviewing files that changed from the base of the PR and between 4d34829 and bad8de2.

⛔ Files ignored due to path filters (2)
  • README.md is excluded by !**/*.md
  • README_EN.md is excluded by !**/*.md
📒 Files selected for processing (25)
  • app/src/main/java/moe/ouom/neriplayer/core/api/lyrics/EditableLyricsMatcher.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/PlayerManager.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/lifecycle/PlayerManagerLifecycleExtensions.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/metadata/PlayerLyricsProvider.kt
  • app/src/main/java/moe/ouom/neriplayer/core/player/persistence/PlayerManagerPersistenceExtensions.kt
  • app/src/main/java/moe/ouom/neriplayer/data/config/ConfigSettingsSanitizer.kt
  • app/src/main/java/moe/ouom/neriplayer/data/settings/AutoSettingsSchema.kt
  • app/src/main/java/moe/ouom/neriplayer/data/settings/LyricSourcePreference.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/NowPlayingScreen.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/SettingsScreen.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/settings/component/SettingsLyricSourceSection.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/settings/component/SettingsLyricsOffsetSection.kt
  • app/src/main/java/moe/ouom/neriplayer/ui/screen/tab/settings/page/SettingsSearchIndex.kt
  • app/src/main/res/values-en/strings_lyrics.xml
  • app/src/main/res/values-en/strings_settings_general.xml
  • app/src/main/res/values-en/strings_settings_playback.xml
  • app/src/main/res/values-zh/strings_lyrics.xml
  • app/src/main/res/values-zh/strings_settings_general.xml
  • app/src/main/res/values-zh/strings_settings_playback.xml
  • app/src/main/res/values/strings_lyrics.xml
  • app/src/main/res/values/strings_settings_general.xml
  • app/src/main/res/values/strings_settings_playback.xml
  • app/src/test/java/moe/ouom/neriplayer/core/player/metadata/PlayerLyricsProviderTest.kt
  • app/src/test/java/moe/ouom/neriplayer/ui/screen/NowPlayingScreenTest.kt
  • app/src/test/java/moe/ouom/neriplayer/ui/screen/tab/settings/page/SettingsPageTest.kt

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.

@cwuom
cwuom merged commit b1cc3b6 into cwuom:master Sep 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants