Skip to content

feat: desktop lyrcis#122

Merged
roitium merged 8 commits into
devfrom
feat/desktop-lyrics
Jan 13, 2026
Merged

feat: desktop lyrcis#122
roitium merged 8 commits into
devfrom
feat/desktop-lyrics

Conversation

@roitium
Copy link
Copy Markdown
Collaborator

@roitium roitium commented Jan 6, 2026

Closes #116

Summary by CodeRabbit

  • 新功能
    • 播放设置新增“桌面歌词”显示与锁定开关,支持权限提示、授权流程与错误提示。
    • 播放开始时自动获取并展示桌面歌词,包含防抖与乱序保护以提高稳定性。
    • 通知栏新增切换循环模式按钮。
  • 修复 / 体验
    • 前后台与页面聚焦时同步播放设置状态,操作反馈更及时。
  • Chores
    • 升级相关依赖以提升兼容性与稳定性。

✏️ Tip: You can customize this high-level summary in your review settings.

@roitium roitium added the enhancement New feature or request label Jan 6, 2026
@safedep
Copy link
Copy Markdown

safedep Bot commented Jan 6, 2026

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

Package Details
Package Malware Vulnerability Risky License Report
icon @roitium/expo-orpheus @ 0.9.2
pnpm-lock.yaml
ok icon
ok icon
ok icon
🔗

This report is generated by SafeDep Github App

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 6, 2026

Walkthrough

播放开始时通过 headless 任务触发并导出 setDesktopLyrics(trackId, reason):1s 防抖与时间戳竞态保护、先显示占位歌词、通过 trackService 获取曲目并调用 lyricService.smartFetchLyrics,最终下发歌词到原生 Orpheus;设置面板新增桌面歌词开关并升级 @roitium/expo-orpheus 依赖。

Changes

Cohort / File(s) 变更摘要
桌面歌词核心逻辑
src/utils/player.ts
新增并导出 setDesktopLyrics(trackId, _transitionReason):1s 防抖、时间戳竞态保护、先设占位歌词、通过 trackService 获取曲目并调用 lyricService.smartFetchLyrics,最后调用 Orpheus.setDesktopLyrics,包含错误捕获与 toastAndLogError
播放事件接入
index.js
使用 registerOrpheusHeadlessTask 注册 headless 任务;在事件中判断 eventName === 'onTrackStarted' 并调用 setDesktopLyrics(event.trackId, event.reason)(替代原有日志)。
设置面板 UI
src/app/(tabs)/settings/playback.tsx
新增本地状态 isDesktopLyricsShown / isDesktopLyricsLocked;新增“桌面歌词”与“桌面歌词锁定”开关;实现 enableDesktopLyrics(含覆盖权限请求、AppState/聚焦同步、提示);若干 Orpheus setter 调用改为直接属性赋值(restorePlaybackPositionEnabledloudnessNormalizationEnabledautoplayOnStartEnabled)。
依赖与元数据
package.json
升级依赖 @roitium/expo-orpheus^0.7.2^0.9.2
变更记录
CHANGELOG.md
新增“桌面歌词”与“通知栏增加切换循环模式按钮”条目。

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Headless as Headless 任务 (index.js)
    participant Player as player 工具 (setDesktopLyrics)
    participant Track as trackService
    participant Lyric as lyricService
    participant Orpheus as 原生 Orpheus
    participant UI as 设置面板

    Headless->>Player: onTrackStarted(trackId, reason)
    Note over Player: 记录时间戳并防抖 1s;显示占位歌词
    Player->>Track: fetch track by id
    Track-->>Player: track data
    Player->>Lyric: smartFetchLyrics(track)
    Lyric-->>Player: lyrics
    Player->>Orpheus: setDesktopLyrics(lyrics)
    Orpheus-->>Player: success / error
    alt 错误
        Player->>UI: toastAndLogError
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 分钟

Possibly related PRs

Poem

