fix(player): 重构单层过渡并阻止锁定集串播
This commit is contained in:
1 parent
70d8e0f982
commit
0cb0ab0613
8 files changed
+657
-335
No files matched your search
@@ -11,12 +11,15 @@ import 'package:fruitshorts/core/data/models/watch_progress.dart';
|
||||
import 'package:fruitshorts/core/data/models/download_license.dart';
|
||||
import 'package:fruitshorts/core/data/models/download_policy.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/download_repository.dart';
|
||||
import 'package:fruitshorts/core/data/repositories/commerce_repository.dart';
|
||||
import 'package:fruitshorts/core/data/services/android_picture_in_picture.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_controller.dart';
|
||||
import 'package:fruitshorts/core/data/services/download_storage.dart';
|
||||
import 'package:fruitshorts/core/data/services/playback_session.dart';
|
||||
import 'package:fruitshorts/core/data/services/playback_position_cache.dart';
|
||||
import 'package:fruitshorts/core/data/services/video_player_port.dart';
|
||||
import 'package:fruitshorts/core/data/services/wallet_controller.dart';
|
||||
import 'package:fruitshorts/core/data/models/wallet.dart';
|
||||
import 'package:fruitshorts/pages/player/player_logic.dart';
|
||||
import 'package:fruitshorts/pages/player/player_view.dart';
|
||||
import 'package:fruitshorts/pages/player/widgets/player_video_surface.dart';
|
||||
@@ -46,6 +49,37 @@ Drama _drama(
|
||||
],
|
||||
);
|
||||
|
||||
Drama _gateDrama() => const Drama(
|
||||
id: 'gate',
|
||||
title: 'Gate',
|
||||
synopsis: 'Gate synopsis',
|
||||
posterUrl: '/files/gate.png',
|
||||
episodes: [
|
||||
Episode(
|
||||
id: 'gate-1',
|
||||
number: 1,
|
||||
title: 'Free',
|
||||
isFree: true,
|
||||
videoUrl: 'https://example.test/gate-1.mp4',
|
||||
),
|
||||
Episode(
|
||||
id: 'gate-2',
|
||||
number: 2,
|
||||
title: 'Locked',
|
||||
isFree: false,
|
||||
locked: true,
|
||||
videoUrl: 'https://example.test/gate-2.mp4',
|
||||
),
|
||||
Episode(
|
||||
id: 'gate-3',
|
||||
number: 3,
|
||||
title: 'Next',
|
||||
isFree: true,
|
||||
videoUrl: 'https://example.test/gate-3.mp4',
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
AndroidPictureInPicture _pip() =>
|
||||
AndroidPictureInPicture(channel: const MethodChannel('test/player_logic/pip'));
|
||||
|
||||
@@ -53,11 +87,13 @@ PlayerLogic _logic(
|
||||
FakeWatchHistory history, {
|
||||
PlaybackRepository? playback,
|
||||
PlaybackPositionCache? positionCache,
|
||||
WalletController? wallet,
|
||||
Future<void> Function()? waitForFrame,
|
||||
}) => PlayerLogic(
|
||||
history: history,
|
||||
playback: playback ?? FakePlaybackRepository(),
|
||||
pictureInPicture: _pip(),
|
||||
wallet: wallet,
|
||||
positionCache: positionCache,
|
||||
waitForFrame: waitForFrame ?? () async {},
|
||||
);
|
||||
@@ -581,6 +617,88 @@ void main() {
|
||||
await _drain(logic);
|
||||
});
|
||||
|
||||
test('should_pause_and_release_when_swiping_to_locked_episode', () async {
|
||||
final factory = _RecordingFactory();
|
||||
final playback = FakePlaybackRepository();
|
||||
final logic = _logic(FakeWatchHistory(), playback: playback);
|
||||
logic.attach(
|
||||
PlayerPage(drama: _gateDrama(), initialIndex: 0),
|
||||
isMounted: () => true,
|
||||
);
|
||||
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
|
||||
await _drain(logic);
|
||||
final previous = factory.created.single;
|
||||
|
||||
playback.ticket = const PlayTicket(
|
||||
episodeId: 'gate-2',
|
||||
accessible: false,
|
||||
accessType: PlayTicket.coinRequired,
|
||||
unlockCoin: 30,
|
||||
);
|
||||
await logic.selectEpisode(1);
|
||||
await _drain(logic);
|
||||
|
||||
expect(previous.pauses, greaterThan(0));
|
||||
expect(previous.disposed, isTrue);
|
||||
expect(logic.session.active, isNull);
|
||||
expect(factory.live, 0);
|
||||
expect(factory.created, hasLength(1));
|
||||
expect(logic.blockedEpisodeId, 'gate-2');
|
||||
|
||||
playback.ticket = null;
|
||||
await logic.selectEpisode(2);
|
||||
await _drain(logic);
|
||||
expect(logic.session.active, isNotNull);
|
||||
expect(factory.created.last.plays, greaterThan(0));
|
||||
|
||||
logic.dispose();
|
||||
await _drain(logic);
|
||||
});
|
||||
|
||||
test('should_start_locked_episode_after_unlock_succeeds', () async {
|
||||
final factory = _RecordingFactory();
|
||||
final playback = FakePlaybackRepository();
|
||||
final wallet = WalletController(_UnlockCommerceRepository());
|
||||
final logic = _logic(
|
||||
FakeWatchHistory(),
|
||||
playback: playback,
|
||||
wallet: wallet,
|
||||
);
|
||||
logic.attach(
|
||||
PlayerPage(drama: _gateDrama(), initialIndex: 0),
|
||||
isMounted: () => true,
|
||||
);
|
||||
logic.start(factory: factory.create, coordinator: PlaybackCoordinator());
|
||||
await _drain(logic);
|
||||
playback.ticket = const PlayTicket(
|
||||
episodeId: 'gate-2',
|
||||
accessible: false,
|
||||
accessType: PlayTicket.coinRequired,
|
||||
unlockCoin: 30,
|
||||
);
|
||||
await logic.selectEpisode(1);
|
||||
await _drain(logic);
|
||||
expect(logic.session.active, isNull);
|
||||
|
||||
playback.ticket = const PlayTicket(
|
||||
episodeId: 'gate-2',
|
||||
accessible: true,
|
||||
accessType: PlayTicket.coinRequired,
|
||||
playUrl: '/files/gate-2.mp4',
|
||||
);
|
||||
final unlocked = await logic.unlockEpisode(1);
|
||||
await _drain(logic);
|
||||
|
||||
expect(unlocked, isTrue);
|
||||
expect(logic.isEpisodeLocked(logic.sessionEpisodes[1]), isFalse);
|
||||
final active = logic.session.active;
|
||||
expect(active, isA<_RecordingPlayer>());
|
||||
expect((active! as _RecordingPlayer).plays, greaterThan(0));
|
||||
|
||||
logic.dispose();
|
||||
await _drain(logic);
|
||||
});
|
||||
|
||||
test('should_preload_only_what_follows_when_starting_from_a_later_index', () async {
|
||||
final factory = _RecordingFactory();
|
||||
final drama = _drama('signal');
|
||||
@@ -1403,3 +1521,23 @@ class _VideoRecordingPlayer extends VideoPlayerPort {
|
||||
await controller.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _UnlockCommerceRepository implements CommerceRepository {
|
||||
@override
|
||||
Future<WalletSnapshot> fetchWallet() async => const WalletSnapshot(
|
||||
wallet: CoinWallet(
|
||||
coinBalance: 100,
|
||||
totalRechargedCoin: 0,
|
||||
totalConsumedCoin: 0,
|
||||
totalRefundedCoin: 0,
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Future<UnlockResult> unlock(String episodeId) async => const UnlockResult(
|
||||
unlocked: true,
|
||||
accessType: PlayTicket.coinRequired,
|
||||
coinCost: 30,
|
||||
coinBalance: 70,
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user