From b335ddec01c050c76a707480e7d5e748b8047995 Mon Sep 17 00:00:00 2001 From: pca006132 Date: Sun, 2 Nov 2025 17:22:41 +0800 Subject: [PATCH] cache artwork bitmap --- .../tempo/service/MediaService.kt | 30 ++++++++- .../tempo/widget/WidgetUpdateManager.java | 65 +++++++++++-------- .../tempo/service/MediaService.kt | 30 ++++++++- 3 files changed, 97 insertions(+), 28 deletions(-) diff --git a/app/src/degoogled/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/degoogled/java/com/cappielloantonio/tempo/service/MediaService.kt index bb237c62..f1ae6de7 100644 --- a/app/src/degoogled/java/com/cappielloantonio/tempo/service/MediaService.kt +++ b/app/src/degoogled/java/com/cappielloantonio/tempo/service/MediaService.kt @@ -5,6 +5,8 @@ import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.TaskStackBuilder import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities @@ -13,6 +15,7 @@ import android.os.Bundle import android.os.IBinder import android.os.Handler import android.os.Looper +import android.text.TextUtils import android.util.Log import androidx.media3.common.* import androidx.media3.common.util.UnstableApi @@ -21,7 +24,10 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.MediaSource import androidx.media3.session.* import androidx.media3.session.MediaSession.ControllerInfo +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition import com.cappielloantonio.tempo.R +import com.cappielloantonio.tempo.glide.CustomGlideRequest import com.cappielloantonio.tempo.repository.QueueRepository import com.cappielloantonio.tempo.ui.activity.MainActivity import com.cappielloantonio.tempo.util.AssetLinkUtil @@ -35,6 +41,7 @@ import com.cappielloantonio.tempo.widget.WidgetUpdateManager import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture +import java.util.Optional @UnstableApi @@ -61,6 +68,7 @@ class MediaService : MediaLibraryService() { widgetUpdateHandler.postDelayed(this, WIDGET_UPDATE_INTERVAL_MS) } } + @Volatile private var artCache : Optional> = Optional.empty>() inner class LocalBinder : Binder() { fun getEqualizerManager(): EqualizerManager { @@ -368,6 +376,7 @@ class MediaService : MediaLibraryService() { } override fun onIsPlayingChanged(isPlaying: Boolean) { + artCache = Optional.empty() if (!isPlaying) { MediaManager.setPlayingPausedTimestamp( player.currentMediaItem, @@ -494,6 +503,16 @@ class MediaService : MediaLibraryService() { .build() } + private inner class CustomGlideTarget : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + artCache = Optional.of(Optional.of(resource)) + } + + override fun onLoadCleared(placeholder: Drawable?) { + artCache = Optional.of(Optional.empty()) + } + } + private fun updateWidget() { val mi = player.currentMediaItem val title = mi?.mediaMetadata?.title?.toString() @@ -512,12 +531,21 @@ class MediaService : MediaLibraryService() { ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId")) val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L + + if (!TextUtils.isEmpty(coverId) && artCache.isEmpty) { + CustomGlideRequest.loadAlbumArtBitmap( + applicationContext, + coverId, + WidgetUpdateManager.WIDGET_SAFE_ART_SIZE, + CustomGlideTarget()) + } + WidgetUpdateManager.updateFromState( this, title ?: "", artist ?: "", album ?: "", - coverId, + artCache, player.isPlaying, player.shuffleModeEnabled, player.repeatMode, diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java index f159c526..6bf52f95 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java @@ -13,7 +13,9 @@ import com.bumptech.glide.request.transition.Transition; import com.cappielloantonio.tempo.glide.CustomGlideRequest; import com.cappielloantonio.tempo.R; +import androidx.annotation.OptIn; import androidx.media3.common.C; +import androidx.media3.common.util.UnstableApi; import androidx.media3.session.MediaController; import androidx.media3.session.SessionToken; @@ -23,11 +25,12 @@ import com.cappielloantonio.tempo.util.MusicUtil; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import java.util.Optional; import java.util.concurrent.ExecutionException; public final class WidgetUpdateManager { - private static final int WIDGET_SAFE_ART_SIZE = 512; + public static final int WIDGET_SAFE_ART_SIZE = 512; public static void updateFromState(Context ctx, String title, @@ -68,11 +71,12 @@ public final class WidgetUpdateManager { } } + public static void updateFromState(Context ctx, String title, String artist, String album, - String coverArtId, + Optional> coverArt, boolean playing, boolean shuffleEnabled, int repeatMode, @@ -93,6 +97,32 @@ public final class WidgetUpdateManager { final String albumLinkFinal = albumLink; final String artistLinkFinal = artistLink; + AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); + int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); + Bitmap resource = coverArt.filter(Optional::isPresent).map(Optional::get).orElse(null); + + for (int id : ids) { + android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p, + timing.elapsedText, timing.totalText, timing.progress, sh, rep, id); + WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal); + mgr.updateAppWidget(id, rv); + } + } + + public static void updateFromState(Context ctx, + String title, + String artist, + String album, + String coverArtId, + boolean playing, + boolean shuffleEnabled, + int repeatMode, + long positionMs, + long durationMs, + String songLink, + String albumLink, + String artistLink) { + final Context appCtx = ctx.getApplicationContext(); if (!TextUtils.isEmpty(coverArtId)) { CustomGlideRequest.loadAlbumArtBitmap( appCtx, @@ -101,41 +131,24 @@ public final class WidgetUpdateManager { new CustomTarget() { @Override public void onResourceReady(Bitmap resource, Transition transition) { - AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); - int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); - for (int id : ids) { - android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p, - timing.elapsedText, timing.totalText, timing.progress, sh, rep, id); - WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal); - mgr.updateAppWidget(id, rv); - } + updateFromState(ctx, title, artist, album, Optional.of(Optional.of(resource)), + playing, shuffleEnabled, repeatMode, positionMs, durationMs, songLink, albumLink, artistLink); } @Override public void onLoadCleared(Drawable placeholder) { - AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); - int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); - for (int id : ids) { - android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p, - timing.elapsedText, timing.totalText, timing.progress, sh, rep, id); - WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal); - mgr.updateAppWidget(id, rv); - } + updateFromState(ctx, title, artist, album, Optional.of(Optional.empty()), + playing, shuffleEnabled, repeatMode, positionMs, durationMs, songLink, albumLink, artistLink); } } ); } else { - AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); - int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); - for (int id : ids) { - android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p, - timing.elapsedText, timing.totalText, timing.progress, sh, rep, id); - WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal); - mgr.updateAppWidget(id, rv); - } + updateFromState(ctx, title, artist, album, Optional.of(Optional.empty()), + playing, shuffleEnabled, repeatMode, positionMs, durationMs, songLink, albumLink, artistLink); } } + @OptIn(markerClass = UnstableApi.class) public static void refreshFromController(Context ctx) { final Context appCtx = ctx.getApplicationContext(); SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class)); diff --git a/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaService.kt index 36ea5b26..62b61255 100644 --- a/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaService.kt +++ b/app/src/tempus/java/com/cappielloantonio/tempo/service/MediaService.kt @@ -4,6 +4,8 @@ import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.TaskStackBuilder import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities @@ -11,6 +13,7 @@ import android.os.Binder import android.os.IBinder import android.os.Handler import android.os.Looper +import android.text.TextUtils import android.util.Log import androidx.core.content.ContextCompat import androidx.media3.cast.CastPlayer @@ -25,7 +28,10 @@ import androidx.media3.exoplayer.DefaultLoadControl import androidx.media3.exoplayer.ExoPlayer import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaSession.ControllerInfo +import com.bumptech.glide.request.target.CustomTarget +import com.bumptech.glide.request.transition.Transition import com.cappielloantonio.tempo.repository.AutomotiveRepository +import com.cappielloantonio.tempo.glide.CustomGlideRequest import com.cappielloantonio.tempo.repository.QueueRepository import com.cappielloantonio.tempo.ui.activity.MainActivity import com.cappielloantonio.tempo.util.AssetLinkUtil @@ -39,6 +45,7 @@ import com.cappielloantonio.tempo.widget.WidgetUpdateManager import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability +import java.util.Optional @UnstableApi class MediaService : MediaLibraryService(), SessionAvailabilityListener { @@ -49,6 +56,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { private lateinit var librarySessionCallback: MediaLibrarySessionCallback private lateinit var networkCallback: CustomNetworkCallback lateinit var equalizerManager: EqualizerManager + @Volatile private var artCache : Optional> = Optional.empty>() inner class LocalBinder : Binder() { fun getEqualizerManager(): EqualizerManager { @@ -278,6 +286,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { } override fun onIsPlayingChanged(isPlaying: Boolean) { + artCache = Optional.empty() if (!isPlaying) { MediaManager.setPlayingPausedTimestamp( player.currentMediaItem, @@ -339,6 +348,16 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { } } + private inner class CustomGlideTarget : CustomTarget() { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + artCache = Optional.of(Optional.of(resource)) + } + + override fun onLoadCleared(placeholder: Drawable?) { + artCache = Optional.of(Optional.empty()) + } + } + private fun updateWidget() { val mi = player.currentMediaItem val title = mi?.mediaMetadata?.title?.toString() @@ -357,12 +376,21 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { ?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId")) val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L + + if (!TextUtils.isEmpty(coverId) && artCache.isEmpty) { + CustomGlideRequest.loadAlbumArtBitmap( + applicationContext, + coverId, + WidgetUpdateManager.WIDGET_SAFE_ART_SIZE, + CustomGlideTarget()) + } + WidgetUpdateManager.updateFromState( this, title ?: "", artist ?: "", album ?: "", - coverId, + artCache, player.isPlaying, player.shuffleModeEnabled, player.repeatMode,