mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-05-06 10:31:09 +03:00
Started implementing Media Browser
Added root menus, playlists and artists
This commit is contained in:
parent
635ea2f55e
commit
f50d6f13f4
File diff suppressed because it is too large
Load Diff
@ -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) {
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
1320
ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt
Normal file
1320
ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt
Normal file
File diff suppressed because it is too large
Load Diff
10
ultrasonic/src/main/res/drawable/ic_artist.xml
Normal file
10
ultrasonic/src/main/res/drawable/ic_artist.xml
Normal 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>
|
11
ultrasonic/src/main/res/drawable/ic_library.xml
Normal file
11
ultrasonic/src/main/res/drawable/ic_library.xml
Normal 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>
|
Loading…
x
Reference in New Issue
Block a user