From 4dad15af412af94063b7f0763c49d334debe31ce Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Fri, 4 Sep 2020 23:19:40 +0200 Subject: [PATCH] Refactor image loading, add icon to background playback notification --- .../org/jellyfin/mobile/ApplicationModule.kt | 2 + .../mobile/player/PlayerNotificationHelper.kt | 86 ++++++++++++------- .../jellyfin/mobile/player/PlayerViewModel.kt | 2 + .../mobile/webapp/RemotePlayerService.kt | 8 +- app/src/main/res/values-v29/dimens.xml | 4 + app/src/main/res/values/dimens.xml | 4 +- buildSrc/src/main/kotlin/Dependencies.kt | 2 +- 7 files changed, 72 insertions(+), 36 deletions(-) create mode 100644 app/src/main/res/values-v29/dimens.xml diff --git a/app/src/main/java/org/jellyfin/mobile/ApplicationModule.kt b/app/src/main/java/org/jellyfin/mobile/ApplicationModule.kt index 646f296f..34bac8d7 100644 --- a/app/src/main/java/org/jellyfin/mobile/ApplicationModule.kt +++ b/app/src/main/java/org/jellyfin/mobile/ApplicationModule.kt @@ -1,5 +1,6 @@ package org.jellyfin.mobile +import coil.ImageLoader import kotlinx.coroutines.channels.Channel import okhttp3.OkHttpClient import org.jellyfin.apiclient.AppInfo @@ -21,6 +22,7 @@ const val WEBAPP_FUNCTION_CHANNEL = "WebAppFunctionChannel" val applicationModule: Module = module { single { AppPreferences(androidApplication()) } single { OkHttpClient() } + single { ImageLoader(androidApplication()) } single(named(PLAYER_EVENT_CHANNEL)) { Channel() } single(named(WEBAPP_FUNCTION_CHANNEL)) { Channel() } single { diff --git a/app/src/main/java/org/jellyfin/mobile/player/PlayerNotificationHelper.kt b/app/src/main/java/org/jellyfin/mobile/player/PlayerNotificationHelper.kt index 188aa74b..55f82592 100644 --- a/app/src/main/java/org/jellyfin/mobile/player/PlayerNotificationHelper.kt +++ b/app/src/main/java/org/jellyfin/mobile/player/PlayerNotificationHelper.kt @@ -8,9 +8,20 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.graphics.Bitmap import android.os.Build import androidx.annotation.StringRes import androidx.core.content.getSystemService +import androidx.core.graphics.drawable.toBitmap +import androidx.lifecycle.viewModelScope +import coil.ImageLoader +import coil.request.GetRequestBuilder +import com.google.android.exoplayer2.Player +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.jellyfin.apiclient.model.dto.ImageOptions +import org.jellyfin.apiclient.model.entities.ImageType.Primary import org.jellyfin.mobile.AppPreferences import org.jellyfin.mobile.BuildConfig import org.jellyfin.mobile.R @@ -22,9 +33,10 @@ import org.koin.core.inject import java.util.concurrent.atomic.AtomicBoolean class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinComponent { - private val appPreferences: AppPreferences by inject() private val context: Context = viewModel.getApplication() + private val appPreferences: AppPreferences by inject() private val notificationManager: NotificationManager? by lazy { context.getSystemService() } + private val imageLoader: ImageLoader by inject() private val receiverRegistered = AtomicBoolean(false) val shouldShowNotification: Boolean @@ -36,41 +48,55 @@ class PlayerNotificationHelper(private val viewModel: PlayerViewModel) : KoinCom val nm = notificationManager ?: return val mediaSource = viewModel.mediaSourceManager.jellyfinMediaSource.value ?: return val player = viewModel.playerOrNull ?: return + val playbackState = player.playbackState + if (playbackState != Player.STATE_READY && playbackState != Player.STATE_BUFFERING) return // Create notification channel context.createMediaNotificationChannel(nm) - val style = Notification.MediaStyle().apply { - setMediaSession(viewModel.mediaSession.sessionToken) - // FIXME Doesn't work properly right now, requires large icon first (primary image via apiclient) - //setShowActionsInCompactView(0, 1, 2) + viewModel.viewModelScope.launch { + val mediaIcon: Bitmap? = withContext(Dispatchers.IO) { + val imageUrl = viewModel.apiClient.GetImageUrl(mediaSource.id, ImageOptions().apply { + imageType = Primary + val size = context.resources.getDimensionPixelSize(R.dimen.media_notification_height) + maxWidth = size + maxHeight = size + }) + imageLoader.execute(GetRequestBuilder(context).data(imageUrl).build()).drawable?.toBitmap() + } + + val style = Notification.MediaStyle().apply { + setMediaSession(viewModel.mediaSession.sessionToken) + setShowActionsInCompactView(0, 1, 2) + } + + val notification = Notification.Builder(context).apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + setChannelId(Constants.MEDIA_NOTIFICATION_CHANNEL_ID) // Set Notification Channel on Android O and above + setColorized(true) + } else { + setPriority(Notification.PRIORITY_LOW) + } + setSmallIcon(R.drawable.ic_notification) + mediaIcon?.let(::setLargeIcon) + setContentTitle(mediaSource.title) + mediaSource.artists?.let(::setContentText) + setStyle(style) + setVisibility(Notification.VISIBILITY_PUBLIC) + addAction(generateAction(R.drawable.ic_rewind_black_32dp, R.string.notification_action_rewind, Constants.ACTION_REWIND)) + val playbackAction = when { + !player.playWhenReady -> generateAction(R.drawable.ic_play_black_42dp, R.string.notification_action_play, Constants.ACTION_PLAY) + else -> generateAction(R.drawable.ic_pause_black_42dp, R.string.notification_action_pause, Constants.ACTION_PAUSE) + } + addAction(playbackAction) + addAction(generateAction(R.drawable.ic_fast_forward_black_32dp, R.string.notification_action_fast_forward, Constants.ACTION_FAST_FORWARD)) + setContentIntent(buildContentIntent()) + setDeleteIntent(buildDeleteIntent()) + }.build() + + nm.notify(VIDEO_PLAYER_NOTIFICATION_ID, notification) } - val notification = Notification.Builder(context).apply { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - setChannelId(Constants.MEDIA_NOTIFICATION_CHANNEL_ID) // Set Notification Channel on Android O and above - setColorized(true) - } else { - setPriority(Notification.PRIORITY_LOW) - } - setSmallIcon(R.drawable.ic_notification) - setContentTitle(mediaSource.title) - mediaSource.artists?.let(::setContentText) - setStyle(style) - setVisibility(Notification.VISIBILITY_PUBLIC) - addAction(generateAction(R.drawable.ic_rewind_black_32dp, R.string.notification_action_rewind, Constants.ACTION_REWIND)) - val playbackAction = when { - !player.playWhenReady -> generateAction(R.drawable.ic_play_black_42dp, R.string.notification_action_play, Constants.ACTION_PLAY) - else -> generateAction(R.drawable.ic_pause_black_42dp, R.string.notification_action_pause, Constants.ACTION_PAUSE) - } - addAction(playbackAction) - addAction(generateAction(R.drawable.ic_fast_forward_black_32dp, R.string.notification_action_fast_forward, Constants.ACTION_FAST_FORWARD)) - setContentIntent(buildContentIntent()) - setDeleteIntent(buildDeleteIntent()) - }.build() - - nm.notify(VIDEO_PLAYER_NOTIFICATION_ID, notification) - if (receiverRegistered.compareAndSet(false, true)) { context.registerReceiver(notificationActionReceiver, IntentFilter().apply { addAction(Constants.ACTION_PLAY) diff --git a/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt b/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt index 893ece72..d4ff06c4 100644 --- a/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt +++ b/app/src/main/java/org/jellyfin/mobile/player/PlayerViewModel.kt @@ -15,6 +15,7 @@ import com.google.android.exoplayer2.util.Clock import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import org.jellyfin.apiclient.interaction.ApiClient import org.jellyfin.mobile.BuildConfig import org.jellyfin.mobile.PLAYER_EVENT_CHANNEL import org.jellyfin.mobile.WEBAPP_FUNCTION_CHANNEL @@ -27,6 +28,7 @@ import org.koin.core.inject import org.koin.core.qualifier.named class PlayerViewModel(application: Application) : AndroidViewModel(application), KoinComponent, Player.EventListener { + val apiClient: ApiClient by inject() val mediaSourceManager = MediaSourceManager(this) val notificationHelper: PlayerNotificationHelper by lazy { PlayerNotificationHelper(this) } private val lifecycleObserver = PlayerLifecycleObserver(this) diff --git a/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt b/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt index 227f8a20..84ac6d91 100644 --- a/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt +++ b/app/src/main/java/org/jellyfin/mobile/webapp/RemotePlayerService.kt @@ -23,7 +23,7 @@ import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.core.content.getSystemService import androidx.core.graphics.drawable.toBitmap -import coil.Coil +import coil.ImageLoader import coil.request.GetRequest import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -71,12 +71,12 @@ class RemotePlayerService : Service(), CoroutineScope { get() = job + Dispatchers.Main private val appPreferences: AppPreferences by inject() + private val notificationManager: NotificationManager by lazy { getSystemService()!! } + private val imageLoader: ImageLoader by inject() private val binder = ServiceBinder(this) - private lateinit var wakeLock: PowerManager.WakeLock - private val notificationManager: NotificationManager by lazy { getSystemService()!! } private var mediaSession: MediaSession? = null private var mediaController: MediaController? = null private var largeItemIcon: Bitmap? = null @@ -208,7 +208,7 @@ class RemotePlayerService : Service(), CoroutineScope { if (imageUrl != null && imageUrl.isNotEmpty()) { launch { val request = GetRequest.Builder(this@RemotePlayerService).data(imageUrl).build() - val bitmap = Coil.imageLoader(this@RemotePlayerService).execute(request).drawable?.toBitmap() + val bitmap = imageLoader.execute(request).drawable?.toBitmap() largeItemIcon = bitmap notifyWithBitmap(handledIntent, bitmap) } diff --git a/app/src/main/res/values-v29/dimens.xml b/app/src/main/res/values-v29/dimens.xml new file mode 100644 index 00000000..3cd9aedb --- /dev/null +++ b/app/src/main/res/values-v29/dimens.xml @@ -0,0 +1,4 @@ + + + 140dp + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 14488935..48e33cb2 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,7 @@ + 128dp + 16sp 12dp 12dp @@ -7,4 +9,4 @@ 42dp 8dp 8dp - \ No newline at end of file + diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 6ff20d31..60544398 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -83,7 +83,7 @@ object Dependencies { object Network { const val okHttp = "com.squareup.okhttp3:okhttp:${Versions.okHttp}" - const val coil = "io.coil-kt:coil:${Versions.coil}" + const val coil = "io.coil-kt:coil-base:${Versions.coil}" val exoPlayerHLS = exoPlayer("hls") }