diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/BitmapProvider.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/BitmapProvider.kt new file mode 100644 index 0000000..27255d6 --- /dev/null +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/BitmapProvider.kt @@ -0,0 +1,71 @@ +package it.vfsfitvnm.vimusic.service + +import android.content.Context +import android.content.res.Configuration +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable +import android.net.Uri +import androidx.core.graphics.applyCanvas +import coil.Coil +import coil.request.Disposable +import coil.request.ImageRequest +import it.vfsfitvnm.vimusic.utils.thumbnail + +context(Context) +class BitmapProvider( + private val bitmapSize: Int, + private val colorProvider: (isSystemInDarkMode: Boolean) -> Int +) { + private var lastUri: Uri? = null + private var lastBitmap: Bitmap? = null + private var lastIsSystemInDarkMode = false + + private var lastEnqueued: Disposable? = null + + private lateinit var defaultBitmap: Bitmap + + val bitmap: Bitmap + get() = lastBitmap ?: defaultBitmap + + init { + setDefaultBitmap() + } + + fun setDefaultBitmap(): Boolean { + val isSystemInDarkMode = resources.configuration.uiMode and + Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES + + if (::defaultBitmap.isInitialized && isSystemInDarkMode == lastIsSystemInDarkMode) return false + + lastIsSystemInDarkMode = isSystemInDarkMode + + defaultBitmap = Bitmap.createBitmap(bitmapSize, bitmapSize, Bitmap.Config.ARGB_8888).applyCanvas { + drawColor(colorProvider(isSystemInDarkMode)) + } + + return lastBitmap == null + } + + fun load(uri: Uri?, onDone: (Bitmap) -> Unit) { + if (lastUri == uri) return + + lastEnqueued?.dispose() + lastUri = uri + + lastEnqueued = Coil.imageLoader(applicationContext).enqueue( + ImageRequest.Builder(applicationContext) + .data(uri.thumbnail(bitmapSize)) + .listener( + onError = { _, _ -> + lastBitmap = null + onDone(bitmap) + }, + onSuccess = { _, result -> + lastBitmap = (result.drawable as BitmapDrawable).bitmap + onDone(bitmap) + } + ) + .build() + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt similarity index 92% rename from app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt rename to app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt index e127cdf..83fbca9 100644 --- a/app/src/main/kotlin/it/vfsfitvnm/vimusic/services/PlayerService.kt +++ b/app/src/main/kotlin/it/vfsfitvnm/vimusic/service/PlayerService.kt @@ -1,4 +1,4 @@ -package it.vfsfitvnm.vimusic.services +package it.vfsfitvnm.vimusic.service import android.app.Notification import android.app.NotificationChannel @@ -7,8 +7,8 @@ import android.app.Service import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.graphics.Bitmap -import android.graphics.drawable.BitmapDrawable +import android.content.res.Configuration +import android.graphics.Color import android.net.Uri import android.os.Build import android.support.v4.media.MediaMetadataCompat @@ -19,7 +19,6 @@ import android.support.v4.media.session.PlaybackStateCompat.* import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat.startForegroundService -import androidx.core.graphics.drawable.toBitmap import androidx.core.net.toUri import androidx.media.session.MediaButtonReceiver import androidx.media3.common.* @@ -36,8 +35,6 @@ import androidx.media3.exoplayer.analytics.AnalyticsListener import androidx.media3.exoplayer.analytics.PlaybackStats import androidx.media3.exoplayer.analytics.PlaybackStatsListener import androidx.media3.exoplayer.source.DefaultMediaSourceFactory -import coil.Coil -import coil.request.ImageRequest import it.vfsfitvnm.vimusic.Database import it.vfsfitvnm.vimusic.MainActivity import it.vfsfitvnm.vimusic.R @@ -73,12 +70,10 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback private var timerJob: TimerJob? = null - private var notificationThumbnailSize: Int = 0 - private var lastArtworkUri: Uri? = null - private var lastBitmap: Bitmap? = null - private var radio: YouTubeRadio? = null + private lateinit var bitmapProvider: BitmapProvider + private val coroutineScope = CoroutineScope(Dispatchers.IO) + Job() private val songPendingLoudnessDb = mutableMapOf() @@ -113,10 +108,12 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback override fun onCreate() { super.onCreate() - notificationThumbnailSize = (256 * resources.displayMetrics.density).roundToInt() - - lastBitmap = resources.getDrawable(R.drawable.disc_placeholder, null) - ?.toBitmap(notificationThumbnailSize, notificationThumbnailSize) + bitmapProvider = BitmapProvider( + bitmapSize = (256 * resources.displayMetrics.density).roundToInt(), + colorProvider = { isSystemInDarkMode -> + if (isSystemInDarkMode) Color.BLACK else Color.WHITE + } + ) createNotificationChannel() @@ -222,6 +219,13 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback super.onDestroy() } + override fun onConfigurationChanged(newConfig: Configuration) { + if (bitmapProvider.setDefaultBitmap()) { + notificationManager.notify(NotificationId, notification()) + } + super.onConfigurationChanged(newConfig) + } + override fun onPlaybackStatsReady( eventTime: AnalyticsListener.EventTime, playbackStats: PlaybackStats @@ -320,7 +324,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback .setContentTitle(mediaMetadata.title) .setContentText(mediaMetadata.artist) .setSubText(player.playerError?.message) - .setLargeIcon(lastBitmap) + .setLargeIcon(bitmapProvider.bitmap) .setAutoCancel(true) .setOnlyAlertOnce(true) .setShowWhen(false) @@ -343,31 +347,8 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback ) .addMediaAction(R.drawable.play_skip_forward, "Skip forward", ACTION_SKIP_TO_NEXT) - if (lastArtworkUri != mediaMetadata.artworkUri) { - lastArtworkUri = mediaMetadata.artworkUri - - Coil.imageLoader(applicationContext).enqueue( - ImageRequest.Builder(applicationContext) - .data(mediaMetadata.artworkUri.thumbnail(notificationThumbnailSize)) - .listener( - onError = { _, _ -> - lastBitmap = resources.getDrawable(R.drawable.disc_placeholder, null) - ?.toBitmap(notificationThumbnailSize, notificationThumbnailSize) - notificationManager.notify( - NotificationId, - builder.setLargeIcon(lastBitmap).build() - ) - }, - onSuccess = { _, result -> - lastBitmap = (result.drawable as BitmapDrawable).bitmap - notificationManager.notify( - NotificationId, - builder.setLargeIcon(lastBitmap).build() - ) - } - ) - .build() - ) + bitmapProvider.load(mediaMetadata.artworkUri) { bitmap -> + notificationManager.notify(NotificationId, builder.setLargeIcon(bitmap).build()) } return builder.build() @@ -401,6 +382,7 @@ class PlayerService : Service(), Player.Listener, PlaybackStatsListener.Callback } } + private fun createCacheDataSource(): DataSource.Factory { return CacheDataSource.Factory().setCache(cache).apply { setUpstreamDataSourceFactory(