add close command

This commit is contained in:
Christian Schabesberger 2024-09-11 12:39:16 +02:00
parent e965528011
commit 2e50634b50
5 changed files with 171 additions and 10 deletions

View File

@ -55,6 +55,8 @@ class NewPlayerImpl(
private val repository: MediaRepository
) : NewPlayer {
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
private var uniqueIdToIdLookup = HashMap<Long, String>()
// 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) {

View File

@ -0,0 +1,56 @@
/* NewPlayer
*
* @author Christian Schabesberger
*
* Copyright (C) NewPipe e.V. 2024 <code(at)newpipe-ev.de>
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
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()
)
)

View File

@ -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<CustomCommand>
@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<SessionResult> {
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()
}
return mediaSession
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()
}
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
</vector>

View File

@ -52,4 +52,5 @@
<string name="shuffle_on">Shuffle playlist enabled</string>
<string name="shuffle_off">Shuffle playlist disabled</string>
<string name="store_playlist">Save current playlist</string>
<string name="close">Close</string>
</resources>