make exoPlayer a StateFlow that can be null
This commit is contained in:
parent
2e50634b50
commit
4fb5d46b2d
|
@ -20,11 +20,8 @@
|
|||
|
||||
package net.newpipe.newplayer
|
||||
|
||||
import android.app.Application
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
@ -49,7 +46,7 @@ interface NewPlayer {
|
|||
// preferences
|
||||
val preferredStreamVariants: List<String>
|
||||
|
||||
val internalPlayer: Player
|
||||
val exoPlayer: StateFlow<Player?>
|
||||
var playWhenReady: Boolean
|
||||
val duration: Long
|
||||
val bufferedPercentage: Int
|
||||
|
@ -81,33 +78,4 @@ interface NewPlayer {
|
|||
fun selectChapter(index: Int)
|
||||
fun playStream(item: String, streamVariant: String, playMode: PlayMode)
|
||||
fun release()
|
||||
|
||||
data class Builder(val app: Application, val repository: MediaRepository) {
|
||||
private var mediaSourceFactory: MediaSource.Factory? = null
|
||||
private var preferredStreamVariants: List<String> = emptyList()
|
||||
|
||||
fun setMediaSourceFactory(mediaSourceFactory: MediaSource.Factory): Builder {
|
||||
this.mediaSourceFactory = mediaSourceFactory
|
||||
return this
|
||||
}
|
||||
|
||||
fun setPreferredStreamVariants(preferredStreamVariants: List<String>): Builder {
|
||||
this.preferredStreamVariants = preferredStreamVariants
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): NewPlayer {
|
||||
val exoPlayerBuilder = ExoPlayer.Builder(app)
|
||||
mediaSourceFactory?.let {
|
||||
exoPlayerBuilder.setMediaSourceFactory(it)
|
||||
}
|
||||
return NewPlayerImpl(
|
||||
app = app,
|
||||
internalPlayer = exoPlayerBuilder.build(),
|
||||
repository = repository,
|
||||
preferredStreamVariants = preferredStreamVariants,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -22,13 +22,12 @@ package net.newpipe.newplayer
|
|||
|
||||
import android.app.Application
|
||||
import android.content.ComponentName
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.MediaController
|
||||
import androidx.media3.session.SessionToken
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
|
@ -50,11 +49,13 @@ private const val TAG = "NewPlayerImpl"
|
|||
|
||||
class NewPlayerImpl(
|
||||
val app: Application,
|
||||
override val internalPlayer: Player,
|
||||
override val preferredStreamVariants: List<String>,
|
||||
private val repository: MediaRepository
|
||||
private val repository: MediaRepository,
|
||||
override val preferredStreamVariants: List<String> = emptyList()
|
||||
) : NewPlayer {
|
||||
|
||||
private val mutableExoPlayer = MutableStateFlow<ExoPlayer?>(null)
|
||||
override val exoPlayer = mutableExoPlayer.asStateFlow()
|
||||
|
||||
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
private var uniqueIdToIdLookup = HashMap<Long, String>()
|
||||
|
@ -66,12 +67,12 @@ class NewPlayerImpl(
|
|||
override val errorFlow = mutableErrorFlow.asSharedFlow()
|
||||
|
||||
override val bufferedPercentage: Int
|
||||
get() = internalPlayer.bufferedPercentage
|
||||
get() = exoPlayer.value?.bufferedPercentage ?: 0
|
||||
|
||||
override var currentPosition: Long
|
||||
get() = internalPlayer.currentPosition
|
||||
get() = exoPlayer.value?.currentPosition ?: 0
|
||||
set(value) {
|
||||
internalPlayer.seekTo(value)
|
||||
exoPlayer.value?.seekTo(value)
|
||||
}
|
||||
|
||||
override var fastSeekAmountSec: Int = 10
|
||||
|
@ -79,23 +80,23 @@ class NewPlayerImpl(
|
|||
override var playBackMode = MutableStateFlow(PlayMode.IDLE)
|
||||
|
||||
override var shuffle: Boolean
|
||||
get() = internalPlayer.shuffleModeEnabled
|
||||
get() = exoPlayer.value?.shuffleModeEnabled ?: false
|
||||
set(value) {
|
||||
internalPlayer.shuffleModeEnabled = value
|
||||
exoPlayer.value?.shuffleModeEnabled = value
|
||||
}
|
||||
|
||||
override var repeatMode: RepeatMode
|
||||
get() = when (internalPlayer.repeatMode) {
|
||||
get() = when (exoPlayer.value?.repeatMode) {
|
||||
Player.REPEAT_MODE_OFF -> RepeatMode.DONT_REPEAT
|
||||
Player.REPEAT_MODE_ALL -> RepeatMode.REPEAT_ALL
|
||||
Player.REPEAT_MODE_ONE -> RepeatMode.REPEAT_ONE
|
||||
else -> throw NewPlayerException("Unknown Repeatmode option returned by ExoPlayer: ${internalPlayer.repeatMode}")
|
||||
else -> throw NewPlayerException("Unknown Repeatmode option returned by ExoPlayer: ${exoPlayer.value?.repeatMode}")
|
||||
}
|
||||
set(value) {
|
||||
when (value) {
|
||||
RepeatMode.DONT_REPEAT -> internalPlayer.repeatMode = Player.REPEAT_MODE_OFF
|
||||
RepeatMode.REPEAT_ALL -> internalPlayer.repeatMode = Player.REPEAT_MODE_ALL
|
||||
RepeatMode.REPEAT_ONE -> internalPlayer.repeatMode = Player.REPEAT_MODE_ONE
|
||||
RepeatMode.DONT_REPEAT -> exoPlayer.value?.repeatMode = Player.REPEAT_MODE_OFF
|
||||
RepeatMode.REPEAT_ALL -> exoPlayer.value?.repeatMode = Player.REPEAT_MODE_ALL
|
||||
RepeatMode.REPEAT_ONE -> exoPlayer.value?.repeatMode = Player.REPEAT_MODE_ONE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,13 +106,13 @@ class NewPlayerImpl(
|
|||
|
||||
override var playWhenReady: Boolean
|
||||
set(value) {
|
||||
internalPlayer.playWhenReady = value
|
||||
exoPlayer.value?.playWhenReady = value
|
||||
}
|
||||
get() = internalPlayer.playWhenReady
|
||||
get() = exoPlayer.value?.playWhenReady ?: false
|
||||
|
||||
|
||||
override val duration: Long
|
||||
get() = internalPlayer.duration
|
||||
get() = exoPlayer.value?.duration ?: 0
|
||||
|
||||
private val mutablePlaylist = MutableStateFlow<List<MediaItem>>(emptyList())
|
||||
override val playlist: StateFlow<List<MediaItem>> =
|
||||
|
@ -124,19 +125,20 @@ class NewPlayerImpl(
|
|||
override val currentChapters: StateFlow<List<Chapter>> = mutableCurrentChapter.asStateFlow()
|
||||
|
||||
override var currentlyPlayingPlaylistItem: Int
|
||||
get() = internalPlayer.currentMediaItemIndex
|
||||
get() = exoPlayer.value?.currentMediaItemIndex ?: -1
|
||||
set(value) {
|
||||
assert(value in 0..<playlist.value.size) {
|
||||
throw NewPlayerException("Playlist item selection out of bound: selected item index: $value, available chapters: ${playlist.value.size}")
|
||||
}
|
||||
internalPlayer.seekTo(value, 0)
|
||||
exoPlayer.value?.seekTo(value, 0)
|
||||
}
|
||||
|
||||
init {
|
||||
internalPlayer.addListener(object : Player.Listener {
|
||||
private fun setupNewExoplayer() {
|
||||
val newExoPlayer = ExoPlayer.Builder(app).build()
|
||||
newExoPlayer.addListener(object : Player.Listener {
|
||||
override fun onPlayerError(error: PlaybackException) {
|
||||
launchJobAndCollectError {
|
||||
val item = internalPlayer.currentMediaItem?.mediaId
|
||||
val item = newExoPlayer.currentMediaItem?.mediaId
|
||||
val newUri = repository.tryAndRescueError(item, exception = error)
|
||||
if (newUri != null) {
|
||||
TODO("Implement handing new uri on fixed error")
|
||||
|
@ -155,8 +157,8 @@ class NewPlayerImpl(
|
|||
|
||||
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
||||
mutablePlaylist.update {
|
||||
(0..<internalPlayer.mediaItemCount).map {
|
||||
internalPlayer.getMediaItemAt(it)
|
||||
(0..<newExoPlayer.mediaItemCount).map {
|
||||
newExoPlayer.getMediaItemAt(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -166,7 +168,12 @@ class NewPlayerImpl(
|
|||
mutableCurrentlyPlaying.update { mediaItem }
|
||||
}
|
||||
})
|
||||
mutableExoPlayer.update {
|
||||
newExoPlayer
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
playerScope.launch {
|
||||
currentlyPlaying.collect { playing ->
|
||||
playing?.let {
|
||||
|
@ -183,9 +190,13 @@ class NewPlayerImpl(
|
|||
}
|
||||
|
||||
override fun prepare() {
|
||||
internalPlayer.prepare()
|
||||
if(exoPlayer.value == null) {
|
||||
setupNewExoplayer()
|
||||
}
|
||||
exoPlayer.value?.prepare()
|
||||
if (mediaController == null) {
|
||||
val sessionToken = SessionToken(app, ComponentName(app, NewPlayerService::class.java))
|
||||
val sessionToken =
|
||||
SessionToken(app, ComponentName(app, NewPlayerService::class.java))
|
||||
val mediaControllerFuture = MediaController.Builder(app, sessionToken).buildAsync()
|
||||
mediaControllerFuture.addListener({
|
||||
mediaController = mediaControllerFuture.get()
|
||||
|
@ -195,36 +206,41 @@ class NewPlayerImpl(
|
|||
|
||||
|
||||
override fun play() {
|
||||
if (internalPlayer.currentMediaItem != null) {
|
||||
internalPlayer.play()
|
||||
} else {
|
||||
Log.i(TAG, "Tried to start playing but no media Item was cued")
|
||||
exoPlayer.value?.let {
|
||||
if (exoPlayer.value?.currentMediaItem != null) {
|
||||
exoPlayer.value?.play()
|
||||
} else {
|
||||
Log.i(TAG, "Tried to start playing but no media Item was cued")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
internalPlayer.pause()
|
||||
exoPlayer.value?.pause()
|
||||
}
|
||||
|
||||
override fun addToPlaylist(item: String) {
|
||||
if (exoPlayer.value == null) {
|
||||
prepare()
|
||||
}
|
||||
launchJobAndCollectError {
|
||||
val mediaItem = toMediaItem(item)
|
||||
internalPlayer.addMediaItem(mediaItem)
|
||||
exoPlayer.value?.addMediaItem(mediaItem)
|
||||
}
|
||||
}
|
||||
|
||||
override fun movePlaylistItem(fromIndex: Int, toIndex: Int) {
|
||||
internalPlayer.moveMediaItem(fromIndex, toIndex)
|
||||
exoPlayer.value?.moveMediaItem(fromIndex, toIndex)
|
||||
}
|
||||
|
||||
override fun removePlaylistItem(uniqueId: Long) {
|
||||
for (i in 0..<internalPlayer.mediaItemCount) {
|
||||
val id = internalPlayer.getMediaItemAt(i).mediaId.toLong()
|
||||
if (id == uniqueId) {
|
||||
internalPlayer.removeMediaItem(i)
|
||||
break
|
||||
for (i in 0..<(exoPlayer.value?.mediaItemCount ?: 0)) {
|
||||
val id = exoPlayer.value?.getMediaItemAt(i)?.mediaId?.toLong() ?: 0
|
||||
if (id == uniqueId) {
|
||||
exoPlayer.value?.removeMediaItem(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun playStream(item: String, playMode: PlayMode) {
|
||||
|
@ -234,7 +250,11 @@ class NewPlayerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun playStream(item: String, streamVariant: String, playMode: PlayMode) {
|
||||
override fun playStream(
|
||||
item: String,
|
||||
streamVariant: String,
|
||||
playMode: PlayMode
|
||||
) {
|
||||
launchJobAndCollectError {
|
||||
val stream = toMediaItem(item, streamVariant)
|
||||
internalPlayStream(stream, playMode)
|
||||
|
@ -252,22 +272,24 @@ class NewPlayerImpl(
|
|||
|
||||
override fun release() {
|
||||
mediaController?.release()
|
||||
internalPlayer.release()
|
||||
exoPlayer.value?.release()
|
||||
playBackMode.update {
|
||||
PlayMode.IDLE
|
||||
}
|
||||
}
|
||||
|
||||
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
|
||||
if (internalPlayer.playbackState == Player.STATE_IDLE) {
|
||||
if (exoPlayer.value?.playbackState == Player.STATE_IDLE || exoPlayer.value == null) {
|
||||
prepare()
|
||||
}
|
||||
this.playBackMode.update { playMode }
|
||||
this.internalPlayer.setMediaItem(mediaItem)
|
||||
this.internalPlayer.play()
|
||||
println("gurken: playervalue: ${this.exoPlayer.value}")
|
||||
this.exoPlayer.value?.setMediaItem(mediaItem)
|
||||
this.exoPlayer.value?.play()
|
||||
}
|
||||
|
||||
private suspend fun toMediaItem(item: String, streamVariant: String): MediaItem {
|
||||
private suspend
|
||||
fun toMediaItem(item: String, streamVariant: String): MediaItem {
|
||||
val dataStream = repository.getStream(item, streamVariant)
|
||||
|
||||
val uniqueId = Random.nextLong()
|
||||
|
@ -286,7 +308,8 @@ class NewPlayerImpl(
|
|||
return mediaItemBuilder.build()
|
||||
}
|
||||
|
||||
private suspend fun toMediaItem(item: String): MediaItem {
|
||||
private suspend
|
||||
fun toMediaItem(item: String): MediaItem {
|
||||
|
||||
val availableStream = repository.getAvailableStreamVariants(item)
|
||||
var selectedStream = availableStream[availableStream.size / 2]
|
||||
|
|
|
@ -138,52 +138,57 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
|
||||
private fun installNewPlayer() {
|
||||
newPlayer?.let { newPlayer ->
|
||||
val player = newPlayer.internalPlayer
|
||||
Log.d(TAG, "Install player: ${player.videoSize.width}")
|
||||
viewModelScope.launch {
|
||||
newPlayer.exoPlayer.collect { player ->
|
||||
|
||||
player.addListener(object : Player.Listener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
super.onIsPlayingChanged(isPlaying)
|
||||
Log.d(TAG, "Playing state changed. Is Playing: $isPlaying")
|
||||
mutableUiState.update {
|
||||
it.copy(playing = isPlaying, isLoading = false)
|
||||
}
|
||||
if (isPlaying && uiState.value.uiMode.controllerUiVisible) {
|
||||
resetHideUiDelayedJob()
|
||||
} else {
|
||||
uiVisibilityJob?.cancel()
|
||||
}
|
||||
}
|
||||
Log.d(TAG, "Install player: ${player?.videoSize?.width}")
|
||||
|
||||
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
|
||||
super.onVideoSizeChanged(videoSize)
|
||||
updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize))
|
||||
}
|
||||
|
||||
|
||||
override fun onIsLoadingChanged(isLoading: Boolean) {
|
||||
super.onIsLoadingChanged(isLoading)
|
||||
if (!player.isPlaying) {
|
||||
mutableUiState.update {
|
||||
it.copy(isLoading = isLoading)
|
||||
player?.addListener(object : Player.Listener {
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
super.onIsPlayingChanged(isPlaying)
|
||||
Log.d(TAG, "Playing state changed. Is Playing: $isPlaying")
|
||||
mutableUiState.update {
|
||||
it.copy(playing = isPlaying, isLoading = false)
|
||||
}
|
||||
if (isPlaying && uiState.value.uiMode.controllerUiVisible) {
|
||||
resetHideUiDelayedJob()
|
||||
} else {
|
||||
uiVisibilityJob?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
super.onRepeatModeChanged(repeatMode)
|
||||
mutableUiState.update {
|
||||
it.copy(repeatMode = newPlayer.repeatMode)
|
||||
}
|
||||
}
|
||||
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
|
||||
super.onVideoSizeChanged(videoSize)
|
||||
updateContentRatio(VideoSize.fromMedia3VideoSize(videoSize))
|
||||
}
|
||||
|
||||
|
||||
override fun onIsLoadingChanged(isLoading: Boolean) {
|
||||
super.onIsLoadingChanged(isLoading)
|
||||
if (!player.isPlaying) {
|
||||
mutableUiState.update {
|
||||
it.copy(isLoading = isLoading)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
||||
super.onRepeatModeChanged(repeatMode)
|
||||
mutableUiState.update {
|
||||
it.copy(repeatMode = newPlayer.repeatMode)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
super.onShuffleModeEnabledChanged(shuffleModeEnabled)
|
||||
mutableUiState.update {
|
||||
it.copy(shuffleEnabled = newPlayer.shuffle)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
||||
super.onShuffleModeEnabledChanged(shuffleModeEnabled)
|
||||
mutableUiState.update {
|
||||
it.copy(shuffleEnabled = newPlayer.shuffle)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
newPlayer.playBackMode.collect { newMode ->
|
||||
|
@ -199,6 +204,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
newPlayer.playlist.collect { playlist ->
|
||||
mutableUiState.update {
|
||||
|
@ -228,9 +234,9 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
|
||||
mutableUiState.update {
|
||||
it.copy(
|
||||
playing = newPlayer.internalPlayer.isPlaying,
|
||||
isLoading = !newPlayer.internalPlayer.isPlaying
|
||||
&& newPlayer.internalPlayer.isLoading
|
||||
playing = newPlayer.exoPlayer.value?.isPlaying ?: false,
|
||||
isLoading = !(newPlayer.exoPlayer.value?.isPlaying
|
||||
?: false) && newPlayer.exoPlayer.value?.isLoading ?: false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +300,9 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
override fun nextStream() {
|
||||
resetHideUiDelayedJob()
|
||||
newPlayer?.let { newPlayer ->
|
||||
if (newPlayer.currentlyPlayingPlaylistItem + 1 < newPlayer.internalPlayer.mediaItemCount) {
|
||||
if (newPlayer.currentlyPlayingPlaylistItem + 1 <
|
||||
(newPlayer.exoPlayer.value?.mediaItemCount ?: 0)
|
||||
) {
|
||||
newPlayer.currentlyPlayingPlaylistItem += 1
|
||||
}
|
||||
}
|
||||
|
@ -329,8 +337,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
private fun updateProgressOnce() {
|
||||
val progress = newPlayer?.currentPosition ?: 0
|
||||
val duration = newPlayer?.duration ?: 1
|
||||
val bufferedPercentage =
|
||||
(newPlayer?.bufferedPercentage?.toFloat() ?: 0f) / 100f
|
||||
val bufferedPercentage = (newPlayer?.bufferedPercentage?.toFloat() ?: 0f) / 100f
|
||||
val progressPercentage = progress.toFloat() / duration.toFloat()
|
||||
|
||||
mutableUiState.update {
|
||||
|
@ -358,8 +365,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
var progress = 0L
|
||||
val currentlyPlaying = uiState.value.currentlyPlaying?.mediaId?.toLong() ?: 0L
|
||||
for (item in uiState.value.playList) {
|
||||
if (item.mediaId.toLong() == currentlyPlaying)
|
||||
break;
|
||||
if (item.mediaId.toLong() == currentlyPlaying) break;
|
||||
progress += item.mediaMetadata.durationMs
|
||||
?: throw NewPlayerException("Media Item not containing duration. Media Item in question: ${item.mediaMetadata.title}")
|
||||
}
|
||||
|
@ -575,7 +581,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getEmbeddedUiRatio() = newPlayer?.internalPlayer?.let { player ->
|
||||
private fun getEmbeddedUiRatio() = newPlayer?.exoPlayer?.value?.let { player ->
|
||||
val videoRatio = VideoSize.fromMedia3VideoSize(player.videoSize).getRatio()
|
||||
return (if (videoRatio.isNaN()) currentContentRatio
|
||||
else videoRatio).coerceIn(minContentRatio, maxContentRatio)
|
||||
|
|
|
@ -37,7 +37,6 @@ 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
|
||||
|
@ -62,66 +61,70 @@ class NewPlayerService : MediaSessionService() {
|
|||
|
||||
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()
|
||||
if(newPlayer.exoPlayer.value != null) {
|
||||
mediaSession = MediaSession.Builder(this, newPlayer.exoPlayer.value!!)
|
||||
.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)
|
||||
customCommands.forEach { command ->
|
||||
command.commandButton.sessionCommand?.let {
|
||||
availableSessionCommands.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
return MediaSession.ConnectionResult.accept(
|
||||
availableSessionCommands.build(),
|
||||
connectionResult.availablePlayerCommands
|
||||
)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
override fun onPostConnect(
|
||||
session: MediaSession,
|
||||
controller: MediaSession.ControllerInfo
|
||||
) {
|
||||
super.onPostConnect(session, controller)
|
||||
mediaSession.setCustomLayout(customCommands.map { it.commandButton })
|
||||
}
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
}
|
||||
|
||||
})
|
||||
.build()
|
||||
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()
|
||||
} else {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
|
||||
serviceScope.launch {
|
||||
newPlayer.playBackMode.collect { mode ->
|
||||
if(mode == PlayMode.IDLE) {
|
||||
if (mode == PlayMode.IDLE) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -132,7 +135,9 @@ class NewPlayerService : MediaSessionService() {
|
|||
|
||||
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) {
|
||||
if (!(newPlayer.exoPlayer.value?.playWhenReady
|
||||
?: false) || newPlayer.playlist.value.size == 0
|
||||
) {
|
||||
// Stop the service
|
||||
stopSelf()
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.content.pm.ActivityInfo
|
|||
import android.util.Log
|
||||
import android.view.SurfaceView
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
|
@ -54,7 +55,7 @@ import androidx.core.view.WindowInsetsControllerCompat
|
|||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.media3.common.Player
|
||||
import net.newpipe.newplayer.model.EmbeddedUiConfig
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import net.newpipe.newplayer.model.UIModeState
|
||||
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
|
||||
|
@ -66,6 +67,7 @@ import net.newpipe.newplayer.utils.setScreenBrightness
|
|||
|
||||
private const val TAG = "VideoPlayerUI"
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
fun VideoPlayerUI(
|
||||
viewModel: VideoPlayerViewModel?,
|
||||
|
@ -76,6 +78,7 @@ fun VideoPlayerUI(
|
|||
VideoPlayerLoadingPlaceholder(viewModel.uiState.collectAsState().value.embeddedUiRatio)
|
||||
} else {
|
||||
val uiState by viewModel.uiState.collectAsState()
|
||||
val exoPlayer by viewModel.newPlayer?.exoPlayer!!.collectAsState()
|
||||
|
||||
var lifecycle by remember {
|
||||
mutableStateOf(Lifecycle.Event.ON_CREATE)
|
||||
|
@ -174,17 +177,22 @@ fun VideoPlayerUI(
|
|||
.aspectRatio(uiState.embeddedUiRatio)
|
||||
), color = Color.Black
|
||||
) {
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
PlaySurface(
|
||||
player = viewModel.newPlayer?.internalPlayer,
|
||||
lifecycle = lifecycle,
|
||||
fitMode = uiState.contentFitMode,
|
||||
uiRatio = if (uiState.uiMode.fullscreen) screenRatio
|
||||
else uiState.embeddedUiRatio,
|
||||
contentRatio = uiState.contentRatio
|
||||
)
|
||||
|
||||
exoPlayer?.let { exoPlayer ->
|
||||
Box(contentAlignment = Alignment.Center) {
|
||||
PlaySurface(
|
||||
player = exoPlayer,
|
||||
lifecycle = lifecycle,
|
||||
fitMode = uiState.contentFitMode,
|
||||
uiRatio = if (uiState.uiMode.fullscreen) screenRatio
|
||||
else uiState.embeddedUiRatio,
|
||||
contentRatio = uiState.contentRatio
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// the checks if VideoPlayerControllerUI should be visible or not are done by
|
||||
// The VideoPlayerControllerUI composable itself. This is because Visibility of
|
||||
// the controller is more complicated than just using a simple if statement.
|
||||
|
|
|
@ -28,6 +28,7 @@ import dagger.hilt.InstallIn
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.launch
|
||||
import net.newpipe.newplayer.NewPlayer
|
||||
import net.newpipe.newplayer.NewPlayerImpl
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
||||
|
@ -38,7 +39,7 @@ object NewPlayerComponent {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideNewPlayer(app: Application) : NewPlayer {
|
||||
val player = NewPlayer.Builder(app, TestMediaRepository(app)).build()
|
||||
val player = NewPlayerImpl(app, TestMediaRepository(app))
|
||||
if(app is NewPlayerApp) {
|
||||
app.appScope.launch {
|
||||
while(true) {
|
||||
|
|
Loading…
Reference in New Issue