🐰 我把歌词送到桌面前,
防抖一秒静候天,
先来一句暖心言,
锁定显示不走散,
小兔欢跳庆新篇 ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3
❌ Failed checks (3 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning 标题 'feat: desktop lyrcis' 包含拼写错误('lyrcis' 应为 'lyrics'),且未准确反映主要改动。除桌面歌词外,还涉及依赖更新和设置UI扩展等多项改动。 更正拼写为 'feat: desktop lyrics',使标题准确简洁地反映主要功能的名称。
Out of Scope Changes check ⚠️ Warning PR 包含超出预定范围的改动:升级 @roitium/expo-orpheus 依赖、在设置界面增加新开关、将 Orpheus 方法调用替换为属性赋值等。 将关于 Orpheus API 迁移和设置 UI 扩展的改动分离为独立 PR,当前 PR 应专注于桌面歌词核心功能实现。
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed PR 实现了桌面歌词功能,包括当轨道启动时通过 setDesktopLyrics 获取和设置歌词、UI 切换、状态管理等,与 issue #116 需求相符。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/app/(tabs)/settings/playback.tsx (1)

90-123: 桌面歌词设置 UI 实现良好。

异步操作先执行后更新状态的模式正确,错误处理与现有代码风格一致。

一个小建议:可以考虑在切换操作进行时禁用 Switch 以防止重复点击,但这不是必须的。

src/utils/player.ts (1)

248-249: 考虑使用 ReturnType<typeof setTimeout> 类型以提高兼容性。

在不同 TypeScript 配置下,setTimeout 的返回类型可能是 NodeJS.Timeout 而非 number

🔎 建议的类型改进
-let debouncedSetDesktopLyrics: number | null = null
+let debouncedSetDesktopLyrics: ReturnType<typeof setTimeout> | null = null
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6734576 and 5ed0fe8.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (4)
  • index.js
  • package.json
  • src/app/(tabs)/settings/playback.tsx
  • src/utils/player.ts
🧰 Additional context used
🧬 Code graph analysis (1)
src/utils/player.ts (2)
src/lib/services/trackService.ts (1)
  • trackService (819-819)
src/utils/error-handling.ts (1)
  • toastAndLogError (11-41)
🔇 Additional comments (5)
index.js (1)

3-6: 集成实现正确。

onTrackStarted 事件中调用 setDesktopLyrics,符合 issue #116 中提到的"每首曲目开始时 JS 层发送歌词到 native"的设计方案。setDesktopLyrics 内部已包含防抖和错误处理。

Also applies to: 27-29

src/app/(tabs)/settings/playback.tsx (1)

21-26: 状态初始化实现正确。

Orpheus.isDesktopLyricsShownOrpheus.isDesktopLyricsLocked 初始化状态,与现有的 Orpheus.restorePlaybackPositionEnabled 等初始化模式一致。

src/utils/player.ts (2)

248-284: 防抖实现正确,有效避免快速切歌时的重复请求。

时间戳检查机制 (lines 263, 270, 276) 正确处理了异步操作中的竞态条件,确保只有最新的调用会完成执行。错误处理覆盖了所有关键路径。


251-254: 未使用的 _transitionReason 参数。

参数 _transitionReason 使用下划线前缀表明有意未使用。如果未来计划根据转换原因(如用户主动切歌 vs 自动播放下一首)采取不同逻辑,保留此参数是合理的。否则可以移除。

package.json (1)

38-38: 依赖升级符合预期。

版本从 ^0.7.2 升级到 ^0.8.0 以支持新的桌面歌词 API。所有必需的 API 都已在代码中正确使用:showDesktopLyricshideDesktopLyricssetDesktopLyricsLockedsetDesktopLyricsisDesktopLyricsShownisDesktopLyricsLocked 分别在 playback.tsxplayer.ts 中被调用。

@roitium
Copy link
Copy Markdown
Collaborator Author

roitium commented Jan 7, 2026

坏了,本以为一切顺利,结果遇到了很多问题:

  1. 在后台切歌时无法获取歌词。
  2. 很多很多 corner cases,体验不好。

人麻了,觉得这功能做不了了😇

@roitium roitium marked this pull request as draft January 13, 2026 11:49
@roitium roitium marked this pull request as ready for review January 13, 2026 12:54
@roitium
Copy link
Copy Markdown
Collaborator Author

roitium commented Jan 13, 2026

@CodeRabbit /review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 13, 2026

@roitium 我理解您的挫败感。实现桌面歌词功能确实涉及很多复杂的场景,尤其是后台切歌和各种边界情况的处理。

我会立即开始审查这个 PR 的代码。如果您需要的话,我也可以帮助调查您提到的具体问题:

  • 后台切歌时无法获取歌词的问题
  • 其他遇到的 corner cases

请让我知道是否需要我深入分析这些问题,或者您更倾向于先看看整体的代码审查结果。

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @src/app/(tabs)/settings/playback.tsx:
- Around line 45-49: Currently the code optimistically calls
setIsDesktopLyricsShown(true) and shows a success toast immediately after
Orpheus.requestOverlayPermission(), but requestOverlayPermission() only opens
system settings and cannot confirm permission; remove the optimistic update and
success toast from the onPress handler and only call
Orpheus.requestOverlayPermission(); rely on the existing AppState listener (the
effect that watches app state on return to foreground) to detect the real
overlay permission state, call setIsDesktopLyricsShown(true) there when
permission is actually granted, and show the success toast from that AppState
handler so UI and messages reflect the true permission state.
- Around line 140-152: The onValueChange handler double-updates state:
enableDesktopLyrics() already calls setIsDesktopLyricsShown(true), but the
handler always calls setIsDesktopLyricsShown(!isDesktopLyricsShown) afterwards,
reverting the change when enabling; fix by only updating local state when the
called function does not already update it — specifically, in onValueChange (the
anonymous async handler) call Orpheus.hideDesktopLyrics() then
setIsDesktopLyricsShown(false) on success, but when calling
enableDesktopLyrics() do NOT call setIsDesktopLyricsShown again (or rely on
enableDesktopLyrics to return success and only set state if it returns false);
keep the try/catch and toastAndLogError as-is and remove the unconditional
setIsDesktopLyricsShown(!isDesktopLyricsShown).
🧹 Nitpick comments (2)
src/utils/player.ts (1)

248-249: setTimeout 返回类型在不同环境下不一致。

在 React Native 环境中,setTimeout 可能返回 NodeJS.Timeout 而非 number。建议使用 ReturnType<typeof setTimeout> 来确保类型安全。

♻️ 建议修复
-let debouncedSetDesktopLyrics: number | null = null
-let lastSetDesktopLyricsTimestamp: number | null = null
+let debouncedSetDesktopLyrics: ReturnType<typeof setTimeout> | null = null
+let lastSetDesktopLyricsTimestamp: number | null = null
src/app/(tabs)/settings/playback.tsx (1)

61-74: 状态同步机制良好,但 useFocusEffect 缺少依赖处理。

AppState 监听器能在应用从后台返回时同步状态。但 useFocusEffect 的回调建议包装在 useCallback 中以避免不必要的重复执行,并确保正确的清理行为。

♻️ 建议改进
+import { useCallback } from 'react'

-useFocusEffect(() => {
-  setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
-  setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
-})
+useFocusEffect(
+  useCallback(() => {
+    setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
+    setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
+  }, [])
+)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9067d3a and 9a747a1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • CHANGELOG.md
  • index.js
  • package.json
  • src/app/(tabs)/settings/playback.tsx
  • src/utils/player.ts
✅ Files skipped from review due to trivial changes (1)
  • CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • package.json
🧰 Additional context used
🧬 Code graph analysis (2)
src/app/(tabs)/settings/playback.tsx (2)
src/components/modals/AlertModal.tsx (1)
  • alert (72-85)
src/types/core/appStore.ts (1)
  • AppState (29-29)
src/utils/player.ts (2)
src/lib/services/trackService.ts (1)
  • trackService (819-819)
src/utils/error-handling.ts (1)
  • toastAndLogError (11-41)
🔇 Additional comments (3)
src/utils/player.ts (1)

251-289: 桌面歌词设置逻辑实现良好,使用时间戳进行竞态保护。

  • 防抖机制正确地在每次调用时清除旧定时器
  • 时间戳检查可有效防止过期请求覆盖新数据
  • 错误处理使用 toastAndLogError 保持一致性

建议:当前设置空歌词(line 265)后获取数据若失败,桌面歌词将保持为空。可考虑在错误场景下显示一条提示信息给用户,或保留之前的歌词。

index.js (1)

27-31: Headless task 实现正确。

通过 registerOrpheusHeadlessTask 注册后台任务来处理 onTrackStarted 事件是正确的做法,可以在应用后台运行时设置桌面歌词。

src/app/(tabs)/settings/playback.tsx (1)

155-169: 桌面歌词锁定开关实现正确。

锁定功能的状态同步逻辑清晰,与 Orpheus 原生层的交互正确。

Comment thread src/app/(tabs)/settings/playback.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
src/app/(tabs)/settings/playback.tsx (2)

38-52: 权限请求流程可优化

用户点击"去设置"后,requestOverlayPermission() 会跳转到系统设置页面,此时无法确定用户是否真的授予了权限。当前在 onPress 中直接设置 setIsDesktopLyricsShown(true) 是乐观的假设。

虽然 AppState 监听器会在用户返回时重新同步状态,但更健壮的做法是在用户返回后再次检查权限并调用 showDesktopLyrics()

♻️ 建议的优化
				{
					text: '去设置',
					onPress: async () => {
						await Orpheus.requestOverlayPermission()
-						setIsDesktopLyricsShown(true)
+						// 状态将由 AppState listener 在用户返回时同步
 					},
 				},

69-72: 建议使用 useCallback 包裹 useFocusEffect 回调

根据 React Navigation 最佳实践,useFocusEffect 的回调应使用 useCallback 包裹,避免每次渲染时重复执行。

♻️ 建议的修改
+import { useCallback, useEffect, useState } from 'react'
-import { useEffect, useState } from 'react'

-	useFocusEffect(() => {
-		setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
-		setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
-	})
+	useFocusEffect(
+		useCallback(() => {
+			setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
+			setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
+		}, []),
+	)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a747a1 and 320207a.

📒 Files selected for processing (1)
  • src/app/(tabs)/settings/playback.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(tabs)/settings/playback.tsx (2)
src/components/modals/AlertModal.tsx (1)
  • alert (72-85)
src/types/core/appStore.ts (1)
  • AppState (29-29)
🔇 Additional comments (5)
src/app/(tabs)/settings/playback.tsx (5)

1-9: LGTM!

导入项与新增的桌面歌词功能一致,无冗余导入。


23-28: LGTM!

状态变量初始化方式与其他播放设置保持一致。


59-67: LGTM!

AppState 监听器正确同步桌面歌词状态,清理逻辑完整。


93-94: LGTM!

属性赋值方式与 @roitium/expo-orpheus 0.9.2 的 API 变更一致。


154-168: LGTM!

锁定开关的实现与其他设置项保持一致,逻辑正确。

Comment thread src/app/(tabs)/settings/playback.tsx
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
src/app/(tabs)/settings/playback.tsx (3)

30-57: 权限授予后未自动启用桌面歌词

用户在系统设置中授予悬浮窗权限后返回应用,桌面歌词不会自动启用,需要再次手动切换开关。结合下方的 AppState 监听器,可以在用户返回时检测权限状态并自动启用。

♻️ 建议的改进方案
 	useEffect(() => {
-		const listener = AppState.addEventListener('change', () => {
+		const listener = AppState.addEventListener('change', async (nextAppState) => {
 			setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
 			setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
+			// 当应用从后台返回前台时,检查是否已获得权限但歌词未显示
+			if (nextAppState === 'active' && !Orpheus.isDesktopLyricsShown) {
+				const hasPermission = await Orpheus.checkOverlayPermission()
+				if (hasPermission) {
+					// 可选:自动启用或提示用户
+				}
+			}
 		})
 		return () => {
 			listener.remove()
 		}
 	}, [])

69-72: useFocusEffect 缺少 useCallback 包装

根据 expo-router 的最佳实践,useFocusEffect 的回调应该用 useCallback 包装,否则会在每次组件渲染时重新执行。

♻️ 建议修改
+import { useCallback, useEffect, useState } from 'react'
-import { useEffect, useState } from 'react'

-	useFocusEffect(() => {
-		setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
-		setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
-	})
+	useFocusEffect(
+		useCallback(() => {
+			setIsDesktopLyricsShown(Orpheus.isDesktopLyricsShown)
+			setIsDesktopLyricsLocked(Orpheus.isDesktopLyricsLocked)
+		}, []),
+	)

153-167: 考虑在桌面歌词未启用时禁用锁定开关

当桌面歌词未显示时,锁定开关的操作没有实际意义,建议禁用该开关以提升用户体验。

♻️ 可选改进
 				<View style={styles.settingRow}>
 					<Text>桌面歌词锁定</Text>
 					<Switch
 						value={isDesktopLyricsLocked}
+						disabled={!isDesktopLyricsShown}
 						onValueChange={() => {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 320207a and 2253867.

📒 Files selected for processing (1)
  • src/app/(tabs)/settings/playback.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
src/app/(tabs)/settings/playback.tsx (2)
src/components/modals/AlertModal.tsx (1)
  • alert (72-85)
src/types/core/appStore.ts (1)
  • AppState (29-29)
🔇 Additional comments (1)
src/app/(tabs)/settings/playback.tsx (1)

93-94: LGTM!

API 从方法调用改为属性赋值的变更一致且正确,错误处理也得到保留。

@roitium roitium merged commit fed5f22 into dev Jan 13, 2026
4 checks passed
@roitium roitium deleted the feat/desktop-lyrics branch January 13, 2026 13:11
@coderabbitai coderabbitai Bot mentioned this pull request Feb 7, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 28, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] 桌面歌词

1 participant