From 8b7b916489cd6d842baeb4b60ed5b98cca29af83 Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Fri, 19 Apr 2024 18:48:54 -0600 Subject: [PATCH] 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 --- .../auxio/image/service/CoilBitmapLoader.kt | 18 +++++- .../auxio/music/service/MediaItemBrowser.kt | 21 +------ .../service/ExoPlaybackStateHolder.kt | 3 +- .../playback/service/MediaSessionPlayer.kt | 18 ++++++ .../service/MediaSessionServiceFragment.kt | 2 +- ...ckReciever.kt => PlaybackActionHandler.kt} | 57 ++++++++++++++++++- .../java/org/oxycblt/auxio/tasker/Tasker.kt | 19 ++++++- media | 2 +- 8 files changed, 116 insertions(+), 24 deletions(-) rename app/src/main/java/org/oxycblt/auxio/playback/service/{SystemPlaybackReciever.kt => PlaybackActionHandler.kt} (81%) diff --git a/app/src/main/java/org/oxycblt/auxio/image/service/CoilBitmapLoader.kt b/app/src/main/java/org/oxycblt/auxio/image/service/CoilBitmapLoader.kt index 58e8b609d..ca94f66cf 100644 --- a/app/src/main/java/org/oxycblt/auxio/image/service/CoilBitmapLoader.kt +++ b/app/src/main/java/org/oxycblt/auxio/image/service/CoilBitmapLoader.kt @@ -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 { 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 { diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt index 5c8c35ede..63b68925d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + 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) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt index 3666f1795..070432bc8 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/ExoPlaybackStateHolder.kt @@ -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() { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionPlayer.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionPlayer.kt index fb662ef2f..6b56d334a 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionPlayer.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionPlayer.kt @@ -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, startIndex: Int, diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt index 4d87ae4ce..a626cd4b6 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt @@ -255,7 +255,7 @@ constructor( mediaSession.setCustomLayout(layout) } - override fun invalidate(ids: Map){ + override fun invalidate(ids: Map) { for (id in ids) { mediaSession.notifyChildrenChanged(id.key, id.value, null) } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/SystemPlaybackReciever.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt similarity index 81% rename from app/src/main/java/org/oxycblt/auxio/playback/service/SystemPlaybackReciever.kt rename to app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt index 5d89acd4c..7aa37e36b 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/SystemPlaybackReciever.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/PlaybackActionHandler.kt @@ -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()) diff --git a/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt b/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt index e823bb338..ec2d6ac99 100644 --- a/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt +++ b/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt @@ -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 . + */ + package org.oxycblt.auxio.tasker - diff --git a/media b/media index bfa4c10f7..6c77cfa13 160000 --- a/media +++ b/media @@ -1 +1 @@ -Subproject commit bfa4c10f773bb9336d9c7dade490463318b12ab6 +Subproject commit 6c77cfa13c83bf2ae5188603d2c9a51ec4cb3ac3