Skip to content

Commit 313a830

Browse files
committed
FIX: 字幕遮罩手柄不再因挂载或布局切换自动弹出
- 遮罩组件挂载不再激发手柄:打开视频、窗口跨过宽高比 1 导致的布局重建都不会弹出 - 遮罩组件在数据加载后常驻,由 isEnabled 驱动;只有开关从关到开才保持 2 秒 - 新增鼠标悬浮激发,悬浮判定区域覆盖遮罩及外扩的手柄触控区,移到手柄上不丢失激发态 - 激发态变化记录原因(点按、拖动、悬浮、开关、保持时间结束)到 [VideoSubtitleMask] 日志,便于从日志判断手柄为何出现 - 补充挂载不弹出、开关激发、悬浮进出、悬浮移到手柄保持、激发日志的回归测试 - 文档:docs/troubleshooting.md 7.49,TASKS.md 记录修复与未完成的真机验证
1 parent 5c8876a commit 313a830

5 files changed

Lines changed: 235 additions & 77 deletions

File tree

‎TASKS.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
- 未完成的验证:真机确认收藏 PDF 的中文字体、分页与长词条观感,以及下载 / 分享流程(iOS / Android / macOS)。
3535
- [x] 视频字幕遮罩:视频画面右上角新增遮罩按钮,开启后在画面底部显示半透明磨砂遮罩,用于遮住视频内烧录的原字幕;单指拖动移动、双指捏合等比缩放、拖动四边中点手柄单独调整对应边;点按遮罩进入编辑态(收起播放控件、显示手柄 2 秒),与点按画面显示控件互不重叠。位置按去黑边后的实际画面归一化,窗口缩放 / 全屏切换后仍对准字幕;开关与位置按视频保存在本地数据库表 `video_subtitle_masks`(schema v68),只在用户操作遮罩后才写入,没有记录即为关闭 + 底部居中默认位置;删除视频时级联清除,不跨端同步。随心听、学习任务与句子讲解页共用 `MediaVisualSurface` 单一实现。**完成时间**: 2026-11-15
3636
- 拖动发卡已修复(手势判定阈值,见 troubleshooting 7.46)。已在 Android 真机(竖屏烧录字幕视频)验证开关、拖动、手柄缩放、全屏前后位置保持;未完成的验证:iOS / macOS 的磨砂效果与双指缩放手感、横屏视频全屏。
37+
- 手柄自动弹出已修复(见 troubleshooting 7.49):边框与手柄只在开关打开、点按 / 拖动后保持 2 秒、鼠标悬浮时显示;打开视频与窗口跨越布局断点导致的重建不再弹出。日志标签 `[VideoSubtitleMask]` 记录挂载与激发态变化及原因。**完成时间**: 2026-11-15。未完成的验证:真机确认桌面端悬浮显示,以及触屏下不出现悬浮残留。
3738
- [x] 修复打开视频时先闪现上一个视频画面:media_kit 跨视频复用同一块原生纹理,新视频出帧前纹理仍是上一视频的最后一帧。`VideoFrameGate` 在本次 open 后 mpv `vo-configured=yes` 且纹理尺寸等于新视频尺寸时才绘制纹理,此前露出黑色画布;`video-params` 实测比原生出帧早 100~400ms,不能作为信号。日志 `[VideoFrame]` 记录每次打开的代次、正在加载的视频与纹理中实际画着的视频。**完成时间**: 2026-11-15
3839
- 已在 macOS 验证横竖屏视频互切 4 次均无旧画面;未完成的验证:两个同分辨率视频互切(纹理不重建,仅靠 `vo-configured`)、iOS / Android。
3940
- [ ] 学习材料 PDF 读取 AI 词典缓存时仍使用旧类型 `ai_dictionary`,而当前写入类型为 `ai_dictionary_v2`,导致学习材料 PDF 读不到 AI 查词结果;收藏导出已改用 `AiDictionarySource.readCachedEntry`。需确认后统一修正。

