playback: remove custom bitmap loading

Media3 simply will not tolerate me doing this. I am basically stuck
at the mercy of the Android OS now, until I can have my own unified
source of truth with cover loading.
This commit is contained in:
Alexander Capehart 2024-05-17 13:38:12 -06:00
parent f23d1a8eaf
commit 66db61899c
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 16 additions and 102 deletions

View file

@ -1,90 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* MediaSessionBitmapLoader.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.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.CoverKeyer
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 keyer: CoverKeyer,
private val imageLoader: ImageLoader,
) : BitmapLoader {
override fun decodeBitmap(data: ByteArray): ListenableFuture<Bitmap> {
throw NotImplementedError()
}
override fun loadBitmap(uri: Uri): ListenableFuture<Bitmap> {
throw NotImplementedError()
}
override fun supportsMimeType(mimeType: String): Boolean {
return true
}
override fun loadBitmapFromMetadata(metadata: MediaMetadata): ListenableFuture<Bitmap>? {
val deviceLibrary = musicRepository.deviceLibrary ?: return null
val future = SettableFuture.create<Bitmap>()
val song =
when (val uid =
metadata.extras?.getString("uid")?.let { MediaSessionUID.fromString(it) }) {
is MediaSessionUID.Single -> deviceLibrary.findSong(uid.uid)
is MediaSessionUID.Joined -> deviceLibrary.findSong(uid.childUid)
else -> return null
}
?: return null
// Even launching a coroutine to obtained cached covers is enough to make the notification
// go without covers.
val key = keyer.key(listOf(song.cover), 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 {
override fun onCompleted(bitmap: Bitmap?) {
if (bitmap == null) {
future.setException(IllegalStateException("Bitmap is null"))
} else {
future.set(bitmap)
}
}
})
return future
}
}

View file

