1
This commit is contained in:
@@ -14,6 +14,7 @@ class VideoPlayerView extends StatefulWidget {
|
||||
required this.slotIndex,
|
||||
required this.coverUrl,
|
||||
required this.isCurrent,
|
||||
this.durationSeconds = 0,
|
||||
});
|
||||
|
||||
final FeedPlayerPool pool;
|
||||
@@ -23,6 +24,10 @@ class VideoPlayerView extends StatefulWidget {
|
||||
/// 该页是否为当前页(暂停图标只在当前页显示,避免划走后旧页残留大图标)
|
||||
final bool isCurrent;
|
||||
|
||||
/// DB 里的视频时长(秒):流 duration 异常(如被当直播流报 0)时兜底,
|
||||
/// 保证进度条/拖动可用
|
||||
final int durationSeconds;
|
||||
|
||||
@override
|
||||
State<VideoPlayerView> createState() => _VideoPlayerViewState();
|
||||
}
|
||||
@@ -45,9 +50,15 @@ class _VideoPlayerViewState extends State<VideoPlayerView> {
|
||||
});
|
||||
}
|
||||
|
||||
/// 有效时长:流上报优先,异常(duration=0)回退 DB 时长
|
||||
int get _totalMs {
|
||||
final streamMs = _duration.inMilliseconds;
|
||||
return streamMs > 0 ? streamMs : widget.durationSeconds * 1000;
|
||||
}
|
||||
|
||||
double get _value {
|
||||
if (_seeking) return _seekValue;
|
||||
final total = _duration.inMilliseconds;
|
||||
final total = _totalMs;
|
||||
if (total <= 0) return 0;
|
||||
final cur = _position.inMilliseconds.clamp(0, total);
|
||||
return cur / total;
|
||||
@@ -66,7 +77,7 @@ class _VideoPlayerViewState extends State<VideoPlayerView> {
|
||||
|
||||
void _onSeekEnd(double v) {
|
||||
setState(() => _seeking = false);
|
||||
final totalMs = _duration.inMilliseconds;
|
||||
final totalMs = _totalMs;
|
||||
if (totalMs > 0) {
|
||||
_slot.player.seek(Duration(milliseconds: (v * totalMs).round()));
|
||||
}
|
||||
@@ -120,7 +131,7 @@ class _VideoPlayerViewState extends State<VideoPlayerView> {
|
||||
),
|
||||
),
|
||||
|
||||
// 抖音风格细进度条(贴底,常驻)
|
||||
// 抖音风格细进度条(贴底,常驻;拖动时显示目标时间气泡)
|
||||
Positioned(
|
||||
left: 0, right: 0, bottom: 0,
|
||||
child: _ThinProgressBar(
|
||||
@@ -128,6 +139,7 @@ class _VideoPlayerViewState extends State<VideoPlayerView> {
|
||||
onChanged: _onSeek,
|
||||
onChangeStart: _onSeekStart,
|
||||
onChangeEnd: _onSeekEnd,
|
||||
totalMs: _totalMs,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -141,16 +153,29 @@ class _ThinProgressBar extends StatefulWidget {
|
||||
required this.onChanged,
|
||||
required this.onChangeStart,
|
||||
required this.onChangeEnd,
|
||||
this.totalMs = 0,
|
||||
});
|
||||
final double value;
|
||||
final ValueChanged<double> onChanged;
|
||||
final ValueChanged<double> onChangeStart;
|
||||
final ValueChanged<double> onChangeEnd;
|
||||
|
||||
/// 视频总时长(毫秒):拖动气泡显示目标位置用时/分/秒
|
||||
final int totalMs;
|
||||
|
||||
@override
|
||||
State<_ThinProgressBar> createState() => _ThinProgressBarState();
|
||||
}
|
||||
|
||||
/// 拖动气泡时间:>1 小时显示 x时xx分xx秒,否则 xx分xx秒
|
||||
String _seekBubbleText(double value, int totalMs) {
|
||||
final d = Duration(milliseconds: (value * totalMs).round());
|
||||
final h = d.inHours;
|
||||
final m = d.inMinutes.remainder(60).toString().padLeft(2, '0');
|
||||
final s = d.inSeconds.remainder(60).toString().padLeft(2, '0');
|
||||
return h > 0 ? '$h时$m分$s秒' : '$m分$s秒';
|
||||
}
|
||||
|
||||
class _ThinProgressBarState extends State<_ThinProgressBar> {
|
||||
bool _dragging = false;
|
||||
double _dragValue = 0;
|
||||
@@ -187,15 +212,44 @@ class _ThinProgressBarState extends State<_ThinProgressBar> {
|
||||
setState(() => _dragging = false);
|
||||
widget.onChangeEnd(_dragValue.clamp(0.0, 1.0));
|
||||
},
|
||||
child: Container(
|
||||
child: SizedBox(
|
||||
height: 32, // 触摸热区 32px(视觉 2px,好点)
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: CustomPaint(
|
||||
painter: _ProgressPainter(
|
||||
value: _dragging ? _dragValue : widget.value,
|
||||
dragging: _dragging,
|
||||
),
|
||||
size: Size(w, _dragging ? 20 : 4),
|
||||
child: Stack(
|
||||
clipBehavior: Clip.none,
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: CustomPaint(
|
||||
painter: _ProgressPainter(
|
||||
value: _dragging ? _dragValue : widget.value,
|
||||
dragging: _dragging,
|
||||
),
|
||||
size: Size(w, _dragging ? 20 : 4),
|
||||
),
|
||||
),
|
||||
// 拖动中的时间气泡(跟随 thumb,两端自动夹在屏内)
|
||||
if (_dragging && widget.totalMs > 0)
|
||||
Positioned(
|
||||
bottom: 14,
|
||||
left: (_dragValue * w - 38)
|
||||
.clamp(0.0, (w - 76).clamp(0.0, w)),
|
||||
child: Container(
|
||||
width: 76,
|
||||
height: 24,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.black.withValues(alpha: 0.82),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Text(
|
||||
_seekBubbleText(_dragValue, widget.totalMs),
|
||||
style: const TextStyle(
|
||||
color: Colors.white, fontSize: 11.5),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user