‎docs/troubleshooting.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -711,3 +711,13 @@
711711
- 规则:判断"画面已是新媒体"必须以原生绘制相关的信号为准,并用原生日志时间戳对照验证;解码层信号不等于已上屏。
712712
- 相关代码:`lib/services/video_frame_gate.dart`、`lib/services/media_kit_player_backend.dart`(`_startVideoFrameGate`)、`test/services/video_frame_gate_test.dart`;日志标签 `[VideoFrame]`。
713713
- 修复时间:2026-11-15。
714+
715+
### 7.49 字幕遮罩手柄在挂载时自动弹出:激发态绑在组件生命周期上,布局切换与打开视频都会重建
716+
717+
- 现象:打开视频、窗口宽高比跨过 1、进出全屏时,已开启的遮罩自动显示边框与手柄约 2 秒;鼠标悬浮遮罩时反而不显示。
718+
- 根因:`initState` 无条件设置“保持显示 2 秒”。遮罩在数据库读出后才挂载(provider 为 autoDispose,每次进入都重新读库);`media_playback_screen.dart` 的单列 / 双列布局是带不同 key 的 `Column` / `Row`,跨过 `width / height > 1` 时整个画面子树被替换,全屏分支也会换父节点。两种情况都会让遮罩 `State` 重建,于是每次都被当成“刚开启”。悬浮显示此前并未实现,`MouseRegion` 只设置了光标。
719+
- 解法:挂载不激发;遮罩组件在数据加载后常驻,由 `isEnabled` 驱动,只有 `didUpdateWidget` 中从关到开才保持 2 秒;悬浮用覆盖整个组件的 `MouseRegion(opaque: false)`,判定区域取遮罩外扩手柄触控区的范围,鼠标从遮罩移到手柄上不会丢失激发态。
720+
- 排查:日志标签 `[VideoSubtitleMask]`。`遮罩组件挂载` 后紧跟 `遮罩激发态开启 原因=…` 且原因不是用户操作(点按 / 开始拖动 / 鼠标悬浮 / 遮罩开启),即为重建误弹出。正常路径下激发开启的原因只能是这四种之一,关闭的原因是 `保持时间结束` 或 `鼠标移出`。
721+
- 规则:激发类 UI 状态只能由用户操作或状态切换触发,不能放在 `initState`;与重建无关的显示状态要么提升到不会因布局切换而重建的父级,要么像这里一样由“变化”触发而不是由“挂载”触发。
722+
- 相关代码:`lib/widgets/common/video_subtitle_mask_overlay.dart`、`lib/widgets/common/media_visual_surface.dart`、`test/widgets/common/video_subtitle_mask_overlay_test.dart`。
723+
- 修复时间:2026-11-15。

‎lib/widgets/common/media_visual_surface.dart‎

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,13 @@ class _MediaVisualSurfaceState extends State<MediaVisualSurface> {
175175
color: Colors.black,
176176
child: widget.buildVideoView(Size(width, height)),
177177
),
178-
if (subtitleMask != null && subtitleMask.enabled)
178+
// 数据加载后即挂载,开关只改变 isEnabled:开启才会激发手柄,
179+
// 挂载本身(打开视频、布局切换)不会。
180+
if (subtitleMask != null)
179181
Positioned.fill(
180182
child: VideoSubtitleMaskOverlay(
181183
normalizedRect: subtitleMask.rect,
184+
isEnabled: subtitleMask.enabled,
182185
contentRect: videoContentRect(
183186
Size(width, height),
184187
state.videoAspectRatio,
@@ -258,12 +261,10 @@ class _MediaVisualSurfaceState extends State<MediaVisualSurface> {
258261
),
259262
active: state.subtitleVisible,
260263
tooltip: state.subtitleVisible
261-
? AppLocalizations.of(
262-
context,
263-
)!.mediaHideVideoSubtitles
264-
: AppLocalizations.of(
265-
context,
266-
)!.mediaShowVideoSubtitles,
264+
? AppLocalizations.of(context)!
265+
.mediaHideVideoSubtitles
266+
: AppLocalizations.of(context)!
267+
.mediaShowVideoSubtitles,
267268
onPressed: widget.actions.onSubtitleToggle,
268269
),
269270
const SizedBox(width: 8),
@@ -276,12 +277,10 @@ class _MediaVisualSurfaceState extends State<MediaVisualSurface> {
276277
),
277278
active: true,
278279
tooltip: state.expanded
279-
? AppLocalizations.of(
280-
context,
281-
)!.mediaExitFullscreen
282-
: AppLocalizations.of(
283-
context,
284-
)!.mediaEnterFullscreen,
280+
? AppLocalizations.of(context)!
281+
.mediaExitFullscreen
282+
: AppLocalizations.of(context)!
283+
.mediaEnterFullscreen,
285284
onPressed: widget.actions.onFullscreenToggle,
286285
),
287286
],
@@ -303,9 +302,8 @@ class _MediaVisualSurfaceState extends State<MediaVisualSurface> {
303302
key: const ValueKey('media-hide-visual-button'),
304303
icon: const Icon(Icons.visibility_off),
305304
active: true,
306-
tooltip: AppLocalizations.of(
307-
context,
308-
)!.videoHideTrack,
305+
tooltip: AppLocalizations.of(context)!
306+
.videoHideTrack,
309307
onPressed: widget.actions.onHide,
310308
),
311309
),

0 commit comments

Comments
 (0)