diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt index 6b4e0c4..1729a37 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/LoadingPlaceholder.kt @@ -1,3 +1,25 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + + + package net.newpipe.newplayer.ui import androidx.compose.foundation.layout.Box diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlaybackController.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlaybackController.kt new file mode 100644 index 0000000..07a546a --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlaybackController.kt @@ -0,0 +1,108 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + +package net.newpipe.newplayer.ui.audioplayer; + +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.SkipNext +import androidx.compose.material.icons.filled.SkipPrevious +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import androidx.media3.common.util.UnstableApi +import net.newpipe.newplayer.R +import net.newpipe.newplayer.model.NewPlayerUIState +import net.newpipe.newplayer.model.NewPlayerViewModel +import net.newpipe.newplayer.ui.common.RepeatModeButton +import net.newpipe.newplayer.ui.common.ShuffleModeButton + +@androidx.annotation.OptIn(UnstableApi::class) +@OptIn(UnstableApi::class) +@Composable +fun AudioPlaybackController(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { + Row(verticalAlignment = Alignment.CenterVertically) { + ShuffleModeButton(viewModel = viewModel, uiState = uiState) + + Box(modifier = Modifier.size(80.dp), contentAlignment = Alignment.Center) { + androidx.compose.animation.AnimatedVisibility( + uiState.currentPlaylistItemIndex != 0, + enter = fadeIn(animationSpec = tween(400)), + exit = fadeOut(animationSpec = tween(400)) + + ) { + IconButton( + onClick = viewModel::toggleShuffle + ) { + Icon( + imageVector = Icons.Filled.SkipPrevious, + contentDescription = stringResource(R.string.widget_description_previous_stream) + ) + } + } + } + + Button( + modifier = Modifier.size(80.dp), + onClick = if (uiState.playing) viewModel::pause else viewModel::play, + shape = CircleShape + ) { + Icon( + imageVector = if (uiState.playing) Icons.Filled.Pause else Icons.Filled.PlayArrow, + contentDescription = stringResource( + if (uiState.playing) R.string.widget_description_pause + else R.string.widget_description_play + ) + ) + } + + Box(modifier = Modifier.size(80.dp), contentAlignment = Alignment.Center) { + androidx.compose.animation.AnimatedVisibility( + uiState.currentPlaylistItemIndex < uiState.playList.size - 1, + enter = fadeIn(animationSpec = tween(400)), + exit = fadeOut(animationSpec = tween(400)) + ) { + IconButton( + onClick = viewModel::toggleShuffle + ) { + Icon( + imageVector = Icons.Filled.SkipNext, + contentDescription = stringResource(R.string.widget_description_next_stream) + ) + } + } + } + + RepeatModeButton(viewModel = viewModel, uiState = uiState) + } +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt new file mode 100644 index 0000000..2276ef1 --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerTopBar.kt @@ -0,0 +1,35 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + + + +package net.newpipe.newplayer.ui.audioplayer + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun AudioPlayerTopBar(modifier: Modifier = Modifier) { + TopAppBar(modifier = modifier, + title = { }) +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt index c645e10..97a6c76 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/audioplayer/AudioPlayerUI.kt @@ -1,27 +1,126 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + + package net.newpipe.newplayer.ui.audioplayer import androidx.annotation.OptIn +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Pause +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.material.icons.filled.Shuffle +import androidx.compose.material.icons.filled.ShuffleOn +import androidx.compose.material.icons.filled.SkipNext +import androidx.compose.material.icons.filled.SkipPrevious +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.em import androidx.media3.common.util.UnstableApi +import net.newpipe.newplayer.R import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy -import net.newpipe.newplayer.ui.NewPlayerUI -import net.newpipe.newplayer.ui.theme.VideoPlayerTheme +import net.newpipe.newplayer.ui.common.NewPlayerSeeker +import net.newpipe.newplayer.ui.common.RepeatModeButton +import net.newpipe.newplayer.ui.common.ShuffleModeButton +import net.newpipe.newplayer.utils.Thumbnail +import net.newpipe.newplayer.utils.getInsets @OptIn(UnstableApi::class) @Composable -fun AudioPlayerUI(viewModel:NewPlayerViewModel, uiState: NewPlayerUIState) { +fun AudioPlayerUI(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { + val insets = getInsets() + Scaffold(modifier = Modifier + .fillMaxSize() + .windowInsetsPadding(insets), + topBar = { AudioPlayerTopBar() }) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(20.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Card( + elevation = CardDefaults.cardElevation(defaultElevation = 4.dp), + ) { + Thumbnail( + thumbnail = uiState.currentlyPlaying?.mediaMetadata?.artworkUri, + contentDescription = stringResource( + id = R.string.stream_thumbnail + ), + ) + } + Text( + text = uiState.currentlyPlaying?.mediaMetadata?.title.toString(), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + fontSize = 6.em + ) + Text( + text = uiState.currentlyPlaying?.mediaMetadata?.artist.toString(), + overflow = TextOverflow.Ellipsis, + maxLines = 1, + fontSize = 4.em + ) + NewPlayerSeeker(viewModel = viewModel, uiState = uiState) + + AudioPlaybackController(viewModel = viewModel, uiState = uiState) + } + } + } } @OptIn(UnstableApi::class) -@Preview(device = "spec:width=1080px,height=700px,dpi=440,orientation=landscape") +@Preview(device = "id:pixel_6") @Composable fun AudioPlayerUIPreviewEmbedded() { - VideoPlayerTheme { - AudioPlayerUI(viewModel = NewPlayerViewModelDummy(), uiState = NewPlayerUIState.DUMMY) - } +// VideoPlayerTheme { + AudioPlayerUI(viewModel = NewPlayerViewModelDummy(), uiState = NewPlayerUIState.DUMMY) +// } } \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/common/NewPlayerSeeker.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/common/NewPlayerSeeker.kt new file mode 100644 index 0000000..ef45cb7 --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/common/NewPlayerSeeker.kt @@ -0,0 +1,90 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + + + +package net.newpipe.newplayer.ui.common + +import android.util.Log +import androidx.annotation.OptIn +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.media3.common.util.UnstableApi +import net.newpipe.newplayer.Chapter +import net.newpipe.newplayer.model.NewPlayerUIState +import net.newpipe.newplayer.model.NewPlayerViewModel +import net.newpipe.newplayer.ui.seeker.ChapterSegment +import net.newpipe.newplayer.ui.seeker.DefaultSeekerColor +import net.newpipe.newplayer.ui.seeker.Seeker +import net.newpipe.newplayer.ui.seeker.SeekerColors + +private const val TAG = "NewPlayerSeeker" + +@OptIn(UnstableApi::class) +@Composable +fun NewPlayerSeeker( + modifier: Modifier = Modifier, + viewModel: NewPlayerViewModel, + uiState: NewPlayerUIState +) { + Seeker( + modifier = modifier, + value = uiState.seekerPosition, + onValueChange = viewModel::seekPositionChanged, + onValueChangeFinished = viewModel::seekingFinished, + readAheadValue = uiState.bufferedPercentage, + colors = customizedSeekerColors(), + chapterSegments = getSeekerSegmentsFromChapters(uiState.chapters, uiState.durationInMs) + ) +} + +@Composable +private fun customizedSeekerColors(): SeekerColors { + val colors = DefaultSeekerColor( + progressColor = MaterialTheme.colorScheme.primary, + thumbColor = MaterialTheme.colorScheme.primary, + trackColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.7f), + readAheadColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), + disabledProgressColor = MaterialTheme.colorScheme.primary, + disabledThumbColor = MaterialTheme.colorScheme.primary, + disabledTrackColor = MaterialTheme.colorScheme.primary + ) + return colors +} + +private fun getSeekerSegmentsFromChapters(chapters: List, duration: Long) = + chapters + .filter { chapter -> + if (chapter.chapterStartInMs in 1.. + val markPosition = chapter.chapterStartInMs.toFloat() / duration.toFloat() + ChapterSegment(name = chapter.chapterTitle ?: "", start = markPosition) + } + diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/common/PlaylistControllButtons.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/common/PlaylistControllButtons.kt new file mode 100644 index 0000000..b1347b6 --- /dev/null +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/common/PlaylistControllButtons.kt @@ -0,0 +1,84 @@ +/* NewPlayer + * + * @author Christian Schabesberger + * + * Copyright (C) NewPipe e.V. 2024 + * + * NewPlayer is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * NewPlayer is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with NewPlayer. If not, see . + */ + + +package net.newpipe.newplayer.ui.common + +import androidx.annotation.OptIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Repeat +import androidx.compose.material.icons.filled.RepeatOn +import androidx.compose.material.icons.filled.RepeatOneOn +import androidx.compose.material.icons.filled.Shuffle +import androidx.compose.material.icons.filled.ShuffleOn +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.media3.common.util.UnstableApi +import net.newpipe.newplayer.R +import net.newpipe.newplayer.RepeatMode +import net.newpipe.newplayer.model.NewPlayerUIState +import net.newpipe.newplayer.model.NewPlayerViewModel + +@OptIn(UnstableApi::class) +@Composable +fun RepeatModeButton(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { + IconButton( + onClick = viewModel::cycleRepeatMode + ) { + when (uiState.repeatMode) { + RepeatMode.DO_NOT_REPEAT -> Icon( + imageVector = Icons.Filled.Repeat, + contentDescription = stringResource(R.string.repeat_mode_no_repeat) + ) + + RepeatMode.REPEAT_ALL -> Icon( + imageVector = Icons.Filled.RepeatOn, + contentDescription = stringResource(R.string.repeat_mode_repeat_all) + ) + + RepeatMode.REPEAT_ONE -> Icon( + imageVector = Icons.Filled.RepeatOneOn, + contentDescription = stringResource(R.string.repeat_mode_repeat_all) + ) + } + } +} + +@OptIn(UnstableApi::class) +@Composable +fun ShuffleModeButton(viewModel: NewPlayerViewModel, uiState: NewPlayerUIState) { + IconButton( + onClick = viewModel::toggleShuffle + ) { + if (uiState.shuffleEnabled) { + Icon( + imageVector = Icons.Filled.ShuffleOn, + contentDescription = stringResource(R.string.shuffle_off) + ) + } else { + Icon( + imageVector = Icons.Filled.Shuffle, + contentDescription = stringResource(R.string.shuffle_on) + ) + } + } +} \ No newline at end of file diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt index 0d83035..eda9589 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectTopBar.kt @@ -27,8 +27,6 @@ import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Repeat import androidx.compose.material.icons.filled.RepeatOn import androidx.compose.material.icons.filled.RepeatOneOn -import androidx.compose.material.icons.filled.Shuffle -import androidx.compose.material.icons.filled.ShuffleOn import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -48,6 +46,8 @@ import net.newpipe.newplayer.RepeatMode import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy +import net.newpipe.newplayer.ui.common.RepeatModeButton +import net.newpipe.newplayer.ui.common.ShuffleModeButton import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.utils.getLocale import net.newpipe.newplayer.utils.getPlaylistDurationInMS @@ -77,42 +77,9 @@ fun StreamSelectTopBar( overflow = TextOverflow.Ellipsis ) }, actions = { - IconButton( - onClick = viewModel::cycleRepeatMode - ) { - when (uiState.repeatMode) { - RepeatMode.DO_NOT_REPEAT -> Icon( - imageVector = Icons.Filled.Repeat, - contentDescription = stringResource(R.string.repeat_mode_no_repeat) - ) + RepeatModeButton(viewModel = viewModel, uiState = uiState) - RepeatMode.REPEAT_ALL -> Icon( - imageVector = Icons.Filled.RepeatOn, - contentDescription = stringResource(R.string.repeat_mode_repeat_all) - ) - - RepeatMode.REPEAT_ONE -> Icon( - imageVector = Icons.Filled.RepeatOneOn, - contentDescription = stringResource(R.string.repeat_mode_repeat_all) - ) - } - } - - IconButton( - onClick = viewModel::toggleShuffle - ) { - if (uiState.shuffleEnabled) { - Icon( - imageVector = Icons.Filled.ShuffleOn, - contentDescription = stringResource(R.string.shuffle_off) - ) - } else { - Icon( - imageVector = Icons.Filled.Shuffle, - contentDescription = stringResource(R.string.shuffle_on) - ) - } - } + ShuffleModeButton(viewModel = viewModel, uiState = uiState) IconButton( onClick = viewModel::onStorePlaylist diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt index 8af57cb..b18a0b4 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/streamselect/StreamSelectUI.kt @@ -164,6 +164,7 @@ fun ReorderableStreamItemsList( } } +@OptIn(UnstableApi::class) @Preview(device = "id:pixel_5") @Composable fun VideoPlayerChannelSelectUIPreview() { diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt index f5f33f9..f10e2ac 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/BottomUI.kt @@ -21,7 +21,7 @@ package net.newpipe.newplayer.ui.videoplayer.controller import android.app.Activity -import android.util.Log +import androidx.annotation.OptIn import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons @@ -29,7 +29,6 @@ import androidx.compose.material.icons.filled.Fullscreen import androidx.compose.material.icons.filled.FullscreenExit import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -39,17 +38,14 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import net.newpipe.newplayer.Chapter +import androidx.media3.common.util.UnstableApi import net.newpipe.newplayer.R import net.newpipe.newplayer.model.EmbeddedUiConfig import net.newpipe.newplayer.model.UIModeState import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy -import net.newpipe.newplayer.ui.seeker.ChapterSegment -import net.newpipe.newplayer.ui.seeker.DefaultSeekerColor -import net.newpipe.newplayer.ui.seeker.Seeker -import net.newpipe.newplayer.ui.seeker.SeekerColors +import net.newpipe.newplayer.ui.common.NewPlayerSeeker import net.newpipe.newplayer.ui.theme.VideoPlayerTheme import net.newpipe.newplayer.utils.getEmbeddedUiConfig import net.newpipe.newplayer.utils.getLocale @@ -58,6 +54,7 @@ import net.newpipe.newplayer.utils.getTimeStringFromMs private const val TAG = "BottomUI" +@OptIn(UnstableApi::class) @Composable fun BottomUI( modifier: Modifier, viewModel: NewPlayerViewModel, uiState: NewPlayerUIState @@ -69,15 +66,8 @@ fun BottomUI( ) { val locale = getLocale()!! Text(getTimeStringFromMs(uiState.playbackPositionInMs, getLocale() ?: locale)) - Seeker( - Modifier.weight(1F), - value = uiState.seekerPosition, - onValueChange = viewModel::seekPositionChanged, - onValueChangeFinished = viewModel::seekingFinished, - readAheadValue = uiState.bufferedPercentage, - colors = customizedSeekerColors(), - chapterSegments = getSeekerSegmentsFromChapters(uiState.chapters, uiState.durationInMs) - ) + + NewPlayerSeeker(modifier = Modifier.weight(1F), viewModel = viewModel, uiState = uiState) Text(getTimeStringFromMs(uiState.durationInMs, getLocale() ?: locale)) @@ -104,43 +94,12 @@ fun BottomUI( } -@Composable -private fun customizedSeekerColors(): SeekerColors { - val colors = DefaultSeekerColor( - progressColor = MaterialTheme.colorScheme.primary, - thumbColor = MaterialTheme.colorScheme.primary, - trackColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.7f), - readAheadColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.5f), - disabledProgressColor = MaterialTheme.colorScheme.primary, - disabledThumbColor = MaterialTheme.colorScheme.primary, - disabledTrackColor = MaterialTheme.colorScheme.primary - ) - return colors -} - -private fun getSeekerSegmentsFromChapters(chapters: List, duration: Long) = - chapters - .filter { chapter -> - if (chapter.chapterStartInMs in 1.. - val markPosition = chapter.chapterStartInMs.toFloat() / duration.toFloat() - ChapterSegment(name = chapter.chapterTitle ?: "", start = markPosition) - } - /////////////////////////////////////////////////////////////////// // Preview /////////////////////////////////////////////////////////////////// +@OptIn(UnstableApi::class) @Preview(device = "spec:width=1080px,height=600px,dpi=440,orientation=landscape") @Composable fun VideoPlayerControllerBottomUIPreview() { diff --git a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/CenterUI.kt b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/CenterUI.kt index ac7eabe..805423f 100644 --- a/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/CenterUI.kt +++ b/new-player/src/main/java/net/newpipe/newplayer/ui/videoplayer/controller/CenterUI.kt @@ -20,6 +20,7 @@ package net.newpipe.newplayer.ui.videoplayer.controller +import androidx.annotation.OptIn import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut @@ -46,12 +47,14 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.media3.common.util.UnstableApi import net.newpipe.newplayer.R import net.newpipe.newplayer.model.NewPlayerUIState import net.newpipe.newplayer.model.NewPlayerViewModel import net.newpipe.newplayer.model.NewPlayerViewModelDummy import net.newpipe.newplayer.ui.theme.VideoPlayerTheme +@OptIn(UnstableApi::class) @Composable fun CenterUI( modifier: Modifier = Modifier, @@ -71,7 +74,7 @@ fun CenterUI( exit = fadeOut(animationSpec = tween(400)) ) { - CenterControllButton( + CenterControlButton( buttonModifier = Modifier.fillMaxSize(), iconModifier = Modifier.size(40.dp), icon = Icons.Filled.SkipPrevious, @@ -81,7 +84,7 @@ fun CenterUI( } } - CenterControllButton( + CenterControlButton( buttonModifier = Modifier.size(80.dp), iconModifier = Modifier.size(60.dp), icon = if (uiState.playing) Icons.Filled.Pause else Icons.Filled.PlayArrow, @@ -97,7 +100,7 @@ fun CenterUI( enter = fadeIn(animationSpec = tween(400)), exit = fadeOut(animationSpec = tween(400)) ) { - CenterControllButton( + CenterControlButton( buttonModifier = Modifier.fillMaxSize(), iconModifier = Modifier.size(40.dp), icon = Icons.Filled.SkipNext, @@ -109,8 +112,9 @@ fun CenterUI( } } + @Composable -private fun CenterControllButton( +private fun CenterControlButton( buttonModifier: Modifier, iconModifier: Modifier, icon: ImageVector, diff --git a/new-player/src/main/res/values/strings.xml b/new-player/src/main/res/values/strings.xml index 245b6ac..8b9454c 100644 --- a/new-player/src/main/res/values/strings.xml +++ b/new-player/src/main/res/values/strings.xml @@ -53,4 +53,5 @@ Shuffle playlist disabled Save current playlist Close + Stream Thumbnail \ No newline at end of file