fix(player): 收敛播放入口缓冲提示
This commit is contained in:
1 parent
f302b396b7
commit
7ad4681049
12 files changed
+218
-42
No files matched your search
@@ -0,0 +1,21 @@
|
|||||||
|
# 播放入口无缓冲提示
|
||||||
|
|
||||||
|
## 目标
|
||||||
|
|
||||||
|
- 未初始化进度条显示静态 inactive 轨道,不出现流动加载高光。
|
||||||
|
- 仅真实 buffering 持续超过 350ms 时显示白色高光。
|
||||||
|
- 起播定位阶段不叠加 session 居中 loading;只有 ticket loading 居中显示。
|
||||||
|
- 同集返回复用 controller;重建时 initialize→seek→play。
|
||||||
|
|
||||||
|
## 实施步骤
|
||||||
|
|
||||||
|
1. 调整 SeekProgressBar 与 _FeedProgressBar 的初始化/缓冲状态判断。
|
||||||
|
2. 删除 PlayerView 的 session loading 居中圈,仅保留 ticket loading。
|
||||||
|
3. preparePlayer 统一通过 VideoPlayerPort.seekTo 定位缓存位置。
|
||||||
|
4. 更新进度条、全屏入口、详情返回和重建顺序测试。
|
||||||
|
5. 执行 `dart analyze lib test` 与 `flutter test --no-pub`,回填 PROGRESS 后提交合并。
|
||||||
|
|
||||||
|
## 风险 / 待确认
|
||||||
|
|
||||||
|
- 真实持续网络卡顿仍保留缓冲高光。
|
||||||
|
- 锁定集继续沿用静态空轨道和禁用 seek 行为。
|
||||||
@@ -391,3 +391,38 @@ $ flutter test --no-pub
|
|||||||
解读:seek 完成后缓存与上报使用实际位置,越界目标被 clamp;seek 后立即
|
解读:seek 完成后缓存与上报使用实际位置,越界目标被 clamp;seek 后立即
|
||||||
进详情并返回仍保持目标位置。全量静态分析零告警,591 项测试全过。
|
进详情并返回仍保持目标位置。全量静态分析零告警,591 项测试全过。
|
||||||
`pubspec.lock` 已还原,未启停服务,未操作真机。
|
`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||||||
|
|
||||||
|
## 2026-10-22 播放入口无缓冲提示
|
||||||
|
|
||||||
|
- 已从 Seek 修复合并后的 `develop`(`f302b39`)创建 `feature/playback-entry-no-buffer`。
|
||||||
|
- 未初始化进度条改为静态 inactive 轨道,不显示流动高光且不可拖动。
|
||||||
|
- 仅 initialized 且 isBuffering 持续超过 350ms 时显示白色缓冲高光。
|
||||||
|
- PlayerView 删除 session 居中 loading,只保留 ticket loading。
|
||||||
|
- preparePlayer 改为初始化完成后先 seek 缓存位置再 play;详情返回复用原 controller。
|
||||||
|
|
||||||
|
执行命令与原始输出:
|
||||||
|
|
||||||
|
```text
|
||||||
|
$ flutter test --no-pub test/playback/seek_progress_bar_test.dart
|
||||||
|
00:00 +7: All tests passed!
|
||||||
|
|
||||||
|
$ flutter test --no-pub test/home/home_preview_layout_test.dart
|
||||||
|
00:01 +8: All tests passed!
|
||||||
|
|
||||||
|
$ flutter test --no-pub test/player/player_logic_test.dart
|
||||||
|
00:00 +40: All tests passed!
|
||||||
|
|
||||||
|
$ flutter test --no-pub test/player/player_fullscreen_test.dart
|
||||||
|
00:09 +37: All tests passed!
|
||||||
|
|
||||||
|
$ dart analyze lib test
|
||||||
|
Analyzing lib, test...
|
||||||
|
No issues found!
|
||||||
|
|
||||||
|
$ flutter test --no-pub
|
||||||
|
00:49 +593: All tests passed!
|
||||||
|
```
|
||||||
|
|
||||||
|
解读:短缓冲不闪、持续缓冲仍显示白色高光;未初始化/入口/返回/重建路径
|
||||||
|
均无多余 loading,重建顺序为 initialize→seek→play。全量静态分析零告警,
|
||||||
|
593 项测试全过。`pubspec.lock` 已还原,未启停服务,未操作真机。
|
||||||
@@ -677,7 +677,7 @@ class PlayerLogic extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
final saved = resumePositionFor(episode.id);
|
final saved = resumePositionFor(episode.id);
|
||||||
if (saved != null) {
|
if (saved != null) {
|
||||||
await player.controller.seekTo(
|
await player.seekTo(
|
||||||
clampResumePosition(saved, player.controller.value.duration),
|
clampResumePosition(saved, player.controller.value.duration),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -442,17 +442,6 @@ class _PlayerPageState extends State<PlayerPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (selected && logic.session.loading)
|
|
||||||
const Center(
|
|
||||||
child: SizedBox.square(
|
|
||||||
dimension: _playerLoadingSize,
|
|
||||||
child: CircularProgressIndicator(
|
|
||||||
key: Key('player-session-loading'),
|
|
||||||
color: AppColors.blue,
|
|
||||||
strokeWidth: _playerLoadingStrokeWidth,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (selected && !ready && logic.session.error != null)
|
if (selected && !ready && logic.session.error != null)
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|||||||
@@ -79,18 +79,16 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
final width = constraints.maxWidth;
|
final width = constraints.maxWidth;
|
||||||
final source = widget.controller;
|
final source = widget.controller;
|
||||||
final scrubbing = _preview != null;
|
final scrubbing = _preview != null;
|
||||||
final loading =
|
final loading = !widget.locked && !scrubbing && _buffering;
|
||||||
!widget.locked &&
|
|
||||||
(source == null ||
|
|
||||||
(!scrubbing && (!source.value.isInitialized || _buffering)));
|
|
||||||
final track = widget.locked
|
final track = widget.locked
|
||||||
? _inactiveTrack()
|
? _inactiveTrack()
|
||||||
: source == null
|
: source == null
|
||||||
? _loadingTrack()
|
? _inactiveTrack()
|
||||||
: ValueListenableBuilder<VideoPlayerValue>(
|
: ValueListenableBuilder<VideoPlayerValue>(
|
||||||
valueListenable: source,
|
valueListenable: source,
|
||||||
builder: (context, value, _) {
|
builder: (context, value, _) {
|
||||||
if (!scrubbing && (!value.isInitialized || _buffering)) {
|
if (!value.isInitialized) return _inactiveTrack();
|
||||||
|
if (!scrubbing && _buffering) {
|
||||||
return _loadingTrack();
|
return _loadingTrack();
|
||||||
}
|
}
|
||||||
final total = value.duration.inMilliseconds;
|
final total = value.duration.inMilliseconds;
|
||||||
@@ -102,7 +100,10 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
return _track(_preview ?? played, width);
|
return _track(_preview ?? played, width);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final interactive = !widget.locked && !loading;
|
final interactive =
|
||||||
|
!widget.locked &&
|
||||||
|
!loading &&
|
||||||
|
(source?.value.isInitialized ?? false);
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
key: const Key('home-feed-progress'),
|
key: const Key('home-feed-progress'),
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
@@ -148,7 +149,7 @@ class _FeedProgressBarState extends State<_FeedProgressBar> {
|
|||||||
if (!value.isInitialized) {
|
if (!value.isInitialized) {
|
||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_setBuffering(true, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!value.isBuffering) {
|
if (!value.isBuffering) {
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
if (!value.isInitialized) {
|
if (!value.isInitialized) {
|
||||||
_bufferTimer?.cancel();
|
_bufferTimer?.cancel();
|
||||||
_bufferTimer = null;
|
_bufferTimer = null;
|
||||||
_setBuffering(true, notify: notify);
|
_setBuffering(false, notify: notify);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!value.isBuffering) {
|
if (!value.isBuffering) {
|
||||||
@@ -181,8 +181,7 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
Widget _buildTrack(BuildContext context, VideoPlayerValue video) {
|
Widget _buildTrack(BuildContext context, VideoPlayerValue video) {
|
||||||
final total = video.duration.inMilliseconds;
|
final total = video.duration.inMilliseconds;
|
||||||
final scrubbing = _previewFraction.value != null;
|
final scrubbing = _previewFraction.value != null;
|
||||||
final loading =
|
final loading = !widget.locked && !scrubbing && _buffering;
|
||||||
!widget.locked && !scrubbing && (!video.isInitialized || _buffering);
|
|
||||||
final enabled =
|
final enabled =
|
||||||
!widget.locked &&
|
!widget.locked &&
|
||||||
video.isInitialized &&
|
video.isInitialized &&
|
||||||
@@ -223,7 +222,7 @@ class _SeekProgressBarState extends State<SeekProgressBar> {
|
|||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: trackAreaHeight,
|
height: trackAreaHeight,
|
||||||
child: widget.locked
|
child: widget.locked || !video.isInitialized
|
||||||
? _inactiveTrack(trackHeight)
|
? _inactiveTrack(trackHeight)
|
||||||
: loading
|
: loading
|
||||||
? Center(
|
? Center(
|
||||||
|
|||||||
@@ -180,7 +180,11 @@ void main() {
|
|||||||
.colorBlendMode,
|
.colorBlendMode,
|
||||||
BlendMode.srcIn,
|
BlendMode.srcIn,
|
||||||
);
|
);
|
||||||
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('home-feed-progress-inactive')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
expect(find.byKey(const Key('home-feed-progress-thumb')), findsNothing);
|
expect(find.byKey(const Key('home-feed-progress-thumb')), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -258,6 +262,65 @@ void main() {
|
|||||||
expect(seeks, 0);
|
expect(seeks, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('should_show_static_track_when_feed_video_is_uninitialized', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final controller = ValueNotifier<VideoPlayerValue>(
|
||||||
|
const VideoPlayerValue.uninitialized(),
|
||||||
|
);
|
||||||
|
addTearDown(controller.dispose);
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
theme: AppTheme.light(),
|
||||||
|
locale: const Locale('en'),
|
||||||
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
|
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||||
|
home: Builder(
|
||||||
|
builder: (context) => Scaffold(
|
||||||
|
body: HomeFeedOverlay(
|
||||||
|
l10n: AppLocalizations.of(context)!,
|
||||||
|
drama: const Drama(
|
||||||
|
id: 'loading-feed',
|
||||||
|
title: 'Loading Feed',
|
||||||
|
synopsis: 'synopsis',
|
||||||
|
posterUrl: '',
|
||||||
|
),
|
||||||
|
episode: const Episode(
|
||||||
|
id: 'loading-feed-1',
|
||||||
|
number: 1,
|
||||||
|
title: 'Loading',
|
||||||
|
isFree: true,
|
||||||
|
),
|
||||||
|
controller: controller,
|
||||||
|
locked: false,
|
||||||
|
liked: false,
|
||||||
|
saved: false,
|
||||||
|
visible: true,
|
||||||
|
boosting: false,
|
||||||
|
onTogglePlay: () {},
|
||||||
|
onBoostStart: () {},
|
||||||
|
onBoostEnd: () {},
|
||||||
|
onLike: () {},
|
||||||
|
onSave: () {},
|
||||||
|
onComments: () {},
|
||||||
|
onShare: () {},
|
||||||
|
onDetail: () {},
|
||||||
|
onOpenFullPlayer: () {},
|
||||||
|
onMore: () {},
|
||||||
|
onSeek: (_) {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(
|
||||||
|
find.byKey(const Key('home-feed-progress-inactive')),
|
||||||
|
findsOneWidget,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('should_seek_and_open_player_from_feed_actions', (tester) async {
|
testWidgets('should_seek_and_open_player_from_feed_actions', (tester) async {
|
||||||
var opened = 0;
|
var opened = 0;
|
||||||
Duration? seeked;
|
Duration? seeked;
|
||||||
|
|||||||
@@ -17,21 +17,26 @@ void main() {
|
|||||||
expect(seekPositionForFraction(1, duration), lessThan(duration));
|
expect(seekPositionForFraction(1, duration), lessThan(duration));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('should_show_loading_track_when_controller_is_missing', (
|
testWidgets('should_show_inactive_track_when_video_is_not_initialized', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
|
final video = ValueNotifier<VideoPlayerValue>(
|
||||||
|
const VideoPlayerValue.uninitialized(),
|
||||||
|
);
|
||||||
|
addTearDown(video.dispose);
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
MaterialApp(
|
MaterialApp(
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: SeekProgressBar(
|
body: SeekProgressBar(
|
||||||
valueListenable: null,
|
valueListenable: video,
|
||||||
semanticsLabel: 'Seek video',
|
semanticsLabel: 'Seek video',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
expect(find.byType(Slider), findsNothing);
|
expect(find.byType(Slider), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -99,13 +99,8 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
await tester.pump(const Duration(milliseconds: 100));
|
await tester.pump(const Duration(milliseconds: 100));
|
||||||
|
|
||||||
final sessionLoading = find.byKey(const Key('player-session-loading'));
|
expect(find.byKey(const Key('player-session-loading')), findsNothing);
|
||||||
expect(sessionLoading, findsOneWidget);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
expect(tester.getSize(sessionLoading), const Size(24, 24));
|
|
||||||
expect(
|
|
||||||
tester.widget<CircularProgressIndicator>(sessionLoading).strokeWidth,
|
|
||||||
2,
|
|
||||||
);
|
|
||||||
|
|
||||||
initialization.complete();
|
initialization.complete();
|
||||||
await tester.pumpWidget(const SizedBox.shrink());
|
await tester.pumpWidget(const SizedBox.shrink());
|
||||||
@@ -127,6 +122,8 @@ void main() {
|
|||||||
await _openSeriesPlayer(tester);
|
await _openSeriesPlayer(tester);
|
||||||
final player = players.firstWhere((item) => item.playing);
|
final player = players.firstWhere((item) => item.playing);
|
||||||
expect(player.playing, isTrue);
|
expect(player.playing, isTrue);
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
|
|
||||||
final navigator = tester.state<NavigatorState>(
|
final navigator = tester.state<NavigatorState>(
|
||||||
find.byType(Navigator).first,
|
find.byType(Navigator).first,
|
||||||
@@ -147,6 +144,8 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(player.playing, isTrue);
|
expect(player.playing, isTrue);
|
||||||
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('progress-inactive')), 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', (
|
||||||
@@ -571,7 +570,8 @@ void main() {
|
|||||||
tester.getSize(find.byKey(const Key('player-bottom-clear-screen'))),
|
tester.getSize(find.byKey(const Key('player-bottom-clear-screen'))),
|
||||||
const Size(40, 40),
|
const Size(40, 40),
|
||||||
);
|
);
|
||||||
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
expect(find.byType(Slider), findsNothing);
|
expect(find.byType(Slider), findsNothing);
|
||||||
|
|
||||||
expect(bottom.height, lessThanOrEqualTo(200));
|
expect(bottom.height, lessThanOrEqualTo(200));
|
||||||
@@ -1301,7 +1301,8 @@ void _expectFullscreenOverlay(WidgetTester tester) {
|
|||||||
expect(find.byKey(const Key('player-pip')), findsNothing);
|
expect(find.byKey(const Key('player-pip')), findsNothing);
|
||||||
expect(find.byKey(const Key('player-rail-comments')), findsOneWidget);
|
expect(find.byKey(const Key('player-rail-comments')), findsOneWidget);
|
||||||
expect(find.byKey(const Key('player-fullscreen-toggle')), findsNothing);
|
expect(find.byKey(const Key('player-fullscreen-toggle')), findsNothing);
|
||||||
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
expect(find.byType(Slider), findsNothing);
|
expect(find.byType(Slider), findsNothing);
|
||||||
expect(find.byKey(const Key('player-title')), findsNothing);
|
expect(find.byKey(const Key('player-title')), findsNothing);
|
||||||
expect(find.byKey(const Key('player-subtitle')), findsNothing);
|
expect(find.byKey(const Key('player-subtitle')), findsNothing);
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
// ignore: depend_on_referenced_packages
|
||||||
|
import 'package:video_player_platform_interface/video_player_platform_interface.dart';
|
||||||
import 'package:fruitshorts/core/data/models/drama.dart';
|
import 'package:fruitshorts/core/data/models/drama.dart';
|
||||||
import 'package:fruitshorts/core/data/models/play_ticket.dart';
|
import 'package:fruitshorts/core/data/models/play_ticket.dart';
|
||||||
import 'package:fruitshorts/core/data/models/player_port.dart';
|
import 'package:fruitshorts/core/data/models/player_port.dart';
|
||||||
@@ -287,6 +289,44 @@ void main() {
|
|||||||
await tester.pump();
|
await tester.pump();
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
testWidgets('should_seek_cached_position_before_play_when_rebuilding', (
|
||||||
|
tester,
|
||||||
|
) async {
|
||||||
|
final previousPlatform = VideoPlayerPlatform.instance;
|
||||||
|
VideoPlayerPlatform.instance = _VideoPlayerPlatformStub();
|
||||||
|
addTearDown(() => VideoPlayerPlatform.instance = previousPlatform);
|
||||||
|
final cache = PlaybackPositionCache();
|
||||||
|
cache.save(
|
||||||
|
episodeId: 'signal-1',
|
||||||
|
dramaId: 'signal',
|
||||||
|
position: const Duration(seconds: 42),
|
||||||
|
duration: const Duration(minutes: 1),
|
||||||
|
);
|
||||||
|
final factory = _VideoRecordingFactory();
|
||||||
|
late PlayerLogic logic;
|
||||||
|
await tester.runAsync(() async {
|
||||||
|
logic = _logic(FakeWatchHistory(), positionCache: cache);
|
||||||
|
logic.attach(
|
||||||
|
PlayerPage(drama: _drama('signal'), initialIndex: 0),
|
||||||
|
isMounted: () => true,
|
||||||
|
);
|
||||||
|
logic.start(
|
||||||
|
factory: factory.create,
|
||||||
|
coordinator: PlaybackCoordinator(),
|
||||||
|
);
|
||||||
|
await _drain(logic);
|
||||||
|
await pumpEventQueue();
|
||||||
|
await logic.session.playbackSettled;
|
||||||
|
await logic.session.preloadSettled;
|
||||||
|
});
|
||||||
|
|
||||||
|
final player = logic.session.active! as _VideoRecordingPlayer;
|
||||||
|
expect(player.events, containsAllInOrder(['initialize', 'seek', 'play']));
|
||||||
|
expect(player.controller.value.position, const Duration(seconds: 42));
|
||||||
|
logic.dispose();
|
||||||
|
await tester.pump();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should_play_local_file_without_ticket_when_episode_is_downloaded',
|
test('should_play_local_file_without_ticket_when_episode_is_downloaded',
|
||||||
@@ -1297,21 +1337,38 @@ class _SessionVideoSurface extends StatelessWidget {
|
|||||||
|
|
||||||
class _VideoRecordingFactory {
|
class _VideoRecordingFactory {
|
||||||
final List<_VideoRecordingPlayer> created = [];
|
final List<_VideoRecordingPlayer> created = [];
|
||||||
|
final List<String> events = [];
|
||||||
|
|
||||||
PlayerPort create(Episode episode) {
|
PlayerPort create(Episode episode) {
|
||||||
final player = _VideoRecordingPlayer(episode);
|
final player = _VideoRecordingPlayer(episode, events);
|
||||||
created.add(player);
|
created.add(player);
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _VideoPlayerPlatformStub extends VideoPlayerPlatform {
|
||||||
|
@override
|
||||||
|
Future<void> init() async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispose(int playerId) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setVolume(int playerId, double volume) async {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> setPlaybackSpeed(int playerId, double speed) async {}
|
||||||
|
}
|
||||||
|
|
||||||
class _VideoRecordingPlayer extends VideoPlayerPort {
|
class _VideoRecordingPlayer extends VideoPlayerPort {
|
||||||
_VideoRecordingPlayer(super.episode);
|
_VideoRecordingPlayer(super.episode, this.events);
|
||||||
|
|
||||||
bool disposed = false;
|
bool disposed = false;
|
||||||
|
final List<String> events;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
|
events.add('initialize');
|
||||||
controller.value = const VideoPlayerValue(
|
controller.value = const VideoPlayerValue(
|
||||||
duration: Duration(minutes: 1),
|
duration: Duration(minutes: 1),
|
||||||
size: Size(1280, 720),
|
size: Size(1280, 720),
|
||||||
@@ -1321,13 +1378,14 @@ class _VideoRecordingPlayer extends VideoPlayerPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> play() async {}
|
Future<void> play() async => events.add('play');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> pause() async {}
|
Future<void> pause() async => events.add('pause');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> seekTo(Duration position) async {
|
Future<void> seekTo(Duration position) async {
|
||||||
|
events.add('seek');
|
||||||
final current = controller.value;
|
final current = controller.value;
|
||||||
controller.value = VideoPlayerValue(
|
controller.value = VideoPlayerValue(
|
||||||
duration: current.duration,
|
duration: current.duration,
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ void main() {
|
|||||||
final active = logic.session.active;
|
final active = logic.session.active;
|
||||||
final index = logic.session.currentIndex;
|
final index = logic.session.currentIndex;
|
||||||
final player = factory.created.first;
|
final player = factory.created.first;
|
||||||
|
final createdBeforeDetail = factory.created.length;
|
||||||
logic
|
logic
|
||||||
..trackedPlayer = player
|
..trackedPlayer = player
|
||||||
..trackedEpisode = drama.episodes.first
|
..trackedEpisode = drama.episodes.first
|
||||||
@@ -106,6 +107,7 @@ void main() {
|
|||||||
expect(logic.session.active, same(active));
|
expect(logic.session.active, same(active));
|
||||||
expect(logic.session.currentIndex, index);
|
expect(logic.session.currentIndex, index);
|
||||||
expect(logic.session.userWantsPlay, isTrue);
|
expect(logic.session.userWantsPlay, isTrue);
|
||||||
|
expect(factory.created, hasLength(createdBeforeDetail));
|
||||||
expect(
|
expect(
|
||||||
player.controller.value.position,
|
player.controller.value.position,
|
||||||
const Duration(minutes: 1, seconds: 22),
|
const Duration(minutes: 1, seconds: 22),
|
||||||
@@ -125,6 +127,7 @@ void main() {
|
|||||||
expect(logic.session.active, same(active));
|
expect(logic.session.active, same(active));
|
||||||
expect(logic.session.currentIndex, index);
|
expect(logic.session.currentIndex, index);
|
||||||
expect(logic.session.userWantsPlay, isTrue);
|
expect(logic.session.userWantsPlay, isTrue);
|
||||||
|
expect(factory.created, hasLength(createdBeforeDetail));
|
||||||
expect(
|
expect(
|
||||||
player.controller.value.position,
|
player.controller.value.position,
|
||||||
const Duration(minutes: 1, seconds: 22),
|
const Duration(minutes: 1, seconds: 22),
|
||||||
|
|||||||
@@ -185,7 +185,8 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await _openTheaterPlayer(tester);
|
await _openTheaterPlayer(tester);
|
||||||
|
|
||||||
expect(find.byKey(const Key('progress-loading')), findsOneWidget);
|
expect(find.byKey(const Key('progress-loading')), findsNothing);
|
||||||
|
expect(find.byKey(const Key('progress-inactive')), findsOneWidget);
|
||||||
expect(find.byType(Slider), findsNothing);
|
expect(find.byType(Slider), findsNothing);
|
||||||
await tester.drag(find.byType(PageView), const Offset(0, -500));
|
await tester.drag(find.byType(PageView), const Offset(0, -500));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|||||||
Reference in new issue
Block a user