corrects problem where google cast was disconnected from player state when app launched from head unit
This commit is contained in:
+63
-18
@@ -73,12 +73,11 @@ class MainPage extends StatefulWidget {
|
||||
State<MainPage> createState() => _MainPageState();
|
||||
}
|
||||
|
||||
class _MainPageState extends State<MainPage> {
|
||||
class _MainPageState extends State<MainPage> with WidgetsBindingObserver {
|
||||
StreamingAudioBusinessObject get _audio => widget.audio;
|
||||
CastingBusinessObject get _casting => widget.casting;
|
||||
final LiveInfoBusinessObject _liveInfo = LiveInfoBusinessObject();
|
||||
static const List<int> _intervalOptions = <int>[5, 10, 15, 30, 60];
|
||||
bool _castStreamStartedForActiveSession = false;
|
||||
bool _isStartingCastStream = false;
|
||||
bool _pendingRemotePlay = false;
|
||||
|
||||
@@ -133,11 +132,33 @@ class _MainPageState extends State<MainPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
_audio.addListener(_handleAudioStateChanged);
|
||||
_casting.addListener(_handleCastingStateChanged);
|
||||
_liveInfo.addListener(_handleLiveInfoChanged);
|
||||
_liveInfo.start();
|
||||
_playbackChannel.setMethodCallHandler(_handlePlaybackMethodCall);
|
||||
|
||||
// When native code requests local audio to stop (cast media loaded)
|
||||
_casting.onStopLocalAudioRequested = () {
|
||||
unawaited(_audio.stop());
|
||||
};
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
if (state == AppLifecycleState.resumed) {
|
||||
// Re-initialize (not just refresh) to re-establish the EventChannel
|
||||
// subscription. In a cold-start Android Auto scenario the Flutter engine
|
||||
// boots headlessly before MainActivity.configureFlutterEngine() runs, so
|
||||
// the native EventChannel StreamHandler is not yet registered when
|
||||
// initialize() first fires. The "listen" message is dropped, eventSink
|
||||
// stays null, and all subsequent emitStatus() calls are silently lost.
|
||||
// Calling initialize() here cancels that dead subscription and creates a
|
||||
// fresh one now that the handler is registered; onListen immediately
|
||||
// re-emits statusPayload so the Dart state is fully re-synced.
|
||||
unawaited(_casting.initialize());
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handlePlaybackMethodCall(MethodCall call) async {
|
||||
@@ -172,15 +193,27 @@ class _MainPageState extends State<MainPage> {
|
||||
}
|
||||
|
||||
void _handleCastingStateChanged() {
|
||||
final currentStatus = _casting.status;
|
||||
|
||||
if (_casting.isPlaying || _casting.isBuffering) {
|
||||
_pendingRemotePlay = false;
|
||||
}
|
||||
|
||||
if (currentStatus != CastingStatus.connected) {
|
||||
_isStartingCastStream = false;
|
||||
}
|
||||
|
||||
if (_usesExternalCastPlayback &&
|
||||
(!_castStreamStartedForActiveSession || _pendingRemotePlay) &&
|
||||
_pendingRemotePlay &&
|
||||
!_isStartingCastStream) {
|
||||
// Only load when _pendingRemotePlay was set by _play() while connecting.
|
||||
// Do NOT trigger on justConnected — native pendingCastRequest handles
|
||||
// all autoplay from showDevicePicker(autoplay: true).
|
||||
_startCastingLiveStream();
|
||||
}
|
||||
|
||||
if (!_usesExternalCastPlayback) {
|
||||
_castStreamStartedForActiveSession = false;
|
||||
_isStartingCastStream = false;
|
||||
if (!_usesExternalCastPlayback &&
|
||||
currentStatus != CastingStatus.connecting) {
|
||||
_pendingRemotePlay = false;
|
||||
}
|
||||
|
||||
@@ -203,21 +236,24 @@ class _MainPageState extends State<MainPage> {
|
||||
subtitle: _audio.currentSubtitle,
|
||||
);
|
||||
|
||||
_castStreamStartedForActiveSession = true;
|
||||
_pendingRemotePlay = false;
|
||||
|
||||
if (_usesExternalCastPlayback && !_audio.isStopped) {
|
||||
await _audio.stop();
|
||||
}
|
||||
} catch (_) {
|
||||
_castStreamStartedForActiveSession = false;
|
||||
// State is already updated by the casting business object.
|
||||
_pendingRemotePlay = !_audio.isStopped;
|
||||
} finally {
|
||||
_isStartingCastStream = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _play() async {
|
||||
if (_casting.status == CastingStatus.connecting) {
|
||||
_pendingRemotePlay = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_usesExternalCastPlayback) {
|
||||
if (_casting.isPlaying || _isStartingCastStream) {
|
||||
return;
|
||||
@@ -227,12 +263,7 @@ class _MainPageState extends State<MainPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_usesExternalCastPlayback &&
|
||||
_casting.status == CastingStatus.connecting) {
|
||||
_pendingRemotePlay = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_pendingRemotePlay = false;
|
||||
await _audio.play();
|
||||
}
|
||||
|
||||
@@ -251,7 +282,21 @@ class _MainPageState extends State<MainPage> {
|
||||
return;
|
||||
}
|
||||
|
||||
await _casting.showDevicePicker();
|
||||
final shouldAutoplay = !_audio.isStopped;
|
||||
// Do NOT set _pendingRemotePlay when native handles autoplay.
|
||||
// The native pendingCastRequest already handles autoplay via
|
||||
// showDevicePicker(autoplay: true). Setting _pendingRemotePlay here
|
||||
// causes a duplicate load from Flutter that races the native load and
|
||||
// corrupts the cast status (especially in the Android Auto path).
|
||||
// _pendingRemotePlay is only set by _play() when the user explicitly
|
||||
// presses Play while the cast session is still connecting.
|
||||
_pendingRemotePlay = false;
|
||||
await _casting.showDevicePicker(
|
||||
autoplay: shouldAutoplay,
|
||||
streamUrl: shouldAutoplay ? _audio.streamUrl : null,
|
||||
title: shouldAutoplay ? _audio.currentTitle : null,
|
||||
subtitle: shouldAutoplay ? _audio.currentSubtitle : null,
|
||||
);
|
||||
}
|
||||
|
||||
String _castingLabel() {
|
||||
@@ -290,6 +335,7 @@ class _MainPageState extends State<MainPage> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
_audio.removeListener(_handleAudioStateChanged);
|
||||
_casting.removeListener(_handleCastingStateChanged);
|
||||
_liveInfo.removeListener(_handleLiveInfoChanged);
|
||||
@@ -412,8 +458,7 @@ class _MainPageState extends State<MainPage> {
|
||||
),
|
||||
ElevatedButton.icon(
|
||||
onPressed:
|
||||
_casting.status == CastingStatus.connecting ||
|
||||
_casting.status == CastingStatus.unavailable
|
||||
_casting.status == CastingStatus.connecting
|
||||
? null
|
||||
: _toggleCasting,
|
||||
icon: Icon(_castingIcon()),
|
||||
|
||||
Reference in New Issue
Block a user