@ -74,7 +74,7 @@ fun Song.toMediaItem(context: Context, parent: MusicParent?): MediaItem {
.setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC) .setMediaType(MediaMetadata.MEDIA_TYPE_MUSIC)
.setIsPlayable(true) .setIsPlayable(true)
.setIsBrowsable(false) .setIsBrowsable(false)
.setArtworkUri(album.cover.single.mediaStoreCoverUri) .setArtworkUri(cover.mediaStoreCoverUri)
.setExtras( .setExtras(
Bundle().apply { Bundle().apply {
putString("uid", mediaSessionUID.toString()) putString("uid", mediaSessionUID.toString())

View file

@ -21,6 +21,7 @@ package org.oxycblt.auxio.playback.service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.media.audiofx.AudioEffect import android.media.audiofx.AudioEffect
import android.os.Bundle
import androidx.media3.common.AudioAttributes import androidx.media3.common.AudioAttributes
import androidx.media3.common.C import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
@ -42,6 +43,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield import kotlinx.coroutines.yield
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.auxio.music.MusicParent import org.oxycblt.auxio.music.MusicParent
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.Song
@ -69,13 +71,15 @@ class ExoPlaybackStateHolder(
private val persistenceRepository: PersistenceRepository, private val persistenceRepository: PersistenceRepository,
private val playbackSettings: PlaybackSettings, private val playbackSettings: PlaybackSettings,
private val commandFactory: PlaybackCommand.Factory, private val commandFactory: PlaybackCommand.Factory,
private val replayGainProcessor: ReplayGainAudioProcessor,
private val musicRepository: MusicRepository, private val musicRepository: MusicRepository,
private val replayGainProcessor: ReplayGainAudioProcessor private val imageSettings: ImageSettings
) : ) :
PlaybackStateHolder, PlaybackStateHolder,
Player.Listener, Player.Listener,
MusicRepository.UpdateListener, MusicRepository.UpdateListener,
PlaybackSettings.Listener { PlaybackSettings.Listener,
ImageSettings.Listener {
private val saveJob = Job() private val saveJob = Job()
private val saveScope = CoroutineScope(Dispatchers.IO + saveJob) private val saveScope = CoroutineScope(Dispatchers.IO + saveJob)
private val restoreScope = CoroutineScope(Dispatchers.IO + saveJob) private val restoreScope = CoroutineScope(Dispatchers.IO + saveJob)
@ -86,6 +90,7 @@ class ExoPlaybackStateHolder(
private set private set
fun attach() { fun attach() {
imageSettings.registerListener(this)
player.addListener(this) player.addListener(this)
replayGainProcessor.attach() replayGainProcessor.attach()
playbackManager.registerStateHolder(this) playbackManager.registerStateHolder(this)
@ -99,6 +104,7 @@ class ExoPlaybackStateHolder(
playbackManager.unregisterStateHolder(this) playbackManager.unregisterStateHolder(this)
musicRepository.removeUpdateListener(this) musicRepository.removeUpdateListener(this)
replayGainProcessor.release() replayGainProcessor.release()
imageSettings.unregisterListener(this)
player.release() player.release()
} }
@ -516,9 +522,10 @@ class ExoPlaybackStateHolder(
private val persistenceRepository: PersistenceRepository, private val persistenceRepository: PersistenceRepository,
private val playbackSettings: PlaybackSettings, private val playbackSettings: PlaybackSettings,
private val commandFactory: PlaybackCommand.Factory, private val commandFactory: PlaybackCommand.Factory,
private val musicRepository: MusicRepository,
private val mediaSourceFactory: MediaSource.Factory, private val mediaSourceFactory: MediaSource.Factory,
private val replayGainProcessor: ReplayGainAudioProcessor private val replayGainProcessor: ReplayGainAudioProcessor,
private val musicRepository: MusicRepository,
private val imageSettings: ImageSettings,
) { ) {
fun create(): ExoPlaybackStateHolder { fun create(): ExoPlaybackStateHolder {
// Since Auxio is a music player, only specify an audio renderer to save // Since Auxio is a music player, only specify an audio renderer to save
@ -556,8 +563,9 @@ class ExoPlaybackStateHolder(
persistenceRepository, persistenceRepository,
playbackSettings, playbackSettings,
commandFactory, commandFactory,
replayGainProcessor,
musicRepository, musicRepository,
replayGainProcessor) imageSettings)
} }
} }

View file

@ -48,7 +48,6 @@ import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.ForegroundListener import org.oxycblt.auxio.ForegroundListener
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.image.service.MediaSessionBitmapLoader
import org.oxycblt.auxio.music.service.MediaItemBrowser import org.oxycblt.auxio.music.service.MediaItemBrowser
import org.oxycblt.auxio.playback.state.DeferredPlayback import org.oxycblt.auxio.playback.state.DeferredPlayback
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
@ -61,7 +60,6 @@ constructor(
private val playbackManager: PlaybackStateManager, private val playbackManager: PlaybackStateManager,
private val actionHandler: PlaybackActionHandler, private val actionHandler: PlaybackActionHandler,
private val mediaItemBrowser: MediaItemBrowser, private val mediaItemBrowser: MediaItemBrowser,
private val bitmapLoader: MediaSessionBitmapLoader,
exoHolderFactory: ExoPlaybackStateHolder.Factory exoHolderFactory: ExoPlaybackStateHolder.Factory
) : ) :
MediaLibrarySession.Callback, MediaLibrarySession.Callback,
@ -143,9 +141,7 @@ constructor(
} }
private fun createSession(service: MediaLibraryService) = private fun createSession(service: MediaLibraryService) =
MediaLibrarySession.Builder(service, exoHolder.mediaSessionPlayer, this) MediaLibrarySession.Builder(service, exoHolder.mediaSessionPlayer, this).build()
.setBitmapLoader(bitmapLoader)
.build()
override fun onConnect( override fun onConnect(
session: MediaSession, session: MediaSession,

View file

@ -12,7 +12,7 @@ buildscript {
} }
plugins { plugins {
id "com.android.application" version '8.3.2' apply false id "com.android.application" version '8.4.0' apply false
id "androidx.navigation.safeargs.kotlin" version "$navigation_version" apply false id "androidx.navigation.safeargs.kotlin" version "$navigation_version" apply false
id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false id "org.jetbrains.kotlin.android" version "$kotlin_version" apply false
id "com.google.devtools.ksp" version '1.9.23-1.0.20' apply false id "com.google.devtools.ksp" version '1.9.23-1.0.20' apply false