Skip to content

fix: restore subtitle auto follow#215

Merged
mkdir700 merged 3 commits intodevfrom
fix/subtitle-auto-scroll
Oct 14, 2025
Merged

fix: restore subtitle auto follow#215
mkdir700 merged 3 commits intodevfrom
fix/subtitle-auto-scroll

Conversation

@mkdir700
Copy link
Owner

@mkdir700 mkdir700 commented Oct 14, 2025

Summary

  • start auto-return timer when user clicks a subtitle item
  • ensure auto-follow recovers without reloading the player

Testing

  • manual

Summary by CodeRabbit

  • Bug Fixes

    • 优化字幕列表的自动返回:点击字幕项也会启动自动回归计时器,提升浏览后自动回到当前字幕的稳定性与一致性。
    • 改进后台资源下载管理:避免重复并发下载并加强错误后清理,减少竞态和安装冲突,提升下载稳定性。
  • Chores

    • 调整静态检查配置以忽略额外构建/媒体相关文件,减少无关报警。

…nteractions

- Extend auto-return timer initiation to include user click item event
- Ensure consistent behavior when transitioning to user browsing state
- Improve user experience by automatically returning to current subtitle after interactions
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 14, 2025

Walkthrough

在字幕滚动状态机中,进入 USER_BROWSING 时启动自动返回计时器的触发条件从仅监听 USER_MANUAL_SCROLL 扩展为同时监听 USER_CLICK_ITEM;新增 ESLint 忽略模式,将 media-server/** 排除于校验之外;以及在 UV 启动器服务中添加 activeDownloads 集合以防止重复下载,并调整下载控制流以在开始/结束时标记与清理活动下载记录。

Changes

Cohort / File(s) Change Summary
字幕滚动状态机
src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
在进入 USER_BROWSING 时,启动自动返回计时器的触发条件新增 ScrollTrigger.USER_CLICK_ITEM,与原有的 USER_MANUAL_SCROLL 并列生效;其余逻辑、迁移与接口未改动。
ESLint 配置
eslint.config.mjs
media-server/** 添加到 ESLint 忽略列表,令该路径下文件不再参与 lint 检查;无其它功能性更改。
UV 启动器服务
src/main/services/UvBootstrapperService.ts
新增实例字段 private activeDownloads = new Set<string>(),用以跟踪进行中的 UV 下载;在下载流程前后检查并设置/清理该标记以避免并发相同平台架构的重复下载,调整检测已安装/已下载的检查位置,并在下载逻辑中使用 try/finally 保证清理。

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as 用户
  participant L as 字幕列表
  participant SM as 滚动状态机
  participant T as 自动返回计时器

  U->>L: 手动滚动 (USER_MANUAL_SCROLL)
  L->>SM: ScrollTrigger.USER_MANUAL_SCROLL
  SM->>SM: 进入 USER_BROWSING
  SM->>T: 启动计时器

  rect rgba(200,240,255,0.25)
  note over U,L: 新增触发路径
  U->>L: 点击字幕项 (USER_CLICK_ITEM)
  L->>SM: ScrollTrigger.USER_CLICK_ITEM
  SM->>SM: 进入/保持 USER_BROWSING
  SM->>T: 启动/重置计时器
  end

  T-->>SM: 超时
  SM->>L: 自动返回到当前字幕
Loading
sequenceDiagram
  autonumber
  actor Caller as 启动器
  participant UBS as UvBootstrapperService
  participant Reg as 区域探测/镜像解析
  participant DL as 下载器

  Caller->>UBS: 请求安装 UV(platform, arch)
  UBS->>UBS: 生成 key (platform|arch)
  alt key 已在 activeDownloads
    UBS-->>Caller: 跳过/返回(正在下载)
  else 未在 activeDownloads
    UBS->>UBS: activeDownloads.add(key)
    UBS->>UBS: 检查本地已安装/已下载(短路可能)
    UBS->>Reg: 探测区域并获取镜像列表
    Reg-->>UBS: 镜像列表(或错误/回退)
    UBS->>UBS: 再次检查 activeDownloads(防竞态)
    UBS->>DL: 执行下载与安装
    DL-->>UBS: 成功/失败
    UBS->>UBS: activeDownloads.delete(key)
    UBS-->>Caller: 返回结果
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

我是一只兔,点点与滚滚皆入眼,
小爪轻触,计时器便跳起舞盘。
下载有守护,重复不再忙乱,
代码静悄悄,运行更安然。 🐇✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed 标题简洁明确地反映了本次更改的核心目的,即恢复字幕自动跟随功能,与变更内容高度一致且无多余信息。
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/subtitle-auto-scroll

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
Contributor

@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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dadc866 and 85d46d4.

📒 Files selected for processing (1)
  • src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
src/renderer/src/**/*.{ts,tsx,scss,css}

