From 2e50634b50de836089aeeeae6bd98cf9d386ec4a Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Wed, 11 Sep 2024 12:39:16 +0200 Subject: [PATCH] add close command --- .../net/newpipe/newplayer/NewPlayerImpl.kt | 8 +- .../NewPlayerNotificationCustomCommands.kt | 56 +++++++++ .../newplayer/service/NewPlayerService.kt | 106 ++++++++++++++++-- .../src/main/res/drawable/close_24px.xml | 10 ++ new-player/src/main/res/values/strings.xml | 1 + 5 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerNotificationCustomCommands.kt create mode 100644 new-player/src/main/res/drawable/close_24px.xml diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt index 072e97a..dbe8f06 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayerImpl.kt @@ -55,6 +55,8 @@ class NewPlayerImpl( private val repository: MediaRepository ) : NewPlayer { + private var playerScope = CoroutineScope(Dispatchers.Main + Job()) + private var uniqueIdToIdLookup = HashMap() // this is used to take care of the NewPlayerService @@ -74,8 +76,6 @@ class NewPlayerImpl( override var fastSeekAmountSec: Int = 10 - private var playerScope = CoroutineScope(Dispatchers.Main + Job()) - override var playBackMode = MutableStateFlow(PlayMode.IDLE) override var shuffle: Boolean @@ -251,7 +251,11 @@ class NewPlayerImpl( } override fun release() { + mediaController?.release() internalPlayer.release() + playBackMode.update { + PlayMode.IDLE + } } private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) { diff --git a/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerNotificationCustomCommands.kt b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerNotificationCustomCommands.kt new file mode 100644 index 0000000..e97c216 --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerNotificationCustomCommands.kt @@ -0,0 +1,56 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + * + */ + +package net.newpipe.newplayer.service + +import android.content.Context +import android.os.Bundle +import androidx.media3.session.CommandButton +import androidx.media3.session.SessionCommand +import net.newpipe.newplayer.R + + +data class CustomCommand( + val action: String, + val commandButton: CommandButton +) { + companion object { + const val NEW_PLAYER_NOTIFICATION_COMMAND_CLOSE_PLAYBACK = "NEW_PLAYER_CLOSE_PLAYBACK" + } +} + +fun buildCustomCommandList(context: Context) = listOf( + CustomCommand( + CustomCommand.NEW_PLAYER_NOTIFICATION_COMMAND_CLOSE_PLAYBACK, + CommandButton.Builder() + .setDisplayName(context.getString(R.string.close)) + .setDisplayName("Close") + .setSessionCommand( + SessionCommand( + CustomCommand.NEW_PLAYER_NOTIFICATION_COMMAND_CLOSE_PLAYBACK, + Bundle() + ) + ) + .setIconResId(R.drawable.close_24px) + .build() + ) +) + diff --git a/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt index 88f8270..5d676bd 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/service/NewPlayerService.kt @@ -21,32 +21,122 @@ package net.newpipe.newplayer.service +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionError +import androidx.media3.session.SessionResult +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch import net.newpipe.newplayer.NewPlayer +import net.newpipe.newplayer.PlayMode import javax.inject.Inject +private const val TAG = "NewPlayerService" + @AndroidEntryPoint class NewPlayerService : MediaSessionService() { - private var mediaSession: MediaSession? = null + private lateinit var mediaSession: MediaSession + private lateinit var customCommands: List @Inject lateinit var newPlayer: NewPlayer + private var serviceScope = CoroutineScope(Dispatchers.Main + Job()) + + @OptIn(UnstableApi::class) + override fun onCreate() { + super.onCreate() + + customCommands = buildCustomCommandList(this) + + mediaSession = MediaSession.Builder(this, newPlayer.internalPlayer) + .setCallback(object : MediaSession.Callback { + override fun onConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult { + val connectionResult = super.onConnect(session, controller) + val availableSessionCommands = + connectionResult.availableSessionCommands.buildUpon() + + customCommands.forEach { command -> + command.commandButton.sessionCommand?.let { + availableSessionCommands.add(it) + } + } + + return MediaSession.ConnectionResult.accept( + availableSessionCommands.build(), + connectionResult.availablePlayerCommands + ) + } + + override fun onPostConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ) { + super.onPostConnect(session, controller) + mediaSession.setCustomLayout(customCommands.map{it.commandButton}) + } + + override fun onCustomCommand( + session: MediaSession, + controller: MediaSession.ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + when(customCommand.customAction) { + CustomCommand.NEW_PLAYER_NOTIFICATION_COMMAND_CLOSE_PLAYBACK -> { + newPlayer.release() + } + else -> { + Log.e(TAG, "Unknown custom command: ${customCommand.customAction}") + return Futures.immediateFuture(SessionResult(SessionError.ERROR_NOT_SUPPORTED)) + } + } + return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } + + }) + .build() + + + serviceScope.launch { + newPlayer.playBackMode.collect { mode -> + if(mode == PlayMode.IDLE) { + stopSelf() + } + } + } + + } + override fun onDestroy() { super.onDestroy() newPlayer.release() - mediaSession?.release() - mediaSession = null + mediaSession.release() } - override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? { - println("gurken get session") - if (mediaSession == null) { - mediaSession = MediaSession.Builder(this, newPlayer.internalPlayer).build() + override fun onTaskRemoved(rootIntent: Intent?) { + // Check if the player is not ready to play or there are no items in the media queue + if (!newPlayer.internalPlayer.playWhenReady || newPlayer.playlist.value.size == 0) { + // Stop the service + stopSelf() } - return mediaSession } + + override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession } \ No newline at end of file diff --git a/new-player/src/main/res/drawable/close_24px.xml b/new-player/src/main/res/drawable/close_24px.xml new file mode 100644 index 0000000..7a0ff35 --- /dev/null +++ b/new-player/src/main/res/drawable/close_24px.xml @@ -0,0 +1,10 @@ + + + diff --git a/new-player/src/main/res/values/strings.xml b/new-player/src/main/res/values/strings.xml index 10eb491..245b6ac 100644 --- a/new-player/src/main/res/values/strings.xml +++ b/new-player/src/main/res/values/strings.xml @@ -52,4 +52,5 @@ Shuffle playlist enabled Shuffle playlist disabled Save current playlist + Close \ No newline at end of file