fix(player): 重构单层过渡并阻止锁定集串播
This commit is contained in:
1 parent
70d8e0f982
commit
0cb0ab0613
8 files changed
+657
-335
No files matched your search
@@ -7,179 +7,130 @@ import 'package:video_player/video_player.dart';
|
||||
import 'package:fruitshorts/pages/player/widgets/player_video_surface.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('should_keep_fallback_visible_before_video_is_ready', (
|
||||
testWidgets('should_show_black_without_placeholder_before_video', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpSurface(tester);
|
||||
|
||||
expect(_fallbackColor(tester), Colors.black);
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsNothing);
|
||||
expect(_opacity(tester), 0);
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsNothing);
|
||||
expect(find.byKey(const Key('player-video-layer')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_show_opening_frame_before_decode_is_ready', (
|
||||
testWidgets('should_show_exactly_one_placeholder_before_first_frame', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpSurface(tester, placeholder: _placeholder('opening'));
|
||||
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsOneWidget);
|
||||
expect(find.byKey(const Key('opening')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsNothing);
|
||||
expect(find.byKey(const Key('player-video-layer')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_skip_placeholder_for_warm_controller', (tester) async {
|
||||
await _pumpSurface(
|
||||
tester,
|
||||
openingFrame: const ColoredBox(
|
||||
key: Key('fake-opening'),
|
||||
color: Colors.red,
|
||||
),
|
||||
placeholder: _placeholder('opening'),
|
||||
warmFrameAvailable: true,
|
||||
thumbnailReady: true,
|
||||
value: _initializedValue,
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsOneWidget);
|
||||
expect(find.byKey(const Key('fake-opening')), findsOneWidget);
|
||||
expect(_opacity(tester), 0);
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsNothing);
|
||||
expect(find.byKey(const Key('player-video-layer')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_reveal_paused_frame_when_initialized', (tester) async {
|
||||
final controller = await _pumpSurface(tester);
|
||||
controller.value = const VideoPlayerValue(
|
||||
duration: Duration(minutes: 1),
|
||||
size: Size(1280, 720),
|
||||
position: Duration.zero,
|
||||
isInitialized: true,
|
||||
isPlaying: false,
|
||||
);
|
||||
await tester.pump();
|
||||
|
||||
expect(_opacity(tester), 1);
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('should_show_cover_until_first_playback_frame', (
|
||||
testWidgets('should_cross_fade_once_after_first_frame_is_confirmed', (
|
||||
tester,
|
||||
) async {
|
||||
final controller = await _pumpSurface(
|
||||
tester,
|
||||
openingFrame: const ColoredBox(color: Colors.red),
|
||||
holdOpeningUntilFirstFrame: true,
|
||||
);
|
||||
expect(_opacity(tester), 0);
|
||||
|
||||
controller.value = const VideoPlayerValue(
|
||||
duration: Duration(minutes: 1),
|
||||
size: Size(1280, 720),
|
||||
position: Duration.zero,
|
||||
isInitialized: true,
|
||||
isPlaying: true,
|
||||
placeholder: _placeholder('opening'),
|
||||
thumbnailReady: true,
|
||||
waitForPlaybackFrame: true,
|
||||
);
|
||||
controller.value = _readyValue(position: Duration.zero, playing: true);
|
||||
await tester.pump();
|
||||
|
||||
expect(_opacity(tester), 0);
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsNothing);
|
||||
|
||||
controller.value = const VideoPlayerValue(
|
||||
duration: Duration(minutes: 1),
|
||||
size: Size(1280, 720),
|
||||
position: Duration(milliseconds: 1),
|
||||
isInitialized: true,
|
||||
isPlaying: true,
|
||||
controller.value = _readyValue(
|
||||
position: const Duration(milliseconds: 1),
|
||||
playing: true,
|
||||
);
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
await tester.pump();
|
||||
|
||||
expect(_opacity(tester), 1);
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsNothing);
|
||||
expect(
|
||||
tester
|
||||
.widget<AnimatedOpacity>(
|
||||
find.byKey(const Key('player-video-opacity')),
|
||||
)
|
||||
.duration,
|
||||
Duration.zero,
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-layer')), findsNothing);
|
||||
expect(_crossFadeValue(tester), 0);
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 90));
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsOneWidget);
|
||||
expect(_crossFadeValue(tester), greaterThan(0));
|
||||
expect(_crossFadeValue(tester), lessThan(1));
|
||||
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
expect(find.byKey(const Key('player-single-placeholder')), findsNothing);
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsNothing);
|
||||
expect(find.byKey(const Key('player-video-layer')), findsOneWidget);
|
||||
|
||||
controller.value = _readyValue(
|
||||
position: const Duration(seconds: 1),
|
||||
playing: true,
|
||||
);
|
||||
await tester.pump();
|
||||
expect(find.byKey(const Key('player-video-crossfade')), findsNothing);
|
||||
expect(find.byKey(const Key('player-video-layer')), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('should_skip_cover_when_prewarmed_frame_is_available', (
|
||||
tester,
|
||||
) async {
|
||||
await _pumpSurface(
|
||||
tester,
|
||||
openingFrame: const ColoredBox(color: Colors.red),
|
||||
holdOpeningUntilFirstFrame: true,
|
||||
preparedFrameAvailable: true,
|
||||
value: const VideoPlayerValue(
|
||||
duration: Duration(minutes: 1),
|
||||
size: Size(1280, 720),
|
||||
position: Duration.zero,
|
||||
isInitialized: true,
|
||||
isPlaying: false,
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsNothing);
|
||||
expect(_opacity(tester), 1);
|
||||
});
|
||||
|
||||
testWidgets('should_show_video_error_above_retained_fallback', (
|
||||
tester,
|
||||
) async {
|
||||
testWidgets('should_keep_placeholder_above_video_error', (tester) async {
|
||||
await _pumpSurface(
|
||||
tester,
|
||||
placeholder: _placeholder('opening'),
|
||||
value: const VideoPlayerValue.erroneous('No route to host'),
|
||||
);
|
||||
|
||||
expect(find.byKey(const Key('opening')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-video-error')), findsOneWidget);
|
||||
expect(find.text('No route to host'), findsOneWidget);
|
||||
expect(_fallbackColor(tester), Colors.black);
|
||||
expect(_opacity(tester), 0);
|
||||
});
|
||||
|
||||
testWidgets('should_cover_new_video_only_while_handoff_is_held', (
|
||||
tester,
|
||||
) async {
|
||||
final current = VideoPlayerController.networkUrl(
|
||||
Uri.parse('http://192.168.31.187:8081/files/720.m3u8'),
|
||||
);
|
||||
final previous = VideoPlayerController.networkUrl(
|
||||
Uri.parse('http://192.168.31.187:8081/files/360.m3u8'),
|
||||
);
|
||||
addTearDown(current.dispose);
|
||||
addTearDown(previous.dispose);
|
||||
const ready = VideoPlayerValue(
|
||||
duration: Duration(minutes: 1),
|
||||
size: Size(1280, 720),
|
||||
position: Duration(seconds: 3),
|
||||
isInitialized: true,
|
||||
isPlaying: true,
|
||||
);
|
||||
current.value = ready;
|
||||
previous.value = ready;
|
||||
|
||||
Future<void> pump(VideoPlayerController? handoff) {
|
||||
return tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: PlayerVideoSurface(
|
||||
controller: current,
|
||||
handoff: handoff,
|
||||
videoUrl: current.dataSource,
|
||||
theater: false,
|
||||
openingFrame: const ColoredBox(color: Colors.red),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await pump(previous);
|
||||
expect(find.byKey(const Key('player-video-handoff')), findsOneWidget);
|
||||
expect(find.byKey(const Key('player-opening-frame')), findsNothing);
|
||||
expect(_opacity(tester), 1);
|
||||
|
||||
await pump(null);
|
||||
expect(find.byKey(const Key('player-video-handoff')), findsNothing);
|
||||
expect(find.byType(VideoPlayer), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _placeholder(String key) => ColoredBox(key: Key(key), color: Colors.red);
|
||||
|
||||
const VideoPlayerValue _initializedValue = VideoPlayerValue(
|
||||
duration: Duration(minutes: 1),
|
||||
size: Size(1280, 720),
|
||||
position: Duration.zero,
|
||||
isInitialized: true,
|
||||
isPlaying: false,
|
||||
);
|
||||
|
||||
VideoPlayerValue _readyValue({
|
||||
required Duration position,
|
||||
required bool playing,
|
||||
}) => VideoPlayerValue(
|
||||
duration: const Duration(minutes: 1),
|
||||
size: const Size(1280, 720),
|
||||
position: position,
|
||||
isInitialized: true,
|
||||
isPlaying: playing,
|
||||
);
|
||||
|
||||
Future<VideoPlayerController> _pumpSurface(
|
||||
WidgetTester tester, {
|
||||
VideoPlayerValue? value,
|
||||
Widget? openingFrame,
|
||||
bool holdOpeningUntilFirstFrame = false,
|
||||
bool preparedFrameAvailable = false,
|
||||
Widget? placeholder,
|
||||
bool warmFrameAvailable = false,
|
||||
bool thumbnailReady = false,
|
||||
bool waitForPlaybackFrame = false,
|
||||
}) async {
|
||||
final controller = VideoPlayerController.networkUrl(
|
||||
Uri.parse('http://192.168.31.187:8081/files/video.mp4'),
|
||||
@@ -193,9 +144,10 @@ Future<VideoPlayerController> _pumpSurface(
|
||||
controller: controller,
|
||||
videoUrl: controller.dataSource,
|
||||
theater: false,
|
||||
openingFrame: openingFrame,
|
||||
holdOpeningUntilFirstFrame: holdOpeningUntilFirstFrame,
|
||||
preparedFrameAvailable: preparedFrameAvailable,
|
||||
placeholder: placeholder,
|
||||
warmFrameAvailable: warmFrameAvailable,
|
||||
thumbnailReady: thumbnailReady,
|
||||
waitForPlaybackFrame: waitForPlaybackFrame,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -210,8 +162,9 @@ Color _fallbackColor(WidgetTester tester) {
|
||||
.color;
|
||||
}
|
||||
|
||||
double _opacity(WidgetTester tester) {
|
||||
double _crossFadeValue(WidgetTester tester) {
|
||||
return tester
|
||||
.widget<AnimatedOpacity>(find.byKey(const Key('player-video-opacity')))
|
||||
.opacity;
|
||||
.widget<FadeTransition>(find.byKey(const Key('player-video-crossfade')))
|
||||
.opacity
|
||||
.value;
|
||||
}
|
||||
Reference in new issue
Block a user