playback: fix notif issues on older devices

- Slight coroutine delay in cover fetch causes the notif to flicker
- Default play/pause actions look absolutely hideous
This commit is contained in:
Alexander Capehart 2024-04-19 18:48:54 -06:00
parent b99cd96726
commit 8b7b916489
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
8 changed files with 116 additions and 24 deletions

View file

@ -18,22 +18,31 @@
package org.oxycblt.auxio.image.service
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import androidx.media3.common.MediaMetadata
import androidx.media3.common.util.BitmapLoader
import coil.ImageLoader
import coil.memory.MemoryCache
import coil.request.Options
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import org.oxycblt.auxio.image.BitmapProvider
import org.oxycblt.auxio.image.extractor.SongKeyer
import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.service.MediaSessionUID
class MediaSessionBitmapLoader
@Inject
constructor(
@ApplicationContext private val context: Context,
private val musicRepository: MusicRepository,
private val bitmapProvider: BitmapProvider
private val bitmapProvider: BitmapProvider,
private val songKeyer: SongKeyer,
private val imageLoader: ImageLoader,
) : BitmapLoader {
override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
throw NotImplementedError()
@ -58,6 +67,13 @@ constructor(
else -> return null
}
?: return null
// Even launching a coroutine to obtained cached covers is enough to make the notification
// go without covers.
val key = songKeyer.key(listOf(song), Options(context))
if (imageLoader.memoryCache?.get(MemoryCache.Key(key)) != null) {
future.set(imageLoader.memoryCache?.get(MemoryCache.Key(key))?.bitmap)
return future
}
bitmapProvider.load(
song,
object : BitmapProvider.Target {

View file

@ -15,7 +15,7 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.music.service
import android.content.Context
@ -141,10 +141,8 @@ constructor(
is MediaSessionUID.Category -> return uid.toMediaItem(context)
is MediaSessionUID.Single ->
musicRepository.find(uid.uid)?.let { musicRepository.find(it.uid) }
is MediaSessionUID.Joined ->
musicRepository.find(uid.childUid)?.let { musicRepository.find(it.uid) }
null -> null
}
?: return null
@ -179,40 +177,32 @@ constructor(
when (mediaSessionUID) {
MediaSessionUID.Category.ROOT ->
MediaSessionUID.Category.IMPORTANT.map { it.toMediaItem(context) }
MediaSessionUID.Category.SONGS ->
listSettings.songSort.songs(deviceLibrary.songs).map {
it.toMediaItem(context, null)
}
MediaSessionUID.Category.ALBUMS ->
listSettings.albumSort.albums(deviceLibrary.albums).map {
it.toMediaItem(context)
}
MediaSessionUID.Category.ARTISTS ->
listSettings.artistSort.artists(deviceLibrary.artists).map {
it.toMediaItem(context)
}
MediaSessionUID.Category.GENRES ->
listSettings.genreSort.genres(deviceLibrary.genres).map {
it.toMediaItem(context)
}
MediaSessionUID.Category.PLAYLISTS ->
userLibrary.playlists.map { it.toMediaItem(context) }
}
}
is MediaSessionUID.Single -> {
getChildMediaItems(mediaSessionUID.uid)
}
is MediaSessionUID.Joined -> {
getChildMediaItems(mediaSessionUID.childUid)
}
null -> {
return null
}
@ -225,24 +215,20 @@ constructor(
val songs = listSettings.albumSongSort.songs(item.songs)
songs.map { it.toMediaItem(context, item) }
}
is Artist -> {
val albums = ARTIST_ALBUMS_SORT.albums(item.explicitAlbums + item.implicitAlbums)
val songs = listSettings.artistSongSort.songs(item.songs)
albums.map { it.toMediaItem(context) } + songs.map { it.toMediaItem(context, item) }
}
is Genre -> {
val artists = GENRE_ARTISTS_SORT.artists(item.artists)
val songs = listSettings.genreSongSort.songs(item.songs)
artists.map { it.toMediaItem(context) } +
songs.map { it.toMediaItem(context, null) }
songs.map { it.toMediaItem(context, null) }
}
is Playlist -> {
item.songs.map { it.toMediaItem(context, item) }
}
is Song,
null -> return null
}
@ -339,8 +325,7 @@ constructor(
deviceLibrary.albums,
deviceLibrary.artists,
deviceLibrary.genres,
userLibrary.playlists
)
userLibrary.playlists)
val results = searchEngine.search(items, query)
for (entry in searchSubscribers.entries) {
if (entry.value == query) {

View file

@ -106,7 +106,8 @@ class ExoPlaybackStateHolder(
private set
val mediaSessionPlayer: Player
get() = MediaSessionPlayer(player, playbackManager, commandFactory, musicRepository)
get() =
MediaSessionPlayer(context, player, playbackManager, commandFactory, musicRepository)
override val progression: Progression
get() {

View file

@ -18,6 +18,8 @@
package org.oxycblt.auxio.playback.service
import android.content.Context
import android.os.Bundle
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
@ -31,6 +33,7 @@ import androidx.media3.common.PlaybackParameters
import androidx.media3.common.Player
import androidx.media3.common.TrackSelectionParameters
import java.lang.Exception
import org.oxycblt.auxio.R
import org.oxycblt.auxio.music.Album
import org.oxycblt.auxio.music.Artist
import org.oxycblt.auxio.music.Genre
@ -60,6 +63,7 @@ import org.oxycblt.auxio.util.logE
* @author Alexander Capehart
*/
class MediaSessionPlayer(
private val context: Context,
player: Player,
private val playbackManager: PlaybackStateManager,
private val commandFactory: PlaybackCommand.Factory,
@ -86,6 +90,20 @@ class MediaSessionPlayer(
setMediaItems(mediaItems, C.INDEX_UNSET, C.TIME_UNSET)
}
override fun getMediaMetadata() =
super.getMediaMetadata().run {
val existingExtras = extras
val newExtras = existingExtras?.let { Bundle(it) } ?: Bundle()
newExtras.apply {
putString(
"parent",
playbackManager.parent?.name?.resolve(context)
?: context.getString(R.string.lbl_all_songs))
}
buildUpon().setExtras(newExtras).build()
}
override fun setMediaItems(
mediaItems: MutableList<MediaItem>,
startIndex: Int,

View file

@ -255,7 +255,7 @@ constructor(
mediaSession.setCustomLayout(layout)
}
override fun invalidate(ids: Map<String, Int>){
override fun invalidate(ids: Map<String, Int>) {
for (id in ids) {
mediaSession.notifyChildrenChanged(id.key, id.value, null)
}

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2024 Auxio Project
* SystemPlaybackReciever.kt is part of Auxio.
* PlaybackActionHandler.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,7 +25,9 @@ import android.content.IntentFilter
import android.media.AudioManager
import android.os.Bundle
import androidx.core.content.ContextCompat
import androidx.media3.common.Player
import androidx.media3.session.CommandButton
import androidx.media3.session.DefaultMediaNotificationProvider
import androidx.media3.session.SessionCommand
import androidx.media3.session.SessionCommands
import dagger.hilt.android.qualifiers.ApplicationContext
@ -36,6 +38,7 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.playback.ActionMode
import org.oxycblt.auxio.playback.PlaybackSettings
import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.playback.state.Progression
import org.oxycblt.auxio.playback.state.RepeatMode
import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.widgets.WidgetComponent
@ -102,6 +105,13 @@ constructor(
.setDisplayName(context.getString(R.string.desc_change_repeat))
.setSessionCommand(
SessionCommand(PlaybackActions.ACTION_INC_REPEAT_MODE, Bundle()))
.setEnabled(true)
.setExtras(
Bundle().apply {
putInt(
DefaultMediaNotificationProvider.COMMAND_KEY_COMPACT_VIEW_INDEX,
0)
})
.build())
}
ActionMode.SHUFFLE -> {
@ -113,16 +123,56 @@ constructor(
.setDisplayName(context.getString(R.string.lbl_shuffle))
.setSessionCommand(
SessionCommand(PlaybackActions.ACTION_INVERT_SHUFFLE, Bundle()))
.setEnabled(true)
.build())
}
else -> {}
}
actions.add(
CommandButton.Builder()
.setIconResId(R.drawable.ic_skip_prev_24)
.setDisplayName(context.getString(R.string.desc_skip_prev))
.setPlayerCommand(Player.COMMAND_SEEK_TO_PREVIOUS)
.setEnabled(true)
.setExtras(
Bundle().apply {
putInt(DefaultMediaNotificationProvider.COMMAND_KEY_COMPACT_VIEW_INDEX, 1)
})
.build())
actions.add(
CommandButton.Builder()
.setIconResId(
if (playbackManager.progression.isPlaying) R.drawable.ic_pause_24
else R.drawable.ic_play_24)
.setDisplayName(context.getString(R.string.desc_play_pause))
.setPlayerCommand(Player.COMMAND_PLAY_PAUSE)
.setEnabled(true)
.setExtras(
Bundle().apply {
putInt(DefaultMediaNotificationProvider.COMMAND_KEY_COMPACT_VIEW_INDEX, 2)
})
.build())
actions.add(
CommandButton.Builder()
.setIconResId(R.drawable.ic_skip_next_24)
.setDisplayName(context.getString(R.string.desc_skip_next))
.setPlayerCommand(Player.COMMAND_SEEK_TO_NEXT)
.setEnabled(true)
.setExtras(
Bundle().apply {
putInt(DefaultMediaNotificationProvider.COMMAND_KEY_COMPACT_VIEW_INDEX, 3)
})
.build())
actions.add(
CommandButton.Builder()
.setIconResId(R.drawable.ic_close_24)
.setDisplayName(context.getString(R.string.desc_exit))
.setSessionCommand(SessionCommand(PlaybackActions.ACTION_EXIT, Bundle()))
.setEnabled(true)
.build())
return actions
@ -133,6 +183,11 @@ constructor(
callback?.onCustomLayoutChanged(createCustomLayout())
}
override fun onProgressionChanged(progression: Progression) {
super.onProgressionChanged(progression)
callback?.onCustomLayoutChanged(createCustomLayout())
}
override fun onRepeatModeChanged(repeatMode: RepeatMode) {
super.onRepeatModeChanged(repeatMode)
callback?.onCustomLayoutChanged(createCustomLayout())

View file

@ -1,2 +1,19 @@
/*
* Copyright (c) 2024 Auxio Project
* Tasker.kt is part of Auxio.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.auxio.tasker

2
media

@ -1 +1 @@
Subproject commit bfa4c10f773bb9336d9c7dade490463318b12ab6
Subproject commit 6c77cfa13c83bf2ae5188603d2c9a51ec4cb3ac3