📄 CodeRabbit inference engine (CLAUDE.md)

优先使用 CSS 变量,避免硬编码样式值(颜色等)

Files:

  • src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
src/renderer/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

尺寸与时长等不要硬编码,优先使用 useTheme() 的 token 或集中样式变量(如 motionDurationMid、borderRadiusSM/MD)

Files:

  • src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: 定制 antd 组件样式优先使用 styled-components 包装 styled(Component),避免全局 classNames
项目的图标统一使用 lucide-react,不使用 emoji 作为图标
组件/Hook 顶层必须通过 useStore(selector) 使用 Zustand,禁止在 useMemo/useEffect 内部调用 store Hook
避免使用返回对象的 Zustand 选择器(如 useStore(s => ({ a: s.a, b: s.b })));应使用单字段选择器或配合 shallow 比较器
遵循 React「副作用与状态更新」规范:渲染纯函数、Effect 三分法、幂等更新、稳定引用、严格清理、禁止写回自身依赖、Provider 值 memo、外部状态 selector 稳定等
统一使用 loggerService 记录日志而不是 console
logger 使用示例中第二个参数必须为对象字面量(如 logger.error('msg', { error }))
任何组件或页面都不要写入 media 元素的 currentTime,播放器控制由编排器统一负责
在 styled-components 中:主题相关属性使用 AntD CSS 变量(如 var(--ant-color-bg-elevated));
在 styled-components 中:设计系统常量(尺寸、动画、层级、字体、毛玻璃等)使用 JS 常量(如 SPACING、BORDER_RADIUS、Z_INDEX、FONT_SIZES 等)

Files:

  • src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
**/player/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Player 页面:统一在组件顶层使用 Zustand selector,禁止在 useMemo/useEffect 内调用 store Hook;useSubtitleEngine 通过参数传入 subtitles 等防御处理

Files:

  • src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (windows-latest, 20)
  • GitHub Check: test (ubuntu-latest, 20)
  • GitHub Check: test (macos-latest, 20)

Comment on lines +240 to +243
if (
trigger === ScrollTrigger.USER_MANUAL_SCROLL ||
trigger === ScrollTrigger.USER_CLICK_ITEM
) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

修复正确,但建议补充用户浏览时点击重置计时器的逻辑

此修复已正确解决了从 LOCKED_TO_CURRENT 或 TRANSITIONING 状态点击字幕项时启动自动返回计时器的问题。

但发现一个潜在的不一致性:当用户已经处于 USER_BROWSING 状态时再次点击字幕项,计时器不会重置。对比第 86 行的手动滚动行为,在 USER_BROWSING 状态下手动滚动会触发自转换并重置计时器。为保持交互一致性,建议在 USER_BROWSING 状态转换表中也添加 USER_CLICK_ITEM 的自转换。

请运行以下脚本验证当前状态机的转换逻辑,并确认是否需要支持浏览时点击重置计时器:

如果需要支持该场景,可应用以下修改:

  [SubtitleScrollState.USER_BROWSING]: {
    [ScrollTrigger.AUTO_TIMEOUT]: SubtitleScrollState.TRANSITIONING,
    [ScrollTrigger.USER_CLICK_RETURN]: SubtitleScrollState.LOCKED_TO_CURRENT,
-    [ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING // 重置定时器
+    [ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING, // 重置定时器
+    [ScrollTrigger.USER_CLICK_ITEM]: SubtitleScrollState.USER_BROWSING // 重置定时器
  },

🏁 Script executed:

#!/bin/bash
# 描述:检查 USER_BROWSING 状态的所有转换定义

# 查找 USER_BROWSING 状态的转换配置
rg -A 5 '\[SubtitleScrollState\.USER_BROWSING\]:' src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts

Length of output: 316


在 USER_BROWSING 状态中点击字幕项应重置自动返回定时器

当前状态机配置只对 USER_MANUAL_SCROLL 触发自转换以重置计时器,却未对 USER_CLICK_ITEM 做同样处理。请在配置中添加:

  [SubtitleScrollState.USER_BROWSING]: {
     [ScrollTrigger.AUTO_TIMEOUT]: SubtitleScrollState.TRANSITIONING,
     [ScrollTrigger.USER_CLICK_RETURN]: SubtitleScrollState.LOCKED_TO_CURRENT,
     [ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING,
+    [ScrollTrigger.USER_CLICK_ITEM]: SubtitleScrollState.USER_BROWSING
  },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
trigger === ScrollTrigger.USER_MANUAL_SCROLL ||
trigger === ScrollTrigger.USER_CLICK_ITEM
) {
[SubtitleScrollState.USER_BROWSING]: {
[ScrollTrigger.AUTO_TIMEOUT]: SubtitleScrollState.TRANSITIONING,
[ScrollTrigger.USER_CLICK_RETURN]: SubtitleScrollState.LOCKED_TO_CURRENT,
[ScrollTrigger.USER_MANUAL_SCROLL]: SubtitleScrollState.USER_BROWSING,
[ScrollTrigger.USER_CLICK_ITEM]: SubtitleScrollState.USER_BROWSING
},
🤖 Prompt for AI Agents
In src/renderer/src/pages/player/hooks/useSubtitleScrollStateMachine.ts around
lines 240 to 243, the USER_BROWSING state only treats
ScrollTrigger.USER_MANUAL_SCROLL as a self-transition to reset the auto-return
timer; add ScrollTrigger.USER_CLICK_ITEM to the same transition so clicks on
subtitle items also reset the timer. Update the transition condition or trigger
list to include USER_CLICK_ITEM alongside USER_MANUAL_SCROLL so both actions
perform the same self-transition and timer-reset behavior.

…ency control

- Introduce `activeDownloads` set to track ongoing downloads and prevent concurrent downloads of the same version.
- Refactor download logic to include defensive checks ensuring no concurrent downloads occur.
- Improve error handling during region detection to default to standard UV download sources if detection fails.
- Ensure proper cleanup of `activeDownloads` set after download completion or failure.
- Enhance logging to provide clearer insights into download initiation and status.
Copy link
Contributor

@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

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 344e5ed and c378079.

📒 Files selected for processing (1)
  • src/main/services/UvBootstrapperService.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: 定制 antd 组件样式优先使用 styled-components 包装 styled(Component),避免全局 classNames
项目的图标统一使用 lucide-react,不使用 emoji 作为图标
组件/Hook 顶层必须通过 useStore(selector) 使用 Zustand,禁止在 useMemo/useEffect 内部调用 store Hook
避免使用返回对象的 Zustand 选择器(如 useStore(s => ({ a: s.a, b: s.b })));应使用单字段选择器或配合 shallow 比较器
遵循 React「副作用与状态更新」规范:渲染纯函数、Effect 三分法、幂等更新、稳定引用、严格清理、禁止写回自身依赖、Provider 值 memo、外部状态 selector 稳定等
统一使用 loggerService 记录日志而不是 console
logger 使用示例中第二个参数必须为对象字面量(如 logger.error('msg', { error }))
任何组件或页面都不要写入 media 元素的 currentTime,播放器控制由编排器统一负责
在 styled-components 中:主题相关属性使用 AntD CSS 变量(如 var(--ant-color-bg-elevated));
在 styled-components 中:设计系统常量(尺寸、动画、层级、字体、毛玻璃等)使用 JS 常量(如 SPACING、BORDER_RADIUS、Z_INDEX、FONT_SIZES 等)

Files:

  • src/main/services/UvBootstrapperService.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: test (macos-latest, 20)
  • GitHub Check: test (ubuntu-latest, 20)
  • GitHub Check: test (windows-latest, 20)
🔇 Additional comments (1)
src/main/services/UvBootstrapperService.ts (1)

196-196: 良好的设计:新增下载去重机制。

使用 activeDownloads 集合跟踪进行中的下载操作是防止重复下载的有效方法。这个字段与 try/finally 块配合使用,确保了即使出现异常也能正确清理状态。

Comment on lines +516 to +564
if (this.activeDownloads.has(key)) {
logger.warn('uv 正在下载中', { platform, arch })
return false
}

if (this.regionDetectionPromise) {
try {
await this.regionDetectionPromise
} catch (error) {
logger.warn('地区检测失败,将使用默认 UV 下载源', {
error: error instanceof Error ? error.message : String(error)
})
this.activeDownloads.add(key)
try {
// 仅当请求的平台与当前进程一致时,才额外使用缓存的系统检测结果
if (platform === process.platform && arch === process.arch) {
const installation = await this.checkUvInstallation()
if (installation.exists && installation.isDownloaded) {
logger.info('uv 已存在,跳过下载', { platform, arch, path: installation.path })
return true
}
}
}

const version = this.getUvVersion(platform, arch)
if (!version) {
logger.error('不支持的平台', { platform, arch })
return false
}
if (this.regionDetectionPromise) {
try {
await this.regionDetectionPromise
} catch (error) {
logger.warn('地区检测失败,将使用默认 UV 下载源', {
error: error instanceof Error ? error.message : String(error)
})
}
}

logger.info('开始下载 uv', {
platform,
arch,
version: version.version,
url: version.url
})
const version = this.getUvVersion(platform, arch)
if (!version) {
logger.error('不支持的平台', { platform, arch })
return false
}

// 再次确认未有并发下载(防御性检查)
if (this.downloadProgress.has(key) || this.downloadController.has(key)) {
logger.warn('uv 正在下载中', { platform, arch })
return false
}

return await this.performDownload(platform, arch, version, onProgress)
logger.info('开始下载 uv', {
platform,
arch,
version: version.version,
url: version.url
})

return await this.performDownload(platform, arch, version, onProgress)
} finally {
this.activeDownloads.delete(key)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

实现正确:有效防止了重复下载。

整体重构达成了防止重复下载的目标:

  1. 下载去重:通过 activeDownloads 集合有效防止并发下载(第 516-519 行)
  2. 状态清理:try/finally 块确保即使出现异常也能清理 activeDownloads(第 562-564 行)
  3. 线程安全:在 JavaScript 事件循环模型下,检查-添加模式(第 516、521 行)是安全的

可选优化建议

  1. 简化检查逻辑:第 510-514 行的早期检查与第 525 行 checkUvInstallation() 内部的下载检查存在一定重复。虽然早期检查是性能优化(避免进入 try 块和添加到 activeDownloads),但可以考虑统一到一处以提高代码可维护性。

  2. 移除防御性检查:第 548-552 行的 downloadProgress/downloadController 检查在正常流程中永远不会触发(因为第 516 行的 activeDownloads 检查已确保唯一性)。如果对 activeDownloads 机制有信心,可以考虑移除此检查以简化逻辑。

  3. 优化地区检测时机:第 532-540 行的地区检测可以移到第 521 行之前执行,减少 activeDownloads 的持有时间,提升并发性能。

这些都是次要优化点,当前实现已经足够可靠。

🤖 Prompt for AI Agents
In src/main/services/UvBootstrapperService.ts around lines 516 to 564, the code
works but can be simplified and optimized: (1) Remove the redundant early
platform/arch existence check at lines ~510-514 and consolidate installation
existence check into the try block after adding to activeDownloads so there is a
single canonical check path; (2) Remove the defensive check against
downloadProgress/downloadController at lines ~548-552 since activeDownloads
already guarantees uniqueness; (3) Move the regionDetectionPromise await (lines
~532-540) to before adding the key to activeDownloads so region detection does
not hold the activeDownloads lock; implement these three changes to reduce
duplication and shorten the time the activeDownloads lock is held.

@mkdir700 mkdir700 merged commit adc027e into dev Oct 14, 2025
5 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.

1 participant