Refactor image loading, add icon to background playback notification

This commit is contained in:
Maxr1998
2020-09-04 23:51:32 +02:00
parent ad8ec399be
commit 4dad15af41
7 changed files with 72 additions and 36 deletions
@@ -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<PlayerEvent>() }
single(named(WEBAPP_FUNCTION_CHANNEL)) { Channel<String>() }
single {
@@ -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<Application>()
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)
@@ -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)
@@ -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)
}
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="media_notification_height">140dp</dimen>
</resources>
+3 -1
View File
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="media_notification_height">128dp</dimen>
<dimen name="exo_title_text_size">16sp</dimen>
<dimen name="exo_player_controls_display_padding">12dp</dimen>
<dimen name="exo_center_icon_padding">12dp</dimen>
@@ -7,4 +9,4 @@
<dimen name="exo_bottom_controls_size">42dp</dimen>
<dimen name="exo_bottom_controls_padding">8dp</dimen>
<dimen name="exo_bottom_controls_margin">8dp</dimen>
</resources>
</resources>
+1 -1
View File
@@ -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")
}