feat(player): 增加进度条拖动画面预览
This commit is contained in:
1 parent
d06dce9753
commit
51c56d884b
15 files changed
+1493
-77
No files matched your search
@@ -0,0 +1,62 @@
|
||||
# 播放器进度拖动画面预览
|
||||
|
||||
## 目标
|
||||
|
||||
首页 feed 与全屏播放器的进度条在拖动时显示当前时间点的视频画面缩略图、
|
||||
当前时间与总时长;松手立即隐藏并提交 seek。缩略图未返回或失败时显示短剧
|
||||
封面,不出现空白闪烁或错误文案。
|
||||
|
||||
## 性能红线
|
||||
|
||||
1. 拖动期间主线程无卡顿;图片解码和原生取帧不得阻塞 UI 线程。
|
||||
2. 原生取帧并发数不超过 1。
|
||||
3. 预览图最大宽度 240px。
|
||||
4. Dart 内存 LRU 缓存不超过 24 帧。
|
||||
5. 不新增 pub 依赖。
|
||||
6. 不改后端契约。
|
||||
|
||||
## 实现
|
||||
|
||||
### 缩略图服务
|
||||
|
||||
- 新增 `PlaybackThumbnailService`,公开入口按 `episodeId + position` 取
|
||||
`ByteData`,底层通过
|
||||
`MethodChannel('com.stormg.lumadrama/thumbnail')` 调用原生。
|
||||
- 时间按 1 秒分桶;同一 bucket 的在途请求合流。
|
||||
- 最多 24 帧 LRU;成功和失败结果都做过期控制,避免旧拖动位置覆盖当前预览。
|
||||
- 全局单飞;拖动期间只保留最新 bucket,尚未开始的旧请求直接丢弃。
|
||||
- 拖动停顿 120ms 后最多预取相邻一个 bucket,松手取消尚未开始的请求。
|
||||
- 原生异常、缺失实现和超过 1.5 秒无结果统一返回 `null`。
|
||||
|
||||
### 原生取帧
|
||||
|
||||
- Android 使用 `MediaMetadataRetriever`,优先本地下载路径,其次签名 URL;
|
||||
`OPTION_CLOSEST_SYNC` 取帧,最大宽 240px,JPEG 质量 70,单线程 Executor,
|
||||
完成后立即 release。
|
||||
- iOS 使用 `AVAssetImageGenerator`,应用画面方向,最大尺寸约 240x426,
|
||||
合理时间容差,后台串行队列生成 JPEG。
|
||||
- 两端均保证只回调一次;失败或 1.5 秒超时返回 `null`,不向 UI 抛异常。
|
||||
|
||||
### UI
|
||||
|
||||
- 新增 `ScrubPreviewCard`:宽 120dp、圆角 8、白色 1px 描边,显示
|
||||
`当前时间 / 总时长`;沿拖动点水平跟随并做安全边距 clamp。
|
||||
- 首页 `_FeedProgressBar` 与全屏 `SeekProgressBar` 共用该卡片。
|
||||
- 封面始终铺在卡片底层;`Image.memory` 仅在帧到达后覆盖,并限制
|
||||
`cacheWidth: 240`。
|
||||
- 开始拖动请求当前 bucket,拖动更新只保留最新位置;松手立即隐藏卡片并取消
|
||||
未开始请求。
|
||||
- `PlayerPage` 持有页面级服务,dispose 时清空缓存,避免跨页面串集。
|
||||
|
||||
## 测试
|
||||
|
||||
- 单测:bucket、LRU 淘汰、同 bucket 合流、过期请求丢弃、松手取消、
|
||||
相邻 bucket 防抖、异常/超时回落。
|
||||
- Widget:拖动显示预览卡片和时间、松手消失、无缩略图显示封面。
|
||||
- 门禁:`dart analyze lib test` 与 `flutter test --no-pub`。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不改服务端接口或持久化策略。
|
||||
- 不新增第三方依赖。
|
||||
- 不启停服务、不操作真机;原生取帧实际效果由 PM 真机验收。
|
||||
@@ -426,3 +426,56 @@ $ flutter test --no-pub
|
||||
解读:短缓冲不闪、持续缓冲仍显示白色高光;未初始化/入口/返回/重建路径
|
||||
均无多余 loading,重建顺序为 initialize→seek→play。全量静态分析零告警,
|
||||
593 项测试全过。`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||||
|
||||
## 2026-10-22 播放器进度拖动画面预览
|
||||
|
||||
- 已从最新 `develop`(`d06dce9`)创建 `feature/scrub-frame-preview`。
|
||||
- 计划文档:`docs/plans/2026-10-22-scrub-frame-preview.md`。
|
||||
- 性能红线:拖动期间主线程无卡顿;原生并发取帧不超过 1;预览图最大宽
|
||||
240px;Dart LRU 缓存不超过 24 帧;不新增 pub 依赖;不改后端契约。
|
||||
- 实施顺序:缩略图服务与缓存 -> 预览卡片及两处进度条 -> Android/iOS
|
||||
原生渠道 -> 单测/widget 测试与全量门禁。
|
||||
|
||||
```text
|
||||
$ git status --short --branch
|
||||
## feature/scrub-frame-preview
|
||||
|
||||
$ git log -5 --oneline --decorate
|
||||
d06dce9 (HEAD -> feature/scrub-frame-preview, origin/develop, develop) merge: 合并播放入口无缓冲提示
|
||||
```
|
||||
|
||||
解读:工作树从最新 develop 创建且开工前无未提交改动。当前仅新增计划与
|
||||
PROGRESS 留痕,后续每完成一个阶段会补充命令原始输出与解读。
|
||||
|
||||
### 实现结果
|
||||
|
||||
- 新增 `PlaybackThumbnailService`:1 秒 bucket、24 帧 LRU、单飞合流、
|
||||
最新 bucket 优先、停顿 120ms 后最多预取相邻 1 帧、1.5 秒统一回落。
|
||||
- 新增 `ScrubPreviewCard`:120dp 宽、圆角 8、白色 1px 描边,显示
|
||||
`当前时间 / 总时长`,安全边距内跟随拖动点;帧到达前始终显示封面。
|
||||
- 首页 feed 与全屏播放器进度条均接入;松手立即隐藏并取消尚未开始的请求。
|
||||
- Android 使用单线程 `MediaMetadataRetriever`,iOS 使用串行
|
||||
`AVAssetImageGenerator`;两端优先本地路径、最大宽 240px、JPEG 约 70、
|
||||
失败或 1.5 秒超时回调 null。
|
||||
- 未新增 pub 依赖,未改后端契约;`pubspec.lock` 已还原,未启停服务,
|
||||
未操作真机。
|
||||
|
||||
执行命令与原始输出:
|
||||
|
||||
```text
|
||||
$ flutter test --no-pub test/playback/playback_thumbnail_service_test.dart test/playback/seek_progress_bar_test.dart test/home/home_preview_layout_test.dart
|
||||
00:01 +28: All tests passed!
|
||||
|
||||
$ dart analyze lib test
|
||||
Analyzing lib, test...
|
||||
No issues found!
|
||||
|
||||
$ flutter test --no-pub
|
||||
00:56 +606: All tests passed!
|
||||
```
|
||||
|
||||
解读:bucket、24 帧 LRU、同 bucket 合流、过期请求丢弃、松手取消、
|
||||
120ms 预取防覆盖、异常/超时回落、首页与全屏预览卡片、封面占位和松手立即
|
||||
消失均通过;全量静态分析零告警,606 项测试全过。性能红线保持:主线程不做
|
||||
图片解码/取帧、原生并发 1、预览最大宽 240px、缓存最多 24 帧、不增依赖、
|
||||
不改后端。原生取帧真实画面与设备性能由 PM 真机验收。
|
||||
Reference in new issue
Block a user