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:
parent
b99cd96726
commit
8b7b916489
8 changed files with 116 additions and 24 deletions
|
@ -18,22 +18,31 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.image.service
|
package org.oxycblt.auxio.image.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.util.BitmapLoader
|
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.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import org.oxycblt.auxio.image.BitmapProvider
|
import org.oxycblt.auxio.image.BitmapProvider
|
||||||
|
import org.oxycblt.auxio.image.extractor.SongKeyer
|
||||||
import org.oxycblt.auxio.music.MusicRepository
|
import org.oxycblt.auxio.music.MusicRepository
|
||||||
import org.oxycblt.auxio.music.service.MediaSessionUID
|
import org.oxycblt.auxio.music.service.MediaSessionUID
|
||||||
|
|
||||||
class MediaSessionBitmapLoader
|
class MediaSessionBitmapLoader
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
|
@ApplicationContext private val context: Context,
|
||||||
private val musicRepository: MusicRepository,
|
private val musicRepository: MusicRepository,
|
||||||
private val bitmapProvider: BitmapProvider
|
private val bitmapProvider: BitmapProvider,
|
||||||
|
private val songKeyer: SongKeyer,
|
||||||
|
private val imageLoader: ImageLoader,
|
||||||
) : BitmapLoader {
|
) : BitmapLoader {
|
||||||
override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
|
override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
|
||||||
throw NotImplementedError()
|
throw NotImplementedError()
|
||||||
|
@ -58,6 +67,13 @@ constructor(
|
||||||
else -> return null
|
else -> return null
|
||||||
}
|
}
|
||||||
?: 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(
|
bitmapProvider.load(
|
||||||
song,
|
song,
|
||||||
object : BitmapProvider.Target {
|
object : BitmapProvider.Target {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.service
|
package org.oxycblt.auxio.music.service
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -141,10 +141,8 @@ constructor(
|
||||||
is MediaSessionUID.Category -> return uid.toMediaItem(context)
|
is MediaSessionUID.Category -> return uid.toMediaItem(context)
|
||||||
is MediaSessionUID.Single ->
|
is MediaSessionUID.Single ->
|
||||||
musicRepository.find(uid.uid)?.let { musicRepository.find(it.uid) }
|
musicRepository.find(uid.uid)?.let { musicRepository.find(it.uid) }
|
||||||
|
|
||||||
is MediaSessionUID.Joined ->
|
is MediaSessionUID.Joined ->
|
||||||
musicRepository.find(uid.childUid)?.let { musicRepository.find(it.uid) }
|
musicRepository.find(uid.childUid)?.let { musicRepository.find(it.uid) }
|
||||||
|
|
||||||
null -> null
|
null -> null
|
||||||
}
|
}
|
||||||
?: return null
|
?: return null
|
||||||
|
@ -179,40 +177,32 @@ constructor(
|
||||||
when (mediaSessionUID) {
|
when (mediaSessionUID) {
|
||||||
MediaSessionUID.Category.ROOT ->
|
MediaSessionUID.Category.ROOT ->
|
||||||
MediaSessionUID.Category.IMPORTANT.map { it.toMediaItem(context) }
|
MediaSessionUID.Category.IMPORTANT.map { it.toMediaItem(context) }
|
||||||
|
|
||||||
MediaSessionUID.Category.SONGS ->
|
MediaSessionUID.Category.SONGS ->
|
||||||
listSettings.songSort.songs(deviceLibrary.songs).map {
|
listSettings.songSort.songs(deviceLibrary.songs).map {
|
||||||
it.toMediaItem(context, null)
|
it.toMediaItem(context, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSessionUID.Category.ALBUMS ->
|
MediaSessionUID.Category.ALBUMS ->
|
||||||
listSettings.albumSort.albums(deviceLibrary.albums).map {
|
listSettings.albumSort.albums(deviceLibrary.albums).map {
|
||||||
it.toMediaItem(context)
|
it.toMediaItem(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSessionUID.Category.ARTISTS ->
|
MediaSessionUID.Category.ARTISTS ->
|
||||||
listSettings.artistSort.artists(deviceLibrary.artists).map {
|
listSettings.artistSort.artists(deviceLibrary.artists).map {
|
||||||
it.toMediaItem(context)
|
it.toMediaItem(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSessionUID.Category.GENRES ->
|
MediaSessionUID.Category.GENRES ->
|
||||||
listSettings.genreSort.genres(deviceLibrary.genres).map {
|
listSettings.genreSort.genres(deviceLibrary.genres).map {
|
||||||
it.toMediaItem(context)
|
it.toMediaItem(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaSessionUID.Category.PLAYLISTS ->
|
MediaSessionUID.Category.PLAYLISTS ->
|
||||||
userLibrary.playlists.map { it.toMediaItem(context) }
|
userLibrary.playlists.map { it.toMediaItem(context) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is MediaSessionUID.Single -> {
|
is MediaSessionUID.Single -> {
|
||||||
getChildMediaItems(mediaSessionUID.uid)
|
getChildMediaItems(mediaSessionUID.uid)
|
||||||
}
|
}
|
||||||
|
|
||||||
is MediaSessionUID.Joined -> {
|
is MediaSessionUID.Joined -> {
|
||||||
getChildMediaItems(mediaSessionUID.childUid)
|
getChildMediaItems(mediaSessionUID.childUid)
|
||||||
}
|
}
|
||||||
|
|
||||||
null -> {
|
null -> {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
@ -225,24 +215,20 @@ constructor(
|
||||||
val songs = listSettings.albumSongSort.songs(item.songs)
|
val songs = listSettings.albumSongSort.songs(item.songs)
|
||||||
songs.map { it.toMediaItem(context, item) }
|
songs.map { it.toMediaItem(context, item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is Artist -> {
|
is Artist -> {
|
||||||
val albums = ARTIST_ALBUMS_SORT.albums(item.explicitAlbums + item.implicitAlbums)
|
val albums = ARTIST_ALBUMS_SORT.albums(item.explicitAlbums + item.implicitAlbums)
|
||||||
val songs = listSettings.artistSongSort.songs(item.songs)
|
val songs = listSettings.artistSongSort.songs(item.songs)
|
||||||
albums.map { it.toMediaItem(context) } + songs.map { it.toMediaItem(context, item) }
|
albums.map { it.toMediaItem(context) } + songs.map { it.toMediaItem(context, item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is Genre -> {
|
is Genre -> {
|
||||||
val artists = GENRE_ARTISTS_SORT.artists(item.artists)
|
val artists = GENRE_ARTISTS_SORT.artists(item.artists)
|
||||||
val songs = listSettings.genreSongSort.songs(item.songs)
|
val songs = listSettings.genreSongSort.songs(item.songs)
|
||||||
artists.map { it.toMediaItem(context) } +
|
artists.map { it.toMediaItem(context) } +
|
||||||
songs.map { it.toMediaItem(context, null) }
|
songs.map { it.toMediaItem(context, null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is Playlist -> {
|
is Playlist -> {
|
||||||
item.songs.map { it.toMediaItem(context, item) }
|
item.songs.map { it.toMediaItem(context, item) }
|
||||||
}
|
}
|
||||||
|
|
||||||
is Song,
|
is Song,
|
||||||
null -> return null
|
null -> return null
|
||||||
}
|
}
|
||||||
|
@ -339,8 +325,7 @@ constructor(
|
||||||
deviceLibrary.albums,
|
deviceLibrary.albums,
|
||||||
deviceLibrary.artists,
|
deviceLibrary.artists,
|
||||||
deviceLibrary.genres,
|
deviceLibrary.genres,
|
||||||
userLibrary.playlists
|
userLibrary.playlists)
|
||||||
)
|
|
||||||
val results = searchEngine.search(items, query)
|
val results = searchEngine.search(items, query)
|
||||||
for (entry in searchSubscribers.entries) {
|
for (entry in searchSubscribers.entries) {
|
||||||
if (entry.value == query) {
|
if (entry.value == query) {
|
||||||
|
|
|
@ -106,7 +106,8 @@ class ExoPlaybackStateHolder(
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val mediaSessionPlayer: Player
|
val mediaSessionPlayer: Player
|
||||||
get() = MediaSessionPlayer(player, playbackManager, commandFactory, musicRepository)
|
get() =
|
||||||
|
MediaSessionPlayer(context, player, playbackManager, commandFactory, musicRepository)
|
||||||
|
|
||||||
override val progression: Progression
|
override val progression: Progression
|
||||||
get() {
|
get() {
|
||||||
|
|
|
@ -18,6 +18,8 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.playback.service
|
package org.oxycblt.auxio.playback.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Bundle
|
||||||
import android.view.Surface
|
import android.view.Surface
|
||||||
import android.view.SurfaceHolder
|
import android.view.SurfaceHolder
|
||||||
import android.view.SurfaceView
|
import android.view.SurfaceView
|
||||||
|
@ -31,6 +33,7 @@ import androidx.media3.common.PlaybackParameters
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.common.TrackSelectionParameters
|
import androidx.media3.common.TrackSelectionParameters
|
||||||
import java.lang.Exception
|
import java.lang.Exception
|
||||||
|
import org.oxycblt.auxio.R
|
||||||
import org.oxycblt.auxio.music.Album
|
import org.oxycblt.auxio.music.Album
|
||||||
import org.oxycblt.auxio.music.Artist
|
import org.oxycblt.auxio.music.Artist
|
||||||
import org.oxycblt.auxio.music.Genre
|
import org.oxycblt.auxio.music.Genre
|
||||||
|
@ -60,6 +63,7 @@ import org.oxycblt.auxio.util.logE
|
||||||
* @author Alexander Capehart
|
* @author Alexander Capehart
|
||||||
*/
|
*/
|
||||||
class MediaSessionPlayer(
|
class MediaSessionPlayer(
|
||||||
|
private val context: Context,
|
||||||
player: Player,
|
player: Player,
|
||||||
private val playbackManager: PlaybackStateManager,
|
private val playbackManager: PlaybackStateManager,
|
||||||
private val commandFactory: PlaybackCommand.Factory,
|
private val commandFactory: PlaybackCommand.Factory,
|
||||||
|
@ -86,6 +90,20 @@ class MediaSessionPlayer(
|
||||||
setMediaItems(mediaItems, C.INDEX_UNSET, C.TIME_UNSET)
|
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(
|
override fun setMediaItems(
|
||||||
mediaItems: MutableList<MediaItem>,
|
mediaItems: MutableList<MediaItem>,
|
||||||
startIndex: Int,
|
startIndex: Int,
|
||||||
|
|
|
@ -255,7 +255,7 @@ constructor(
|
||||||
mediaSession.setCustomLayout(layout)
|
mediaSession.setCustomLayout(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun invalidate(ids: Map<String, Int>){
|
override fun invalidate(ids: Map<String, Int>) {
|
||||||
for (id in ids) {
|
for (id in ids) {
|
||||||
mediaSession.notifyChildrenChanged(id.key, id.value, null)
|
mediaSession.notifyChildrenChanged(id.key, id.value, null)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 Auxio Project
|
* 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
|
* 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
|
* 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.media.AudioManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.media3.common.Player
|
||||||
import androidx.media3.session.CommandButton
|
import androidx.media3.session.CommandButton
|
||||||
|
import androidx.media3.session.DefaultMediaNotificationProvider
|
||||||
import androidx.media3.session.SessionCommand
|
import androidx.media3.session.SessionCommand
|
||||||
import androidx.media3.session.SessionCommands
|
import androidx.media3.session.SessionCommands
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
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.ActionMode
|
||||||
import org.oxycblt.auxio.playback.PlaybackSettings
|
import org.oxycblt.auxio.playback.PlaybackSettings
|
||||||
import org.oxycblt.auxio.playback.state.PlaybackStateManager
|
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.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.widgets.WidgetComponent
|
import org.oxycblt.auxio.widgets.WidgetComponent
|
||||||
|
@ -102,6 +105,13 @@ constructor(
|
||||||
.setDisplayName(context.getString(R.string.desc_change_repeat))
|
.setDisplayName(context.getString(R.string.desc_change_repeat))
|
||||||
.setSessionCommand(
|
.setSessionCommand(
|
||||||
SessionCommand(PlaybackActions.ACTION_INC_REPEAT_MODE, Bundle()))
|
SessionCommand(PlaybackActions.ACTION_INC_REPEAT_MODE, Bundle()))
|
||||||
|
.setEnabled(true)
|
||||||
|
.setExtras(
|
||||||
|
Bundle().apply {
|
||||||
|
putInt(
|
||||||
|
DefaultMediaNotificationProvider.COMMAND_KEY_COMPACT_VIEW_INDEX,
|
||||||
|
0)
|
||||||
|
})
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
ActionMode.SHUFFLE -> {
|
ActionMode.SHUFFLE -> {
|
||||||
|
@ -113,16 +123,56 @@ constructor(
|
||||||
.setDisplayName(context.getString(R.string.lbl_shuffle))
|
.setDisplayName(context.getString(R.string.lbl_shuffle))
|
||||||
.setSessionCommand(
|
.setSessionCommand(
|
||||||
SessionCommand(PlaybackActions.ACTION_INVERT_SHUFFLE, Bundle()))
|
SessionCommand(PlaybackActions.ACTION_INVERT_SHUFFLE, Bundle()))
|
||||||
|
.setEnabled(true)
|
||||||
.build())
|
.build())
|
||||||
}
|
}
|
||||||
else -> {}
|
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(
|
actions.add(
|
||||||
CommandButton.Builder()
|
CommandButton.Builder()
|
||||||
.setIconResId(R.drawable.ic_close_24)
|
.setIconResId(R.drawable.ic_close_24)
|
||||||
.setDisplayName(context.getString(R.string.desc_exit))
|
.setDisplayName(context.getString(R.string.desc_exit))
|
||||||
.setSessionCommand(SessionCommand(PlaybackActions.ACTION_EXIT, Bundle()))
|
.setSessionCommand(SessionCommand(PlaybackActions.ACTION_EXIT, Bundle()))
|
||||||
|
.setEnabled(true)
|
||||||
.build())
|
.build())
|
||||||
|
|
||||||
return actions
|
return actions
|
||||||
|
@ -133,6 +183,11 @@ constructor(
|
||||||
callback?.onCustomLayoutChanged(createCustomLayout())
|
callback?.onCustomLayoutChanged(createCustomLayout())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onProgressionChanged(progression: Progression) {
|
||||||
|
super.onProgressionChanged(progression)
|
||||||
|
callback?.onCustomLayoutChanged(createCustomLayout())
|
||||||
|
}
|
||||||
|
|
||||||
override fun onRepeatModeChanged(repeatMode: RepeatMode) {
|
override fun onRepeatModeChanged(repeatMode: RepeatMode) {
|
||||||
super.onRepeatModeChanged(repeatMode)
|
super.onRepeatModeChanged(repeatMode)
|
||||||
callback?.onCustomLayoutChanged(createCustomLayout())
|
callback?.onCustomLayoutChanged(createCustomLayout())
|
|
@ -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
|
package org.oxycblt.auxio.tasker
|
||||||
|
|
||||||
|
|
2
media
2
media
|
@ -1 +1 @@
|
||||||
Subproject commit bfa4c10f773bb9336d9c7dade490463318b12ab6
|
Subproject commit 6c77cfa13c83bf2ae5188603d2c9a51ec4cb3ac3
|
Loading…
Reference in a new issue