Skip to content

移除中间态渲染 ( LyricManager.ts ) - 在 handleOnlineLyric 方法的 adoptQQMusic 流程中,移除了提前调用的 setFinalLyric 。 - 原逻辑 :获取到 QQ 音乐歌词 -> 排除处理 -> 立即显示 -> 返回上层 -> 上层再次处理 -> 再次显示 (导致抖动)。 - 新逻辑 :获取到 QQ 音乐歌词 -> 填充数据 -> 返回上层 -> 统一排除处理 -> 最终显示 (仅一次渲染)。#841

Closed
kazukokawagawa wants to merge 14 commits intodevfrom
dev-fix-04

Conversation

@kazukokawagawa
Copy link
Collaborator

No description provided.

- 重构时间解析函数,使用纯数学运算替代字符串操作以提高性能
- 移除全局正则表达式,改为在函数内局部使用避免状态污染
- 在解析过程中直接计算结束时间,消除二次遍历
- 优化歌词对齐算法,使用双指针实现 O(N) 复杂度
- 改进 QRC 格式解析,提前编译正则并优化 XML 内容提取
- 统一默认单词持续时间处理逻辑
- 在 DownloadManager 中添加 removeDownload 方法,支持从队列和 store 中移除任务
- 修复下载页面中播放全部按钮的逻辑,仅对已下载歌曲生效
- 优化下载中页面的封面显示逻辑,统一使用 getCover 方法
- 将 handleRemoveDownload 的参数类型扩展为 number | string 以支持多种 ID 类型
- 使用字符串补齐替代浮点数计算,避免时间解析的精度误差
- 将 alignLyrics 改为纯函数,不再修改输入数组
- 引入 XmlNode 辅助类重构 TTML 生成逻辑,提高可读性
- 优化 QRC 内容提取的正则表达式,增强健壮性
将 QRC 歌词解析中的 LyricContent 提取逻辑重构为策略模式,根据运行环境自动选择 DOM 或正则解析器,提高代码可维护性和跨环境兼容性。
- 导出 `getThemeFromColor` 工具函数用于颜色主题计算
- 新增 IPC 通信通道传递主题色数据
- 在任务栏歌词组件中根据主题色动态调整文本颜色
- 监听主题相关设置变化并实时更新任务栏歌词颜色
在设置中添加“任务栏歌词跟随主题色”选项,允许用户控制任务栏歌词颜色是否与应用主题同步。当关闭时,歌词将恢复为默认颜色。
使用Data URL替代Blob URL存储音乐封面,避免跨窗口或跨进程引用失效导致的封面闪烁和消失问题。移除对BlobURLManager的依赖,简化封面处理逻辑。
在任务栏歌词组件中添加设置选项,允许用户控制是否显示翻译歌词和罗马音歌词。通过 IPC 将设置同步到主进程,确保前后端状态一致。
当读取操作被中止时,未处理onabort事件可能导致Promise永远挂起。
添加onabort事件处理器来拒绝Promise,确保异常情况能被正确处理。
添加 Windows 和 macOS/Linux 系统的条件步骤,用于在 .env 文件不存在时从 .env.example 复制。这确保了 nightly 构建在不同操作系统上都能正确加载环境配置。
- 在 handleOnlineLyric 方法的 adoptQQMusic 流程中,移除了提前调用的 setFinalLyric 。
- 原逻辑 :获取到 QQ 音乐歌词 -> 排除处理 -> 立即显示 -> 返回上层 -> 上层再次处理 -> 再次显示 (导致抖动)。
- 新逻辑 :获取到 QQ 音乐歌词 -> 填充数据 -> 返回上层 -> 统一排除处理 -> 最终显示 (仅一次渲染)。
新增配置选项允许在当前歌曲播放时提前下载并缓存下一首歌曲的歌词,以提升歌词切换的流畅度。重构了歌词获取逻辑,将核心的在线歌词获取方法提取为独立函数,便于预加载功能复用。
- 在任务栏歌词组件中新增动态封面显示功能
- 添加动态封面获取逻辑,根据设置自动获取并更新
- 扩展 IPC 接口以支持动态封面数据传输
- 在音乐存储中增加动态封面状态管理
@kazukokawagawa kazukokawagawa marked this pull request as ready for review February 7, 2026 14:04
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @kazukokawagawa, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求致力于提升歌词显示与管理系统,引入了多项新功能并优化了用户体验和性能。它通过集中化最终歌词渲染来解决歌词闪烁问题,新增了预加载下一首歌曲歌词的设置,并集成了动态封面支持。此外,任务栏歌词的自定义选项也得到了扩展,包括主题色同步以及对翻译和音译歌词显示更精细的控制。歌词解析和处理逻辑的重大重构也使得系统更加健壮和高效。

