diff --git a/app/src/main/java/com/github/libretube/enums/PlayerEvent.kt b/app/src/main/java/com/github/libretube/enums/PlayerEvent.kt index 9f51efe93..39b171e20 100644 --- a/app/src/main/java/com/github/libretube/enums/PlayerEvent.kt +++ b/app/src/main/java/com/github/libretube/enums/PlayerEvent.kt @@ -6,5 +6,6 @@ enum class PlayerEvent { Rewind, Next, Prev, - Background + Background, + Stop } diff --git a/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt b/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt index 578d18a78..9ad6733c3 100644 --- a/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt +++ b/app/src/main/java/com/github/libretube/helpers/PlayerHelper.kt @@ -1,6 +1,7 @@ package com.github.libretube.helpers import android.app.Activity +import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo @@ -38,6 +39,8 @@ import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.obj.WatchPosition import com.github.libretube.enums.PlayerEvent import com.github.libretube.enums.SbSkipOptions +import com.github.libretube.extensions.seekBy +import com.github.libretube.extensions.togglePlayPauseState import com.github.libretube.extensions.updateParameters import com.github.libretube.obj.VideoStats import com.github.libretube.util.PlayingQueue @@ -800,4 +803,25 @@ object PlayerHelper { DatabaseHolder.Database.watchPositionDao().insert(watchPosition) } } + + fun handlePlayerAction(player: Player, playerEvent: PlayerEvent): Boolean { + return when (playerEvent) { + PlayerEvent.PlayPause -> { + player.togglePlayPauseState() + true + } + + PlayerEvent.Forward -> { + player.seekBy(PlayerHelper.seekIncrement) + true + } + + PlayerEvent.Rewind -> { + player.seekBy(-PlayerHelper.seekIncrement) + true + } + + else -> false + } + } } diff --git a/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt b/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt index 0e6cce976..c96941d41 100644 --- a/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt +++ b/app/src/main/java/com/github/libretube/services/OfflinePlayerService.kt @@ -1,12 +1,16 @@ package com.github.libretube.services +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Handler import android.os.IBinder import android.os.Looper import androidx.annotation.OptIn import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat +import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope import androidx.media3.common.C @@ -22,6 +26,8 @@ import com.github.libretube.db.DatabaseHolder import com.github.libretube.db.obj.DownloadWithItems import com.github.libretube.enums.FileType import com.github.libretube.enums.NotificationId +import com.github.libretube.enums.PlayerEvent +import com.github.libretube.extensions.serializableExtra import com.github.libretube.extensions.toAndroidUri import com.github.libretube.extensions.updateParameters import com.github.libretube.helpers.PlayerHelper @@ -75,6 +81,20 @@ class OfflinePlayerService : LifecycleService() { } } + private val playerActionReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val event = intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return + val player = player ?: return + + if (PlayerHelper.handlePlayerAction(player, event)) return + + when (event) { + PlayerEvent.Stop -> onDestroy() + else -> Unit + } + } + } + override fun onCreate() { super.onCreate() @@ -85,6 +105,13 @@ class OfflinePlayerService : LifecycleService() { .build() startForeground(NotificationId.PLAYER_PLAYBACK.id, notification) + + ContextCompat.registerReceiver( + this, + playerActionReceiver, + IntentFilter(PlayerHelper.getIntentActionName(this)), + ContextCompat.RECEIVER_NOT_EXPORTED + ) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { @@ -189,6 +216,7 @@ class OfflinePlayerService : LifecycleService() { nowPlayingNotification = null watchPositionTimer.destroy() + unregisterReceiver(playerActionReceiver) ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) stopSelf() diff --git a/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt b/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt index d9535665d..f87baf260 100644 --- a/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt +++ b/app/src/main/java/com/github/libretube/services/OnlinePlayerService.kt @@ -1,7 +1,10 @@ package com.github.libretube.services import android.app.Notification +import android.content.BroadcastReceiver +import android.content.Context import android.content.Intent +import android.content.IntentFilter import android.os.Binder import android.os.Handler import android.os.IBinder @@ -9,6 +12,7 @@ import android.os.Looper import android.widget.Toast import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat +import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope @@ -30,7 +34,9 @@ import com.github.libretube.constants.IntentData import com.github.libretube.constants.PreferenceKeys import com.github.libretube.db.DatabaseHelper import com.github.libretube.enums.NotificationId +import com.github.libretube.enums.PlayerEvent import com.github.libretube.extensions.parcelableExtra +import com.github.libretube.extensions.serializableExtra import com.github.libretube.extensions.setMetadata import com.github.libretube.extensions.toID import com.github.libretube.extensions.updateParameters @@ -159,6 +165,28 @@ class OnlinePlayerService : LifecycleService() { } } + private val playerActionReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val event = intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return + val player = player ?: return + + if (PlayerHelper.handlePlayerAction(player, event)) return + + when (event) { + PlayerEvent.Next -> { + PlayingQueue.navigateNext() + } + PlayerEvent.Prev -> { + PlayingQueue.navigatePrev() + } + PlayerEvent.Stop -> { + onDestroy() + } + else -> Unit + } + } + } + /** * Setting the required [Notification] for running as a foreground service */ @@ -172,6 +200,13 @@ class OnlinePlayerService : LifecycleService() { .build() startForeground(NotificationId.PLAYER_PLAYBACK.id, notification) + + ContextCompat.registerReceiver( + this, + playerActionReceiver, + IntentFilter(PlayerHelper.getIntentActionName(this)), + ContextCompat.RECEIVER_NOT_EXPORTED + ) } /** @@ -338,7 +373,8 @@ class OnlinePlayerService : LifecycleService() { this, ) to MimeTypes.APPLICATION_MPD } else { - ProxyHelper.unwrapStreamUrl(streams.hls.orEmpty()).toUri() to MimeTypes.APPLICATION_M3U8 + ProxyHelper.unwrapStreamUrl(streams.hls.orEmpty()) + .toUri() to MimeTypes.APPLICATION_M3U8 } val mediaItem = MediaItem.Builder() @@ -395,6 +431,7 @@ class OnlinePlayerService : LifecycleService() { player?.release() watchPositionTimer.destroy() + unregisterReceiver(playerActionReceiver) // called when the user pressed stop in the notification // stop the service from being in the foreground and remove the notification diff --git a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt index b04a4bd7f..8168bb5ed 100644 --- a/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt +++ b/app/src/main/java/com/github/libretube/ui/activities/OfflinePlayerActivity.kt @@ -36,10 +36,8 @@ import com.github.libretube.db.DatabaseHolder.Database import com.github.libretube.db.obj.DownloadChapter import com.github.libretube.enums.FileType import com.github.libretube.enums.PlayerEvent -import com.github.libretube.extensions.seekBy import com.github.libretube.extensions.serializableExtra import com.github.libretube.extensions.toAndroidUri -import com.github.libretube.extensions.togglePlayPauseState import com.github.libretube.extensions.updateParameters import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.WindowHelper @@ -115,21 +113,8 @@ class OfflinePlayerActivity : BaseActivity() { private val playerActionReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - when (intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return) { - PlayerEvent.PlayPause -> { - player.togglePlayPauseState() - } - - PlayerEvent.Forward -> { - player.seekBy(PlayerHelper.seekIncrement) - } - - PlayerEvent.Rewind -> { - player.seekBy(-PlayerHelper.seekIncrement) - } - - else -> Unit - } + val event = intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return + PlayerHelper.handlePlayerAction(player, event) } } diff --git a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt index d8809365c..a0b52ade8 100644 --- a/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/ui/fragments/PlayerFragment.kt @@ -66,7 +66,6 @@ import com.github.libretube.enums.PlayerEvent import com.github.libretube.enums.ShareObjectType import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.parcelable -import com.github.libretube.extensions.seekBy import com.github.libretube.extensions.serializableExtra import com.github.libretube.extensions.setMetadata import com.github.libretube.extensions.toID @@ -204,23 +203,19 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions { */ private val playerActionReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - when (intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return) { - PlayerEvent.PlayPause -> { - exoPlayer.togglePlayPauseState() - } + val event = intent.serializableExtra(PlayerHelper.CONTROL_TYPE) ?: return - PlayerEvent.Forward -> { - exoPlayer.seekBy(PlayerHelper.seekIncrement) - } - - PlayerEvent.Rewind -> { - exoPlayer.seekBy(-PlayerHelper.seekIncrement) - } + if (PlayerHelper.handlePlayerAction(exoPlayer, event)) return + when (event) { PlayerEvent.Next -> { playNextVideo(PlayingQueue.getNext()) } + PlayerEvent.Prev -> { + playNextVideo(PlayingQueue.getPrev()) + } + PlayerEvent.Background -> { playOnBackground() // wait some time in order for the service to get started properly diff --git a/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt b/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt index 06d90b406..d1da5e13e 100644 --- a/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt +++ b/app/src/main/java/com/github/libretube/util/NowPlayingNotification.kt @@ -2,10 +2,8 @@ package com.github.libretube.util import android.app.NotificationManager import android.app.PendingIntent -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 android.os.Bundle @@ -14,7 +12,6 @@ import android.support.v4.media.session.PlaybackStateCompat import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import androidx.core.app.PendingIntentCompat -import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.core.graphics.drawable.toBitmap import androidx.media.app.NotificationCompat.MediaStyle @@ -26,14 +23,11 @@ import com.github.libretube.LibreTubeApp.Companion.PLAYER_CHANNEL_NAME import com.github.libretube.R import com.github.libretube.constants.IntentData import com.github.libretube.enums.NotificationId -import com.github.libretube.extensions.seekBy +import com.github.libretube.enums.PlayerEvent import com.github.libretube.extensions.toMediaMetadataCompat -import com.github.libretube.extensions.togglePlayPauseState -import com.github.libretube.helpers.BackgroundHelper import com.github.libretube.helpers.ImageHelper import com.github.libretube.helpers.PlayerHelper import com.github.libretube.obj.PlayerNotificationData -import com.github.libretube.services.OfflinePlayerService import com.github.libretube.ui.activities.MainActivity import java.util.UUID @@ -133,14 +127,14 @@ class NowPlayingNotification( private val legacyNotificationButtons get() = listOf( - createNotificationAction(R.drawable.ic_prev_outlined, PREV), + createNotificationAction(R.drawable.ic_prev_outlined, PlayerEvent.Prev.name), createNotificationAction( if (player.isPlaying) R.drawable.ic_pause else R.drawable.ic_play, - PLAY_PAUSE + PlayerEvent.PlayPause.name ), - createNotificationAction(R.drawable.ic_next_outlined, NEXT), - createNotificationAction(R.drawable.ic_rewind_md, REWIND), - createNotificationAction(R.drawable.ic_forward_md, FORWARD) + createNotificationAction(R.drawable.ic_next_outlined, PlayerEvent.Next.name), + createNotificationAction(R.drawable.ic_rewind_md, PlayerEvent.Rewind.name), + createNotificationAction(R.drawable.ic_forward_md, PlayerEvent.Forward.name) ) private fun createNotificationAction( @@ -165,38 +159,38 @@ class NowPlayingNotification( if (this::mediaSession.isInitialized) return val sessionCallback = object : MediaSessionCompat.Callback() { - override fun onSkipToNext() { - handlePlayerAction(NEXT) - super.onSkipToNext() - } - - override fun onSkipToPrevious() { - handlePlayerAction(PREV) - super.onSkipToPrevious() - } - override fun onRewind() { - handlePlayerAction(REWIND) + handlePlayerAction(PlayerEvent.Rewind) super.onRewind() } override fun onFastForward() { - handlePlayerAction(FORWARD) + handlePlayerAction(PlayerEvent.Forward) super.onFastForward() } override fun onPlay() { - handlePlayerAction(PLAY_PAUSE) + handlePlayerAction(PlayerEvent.PlayPause) super.onPlay() } override fun onPause() { - handlePlayerAction(PLAY_PAUSE) + handlePlayerAction(PlayerEvent.PlayPause) super.onPause() } + override fun onSkipToNext() { + handlePlayerAction(PlayerEvent.Next) + super.onSkipToNext() + } + + override fun onSkipToPrevious() { + handlePlayerAction(PlayerEvent.Prev) + super.onSkipToPrevious() + } + override fun onStop() { - handlePlayerAction(STOP) + handlePlayerAction(PlayerEvent.Stop) super.onStop() } @@ -206,7 +200,7 @@ class NowPlayingNotification( } override fun onCustomAction(action: String, extras: Bundle?) { - handlePlayerAction(action) + runCatching { handlePlayerAction(PlayerEvent.valueOf(action)) } super.onCustomAction(action, extras) } } @@ -272,47 +266,20 @@ class NowPlayingNotification( return PlaybackStateCompat.Builder() .setActions(stateActions) - .addCustomAction(createMediaSessionAction(R.drawable.ic_rewind_md, REWIND)) - .addCustomAction(createMediaSessionAction(R.drawable.ic_forward_md, FORWARD)) + .addCustomAction(createMediaSessionAction(R.drawable.ic_rewind_md, PlayerEvent.Rewind.name)) + .addCustomAction(createMediaSessionAction(R.drawable.ic_forward_md, PlayerEvent.Forward.name)) .setState(state, player.currentPosition, player.playbackParameters.speed) .build() } - private fun handlePlayerAction(action: String) { - when (action) { - NEXT -> { - PlayingQueue.navigateNext() - } - - PREV -> { - PlayingQueue.navigatePrev() - } - - REWIND -> { - player.seekBy(-PlayerHelper.seekIncrement) - } - - FORWARD -> { - player.seekBy(PlayerHelper.seekIncrement) - } - - PLAY_PAUSE -> { - player.togglePlayPauseState() - } - - STOP -> { - when (notificationType) { - NowPlayingNotificationType.AUDIO_ONLINE -> BackgroundHelper.stopBackgroundPlay( - context - ) - NowPlayingNotificationType.AUDIO_OFFLINE -> BackgroundHelper.stopBackgroundPlay( - context, - OfflinePlayerService::class.java - ) - else -> Unit - } - } - } + /** + * Forward the action to the responsible notification owner (e.g. PlayerFragment) + */ + private fun handlePlayerAction(action: PlayerEvent) { + val intent = Intent(PlayerHelper.getIntentActionName(context)) + .setPackage(context.packageName) + .putExtra(PlayerHelper.CONTROL_TYPE, action) + context.sendBroadcast(intent) } /** @@ -329,7 +296,6 @@ class NowPlayingNotification( if (notificationBuilder == null) { createMediaSession() createNotificationBuilder() - createActionReceiver() // update the notification each time the player continues playing or pauses player.addListener(object : Player.Listener { override fun onIsPlayingChanged(isPlaying: Boolean) { @@ -349,7 +315,7 @@ class NowPlayingNotification( notificationBuilder = NotificationCompat.Builder(context, PLAYER_CHANNEL_NAME) .setSmallIcon(R.drawable.ic_launcher_lockscreen) .setContentIntent(createCurrentContentIntent()) - .setDeleteIntent(createIntent(STOP)) + .setDeleteIntent(createIntent(PlayerEvent.Stop.name)) .setStyle( MediaStyle() .setMediaSession(mediaSession.sessionToken) @@ -374,36 +340,12 @@ class NowPlayingNotification( nManager.notify(NotificationId.PLAYER_PLAYBACK.id, notification) } - private val notificationActionReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - handlePlayerAction(intent.action ?: return) - } - } - - private fun createActionReceiver() { - val filter = IntentFilter().apply { - listOf(PREV, NEXT, REWIND, FORWARD, PLAY_PAUSE, STOP).forEach { - addAction(it) - } - } - ContextCompat.registerReceiver( - context, - notificationActionReceiver, - filter, - ContextCompat.RECEIVER_NOT_EXPORTED - ) - } - /** * Destroy the [NowPlayingNotification] */ fun destroySelf() { mediaSession.release() - runCatching { - context.unregisterReceiver(notificationActionReceiver) - } - nManager.cancel(NotificationId.PLAYER_PLAYBACK.id) } @@ -416,13 +358,6 @@ class NowPlayingNotification( } companion object { - private const val PREV = "prev" - private const val NEXT = "next" - private const val REWIND = "rewind" - private const val FORWARD = "forward" - private const val PLAY_PAUSE = "play_pause" - private const val STOP = "stop" - enum class NowPlayingNotificationType { VIDEO_ONLINE, VIDEO_OFFLINE,