add chapter select preview
This commit is contained in:
parent
cb122306c6
commit
22d7bcf552
|
@ -24,7 +24,7 @@ import android.net.Uri
|
||||||
import androidx.media3.common.PlaybackException
|
import androidx.media3.common.PlaybackException
|
||||||
import net.newpipe.newplayer.utils.Thumbnail
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
|
||||||
data class Chapter(val chapterStartInMs: Long, val chapterTitle: String?)
|
data class Chapter(val chapterStartInMs: Long, val chapterTitle: String?, val thumbnail: Thumbnail?)
|
||||||
|
|
||||||
data class MetaInfo(
|
data class MetaInfo(
|
||||||
val title: String,
|
val title: String,
|
||||||
|
@ -46,7 +46,6 @@ interface MediaRepository {
|
||||||
|
|
||||||
suspend fun getPreviewThumbnails(item: String): HashMap<Long, Thumbnail>?
|
suspend fun getPreviewThumbnails(item: String): HashMap<Long, Thumbnail>?
|
||||||
suspend fun getChapters(item: String): List<Chapter>
|
suspend fun getChapters(item: String): List<Chapter>
|
||||||
suspend fun getChapterThumbnail(item: String, chapter: Long): Thumbnail?
|
|
||||||
|
|
||||||
suspend fun getTimestampLink(item: String, timestampInSeconds: Long): String
|
suspend fun getTimestampLink(item: String, timestampInSeconds: Long): String
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import android.os.Bundle
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import net.newpipe.newplayer.Chapter
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
import net.newpipe.newplayer.ui.ContentScale
|
import net.newpipe.newplayer.ui.ContentScale
|
||||||
import net.newpipe.newplayer.utils.Thumbnail
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
@ -58,4 +59,6 @@ interface VideoPlayerViewModel {
|
||||||
fun volumeChange(changeRate: Float)
|
fun volumeChange(changeRate: Float)
|
||||||
fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig)
|
fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig)
|
||||||
fun closeStreamSelection()
|
fun closeStreamSelection()
|
||||||
|
fun chapterSelected(chapter: Chapter)
|
||||||
|
fun streamSelected(streamId: Int)
|
||||||
}
|
}
|
|
@ -43,6 +43,7 @@ import javax.inject.Inject
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import net.newpipe.newplayer.Chapter
|
||||||
import net.newpipe.newplayer.utils.VideoSize
|
import net.newpipe.newplayer.utils.VideoSize
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromItemList
|
import net.newpipe.newplayer.playerInternals.getPlaylistItemsFromItemList
|
||||||
|
@ -399,6 +400,14 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
||||||
updateUiMode(UIModeState.FULLSCREEN_VIDEO)
|
updateUiMode(UIModeState.FULLSCREEN_VIDEO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun chapterSelected(chapter: Chapter) {
|
||||||
|
println("gurken chapter selectd: $chapter")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun streamSelected(streamId: Int) {
|
||||||
|
println("stream selected: $streamId")
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateUiMode(newState: UIModeState) {
|
private fun updateUiMode(newState: UIModeState) {
|
||||||
val newPlayMode = newState.toPlayMode()
|
val newPlayMode = newState.toPlayMode()
|
||||||
val currentPlayMode = mutableUiState.value.uiMode.toPlayMode()
|
val currentPlayMode = mutableUiState.value.uiMode.toPlayMode()
|
||||||
|
|
|
@ -6,6 +6,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import net.newpipe.newplayer.Chapter
|
||||||
import net.newpipe.newplayer.NewPlayer
|
import net.newpipe.newplayer.NewPlayer
|
||||||
import net.newpipe.newplayer.ui.ContentScale
|
import net.newpipe.newplayer.ui.ContentScale
|
||||||
|
|
||||||
|
@ -83,6 +84,14 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
|
||||||
println("dummy impl")
|
println("dummy impl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun chapterSelected(chapter: Chapter) {
|
||||||
|
println("dummp impl chapter selected: $chapter")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun streamSelected(streamId: Int) {
|
||||||
|
println("dummy impl stream selected: $streamId")
|
||||||
|
}
|
||||||
|
|
||||||
override fun pause() {
|
override fun pause() {
|
||||||
println("dummy pause")
|
println("dummy pause")
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,29 +21,32 @@
|
||||||
package net.newpipe.newplayer.ui.videoplayer
|
package net.newpipe.newplayer.ui.videoplayer
|
||||||
|
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
import android.view.Surface
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Close
|
import androidx.compose.material.icons.filled.Close
|
||||||
import androidx.compose.material.icons.filled.DragHandle
|
import androidx.compose.material.icons.filled.DragHandle
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults.topAppBarColors
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.ExperimentalComposeUiApi
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
|
@ -65,6 +68,7 @@ import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||||
import net.newpipe.newplayer.utils.getLocale
|
import net.newpipe.newplayer.utils.getLocale
|
||||||
import net.newpipe.newplayer.utils.getTimeStringFromMs
|
import net.newpipe.newplayer.utils.getTimeStringFromMs
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
|
import net.newpipe.newplayer.Chapter
|
||||||
import net.newpipe.newplayer.utils.BitmapThumbnail
|
import net.newpipe.newplayer.utils.BitmapThumbnail
|
||||||
import net.newpipe.newplayer.utils.OnlineThumbnail
|
import net.newpipe.newplayer.utils.OnlineThumbnail
|
||||||
import net.newpipe.newplayer.utils.Thumbnail
|
import net.newpipe.newplayer.utils.Thumbnail
|
||||||
|
@ -79,12 +83,14 @@ fun StreamSelectUI(
|
||||||
) {
|
) {
|
||||||
val insets = getInsets()
|
val insets = getInsets()
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.fillMaxSize()
|
|
||||||
.windowInsetsPadding(insets),
|
|
||||||
color = CONTROLLER_UI_BACKGROUND_COLOR
|
color = CONTROLLER_UI_BACKGROUND_COLOR
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.windowInsetsPadding(insets),
|
||||||
|
containerColor = Color.Transparent,
|
||||||
topBar = {
|
topBar = {
|
||||||
if (isChapterSelect) {
|
if (isChapterSelect) {
|
||||||
ChapterSelectTopBar(onClose = {
|
ChapterSelectTopBar(onClose = {
|
||||||
|
@ -95,28 +101,41 @@ fun StreamSelectUI(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
Surface(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.fillMaxSize(), color = Color.Red
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
|
if (isChapterSelect) {
|
||||||
|
items(uiState.chapters.size) { chapterIndex ->
|
||||||
|
val chapter = uiState.chapters[chapterIndex]
|
||||||
|
ChapterItem(
|
||||||
|
id = chapterIndex,
|
||||||
|
chapterTitle = chapter.chapterTitle ?: "",
|
||||||
|
chapterStartInMs = chapter.chapterStartInMs,
|
||||||
|
thumbnail = chapter.thumbnail,
|
||||||
|
onClicked = {
|
||||||
|
viewModel.chapterSelected(chapter)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun ChapterSelectTopBar(modifier: Modifier = Modifier, onClose: () -> Unit) {
|
private fun ChapterSelectTopBar(modifier: Modifier = Modifier, onClose: () -> Unit) {
|
||||||
Row(
|
TopAppBar(modifier = modifier,
|
||||||
modifier = modifier,
|
colors = topAppBarColors(containerColor = Color.Transparent),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
title = {
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
|
||||||
//Text(stringResource(R.string.chapter))
|
|
||||||
Text("Chapter TODO")
|
Text("Chapter TODO")
|
||||||
Spacer(modifier = Modifier.weight(1F))
|
//Text(stringResource(R.string.chapter))
|
||||||
|
}, actions = {
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onClose
|
onClick = onClose
|
||||||
) {
|
) {
|
||||||
|
@ -125,7 +144,7 @@ private fun ChapterSelectTopBar(modifier: Modifier = Modifier, onClose: () -> Un
|
||||||
contentDescription = stringResource(R.string.close_chapter_selection)
|
contentDescription = stringResource(R.string.close_chapter_selection)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -150,7 +169,7 @@ private fun ChapterItem(
|
||||||
top = 4.dp,
|
top = 4.dp,
|
||||||
bottom = 4.dp,
|
bottom = 4.dp,
|
||||||
end = 4.dp
|
end = 4.dp
|
||||||
)
|
).height(80.dp)
|
||||||
.clickable { onClicked(id) }
|
.clickable { onClicked(id) }
|
||||||
) {
|
) {
|
||||||
val contentDescription = stringResource(R.string.chapter)
|
val contentDescription = stringResource(R.string.chapter)
|
||||||
|
@ -348,11 +367,29 @@ fun ChapterTopBarPreview() {
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoPlayerStreamSelectUIPreview() {
|
fun VideoPlayerStreamSelectUIPreview() {
|
||||||
VideoPlayerTheme {
|
VideoPlayerTheme {
|
||||||
Surface(modifier = Modifier.fillMaxSize(), color = Color.Green) {
|
Surface(modifier = Modifier.fillMaxSize(), color = Color.Red) {
|
||||||
StreamSelectUI(
|
StreamSelectUI(
|
||||||
isChapterSelect = true,
|
isChapterSelect = true,
|
||||||
viewModel = VideoPlayerViewModelDummy(),
|
viewModel = VideoPlayerViewModelDummy(),
|
||||||
uiState = VideoPlayerUIState.DEFAULT
|
uiState = VideoPlayerUIState.DEFAULT.copy(
|
||||||
|
chapters = arrayListOf(
|
||||||
|
Chapter(
|
||||||
|
chapterStartInMs = 5000,
|
||||||
|
chapterTitle = "First Chapter",
|
||||||
|
thumbnail = null
|
||||||
|
),
|
||||||
|
Chapter(
|
||||||
|
chapterStartInMs = 10000,
|
||||||
|
chapterTitle = "Second Chapter",
|
||||||
|
thumbnail = null
|
||||||
|
),
|
||||||
|
Chapter(
|
||||||
|
chapterStartInMs = 20000,
|
||||||
|
chapterTitle = "Third Chapter",
|
||||||
|
thumbnail = null
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,12 +30,14 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
||||||
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_6502_thumbnail)),
|
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_6502_thumbnail)),
|
||||||
lengthInS = context.resources.getInteger(R.integer.ccc_6502_length)
|
lengthInS = context.resources.getInteger(R.integer.ccc_6502_length)
|
||||||
)
|
)
|
||||||
|
|
||||||
"imu" -> MetaInfo(
|
"imu" -> MetaInfo(
|
||||||
title = context.getString(R.string.ccc_imu_title),
|
title = context.getString(R.string.ccc_imu_title),
|
||||||
channelName = context.getString(R.string.ccc_imu_channel),
|
channelName = context.getString(R.string.ccc_imu_channel),
|
||||||
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_imu_thumbnail)),
|
thumbnail = OnlineThumbnail(context.getString(R.string.ccc_imu_thumbnail)),
|
||||||
lengthInS = context.resources.getInteger(R.integer.ccc_imu_length)
|
lengthInS = context.resources.getInteger(R.integer.ccc_imu_length)
|
||||||
)
|
)
|
||||||
|
|
||||||
"portrait" -> MetaInfo(
|
"portrait" -> MetaInfo(
|
||||||
title = context.getString(R.string.portrait_title),
|
title = context.getString(R.string.portrait_title),
|
||||||
channelName = context.getString(R.string.portrait_channel),
|
channelName = context.getString(R.string.portrait_channel),
|
||||||
|
@ -113,30 +115,30 @@ class TestMediaRepository(val context: Context) : MediaRepository {
|
||||||
override suspend fun getChapters(item: String) =
|
override suspend fun getChapters(item: String) =
|
||||||
when (item) {
|
when (item) {
|
||||||
"6502" -> context.resources.getIntArray(R.array.ccc_6502_chapters)
|
"6502" -> context.resources.getIntArray(R.array.ccc_6502_chapters)
|
||||||
"imu" -> TODO()
|
"imu" -> context.resources.getIntArray(R.array.ccc_imu_chapters)
|
||||||
else -> intArrayOf()
|
else -> intArrayOf()
|
||||||
}.map {
|
}.map {
|
||||||
Chapter(it.toLong(), "Dummy Chapter at timestamp $it")
|
Chapter(
|
||||||
}
|
it.toLong(), chapterTitle = "Dummy Chapter at timestamp $it",
|
||||||
|
thumbnail = when (item) {
|
||||||
override suspend fun getChapterThumbnail(item: String, chapter: Long) =
|
|
||||||
when (item) {
|
|
||||||
"6502" -> OnlineThumbnail(
|
"6502" -> OnlineThumbnail(
|
||||||
String.format(
|
String.format(
|
||||||
context.getString(R.string.ccc_6502_preview_thumbnails),
|
context.getString(R.string.ccc_6502_preview_thumbnails),
|
||||||
chapter / (10 * 1000)
|
it / (10 * 1000)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
"imu" -> OnlineThumbnail(
|
"imu" -> OnlineThumbnail(
|
||||||
String.format(
|
String.format(
|
||||||
context.getString(R.string.ccc_imu_preview_thumbnails),
|
context.getString(R.string.ccc_imu_preview_thumbnails),
|
||||||
chapter / (10 * 1000)
|
it / (10 * 1000)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
override suspend fun getTimestampLink(item: String, timestampInSeconds: Long) =
|
||||||
when (item) {
|
when (item) {
|
||||||
|
|
Loading…
Reference in New Issue