Highlights

  • 歌词渲染优化: 通过移除 handleOnlineLyric 方法中 adoptQQMusic 流程的提前 setFinalLyric 调用,解决了歌词显示时的抖动问题,确保歌词仅在最终处理完成后进行一次渲染。
  • 新增歌词预加载功能: 引入了“预加载下一首歌词”的设置选项,允许应用在当前歌曲播放时提前下载并缓存下一首歌曲的歌词,提升用户体验。
  • 支持歌曲动态封面: 实现了歌曲动态封面的显示功能,并在任务栏歌词窗口中也增加了对动态封面的支持,使界面更加生动。
  • 任务栏歌词个性化设置: 任务栏歌词新增了“跟随主题色”的选项,并支持根据用户设置控制翻译和音译歌词的显示,提供了更丰富的自定义体验。
  • 歌词解析逻辑重构: 对逐字 LRC、增强型 LRC 和 QRC 歌词的解析逻辑进行了优化和重构,提高了解析效率和准确性,并改进了歌词对齐算法。
Changelog
  • electron/main/ipc/ipc-taskbar.ts
    • 新增 IPC 处理器 taskbar:set-theme-color,用于接收并转发主题色数据到任务栏歌词窗口。
  • src/components/Setting/config/lyric.ts
    • 新增“预加载下一首歌词”的设置选项,允许用户控制是否提前下载下一首歌曲的歌词。
    • 新增“任务栏歌词跟随主题色”的设置选项,允许任务栏歌词颜色与应用主题同步。
  • src/composables/useInit.ts
    • 修改了 taskbar:broadcast-settings 的监听器,使其包含 settingStore.showTransettingStore.showRoma
    • taskbar:broadcast-settings 的负载中添加了 showTranshowRoma 属性。
  • src/core/player/LyricManager.ts
    • 将在线歌词获取的核心逻辑提取到新的私有方法 fetchOnlineLyricData 中。
    • 移除了 adoptQQMusicadoptLRC 方法中提前调用的 setFinalLyric,以防止歌词渲染抖动,确保统一渲染。
    • 新增私有方法 preloadNextLyric,用于根据设置预加载播放列表中下一首歌曲的歌词。
    • handleLyrichandleOnlineLyric 方法中调用 preloadNextLyric,实现歌词预加载。
  • src/core/player/PlayerController.ts
    • 移除了 useBlobURLManager 及其相关逻辑,将本地歌曲封面处理方式从 Blob URL 更改为 Data URL,以解决跨窗口/进程引用失效导致的封面闪烁问题。
  • src/core/player/PlayerIpc.ts
    • TaskbarMetadataPayload 接口中新增 dynamicCover?: string 属性。
    • 新增导出函数 sendTaskbarThemeColor,用于向任务栏发送主题色数据。
  • src/core/resource/DownloadManager.ts
    • 新增公共方法 removeDownload(id: number | string),用于从活动下载、队列和数据存储中移除下载任务。
  • src/layout/AppLayout.vue
    • 引入 songDynamicCoverisEmpty 模块。
    • 新增 watch 效果,根据歌曲 ID 和动态封面设置,异步获取并更新 musicStore.dynamicCover
  • src/stores/music.ts
    • MusicState 接口中新增 dynamicCover: string 属性。
    • resetMusicData 方法中重置 dynamicCover
    • 新增 setDynamicCover(url: string) action,用于更新动态封面并向任务栏发送包含动态封面的元数据。
  • src/stores/setting.ts
    • SettingState 接口中新增 taskbarLyricUseThemeColor: boolean 属性,默认值为 true
    • SettingState 接口中新增 preloadNextLyric: boolean 属性,默认值为 false
    • 更新了歌词优先级相关的注释。
  • src/utils/color.ts
    • getThemeFromColor 函数导出,使其可在其他模块中使用。
  • src/utils/initIpc.ts
    • 引入 watch, themeColor, getThemeFromColor, rgbToHexplayerIpc 模块。
    • 实现了根据全局主题设置、封面主题和 taskbarLyricUseThemeColor 设置计算并发送任务栏主题色的逻辑。
    • 在初始加载和主题相关设置变化时调用 sendTaskbarTheme
    • sendTaskbarMetadata 调用中包含了 dynamicCover
    • taskbar:broadcast-settings 负载中添加了 showTranshowRoma 属性。
  • src/utils/lyric/lyricParser.ts
    • 引入 cloneDeepextractLyricContent 模块。
    • 移除了 fixLineEndTimes 函数。
    • 优化了 parseWordByWordLrcparseEnhancedLrc 函数,在单次遍历中计算字和行的结束时间,提高了性能。
    • 调整了 parseTimeToMs 函数,使用字符串填充处理毫秒部分,避免浮点数计算误差。
    • 更新了 alignLyrics 函数,采用双指针算法实现 O(N) 复杂度,并返回歌词的深拷贝副本。
    • 重构了 parseQRCLyric 函数,引入了 parseQRCContent 辅助函数和 extractLyricContent
    • 新增 XmlNode 辅助类,并使用其重构了 lyricLinesToTTML 函数,以更清晰地生成 TTML XML。
  • src/utils/lyric/qrc-parser.ts
    • 新增文件,引入了 QRC XML 内容提取的策略模式,根据环境(浏览器/Node)选择使用 DOMParser 或正则表达式进行解析。
  • src/views/Download/downloading.vue
    • 更新了封面显示逻辑,使用 getCover 辅助函数处理 coverSize.scover
    • 修改了 handleRemoveDownload 函数,使其接受 id: number | string 类型的参数。
  • src/views/Download/layout.vue
    • 修改了“全部播放”按钮的行为,使其调用 handlePlayAll 函数。
    • handlePlayAll 函数现在仅在当前标签页为“已下载”时,才使用 listData.value 更新播放列表,并移除了 currentListData 计算属性。
  • src/views/TaskbarLyric/index.vue
    • cover-wrapper 中增加了对 state.dynamicCover 的检查,如果存在则渲染 <video> 元素,否则渲染 <img>
    • state 响应式对象中新增 dynamicCover 属性。
    • 更新了 taskbar:update-metadata IPC 处理器,使其接收并更新 state.dynamicCover
    • 新增 rootStyle 计算属性,用于处理动态样式,包括主题色。
    • state 中新增 themeColor 属性,用于存储主题色数据。
    • 新增 taskbar:update-theme-color IPC 处理器,用于更新 state.themeColor
    • 更新了 taskbar:update-settings 处理器,使其接收并应用 showTranshowRoma 设置。
    • 修改了 displayItems 计算属性,根据 settingStore.showTransettingStore.showRoma 的值有条件地显示翻译/音译歌词。
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/nightly.yml
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

