Skip to content

feat(player): 编辑弹幕时暂停播放 - #2706

Closed
JesseLee-CN wants to merge 1 commit into
Predidit:mainfrom
JesseLee-CN:feat/pause-on-danmaku-edit
Closed

JesseLee-CN wants to merge 1 commit into
Predidit:mainfrom
JesseLee-CN:feat/pause-on-danmaku-edit

Conversation

@JesseLee-CN

@JesseLee-CN JesseLee-CN commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

描述

在播放器底部的弹幕输入框(B 站等来源的弹幕都在这里编辑)里打字时,视频仍在继续播放:等弹幕打完,想吐槽的那一幕往往已经过去,只能先手动暂停、发完再手动继续。这个 PR 让聚焦输入框时自动暂停播放、输入框失焦后自动恢复,和 B 站发弹幕逻辑类似,同时参考了 animeko 的 pauseVideoOnEditDanmaku 的行为。

默认值与 B 站客户端一致:桌面端不暂停,移动端暂停(移动端行为已在 Android / 鸿蒙 / iOS 上确认,见 #2708)。设置项位于 设置 → 弹幕 → 弹幕显示 → 编辑弹幕时暂停播放,可随时开关。

开启后的行为:

操作 结果
播放中聚焦输入框 暂停
输入框失焦(回车发送、点画面、点面板其它区域) 恢复播放
先手动暂停,再聚焦输入框 保持暂停;失焦也不会自动播放
发送弹幕 等「发送到」弹窗关闭后再恢复
编辑中切到其它窗口(桌面 inactive) 恢复播放(只有真正进入后台 paused/hidden 才拦)
编辑中应用进入后台 不立即恢复;回到前台自动补上
编辑中弹出提示框(例如定时关闭到点) 保持暂停,不会被自动恢复顶掉
面板被卸载(进入画中画、全屏变化) 归还暂停,不会让视频一直停着
一起看房间内(含正在连接) 完全不介入播放
关闭设置开关 完全不接触播放状态,与改动前一致

实现要点

  • 新增 lib/pages/player/controller/danmaku_edit_pause.dart:记录「这次暂停是否由输入框造成」,因此只撤销自己造成的暂停 —— 用户手动暂停的视频不会被自动播放,开关关闭时完全不接触播放状态;门槛抽成纯函数 shouldPause({enabled, hasSession}),发送期间的延迟恢复由 onSubmitStarted/onSubmitFinished 表达,面板卸载由 onDisposed 归还暂停,这些都能直接单测;
  • lib/pages/player/player_item_panel.dart 把输入框的焦点变化接入该控制器:聚焦 → 暂停,失焦 → 恢复;
  • 编辑期间用 TapRegion.consumeOutsideTaps 把输入框之外的点击从手势竞技场里摘掉:这一次点击只用于退出输入框(失焦即恢复播放),不会再落到视频画面、播放/暂停键或进度条上。否则同一次点击会「先恢复、再切回暂停」,看起来像点了没反应。这里复用项目已有的机制 —— 倍速、超分辨率等菜单用的 KazumiMenuButton → MenuAnchor.consumeOutsideTap 就是同一套语义;
  • 消费范围收窄到「确实由本功能暂停时」(pausedByEdit),并如实说明边界:它消费的是以 pointer-down 起手的整条手势,因此编辑期间进度条拖拽、亮度/音量滑动第一次会被吞掉;无障碍语义点击框架层面拦不住(tap_region.dart 里仍是 TODO),读屏用户仍可能遇到同一次点击先恢复再暂停;
  • 一起看房间内完全不介入播放,判据用 syncplay.hasSession(不是房间名——房间名要等 joinRoom 之后才有值,连接窗口内房间消息已经在驱动本机播放了);改动只作用于本地(pause/play(enableSync: false)),既不暂停/恢复所有人的播放,也不会把本地的编辑暂停广播给房间;
  • 恢复播放带守卫(对齐截图流程 player_item.dart 的既有做法):应用真正进入后台不恢复(桌面切窗口只是 inactive,不算后台)、有一起看会话不恢复、有弹窗或弹层在顶层不恢复(编辑到一半定时关闭弹窗夺焦时,视频必须保持暂停)、暂停的播放器已被替换不恢复、也不越过编辑期间到期的睡眠定时;被拦下的那次恢复不会丢:所有权保留到真正恢复为止,回到前台会自动重试;
  • 面板被卸载(进入画中画、全屏变化、离开播放器)是上述守卫的唯一例外:画中画时 Activity 处于 paused 而播放应继续,因此这里不做后台/路由判断,但仍然尊重一起看会话、睡眠定时与播放器身份;
  • 输入框只用 ListenableBuilder 重算一个布尔量,输入框所在子树不重建,因此编辑期间焦点与输入法连接不受影响;
  • 默认值用已有的 SettingKey.defaultResolver 按平台解析:Windows / macOS / Linux 默认关闭,移动端默认开启;用户手动改过之后以存储值为准;
  • 新设置项同时登记进 SettingsKeys.all,因此「设置 → 弹幕 → 恢复默认弹幕设置」会一并重置它。

关联的 Issues

PR 类型

  • 添加新功能

AI 辅助开发

  • 基本上都由 AI 实现,但是我非常了解 AI 的代码与写完的产物

AI 辅助模型: deepseek-v4.1-flash

提交前检查

  • 我已经填写了 PR 模板中的所有内容
  • 我已经阅读了 贡献指引 ,并遵守指引进行开发
  • 这个 PR 的功能已经经过我的测试,并且我完全理解我(或 AI)做出的改动

附注

  • 如果希望所有平台都默认暂停:删掉 SettingsKeys.pauseOnDanmakuEdit 的 defaultResolver 那一行即可(defaultValue 已经是 true);用户也可以在「设置 → 弹幕 → 弹幕显示」里自行打开。
  • 参考实现:animeko 的 pauseVideoOnEditDanmaku 提供了同样的能力,见 VideoScaffoldConfig.kt。
  • 发送链路只做了不改变行为的抽取(_submitDanmakuText 拆出 _sendDanmakuText,外层加 try/finally),目的是把恢复播放推迟到「发送到」弹窗关闭之后;发送本身的逻辑未改动。
  • 验证:
    • 新增 test/danmaku_edit_pause_test.dart,17 个用例:聚焦暂停、失焦恢复、手动暂停不被覆盖、开关关闭不介入、一起看会话内不介入(含连接窗口)、失焦幂等、重复聚焦不重复暂停、多轮聚焦循环、发送期间延迟恢复且恰好一次、销毁时归还暂停、默认值按平台解析(Windows/macOS/Linux 关闭,Android/iOS 开启),以及用真实 Hive box 验证「写入 → 恢复默认弹幕设置 → 回落平台默认」;
    • flutter test 全量 230 项通过;
    • flutter analyze --no-fatal-infos --fatal-warnings 退出码 0,未引入新的提示;
    • 手动测试覆盖上表行为,以及编辑期间点视频画面 / 播放键 / 进度条、回车发送、反复进出输入框(Linux 桌面);
    • 移动端实机验证:Android 构建在鸿蒙设备的 Android 兼容层中安装运行,播放番剧后聚焦弹幕输入框确实暂停、失焦恢复、设置开关默认开启且可关闭,编辑期间点画面/播放键不会出现「恢复又暂停」。

///
/// 想让所有平台都默认暂停:把 `defaultResolver` 这一行删掉即可(`defaultValue`
/// 就是 `true`);用户也可以在「设置 → 弹幕 → 弹幕显示」里自行打开。
static final pauseOnDanmakuEdit = SettingKey<bool>(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: pauseOnDanmakuEdit is not registered in SettingsKeys.all

SettingsKeys.byGroup() iterates all, and GStorage.resetDanmakuSettings() uses byGroup(SettingGroup.danmaku) to choose which keys to clear. This key is missing from the all list (around line 595, next to danmakuEnhance), so "恢复默认弹幕设置" will not reset it. A user who toggled it keeps the old value after a reset, even though the dialog says display settings go back to defaults. Add pauseOnDanmakuEdit, to all.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Comment thread lib/pages/player/player_item_panel.dart Outdated
);
switch (action) {
case DanmakuEditPauseAction.pause:
unawaited(playerController.pause());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: In a SyncPlay session, the auto pause/resume is sent to the whole room

playerController.pause() and play() default to enableSync: true. When syncplay.hasSession is true they call requestSyncPlaySync(). So when one participant focuses the composer, for example to send a chat-room danmaku (a supported destination), playback pauses for everyone in the room. On blur, play() resumes for everyone. It also overrides a pause another participant made while this user was typing. The option is on by default on mobile, so typing a chat message in an 一起看 room would interrupt all peers. You could skip the auto-pause when playerController.syncplay.hasSession is true, or pass enableSync: false and handle the local desync.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 1
Issue Details (click to expand)

WARNING

File Line Issue
lib/pages/player/player_item_panel.dart 144 In a SyncPlay session, the next server State message undoes the local pause(enableSync: false): the listener calls play(enableSync: false) and shows a misleading "开始了播放" toast. On blur, the composer can also resume this client while the room is paused

SUGGESTION

File Line Issue
lib/pages/player/player_item_panel.dart 146 _submitDanmakuText unfocuses before it shows the destination sheet, so playback resumes while the user is still picking a destination
Files Reviewed (5 files)
  • lib/pages/player/controller/danmaku_edit_pause.dart - 0 issues
  • lib/pages/player/player_item_panel.dart - 2 issues
  • lib/pages/settings/danmaku/danmaku_settings.dart - 0 issues
  • lib/services/storage/settings_keys.dart - 0 issues
  • test/danmaku_edit_pause_test.dart - 0 issues

Note: The incremental diff from 204a6bc could not be computed because history was rewritten, so this run reviewed the full PR diff instead. Both previous findings are fixed. pauseOnDanmakuEdit is now in SettingsKeys.all, and the room-wide pause/resume is replaced with enableSync: false.

Fix these issues in Kilo Cloud

Previous Review Summary (commit 204a6bc)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 204a6bc)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
lib/services/storage/settings_keys.dart 116 pauseOnDanmakuEdit is missing from SettingsKeys.all, so "恢复默认弹幕设置" (byGroup(SettingGroup.danmaku)) does not reset it
lib/pages/player/player_item_panel.dart 142 The auto pause/resume uses the default enableSync: true. In a SyncPlay session, focusing the composer pauses and resumes playback for the whole room, and can override a peer's pause
Files Reviewed (5 files)
  • lib/pages/player/controller/danmaku_edit_pause.dart - 0 issues
  • lib/pages/player/player_item_panel.dart - 1 issue
  • lib/pages/settings/danmaku/danmaku_settings.dart - 0 issues
  • lib/services/storage/settings_keys.dart - 1 issue
  • test/danmaku_edit_pause_test.dart - 0 issues

Fix these issues in Kilo Cloud


Reviewed by claude-opus-5.5 · Input: 296 · Output: 10K · Cached: 825.1K

## 问题

在播放器底部的弹幕输入框里打字时视频仍在播放,想发的弹幕对应的画面往往已经过去,
只能先手动暂停、发完再手动继续。这个行为与 B 站客户端的发弹幕逻辑类似,
同时参考了 animeko 的 pauseVideoOnEditDanmaku。

## 修改

- 新增 DanmakuEditPauseController:聚焦输入框时暂停,失焦时恢复;
  只撤销自己造成的暂停,用户手动暂停的视频不会被自动播放,开关关闭时完全不接触播放;
- 面板把 textFieldFocus 的焦点变化接入该控制器,并且只影响本地播放
  (pause/play(enableSync: false)):一起看房间内写弹幕不会暂停或恢复所有人的播放,
  也不会覆盖同伴的暂停;
- 编辑期间用 TapRegion.consumeOutsideTaps 把输入框之外的点击从手势竞技场里摘掉:
  这一次点击只用于退出输入框(失焦即恢复播放),不会再落到视频画面、播放/暂停键
  或进度条上。否则同一次点击会「先恢复、再切回暂停」,看起来像点了没反应。
  这里复用的是项目已有的框架机制:倍速、超分辨率等菜单用的
  KazumiMenuButton → MenuAnchor.consumeOutsideTap 就是同一套语义
  (TapRegion.consumeOutsideTaps:把该指针下的其它手势识别器全部拒绝);
  只在开关开启且正在编辑时消费,因此关闭开关后行为与改动前完全一致;
- 默认值与 B 站客户端一致:桌面端(Windows/macOS/Linux)不暂停、移动端暂停。
  用已有的 SettingKey.defaultResolver 按平台解析,用户手动设置后以存储值为准;
  想让所有平台都默认暂停,删掉 defaultResolver 一行即可(defaultValue 已是 true);
- 新增设置项 SettingsKeys.pauseOnDanmakuEdit,位置在「设置 → 弹幕 → 弹幕显示」,
  并登记进 SettingsKeys.all,因此「恢复默认弹幕设置」会一并重置它。

## 验证

- 新增 9 个用例(test/danmaku_edit_pause_test.dart):聚焦即暂停并在失焦后恢复、
  手动暂停不被覆盖、开关关闭时不介入、失焦幂等、重复聚焦不重复暂停、多轮聚焦循环、
  默认值按平台解析(桌面关闭 / 移动开启)、设置项会被「恢复默认弹幕设置」重置;
- flutter test:全量 222 项通过;
- flutter analyze --no-fatal-infos --fatal-warnings:退出码 0,且改动文件没有任何
  新增提示(仍为改动前既有的 17 条 info)。
@JesseLee-CN
JesseLee-CN force-pushed the feat/pause-on-danmaku-edit branch from 204a6bc to 1e90471 Compare October 15, 2026 14:38
);
switch (action) {
case DanmakuEditPauseAction.pause:
unawaited(playerController.pause(enableSync: false));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

WARNING: In a SyncPlay session the local auto-pause is undone within about a second, and a misleading toast appears

pause(enableSync: false) pauses only the local player. The room on the server stays playing. On the next server State message, SyncplayClient (syncplay_client.dart ~L642-661) sets _isPaused back to the room state and emits onPositionChangedMessage. The listener in player_syncplay_controller.dart ~L219-233 sees message['paused'] != !playing(), so it calls play(enableSync: false) and shows "SyncPlay: 开始了播放". The composer therefore pauses, then playback resumes almost at once while the user is still typing. If a peer pauses the room during editing, the blur-time play(enableSync: false) also desyncs this client from the paused room until the next correction.

The option is on by default on mobile, so this happens whenever someone types in an 一起看 room. The simplest fix is to skip the auto pause/resume when playerController.syncplay.hasSession is true, for example by passing enabled: setting && !playerController.syncplay.hasSession.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

case DanmakuEditPauseAction.pause:
unawaited(playerController.pause(enableSync: false));
case DanmakuEditPauseAction.resume:
unawaited(playerController.play(enableSync: false));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

SUGGESTION: Sending a danmaku resumes playback before the destination is chosen

_submitDanmakuText calls textFieldFocus.unfocus() first (L153), and only then awaits showDanmakuDestinationSheet. The blur fires this resume branch right away, so the video starts playing while the destination sheet is open. The danmaku is echoed only after the user picks a destination, by which time the moment they paused for may have passed. That works against the purpose of the feature. You could defer the resume until _submitDanmakuText finishes (or is cancelled), for example by suppressing the blur-triggered resume during submission and calling it explicitly at the end.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@JesseLee-CN

Copy link
Copy Markdown
Contributor Author

关闭此 PR。

我先核对了 #933:同样的需求在 2025-04 被关闭,理由是「我个人倾向于不暂停,bilibili 的设计也是这样的」。这份 PR 的默认值论证恰好建立在对 B 站行为的相反理解上,属于不在计划内的功能,因此不再占用审查时间。

功能本身已按项目惯例实现(设置项默认关闭的形态可直接启用;Kilo Code Review、analyze、测试与 6 个平台构建均通过),代码保留在本地分支,若将来方向有变化可以复用。

感谢审查。

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