fix(player): isolate dispose teardown races

This commit is contained in:
edde746
2026-06-13 13:23:29 +02:00
parent 1e40d21bd8
commit 75ba6b0627
2 changed files with 46 additions and 5 deletions
+16 -4
View File
@@ -757,11 +757,23 @@ abstract class PlayerBase with PlayerStreamControllersMixin implements Player {
if (_disposed) return;
_disposed = true;
await _eventSubscription?.cancel();
try {
await _eventSubscription?.cancel();
} on PlatformException catch (e, st) {
appLogger.d('Player event stream already detached during dispose', error: e, stackTrace: st);
} on MissingPluginException catch (e, st) {
appLogger.d('Player event stream plugin missing during dispose', error: e, stackTrace: st);
}
await _logSubscription?.cancel();
await methodChannel.invokeMethod('dispose', {
'preserveDisplayMode': preserveDisplayMode,
}); // Direct call — already guarded by _disposed check above
try {
await methodChannel.invokeMethod('dispose', {
'preserveDisplayMode': preserveDisplayMode,
}); // Direct call — already guarded by _disposed check above
} on PlatformException catch (e, st) {
appLogger.w('Player native dispose failed during teardown', error: e, stackTrace: st);
} on MissingPluginException catch (e, st) {
appLogger.w('Player native dispose plugin missing during teardown', error: e, stackTrace: st);
}
await closeStreamControllers();
}
}
+30 -1
View File
@@ -417,6 +417,34 @@ void main() {
},
);
});
test('dispose continues when native event stream cancellation is already detached', () async {
final calls = <MethodCall>[];
await _withMockChannels(
methodChannelName: 'com.plezy/mpv_player',
eventChannelName: 'com.plezy/mpv_player/events',
methodHandler: (call) {
calls.add(call);
return Future.value(null);
},
eventHandler: (call) {
if (call.method == 'cancel') {
throw PlatformException(code: 'error', message: 'No active stream to cancel');
}
return Future.value(null);
},
testBody: () async {
final player = PlayerNative();
final playingDone = expectLater(player.streams.playing, emitsDone);
await expectLater(player.dispose(), completes);
await playingDone;
expect(calls.where((call) => call.method == 'dispose'), hasLength(1));
},
);
});
});
}
@@ -424,6 +452,7 @@ Future<void> _withMockChannels({
required String methodChannelName,
required String eventChannelName,
Future<Object?> Function(MethodCall call)? methodHandler,
Future<Object?> Function(MethodCall call)? eventHandler,
required Future<void> Function() testBody,
}) async {
final messenger = TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger;
@@ -449,7 +478,7 @@ Future<void> _withMockChannels({
}
},
);
messenger.setMockMethodCallHandler(eventChannel, (call) async => null);
messenger.setMockMethodCallHandler(eventChannel, eventHandler ?? (call) async => null);
try {
await testBody();