bug fixes for android auto

This commit is contained in:
2026-05-16 21:05:34 -07:00
parent 5ea7442c7c
commit 85740b399b
3 changed files with 59 additions and 14 deletions
@@ -5,6 +5,8 @@ import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.os.Handler
import android.os.Looper
import android.support.v4.media.MediaBrowserCompat import android.support.v4.media.MediaBrowserCompat
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
@@ -43,8 +45,10 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
private lateinit var mediaSession: MediaSessionCompat private lateinit var mediaSession: MediaSessionCompat
private var audioServiceBrowser: MediaBrowserCompat? = null private var audioServiceBrowser: MediaBrowserCompat? = null
private var audioServiceController: MediaControllerCompat? = null private var audioServiceController: MediaControllerCompat? = null
private val mainHandler = Handler(Looper.getMainLooper())
private var pendingPlayRequest: Boolean = false private var pendingPlayRequest: Boolean = false
private var pendingStopRequest: Boolean = false private var pendingStopRequest: Boolean = false
private var reconnectScheduled: Boolean = false
private val mediaControllerCallback = object : MediaControllerCompat.Callback() { private val mediaControllerCallback = object : MediaControllerCompat.Callback() {
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
@@ -75,12 +79,14 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
override fun onConnectionSuspended() { override fun onConnectionSuspended() {
Log.w(TAG, "audio_service browser connection suspended") Log.w(TAG, "audio_service browser connection suspended")
disconnectFromFlutterAudioService() handleControllerDisconnected()
scheduleBrowserReconnect()
} }
override fun onConnectionFailed() { override fun onConnectionFailed() {
Log.e(TAG, "audio_service browser connection failed") Log.e(TAG, "audio_service browser connection failed")
disconnectFromFlutterAudioService() handleControllerDisconnected()
scheduleBrowserReconnect()
} }
} }
@@ -331,11 +337,24 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
} }
private fun connectToFlutterAudioService() { private fun connectToFlutterAudioService() {
if (audioServiceController != null) {
return
}
val existing = audioServiceBrowser val existing = audioServiceBrowser
if (existing?.isConnected == true) { if (existing?.isConnected == true) {
return return
} }
if (existing != null) {
try {
existing.connect()
} catch (error: Exception) {
Log.w(TAG, "Failed reconnect attempt: ${error.message}")
}
return
}
val browser = MediaBrowserCompat( val browser = MediaBrowserCompat(
this, this,
ComponentName(this, AudioService::class.java), ComponentName(this, AudioService::class.java),
@@ -348,8 +367,10 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
} }
private fun disconnectFromFlutterAudioService() { private fun disconnectFromFlutterAudioService() {
audioServiceController?.unregisterCallback(mediaControllerCallback) reconnectScheduled = false
audioServiceController = null mainHandler.removeCallbacksAndMessages(null)
handleControllerDisconnected()
audioServiceBrowser?.let { browser -> audioServiceBrowser?.let { browser ->
if (browser.isConnected) { if (browser.isConnected) {
@@ -359,6 +380,26 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
audioServiceBrowser = null audioServiceBrowser = null
} }
private fun handleControllerDisconnected() {
audioServiceController?.unregisterCallback(mediaControllerCallback)
audioServiceController = null
}
private fun scheduleBrowserReconnect() {
if (reconnectScheduled) {
return
}
reconnectScheduled = true
mainHandler.postDelayed(
{
reconnectScheduled = false
connectToFlutterAudioService()
},
1200,
)
}
private fun buildLiveStreamMetadata(): MediaMetadataCompat { private fun buildLiveStreamMetadata(): MediaMetadataCompat {
val iconUri = Uri.parse("android.resource://${packageName}/mipmap/ic_launcher") val iconUri = Uri.parse("android.resource://${packageName}/mipmap/ic_launcher")
return MediaMetadataCompat.Builder() return MediaMetadataCompat.Builder()
+10 -6
View File
@@ -13,13 +13,16 @@ import 'streaming_audio_business_object.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
tz.initializeTimeZones(); tz.initializeTimeZones();
unawaited(StreamingAudioBusinessObject.initializeBackgroundAudio());
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
runApp(const MyApp()); runApp(const MyApp());
// Keep startup non-blocking so UI can render even if background media
// services are initializing after an Android Auto cold start.
unawaited(
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]),
);
} }
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
@@ -36,6 +39,7 @@ class _MyAppState extends State<MyApp> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
unawaited(_audio.ensureInitialized());
_casting.initialize(); _casting.initialize();
} }
+4 -4
View File
@@ -10,10 +10,6 @@ enum StreamingPlaybackStatus { stopped, buffering, playing }
class StreamingAudioBusinessObject extends ChangeNotifier { class StreamingAudioBusinessObject extends ChangeNotifier {
StreamingAudioBusinessObject() { StreamingAudioBusinessObject() {
if (_audioHandler == null && _audioHandlerInitFuture == null) {
initializeBackgroundAudio();
}
final initFuture = _audioHandlerInitFuture; final initFuture = _audioHandlerInitFuture;
if (initFuture != null) { if (initFuture != null) {
initFuture.then((handler) { initFuture.then((handler) {
@@ -74,6 +70,10 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
String get currentTitle => _currentTitle; String get currentTitle => _currentTitle;
String get currentSubtitle => _currentSubtitle; String get currentSubtitle => _currentSubtitle;
Future<void> ensureInitialized() async {
await initializeBackgroundAudio();
}
StreamingPlaybackStatus _mapPlaybackStateToStatus(PlaybackState state) { StreamingPlaybackStatus _mapPlaybackStateToStatus(PlaybackState state) {
if (state.processingState == AudioProcessingState.completed) { if (state.processingState == AudioProcessingState.completed) {
return StreamingPlaybackStatus.stopped; return StreamingPlaybackStatus.stopped;