Updated Android Auto to use MediaPlayerService separately
Added some missing features found in the docs
This commit is contained in:
parent
db0669098c
commit
83c6b76d0a
|
@ -60,7 +60,7 @@
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".service.AutoMediaPlayerService"
|
android:name=".service.AutoMediaBrowserService"
|
||||||
android:label="Ultrasonic Auto Media Player Service"
|
android:label="Ultrasonic Auto Media Player Service"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
import org.moire.ultrasonic.subsonic.ImageLoaderProvider
|
||||||
|
import org.moire.ultrasonic.util.MediaSessionEventDistributor
|
||||||
import org.moire.ultrasonic.util.NowPlayingEventDistributor
|
import org.moire.ultrasonic.util.NowPlayingEventDistributor
|
||||||
import org.moire.ultrasonic.util.PermissionUtil
|
import org.moire.ultrasonic.util.PermissionUtil
|
||||||
import org.moire.ultrasonic.util.ThemeChangedEventDistributor
|
import org.moire.ultrasonic.util.ThemeChangedEventDistributor
|
||||||
|
@ -17,4 +18,5 @@ val applicationModule = module {
|
||||||
single { PermissionUtil(androidContext()) }
|
single { PermissionUtil(androidContext()) }
|
||||||
single { NowPlayingEventDistributor() }
|
single { NowPlayingEventDistributor() }
|
||||||
single { ThemeChangedEventDistributor() }
|
single { ThemeChangedEventDistributor() }
|
||||||
|
single { MediaSessionEventDistributor() }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
package org.moire.ultrasonic.service
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.support.v4.media.MediaBrowserCompat
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
import androidx.media.MediaBrowserServiceCompat
|
||||||
|
import androidx.media.utils.MediaConstants
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
import org.moire.ultrasonic.util.MediaSessionEventDistributor
|
||||||
|
import org.moire.ultrasonic.util.MediaSessionEventListener
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
||||||
|
const val MY_MEDIA_ROOT_ID = "MY_MEDIA_ROOT_ID"
|
||||||
|
const val MY_MEDIA_ALBUM_ID = "MY_MEDIA_ALBUM_ID"
|
||||||
|
const val MY_MEDIA_ARTIST_ID = "MY_MEDIA_ARTIST_ID"
|
||||||
|
const val MY_MEDIA_ALBUM_ITEM = "MY_MEDIA_ALBUM_ITEM"
|
||||||
|
const val MY_MEDIA_LIBRARY_ID = "MY_MEDIA_LIBRARY_ID"
|
||||||
|
const val MY_MEDIA_PLAYLIST_ID = "MY_MEDIA_PLAYLIST_ID"
|
||||||
|
|
||||||
|
class AutoMediaBrowserService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
|
private lateinit var mediaSessionEventListener: MediaSessionEventListener
|
||||||
|
private val mediaSessionEventDistributor: MediaSessionEventDistributor by inject()
|
||||||
|
private val lifecycleSupport: MediaPlayerLifecycleSupport by inject()
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
mediaSessionEventListener = object : MediaSessionEventListener {
|
||||||
|
override fun onMediaSessionTokenCreated(token: MediaSessionCompat.Token) {
|
||||||
|
Timber.i("AutoMediaBrowserService onMediaSessionTokenCreated called")
|
||||||
|
if (sessionToken == null) {
|
||||||
|
Timber.i("AutoMediaBrowserService onMediaSessionTokenCreated session token was null, set it to %s", token.toString())
|
||||||
|
sessionToken = token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayFromMediaIdRequested(mediaId: String?, extras: Bundle?) {
|
||||||
|
// TODO implement
|
||||||
|
Timber.i("AutoMediaBrowserService onPlayFromMediaIdRequested called")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlayFromSearchRequested(query: String?, extras: Bundle?) {
|
||||||
|
// TODO implement
|
||||||
|
Timber.i("AutoMediaBrowserService onPlayFromSearchRequested called")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaSessionEventDistributor.subscribe(mediaSessionEventListener)
|
||||||
|
|
||||||
|
val handler = Handler()
|
||||||
|
handler.postDelayed({
|
||||||
|
Timber.i("AutoMediaBrowserService starting lifecycleSupport and MediaPlayerService...")
|
||||||
|
// TODO it seems Android Auto handles autostart, but we must check that
|
||||||
|
lifecycleSupport.onCreate()
|
||||||
|
MediaPlayerService.getInstance()
|
||||||
|
}, 100)
|
||||||
|
|
||||||
|
Timber.i("AutoMediaBrowserService onCreate called")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
|
mediaSessionEventDistributor.unsubscribe(mediaSessionEventListener)
|
||||||
|
Timber.i("AutoMediaBrowserService onDestroy called")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetRoot(
|
||||||
|
clientPackageName: String,
|
||||||
|
clientUid: Int,
|
||||||
|
rootHints: Bundle?
|
||||||
|
): BrowserRoot? {
|
||||||
|
Timber.i("AutoMediaBrowserService onGetRoot called")
|
||||||
|
|
||||||
|
// TODO: The number of horizontal items available on the Andoid Auto screen. Check and handle.
|
||||||
|
val maximumRootChildLimit = rootHints!!.getInt(
|
||||||
|
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
|
||||||
|
4
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: The type of the horizontal items children on the Android Auto screen. Check and handle.
|
||||||
|
val supportedRootChildFlags = rootHints!!.getInt(
|
||||||
|
MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS,
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
|
|
||||||
|
val extras = Bundle()
|
||||||
|
extras.putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
||||||
|
extras.putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
||||||
|
|
||||||
|
return BrowserRoot(MY_MEDIA_ROOT_ID, extras)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadChildren(
|
||||||
|
parentId: String,
|
||||||
|
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
|
||||||
|
) {
|
||||||
|
Timber.i("AutoMediaBrowserService onLoadChildren called")
|
||||||
|
|
||||||
|
if (parentId == MY_MEDIA_ROOT_ID) {
|
||||||
|
return getRootItems(result)
|
||||||
|
} else {
|
||||||
|
return getAlbumLists(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSearch(
|
||||||
|
query: String,
|
||||||
|
extras: Bundle?,
|
||||||
|
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
|
||||||
|
) {
|
||||||
|
super.onSearch(query, extras, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRootItems(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||||
|
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
|
||||||
|
|
||||||
|
// TODO implement this with proper texts, icons, etc
|
||||||
|
mediaItems.add(
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Library")
|
||||||
|
.setMediaId(MY_MEDIA_LIBRARY_ID)
|
||||||
|
.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mediaItems.add(
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Artists")
|
||||||
|
.setMediaId(MY_MEDIA_ARTIST_ID)
|
||||||
|
.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mediaItems.add(
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Albums")
|
||||||
|
.setMediaId(MY_MEDIA_ALBUM_ID)
|
||||||
|
.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
mediaItems.add(
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Playlists")
|
||||||
|
.setMediaId(MY_MEDIA_PLAYLIST_ID)
|
||||||
|
.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.sendResult(mediaItems)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAlbumLists(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||||
|
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
|
||||||
|
|
||||||
|
val description = MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Test")
|
||||||
|
.setMediaId(MY_MEDIA_ALBUM_ITEM + 1)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
mediaItems.add(
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
description,
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
result.sendResult(mediaItems)
|
||||||
|
}
|
||||||
|
}
|
|
@ -171,7 +171,7 @@ class LocalMediaPlayer(
|
||||||
val mainHandler = Handler(context.mainLooper)
|
val mainHandler = Handler(context.mainLooper)
|
||||||
|
|
||||||
val myRunnable = Runnable {
|
val myRunnable = Runnable {
|
||||||
onPlayerStateChanged!!(playerState, currentPlaying)
|
onPlayerStateChanged?.invoke(playerState, currentPlaying)
|
||||||
}
|
}
|
||||||
mainHandler.post(myRunnable)
|
mainHandler.post(myRunnable)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,13 @@ import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.support.v4.media.MediaBrowserCompat
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
import android.support.v4.media.session.PlaybackStateCompat
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.media.MediaBrowserServiceCompat
|
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
@ -37,7 +36,12 @@ import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3
|
||||||
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4
|
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4
|
||||||
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
|
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
||||||
import org.moire.ultrasonic.util.*
|
import org.moire.ultrasonic.util.Constants
|
||||||
|
import org.moire.ultrasonic.util.MediaSessionEventDistributor
|
||||||
|
import org.moire.ultrasonic.util.NowPlayingEventDistributor
|
||||||
|
import org.moire.ultrasonic.util.ShufflePlayBuffer
|
||||||
|
import org.moire.ultrasonic.util.SimpleServiceBinder
|
||||||
|
import org.moire.ultrasonic.util.Util
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -56,9 +60,10 @@ class MediaPlayerService : Service() {
|
||||||
private val localMediaPlayer by inject<LocalMediaPlayer>()
|
private val localMediaPlayer by inject<LocalMediaPlayer>()
|
||||||
private val nowPlayingEventDistributor by inject<NowPlayingEventDistributor>()
|
private val nowPlayingEventDistributor by inject<NowPlayingEventDistributor>()
|
||||||
private val mediaPlayerLifecycleSupport by inject<MediaPlayerLifecycleSupport>()
|
private val mediaPlayerLifecycleSupport by inject<MediaPlayerLifecycleSupport>()
|
||||||
|
private val mediaSessionEventDistributor: MediaSessionEventDistributor by inject()
|
||||||
|
|
||||||
private var mediaSession: MediaSessionCompat? = null
|
private var mediaSession: MediaSessionCompat? = null
|
||||||
private var mediaSessionToken: MediaSessionCompat.Token? = null
|
var mediaSessionToken: MediaSessionCompat.Token? = null
|
||||||
private var isInForeground = false
|
private var isInForeground = false
|
||||||
private var notificationBuilder: NotificationCompat.Builder? = null
|
private var notificationBuilder: NotificationCompat.Builder? = null
|
||||||
|
|
||||||
|
@ -91,6 +96,12 @@ class MediaPlayerService : Service() {
|
||||||
|
|
||||||
localMediaPlayer.onNextSongRequested = Runnable { setNextPlaying() }
|
localMediaPlayer.onNextSongRequested = Runnable { setNextPlaying() }
|
||||||
|
|
||||||
|
// TODO maybe MediaSession must be in an independent class after all...
|
||||||
|
// It seems this must be initialized in the stopped state too, e.g. for Android Auto.
|
||||||
|
// So it is best to init this early.
|
||||||
|
initMediaSessions()
|
||||||
|
updateMediaSession(null, PlayerState.IDLE)
|
||||||
|
|
||||||
// Create Notification Channel
|
// Create Notification Channel
|
||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
|
||||||
|
@ -113,6 +124,8 @@ class MediaPlayerService : Service() {
|
||||||
localMediaPlayer.release()
|
localMediaPlayer.release()
|
||||||
downloader.stop()
|
downloader.stop()
|
||||||
shufflePlayBuffer.onDestroy()
|
shufflePlayBuffer.onDestroy()
|
||||||
|
|
||||||
|
mediaSessionEventDistributor.ReleaseCachedMediaSessionToken()
|
||||||
mediaSession?.release()
|
mediaSession?.release()
|
||||||
mediaSession = null
|
mediaSession = null
|
||||||
} catch (ignored: Throwable) {
|
} catch (ignored: Throwable) {
|
||||||
|
@ -467,7 +480,7 @@ class MediaPlayerService : Service() {
|
||||||
fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState) {
|
fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState) {
|
||||||
Timber.d("Updating the MediaSession")
|
Timber.d("Updating the MediaSession")
|
||||||
|
|
||||||
if (mediaSession == null) initMediaSessions()
|
val playbackState = PlaybackStateCompat.Builder()
|
||||||
|
|
||||||
// Set Metadata
|
// Set Metadata
|
||||||
val metadata = MediaMetadataCompat.Builder()
|
val metadata = MediaMetadataCompat.Builder()
|
||||||
|
@ -483,6 +496,9 @@ class MediaPlayerService : Service() {
|
||||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album)
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, song.album)
|
||||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
|
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.title)
|
||||||
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover)
|
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, cover)
|
||||||
|
|
||||||
|
playbackState.setActiveQueueItemId(downloader.currentPlayingIndex.toLong())
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Timber.e(e, "Error setting the metadata")
|
Timber.e(e, "Error setting the metadata")
|
||||||
}
|
}
|
||||||
|
@ -492,13 +508,15 @@ class MediaPlayerService : Service() {
|
||||||
mediaSession!!.setMetadata(metadata.build())
|
mediaSession!!.setMetadata(metadata.build())
|
||||||
|
|
||||||
// Create playback State
|
// Create playback State
|
||||||
val playbackState = PlaybackStateCompat.Builder()
|
|
||||||
val state: Int
|
val state: Int
|
||||||
val isActive: Boolean
|
val isActive: Boolean
|
||||||
|
|
||||||
var actions: Long = PlaybackStateCompat.ACTION_PLAY_PAUSE or
|
var actions: Long = PlaybackStateCompat.ACTION_PLAY_PAUSE or
|
||||||
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
|
PlaybackStateCompat.ACTION_SKIP_TO_NEXT or
|
||||||
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
|
PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
|
||||||
|
PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID or
|
||||||
|
PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH or
|
||||||
|
PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM
|
||||||
|
|
||||||
// Map our playerState to native PlaybackState
|
// Map our playerState to native PlaybackState
|
||||||
// TODO: Synchronize these APIs
|
// TODO: Synchronize these APIs
|
||||||
|
@ -534,7 +552,8 @@ class MediaPlayerService : Service() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
playbackState.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1.0f)
|
// TODO playerPosition should be updated more frequently (currently this function is called only when the playing track changes)
|
||||||
|
playbackState.setState(state, playerPosition.toLong(), 1.0f)
|
||||||
|
|
||||||
// Set actions
|
// Set actions
|
||||||
playbackState.setActions(actions)
|
playbackState.setActions(actions)
|
||||||
|
@ -545,6 +564,14 @@ class MediaPlayerService : Service() {
|
||||||
// Set Active state
|
// Set Active state
|
||||||
mediaSession!!.isActive = isActive
|
mediaSession!!.isActive = isActive
|
||||||
|
|
||||||
|
// TODO Implement Now Playing queue handling properly
|
||||||
|
mediaSession!!.setQueueTitle("Now Playing")
|
||||||
|
mediaSession!!.setQueue(downloader.downloadList.mapIndexed { id, file ->
|
||||||
|
MediaSessionCompat.QueueItem(MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle(file.song.title)
|
||||||
|
.build(), id.toLong())
|
||||||
|
})
|
||||||
|
|
||||||
Timber.d("Setting the MediaSession to active = %s", isActive)
|
Timber.d("Setting the MediaSession to active = %s", isActive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -795,6 +822,7 @@ class MediaPlayerService : Service() {
|
||||||
|
|
||||||
mediaSession = MediaSessionCompat(applicationContext, "UltrasonicService")
|
mediaSession = MediaSessionCompat(applicationContext, "UltrasonicService")
|
||||||
mediaSessionToken = mediaSession!!.sessionToken
|
mediaSessionToken = mediaSession!!.sessionToken
|
||||||
|
mediaSessionEventDistributor.RaiseMediaSessionTokenCreatedEvent(mediaSessionToken!!)
|
||||||
|
|
||||||
updateMediaButtonReceiver()
|
updateMediaButtonReceiver()
|
||||||
|
|
||||||
|
@ -810,35 +838,21 @@ class MediaPlayerService : Service() {
|
||||||
|
|
||||||
Timber.v("Media Session Callback: onPlay")
|
Timber.v("Media Session Callback: onPlay")
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
|
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
|
||||||
super.onPlayFromMediaId(mediaId, extras)
|
super.onPlayFromMediaId(mediaId, extras)
|
||||||
|
|
||||||
val result = autoMediaBrowser!!.getBundleData(extras)
|
Timber.d("Media Session Callback: onPlayFromMediaId")
|
||||||
if (result != null) {
|
mediaSessionEventDistributor.RaisePlayFromMediaIdRequestedEvent(mediaId, extras)
|
||||||
val mediaId = result.first
|
|
||||||
val directoryList = result.second
|
|
||||||
|
|
||||||
resetPlayback()
|
|
||||||
val songs: MutableList<MusicDirectory.Entry> = mutableListOf()
|
|
||||||
var found = false
|
|
||||||
for (item in directoryList) {
|
|
||||||
if (found || item.id == mediaId) {
|
|
||||||
found = true
|
|
||||||
songs.add(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
downloader.download(songs, false, false, false, true)
|
|
||||||
|
|
||||||
getPendingIntentForMediaAction(
|
|
||||||
applicationContext,
|
|
||||||
KeyEvent.KEYCODE_MEDIA_PLAY,
|
|
||||||
keycode
|
|
||||||
).send()
|
|
||||||
}
|
|
||||||
Timber.v("Media Session Callback: onPlayFromMediaId")
|
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
override fun onPlayFromSearch(query: String?, extras: Bundle?) {
|
||||||
|
super.onPlayFromSearch(query, extras)
|
||||||
|
|
||||||
|
Timber.d("Media Session Callback: onPlayFromSearch")
|
||||||
|
mediaSessionEventDistributor.RaisePlayFromSearchRequestedEvent(query, extras)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
getPendingIntentForMediaAction(
|
getPendingIntentForMediaAction(
|
||||||
|
@ -886,6 +900,11 @@ class MediaPlayerService : Service() {
|
||||||
mediaPlayerLifecycleSupport.handleKeyEvent(event)
|
mediaPlayerLifecycleSupport.handleKeyEvent(event)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSkipToQueueItem(id: Long) {
|
||||||
|
super.onSkipToQueueItem(id)
|
||||||
|
play(id.toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class distributes MediaSession related events to its subscribers.
|
||||||
|
* It is a primitive implementation of a pub-sub event bus
|
||||||
|
*/
|
||||||
|
class MediaSessionEventDistributor {
|
||||||
|
var eventListenerList: MutableList<MediaSessionEventListener> =
|
||||||
|
listOf<MediaSessionEventListener>().toMutableList()
|
||||||
|
|
||||||
|
var cachedToken: MediaSessionCompat.Token? = null
|
||||||
|
|
||||||
|
fun subscribe(listener: MediaSessionEventListener) {
|
||||||
|
eventListenerList.add(listener)
|
||||||
|
|
||||||
|
synchronized(this) {
|
||||||
|
if (cachedToken != null)
|
||||||
|
listener.onMediaSessionTokenCreated(cachedToken!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unsubscribe(listener: MediaSessionEventListener) {
|
||||||
|
eventListenerList.remove(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ReleaseCachedMediaSessionToken() {
|
||||||
|
synchronized(this) {
|
||||||
|
cachedToken = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RaiseMediaSessionTokenCreatedEvent(token: MediaSessionCompat.Token) {
|
||||||
|
synchronized(this) {
|
||||||
|
cachedToken = token
|
||||||
|
eventListenerList.forEach { listener -> listener.onMediaSessionTokenCreated(token) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RaisePlayFromMediaIdRequestedEvent(mediaId: String?, extras: Bundle?) {
|
||||||
|
eventListenerList.forEach { listener -> listener.onPlayFromMediaIdRequested(mediaId, extras) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RaisePlayFromSearchRequestedEvent(query: String?, extras: Bundle?) {
|
||||||
|
eventListenerList.forEach { listener -> listener.onPlayFromSearchRequested(query, extras) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback interface for MediaSession related event subscribers
|
||||||
|
*/
|
||||||
|
interface MediaSessionEventListener {
|
||||||
|
fun onMediaSessionTokenCreated(token: MediaSessionCompat.Token)
|
||||||
|
fun onPlayFromMediaIdRequested(mediaId: String?, extras: Bundle?)
|
||||||
|
fun onPlayFromSearchRequested(query: String?, extras: Bundle?)
|
||||||
|
}
|
Loading…
Reference in New Issue