fix(player): 修正缓冲态与首帧时间闪现
This commit is contained in:
1 parent
0f27daf664
commit
350d16db75
8 files changed
+262
-22
No files matched your search
@@ -1161,6 +1161,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
: null,
|
: null,
|
||||||
thumbnailService: _thumbnailService,
|
thumbnailService: _thumbnailService,
|
||||||
spriteAxisService: _spriteAxisService,
|
spriteAxisService: _spriteAxisService,
|
||||||
|
suppressBufferLoading: !widget.embedded && !_loadingRemoved,
|
||||||
)
|
)
|
||||||
else if (!widget.homeMode)
|
else if (!widget.homeMode)
|
||||||
_standardPlaybackOverlay(context, index, interactions),
|
_standardPlaybackOverlay(context, index, interactions),
|
||||||
@@ -1284,6 +1285,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
onBoostEnd: _endBoost,
|
onBoostEnd: _endBoost,
|
||||||
),
|
),
|
||||||
controlsVisible: _chromeVisible,
|
controlsVisible: _chromeVisible,
|
||||||
|
suppressProgressLoading: !widget.embedded && !_loadingRemoved,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ class HomeFeedOverlay extends StatefulWidget {
|
|||||||
this.cancelScrubRequest = 0,
|
this.cancelScrubRequest = 0,
|
||||||
this.thumbnailService,
|
this.thumbnailService,
|
||||||
this.spriteAxisService,
|
this.spriteAxisService,
|
||||||
|
this.suppressBufferLoading = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final AppLocalizations l10n;
|
final AppLocalizations l10n;
|
||||||
@@ -104,6 +105,7 @@ class HomeFeedOverlay extends StatefulWidget {
|
|||||||
final int cancelScrubRequest;
|
final int cancelScrubRequest;
|
||||||
final PlaybackThumbnailService? thumbnailService;
|
final PlaybackThumbnailService? thumbnailService;
|
||||||
final SpriteThumbnailAxisService? spriteAxisService;
|
final SpriteThumbnailAxisService? spriteAxisService;
|
||||||
|
final bool suppressBufferLoading;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<HomeFeedOverlay> createState() => _HomeFeedOverlayState();
|
State<HomeFeedOverlay> createState() => _HomeFeedOverlayState();
|
||||||
@@ -195,6 +197,7 @@ class _HomeFeedOverlayState extends State<HomeFeedOverlay> {
|
|||||||
spriteAxisService: widget.spriteAxisService,
|
spriteAxisService: widget.spriteAxisService,
|
||||||
thumbnailsUrl: widget.episode.thumbnailsUrl,
|
thumbnailsUrl: widget.episode.thumbnailsUrl,
|
||||||
episodeId: widget.episode.id,
|
episodeId: widget.episode.id,
|
||||||
|
suppressBufferLoading: widget.suppressBufferLoading,
|
||||||
onScrubStart: _startScrub,
|
onScrubStart: _startScrub,
|
||||||
onScrubEnd: _endScrub,
|
onScrubEnd: _endScrub,
|
||||||
onSeek: widget.onSeek,
|
onSeek: widget.onSeek,
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class _FeedProgressBar extends StatefulWidget {
|
|||||||
this.spriteAxisService,
|
this.spriteAxisService,
|
||||||
this.thumbnailsUrl,
|
this.thumbnailsUrl,
|
||||||
this.episodeId,
|
this.episodeId,
|
||||||
|
this.suppressBufferLoading = false,
|
||||||
this.onScrubStart,
|
this.onScrubStart,
|
||||||
this.onScrubEnd,
|
this.onScrubEnd,
|
||||||
this.onSeek,
|
this.onSeek,
|
||||||
@@ -37,6 +38,7 @@ class _FeedProgressBar extends StatefulWidget {
|
|||||||
final SpriteThumbnailAxisSource? spriteAxisService;
|
final SpriteThumbnailAxisSource? spriteAxisService;
|
||||||
final String? thumbnailsUrl;
|
final String? thumbnailsUrl;
|
||||||
final String? episodeId;
|
final String? episodeId;
|
||||||
|
final bool suppressBufferLoading;
|
||||||
final VoidCallback? onScrubStart;
|
final VoidCallback? onScrubStart;
|
||||||
final VoidCallback? onScrubEnd;
|
final VoidCallback? onScrubEnd;
|
||||||
final ValueChanged<Duration>? onSeek;
|
final ValueChanged<Duration>? onSeek;
|
||||||
@@ -52,6 +54,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
Timer? _bufferTimer;
|
Timer? _bufferTimer;
|
||||||
bool _buffering = false;
|
bool _buffering = false;
|
||||||
bool _dragging = false;
|
bool _dragging = false;
|
||||||
|
Duration? _lastPosition;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -67,6 +70,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_buffering = false;
|
_buffering = false;
|
||||||
|
_lastPosition = null;
|
||||||
_attachSource();
|
_attachSource();
|
||||||
}
|
}
|
||||||
if (oldWidget.cancelScrubRequest != widget.cancelScrubRequest) {
|
if (oldWidget.cancelScrubRequest != widget.cancelScrubRequest) {
|
||||||
@@ -77,6 +81,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_buffering = false;
|
_buffering = false;
|
||||||
|
_lastPosition = null;
|
||||||
if (widget.locked) {
|
if (widget.locked) {
|
||||||
_dragging = false;
|
_dragging = false;
|
||||||
_preview = null;
|
_preview = null;
|
||||||
@@ -84,6 +89,13 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
_syncBufferState(notify: false);
|
_syncBufferState(notify: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (oldWidget.suppressBufferLoading != widget.suppressBufferLoading) {
|
||||||
|
_bufferTimer?.cancel();
|
||||||
|
_bufferTimer = null;
|
||||||
|
_buffering = false;
|
||||||
|
_lastPosition = null;
|
||||||
|
_syncBufferState(notify: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -100,7 +112,11 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
final width = constraints.maxWidth;
|
final width = constraints.maxWidth;
|
||||||
final source = widget.controller;
|
final source = widget.controller;
|
||||||
final scrubbing = _dragging;
|
final scrubbing = _dragging;
|
||||||
final loading = !widget.locked && !scrubbing && _buffering;
|
final loading =
|
||||||
|
!widget.locked &&
|
||||||
|
!widget.suppressBufferLoading &&
|
||||||
|
!scrubbing &&
|
||||||
|
_buffering;
|
||||||
final track = widget.locked
|
final track = widget.locked
|
||||||
? _inactiveTrack()
|
? _inactiveTrack()
|
||||||
: source == null
|
: source == null
|
||||||
@@ -199,14 +215,24 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
|
|
||||||
void _syncBufferState({bool notify = true}) {
|
void _syncBufferState({bool notify = true}) {
|
||||||
final source = widget.controller;
|
final source = widget.controller;
|
||||||
if (widget.locked || source == null) {
|
if (widget.locked || widget.suppressBufferLoading || source == null) {
|
||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
|
_lastPosition = null;
|
||||||
_setBuffering(false, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final value = source.value;
|
final value = source.value;
|
||||||
if (!value.isInitialized) {
|
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?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_setBuffering(false, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
@@ -218,14 +244,22 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
_setBuffering(false, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (previousPosition == null &&
|
||||||
|
value.isPlaying &&
|
||||||
|
value.position > Duration.zero) {
|
||||||
|
_setBuffering(false, notify: notify);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_buffering || _bufferTimer != null) return;
|
if (_buffering || _bufferTimer != null) return;
|
||||||
|
final bufferingPosition = value.position;
|
||||||
_bufferTimer = Timer(_bufferDebounce, () {
|
_bufferTimer = Timer(_bufferDebounce, () {
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
final current = widget.controller;
|
final current = widget.controller;
|
||||||
if (!mounted ||
|
if (!mounted ||
|
||||||
current != source ||
|
current != source ||
|
||||||
!current!.value.isInitialized ||
|
!current!.value.isInitialized ||
|
||||||
!current.value.isBuffering) {
|
!current.value.isBuffering ||
|
||||||
|
current.value.position != bufferingPosition) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_setBuffering(true);
|
_setBuffering(true);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
|
|||||||
required this.actions,
|
required this.actions,
|
||||||
required this.controlsVisible,
|
required this.controlsVisible,
|
||||||
required this.scrubbing,
|
required this.scrubbing,
|
||||||
|
this.suppressProgressLoading = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// 展示数据。
|
/// 展示数据。
|
||||||
@@ -50,6 +51,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
|
|||||||
|
|
||||||
/// 正在拖动进度条。拖动期间清屏,只留进度条。
|
/// 正在拖动进度条。拖动期间清屏,只留进度条。
|
||||||
final bool scrubbing;
|
final bool scrubbing;
|
||||||
|
final bool suppressProgressLoading;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -306,6 +308,7 @@ class PlayerPlaybackOverlay extends StatelessWidget {
|
|||||||
episodeId: data.episode.id,
|
episodeId: data.episode.id,
|
||||||
inactiveTrackColor: AppColors.inactiveTrack,
|
inactiveTrackColor: AppColors.inactiveTrack,
|
||||||
fallbackDuration: Duration(seconds: data.episode.durationSec),
|
fallbackDuration: Duration(seconds: data.episode.durationSec),
|
||||||
|
suppressBufferLoading: suppressProgressLoading,
|
||||||
onScrubStart: actions.onScrubStart,
|
onScrubStart: actions.onScrubStart,
|
||||||
onScrubEnd: actions.onScrubEnd,
|
onScrubEnd: actions.onScrubEnd,
|
||||||
onSeek: actions.onSeek,
|
onSeek: actions.onSeek,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class SeekProgressBar extends StatefulWidget {
|
|||||||
this.episodeId,
|
this.episodeId,
|
||||||
this.thumbnailsUrl,
|
this.thumbnailsUrl,
|
||||||
this.fallbackDuration = Duration.zero,
|
this.fallbackDuration = Duration.zero,
|
||||||
|
this.suppressBufferLoading = false,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ValueListenable<VideoPlayerValue>? valueListenable;
|
final ValueListenable<VideoPlayerValue>? valueListenable;
|
||||||
@@ -59,6 +60,7 @@ class SeekProgressBar extends StatefulWidget {
|
|||||||
final String? episodeId;
|
final String? episodeId;
|
||||||
final String? thumbnailsUrl;
|
final String? thumbnailsUrl;
|
||||||
final Duration fallbackDuration;
|
final Duration fallbackDuration;
|
||||||
|
final bool suppressBufferLoading;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SeekProgressBar> createState() => _SeekProgressBarState();
|
State<SeekProgressBar> createState() => _SeekProgressBarState();
|
||||||
@@ -74,6 +76,7 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
bool _pointerMoved = false;
|
bool _pointerMoved = false;
|
||||||
Offset? _pointerStart;
|
Offset? _pointerStart;
|
||||||
double? _dragStartFraction;
|
double? _dragStartFraction;
|
||||||
|
Duration? _lastPosition;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -90,13 +93,22 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_buffering = false;
|
_buffering = false;
|
||||||
|
_lastPosition = null;
|
||||||
widget.thumbnailService?.cancelPending();
|
widget.thumbnailService?.cancelPending();
|
||||||
_attachSource();
|
_attachSource();
|
||||||
}
|
}
|
||||||
|
if (oldWidget.suppressBufferLoading != widget.suppressBufferLoading) {
|
||||||
|
_bufferTimer?.cancel();
|
||||||
|
_bufferTimer = null;
|
||||||
|
_lastPosition = null;
|
||||||
|
_buffering = false;
|
||||||
|
_syncBufferState(notify: false);
|
||||||
|
}
|
||||||
if (oldWidget.locked != widget.locked) {
|
if (oldWidget.locked != widget.locked) {
|
||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_buffering = false;
|
_buffering = false;
|
||||||
|
_lastPosition = null;
|
||||||
if (widget.locked) {
|
if (widget.locked) {
|
||||||
widget.thumbnailService?.cancelPending();
|
widget.thumbnailService?.cancelPending();
|
||||||
_previewFraction.value = null;
|
_previewFraction.value = null;
|
||||||
@@ -130,14 +142,24 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
|
|
||||||
void _syncBufferState({bool notify = true}) {
|
void _syncBufferState({bool notify = true}) {
|
||||||
final source = widget.valueListenable;
|
final source = widget.valueListenable;
|
||||||
if (widget.locked || source == null) {
|
if (widget.locked || widget.suppressBufferLoading || source == null) {
|
||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
|
_lastPosition = null;
|
||||||
_setBuffering(false, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final value = source.value;
|
final value = source.value;
|
||||||
if (!value.isInitialized) {
|
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?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_setBuffering(false, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
@@ -149,14 +171,24 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
_setBuffering(false, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// 首次看到“已播放且有画面”的 buffering 状态时,以 position 为基线,
|
||||||
|
// 不抢先显示 loading;后续 position 停滞超过阈值才视为真实卡顿。
|
||||||
|
if (previousPosition == null &&
|
||||||
|
value.isPlaying &&
|
||||||
|
value.position > Duration.zero) {
|
||||||
|
_setBuffering(false, notify: notify);
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (_buffering || _bufferTimer != null) return;
|
if (_buffering || _bufferTimer != null) return;
|
||||||
|
final bufferingPosition = value.position;
|
||||||
_bufferTimer = Timer(_bufferDebounce, () {
|
_bufferTimer = Timer(_bufferDebounce, () {
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
final current = widget.valueListenable;
|
final current = widget.valueListenable;
|
||||||
if (!mounted ||
|
if (!mounted ||
|
||||||
current != source ||
|
current != source ||
|
||||||
!current!.value.isInitialized ||
|
!current!.value.isInitialized ||
|
||||||
!current.value.isBuffering) {
|
!current.value.isBuffering ||
|
||||||
|
current.value.position != bufferingPosition) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_setBuffering(true);
|
_setBuffering(true);
|
||||||
@@ -227,7 +259,11 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
: widget.fallbackDuration;
|
: widget.fallbackDuration;
|
||||||
final total = duration.inMilliseconds;
|
final total = duration.inMilliseconds;
|
||||||
final scrubbing = _dragging;
|
final scrubbing = _dragging;
|
||||||
final loading = !widget.locked && !scrubbing && _buffering;
|
final loading =
|
||||||
|
!widget.locked &&
|
||||||
|
!widget.suppressBufferLoading &&
|
||||||
|
!scrubbing &&
|
||||||
|
_buffering;
|
||||||
final enabled =
|
final enabled =
|
||||||
!widget.locked &&
|
!widget.locked &&
|
||||||
video.isInitialized &&
|
video.isInitialized &&
|
||||||
@@ -243,6 +279,8 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
final showPreview =
|
final showPreview =
|
||||||
scrubbing &&
|
scrubbing &&
|
||||||
widget.showTimes &&
|
widget.showTimes &&
|
||||||
|
video.isInitialized &&
|
||||||
|
total > 0 &&
|
||||||
widget.thumbnailService != null &&
|
widget.thumbnailService != null &&
|
||||||
(widget.episodeId?.isNotEmpty ?? false);
|
(widget.episodeId?.isNotEmpty ?? false);
|
||||||
final trackHeight = scrubbing ? 6.0 : 2.0;
|
final trackHeight = scrubbing ? 6.0 : 2.0;
|
||||||
@@ -253,21 +291,6 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
final track = Column(
|
final track = Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
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)
|
if (widget.showTimes && scrubbing && !showPreview)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 2),
|
padding: const EdgeInsets.only(bottom: 2),
|
||||||
|
|||||||
@@ -855,6 +855,16 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
await gesture.up();
|
await gesture.up();
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
controller.value = const VideoPlayerValue(
|
||||||
|
duration: Duration(seconds: 100),
|
||||||
|
position: Duration(seconds: 40),
|
||||||
|
isInitialized: true,
|
||||||
|
isBuffering: true,
|
||||||
|
);
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_show_refresh_spinner_until_refresh_finishes', (
|
testWidgets('should_show_refresh_spinner_until_refresh_finishes', (
|
||||||
|
|||||||
@@ -54,6 +54,11 @@ void main() {
|
|||||||
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
expect(find.byType(Slider), findsNothing);
|
expect(find.byType(Slider), findsNothing);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('player-progress-uninitialized-time')),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
expect(find.textContaining('00:00'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_show_inactive_track_and_disable_seek_when_locked', (
|
testWidgets('should_show_inactive_track_and_disable_seek_when_locked', (
|
||||||
@@ -144,6 +149,119 @@ void main() {
|
|||||||
expect(find.byType(Slider), findsOneWidget);
|
expect(find.byType(Slider), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'should_hide_buffering_loading_immediately_when_position_advances',
|
||||||
|
(tester) async {
|
||||||
|
final video = ValueNotifier<VideoPlayerValue>(
|
||||||
|
const VideoPlayerValue(
|
||||||
|
duration: Duration(seconds: 12),
|
||||||
|
position: Duration(seconds: 3),
|
||||||
|
isInitialized: true,
|
||||||
|
isPlaying: true,
|
||||||
|
isBuffering: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
addTearDown(video.dispose);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SeekProgressBar(
|
||||||
|
valueListenable: video,
|
||||||
|
semanticsLabel: 'Seek video',
|
||||||
|
onSeek: (_) async {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
await tester.pump(const Duration(milliseconds: 200));
|
||||||
|
video.value = const VideoPlayerValue(
|
||||||
|
duration: Duration(seconds: 12),
|
||||||
|
position: Duration(seconds: 4),
|
||||||
|
isInitialized: true,
|
||||||
|
isBuffering: true,
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byType(Slider), findsOneWidget);
|
||||||
|
|
||||||
|
video.value = const VideoPlayerValue(
|
||||||
|
duration: Duration(seconds: 12),
|
||||||
|
position: Duration(seconds: 4),
|
||||||
|
isInitialized: true,
|
||||||
|
isPlaying: true,
|
||||||
|
isBuffering: true,
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
||||||
|
expect(find.byType(Slider), findsNothing);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
testWidgets('should_not_show_loading_for_prewarmed_episode', (tester) async {
|
||||||
|
final video = ValueNotifier<VideoPlayerValue>(
|
||||||
|
const VideoPlayerValue(
|
||||||
|
duration: Duration(seconds: 12),
|
||||||
|
position: Duration(seconds: 5),
|
||||||
|
isInitialized: true,
|
||||||
|
isPlaying: true,
|
||||||
|
isBuffering: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
addTearDown(video.dispose);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SeekProgressBar(
|
||||||
|
valueListenable: video,
|
||||||
|
semanticsLabel: 'Seek video',
|
||||||
|
onSeek: (_) async {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byType(Slider), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets(
|
||||||
|
'should_suppress_progress_loading_while_entry_loading_is_visible',
|
||||||
|
(tester) async {
|
||||||
|
final video = ValueNotifier<VideoPlayerValue>(
|
||||||
|
const VideoPlayerValue(
|
||||||
|
duration: Duration(seconds: 12),
|
||||||
|
position: Duration.zero,
|
||||||
|
isInitialized: true,
|
||||||
|
isBuffering: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
addTearDown(video.dispose);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: SeekProgressBar(
|
||||||
|
valueListenable: video,
|
||||||
|
semanticsLabel: 'Seek video',
|
||||||
|
suppressBufferLoading: true,
|
||||||
|
onSeek: (_) async {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byType(Slider), findsOneWidget);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
testWidgets('should_keep_slider_while_scrubbing_during_buffering', (
|
testWidgets('should_keep_slider_while_scrubbing_during_buffering', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
@@ -361,7 +361,11 @@ void main() {
|
|||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
expect(find.byKey(const Key('player-progress')), findsOneWidget);
|
expect(find.byKey(const Key('player-progress')), findsOneWidget);
|
||||||
expect(find.text('00:00 / 01:00'), findsOneWidget);
|
expect(
|
||||||
|
find.byKey(const Key('player-progress-uninitialized-time')),
|
||||||
|
findsNothing,
|
||||||
|
);
|
||||||
|
expect(find.text('00:00 / 01:00'), findsNothing);
|
||||||
|
|
||||||
// IgnorePointer 让加载中的播放暂停与上下滑穿透,滑动会关闭入口 loading。
|
// IgnorePointer 让加载中的播放暂停与上下滑穿透,滑动会关闭入口 loading。
|
||||||
expect(fullLogic.session.userWantsPlay, isTrue);
|
expect(fullLogic.session.userWantsPlay, isTrue);
|
||||||
@@ -515,6 +519,49 @@ void main() {
|
|||||||
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('should_only_show_progress_buffer_after_video_first_frame', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final factory = _RouteVideoFactory();
|
||||||
|
await _pumpApp(tester, playerFactory: factory.create);
|
||||||
|
await _openSeriesPlayer(tester);
|
||||||
|
|
||||||
|
final videoIndex = factory.episodes.lastIndexWhere(
|
||||||
|
(episode) => episode.id == 'city-lights-demo-1',
|
||||||
|
);
|
||||||
|
final video = factory.created[videoIndex];
|
||||||
|
expect(video.controller.value.isInitialized, isTrue);
|
||||||
|
expect(find.byKey(const Key('player-entry-loading-overlay')), findsNothing);
|
||||||
|
|
||||||
|
video.controller.value = const VideoPlayerValue(
|
||||||
|
duration: Duration(minutes: 1),
|
||||||
|
size: Size(1280, 720),
|
||||||
|
position: Duration(seconds: 5),
|
||||||
|
isInitialized: true,
|
||||||
|
isPlaying: true,
|
||||||
|
isBuffering: true,
|
||||||
|
);
|
||||||
|
await tester.pump(const Duration(milliseconds: 400));
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
||||||
|
expect(find.byKey(const Key('player-entry-loading-overlay')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('player-video-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('player-ticket-loading')), findsNothing);
|
||||||
|
|
||||||
|
video.controller.value = const VideoPlayerValue(
|
||||||
|
duration: Duration(minutes: 1),
|
||||||
|
size: Size(1280, 720),
|
||||||
|
position: Duration(seconds: 6),
|
||||||
|
isInitialized: true,
|
||||||
|
isPlaying: true,
|
||||||
|
isBuffering: true,
|
||||||
|
);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byType(Slider), findsOneWidget);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('should_pause_feed_when_auth_page_is_pushed_and_resume_on_pop', (
|
testWidgets('should_pause_feed_when_auth_page_is_pushed_and_resume_on_pop', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
|||||||
Reference in new issue
Block a user