diff --git a/ultrasonic/src/main/AndroidManifest.xml b/ultrasonic/src/main/AndroidManifest.xml
index 7aa13db8..f3a0d39c 100644
--- a/ultrasonic/src/main/AndroidManifest.xml
+++ b/ultrasonic/src/main/AndroidManifest.xml
@@ -56,6 +56,12 @@
+
+
+
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/AndroidAutoMediaBrowser.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaPlayerService.kt
similarity index 62%
rename from ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/AndroidAutoMediaBrowser.kt
rename to ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaPlayerService.kt
index d951d514..2eb7fca5 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/AndroidAutoMediaBrowser.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaPlayerService.kt
@@ -1,6 +1,5 @@
-package org.moire.ultrasonic.util
+package org.moire.ultrasonic.service
-import android.app.Application
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
@@ -8,24 +7,26 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants
+import org.moire.ultrasonic.api.subsonic.models.AlbumListType
+import org.moire.ultrasonic.domain.ArtistOrIndex
+import org.moire.ultrasonic.domain.MusicDirectory
+import org.moire.ultrasonic.domain.PlayerState
+import org.moire.ultrasonic.fragment.AlbumListModel
+import org.moire.ultrasonic.fragment.ArtistListModel
+import org.moire.ultrasonic.util.Constants
+import org.moire.ultrasonic.util.Pair
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.ObjectInputStream
import java.io.ObjectOutputStream
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
-import org.moire.ultrasonic.api.subsonic.models.AlbumListType
-import org.moire.ultrasonic.domain.Artist
-import org.moire.ultrasonic.domain.ArtistOrIndex
-import org.moire.ultrasonic.domain.MusicDirectory
-import org.moire.ultrasonic.fragment.AlbumListModel
-import org.moire.ultrasonic.fragment.ArtistListModel
-import org.moire.ultrasonic.service.MusicServiceFactory
-class AndroidAutoMediaBrowser(application: Application) {
+class AutoMediaPlayerService: MediaBrowserServiceCompat() {
- val albumListModel: AlbumListModel = AlbumListModel(application)
- val artistListModel: ArtistListModel = ArtistListModel(application)
+ val mediaPlayerService : MediaPlayerService = MediaPlayerService()
+ var albumListModel: AlbumListModel? = null
+ var artistListModel: ArtistListModel? = null
val executorService: ExecutorService = Executors.newFixedThreadPool(4)
var maximumRootChildLimit: Int = 4
@@ -44,11 +45,11 @@ class AndroidAutoMediaBrowser(application: Application) {
private val MEDIA_BROWSER_EXTRA_MEDIA_ID = "_Ultrasonic_mb_extra_media_id_"
class AlbumListObserver(
- val idPrefix: String,
- val result: MediaBrowserServiceCompat.Result>,
- data: LiveData>
+ val idPrefix: String,
+ val result: MediaBrowserServiceCompat.Result>,
+ data: LiveData>
) :
- Observer> {
+ Observer> {
private var liveData: LiveData>? = null
@@ -73,15 +74,15 @@ class AndroidAutoMediaBrowser(application: Application) {
val mediaItems: MutableList = mutableListOf()
for (item in albumList) {
val entryBuilder: MediaDescriptionCompat.Builder =
- MediaDescriptionCompat.Builder()
+ MediaDescriptionCompat.Builder()
entryBuilder
- .setTitle(item.title)
- .setMediaId(idPrefix + item.id)
+ .setTitle(item.title)
+ .setMediaId(idPrefix + item.id)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- entryBuilder.build(),
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
- )
+ MediaBrowserCompat.MediaItem(
+ entryBuilder.build(),
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ )
)
}
@@ -90,11 +91,11 @@ class AndroidAutoMediaBrowser(application: Application) {
}
class ArtistListObserver(
- val idPrefix: String,
- val result: MediaBrowserServiceCompat.Result>,
- data: LiveData>
+ val idPrefix: String,
+ val result: MediaBrowserServiceCompat.Result>,
+ data: LiveData>
) :
- Observer> {
+ Observer> {
private var liveData: LiveData>? = null
@@ -119,15 +120,15 @@ class AndroidAutoMediaBrowser(application: Application) {
val mediaItems: MutableList = mutableListOf()
for (item in artistList) {
val entryBuilder: MediaDescriptionCompat.Builder =
- MediaDescriptionCompat.Builder()
+ MediaDescriptionCompat.Builder()
entryBuilder
- .setTitle(item.name)
- .setMediaId(idPrefix + item.id)
+ .setTitle(item.name)
+ .setMediaId(idPrefix + item.id)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- entryBuilder.build(),
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
- )
+ MediaBrowserCompat.MediaItem(
+ entryBuilder.build(),
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ )
)
}
@@ -135,15 +136,21 @@ class AndroidAutoMediaBrowser(application: Application) {
}
}
- fun getRoot(
- clientPackageName: String,
- clientUid: Int,
- rootHints: Bundle?
- ): MediaBrowserServiceCompat.BrowserRoot {
+ override fun onCreate() {
+ super.onCreate()
+
+ albumListModel = AlbumListModel(application)
+ artistListModel = ArtistListModel(application)
+
+ mediaPlayerService.onCreate()
+ mediaPlayerService.updateMediaSession(null, PlayerState.IDLE)
+ }
+
+ override fun onGetRoot(clientPackageName: String, clientUid: Int, rootHints: Bundle?): BrowserRoot? {
if (rootHints != null) {
maximumRootChildLimit = rootHints.getInt(
- MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
- 4
+ MediaConstants.BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT,
+ 4
)
}
// opt into the root tabs (because it's gonna be non-optional
@@ -154,60 +161,56 @@ class AndroidAutoMediaBrowser(application: Application) {
return MediaBrowserServiceCompat.BrowserRoot(MEDIA_BROWSER_ROOT_ID, extras)
}
- fun loadChildren(
- parentMediaId: String,
- result: MediaBrowserServiceCompat.Result>
- ) {
-
+ override fun onLoadChildren(parentId: String, result: Result>) {
val mediaItems: MutableList = mutableListOf()
- if (MEDIA_BROWSER_ROOT_ID == parentMediaId) {
+ if (MEDIA_BROWSER_ROOT_ID == parentId) {
// Build the MediaItem objects for the top level,
// and put them in the mediaItems list...
var recentList: MediaDescriptionCompat.Builder = MediaDescriptionCompat.Builder()
recentList.setTitle("Recent").setMediaId(MEDIA_BROWSER_RECENT_LIST_ROOT)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- recentList.build(),
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
- )
+ MediaBrowserCompat.MediaItem(
+ recentList.build(),
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ )
)
var albumList: MediaDescriptionCompat.Builder = MediaDescriptionCompat.Builder()
albumList.setTitle("Albums").setMediaId(MEDIA_BROWSER_ALBUM_LIST_ROOT)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- albumList.build(),
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
- )
+ MediaBrowserCompat.MediaItem(
+ albumList.build(),
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ )
)
var artistList: MediaDescriptionCompat.Builder = MediaDescriptionCompat.Builder()
artistList.setTitle("Artists").setMediaId(MEDIA_BROWSER_ARTIST_LIST_ROOT)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- artistList.build(),
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
- )
+ MediaBrowserCompat.MediaItem(
+ artistList.build(),
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ )
)
- } else if (MEDIA_BROWSER_RECENT_LIST_ROOT == parentMediaId) {
+ } else if (MEDIA_BROWSER_RECENT_LIST_ROOT == parentId) {
fetchAlbumList(AlbumListType.RECENT, MEDIA_BROWSER_RECENT_PREFIX, result)
return
- } else if (MEDIA_BROWSER_ALBUM_LIST_ROOT == parentMediaId) {
+ } else if (MEDIA_BROWSER_ALBUM_LIST_ROOT == parentId) {
fetchAlbumList(AlbumListType.SORTED_BY_NAME, MEDIA_BROWSER_ALBUM_PREFIX, result)
return
- } else if (MEDIA_BROWSER_ARTIST_LIST_ROOT == parentMediaId) {
+ } else if (MEDIA_BROWSER_ARTIST_LIST_ROOT == parentId) {
fetchArtistList(MEDIA_BROWSER_ARTIST_PREFIX, result)
return
- } else if (parentMediaId.startsWith(MEDIA_BROWSER_RECENT_PREFIX)) {
- fetchTrackList(parentMediaId.substring(MEDIA_BROWSER_RECENT_PREFIX.length), result)
+ } else if (parentId.startsWith(MEDIA_BROWSER_RECENT_PREFIX)) {
+ fetchTrackList(parentId.substring(MEDIA_BROWSER_RECENT_PREFIX.length), result)
return
- } else if (parentMediaId.startsWith(MEDIA_BROWSER_ALBUM_PREFIX)) {
- fetchTrackList(parentMediaId.substring(MEDIA_BROWSER_ALBUM_PREFIX.length), result)
+ } else if (parentId.startsWith(MEDIA_BROWSER_ALBUM_PREFIX)) {
+ fetchTrackList(parentId.substring(MEDIA_BROWSER_ALBUM_PREFIX.length), result)
return
- } else if (parentMediaId.startsWith(MEDIA_BROWSER_ARTIST_PREFIX)) {
+ } else if (parentId.startsWith(MEDIA_BROWSER_ARTIST_PREFIX)) {
fetchArtistAlbumList(
- parentMediaId.substring(MEDIA_BROWSER_ARTIST_PREFIX.length),
- result
+ parentId.substring(MEDIA_BROWSER_ARTIST_PREFIX.length),
+ result
)
return
} else {
@@ -217,13 +220,14 @@ class AndroidAutoMediaBrowser(application: Application) {
result.sendResult(mediaItems)
}
+
fun getBundleData(bundle: Bundle?): Pair>? {
if (bundle == null) {
return null
}
if (!bundle.containsKey(MEDIA_BROWSER_EXTRA_ALBUM_LIST) ||
- !bundle.containsKey(MEDIA_BROWSER_EXTRA_MEDIA_ID)
+ !bundle.containsKey(MEDIA_BROWSER_EXTRA_MEDIA_ID)
) {
return null
}
@@ -231,58 +235,58 @@ class AndroidAutoMediaBrowser(application: Application) {
val byteArrayInputStream = ByteArrayInputStream(bytes)
val objectInputStream = ObjectInputStream(byteArrayInputStream)
return Pair(
- bundle.getString(MEDIA_BROWSER_EXTRA_MEDIA_ID),
- objectInputStream.readObject() as List
+ bundle.getString(MEDIA_BROWSER_EXTRA_MEDIA_ID),
+ objectInputStream.readObject() as List
)
}
private fun fetchAlbumList(
- type: AlbumListType,
- idPrefix: String,
- result: MediaBrowserServiceCompat.Result>
+ type: AlbumListType,
+ idPrefix: String,
+ result: MediaBrowserServiceCompat.Result>
) {
- AlbumListObserver(
- idPrefix, result,
- albumListModel.albumList
+ AutoMediaPlayerService.AlbumListObserver(
+ idPrefix, result,
+ albumListModel!!.albumList
)
val args: Bundle = Bundle()
args.putString(Constants.INTENT_EXTRA_NAME_ALBUM_LIST_TYPE, type.toString())
- albumListModel.getAlbumList(false, null, args)
+ albumListModel!!.getAlbumList(false, null, args)
result.detach()
}
private fun fetchArtistList(
- idPrefix: String,
- result: MediaBrowserServiceCompat.Result>
+ idPrefix: String,
+ result: MediaBrowserServiceCompat.Result>
) {
- ArtistListObserver(idPrefix, result, artistListModel.artists)
+ AutoMediaPlayerService.ArtistListObserver(idPrefix, result, artistListModel!!.artists)
- artistListModel.getItems(false, null)
+ artistListModel!!.getItems(false, null)
result.detach()
}
private fun fetchArtistAlbumList(
- id: String,
- result: MediaBrowserServiceCompat.Result>
+ id: String,
+ result: MediaBrowserServiceCompat.Result>
) {
executorService.execute {
val musicService = MusicServiceFactory.getMusicService()
val musicDirectory = musicService.getMusicDirectory(
- id, "", false
+ id, "", false
)
val mediaItems: MutableList = mutableListOf()
for (item in musicDirectory.getAllChild()) {
val entryBuilder: MediaDescriptionCompat.Builder =
- MediaDescriptionCompat.Builder()
+ MediaDescriptionCompat.Builder()
entryBuilder.setTitle(item.title).setMediaId(MEDIA_BROWSER_ALBUM_PREFIX + item.id)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- entryBuilder.build(),
- MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
- )
+ MediaBrowserCompat.MediaItem(
+ entryBuilder.build(),
+ MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
+ )
)
}
result.sendResult(mediaItems)
@@ -291,14 +295,14 @@ class AndroidAutoMediaBrowser(application: Application) {
}
private fun fetchTrackList(
- id: String,
- result: MediaBrowserServiceCompat.Result>
+ id: String,
+ result: MediaBrowserServiceCompat.Result>
) {
executorService.execute {
val musicService = MusicServiceFactory.getMusicService()
val albumDirectory = musicService.getAlbum(
- id, "", false
+ id, "", false
)
// The idea here is that we want to attach the full album list to every song,
@@ -315,26 +319,26 @@ class AndroidAutoMediaBrowser(application: Application) {
val extras = Bundle()
extras.putByteArray(
- MEDIA_BROWSER_EXTRA_ALBUM_LIST,
- songList
+ MEDIA_BROWSER_EXTRA_ALBUM_LIST,
+ songList
)
extras.putString(
- MEDIA_BROWSER_EXTRA_MEDIA_ID,
- item.id
+ MEDIA_BROWSER_EXTRA_MEDIA_ID,
+ item.id
)
val entryBuilder: MediaDescriptionCompat.Builder =
- MediaDescriptionCompat.Builder()
+ MediaDescriptionCompat.Builder()
entryBuilder.setTitle(item.title).setMediaId(item.id).setExtras(extras)
mediaItems.add(
- MediaBrowserCompat.MediaItem(
- entryBuilder.build(),
- MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
- )
+ MediaBrowserCompat.MediaItem(
+ entryBuilder.build(),
+ MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
+ )
)
}
result.sendResult(mediaItems)
}
result.detach()
}
-}
+}
\ No newline at end of file
diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt
index 1972bfaf..101a8f5d 100644
--- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt
+++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerService.kt
@@ -7,15 +7,13 @@
package org.moire.ultrasonic.service
-import android.app.Notification
-import android.app.NotificationChannel
-import android.app.NotificationManager
-import android.app.PendingIntent
+import android.app.*
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
+import android.os.IBinder
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
@@ -39,11 +37,7 @@ import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X3
import org.moire.ultrasonic.provider.UltrasonicAppWidgetProvider4X4
import org.moire.ultrasonic.receiver.MediaButtonIntentReceiver
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
-import org.moire.ultrasonic.util.AndroidAutoMediaBrowser
-import org.moire.ultrasonic.util.Constants
-import org.moire.ultrasonic.util.NowPlayingEventDistributor
-import org.moire.ultrasonic.util.ShufflePlayBuffer
-import org.moire.ultrasonic.util.Util
+import org.moire.ultrasonic.util.*
import timber.log.Timber
/**
@@ -51,7 +45,8 @@ import timber.log.Timber
* while the rest of the Ultrasonic App is in the background.
*/
@Suppress("LargeClass")
-class MediaPlayerService : MediaBrowserServiceCompat() {
+class MediaPlayerService : Service() {
+ private val binder: IBinder = SimpleServiceBinder(this)
private val scrobbler = Scrobbler()
private val jukeboxMediaPlayer by inject()
@@ -62,20 +57,21 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
private val nowPlayingEventDistributor by inject()
private val mediaPlayerLifecycleSupport by inject()
- private var autoMediaBrowser: AndroidAutoMediaBrowser? = null
private var mediaSession: MediaSessionCompat? = null
+ private var mediaSessionToken: MediaSessionCompat.Token? = null
private var isInForeground = false
private var notificationBuilder: NotificationCompat.Builder? = null
private val repeatMode: RepeatMode
get() = Util.getRepeatMode()
+ override fun onBind(intent: Intent): IBinder {
+ return binder
+ }
+
override fun onCreate() {
super.onCreate()
- autoMediaBrowser = AndroidAutoMediaBrowser(application)
- updateMediaSession(null, PlayerState.IDLE)
-
downloader.onCreate()
shufflePlayBuffer.onCreate()
localMediaPlayer.init()
@@ -136,21 +132,6 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
}
}
- override fun onGetRoot(
- clientPackageName: String,
- clientUid: Int,
- rootHints: Bundle?
- ): MediaBrowserServiceCompat.BrowserRoot {
- return autoMediaBrowser!!.getRoot(clientPackageName, clientUid, rootHints)
- }
-
- override fun onLoadChildren(
- parentMediaId: String,
- result: MediaBrowserServiceCompat.Result>
- ) {
- autoMediaBrowser!!.loadChildren(parentMediaId, result)
- }
-
@Synchronized
fun seekTo(position: Int) {
if (jukeboxMediaPlayer.isEnabled) {
@@ -483,7 +464,7 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
}
}
- private fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState) {
+ fun updateMediaSession(currentPlaying: DownloadFile?, playerState: PlayerState) {
Timber.d("Updating the MediaSession")
if (mediaSession == null) initMediaSessions()
@@ -645,8 +626,8 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
// Use the Media Style, to enable native Android support for playback notification
val style = androidx.media.app.NotificationCompat.MediaStyle()
- if (getSessionToken() != null) {
- style.setMediaSession(getSessionToken())
+ if (mediaSessionToken != null) {
+ style.setMediaSession(mediaSessionToken)
}
// Clear old actions
@@ -813,7 +794,7 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
Timber.w("Creating media session")
mediaSession = MediaSessionCompat(applicationContext, "UltrasonicService")
- setSessionToken(mediaSession!!.sessionToken)
+ mediaSessionToken = mediaSession!!.sessionToken
updateMediaButtonReceiver()
@@ -829,7 +810,7 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
Timber.v("Media Session Callback: onPlay")
}
-
+/*
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
super.onPlayFromMediaId(mediaId, extras)
@@ -857,7 +838,7 @@ class MediaPlayerService : MediaBrowserServiceCompat() {
}
Timber.v("Media Session Callback: onPlayFromMediaId")
}
-
+*/
override fun onPause() {
super.onPause()
getPendingIntentForMediaAction(