add close command
This commit is contained in:
parent
e965528011
commit
2e50634b50
|
@ -55,6 +55,8 @@ class NewPlayerImpl(
|
||||||
private val repository: MediaRepository
|
private val repository: MediaRepository
|
||||||
) : NewPlayer {
|
) : NewPlayer {
|
||||||
|
|
||||||
|
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
|
||||||
|
|
||||||
private var uniqueIdToIdLookup = HashMap<Long, String>()
|
private var uniqueIdToIdLookup = HashMap<Long, String>()
|
||||||
|
|
||||||
// this is used to take care of the NewPlayerService
|
// this is used to take care of the NewPlayerService
|
||||||
|
@ -74,8 +76,6 @@ class NewPlayerImpl(
|
||||||
|
|
||||||
override var fastSeekAmountSec: Int = 10
|
override var fastSeekAmountSec: Int = 10
|
||||||
|
|
||||||
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
|
|
||||||
|
|
||||||
override var playBackMode = MutableStateFlow(PlayMode.IDLE)
|
override var playBackMode = MutableStateFlow(PlayMode.IDLE)
|
||||||
|
|
||||||
override var shuffle: Boolean
|
override var shuffle: Boolean
|
||||||
|
@ -251,7 +251,11 @@ class NewPlayerImpl(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun release() {
|
override fun release() {
|
||||||
|
mediaController?.release()
|
||||||
internalPlayer.release()
|
internalPlayer.release()
|
||||||
|
playBackMode.update {
|
||||||
|
PlayMode.IDLE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
|
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
|
||||||
|
|
|
@ -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()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
|
@ -21,32 +21,122 @@
|
||||||
|
|
||||||
package net.newpipe.newplayer.service
|
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.MediaSession
|
||||||
import androidx.media3.session.MediaSessionService
|
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 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.NewPlayer
|
||||||
|
import net.newpipe.newplayer.PlayMode
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val TAG = "NewPlayerService"
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class NewPlayerService : MediaSessionService() {
|
class NewPlayerService : MediaSessionService() {
|
||||||
|
|
||||||
private var mediaSession: MediaSession? = null
|
private lateinit var mediaSession: MediaSession
|
||||||
|
private lateinit var customCommands: List<CustomCommand>
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
lateinit var newPlayer: NewPlayer
|
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() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
newPlayer.release()
|
newPlayer.release()
|
||||||
mediaSession?.release()
|
mediaSession.release()
|
||||||
mediaSession = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? {
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
println("gurken get session")
|
// Check if the player is not ready to play or there are no items in the media queue
|
||||||
if (mediaSession == null) {
|
if (!newPlayer.internalPlayer.playWhenReady || newPlayer.playlist.value.size == 0) {
|
||||||
mediaSession = MediaSession.Builder(this, newPlayer.internalPlayer).build()
|
// Stop the service
|
||||||
|
stopSelf()
|
||||||
}
|
}
|
||||||
return mediaSession
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo) = mediaSession
|
||||||
}
|
}
|
|
@ -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>
|
|
@ -52,4 +52,5 @@
|
||||||
<string name="shuffle_on">Shuffle playlist enabled</string>
|
<string name="shuffle_on">Shuffle playlist enabled</string>
|
||||||
<string name="shuffle_off">Shuffle playlist disabled</string>
|
<string name="shuffle_off">Shuffle playlist disabled</string>
|
||||||
<string name="store_playlist">Save current playlist</string>
|
<string name="store_playlist">Save current playlist</string>
|
||||||
|
<string name="close">Close</string>
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue