refactors design to flutter driven service core. #6
+45
-4
@@ -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
@@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user