fix(player): 修正缓冲态与首帧时间闪现
This commit is contained in:
1 parent
0f27daf664
commit
350d16db75
8 files changed
+262
-22
No files matched your search
@@ -43,6 +43,7 @@ class SeekProgressBar extends StatefulWidget {
|
||||
this.episodeId,
|
||||
this.thumbnailsUrl,
|
||||
this.fallbackDuration = Duration.zero,
|
||||
this.suppressBufferLoading = false,
|
||||
});
|
||||
|
||||
final ValueListenable<VideoPlayerValue>? valueListenable;
|
||||
@@ -59,6 +60,7 @@ class SeekProgressBar extends StatefulWidget {
|
||||
final String? episodeId;
|
||||
final String? thumbnailsUrl;
|
||||
final Duration fallbackDuration;
|
||||
final bool suppressBufferLoading;
|
||||
|
||||
@override
|
||||
State<SeekProgressBar> createState() => _SeekProgressBarState();
|
||||
@@ -74,6 +76,7 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
bool _pointerMoved = false;
|
||||
Offset? _pointerStart;
|
||||
double? _dragStartFraction;
|
||||
Duration? _lastPosition;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -90,13 +93,22 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
_bufferTimer?.cancel();
|
||||
_bufferTimer = null;
|
||||
_buffering = false;
|
||||
_lastPosition = null;
|
||||
widget.thumbnailService?.cancelPending();
|
||||
_attachSource();
|
||||
}
|
||||
if (oldWidget.suppressBufferLoading != widget.suppressBufferLoading) {
|
||||
_bufferTimer?.cancel();
|
||||
_bufferTimer = null;
|
||||
_lastPosition = null;
|
||||
_buffering = false;
|
||||
_syncBufferState(notify: false);
|
||||
}
|
||||
if (oldWidget.locked != widget.locked) {
|
||||
_bufferTimer?.cancel();
|
||||
_bufferTimer = null;
|
||||
_buffering = false;
|
||||
_lastPosition = null;
|
||||
if (widget.locked) {
|
||||
widget.thumbnailService?.cancelPending();
|
||||
_previewFraction.value = null;
|
||||
@@ -130,14 +142,24 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
|
||||
void _syncBufferState({bool notify = true}) {
|
||||
final source = widget.valueListenable;
|
||||
if (widget.locked || source == null) {
|
||||
if (widget.locked || widget.suppressBufferLoading || source == null) {
|
||||
_bufferTimer?.cancel();
|
||||
_bufferTimer = null;
|
||||
_lastPosition = null;
|
||||
_setBuffering(false, notify: notify);
|
||||
return;
|
||||
}
|
||||
final value = source.value;
|
||||
if (!value.isInitialized) {
|
||||
_bufferTimer?.cancel();
|
||||
_bufferTimer = null;
|
||||
_lastPosition = null;
|
||||
_setBuffering(false, notify: notify);
|
||||
return;
|
||||
}
|
||||
final previousPosition = _lastPosition;
|
||||
_lastPosition = value.position;
|
||||
if (previousPosition != null && value.position > previousPosition) {
|
||||
_bufferTimer?.cancel();
|
||||
_bufferTimer = null;
|
||||
_setBuffering(false, notify: notify);
|
||||
@@ -149,14 +171,24 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
_setBuffering(false, notify: notify);
|
||||
return;
|
||||
}
|
||||
// 首次看到“已播放且有画面”的 buffering 状态时,以 position 为基线,
|
||||
// 不抢先显示 loading;后续 position 停滞超过阈值才视为真实卡顿。
|
||||
if (previousPosition == null &&
|
||||
value.isPlaying &&
|
||||
value.position > Duration.zero) {
|
||||
_setBuffering(false, notify: notify);
|
||||
return;
|
||||
}
|
||||
if (_buffering || _bufferTimer != null) return;
|
||||
final bufferingPosition = value.position;
|
||||
_bufferTimer = Timer(_bufferDebounce, () {
|
||||
_bufferTimer = null;
|
||||
final current = widget.valueListenable;
|
||||
if (!mounted ||
|
||||
current != source ||
|
||||
!current!.value.isInitialized ||
|
||||
!current.value.isBuffering) {
|
||||
!current.value.isBuffering ||
|
||||
current.value.position != bufferingPosition) {
|
||||
return;
|
||||
}
|
||||
_setBuffering(true);
|
||||
@@ -227,7 +259,11 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
: widget.fallbackDuration;
|
||||
final total = duration.inMilliseconds;
|
||||
final scrubbing = _dragging;
|
||||
final loading = !widget.locked && !scrubbing && _buffering;
|
||||
final loading =
|
||||
!widget.locked &&
|
||||
!widget.suppressBufferLoading &&
|
||||
!scrubbing &&
|
||||
_buffering;
|
||||
final enabled =
|
||||
!widget.locked &&
|
||||
video.isInitialized &&
|
||||
@@ -243,6 +279,8 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
final showPreview =
|
||||
scrubbing &&
|
||||
widget.showTimes &&
|
||||
video.isInitialized &&
|
||||
total > 0 &&
|
||||
widget.thumbnailService != null &&
|
||||
(widget.episodeId?.isNotEmpty ?? false);
|
||||
final trackHeight = scrubbing ? 6.0 : 2.0;
|
||||
@@ -253,21 +291,6 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
||||
final track = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.showTimes && !video.isInitialized)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
child: Text(
|
||||
'00:00 / ${_format(duration)}',
|
||||
key: const Key('player-progress-uninitialized-time'),
|
||||
textAlign: TextAlign.center,
|
||||
style: const TextStyle(
|
||||
color: AppColors.onPrimary,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showTimes && scrubbing && !showPreview)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 2),
|
||||
|
||||
Reference in new issue
Block a user