corrects problem where google cast was disconnected from player state when app launched from head unit

This commit is contained in:
2026-05-16 23:45:11 -07:00
parent 1c7e27b83d
commit a2b5da3669
4 changed files with 361 additions and 46 deletions
+51 -3
View File
@@ -20,6 +20,9 @@ class CastingBusinessObject extends ChangeNotifier {
CastingPlaybackStatus _playbackStatus = CastingPlaybackStatus.unknown;
String? _connectedDeviceName;
String? _lastError;
/// Callback for when native side requests local audio to stop
VoidCallback? onStopLocalAudioRequested;
CastingStatus get status => _status;
CastingPlaybackStatus get playbackStatus => _playbackStatus;
@@ -55,6 +58,18 @@ class CastingBusinessObject extends ChangeNotifier {
},
);
// Set up handler for native method calls (e.g., stopLocalAudio)
_methodChannel.setMethodCallHandler(_handleMethodCall);
await refreshStatus();
}
Future<void> refreshStatus() async {
if (!isSupportedOnThisPlatform) {
_setStatus(CastingStatus.unavailable);
return;
}
try {
final dynamic status = await _methodChannel.invokeMethod<dynamic>(
'getStatus',
@@ -66,7 +81,12 @@ class CastingBusinessObject extends ChangeNotifier {
}
}
Future<void> showDevicePicker() async {
Future<void> showDevicePicker({
bool autoplay = false,
String? streamUrl,
String? title,
String? subtitle,
}) async {
if (!isSupportedOnThisPlatform) {
_setStatus(CastingStatus.unavailable);
return;
@@ -77,7 +97,12 @@ class CastingBusinessObject extends ChangeNotifier {
}
try {
await _methodChannel.invokeMethod<void>('showDevicePicker');
await _methodChannel.invokeMethod<void>('showDevicePicker', {
'autoplay': autoplay,
'streamUrl': streamUrl,
'title': title,
'subtitle': subtitle,
});
} on PlatformException catch (error) {
_lastError = error.message ?? error.code;
_setStatus(CastingStatus.error);
@@ -102,7 +127,10 @@ class CastingBusinessObject extends ChangeNotifier {
});
} on PlatformException catch (error) {
_lastError = error.message ?? error.code;
_setStatus(CastingStatus.error);
// Do NOT set status to error here — native emits its own status events.
// Overriding status from Dart corrupts the UI when native considers the
// session still connected (e.g. during a retry after a race condition).
rethrow;
}
}
@@ -146,6 +174,8 @@ class CastingBusinessObject extends ChangeNotifier {
if (deviceName != null && deviceName.isNotEmpty) {
_connectedDeviceName = deviceName;
} else if (status != 'connected') {
_connectedDeviceName = null;
}
if (message != null && message.isNotEmpty) {
@@ -210,4 +240,22 @@ class CastingBusinessObject extends ChangeNotifier {
_eventSubscription?.cancel();
super.dispose();
}
/// Handles method calls from native code
Future<dynamic> _handleMethodCall(MethodCall call) async {
switch (call.method) {
case 'stopLocalAudio':
onStopLocalAudioRequested?.call();
return null;
case 'stopAudioService':
// Stop the audio service directly (called when transferring to cast)
onStopLocalAudioRequested?.call();
return null;
case 'resumeLocalAudio':
// Audio service is resuming after cast disconnect; no action needed on Flutter side
return null;
default:
return null;
}
}
}