63 lines
2.7 KiB
Markdown
63 lines
2.7 KiB
Markdown
# 播放器进度拖动画面预览
|
||
|
||
## 目标
|
||
|
||
首页 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 真机验收。
|