refactors design to flutter driven service core. #6
+45
-4
@@ -5,6 +5,8 @@ import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.support.v4.media.MediaBrowserCompat
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
@@ -43,8 +45,10 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
private lateinit var mediaSession: MediaSessionCompat
|
||||
private var audioServiceBrowser: MediaBrowserCompat? = null
|
||||
private var audioServiceController: MediaControllerCompat? = null
|
||||
private val mainHandler = Handler(Looper.getMainLooper())
|
||||
private var pendingPlayRequest: Boolean = false
|
||||
private var pendingStopRequest: Boolean = false
|
||||
private var reconnectScheduled: Boolean = false
|
||||
|
||||
private val mediaControllerCallback = object : MediaControllerCompat.Callback() {
|
||||
override fun onPlaybackStateChanged(state: PlaybackStateCompat?) {
|
||||
@@ -75,12 +79,14 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
|
||||
override fun onConnectionSuspended() {
|
||||
Log.w(TAG, "audio_service browser connection suspended")
|
||||
disconnectFromFlutterAudioService()
|
||||
handleControllerDisconnected()
|
||||
scheduleBrowserReconnect()
|
||||
}
|
||||
|
||||
override fun onConnectionFailed() {
|
||||
Log.e(TAG, "audio_service browser connection failed")
|
||||
disconnectFromFlutterAudioService()
|
||||
handleControllerDisconnected()
|
||||
scheduleBrowserReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,11 +337,24 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
}
|
||||
|
||||
private fun connectToFlutterAudioService() {
|
||||
if (audioServiceController != null) {
|
||||
return
|
||||
}
|
||||
|
||||
val existing = audioServiceBrowser
|
||||
if (existing?.isConnected == true) {
|
||||
return
|
||||
}
|
||||
|
||||
if (existing != null) {
|
||||
try {
|
||||
existing.connect()
|
||||
} catch (error: Exception) {
|
||||
Log.w(TAG, "Failed reconnect attempt: ${error.message}")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val browser = MediaBrowserCompat(
|
||||
this,
|
||||
ComponentName(this, AudioService::class.java),
|
||||
@@ -348,8 +367,10 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
}
|
||||
|
||||
private fun disconnectFromFlutterAudioService() {
|
||||
audioServiceController?.unregisterCallback(mediaControllerCallback)
|
||||
audioServiceController = null
|
||||
reconnectScheduled = false
|
||||
mainHandler.removeCallbacksAndMessages(null)
|
||||
|
||||
handleControllerDisconnected()
|
||||
|
||||
audioServiceBrowser?.let { browser ->
|
||||
if (browser.isConnected) {
|
||||
@@ -359,6 +380,26 @@ class KryzAutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||
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 {
|
||||
val iconUri = Uri.parse("android.resource://${packageName}/mipmap/ic_launcher")
|
||||
return MediaMetadataCompat.Builder()
|
||||
|
||||
+10
-6
@@ -13,13 +13,16 @@ import 'streaming_audio_business_object.dart';
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
tz.initializeTimeZones();
|
||||
unawaited(StreamingAudioBusinessObject.initializeBackgroundAudio());
|
||||
await SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp,
|
||||
DeviceOrientation.portraitDown,
|
||||
]);
|
||||
|
||||
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 {
|
||||
@@ -36,6 +39,7 @@ class _MyAppState extends State<MyApp> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
unawaited(_audio.ensureInitialized());
|
||||
_casting.initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,6 @@ enum StreamingPlaybackStatus { stopped, buffering, playing }
|
||||
|
||||
class StreamingAudioBusinessObject extends ChangeNotifier {
|
||||
StreamingAudioBusinessObject() {
|
||||
if (_audioHandler == null && _audioHandlerInitFuture == null) {
|
||||
initializeBackgroundAudio();
|
||||
}
|
||||
|
||||
final initFuture = _audioHandlerInitFuture;
|
||||
if (initFuture != null) {
|
||||
initFuture.then((handler) {
|
||||
@@ -74,6 +70,10 @@ class StreamingAudioBusinessObject extends ChangeNotifier {
|
||||
String get currentTitle => _currentTitle;
|
||||
String get currentSubtitle => _currentSubtitle;
|
||||
|
||||
Future<void> ensureInitialized() async {
|
||||
await initializeBackgroundAudio();
|
||||
}
|
||||
|
||||
StreamingPlaybackStatus _mapPlaybackStateToStatus(PlaybackState state) {
|
||||
if (state.processingState == AudioProcessingState.completed) {
|
||||
return StreamingPlaybackStatus.stopped;
|
||||
|
||||
Reference in New Issue
Block a user