本次 PR 包含了一系列重要的优化和新功能,整体质量非常高。

核心亮点在于 LyricManager.ts 的重构,通过将歌词获取逻辑与状态更新分离,有效解决了歌词渲染抖动的问题,代码结构也变得更加清晰。lyricParser.ts 中的性能优化同样值得称赞,特别是 alignLyrics 函数从 O(N*M) 优化到 O(N+M),以及在多个解析函数中采用单次遍历(single-pass)的策略,显著提升了效率和代码健壮性。

新增的歌词预加载、动态封面、任务栏歌词主题色等功能,实现方式合理,考虑周全。将本地歌曲封面的处理从 Blob URL 切换到 Data URL 是一个很棒的修复,解决了跨窗口引用失效的问题。

总体而言,这是一次出色的代码提交,极大地提升了歌词系统的性能、稳定性和功能性。我只在 alignLyrics 函数中发现一个可以改进的地方,以处理匹配的准确性问题,具体见我的评论。

Comment on lines +283 to +298
const line = result[i];
const other = otherLyrics[j];
const diff = line.startTime - other.startTime;

if (Math.abs(diff) <= ALIGN_TOLERANCE_MS) {
// 匹配成功
line[key] = other.words.map((word) => word.word).join("");
i++;
j++;
} else if (diff < 0) {
// 当前歌词时间较早,移动当前指针
i++;
} else {
// 目标歌词时间较早,移动目标指针
j++;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

当前的双指针算法虽然高效,但在某些边缘情况下可能会选择一个并非最优的匹配。例如,当一个时间轴上的点 A 同时与另一个时间轴上的点 BC 都满足容差范围时,当前实现会贪心地选择它遇到的第一个匹配项(比如 B),即使 C 在时间上与 A 更接近。

为了提高匹配的准确性,同时保持 O(N+M) 的时间复杂度,我建议对算法进行微调:当找到一个潜在匹配时,继续向前查看(在 otherLyrics 中),只要后续行的匹配度更高(时间差更小),就继续移动 j 指针。直到找到局部最优匹配后,再进行匹配并同时推进 ij

这样可以确保每一行歌词都能与其在时间上最接近的翻译/音译行进行匹配,从而提高对齐的精确度。

    const line = result[i];
    const other = otherLyrics[j];
    const diff = line.startTime - other.startTime;

    if (diff > ALIGN_TOLERANCE_MS) {
      // other is too early, advance j
      j++;
    } else if (diff < -ALIGN_TOLERANCE_MS) {
      // line is too early, advance i
      i++;
    } else {
      // Found a potential match. Let's see if the next `other` is an even better match.
      if (j + 1 < otherLyrics.length) {
        const nextOther = otherLyrics[j + 1];
        const nextDiff = line.startTime - nextOther.startTime;
        // If the next item is closer and also within tolerance, let's check it in the next iteration.
        if (Math.abs(nextDiff) < Math.abs(diff)) {
          j++;
          continue;
        }
      }
      // This is the best match we can find for `line` by moving `j` forward.
      line[key] = other.words.map((word) => word.word).join("");
      i++;
      j++;
    }

@kazukokawagawa kazukokawagawa self-assigned this Feb 8, 2026
@imsyy imsyy closed this Feb 8, 2026
@imsyy imsyy deleted the dev-fix-04 branch February 8, 2026 10:06
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