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