feat(player): 增加进度条拖动画面预览

This commit is contained in:
Zhou Weidong committed 2026-10-22 11:03:17 +08:00
1 parent d06dce9753
commit 51c56d884b
15 files changed
+1493 -77

No files matched your search

+86
View File
@@ -1,8 +1,12 @@
// Author: fruit.com.cn
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:fruitshorts/core/data/services/playback_thumbnail_service.dart';
import 'package:fruitshorts/core/theme/app_colors.dart';
import 'package:fruitshorts/pages/player/widgets/scrub_preview_card.dart';
import 'package:fruitshorts/pages/player/widgets/seek_progress_bar.dart';
import 'package:video_player/video_player.dart';
@@ -17,6 +21,18 @@ void main() {
expect(seekPositionForFraction(1, duration), lessThan(duration));
});
test('should_clamp_preview_card_inside_track_safe_margins', () {
expect(
scrubPreviewLeft(trackWidth: 300, fraction: 0),
scrubPreviewSafeMargin,
);
expect(
scrubPreviewLeft(trackWidth: 300, fraction: 1),
300 - scrubPreviewWidth - scrubPreviewSafeMargin,
);
expect(scrubPreviewLeft(trackWidth: 100, fraction: 0.5), 0);
});
testWidgets('should_show_inactive_track_when_video_is_not_initialized', (
tester,
) async {
@@ -233,6 +249,59 @@ void main() {
},
);
testWidgets('should_show_frame_card_with_cover_then_hide_on_release', (
tester,
) async {
final video = ValueNotifier<VideoPlayerValue>(
const VideoPlayerValue(
duration: Duration(seconds: 12),
position: Duration(seconds: 3),
isInitialized: true,
),
);
final service = _thumbnailService();
addTearDown(video.dispose);
addTearDown(service.dispose);
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: SizedBox(
width: 300,
child: SeekProgressBar(
valueListenable: video,
semanticsLabel: 'Seek video',
episodeId: 'episode-1',
posterUrl: '',
thumbnailService: service,
onSeek: (_) async {},
),
),
),
),
),
);
final gesture = await tester.startGesture(
tester.getCenter(find.byType(Slider)),
);
await gesture.moveBy(const Offset(80, 0));
await tester.pump();
expect(find.byKey(const Key('scrub-preview-card')), findsOneWidget);
expect(find.byKey(const Key('scrub-preview-poster')), findsOneWidget);
expect(find.byKey(const Key('scrub-preview-frame')), findsNothing);
expect(find.textContaining('/ 00:12'), findsOneWidget);
expect(
tester.getSize(find.byKey(const Key('scrub-preview-card'))).width,
120,
);
await gesture.up();
await tester.pump();
expect(find.byKey(const Key('scrub-preview-card')), findsNothing);
});
testWidgets('should_show_time_only_while_dragging_when_seek_bar_is_compact', (
tester,
) async {
@@ -267,3 +336,20 @@ void main() {
expect(find.textContaining('00:12'), findsNothing);
});
}
PlaybackThumbnailService _thumbnailService() {
return PlaybackThumbnailService(
frameLoader: _NullThumbnailLoader(),
sourceResolver: (_) =>
const PlaybackThumbnailSource(remoteUrl: 'https://example/video.mp4'),
prefetchDelay: const Duration(hours: 1),
);
}
class _NullThumbnailLoader implements PlaybackThumbnailFrameLoader {
@override
Future<ByteData?> load(PlaybackThumbnailFrameRequest request) async => null;
@override
void dispose() {}
}