Show only visible tabs in the media tree
This commit is contained in:
parent
2aabdde5f5
commit
3ea9263d0e
3 changed files with 155 additions and 122 deletions
|
@ -327,3 +327,6 @@ fun Context.loadTrackCoverArt(track: Track?): Bitmap? {
|
|||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
fun Context.isTabVisible(flag: Int) = config.showTabs and flag != 0
|
||||
|
|
|
@ -14,7 +14,7 @@ import java.util.concurrent.Executors
|
|||
|
||||
@UnstableApi
|
||||
internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySession.Callback {
|
||||
private val browsers = mutableMapOf<String, MediaSession.ControllerInfo>()
|
||||
private val browsers = mutableMapOf<MediaSession.ControllerInfo, String>()
|
||||
private val executorService by lazy {
|
||||
MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(4))
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySe
|
|||
val children = mediaItemProvider.getChildren(parentId)
|
||||
?: return@callWhenSourceReady LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
|
||||
|
||||
browsers += parentId to browser
|
||||
browsers[browser] = parentId
|
||||
session.notifyChildrenChanged(browser, parentId, children.size, params)
|
||||
LibraryResult.ofVoid()
|
||||
}
|
||||
|
@ -145,16 +145,20 @@ internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySe
|
|||
startIndex: Int,
|
||||
startPositionMs: Long
|
||||
) = callWhenSourceReady {
|
||||
// this is to avoid single items in the queue: https://github.com/androidx/media/issues/156
|
||||
var allMediaItems = mediaItems
|
||||
var allItems = mediaItems
|
||||
var realStartIndex = startIndex
|
||||
|
||||
// this is to avoid single items in the queue: https://github.com/androidx/media/issues/156
|
||||
if (startIndex == C.INDEX_UNSET && mediaItems.size == 1) {
|
||||
val currentItem = mediaItems[0]
|
||||
allMediaItems = mediaItemProvider.getChildren(currentRoot)?.toMutableList() ?: allMediaItems
|
||||
realStartIndex = allMediaItems.indexOfFirst { currentItem.mediaId == it.mediaId }
|
||||
val queueItems = mediaItemProvider.getChildren(currentRoot)?.toMutableList()
|
||||
|
||||
allItems = queueItems?.takeIf { it.contains(currentItem) }
|
||||
?: mediaItemProvider.getDefaultQueue()?.toMutableList() ?: allItems
|
||||
realStartIndex = allItems.indexOfFirst { it.mediaId == currentItem.mediaId }
|
||||
}
|
||||
|
||||
super.onSetMediaItems(mediaSession, controller, allMediaItems, realStartIndex, startPositionMs).get()
|
||||
super.onSetMediaItems(mediaSession, controller, allItems, realStartIndex, startPositionMs).get()
|
||||
}
|
||||
|
||||
override fun onAddMediaItems(
|
||||
|
@ -184,10 +188,14 @@ internal fun PlaybackService.getMediaSessionCallback() = object : MediaLibrarySe
|
|||
private fun reloadContent() {
|
||||
mediaItemProvider.reload()
|
||||
mediaItemProvider.whenReady {
|
||||
val rootItem = mediaItemProvider.getRootItem()
|
||||
val rootItemCount = mediaItemProvider.getChildren(rootItem.mediaId)?.size ?: 0
|
||||
|
||||
executorService.execute {
|
||||
browsers.forEach { (parentId, browser) ->
|
||||
browsers.forEach { (browser, parentId) ->
|
||||
val itemCount = mediaItemProvider.getChildren(parentId)?.size ?: 0
|
||||
mediaSession.notifyChildrenChanged(browser, parentId, itemCount, null)
|
||||
mediaSession.notifyChildrenChanged(browser, rootItem.mediaId, rootItemCount, null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,19 +5,37 @@ import android.content.Context
|
|||
import android.net.Uri
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MediaMetadata
|
||||
import androidx.media3.common.MediaMetadata.MEDIA_TYPE_PLAYLIST
|
||||
import androidx.media3.common.MediaMetadata.MediaType
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
|
||||
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
|
||||
import com.simplemobiletools.musicplayer.R
|
||||
import com.simplemobiletools.musicplayer.extensions.audioHelper
|
||||
import com.simplemobiletools.musicplayer.extensions.buildMediaItem
|
||||
import com.simplemobiletools.musicplayer.extensions.queueDAO
|
||||
import com.simplemobiletools.musicplayer.extensions.toMediaItem
|
||||
import com.simplemobiletools.musicplayer.extensions.*
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_ALBUMS
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_ARTISTS
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_FOLDERS
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_GENRES
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_PLAYLISTS
|
||||
import com.simplemobiletools.musicplayer.helpers.TAB_TRACKS
|
||||
import com.simplemobiletools.musicplayer.models.QueueItem
|
||||
|
||||
private const val STATE_CREATED = 1
|
||||
private const val STATE_INITIALIZING = 2
|
||||
private const val STATE_INITIALIZED = 3
|
||||
private const val STATE_ERROR = 4
|
||||
|
||||
private const val SMP_ROOT_ID = "__ROOT__"
|
||||
private const val SMP_PLAYLISTS_ROOT_ID = "__PLAYLISTS__"
|
||||
private const val SMP_FOLDERS_ROOT_ID = "__FOLDERS__"
|
||||
private const val SMP_ARTISTS_ROOT_ID = "__ARTISTS__"
|
||||
private const val SMP_ALBUMS_ROOT_ID = "__ALBUMS__"
|
||||
private const val SMP_TRACKS_ROOT_ID = "__TRACKS__"
|
||||
private const val SMP_GENRES_ROOT_ID = "__GENRES__"
|
||||
|
||||
@UnstableApi
|
||||
internal class MediaItemProvider(private val context: Context) {
|
||||
|
||||
|
@ -47,57 +65,9 @@ internal class MediaItemProvider(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
private val audioHelper = context.audioHelper
|
||||
|
||||
init {
|
||||
state = STATE_INITIALIZING
|
||||
|
||||
val rootChildren = mutableListOf<MediaItem>()
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(R.string.playlists),
|
||||
artworkUri = buildDrawableUri(R.drawable.ic_playlist_vector),
|
||||
mediaId = SMP_PLAYLISTS_ROOT_ID,
|
||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
|
||||
)
|
||||
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(R.string.folders),
|
||||
artworkUri = buildDrawableUri(R.drawable.ic_folders_vector),
|
||||
mediaId = SMP_FOLDERS_ROOT_ID,
|
||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS
|
||||
)
|
||||
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(R.string.artists),
|
||||
artworkUri = buildDrawableUri(R.drawable.ic_person_vector),
|
||||
mediaId = SMP_ARTISTS_ROOT_ID,
|
||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS
|
||||
)
|
||||
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(R.string.albums),
|
||||
artworkUri = buildDrawableUri(R.drawable.ic_album_vector),
|
||||
mediaId = SMP_ALBUMS_ROOT_ID,
|
||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
|
||||
)
|
||||
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(R.string.tracks),
|
||||
artworkUri = buildDrawableUri(R.drawable.ic_music_note_vector),
|
||||
mediaId = SMP_TRACKS_ROOT_ID,
|
||||
mediaType = MEDIA_TYPE_PLAYLIST
|
||||
)
|
||||
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(R.string.genres),
|
||||
artworkUri = buildDrawableUri(R.drawable.ic_genre_vector),
|
||||
mediaId = SMP_GENRES_ROOT_ID,
|
||||
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_GENRES
|
||||
)
|
||||
|
||||
addNodeAndChildren(
|
||||
item = buildMediaItem(title = context.getString(R.string.root), mediaId = SMP_ROOT_ID, mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED),
|
||||
children = rootChildren
|
||||
)
|
||||
|
||||
reload()
|
||||
}
|
||||
|
||||
|
@ -131,18 +101,17 @@ internal class MediaItemProvider(private val context: Context) {
|
|||
}
|
||||
|
||||
fun getItemFromSearch(searchQuery: String): MediaItem? {
|
||||
return if (searchQuery in titleMap) {
|
||||
titleMap[searchQuery]?.item
|
||||
} else {
|
||||
val partialMatches = titleMap.keys.filter { it.contains(searchQuery) }
|
||||
if (partialMatches.isNotEmpty()) {
|
||||
titleMap[partialMatches.first()]?.item
|
||||
} else {
|
||||
null
|
||||
}
|
||||
var mediaItem = titleMap[searchQuery]?.item
|
||||
if (mediaItem == null) {
|
||||
val partialMatch = titleMap.keys.find { it.contains(searchQuery) } ?: return null
|
||||
mediaItem = titleMap[partialMatch]?.item
|
||||
}
|
||||
|
||||
return mediaItem
|
||||
}
|
||||
|
||||
fun getDefaultQueue() = getChildren(SMP_TRACKS_ROOT_ID)
|
||||
|
||||
fun getRecentItemsWithStartPosition(random: Boolean = true): MediaItemsWithStartPosition {
|
||||
val recentItems = context.queueDAO.getAll().mapNotNull { getMediaItemFromQueueItem(it) }
|
||||
var startPosition = 0L
|
||||
|
@ -171,87 +140,140 @@ internal class MediaItemProvider(private val context: Context) {
|
|||
}
|
||||
|
||||
ensureBackgroundThread {
|
||||
val trackId = mediaId.toLong()
|
||||
val trackId = mediaId.filter { it.isDigit() }.toLong()
|
||||
val queueItems = mediaItems.mapIndexed { index, mediaItem ->
|
||||
QueueItem(trackId = mediaItem.mediaId.toLong(), trackOrder = index, isCurrent = false, lastPosition = 0)
|
||||
QueueItem(trackId = mediaItem.mediaId.filter { it.isDigit() }.toLong(), trackOrder = index, isCurrent = false, lastPosition = 0)
|
||||
}
|
||||
|
||||
context.audioHelper.updateQueue(queueItems, trackId, startPosition)
|
||||
audioHelper.updateQueue(queueItems, trackId, startPosition)
|
||||
}
|
||||
}
|
||||
|
||||
fun reload() {
|
||||
state = STATE_INITIALIZING
|
||||
|
||||
val root = buildMediaItem(title = context.getString(R.string.root), mediaId = SMP_ROOT_ID, mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED)
|
||||
val rootChildren = RootCategories.buildRootChildren(context)
|
||||
addNodeAndChildren(item = root, children = rootChildren)
|
||||
|
||||
ensureBackgroundThread {
|
||||
with(context.audioHelper) {
|
||||
getAllPlaylists().forEach { playlist ->
|
||||
addNodeAndChildren(SMP_PLAYLISTS_ROOT_ID, playlist.toMediaItem(), getPlaylistTracks(playlist.id).map { it.toMediaItem() })
|
||||
}
|
||||
|
||||
getAllFolders().forEach { folder ->
|
||||
addNodeAndChildren(SMP_FOLDERS_ROOT_ID, folder.toMediaItem(), getFolderTracks(folder.title).map { it.toMediaItem() })
|
||||
}
|
||||
|
||||
getAllArtists().forEach { artist ->
|
||||
addNodeAndChildren(SMP_ARTISTS_ROOT_ID, artist.toMediaItem(), getArtistAlbums(artist.id).map { it.toMediaItem() })
|
||||
}
|
||||
|
||||
getAllAlbums().forEach { album ->
|
||||
addNodeAndChildren(SMP_ALBUMS_ROOT_ID, album.toMediaItem(), getAlbumTracks(album.id).map { it.toMediaItem() })
|
||||
}
|
||||
|
||||
getAllTracks().forEach { track ->
|
||||
val trackMediaItem = track.toMediaItem()
|
||||
addNodeAndChildren(SMP_TRACKS_ROOT_ID, trackMediaItem)
|
||||
titleMap[track.title.lowercase()] = MediaItemNode(trackMediaItem)
|
||||
}
|
||||
|
||||
getAllGenres().forEach { genre ->
|
||||
addNodeAndChildren(SMP_GENRES_ROOT_ID, genre.toMediaItem(), getGenreTracks(genre.id).map { it.toMediaItem() })
|
||||
}
|
||||
try {
|
||||
reloadPlaylists()
|
||||
reloadFolders()
|
||||
reloadArtists()
|
||||
reloadAlbums()
|
||||
reloadTracks()
|
||||
reloadGenres()
|
||||
} catch (e: Exception) {
|
||||
state = STATE_ERROR
|
||||
throw e
|
||||
}
|
||||
|
||||
state = STATE_INITIALIZED
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadPlaylists() = with(audioHelper) {
|
||||
getAllPlaylists().forEach { playlist ->
|
||||
addNodeAndChildren(SMP_PLAYLISTS_ROOT_ID, playlist.toMediaItem(), getPlaylistTracks(playlist.id).map { it.toMediaItem() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadFolders() = with(audioHelper) {
|
||||
getAllFolders().forEach { folder ->
|
||||
addNodeAndChildren(SMP_FOLDERS_ROOT_ID, folder.toMediaItem(), getFolderTracks(folder.title).map { it.toMediaItem() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadArtists() = with(audioHelper) {
|
||||
getAllArtists().forEach { artist ->
|
||||
addNodeAndChildren(SMP_ARTISTS_ROOT_ID, artist.toMediaItem(), getArtistAlbums(artist.id).map { it.toMediaItem() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadAlbums() = with(audioHelper) {
|
||||
getAllAlbums().forEach { album ->
|
||||
addNodeAndChildren(SMP_ALBUMS_ROOT_ID, album.toMediaItem(), getAlbumTracks(album.id).map { it.toMediaItem() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadTracks() = with(audioHelper) {
|
||||
getAllTracks().forEach { track ->
|
||||
addNodeAndChildren(SMP_TRACKS_ROOT_ID, track.toMediaItem())
|
||||
titleMap[track.title.lowercase()] = MediaItemNode(track.toMediaItem())
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadGenres() = with(audioHelper) {
|
||||
getAllGenres().forEach { genre ->
|
||||
addNodeAndChildren(SMP_GENRES_ROOT_ID, genre.toMediaItem(), getGenreTracks(genre.id).map { it.toMediaItem() })
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNode(id: String) = treeNodes[id]
|
||||
|
||||
private fun addNodeAndChildren(parentId: String? = null, item: MediaItem, children: List<MediaItem>? = null) {
|
||||
val itemNode = MediaItemNode(item)
|
||||
treeNodes[item.mediaId] = itemNode
|
||||
|
||||
children?.forEach { child ->
|
||||
treeNodes[child.mediaId] = MediaItemNode(child)
|
||||
itemNode.addChild(child.mediaId)
|
||||
}
|
||||
|
||||
if (parentId != null) {
|
||||
getNode(parentId)!!.addChild(item.mediaId)
|
||||
getNode(parentId)?.addChild(item.mediaId)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDrawableUri(drawableRes: Int): Uri? {
|
||||
return Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
|
||||
.authority(context.resources.getResourcePackageName(drawableRes))
|
||||
.appendPath(context.resources.getResourceTypeName(drawableRes))
|
||||
.appendPath(context.resources.getResourceEntryName(drawableRes))
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun getMediaItemFromQueueItem(queueItem: QueueItem) = getNode(queueItem.trackId.toString())?.item
|
||||
}
|
||||
|
||||
private enum class RootCategories(@StringRes val titleRes: Int, @DrawableRes val drawableRes: Int, val mediaId: String, val mediaType: @MediaType Int) {
|
||||
PLAYLISTS(R.string.playlists, R.drawable.ic_playlist_vector, SMP_PLAYLISTS_ROOT_ID, MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS),
|
||||
FOLDERS(R.string.folders, R.drawable.ic_folders_vector, SMP_FOLDERS_ROOT_ID, MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS),
|
||||
ARTISTS(R.string.artists, R.drawable.ic_person_vector, SMP_ARTISTS_ROOT_ID, MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS),
|
||||
ALBUMS(R.string.albums, R.drawable.ic_album_vector, SMP_ALBUMS_ROOT_ID, MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS),
|
||||
TRACKS(R.string.tracks, R.drawable.ic_music_note_vector, SMP_TRACKS_ROOT_ID, MediaMetadata.MEDIA_TYPE_PLAYLIST),
|
||||
GENRES(R.string.genres, R.drawable.ic_genre_vector, SMP_GENRES_ROOT_ID, MediaMetadata.MEDIA_TYPE_FOLDER_GENRES);
|
||||
|
||||
companion object {
|
||||
private const val SMP_ROOT_ID = "__ROOT__"
|
||||
private const val SMP_PLAYLISTS_ROOT_ID = "__PLAYLISTS__"
|
||||
private const val SMP_FOLDERS_ROOT_ID = "__FOLDERS__"
|
||||
private const val SMP_ARTISTS_ROOT_ID = "__ARTISTS__"
|
||||
private const val SMP_ALBUMS_ROOT_ID = "__ALBUMS__"
|
||||
private const val SMP_TRACKS_ROOT_ID = "__TRACKS__"
|
||||
private const val SMP_GENRES_ROOT_ID = "__GENRES__"
|
||||
fun buildRootChildren(context: Context): List<MediaItem> {
|
||||
val rootChildren = mutableListOf<MediaItem>()
|
||||
values().forEach {
|
||||
val flag = getTabFlag(it.mediaId)
|
||||
if (context.isTabVisible(flag)) {
|
||||
rootChildren += buildMediaItem(
|
||||
title = context.getString(it.titleRes),
|
||||
artworkUri = buildDrawableUri(context, it.drawableRes),
|
||||
mediaId = it.mediaId,
|
||||
mediaType = it.mediaType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val STATE_CREATED = 1
|
||||
private const val STATE_INITIALIZING = 2
|
||||
private const val STATE_INITIALIZED = 3
|
||||
private const val STATE_ERROR = 4
|
||||
return rootChildren
|
||||
}
|
||||
|
||||
private fun getTabFlag(mediaId: String): Int {
|
||||
return when (mediaId) {
|
||||
SMP_PLAYLISTS_ROOT_ID -> TAB_PLAYLISTS
|
||||
SMP_FOLDERS_ROOT_ID -> TAB_FOLDERS
|
||||
SMP_ARTISTS_ROOT_ID -> TAB_ARTISTS
|
||||
SMP_ALBUMS_ROOT_ID -> TAB_ALBUMS
|
||||
SMP_TRACKS_ROOT_ID -> TAB_TRACKS
|
||||
SMP_GENRES_ROOT_ID -> TAB_GENRES
|
||||
else -> throw IllegalArgumentException("Invalid media id: $mediaId")
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildDrawableUri(context: Context, drawableRes: Int): Uri? {
|
||||
return Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
|
||||
.authority(context.resources.getResourcePackageName(drawableRes))
|
||||
.appendPath(context.resources.getResourceTypeName(drawableRes))
|
||||
.appendPath(context.resources.getResourceEntryName(drawableRes))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue