handle currently plaing and show title and creator
This commit is contained in:
parent
d97ecc7519
commit
a47ea8e078
|
@ -40,8 +40,8 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||
import net.newpipe.newplayer.playerInternals.fetchPlaylistItem
|
||||
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromExoplayer
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
import kotlin.Exception
|
||||
import kotlin.random.Random
|
||||
|
||||
|
@ -72,12 +72,12 @@ interface NewPlayer {
|
|||
val sharingLinkWithOffsetPossible: Boolean
|
||||
var currentPosition: Long
|
||||
var fastSeekAmountSec: Int
|
||||
var playBackMode: PlayMode
|
||||
var playMode: StateFlow<PlayMode?>
|
||||
val playBackMode: MutableStateFlow<PlayMode?>
|
||||
var shuffle: Boolean
|
||||
var repeatMode: RepeatMode
|
||||
|
||||
val playlist: StateFlow<List<PlaylistItem>>
|
||||
val currentlyPlaying: StateFlow<PlaylistItem?>
|
||||
|
||||
// callbacks
|
||||
|
||||
|
@ -93,7 +93,6 @@ interface NewPlayer {
|
|||
fun removePlaylistItem(index: Int)
|
||||
fun playStream(item: String, playMode: PlayMode)
|
||||
fun playStream(item: String, streamVariant: String, playMode: PlayMode)
|
||||
fun setPlayMode(playMode: PlayMode)
|
||||
|
||||
data class Builder(val app: Application, val repository: MediaRepository) {
|
||||
private var mediaSourceFactory: MediaSource.Factory? = null
|
||||
|
@ -155,12 +154,10 @@ class NewPlayerImpl(
|
|||
}
|
||||
|
||||
override var fastSeekAmountSec: Int = 10
|
||||
override var playBackMode: PlayMode = PlayMode.EMBEDDED_VIDEO
|
||||
|
||||
private var playerScope = CoroutineScope(Dispatchers.Main + Job())
|
||||
|
||||
var mutablePlayMode = MutableStateFlow<PlayMode?>(null)
|
||||
override var playMode = mutablePlayMode.asStateFlow()
|
||||
override var playBackMode = MutableStateFlow<PlayMode?>(null)
|
||||
|
||||
override var shuffle: Boolean
|
||||
get() = internalPlayer.shuffleModeEnabled
|
||||
|
@ -169,21 +166,21 @@ class NewPlayerImpl(
|
|||
}
|
||||
|
||||
override var repeatMode: RepeatMode
|
||||
get() = when(internalPlayer.repeatMode) {
|
||||
get() = when (internalPlayer.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}")
|
||||
}
|
||||
set(value) {
|
||||
when(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
|
||||
}
|
||||
}
|
||||
|
||||
var mutableOnEvent = MutableSharedFlow<Pair<Player, Player.Events>>()
|
||||
private var mutableOnEvent = MutableSharedFlow<Pair<Player, Player.Events>>()
|
||||
override val onExoPlayerEvent: SharedFlow<Pair<Player, Player.Events>> =
|
||||
mutableOnEvent.asSharedFlow()
|
||||
|
||||
|
@ -197,10 +194,13 @@ class NewPlayerImpl(
|
|||
override val duration: Long
|
||||
get() = internalPlayer.duration
|
||||
|
||||
val mutablePlaylist = MutableStateFlow<List<PlaylistItem>>(emptyList())
|
||||
private val mutablePlaylist = MutableStateFlow<List<PlaylistItem>>(emptyList())
|
||||
override val playlist: StateFlow<List<PlaylistItem>> =
|
||||
mutablePlaylist.asStateFlow()
|
||||
|
||||
private val mutableCurrentlyPlaying = MutableStateFlow<PlaylistItem?>(null)
|
||||
override val currentlyPlaying: StateFlow<PlaylistItem?> = mutableCurrentlyPlaying.asStateFlow()
|
||||
|
||||
init {
|
||||
println("gurken init")
|
||||
internalPlayer.addListener(object : Player.Listener {
|
||||
|
@ -227,12 +227,37 @@ class NewPlayerImpl(
|
|||
super.onTimelineChanged(timeline, reason)
|
||||
updatePlaylistItems()
|
||||
}
|
||||
|
||||
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
|
||||
super.onMediaItemTransition(mediaItem, reason)
|
||||
mediaItem?.let {
|
||||
val playlistItem = getPlaylistItem(mediaItem.mediaId.toLong())
|
||||
if (playlistItem != null) {
|
||||
mutableCurrentlyPlaying.update {
|
||||
playlistItem
|
||||
}
|
||||
} else {
|
||||
launchJobAndCollectError {
|
||||
val item = fetchPlaylistItem(
|
||||
uniqueId = mediaItem.mediaId.toLong(),
|
||||
mediaRepo = repository,
|
||||
idLookupTable = uniqueIdToIdLookup
|
||||
)
|
||||
mutableCurrentlyPlaying.update { item }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updatePlaylistItems() {
|
||||
playerScope.launch {
|
||||
val playlist = getPlaylistItemsFromExoplayer(internalPlayer, repository, uniqueIdToIdLookup)
|
||||
val playlist =
|
||||
getPlaylistItemsFromExoplayer(internalPlayer, repository, uniqueIdToIdLookup)
|
||||
var playlistDuration = 0
|
||||
for (item in playlist) {
|
||||
playlistDuration += item.lengthInS
|
||||
|
@ -244,6 +269,15 @@ class NewPlayerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getPlaylistItem(uniqueId: Long): PlaylistItem? {
|
||||
for (item in playlist.value) {
|
||||
if (item.uniqueId == uniqueId) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun prepare() {
|
||||
internalPlayer.prepare()
|
||||
}
|
||||
|
@ -289,15 +323,12 @@ class NewPlayerImpl(
|
|||
}
|
||||
}
|
||||
|
||||
override fun setPlayMode(playMode: PlayMode) {
|
||||
this.mutablePlayMode.update { playMode }
|
||||
}
|
||||
|
||||
private fun internalPlayStream(mediaItem: MediaItem, playMode: PlayMode) {
|
||||
if (internalPlayer.playbackState == Player.STATE_IDLE) {
|
||||
internalPlayer.prepare()
|
||||
}
|
||||
this.mutablePlayMode.update { playMode }
|
||||
this.playBackMode.update { playMode }
|
||||
this.internalPlayer.setMediaItem(mediaItem)
|
||||
this.internalPlayer.play()
|
||||
}
|
||||
|
@ -305,7 +336,7 @@ class NewPlayerImpl(
|
|||
private suspend fun toMediaItem(item: String, streamVariant: String): MediaItem {
|
||||
val dataStream = repository.getStream(item, streamVariant)
|
||||
val uniqueId = Random.nextLong()
|
||||
uniqueIdToIdLookup.set(uniqueId, item)
|
||||
uniqueIdToIdLookup[uniqueId] = item
|
||||
val mediaItem = MediaItem.Builder().setMediaId(uniqueId.toString()).setUri(dataStream)
|
||||
return mediaItem.build()
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
package net.newpipe.newplayer.model
|
||||
|
||||
import androidx.media3.common.Player
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.RepeatMode
|
||||
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||
|
@ -45,14 +44,12 @@ data class VideoPlayerUIState(
|
|||
val chapters: List<Chapter>,
|
||||
val shuffleEnabled: Boolean,
|
||||
val repeatMode: RepeatMode,
|
||||
val playListDurationInS: Int
|
||||
val playListDurationInS: Int,
|
||||
val currentlyPlaying: PlaylistItem
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT = VideoPlayerUIState(
|
||||
// TODO: replace this with the placeholder state.
|
||||
// The actual initial state upon starting to play is dictated by the NewPlayer instance
|
||||
uiMode = UIModeState.PLACEHOLDER,
|
||||
//uiMode = UIModeState.PLACEHOLDER,
|
||||
playing = false,
|
||||
contentRatio = 16 / 9f,
|
||||
embeddedUiRatio = 16f / 9f,
|
||||
|
@ -70,7 +67,24 @@ data class VideoPlayerUIState(
|
|||
chapters = emptyList(),
|
||||
shuffleEnabled = false,
|
||||
repeatMode = RepeatMode.DONT_REPEAT,
|
||||
playListDurationInS = 0
|
||||
playListDurationInS = 0,
|
||||
currentlyPlaying = PlaylistItem.DEFAULT
|
||||
)
|
||||
|
||||
val DUMMY = DEFAULT.copy(
|
||||
uiMode = UIModeState.EMBEDDED_VIDEO,
|
||||
playing = true,
|
||||
seekerPosition = 0.3f,
|
||||
bufferedPercentage = 0.5f,
|
||||
isLoading = false,
|
||||
durationInMs = 420,
|
||||
playbackPositionInMs = 69,
|
||||
fastSeekSeconds = 10,
|
||||
soundVolume = 0.5f,
|
||||
brightness = 0.2f,
|
||||
shuffleEnabled = true,
|
||||
playListDurationInS = 5493,
|
||||
currentlyPlaying = PlaylistItem.DUMMY
|
||||
)
|
||||
}
|
||||
}
|
|
@ -67,4 +67,5 @@ interface VideoPlayerViewModel {
|
|||
fun movePlaylistItem(from: Int, to: Int)
|
||||
fun removePlaylistItem(index: Int)
|
||||
fun onStreamItemDragFinished()
|
||||
fun dialogVisible(visible: Boolean)
|
||||
}
|
|
@ -40,12 +40,14 @@ import kotlinx.coroutines.flow.SharedFlow
|
|||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.utils.VideoSize
|
||||
import net.newpipe.newplayer.NewPlayer
|
||||
import net.newpipe.newplayer.RepeatMode
|
||||
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||
import net.newpipe.newplayer.ui.ContentScale
|
||||
import java.util.LinkedList
|
||||
|
||||
|
@ -179,7 +181,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
}
|
||||
newPlayer?.let { newPlayer ->
|
||||
viewModelScope.launch {
|
||||
newPlayer.playMode.collect { newMode ->
|
||||
newPlayer.playBackMode.collect { newMode ->
|
||||
val currentMode = mutableUiState.value.uiMode.toPlayMode()
|
||||
if (currentMode != newMode) {
|
||||
mutableUiState.update {
|
||||
|
@ -196,6 +198,13 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
mutableUiState.update { it.copy(playList = playlist) }
|
||||
}
|
||||
}
|
||||
viewModelScope.launch {
|
||||
newPlayer.currentlyPlaying.collect { playlistItem ->
|
||||
mutableUiState.update {
|
||||
it.copy(currentlyPlaying = playlistItem ?: PlaylistItem.DEFAULT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -467,6 +476,14 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
playlistItemToBeMoved = null
|
||||
}
|
||||
|
||||
override fun dialogVisible(visible: Boolean) {
|
||||
if(visible) {
|
||||
uiVisibilityJob?.cancel()
|
||||
} else {
|
||||
resetHideUiDelayedJob()
|
||||
}
|
||||
}
|
||||
|
||||
override fun removePlaylistItem(index: Int) {
|
||||
newPlayer?.removePlaylistItem(index)
|
||||
}
|
||||
|
@ -476,7 +493,9 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
val newPlayMode = newState.toPlayMode()
|
||||
val currentPlayMode = mutableUiState.value.uiMode.toPlayMode()
|
||||
if (newPlayMode != currentPlayMode) {
|
||||
newPlayer?.setPlayMode(newPlayMode!!)
|
||||
newPlayer?.playBackMode?.update {
|
||||
newPlayMode!!
|
||||
}
|
||||
} else {
|
||||
mutableUiState.update {
|
||||
it.copy(uiMode = newState)
|
||||
|
|
|
@ -116,6 +116,10 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
|
|||
println("dummy impl")
|
||||
}
|
||||
|
||||
override fun dialogVisible(visible: Boolean) {
|
||||
println("dummy impl dialog visible: $visible")
|
||||
}
|
||||
|
||||
override fun pause() {
|
||||
println("dummy pause")
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
package net.newpipe.newplayer.playerInternals
|
||||
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.async
|
||||
|
@ -37,12 +38,55 @@ data class PlaylistItem(
|
|||
val uniqueId: Long,
|
||||
val thumbnail: Thumbnail?,
|
||||
val lengthInS: Int
|
||||
)
|
||||
) {
|
||||
companion object {
|
||||
val DEFAULT = PlaylistItem(
|
||||
title = "",
|
||||
creator = "",
|
||||
id = "",
|
||||
uniqueId = -1L,
|
||||
thumbnail = null,
|
||||
lengthInS = 0
|
||||
)
|
||||
|
||||
suspend fun getPlaylistItemsFromExoplayer(player: Player, mediaRepo: MediaRepository, idLookupTable: HashMap<Long, String>) =
|
||||
val DUMMY = PlaylistItem(
|
||||
title = "Superawesome Video",
|
||||
creator = "Yours truely",
|
||||
id = "some_id",
|
||||
uniqueId = 12345L,
|
||||
thumbnail = null,
|
||||
lengthInS = 420
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchPlaylistItem(
|
||||
uniqueId: Long,
|
||||
mediaRepo: MediaRepository,
|
||||
idLookupTable: HashMap<Long, String>
|
||||
) : PlaylistItem {
|
||||
val id = idLookupTable[uniqueId]
|
||||
?: throw NewPlayerException("Unknown uniqueId: $uniqueId, uniqueId Id mapping error. Something went wrong during datafetching.")
|
||||
val metaInfo = mediaRepo.getMetaInfo(id)
|
||||
|
||||
return PlaylistItem(
|
||||
title = metaInfo.title,
|
||||
creator = metaInfo.channelName,
|
||||
id = id,
|
||||
thumbnail = metaInfo.thumbnail,
|
||||
lengthInS = metaInfo.lengthInS,
|
||||
uniqueId = uniqueId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
suspend fun getPlaylistItemsFromExoplayer(
|
||||
player: Player,
|
||||
mediaRepo: MediaRepository,
|
||||
idLookupTable: HashMap<Long, String>
|
||||
) =
|
||||
with(CoroutineScope(coroutineContext)) {
|
||||
(0..player.mediaItemCount-1).map { index ->
|
||||
println("gurken index: $index")
|
||||
(0..player.mediaItemCount - 1).map { index ->
|
||||
val mediaItem = player.getMediaItemAt(index)
|
||||
val uniqueId = mediaItem.mediaId.toLong()
|
||||
val id = idLookupTable.get(uniqueId)
|
||||
|
@ -67,9 +111,9 @@ suspend fun getPlaylistItemsFromExoplayer(player: Player, mediaRepo: MediaReposi
|
|||
}
|
||||
}
|
||||
|
||||
fun getPlaylistDurationInS(items: List<PlaylistItem>) : Int {
|
||||
fun getPlaylistDurationInS(items: List<PlaylistItem>): Int {
|
||||
var duration = 0
|
||||
for(item in items) {
|
||||
for (item in items) {
|
||||
duration += item.lengthInS
|
||||
}
|
||||
return duration
|
||||
|
|
|
@ -126,7 +126,7 @@ fun VideoPlayerControllerBottomUIPreview() {
|
|||
BottomUI(
|
||||
modifier = Modifier,
|
||||
viewModel = VideoPlayerViewModelDummy(),
|
||||
uiState = VideoPlayerUIState.DEFAULT.copy(
|
||||
uiState = VideoPlayerUIState.DUMMY.copy(
|
||||
uiMode = UIModeState.FULLSCREEN_VIDEO_CONTROLLER_UI,
|
||||
seekerPosition = 0.4f,
|
||||
durationInMs = 90 * 60 * 1000,
|
||||
|
|
|
@ -122,7 +122,7 @@ fun VideoPlayerControllerUICenterUIPreview() {
|
|||
Surface(color = Color.Black) {
|
||||
CenterUI(
|
||||
viewModel = VideoPlayerViewModelDummy(),
|
||||
uiState = VideoPlayerUIState.DEFAULT.copy(
|
||||
uiState = VideoPlayerUIState.DUMMY.copy(
|
||||
isLoading = false,
|
||||
playing = true
|
||||
)
|
||||
|
|
|
@ -52,13 +52,16 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.DpOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.model.VideoPlayerUIState
|
||||
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
|
||||
@Composable
|
||||
fun DropDownMenu() {
|
||||
fun DropDownMenu(viewModel: VideoPlayerViewModel, uiState: VideoPlayerUIState) {
|
||||
var showMainMenu: Boolean by remember { mutableStateOf(false) }
|
||||
|
||||
var pixel_density = LocalDensity.current
|
||||
val pixel_density = LocalDensity.current
|
||||
|
||||
var offsetY by remember {
|
||||
mutableStateOf(0.dp)
|
||||
|
@ -140,8 +143,8 @@ fun DropDownMenu() {
|
|||
@Composable
|
||||
fun VideoPlayerControllerDropDownPreview() {
|
||||
VideoPlayerTheme {
|
||||
Box(Modifier.fillMaxSize()){
|
||||
DropDownMenu()
|
||||
Box(Modifier.fillMaxSize()) {
|
||||
DropDownMenu(VideoPlayerViewModelDummy(), VideoPlayerUIState.DUMMY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,7 +201,7 @@ fun VideoPlayerStreamSelectUIPreview() {
|
|||
StreamSelectUI(
|
||||
isChapterSelect = false,
|
||||
viewModel = VideoPlayerViewModelDummy(),
|
||||
uiState = VideoPlayerUIState.DEFAULT.copy(
|
||||
uiState = VideoPlayerUIState.DUMMY.copy(
|
||||
playList = arrayListOf(
|
||||
PlaylistItem(
|
||||
id = "6502",
|
||||
|
|
|
@ -51,6 +51,7 @@ import net.newpipe.newplayer.R
|
|||
import net.newpipe.newplayer.model.VideoPlayerUIState
|
||||
import net.newpipe.newplayer.model.VideoPlayerViewModel
|
||||
import net.newpipe.newplayer.model.VideoPlayerViewModelDummy
|
||||
import net.newpipe.newplayer.playerInternals.PlaylistItem
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
import net.newpipe.newplayer.ui.theme.video_player_onSurface
|
||||
import net.newpipe.newplayer.utils.getEmbeddedUiConfig
|
||||
|
@ -67,14 +68,17 @@ fun TopUI(
|
|||
) {
|
||||
Column(horizontalAlignment = Alignment.Start, modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
"The Title",
|
||||
uiState.currentlyPlaying.title,
|
||||
fontSize = 15.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
"The Channel", fontSize = 12.sp, maxLines = 1, overflow = TextOverflow.Ellipsis
|
||||
uiState.currentlyPlaying.creator,
|
||||
fontSize = 12.sp,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
Button(
|
||||
|
@ -105,9 +109,14 @@ fun TopUI(
|
|||
)
|
||||
}
|
||||
}
|
||||
androidx.compose.animation.AnimatedVisibility(visible = 1 < uiState.playList.size) {
|
||||
AnimatedVisibility(visible = 1 < uiState.playList.size) {
|
||||
IconButton(
|
||||
onClick = { viewModel.openStreamSelection(selectChapter = false, embeddedUiConfig) },
|
||||
onClick = {
|
||||
viewModel.openStreamSelection(
|
||||
selectChapter = false,
|
||||
embeddedUiConfig
|
||||
)
|
||||
},
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.List,
|
||||
|
@ -115,7 +124,7 @@ fun TopUI(
|
|||
)
|
||||
}
|
||||
}
|
||||
DropDownMenu()
|
||||
DropDownMenu(viewModel, uiState)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +137,9 @@ fun TopUI(
|
|||
fun VideoPlayerControllerTopUIPreview() {
|
||||
VideoPlayerTheme {
|
||||
Surface(color = Color.Black) {
|
||||
TopUI(modifier = Modifier, VideoPlayerViewModelDummy(), VideoPlayerUIState.DEFAULT)
|
||||
TopUI(
|
||||
modifier = Modifier, VideoPlayerViewModelDummy(), VideoPlayerUIState.DUMMY
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue