Started implementing Media Browser

Added root menus, playlists and artists
This commit is contained in:
Nite 2021-07-16 17:29:21 +02:00
parent 635ea2f55e
commit f50d6f13f4
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
10 changed files with 1768 additions and 1494 deletions

File diff suppressed because it is too large Load Diff

View File

@ -313,7 +313,7 @@ class PlayerFragment : Fragment(), GestureDetector.OnGestureListener, KoinCompon
}
repeatButton.setOnClickListener {
val repeatMode = mediaPlayerController.repeatMode?.next()
val repeatMode = mediaPlayerController.repeatMode.next()
mediaPlayerController.repeatMode = repeatMode
onDownloadListChanged()
when (repeatMode) {

View File

@ -7,26 +7,65 @@ import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.session.MediaSessionCompat
import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.koin.android.ext.android.inject
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.util.MediaSessionEventDistributor
import org.moire.ultrasonic.util.MediaSessionEventListener
import org.moire.ultrasonic.util.MediaSessionHandler
import org.moire.ultrasonic.util.Util
import timber.log.Timber
const val MEDIA_ROOT_ID = "MEDIA_ROOT_ID"
const val MEDIA_ALBUM_ID = "MEDIA_ALBUM_ID"
const val MEDIA_ALBUM_NEWEST_ID = "MEDIA_ALBUM_NEWEST_ID"
const val MEDIA_ALBUM_RECENT_ID = "MEDIA_ALBUM_RECENT_ID"
const val MEDIA_ALBUM_FREQUENT_ID = "MEDIA_ALBUM_FREQUENT_ID"
const val MEDIA_ALBUM_RANDOM_ID = "MEDIA_ALBUM_RANDOM_ID"
const val MEDIA_ALBUM_STARRED_ID = "MEDIA_ALBUM_STARRED_ID"
const val MEDIA_SONG_RANDOM_ID = "MEDIA_SONG_RANDOM_ID"
const val MEDIA_SONG_STARRED_ID = "MEDIA_SONG_STARRED_ID"
const val MEDIA_ARTIST_ID = "MEDIA_ARTIST_ID"
const val MEDIA_LIBRARY_ID = "MEDIA_LIBRARY_ID"
const val MEDIA_PLAYLIST_ID = "MEDIA_PLAYLIST_ID"
const val MEDIA_SHARE_ID = "MEDIA_SHARE_ID"
const val MEDIA_BOOKMARK_ID = "MEDIA_BOOKMARK_ID"
const val MEDIA_PODCAST_ID = "MEDIA_PODCAST_ID"
const val MEDIA_ALBUM_ITEM = "MEDIA_ALBUM_ITEM"
const val MEDIA_PLAYLIST_SONG_ITEM = "MEDIA_PLAYLIST_SONG_ITEM"
const val MEDIA_PLAYLIST_ITEM = "MEDIA_ALBUM_ITEM"
const val MEDIA_ARTIST_ITEM = "MEDIA_ARTIST_ITEM"
const val MEDIA_ARTIST_SECTION = "MEDIA_ARTIST_SECTION"
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"
// Currently the display limit for long lists is 100 items
const val displayLimit = 100
/**
* MediaBrowserService implementation for e.g. Android Auto
*/
class AutoMediaBrowserService : MediaBrowserServiceCompat() {
private lateinit var mediaSessionEventListener: MediaSessionEventListener
private val mediaSessionEventDistributor by inject<MediaSessionEventDistributor>()
private val lifecycleSupport by inject<MediaPlayerLifecycleSupport>()
private val mediaSessionHandler by inject<MediaSessionHandler>()
private val mediaPlayerController by inject<MediaPlayerController>()
private val activeServerProvider: ActiveServerProvider by inject()
private val musicService by lazy { MusicServiceFactory.getMusicService() }
private val serviceJob = Job()
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
private var playlistCache: List<MusicDirectory.Entry>? = null
private val isOffline get() = ActiveServerProvider.isOffline()
private val useId3Tags get() = Util.getShouldUseId3Tags()
private val musicFolderId get() = activeServerProvider.getActiveServer().musicFolderId
override fun onCreate() {
super.onCreate()
@ -39,7 +78,15 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
}
override fun onPlayFromMediaIdRequested(mediaId: String?, extras: Bundle?) {
// TODO implement
Timber.d("AutoMediaBrowserService onPlayFromMediaIdRequested called. mediaId: %s", mediaId)
if (mediaId == null) return
val mediaIdParts = mediaId.split('|')
when (mediaIdParts.first()) {
MEDIA_PLAYLIST_ITEM -> playPlaylist(mediaIdParts[1], mediaIdParts[2])
MEDIA_PLAYLIST_SONG_ITEM -> playPlaylistSong(mediaIdParts[1], mediaIdParts[2], mediaIdParts[3])
}
}
override fun onPlayFromSearchRequested(query: String?, extras: Bundle?) {
@ -65,6 +112,7 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
super.onDestroy()
mediaSessionEventDistributor.unsubscribe(mediaSessionEventListener)
mediaSessionHandler.release()
serviceJob.cancel()
Timber.i("AutoMediaBrowserService onDestroy finished")
}
@ -73,20 +121,8 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
clientPackageName: String,
clientUid: Int,
rootHints: Bundle?
): BrowserRoot? {
Timber.d("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
)
): BrowserRoot {
Timber.d("AutoMediaBrowserService onGetRoot called. clientPackageName: %s; clientUid: %d", clientPackageName, clientUid)
val extras = Bundle()
extras.putInt(
@ -96,19 +132,37 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
return BrowserRoot(MY_MEDIA_ROOT_ID, extras)
return BrowserRoot(MEDIA_ROOT_ID, extras)
}
override fun onLoadChildren(
parentId: String,
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
Timber.d("AutoMediaBrowserService onLoadChildren called")
Timber.d("AutoMediaBrowserService onLoadChildren called. ParentId: %s", parentId)
if (parentId == MY_MEDIA_ROOT_ID) {
return getRootItems(result)
} else {
return getAlbumLists(result)
val parentIdParts = parentId.split('|')
when (parentIdParts.first()) {
MEDIA_ROOT_ID -> return getRootItems(result)
MEDIA_LIBRARY_ID -> return getLibrary(result)
MEDIA_ARTIST_ID -> return getArtists(result)
MEDIA_ARTIST_SECTION -> return getArtists(result, parentIdParts[1])
MEDIA_ALBUM_ID -> return getAlbums(result)
MEDIA_PLAYLIST_ID -> return getPlaylists(result)
MEDIA_ALBUM_FREQUENT_ID -> return getFrequentAlbums(result)
MEDIA_ALBUM_NEWEST_ID -> return getNewestAlbums(result)
MEDIA_ALBUM_RECENT_ID -> return getRecentAlbums(result)
MEDIA_ALBUM_RANDOM_ID -> return getRandomAlbums(result)
MEDIA_ALBUM_STARRED_ID -> return getStarredAlbums(result)
MEDIA_SONG_RANDOM_ID -> return getRandomSongs(result)
MEDIA_SONG_STARRED_ID -> return getStarredSongs(result)
MEDIA_SHARE_ID -> return getShares(result)
MEDIA_BOOKMARK_ID -> return getBookmarks(result)
MEDIA_PODCAST_ID -> return getPodcasts(result)
MEDIA_PLAYLIST_ITEM -> return getPlaylist(parentIdParts[1], parentIdParts[2], result)
MEDIA_ARTIST_ITEM -> return getAlbums(result, parentIdParts[1])
else -> result.sendResult(mutableListOf())
}
}
@ -118,70 +172,361 @@ class AutoMediaBrowserService : MediaBrowserServiceCompat() {
result: Result<MutableList<MediaBrowserCompat.MediaItem>>
) {
super.onSearch(query, extras, result)
// TODO implement
}
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
)
R.string.music_library_label,
MEDIA_LIBRARY_ID,
R.drawable.ic_library,
null
)
mediaItems.add(
MediaBrowserCompat.MediaItem(
MediaDescriptionCompat.Builder()
.setTitle("Artists")
.setMediaId(MY_MEDIA_ARTIST_ID)
.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
R.string.main_artists_title,
MEDIA_ARTIST_ID,
R.drawable.ic_artist,
null
)
mediaItems.add(
MediaBrowserCompat.MediaItem(
MediaDescriptionCompat.Builder()
.setTitle("Albums")
.setMediaId(MY_MEDIA_ALBUM_ID)
.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
R.string.main_albums_title,
MEDIA_ALBUM_ID,
R.drawable.ic_menu_browse_dark,
null
)
mediaItems.add(
MediaBrowserCompat.MediaItem(
MediaDescriptionCompat.Builder()
.setTitle("Playlists")
.setMediaId(MY_MEDIA_PLAYLIST_ID)
.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
R.string.playlist_label,
MEDIA_PLAYLIST_ID,
R.drawable.ic_menu_playlists_dark,
null
)
result.sendResult(mediaItems)
}
private fun getAlbumLists(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
private fun getLibrary(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
val description = MediaDescriptionCompat.Builder()
.setTitle("Test")
.setMediaId(MY_MEDIA_ALBUM_ITEM + 1)
.build()
// Songs
mediaItems.add(
R.string.main_songs_random,
MEDIA_SONG_RANDOM_ID,
null,
R.string.main_songs_title
)
mediaItems.add(
MediaBrowserCompat.MediaItem(
description,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
R.string.main_songs_starred,
MEDIA_SONG_STARRED_ID,
null,
R.string.main_songs_title
)
// Albums
mediaItems.add(
R.string.main_albums_newest,
MEDIA_ALBUM_NEWEST_ID,
null,
R.string.main_albums_title
)
mediaItems.add(
R.string.main_albums_recent,
MEDIA_ALBUM_RECENT_ID,
null,
R.string.main_albums_title
)
mediaItems.add(
R.string.main_albums_frequent,
MEDIA_ALBUM_FREQUENT_ID,
null,
R.string.main_albums_title
)
mediaItems.add(
R.string.main_albums_random,
MEDIA_ALBUM_RANDOM_ID,
null,
R.string.main_albums_title
)
mediaItems.add(
R.string.main_albums_starred,
MEDIA_ALBUM_STARRED_ID,
null,
R.string.main_albums_title
)
// Other
mediaItems.add(R.string.button_bar_shares, MEDIA_SHARE_ID, null, null)
mediaItems.add(R.string.button_bar_bookmarks, MEDIA_BOOKMARK_ID, null, null)
mediaItems.add(R.string.button_bar_podcasts, MEDIA_PODCAST_ID, null, null)
result.sendResult(mediaItems)
}
private fun getArtists(result: Result<MutableList<MediaBrowserCompat.MediaItem>>, section: String? = null) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
serviceScope.launch {
var artists = if (!isOffline && useId3Tags) {
// TODO this list can be big so we're not refreshing.
// Maybe a refresh menu item can be added
musicService.getArtists(false)
} else {
musicService.getIndexes(musicFolderId, false)
}
if (section != null)
artists = artists.filter {
artist -> getSectionFromName(artist.name ?: "") == section
}
// If there are too many artists, create alphabetic index of them
if (section == null && artists.count() > displayLimit) {
val index = mutableListOf<String>()
// TODO This sort should use ignoredArticles somehow...
artists = artists.sortedBy { artist -> artist.name }
artists.map { artist ->
val currentSection = getSectionFromName(artist.name ?: "")
if (!index.contains(currentSection)) {
index.add(currentSection)
mediaItems.add(
currentSection,
listOf(MEDIA_ARTIST_SECTION, currentSection).joinToString("|"),
null
)
}
}
} else {
artists.map { artist ->
mediaItems.add(
artist.name ?: "",
listOf(MEDIA_ARTIST_ITEM, artist.id).joinToString("|"),
null
)
}
}
result.sendResult(mediaItems)
}
}
private fun getAlbums(
result: Result<MutableList<MediaBrowserCompat.MediaItem>>,
artistId: String? = null
) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getPlaylists(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
serviceScope.launch {
val playlists = musicService.getPlaylists(true)
playlists.map { playlist ->
mediaItems.add(
playlist.name,
listOf(MEDIA_PLAYLIST_ITEM, playlist.id, playlist.name)
.joinToString("|"),
null
)
}
result.sendResult(mediaItems)
}
}
private fun getPlaylist(id: String, name: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
serviceScope.launch {
val content = musicService.getPlaylist(id, name)
mediaItems.add(
R.string.select_album_play_all,
listOf(MEDIA_PLAYLIST_ITEM, id, name).joinToString("|"),
R.drawable.ic_stat_play_dark,
null,
false
)
// Playlist should be cached as it may contain random elements
playlistCache = content.getAllChild()
playlistCache!!.take(displayLimit).map { item ->
mediaItems.add(MediaBrowserCompat.MediaItem(
Util.getMediaDescriptionForEntry(
item,
listOf(MEDIA_PLAYLIST_SONG_ITEM, id, name, item.id).joinToString("|")
),
MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
))
}
result.sendResult(mediaItems)
}
}
private fun playPlaylist(id: String, name: String) {
serviceScope.launch {
if (playlistCache == null) {
// This can only happen if Android Auto cached items, but Ultrasonic has forgot them
val content = musicService.getPlaylist(id, name)
playlistCache = content.getAllChild()
}
mediaPlayerController.download(
playlistCache,
save = false,
autoPlay = true,
playNext = false,
shuffle = false,
newPlaylist = true
)
}
}
private fun playPlaylistSong(id: String, name: String, songId: String) {
serviceScope.launch {
if (playlistCache == null) {
// This can only happen if Android Auto cached items, but Ultrasonic has forgot them
val content = musicService.getPlaylist(id, name)
playlistCache = content.getAllChild()
}
val song = playlistCache!!.firstOrNull{x -> x.id == songId}
if (song != null) {
mediaPlayerController.download(
listOf(song),
save = false,
autoPlay = false,
playNext = true,
shuffle = false,
newPlaylist = false
)
mediaPlayerController.next()
}
}
}
private fun getPodcasts(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getBookmarks(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getShares(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getStarredSongs(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getRandomSongs(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getStarredAlbums(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getRandomAlbums(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getRecentAlbums(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getNewestAlbums(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun getFrequentAlbums(result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
val mediaItems: MutableList<MediaBrowserCompat.MediaItem> = ArrayList()
result.detach()
result.sendResult(mediaItems)
}
private fun MutableList<MediaBrowserCompat.MediaItem>.add(
title: String,
mediaId: String,
icon: Int?,
) {
val builder = MediaDescriptionCompat.Builder()
builder.setTitle(title)
builder.setMediaId(mediaId)
if (icon != null)
builder.setIconUri(Util.getUriToDrawable(applicationContext, icon))
val mediaItem = MediaBrowserCompat.MediaItem(
builder.build(),
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
this.add(mediaItem)
}
private fun MutableList<MediaBrowserCompat.MediaItem>.add(
resId: Int,
mediaId: String,
icon: Int?,
groupNameId: Int?,
browsable: Boolean = true
) {
val builder = MediaDescriptionCompat.Builder()
builder.setTitle(getString(resId))
builder.setMediaId(mediaId)
if (icon != null)
builder.setIconUri(Util.getUriToDrawable(applicationContext, icon))
if (groupNameId != null)
builder.setExtras(Bundle().apply { putString(
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE,
getString(groupNameId)
) })
val mediaItem = MediaBrowserCompat.MediaItem(
builder.build(),
if (browsable) MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
else MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
)
this.add(mediaItem)
}
private fun getSectionFromName(name: String): String {
var section = name.first().uppercaseChar()
if (!section.isLetter()) section = '#'
return section.toString()
}
}

View File

@ -247,10 +247,10 @@ class MediaPlayerController(
}
@set:Synchronized
var repeatMode: RepeatMode?
get() = Util.getRepeatMode()
var repeatMode: RepeatMode
get() = Util.repeatMode
set(repeatMode) {
Util.setRepeatMode(repeatMode)
Util.repeatMode = repeatMode
val mediaPlayerService = runningInstance
mediaPlayerService?.setNextPlaying()
}

View File

@ -65,7 +65,7 @@ class MediaPlayerService : Service() {
private lateinit var mediaSessionEventListener: MediaSessionEventListener
private val repeatMode: RepeatMode
get() = Util.getRepeatMode()
get() = Util.repeatMode
override fun onBind(intent: Intent): IBinder {
return binder

View File

@ -40,13 +40,13 @@ class ShareHandler(val context: Context) {
swipe: SwipeRefreshLayout?,
cancellationToken: CancellationToken
) {
val askForDetails = Util.getShouldAskForShareDetails()
val askForDetails = Util.shouldAskForShareDetails
val shareDetails = ShareDetails()
shareDetails.Entries = entries
if (askForDetails) {
showDialog(fragment, shareDetails, swipe, cancellationToken)
} else {
shareDetails.Description = Util.getDefaultShareDescription()
shareDetails.Description = Util.defaultShareDescription
shareDetails.Expiration = TimeSpan.getCurrentTime().add(
Util.getDefaultShareExpirationInMillis(context)
).totalMilliseconds
@ -133,16 +133,16 @@ class ShareHandler(val context: Context) {
}
shareDetails.Description = shareDescription!!.text.toString()
if (hideDialogCheckBox!!.isChecked) {
Util.setShouldAskForShareDetails(false)
Util.shouldAskForShareDetails = false
}
if (saveAsDefaultsCheckBox!!.isChecked) {
val timeSpanType: String = timeSpanPicker!!.timeSpanType
val timeSpanAmount: Int = timeSpanPicker!!.timeSpanAmount
Util.setDefaultShareExpiration(
Util.defaultShareExpiration =
if (!noExpirationCheckBox!!.isChecked && timeSpanAmount > 0)
String.format("%d:%s", timeSpanAmount, timeSpanType) else ""
)
Util.setDefaultShareDescription(shareDetails.Description)
Util.defaultShareDescription = shareDetails.Description
}
share(fragment, shareDetails, swipe, cancellationToken)
}
@ -157,8 +157,8 @@ class ShareHandler(val context: Context) {
b ->
timeSpanPicker!!.isEnabled = !b
}
val defaultDescription = Util.getDefaultShareDescription()
val timeSpan = Util.getDefaultShareExpiration()
val defaultDescription = Util.defaultShareDescription
val timeSpan = Util.defaultShareExpiration
val split = pattern.split(timeSpan)
if (split.size == 2) {
val timeSpanAmount = split[0].toInt()

View File

@ -24,6 +24,9 @@ import timber.log.Timber
private const val INTENT_CODE_MEDIA_BUTTON = 161
/**
* Central place to handle the state of the MediaSession
*/
class MediaSessionHandler : KoinComponent {
private var mediaSession: MediaSessionCompat? = null
@ -249,7 +252,7 @@ class MediaSessionHandler : KoinComponent {
mediaSession!!.setQueueTitle(applicationContext.getString(R.string.button_bar_now_playing))
mediaSession!!.setQueue(playlist.mapIndexed { id, song ->
MediaSessionCompat.QueueItem(
getMediaDescriptionForEntry(song),
Util.getMediaDescriptionForEntry(song),
id.toLong())
})
}
@ -316,66 +319,4 @@ class MediaSessionHandler : KoinComponent {
intent.putExtra(Intent.EXTRA_KEY_EVENT, KeyEvent(KeyEvent.ACTION_DOWN, keycode))
return PendingIntent.getBroadcast(context, requestCode, intent, flags)
}
private fun getMediaDescriptionForEntry(song: MusicDirectory.Entry): MediaDescriptionCompat {
val descriptionBuilder = MediaDescriptionCompat.Builder()
val artist = StringBuilder(60)
var bitRate: String? = null
val duration = song.duration
if (duration != null) {
artist.append(String.format("%s ", Util.formatTotalDuration(duration.toLong())))
}
if (song.bitRate != null)
bitRate = String.format(
applicationContext.getString(R.string.song_details_kbps), song.bitRate
)
val fileFormat: String?
val suffix = song.suffix
val transcodedSuffix = song.transcodedSuffix
fileFormat = if (
TextUtils.isEmpty(transcodedSuffix) || transcodedSuffix == suffix || song.isVideo
) suffix else String.format("%s > %s", suffix, transcodedSuffix)
val artistName = song.artist
if (artistName != null) {
if (Util.shouldDisplayBitrateWithArtist()) {
artist.append(artistName).append(" (").append(
String.format(
applicationContext.getString(R.string.song_details_all),
if (bitRate == null) "" else String.format("%s ", bitRate), fileFormat
)
).append(')')
} else {
artist.append(artistName)
}
}
val trackNumber = song.track ?: 0
val title = StringBuilder(60)
if (Util.shouldShowTrackNumber() && trackNumber > 0)
title.append(String.format("%02d - ", trackNumber))
title.append(song.title)
if (song.isVideo && Util.shouldDisplayBitrateWithArtist()) {
title.append(" (").append(
String.format(
applicationContext.getString(R.string.song_details_all),
if (bitRate == null) "" else String.format("%s ", bitRate), fileFormat
)
).append(')')
}
descriptionBuilder.setTitle(title)
descriptionBuilder.setSubtitle(artist)
return descriptionBuilder.build()
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M9,13.75c-2.34,0 -7,1.17 -7,3.5L2,19h14v-1.75c0,-2.33 -4.66,-3.5 -7,-3.5zM4.34,17c0.84,-0.58 2.87,-1.25 4.66,-1.25s3.82,0.67 4.66,1.25L4.34,17zM9,12c1.93,0 3.5,-1.57 3.5,-3.5S10.93,5 9,5 5.5,6.57 5.5,8.5 7.07,12 9,12zM9,7c0.83,0 1.5,0.67 1.5,1.5S9.83,10 9,10s-1.5,-0.67 -1.5,-1.5S8.17,7 9,7zM16.04,13.81c1.16,0.84 1.96,1.96 1.96,3.44L18,19h4v-1.75c0,-2.02 -3.5,-3.17 -5.96,-3.44zM15,12c1.93,0 3.5,-1.57 3.5,-3.5S16.93,5 15,5c-0.54,0 -1.04,0.13 -1.5,0.35 0.63,0.89 1,1.98 1,3.15s-0.37,2.26 -1,3.15c0.46,0.22 0.96,0.35 1.5,0.35z"/>
</vector>

View File

@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M22,6h-5v8.18C16.69,14.07 16.35,14 16,14c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3V8h3V6zM15,6H3v2h12V6zM15,10H3v2h12V10zM11,14H3v2h8V14z"/>
</vector>