From a47ea8e07881e6317a17670a7352d084d11519a5 Mon Sep 17 00:00:00 2001 From: Christian Schabesberger Date: Wed, 4 Sep 2024 13:19:36 +0200 Subject: [PATCH] handle currently plaing and show title and creator --- .../java/net/newpipe/newplayer/NewPlayer.kt | 65 ++++++++++++++----- .../newplayer/model/VideoPlayerUIState.kt | 26 ++++++-- .../newplayer/model/VideoPlayerViewModel.kt | 1 + .../model/VideoPlayerViewModelImpl.kt | 23 ++++++- .../model/ViewoPlayerViewModelDummy.kt | 4 ++ .../newplayer/playerInternals/PlayListItem.kt | 56 ++++++++++++++-- .../newplayer/ui/videoplayer/BottomUI.kt | 2 +- .../newplayer/ui/videoplayer/CenterUI.kt | 2 +- .../newpipe/newplayer/ui/videoplayer/Menu.kt | 11 ++-- .../ui/videoplayer/StreamSelectUI.kt | 2 +- .../newpipe/newplayer/ui/videoplayer/TopUI.kt | 23 +++++-- 11 files changed, 171 insertions(+), 44 deletions(-) diff --git a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt index 77f0d2b..38ad00f 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/NewPlayer.kt @@ -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 + val playBackMode: MutableStateFlow var shuffle: Boolean var repeatMode: RepeatMode val playlist: StateFlow> + val currentlyPlaying: StateFlow // 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(null) - override var playMode = mutablePlayMode.asStateFlow() + override var playBackMode = MutableStateFlow(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>() + private var mutableOnEvent = MutableSharedFlow>() override val onExoPlayerEvent: SharedFlow> = mutableOnEvent.asSharedFlow() @@ -197,10 +194,13 @@ class NewPlayerImpl( override val duration: Long get() = internalPlayer.duration - val mutablePlaylist = MutableStateFlow>(emptyList()) + private val mutablePlaylist = MutableStateFlow>(emptyList()) override val playlist: StateFlow> = mutablePlaylist.asStateFlow() + private val mutableCurrentlyPlaying = MutableStateFlow(null) + override val currentlyPlaying: StateFlow = 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() } diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt index fd896b4..02b0221 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerUIState.kt @@ -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, 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 ) } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt index e8900c5..b4c81f8 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModel.kt @@ -67,4 +67,5 @@ interface VideoPlayerViewModel { fun movePlaylistItem(from: Int, to: Int) fun removePlaylistItem(index: Int) fun onStreamItemDragFinished() + fun dialogVisible(visible: Boolean) } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt index 7e8228e..d99d54a 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/VideoPlayerViewModelImpl.kt @@ -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) diff --git a/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt b/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt index d3065db..8dc7072 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/model/ViewoPlayerViewModelDummy.kt @@ -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") } diff --git a/new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt b/new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt index b90323c..20d0069 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/playerInternals/PlayListItem.kt @@ -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) = + 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 +) : 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 +) = 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) : Int { +fun getPlaylistDurationInS(items: List): Int { var duration = 0 - for(item in items) { + for (item in items) { duration += item.lengthInS } return duration diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt index f732bd8..7ba19d0 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/BottomUI.kt @@ -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, diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/CenterUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/CenterUI.kt index 38c55b3..c9959d8 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/CenterUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/CenterUI.kt @@ -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 ) diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/Menu.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/Menu.kt index c29c83f..9ef0a0e 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/Menu.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/Menu.kt @@ -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) } } } diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt index e059ca8..4632e06 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/StreamSelectUI.kt @@ -201,7 +201,7 @@ fun VideoPlayerStreamSelectUIPreview() { StreamSelectUI( isChapterSelect = false, viewModel = VideoPlayerViewModelDummy(), - uiState = VideoPlayerUIState.DEFAULT.copy( + uiState = VideoPlayerUIState.DUMMY.copy( playList = arrayListOf( PlaylistItem( id = "6502", diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt index 2a289e2..f804e12 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/TopUI.kt @@ -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 + ) } } } \ No newline at end of file