fix(player): 修复返回首页黑屏并恢复预热
This commit is contained in:
1 parent
6667330ce0
commit
54eb5a60a8
9 files changed
+56
-101
No files matched your search
@@ -69,7 +69,6 @@ Future<void> _placeNeighbor(PlaybackSession session, _PlayerSlot? slot) async {
|
|||||||
|
|
||||||
/// 当前集已经出画后,按手势方向静默准备上一集和下一集。
|
/// 当前集已经出画后,按手势方向静默准备上一集和下一集。
|
||||||
void _warmNeighbors(PlaybackSession session, {bool preferredNext = true}) {
|
void _warmNeighbors(PlaybackSession session, {bool preferredNext = true}) {
|
||||||
if (!session.warmNeighborsEnabled) return;
|
|
||||||
final request = session._generation;
|
final request = session._generation;
|
||||||
final nextTask = _warmOne(session, request, session._nextIndex, next: true);
|
final nextTask = _warmOne(session, request, session._nextIndex, next: true);
|
||||||
final previousTask = _warmOne(
|
final previousTask = _warmOne(
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
this.activate,
|
this.activate,
|
||||||
PlaybackCoordinator? coordinator,
|
PlaybackCoordinator? coordinator,
|
||||||
this.waitForFrame,
|
this.waitForFrame,
|
||||||
this.warmNeighborsEnabled = true,
|
|
||||||
}) : _coordinator = coordinator ?? PlaybackCoordinator();
|
}) : _coordinator = coordinator ?? PlaybackCoordinator();
|
||||||
|
|
||||||
final List<Episode> episodes;
|
final List<Episode> episodes;
|
||||||
@@ -40,7 +39,6 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
/// Runs only when a slot becomes current (e.g. progress listeners).
|
/// Runs only when a slot becomes current (e.g. progress listeners).
|
||||||
final PlayerPreparation? activate;
|
final PlayerPreparation? activate;
|
||||||
final Future<void> Function()? waitForFrame;
|
final Future<void> Function()? waitForFrame;
|
||||||
final bool warmNeighborsEnabled;
|
|
||||||
final PlaybackCoordinator _coordinator;
|
final PlaybackCoordinator _coordinator;
|
||||||
final Set<_PlayerSlot> _live = {};
|
final Set<_PlayerSlot> _live = {};
|
||||||
_PlayerSlot? _active;
|
_PlayerSlot? _active;
|
||||||
@@ -142,8 +140,8 @@ class PlaybackSession extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _rememberNeighbors(int? preloadIndex, int? previousIndex) {
|
void _rememberNeighbors(int? preloadIndex, int? previousIndex) {
|
||||||
_nextIndex = warmNeighborsEnabled ? preloadIndex : null;
|
_nextIndex = preloadIndex;
|
||||||
_previousIndex = warmNeighborsEnabled ? previousIndex : null;
|
_previousIndex = previousIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _enqueue(Future<void> Function() operation) {
|
Future<void> _enqueue(Future<void> Function() operation) {
|
||||||
|
|||||||
@@ -6,12 +6,7 @@ part of 'playback_session.dart';
|
|||||||
extension PlaybackSessionControls on PlaybackSession {
|
extension PlaybackSessionControls on PlaybackSession {
|
||||||
/// 滑动手势开始时按方向优先准备相邻集,不改变当前集播放状态。
|
/// 滑动手势开始时按方向优先准备相邻集,不改变当前集播放状态。
|
||||||
void prewarmNeighbors({required bool forward}) {
|
void prewarmNeighbors({required bool forward}) {
|
||||||
if (!warmNeighborsEnabled ||
|
if (!_visible || _overlayOpen || _active?.prepared != true) return;
|
||||||
!_visible ||
|
|
||||||
_overlayOpen ||
|
|
||||||
_active?.prepared != true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_warmNeighbors(this, preferredNext: forward);
|
_warmNeighbors(this, preferredNext: forward);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +57,7 @@ extension PlaybackSessionControls on PlaybackSession {
|
|||||||
slot.activated = true;
|
slot.activated = true;
|
||||||
}
|
}
|
||||||
await _applyPlayback(slot);
|
await _applyPlayback(slot);
|
||||||
|
if (_visible && !_overlayOpen && !_loading) _warmNeighbors(this);
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
'[feed-handoff] phase=resumed activated=${slot.activated} '
|
'[feed-handoff] phase=resumed activated=${slot.activated} '
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
import '../../config/app_config.dart';
|
import '../../config/app_config.dart';
|
||||||
@@ -68,6 +69,13 @@ class VideoPlayerPort implements PlayerPort {
|
|||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
if (_disposed) return;
|
if (_disposed) return;
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
if (kDebugMode) {
|
||||||
|
debugPrint(
|
||||||
|
'[player-controller] phase=dispose '
|
||||||
|
'controller=${identityHashCode(controller)} '
|
||||||
|
'source=${controller.dataSource}\n${StackTrace.current}',
|
||||||
|
);
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await _initialization;
|
await _initialization;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
@@ -221,30 +222,36 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
/// 独立播放页覆盖首页时只暂停并保存进度,首页 session 与 controller 保持存活。
|
/// 独立播放页覆盖首页时只暂停并保存进度,首页 session 与 controller 保持存活。
|
||||||
Future<void> pauseForFullPlayer() async {
|
Future<void> pauseForFullPlayer() async {
|
||||||
if (!_started) return;
|
if (!_started) return;
|
||||||
|
_logFeedHandoff('pause');
|
||||||
cancelScrub();
|
cancelScrub();
|
||||||
await session.pauseForRouteHandoff();
|
await session.pauseForRouteHandoff();
|
||||||
|
_logFeedHandoff('paused');
|
||||||
await saveCurrentProgress();
|
await saveCurrentProgress();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 返回首页前把仍存活的 feed controller 定位到第 1 集起点。
|
|
||||||
Future<void> prepareFeedResume() async {
|
|
||||||
if (!_started) return;
|
|
||||||
await session.playbackSettled;
|
|
||||||
final player = session.active;
|
|
||||||
if (player is VideoPlayerPort && player.controller.value.isInitialized) {
|
|
||||||
await player.seekTo(Duration.zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 独立播放页关闭并恢复首页路由后,等原生播放器实际恢复再结束交接。
|
/// 独立播放页关闭并恢复首页路由后,等原生播放器实际恢复再结束交接。
|
||||||
Future<void> resumeAfterFullPlayer() async {
|
Future<void> resumeAfterFullPlayer() async {
|
||||||
if (!_started) return;
|
if (!_started) return;
|
||||||
|
_logFeedHandoff('resume');
|
||||||
if (visibleIndices.isNotEmpty) {
|
if (visibleIndices.isNotEmpty) {
|
||||||
await session.resumeForRouteHandoff();
|
await session.resumeForRouteHandoff();
|
||||||
} else {
|
} else {
|
||||||
session.setRouteVisible(false);
|
session.setRouteVisible(false);
|
||||||
await session.playbackSettled;
|
await session.playbackSettled;
|
||||||
}
|
}
|
||||||
|
_logFeedHandoff('resumed');
|
||||||
|
}
|
||||||
|
|
||||||
|
void _logFeedHandoff(String phase) {
|
||||||
|
if (!kDebugMode) return;
|
||||||
|
final player = session.active;
|
||||||
|
final video = player is VideoPlayerPort ? player : null;
|
||||||
|
debugPrint(
|
||||||
|
'[feed-session] phase=$phase session=${identityHashCode(session)} '
|
||||||
|
'index=${session.currentIndex} episode=${session.currentEpisode.id} '
|
||||||
|
'controller=${video == null ? '-' : identityHashCode(video.controller)} '
|
||||||
|
'live=${session.livePlayerCount}',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 串行替换首页 feed 队列;频道变化时重置到第 1 个,刷新时尽量保留当前剧。
|
/// 串行替换首页 feed 队列;频道变化时重置到第 1 个,刷新时尽量保留当前剧。
|
||||||
@@ -283,7 +290,6 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
activate: activatePlayer,
|
activate: activatePlayer,
|
||||||
coordinator: coordinator,
|
coordinator: coordinator,
|
||||||
waitForFrame: _waitForFrame,
|
waitForFrame: _waitForFrame,
|
||||||
warmNeighborsEnabled: !homeMode,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1230,7 +1236,6 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (!isMounted()) return;
|
if (!isMounted()) return;
|
||||||
await prepareFeedResume();
|
|
||||||
await resumeAfterFullPlayer();
|
await resumeAfterFullPlayer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,12 +57,8 @@ const double _playerLoadingStrokeWidth = 2;
|
|||||||
const double _feedRefreshTopGap = 56;
|
const double _feedRefreshTopGap = 56;
|
||||||
|
|
||||||
class PlayerPageHandoff {
|
class PlayerPageHandoff {
|
||||||
PlayerPageHandoff({
|
PlayerPageHandoff({required this.sourceUserWantsPlay});
|
||||||
required this.prepareSource,
|
|
||||||
required this.sourceUserWantsPlay,
|
|
||||||
});
|
|
||||||
|
|
||||||
final Future<void> Function() prepareSource;
|
|
||||||
final bool sourceUserWantsPlay;
|
final bool sourceUserWantsPlay;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,8 +141,6 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
DateTime? _swipeStartedAt;
|
DateTime? _swipeStartedAt;
|
||||||
bool _swipePrewarmCaptured = false;
|
bool _swipePrewarmCaptured = false;
|
||||||
bool _presentationActive = false;
|
bool _presentationActive = false;
|
||||||
bool _presentationSuspended = false;
|
|
||||||
bool _presentationReturning = false;
|
|
||||||
bool _leavingHandoff = false;
|
bool _leavingHandoff = false;
|
||||||
bool _preparingFullPlayer = false;
|
bool _preparingFullPlayer = false;
|
||||||
final Set<String> _precachedPosterUrls = {};
|
final Set<String> _precachedPosterUrls = {};
|
||||||
@@ -538,8 +532,6 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPlayer(BuildContext context) {
|
Widget _buildPlayer(BuildContext context) {
|
||||||
if (_presentationSuspended) return const SizedBox.expand();
|
|
||||||
if (_presentationReturning) return _buildPresentationReturnLayer();
|
|
||||||
final interactions = context.watch<InteractionController>();
|
final interactions = context.watch<InteractionController>();
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final indices = logic.visibleIndices;
|
final indices = logic.visibleIndices;
|
||||||
@@ -736,17 +728,6 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPresentationReturnLayer() {
|
|
||||||
final indices = logic.visibleIndices;
|
|
||||||
return AnimatedBuilder(
|
|
||||||
animation: logic.session,
|
|
||||||
builder: (context, _) => PlayerRenderStack(
|
|
||||||
pageController: _pageController,
|
|
||||||
slots: _renderSlots(indices),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
PlayerRenderSlotData _renderSlot(
|
PlayerRenderSlotData _renderSlot(
|
||||||
PlayerRenderSlotRole role,
|
PlayerRenderSlotRole role,
|
||||||
int? index,
|
int? index,
|
||||||
@@ -1191,10 +1172,7 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
_preparingFullPlayer = false;
|
_preparingFullPlayer = false;
|
||||||
}
|
}
|
||||||
if (!mounted || !context.mounted) return;
|
if (!mounted || !context.mounted) return;
|
||||||
final handoff = PlayerPageHandoff(
|
final handoff = PlayerPageHandoff(sourceUserWantsPlay: sourceUserWantsPlay);
|
||||||
prepareSource: _prepareSourceAfterFullPlayer,
|
|
||||||
sourceUserWantsPlay: sourceUserWantsPlay,
|
|
||||||
);
|
|
||||||
_logFullPlayerRoute('present');
|
_logFullPlayerRoute('present');
|
||||||
setState(() => _presentationActive = true);
|
setState(() => _presentationActive = true);
|
||||||
final route = Navigator.of(context).push<void>(
|
final route = Navigator.of(context).push<void>(
|
||||||
@@ -1208,41 +1186,19 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await WidgetsBinding.instance.endOfFrame;
|
|
||||||
if (mounted) {
|
|
||||||
setState(() => _presentationSuspended = true);
|
|
||||||
await WidgetsBinding.instance.endOfFrame;
|
|
||||||
}
|
|
||||||
await route;
|
await route;
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
logic.attach(widget, isMounted: () => mounted);
|
logic.attach(widget, isMounted: () => mounted);
|
||||||
setState(() {
|
setState(() => _presentationActive = false);
|
||||||
_presentationActive = false;
|
|
||||||
_presentationSuspended = false;
|
|
||||||
_presentationReturning = false;
|
|
||||||
});
|
|
||||||
await logic.resumeAfterFullPlayer();
|
await logic.resumeAfterFullPlayer();
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_logFullPlayerRoute('restored');
|
_logFullPlayerRoute('restored');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _prepareSourceAfterFullPlayer() async {
|
|
||||||
if (!mounted) return;
|
|
||||||
logic.attach(widget, isMounted: () => mounted);
|
|
||||||
await logic.prepareFeedResume();
|
|
||||||
if (!mounted) return;
|
|
||||||
setState(() {
|
|
||||||
_presentationSuspended = false;
|
|
||||||
_presentationReturning = true;
|
|
||||||
});
|
|
||||||
await WidgetsBinding.instance.endOfFrame;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _leaveHandoff(BuildContext context) async {
|
Future<void> _leaveHandoff(BuildContext context) async {
|
||||||
if (_leavingHandoff) return;
|
if (_leavingHandoff) return;
|
||||||
setState(() => _leavingHandoff = true);
|
setState(() => _leavingHandoff = true);
|
||||||
await logic.session.pauseForRouteHandoff();
|
await logic.session.pauseForRouteHandoff();
|
||||||
await widget.handoff?.prepareSource();
|
|
||||||
if (context.mounted) Navigator.of(context).pop();
|
if (context.mounted) Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -348,7 +348,9 @@ class _PlayerVideoSurfaceState extends State<PlayerVideoSurface>
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
'[player-layer] ts=${DateTime.now().millisecondsSinceEpoch} '
|
'[player-layer] ts=${DateTime.now().millisecondsSinceEpoch} '
|
||||||
'slot=${widget.debugSlot} index=${widget.debugIndex ?? '-'} '
|
'slot=${widget.debugSlot} index=${widget.debugIndex ?? '-'} '
|
||||||
'phase=$phase init=${value?.isInitialized == true ? 1 : 0}',
|
'phase=$phase state=${identityHashCode(this)} '
|
||||||
|
'controller=${widget.controller == null ? '-' : identityHashCode(widget.controller)} '
|
||||||
|
'init=${value?.isInitialized == true ? 1 : 0}',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,33 +32,6 @@ void main() {
|
|||||||
await session.close();
|
await session.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should_keep_only_current_player_when_neighbor_warmup_disabled', () async {
|
|
||||||
final factory = FakeFactory();
|
|
||||||
final session = PlaybackSession(
|
|
||||||
episodes: episodes,
|
|
||||||
factory: factory.create,
|
|
||||||
canPlay: (episode) => episode.isFree,
|
|
||||||
warmNeighborsEnabled: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
await session.select(0, preloadIndex: 1);
|
|
||||||
await session.preloadSettled;
|
|
||||||
expect(factory.created, hasLength(1));
|
|
||||||
expect(factory.live, 1);
|
|
||||||
expect(session.preloadedIndex, isNull);
|
|
||||||
|
|
||||||
final first = factory.created.single;
|
|
||||||
await session.select(1, preloadIndex: 2, previousIndex: 0);
|
|
||||||
await session.preloadSettled;
|
|
||||||
|
|
||||||
expect(first.disposed, isTrue);
|
|
||||||
expect(factory.created, hasLength(2));
|
|
||||||
expect(factory.live, 1);
|
|
||||||
expect(session.livePlayerCount, 1);
|
|
||||||
expect(session.active, same(factory.created.last));
|
|
||||||
await session.close();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should_release_previous_player_immediately_when_selecting_locked', () async {
|
test('should_release_previous_player_immediately_when_selecting_locked', () async {
|
||||||
final lockedEpisodes = [
|
final lockedEpisodes = [
|
||||||
Episode(
|
Episode(
|
||||||
|
|||||||
@@ -109,14 +109,24 @@ void main() {
|
|||||||
final recommendPlayer = find.byKey(const Key('recommend-feed-player'));
|
final recommendPlayer = find.byKey(const Key('recommend-feed-player'));
|
||||||
final sourceState = tester.state(recommendPlayer);
|
final sourceState = tester.state(recommendPlayer);
|
||||||
final sourceLogic = _pageLogic(tester, recommendPlayer);
|
final sourceLogic = _pageLogic(tester, recommendPlayer);
|
||||||
|
await sourceLogic.session.preloadSettled;
|
||||||
|
await tester.pumpAndSettle();
|
||||||
final sourceIndex = sourceLogic.session.currentIndex;
|
final sourceIndex = sourceLogic.session.currentIndex;
|
||||||
final sourceDramaId = sourceLogic.currentDramaId;
|
final sourceDramaId = sourceLogic.currentDramaId;
|
||||||
final sourceEpisodeId = sourceLogic.session.currentEpisode.id;
|
final sourceEpisodeId = sourceLogic.session.currentEpisode.id;
|
||||||
final feedPlayer = sourceLogic.session.active! as _RouteVideoPlayer;
|
final feedPlayer = sourceLogic.session.active! as _RouteVideoPlayer;
|
||||||
final feedPlayCalls = feedPlayer.playCalls;
|
final feedPlayCalls = feedPlayer.playCalls;
|
||||||
|
final feedPositionBefore = feedPlayer.controller.value.position;
|
||||||
|
final sourceLiveBefore = sourceLogic.session.livePlayerCount;
|
||||||
|
final sourceCurrentSurface = find.descendant(
|
||||||
|
of: recommendPlayer,
|
||||||
|
matching: find.byKey(const ValueKey('player-render-slot-current')),
|
||||||
|
);
|
||||||
|
final sourceSurfaceState = tester.state(sourceCurrentSurface);
|
||||||
|
final sourceSurfaceElement = tester.element(sourceCurrentSurface);
|
||||||
expect(sourceDramaId, 'vengeance-demo');
|
expect(sourceDramaId, 'vengeance-demo');
|
||||||
expect(sourceIndex, 2);
|
expect(sourceIndex, 2);
|
||||||
expect(sourceLogic.session.livePlayerCount, 1);
|
expect(sourceLiveBefore, greaterThan(1));
|
||||||
|
|
||||||
await tester.tap(find.byKey(const Key('home-feed-full-series')));
|
await tester.tap(find.byKey(const Key('home-feed-full-series')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
@@ -124,6 +134,8 @@ void main() {
|
|||||||
final fullPlayer = find.byType(PlayerPage);
|
final fullPlayer = find.byType(PlayerPage);
|
||||||
final fullLogic = _pageLogic(tester, fullPlayer);
|
final fullLogic = _pageLogic(tester, fullPlayer);
|
||||||
expect(fullLogic.session, isNot(same(sourceLogic.session)));
|
expect(fullLogic.session, isNot(same(sourceLogic.session)));
|
||||||
|
final fullPlayerPort = fullLogic.session.active! as VideoPlayerPort;
|
||||||
|
expect(fullPlayerPort.controller, isNot(same(feedPlayer.controller)));
|
||||||
expect(feedPlayer.disposed, isFalse);
|
expect(feedPlayer.disposed, isFalse);
|
||||||
expect(feedPlayer.playing, isFalse);
|
expect(feedPlayer.playing, isFalse);
|
||||||
|
|
||||||
@@ -150,6 +162,8 @@ void main() {
|
|||||||
final requestsBeforeReturn = playback.requests.length;
|
final requestsBeforeReturn = playback.requests.length;
|
||||||
await tester.tap(find.byKey(const Key('player-back')));
|
await tester.tap(find.byKey(const Key('player-back')));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
await sourceLogic.session.preloadSettled;
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
final currentSurface = find.descendant(
|
final currentSurface = find.descendant(
|
||||||
of: recommendPlayer,
|
of: recommendPlayer,
|
||||||
@@ -170,11 +184,15 @@ void main() {
|
|||||||
expect(sourceLogic.session.loading, isFalse);
|
expect(sourceLogic.session.loading, isFalse);
|
||||||
expect(sourceLogic.session.activeSlotActivated, isTrue);
|
expect(sourceLogic.session.activeSlotActivated, isTrue);
|
||||||
expect(sourceLogic.session.activeSlotPlaying, isTrue);
|
expect(sourceLogic.session.activeSlotPlaying, isTrue);
|
||||||
expect(feedPlayer.seekCalls, 1);
|
expect(feedPlayer.seekCalls, 0);
|
||||||
expect(feedPlayer.playCalls, greaterThan(feedPlayCalls));
|
expect(feedPlayer.playCalls, greaterThan(feedPlayCalls));
|
||||||
expect(playback.requests.length, requestsBeforeReturn);
|
expect(playback.requests.length, requestsBeforeReturn);
|
||||||
expect(feedPlayer.controller.value.position, Duration.zero);
|
expect(feedPlayer.controller.value.position, feedPositionBefore);
|
||||||
expect(feedPlayer.playing, isTrue);
|
expect(feedPlayer.playing, isTrue);
|
||||||
|
expect(sourceLogic.session.livePlayerCount, sourceLiveBefore);
|
||||||
|
expect(sourceLogic.session.livePlayerCount, greaterThan(1));
|
||||||
|
expect(tester.state(currentSurface), same(sourceSurfaceState));
|
||||||
|
expect(tester.element(currentSurface), same(sourceSurfaceElement));
|
||||||
expect(
|
expect(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: currentSurface,
|
of: currentSurface,
|
||||||
|
|||||||
Reference in new issue
Block a user