ultrasonic-app-subsonic-and.../ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AutoMediaPlayerService.kt

344 lines
14 KiB
Kotlin

package org.moire.ultrasonic.service
import android.os.Bundle
import android.support.v4.media.MediaBrowserCompat
import android.support.v4.media.MediaDescriptionCompat
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
class AutoMediaPlayerService: MediaBrowserServiceCompat() {
val mediaPlayerService : MediaPlayerService = MediaPlayerService()
var albumListModel: AlbumListModel? = null
var artistListModel: ArtistListModel? = null
val executorService: ExecutorService = Executors.newFixedThreadPool(4)
var maximumRootChildLimit: Int = 4
private val MEDIA_BROWSER_ROOT_ID = "_Ultrasonice_mb_root_"
private val MEDIA_BROWSER_RECENT_LIST_ROOT = "_Ultrasonic_mb_recent_list_root_"
private val MEDIA_BROWSER_ALBUM_LIST_ROOT = "_Ultrasonic_mb_album_list_root_"
private val MEDIA_BROWSER_ARTIST_LIST_ROOT = "_Ultrasonic_mb_rtist_list_root_"
private val MEDIA_BROWSER_RECENT_PREFIX = "_Ultrasonic_mb_recent_prefix_"
private val MEDIA_BROWSER_ALBUM_PREFIX = "_Ultrasonic_mb_album_prefix_"
private val MEDIA_BROWSER_ARTIST_PREFIX = "_Ultrasonic_mb_artist_prefix_"
private val MEDIA_BROWSER_EXTRA_ALBUM_LIST = "_Ultrasonic_mb_extra_album_list_"
private val MEDIA_BROWSER_EXTRA_MEDIA_ID = "_Ultrasonic_mb_extra_media_id_"
class AlbumListObserver(
val idPrefix: String,
val result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>,
data: LiveData<List<MusicDirectory.Entry>>
) :
Observer<List<MusicDirectory.Entry>> {
private var liveData: LiveData<List<MusicDirectory.Entry>>? = null
init {
// Order is very important here. When observerForever is called onChanged
// will immediately be called with any past data updates. We don't care
// about those. So by having it called *before* liveData is set will
// signal to onChanged to ignore the first input
data.observeForever(this)
liveData = data
}
override fun onChanged(albumList: List<MusicDirectory.Entry>?) {
if (liveData == null) {
// See comment in the initializer
return
}
liveData!!.removeObserver(this)
if (albumList == null) {
return
}
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
for (item in albumList) {
val entryBuilder: MediaDescriptionCompat.Builder =
MediaDescriptionCompat.Builder()
entryBuilder
.setTitle(item.title)
.setMediaId(idPrefix + item.id)
mediaItems.add(
MediaBrowserCompat.MediaItem(
entryBuilder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
)
}
result.sendResult(mediaItems)
}
}
class ArtistListObserver(
val idPrefix: String,
val result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>,
data: LiveData<List<ArtistOrIndex>>
) :
Observer<List<ArtistOrIndex>> {
private var liveData: LiveData<List<ArtistOrIndex>>? = null
init {
// Order is very important here. When observerForever is called onChanged
// will immediately be called with any past data updates. We don't care
// about those. So by having it called *before* liveData is set will
// signal to onChanged to ignore the first input
data.observeForever(this)
liveData = data
}
override fun onChanged(artistList: List<ArtistOrIndex>?) {
if (liveData == null) {
// See comment in the initializer
return
}
liveData!!.removeObserver(this)
if (artistList == null) {
return
}
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
for (item in artistList) {
val entryBuilder: MediaDescriptionCompat.Builder =
MediaDescriptionCompat.Builder()
entryBuilder
.setTitle(item.name)
.setMediaId(idPrefix + item.id)
mediaItems.add(
MediaBrowserCompat.MediaItem(
entryBuilder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
)
}
result.sendResult(mediaItems)
}
}
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
)
}
// opt into the root tabs (because it's gonna be non-optional
// real soon anyway)
val extras = Bundle()
val TABS_OPT_IN_HINT = "android.media.browse.AUTO_TABS_OPT_IN_HINT"
extras.putBoolean(TABS_OPT_IN_HINT, true)
return MediaBrowserServiceCompat.BrowserRoot(MEDIA_BROWSER_ROOT_ID, extras)
}
override fun onLoadChildren(parentId: String, result: Result<List<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
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
)
)
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
)
)
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
)
)
} else if (MEDIA_BROWSER_RECENT_LIST_ROOT == parentId) {
fetchAlbumList(AlbumListType.RECENT, MEDIA_BROWSER_RECENT_PREFIX, result)
return
} 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 == parentId) {
fetchArtistList(MEDIA_BROWSER_ARTIST_PREFIX, result)
return
} else if (parentId.startsWith(MEDIA_BROWSER_RECENT_PREFIX)) {
fetchTrackList(parentId.substring(MEDIA_BROWSER_RECENT_PREFIX.length), result)
return
} else if (parentId.startsWith(MEDIA_BROWSER_ALBUM_PREFIX)) {
fetchTrackList(parentId.substring(MEDIA_BROWSER_ALBUM_PREFIX.length), result)
return
} else if (parentId.startsWith(MEDIA_BROWSER_ARTIST_PREFIX)) {
fetchArtistAlbumList(
parentId.substring(MEDIA_BROWSER_ARTIST_PREFIX.length),
result
)
return
} else {
// Examine the passed parentMediaId to see which submenu we're at,
// and put the children of that menu in the mediaItems list...
}
result.sendResult(mediaItems)
}
fun getBundleData(bundle: Bundle?): Pair<String, List<MusicDirectory.Entry>>? {
if (bundle == null) {
return null
}
if (!bundle.containsKey(MEDIA_BROWSER_EXTRA_ALBUM_LIST) ||
!bundle.containsKey(MEDIA_BROWSER_EXTRA_MEDIA_ID)
) {
return null
}
val bytes = bundle.getByteArray(MEDIA_BROWSER_EXTRA_ALBUM_LIST)
val byteArrayInputStream = ByteArrayInputStream(bytes)
val objectInputStream = ObjectInputStream(byteArrayInputStream)
return Pair(
bundle.getString(MEDIA_BROWSER_EXTRA_MEDIA_ID),
objectInputStream.readObject() as List<MusicDirectory.Entry>
)
}
private fun fetchAlbumList(
type: AlbumListType,
idPrefix: String,
result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
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)
result.detach()
}
private fun fetchArtistList(
idPrefix: String,
result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
AutoMediaPlayerService.ArtistListObserver(idPrefix, result, artistListModel!!.artists)
artistListModel!!.getItems(false, null)
result.detach()
}
private fun fetchArtistAlbumList(
id: String,
result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
executorService.execute {
val musicService = MusicServiceFactory.getMusicService()
val musicDirectory = musicService.getMusicDirectory(
id, "", false
)
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
for (item in musicDirectory.getAllChild()) {
val entryBuilder: 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
)
)
}
result.sendResult(mediaItems)
}
result.detach()
}
private fun fetchTrackList(
id: String,
result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>>
) {
executorService.execute {
val musicService = MusicServiceFactory.getMusicService()
val albumDirectory = musicService.getAlbum(
id, "", false
)
// The idea here is that we want to attach the full album list to every song,
// as well as the id of the specific song. This way if someone chooses to play a song
// we can add the song and all subsequent songs in the album
val byteArrayOutputStream = ByteArrayOutputStream()
val objectOutputStream = ObjectOutputStream(byteArrayOutputStream)
objectOutputStream.writeObject(albumDirectory.getAllChild())
objectOutputStream.close()
val songList = byteArrayOutputStream.toByteArray()
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
for (item in albumDirectory.getAllChild()) {
val extras = Bundle()
extras.putByteArray(
MEDIA_BROWSER_EXTRA_ALBUM_LIST,
songList
)
extras.putString(
MEDIA_BROWSER_EXTRA_MEDIA_ID,
item.id
)
val entryBuilder: MediaDescriptionCompat.Builder =
MediaDescriptionCompat.Builder()
entryBuilder.setTitle(item.title).setMediaId(item.id).setExtras(extras)
mediaItems.add(
MediaBrowserCompat.MediaItem(
entryBuilder.build(),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
)
}
result.sendResult(mediaItems)
}
result.detach()
}
}