import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:smplayer/core/contracts/cancellation.dart'; import 'package:smplayer/core/infra/player_engine/player_engine.dart'; import 'package:smplayer/features/player/session/player_engine_holder.dart'; import 'package:smplayer/features/player/session/player_engine_resolution.dart'; void main() { group('PlayerEngineHolder', () { test('同 kind 复用:第二次 acquire 不再调 create', () async { final holder = PlayerEngineHolder(); var createCalls = 0; _FakeEngine factory(String tag) { createCalls++; return _FakeEngine(tag); } final first = await holder.acquire( PlayerEngineKind.media3, () => factory('a'), ); final second = await holder.acquire( PlayerEngineKind.media3, () => factory('b'), ); expect(createCalls, 1); expect(identical(first, second), isTrue); expect((second as _FakeEngine).tag, 'a'); }); test('异 kind dispose+create:旧引擎先 dispose 再新建', () async { final holder = PlayerEngineHolder(); final disposeOrder = []; final acquireOrder = []; final fvp = _FakeEngine( 'fvp', onDispose: () async { await Future.delayed(const Duration(milliseconds: 10)); disposeOrder.add('fvp'); }, ); await holder.acquire(PlayerEngineKind.fvp, () => fvp); acquireOrder.add('fvp'); final media3 = _FakeEngine('media3'); final second = await holder.acquire(PlayerEngineKind.media3, () { acquireOrder.add('media3-create'); return media3; }); expect(disposeOrder, ['fvp']); expect(acquireOrder, ['fvp', 'media3-create']); expect(second, media3); expect(fvp.disposed, isTrue); expect(media3.disposed, isFalse); }); }); } class _FakeEngine implements PlayerEngine { final String tag; final Future Function()? onDispose; bool disposed = false; _FakeEngine(this.tag, {this.onDispose}); @override Future dispose() async { if (disposed) return; if (onDispose != null) await onDispose!(); disposed = true; } @override String get displayName => tag; @override bool get usesPlatformSurface => false; @override Future totalRxBytes() async => null; @override bool isNetworkStreamError(String error) => false; @override void configureHardwareDecoding(String mode) {} @override Future open( String url, { Duration start = Duration.zero, bool play = true, Map? headers, CancellationToken? cancel, }) async {} @override Future play() async {} @override Future pause() async {} @override Future stop() async {} @override Future seek(Duration position) async {} @override Future setRate(double rate) async {} @override Future setVolume(double volume) async {} @override Future toggleMute() async {} @override Future setAudioTrack(AudioTrack track) async {} @override Future setSubtitleTrack(SubtitleTrack track) async {} @override void setNativeSubtitleRendering(bool enabled) {} @override void setSubtitleStyle({ required double fontSize, required double bgOpacity, required int position, }) {} @override Widget buildVideoView(BuildContext context, ValueListenable fit) => const SizedBox.shrink(); Future getProperty(String name) async => ''; @override PlayerState get state => PlayerState(); @override PlayerStreams get stream => PlayerStreams(); @override ValueListenable get textureId => ValueNotifier(null); @override ValueListenable get textureVersion => ValueNotifier(0); @override ValueNotifier get videoSize => ValueNotifier(null); @override ValueListenable get debugStats => ValueNotifier(null); }