[feat] Video Player lock

This commit is contained in:
acite
2025-09-23 02:53:42 +08:00
parent d918508c16
commit 49751c55d9
2 changed files with 367 additions and 163 deletions

View File

@@ -60,13 +60,18 @@ import androidx.compose.material.icons.filled.Brightness4
import androidx.compose.material.icons.filled.FastForward import androidx.compose.material.icons.filled.FastForward
import androidx.compose.material.icons.filled.Fullscreen import androidx.compose.material.icons.filled.Fullscreen
import androidx.compose.material.icons.filled.Info import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.LockOpen
import androidx.compose.material.icons.filled.Share import androidx.compose.material.icons.filled.Share
import androidx.compose.material.icons.filled.Star import androidx.compose.material.icons.filled.Star
import androidx.compose.material.icons.filled.ThumbDown import androidx.compose.material.icons.filled.ThumbDown
import androidx.compose.material.icons.filled.ThumbUp import androidx.compose.material.icons.filled.ThumbUp
import androidx.compose.material.icons.filled.VolumeUp import androidx.compose.material.icons.filled.VolumeUp
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card import androidx.compose.material3.Card
import androidx.compose.material3.CardColors import androidx.compose.material3.CardColors
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DividerDefaults import androidx.compose.material3.DividerDefaults
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.HorizontalDivider
@@ -92,6 +97,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.input.pointer.pointerInteropFilter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
@@ -230,7 +236,9 @@ fun SubtitleOverlay(
cornerRadius: Dp = 6.dp, cornerRadius: Dp = 6.dp,
textColor: Color = Color.White textColor: Color = Color.White
) { ) {
val raw = if (cues.isEmpty()) "" else cues.joinToString(separator = "\n") { it.text?.toString() ?: "" }.trim() val raw = if (cues.isEmpty()) "" else cues.joinToString(separator = "\n") {
it.text?.toString() ?: ""
}.trim()
if (raw.isEmpty()) return if (raw.isEmpty()) return
val textAlign = when (cues.firstOrNull()?.textAlignment) { val textAlign = when (cues.firstOrNull()?.textAlignment) {
@@ -281,15 +289,45 @@ fun VideoPlayer(
videoId: String, videoId: String,
navController: NavHostController navController: NavHostController
) { ) {
val colorScheme = MaterialTheme.colorScheme
videoPlayerViewModel.init(videoId) videoPlayerViewModel.init(videoId)
if(videoPlayerViewModel.startPlaying) if (videoPlayerViewModel.startPlaying) {
{
if (isLandscape()) { if (isLandscape()) {
Box {
VideoPlayerLandscape(videoPlayerViewModel) VideoPlayerLandscape(videoPlayerViewModel)
AnimatedVisibility(
visible = videoPlayerViewModel.locked || videoPlayerViewModel.planeVisibility,
enter = fadeIn(
initialAlpha = 0f,
),
exit = fadeOut(
targetAlpha = 0f
),
modifier = Modifier.align(Alignment.CenterEnd)
) {
Card(
modifier = Modifier.padding(4.dp),
colors = CardDefaults.cardColors(
containerColor = colorScheme.primary.copy(
if (videoPlayerViewModel.locked) 0.2f else 1f
)
),
onClick = {
videoPlayerViewModel.locked = !videoPlayerViewModel.locked
}) {
Icon(
imageVector = if (videoPlayerViewModel.locked) Icons.Default.LockOpen else Icons.Default.Lock,
contentDescription = "Lock",
tint = Color.White.copy(if (videoPlayerViewModel.locked) 0.2f else 1f),
modifier = Modifier
.size(36.dp)
.padding(6.dp)
)
} }
else }
{ }
} else {
VideoPlayerPortal(videoPlayerViewModel, navController) VideoPlayerPortal(videoPlayerViewModel, navController)
} }
} }
@@ -297,15 +335,18 @@ fun VideoPlayer(
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
@Composable @Composable
fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel, cover: Float) fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel, cover: Float) {
{
val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!; val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!;
val context = LocalContext.current val context = LocalContext.current
val activity = context as? Activity val activity = context as? Activity
val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager } val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) } val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) }
var volFactor by remember { mutableFloatStateOf(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat()) } var volFactor by remember {
mutableFloatStateOf(
audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat()
)
}
fun setVolume(value: Int) { fun setVolume(value: Int) {
audioManager.setStreamVolume( audioManager.setStreamVolume(
@@ -335,8 +376,8 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
.pointerInput(Unit) { .pointerInput(Unit) {
detectDragGestures( detectDragGestures(
onDragStart = { offset -> onDragStart = { offset ->
if(offset.x < size.width / 2) if (videoPlayerViewModel.locked) return@detectDragGestures
{ if (offset.x < size.width / 2) {
videoPlayerViewModel.draggingPurpose = -1; videoPlayerViewModel.draggingPurpose = -1;
} else { } else {
videoPlayerViewModel.draggingPurpose = -2; videoPlayerViewModel.draggingPurpose = -2;
@@ -349,31 +390,36 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
videoPlayerViewModel.draggingPurpose = -1; videoPlayerViewModel.draggingPurpose = -1;
}, },
onDrag = { change, dragAmount -> onDrag = { change, dragAmount ->
if (videoPlayerViewModel.locked) return@detectDragGestures
if (abs(dragAmount.x) > abs(dragAmount.y) && if (abs(dragAmount.x) > abs(dragAmount.y) &&
(videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2)) (videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2)
{ ) {
videoPlayerViewModel.draggingPurpose = 0 videoPlayerViewModel.draggingPurpose = 0
videoPlayerViewModel.planeVisibility = true videoPlayerViewModel.planeVisibility = true
exoPlayer.pause() exoPlayer.pause()
} } else if (videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose =
else if(videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose = 1 1
else if(videoPlayerViewModel.draggingPurpose == -2) videoPlayerViewModel.draggingPurpose = 2 else if (videoPlayerViewModel.draggingPurpose == -2) videoPlayerViewModel.draggingPurpose =
2
if(videoPlayerViewModel.draggingPurpose == 0) if (videoPlayerViewModel.draggingPurpose == 0) {
{
exoPlayer.seekTo((exoPlayer.currentPosition + dragAmount.x * 200.0f).toLong()) exoPlayer.seekTo((exoPlayer.currentPosition + dragAmount.x * 200.0f).toLong())
videoPlayerViewModel.playProcess = exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat() videoPlayerViewModel.playProcess =
}else if(videoPlayerViewModel.draggingPurpose == 2) exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat()
{ } else if (videoPlayerViewModel.draggingPurpose == 2) {
val cu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) val cu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
volFactor = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat() volFactor = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
.toFloat() / maxVolume.toFloat()
if (dragAmount.y < 0) if (dragAmount.y < 0)
setVolume(cu + 1); setVolume(cu + 1);
else if (dragAmount.y > 0) else if (dragAmount.y > 0)
setVolume(cu - 1); setVolume(cu - 1);
}else if(videoPlayerViewModel.draggingPurpose == 1) } else if (videoPlayerViewModel.draggingPurpose == 1) {
{ videoPlayerViewModel.brit =
videoPlayerViewModel.brit = (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn(0f, 1f); (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn(
0f,
1f
);
activity?.window?.attributes = activity.window.attributes.apply { activity?.window?.attributes = activity.window.attributes.apply {
screenBrightness = videoPlayerViewModel.brit.coerceIn(0f, 1f) screenBrightness = videoPlayerViewModel.brit.coerceIn(0f, 1f)
@@ -387,14 +433,17 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onDoubleTap = { onDoubleTap = {
if (videoPlayerViewModel.locked) return@detectTapGestures
videoPlayerViewModel.isPlaying = !videoPlayerViewModel.isPlaying videoPlayerViewModel.isPlaying = !videoPlayerViewModel.isPlaying
if (videoPlayerViewModel.isPlaying) exoPlayer.play() else exoPlayer.pause() if (videoPlayerViewModel.isPlaying) exoPlayer.play() else exoPlayer.pause()
}, },
onTap = { onTap = {
if (videoPlayerViewModel.locked) return@detectTapGestures
videoPlayerViewModel.planeVisibility = videoPlayerViewModel.planeVisibility =
!videoPlayerViewModel.planeVisibility !videoPlayerViewModel.planeVisibility
}, },
onLongPress = { onLongPress = {
if (videoPlayerViewModel.locked) return@detectTapGestures
videoPlayerViewModel.isLongPressing = true videoPlayerViewModel.isLongPressing = true
exoPlayer.playbackParameters = exoPlayer.playbackParameters exoPlayer.playbackParameters = exoPlayer.playbackParameters
.withSpeed(3.0f) .withSpeed(3.0f)
@@ -419,19 +468,29 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
) )
{ {
Box(modifier = Modifier.align(Alignment.TopCenter).padding(top = 24.dp).background(Color(0x44000000), RoundedCornerShape(18))) Box(
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 24.dp)
.background(Color(0x44000000), RoundedCornerShape(18))
)
{ {
Row { Row {
Icon( Icon(
imageVector = Icons.Filled.FastForward, imageVector = Icons.Filled.FastForward,
contentDescription = "Fast Forward", contentDescription = "Fast Forward",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(36.dp).padding(4.dp).align(Alignment.CenterVertically) modifier = Modifier
.size(36.dp)
.padding(4.dp)
.align(Alignment.CenterVertically)
) )
Text( Text(
text = "3X Speed...", text = "3X Speed...",
modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically), modifier = Modifier
.padding(4.dp)
.align(Alignment.CenterVertically),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color(0xFFFFFFFF) color = Color(0xFFFFFFFF)
@@ -474,13 +533,17 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
{ {
Row(Modifier.background(Color(0x88000000), RoundedCornerShape(18)).width(200.dp)) Row(Modifier
.background(Color(0x88000000), RoundedCornerShape(18))
.width(200.dp))
{ {
Icon( Icon(
imageVector = Icons.AutoMirrored.Filled.VolumeUp, imageVector = Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = "Vol", contentDescription = "Vol",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(48.dp).padding(8.dp) modifier = Modifier
.size(48.dp)
.padding(8.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) )
BiliMiniSlider( BiliMiniSlider(
@@ -505,13 +568,17 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
{ {
Row(Modifier.background(Color(0x88000000), RoundedCornerShape(18)).width(200.dp)) Row(Modifier
.background(Color(0x88000000), RoundedCornerShape(18))
.width(200.dp))
{ {
Icon( Icon(
imageVector = Icons.Default.Brightness4, imageVector = Icons.Default.Brightness4,
contentDescription = "Brightness", contentDescription = "Brightness",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(48.dp).padding(8.dp) modifier = Modifier
.size(48.dp)
.padding(8.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) )
BiliMiniSlider( BiliMiniSlider(
@@ -526,17 +593,21 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
} }
if (cover > 0.0f) if (cover > 0.0f)
Spacer(Modifier.background(MaterialTheme.colorScheme.primary.copy(cover)).fillMaxSize()) Spacer(Modifier
.background(MaterialTheme.colorScheme.primary.copy(cover))
.fillMaxSize())
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = !videoPlayerViewModel.planeVisibility, visible = (!videoPlayerViewModel.planeVisibility) || videoPlayerViewModel.locked,
enter = fadeIn( enter = fadeIn(
initialAlpha = 0f, initialAlpha = 0f,
), ),
exit = fadeOut( exit = fadeOut(
targetAlpha = 0f targetAlpha = 0f
), ),
modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter) modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
) { ) {
BiliMiniSlider( BiliMiniSlider(
value = videoPlayerViewModel.playProcess, value = videoPlayerViewModel.playProcess,
@@ -548,26 +619,31 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
} }
androidx.compose.animation.AnimatedVisibility( androidx.compose.animation.AnimatedVisibility(
visible = videoPlayerViewModel.planeVisibility, visible = videoPlayerViewModel.planeVisibility && (!videoPlayerViewModel.locked),
enter = fadeIn( enter = fadeIn(
initialAlpha = 0f, initialAlpha = 0f,
), ),
exit = fadeOut( exit = fadeOut(
targetAlpha = 0f targetAlpha = 0f
), ),
modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter).height(42.dp) modifier = Modifier
.fillMaxWidth()
.align(Alignment.BottomCenter)
.height(42.dp)
) )
{ {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.align(Alignment.BottomCenter).background( .align(Alignment.BottomCenter)
.background(
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = listOf( colors = listOf(
Color.Transparent, Color.Transparent,
Color.Black.copy(alpha = 0.4f), Color.Black.copy(alpha = 0.4f),
) )
)), )
),
horizontalArrangement = Arrangement.SpaceBetween, horizontalArrangement = Arrangement.SpaceBetween,
) { ) {
IconButton( IconButton(
@@ -636,8 +712,11 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo
} }
@Composable @Composable
fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: NavHostController) fun VideoPlayerPortal(
{ videoPlayerViewModel: VideoPlayerViewModel,
navController: NavHostController
) {
val colorScheme = MaterialTheme.colorScheme
val configuration = LocalConfiguration.current val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp; val screenHeight = configuration.screenHeightDp.dp;
@@ -684,21 +763,56 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
} }
ToggleFullScreen(false) ToggleFullScreen(false)
Column(Modifier.nestedScroll(nestedScrollConnection).fillMaxHeight()) Column(Modifier
.nestedScroll(nestedScrollConnection)
.fillMaxHeight())
{ {
Box {
PortalCorePlayer( PortalCorePlayer(
Modifier Modifier
.padding(top = 42.dp) .padding(top = 32.dp)
.heightIn(max = playerHeight) .heightIn(max = playerHeight)
.onGloballyPositioned { layoutCoordinates -> .onGloballyPositioned { layoutCoordinates ->
if(!posed && videoPlayerViewModel.renderedFirst) if (!posed && videoPlayerViewModel.renderedFirst) {
{
maxHeight = with(dens) { layoutCoordinates.size.height.toDp() } maxHeight = with(dens) { layoutCoordinates.size.height.toDp() }
playerHeight = maxHeight playerHeight = maxHeight
posed = true posed = true
} }
}, },
videoPlayerViewModel = videoPlayerViewModel, coverAlpha) videoPlayerViewModel = videoPlayerViewModel, coverAlpha
)
androidx.compose.animation.AnimatedVisibility(
visible = videoPlayerViewModel.locked || videoPlayerViewModel.planeVisibility,
enter = fadeIn(
initialAlpha = 0f,
),
exit = fadeOut(
targetAlpha = 0f
),
modifier = Modifier.align(Alignment.CenterEnd)
) {
Card(
modifier = Modifier.padding(4.dp),
colors = CardDefaults.cardColors(
containerColor = colorScheme.primary.copy(
if (videoPlayerViewModel.locked) 0.2f else 1f
)
),
onClick = {
videoPlayerViewModel.locked = !videoPlayerViewModel.locked
}) {
Icon(
imageVector = if (videoPlayerViewModel.locked) Icons.Default.LockOpen else Icons.Default.Lock,
contentDescription = "Lock",
tint = Color.White.copy(if (videoPlayerViewModel.locked) 0.2f else 1f),
modifier = Modifier
.size(36.dp)
.padding(6.dp)
)
}
}
}
Row() Row()
{ {
@@ -725,14 +839,20 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
LazyColumn(state = listState, modifier = Modifier.fillMaxWidth()) { LazyColumn(state = listState, modifier = Modifier.fillMaxWidth()) {
item { item {
Text( Text(
modifier = Modifier.align(Alignment.Start).padding(horizontal = 12.dp).padding(top = 12.dp), modifier = Modifier
.align(Alignment.Start)
.padding(horizontal = 12.dp)
.padding(top = 12.dp),
text = videoPlayerViewModel.video?.video?.name ?: "", text = videoPlayerViewModel.video?.video?.name ?: "",
fontSize = 16.sp, fontSize = 16.sp,
maxLines = 2, maxLines = 2,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
) )
Row(Modifier.align(Alignment.Start).padding(horizontal = 4.dp).alpha(0.5f)) { Row(Modifier
.align(Alignment.Start)
.padding(horizontal = 4.dp)
.alpha(0.5f)) {
Text( Text(
modifier = Modifier.padding(horizontal = 8.dp), modifier = Modifier.padding(horizontal = 8.dp),
text = videoPlayerViewModel.video?.klass ?: "", text = videoPlayerViewModel.video?.klass ?: "",
@@ -752,15 +872,19 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color) HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
SocialPanel(Modifier.align(Alignment.CenterHorizontally).fillMaxWidth(), videoPlayerViewModel = videoPlayerViewModel) SocialPanel(
Modifier
.align(Alignment.CenterHorizontally)
.fillMaxWidth(),
videoPlayerViewModel = videoPlayerViewModel
)
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color) HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
HorizontalGallery(videoPlayerViewModel) HorizontalGallery(videoPlayerViewModel)
HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color) HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color)
for(i in Global.sameClassVideos ?: listOf()) for (i in Global.sameClassVideos ?: listOf()) {
{
if (i.id == videoPlayerViewModel.video?.id) continue if (i.id == videoPlayerViewModel.video?.id) continue
MiniVideoCard( MiniVideoCard(
@@ -772,8 +896,15 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
videoPlayerViewModel._player?.pause() videoPlayerViewModel._player?.pause()
val route = "video_player_route/${"${i.klass}/${i.id}".toHex()}" val route = "video_player_route/${"${i.klass}/${i.id}".toHex()}"
navController.navigate(route) navController.navigate(route)
}, videoPlayerViewModel.imageLoader!!) }, videoPlayerViewModel.imageLoader!!
HorizontalDivider(Modifier.padding(vertical = 8.dp).alpha(0.25f), 1.dp, DividerDefaults.color) )
HorizontalDivider(
Modifier
.padding(vertical = 8.dp)
.alpha(0.25f),
1.dp,
DividerDefaults.color
)
} }
} }
} }
@@ -781,8 +912,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController:
} }
@Composable @Composable
fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) {
{
val colorScheme = MaterialTheme.colorScheme val colorScheme = MaterialTheme.colorScheme
Row( Row(
modifier, modifier,
@@ -792,7 +922,10 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
Column(modifier = Modifier.padding(horizontal = 12.dp)) { Column(modifier = Modifier.padding(horizontal = 12.dp)) {
IconButton( IconButton(
onClick = { }, onClick = { },
modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), modifier = Modifier
.padding(horizontal = 4.dp)
.align(Alignment.CenterHorizontally)
.size(36.dp),
) { ) {
Icon( Icon(
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp),
@@ -807,13 +940,17 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
text = videoPlayerViewModel.thumbUp.toString(), text = videoPlayerViewModel.thumbUp.toString(),
fontSize = 12.sp, fontSize = 12.sp,
maxLines = 1, maxLines = 1,
fontWeight = FontWeight.Bold) fontWeight = FontWeight.Bold
)
} }
Column(modifier = Modifier.padding(horizontal = 12.dp)) { Column(modifier = Modifier.padding(horizontal = 12.dp)) {
IconButton( IconButton(
onClick = { }, onClick = { },
modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), modifier = Modifier
.padding(horizontal = 4.dp)
.align(Alignment.CenterHorizontally)
.size(36.dp),
) { ) {
Icon( Icon(
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp),
@@ -828,13 +965,17 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
text = videoPlayerViewModel.thumbDown.toString(), text = videoPlayerViewModel.thumbDown.toString(),
fontSize = 12.sp, fontSize = 12.sp,
maxLines = 1, maxLines = 1,
fontWeight = FontWeight.Bold) fontWeight = FontWeight.Bold
)
} }
Column(modifier = Modifier.padding(horizontal = 12.dp)) { Column(modifier = Modifier.padding(horizontal = 12.dp)) {
IconButton( IconButton(
onClick = { videoPlayerViewModel.star = !videoPlayerViewModel.star }, onClick = { videoPlayerViewModel.star = !videoPlayerViewModel.star },
modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), modifier = Modifier
.padding(horizontal = 4.dp)
.align(Alignment.CenterHorizontally)
.size(36.dp),
) { ) {
Icon( Icon(
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp),
@@ -848,7 +989,10 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
Column(modifier = Modifier.padding(horizontal = 12.dp)) { Column(modifier = Modifier.padding(horizontal = 12.dp)) {
IconButton( IconButton(
onClick = { }, onClick = { },
modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), modifier = Modifier
.padding(horizontal = 4.dp)
.align(Alignment.CenterHorizontally)
.size(36.dp),
) { ) {
Icon( Icon(
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp),
@@ -862,7 +1006,10 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
Column(modifier = Modifier.padding(horizontal = 12.dp)) { Column(modifier = Modifier.padding(horizontal = 12.dp)) {
IconButton( IconButton(
onClick = { }, onClick = { },
modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), modifier = Modifier
.padding(horizontal = 4.dp)
.align(Alignment.CenterHorizontally)
.size(36.dp),
) { ) {
Icon( Icon(
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp),
@@ -876,10 +1023,11 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel)
} }
@Composable @Composable
fun HorizontalGallery(videoPlayerViewModel: VideoPlayerViewModel) fun HorizontalGallery(videoPlayerViewModel: VideoPlayerViewModel) {
{
LazyRow( LazyRow(
modifier = Modifier.fillMaxWidth().height(120.dp), modifier = Modifier
.fillMaxWidth()
.height(120.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
contentPadding = PaddingValues(horizontal = 24.dp) contentPadding = PaddingValues(horizontal = 24.dp)
) { ) {
@@ -908,15 +1056,18 @@ fun SingleImageItem(img: KeyImage, imageLoader: ImageLoader) {
@androidx.annotation.OptIn(UnstableApi::class) @androidx.annotation.OptIn(UnstableApi::class)
@Composable @Composable
fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) {
{
val context = LocalContext.current val context = LocalContext.current
val activity = context as? Activity val activity = context as? Activity
val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!; val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!;
val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager } val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager }
val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) } val maxVolume = remember { audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) }
var volFactor by remember { mutableFloatStateOf(audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat()) } var volFactor by remember {
mutableFloatStateOf(
audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat()
)
}
fun setVolume(value: Int) { fun setVolume(value: Int) {
audioManager.setStreamVolume( audioManager.setStreamVolume(
@@ -935,7 +1086,8 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
{ {
Box( Box(
modifier = Modifier modifier = Modifier
.background(Color.Black).align(Alignment.Center) .background(Color.Black)
.align(Alignment.Center)
) )
{ {
AndroidView( AndroidView(
@@ -955,8 +1107,8 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
.pointerInput(Unit) { .pointerInput(Unit) {
detectDragGestures( detectDragGestures(
onDragStart = { offset -> onDragStart = { offset ->
if(offset.x < size.width / 2) if (videoPlayerViewModel.locked) return@detectDragGestures
{ if (offset.x < size.width / 2) {
videoPlayerViewModel.draggingPurpose = -1; videoPlayerViewModel.draggingPurpose = -1;
} else { } else {
videoPlayerViewModel.draggingPurpose = -2; videoPlayerViewModel.draggingPurpose = -2;
@@ -969,34 +1121,42 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
videoPlayerViewModel.draggingPurpose = -1; videoPlayerViewModel.draggingPurpose = -1;
}, },
onDrag = { change, dragAmount -> onDrag = { change, dragAmount ->
if (videoPlayerViewModel.locked) return@detectDragGestures
if (abs(dragAmount.x) > abs(dragAmount.y) && if (abs(dragAmount.x) > abs(dragAmount.y) &&
(videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2)) (videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2)
{ ) {
videoPlayerViewModel.draggingPurpose = 0 videoPlayerViewModel.draggingPurpose = 0
videoPlayerViewModel.planeVisibility = true videoPlayerViewModel.planeVisibility = true
exoPlayer.pause() exoPlayer.pause()
} } else if (videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose =
else if(videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose = 1 1
else if(videoPlayerViewModel.draggingPurpose == -2) videoPlayerViewModel.draggingPurpose = 2 else if (videoPlayerViewModel.draggingPurpose == -2) videoPlayerViewModel.draggingPurpose =
2
if(videoPlayerViewModel.draggingPurpose == 0) if (videoPlayerViewModel.draggingPurpose == 0) {
{
exoPlayer.seekTo((exoPlayer.currentPosition + dragAmount.x * 200.0f).toLong()) exoPlayer.seekTo((exoPlayer.currentPosition + dragAmount.x * 200.0f).toLong())
videoPlayerViewModel.playProcess = exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat() videoPlayerViewModel.playProcess =
}else if(videoPlayerViewModel.draggingPurpose == 2) exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat()
{ } else if (videoPlayerViewModel.draggingPurpose == 2) {
val cu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) val cu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
volFactor = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat() volFactor =
audioManager.getStreamVolume(AudioManager.STREAM_MUSIC)
.toFloat() / maxVolume.toFloat()
if (dragAmount.y < 0) if (dragAmount.y < 0)
setVolume(cu + 1); setVolume(cu + 1);
else if (dragAmount.y > 0) else if (dragAmount.y > 0)
setVolume(cu - 1); setVolume(cu - 1);
}else if(videoPlayerViewModel.draggingPurpose == 1) } else if (videoPlayerViewModel.draggingPurpose == 1) {
{ videoPlayerViewModel.brit =
videoPlayerViewModel.brit = (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn(0f, 1f); (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn(
0f,
1f
);
activity?.window?.attributes = activity.window.attributes.apply { activity?.window?.attributes =
screenBrightness = videoPlayerViewModel.brit.coerceIn(0f, 1f) activity.window.attributes.apply {
screenBrightness =
videoPlayerViewModel.brit.coerceIn(0f, 1f)
} }
activity?.window?.setAttributes(activity.window.attributes) activity?.window?.setAttributes(activity.window.attributes)
} }
@@ -1007,14 +1167,20 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
.pointerInput(Unit) { .pointerInput(Unit) {
detectTapGestures( detectTapGestures(
onDoubleTap = { onDoubleTap = {
if (videoPlayerViewModel.locked) return@detectTapGestures
videoPlayerViewModel.isPlaying = !videoPlayerViewModel.isPlaying videoPlayerViewModel.isPlaying = !videoPlayerViewModel.isPlaying
if (videoPlayerViewModel.isPlaying) exoPlayer.play() else exoPlayer.pause() if (videoPlayerViewModel.isPlaying) exoPlayer.play() else exoPlayer.pause()
}, },
onTap = { onTap = {
if (videoPlayerViewModel.locked) return@detectTapGestures
videoPlayerViewModel.planeVisibility = videoPlayerViewModel.planeVisibility =
!videoPlayerViewModel.planeVisibility !videoPlayerViewModel.planeVisibility
}, },
onLongPress = { onLongPress = {
if (videoPlayerViewModel.locked) return@detectTapGestures
videoPlayerViewModel.isLongPressing = true videoPlayerViewModel.isLongPressing = true
exoPlayer.playbackParameters = exoPlayer.playbackParameters exoPlayer.playbackParameters = exoPlayer.playbackParameters
.withSpeed(3.0f) .withSpeed(3.0f)
@@ -1065,13 +1231,17 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
{ {
Row(Modifier.background(Color(0x88000000), RoundedCornerShape(18)).width(200.dp)) Row(Modifier
.background(Color(0x88000000), RoundedCornerShape(18))
.width(200.dp))
{ {
Icon( Icon(
imageVector = Icons.AutoMirrored.Filled.VolumeUp, imageVector = Icons.AutoMirrored.Filled.VolumeUp,
contentDescription = "Vol", contentDescription = "Vol",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(48.dp).padding(8.dp) modifier = Modifier
.size(48.dp)
.padding(8.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) )
BiliMiniSlider( BiliMiniSlider(
@@ -1096,13 +1266,17 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
modifier = Modifier.align(Alignment.Center) modifier = Modifier.align(Alignment.Center)
) )
{ {
Row(Modifier.background(Color(0x88000000), RoundedCornerShape(18)).width(200.dp)) Row(Modifier
.background(Color(0x88000000), RoundedCornerShape(18))
.width(200.dp))
{ {
Icon( Icon(
imageVector = Icons.Default.Brightness4, imageVector = Icons.Default.Brightness4,
contentDescription = "Brightness", contentDescription = "Brightness",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(48.dp).padding(8.dp) modifier = Modifier
.size(48.dp)
.padding(8.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) )
BiliMiniSlider( BiliMiniSlider(
@@ -1124,19 +1298,29 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
.align(Alignment.TopCenter) .align(Alignment.TopCenter)
) )
{ {
Box(modifier = Modifier.align(Alignment.TopCenter).padding(top = 24.dp).background(Color(0x44000000), RoundedCornerShape(18))) Box(
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = 24.dp)
.background(Color(0x44000000), RoundedCornerShape(18))
)
{ {
Row { Row {
Icon( Icon(
imageVector = Icons.Filled.FastForward, imageVector = Icons.Filled.FastForward,
contentDescription = "Fast Forward", contentDescription = "Fast Forward",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(36.dp).padding(4.dp).align(Alignment.CenterVertically) modifier = Modifier
.size(36.dp)
.padding(4.dp)
.align(Alignment.CenterVertically)
) )
Text( Text(
text = "3X Speed...", text = "3X Speed...",
modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically), modifier = Modifier
.padding(4.dp)
.align(Alignment.CenterVertically),
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = Color(0xFFFFFFFF) color = Color(0xFFFFFFFF)
@@ -1146,7 +1330,7 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
} }
AnimatedVisibility( AnimatedVisibility(
visible = videoPlayerViewModel.planeVisibility, visible = videoPlayerViewModel.planeVisibility && (!videoPlayerViewModel.locked),
enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }), enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }),
exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight }), exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight }),
modifier = Modifier modifier = Modifier
@@ -1154,21 +1338,28 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
.fillMaxWidth() .fillMaxWidth()
) )
{ {
Row(Modifier Row(
Modifier
.align(Alignment.TopStart) .align(Alignment.TopStart)
.padding(horizontal = 32.dp).background( .padding(horizontal = 42.dp)
.background(
brush = Brush.verticalGradient( brush = Brush.verticalGradient(
colors = listOf( colors = listOf(
Color.Black.copy(alpha = 0.4f), Color.Black.copy(alpha = 0.4f),
Color.Transparent, Color.Transparent,
) )
))) )
)
)
{ {
IconButton( IconButton(
onClick = { onClick = {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT activity?.requestedOrientation =
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
}, },
modifier = Modifier.size(36.dp).align(Alignment.CenterVertically) modifier = Modifier
.size(36.dp)
.align(Alignment.CenterVertically)
) { ) {
Icon( Icon(
modifier = Modifier.size(36.dp), modifier = Modifier.size(36.dp),
@@ -1181,14 +1372,16 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
Text( Text(
text = "${videoPlayerViewModel.video?.video?.name}", text = "${videoPlayerViewModel.video?.video?.name}",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 12.dp).align(Alignment.CenterVertically), modifier = Modifier
.padding(horizontal = 12.dp)
.align(Alignment.CenterVertically),
fontSize = 18.sp fontSize = 18.sp
) )
} }
} }
AnimatedVisibility( AnimatedVisibility(
visible = videoPlayerViewModel.planeVisibility, visible = videoPlayerViewModel.planeVisibility && (!videoPlayerViewModel.locked),
enter = slideInVertically(initialOffsetY = { fullHeight -> fullHeight }), enter = slideInVertically(initialOffsetY = { fullHeight -> fullHeight }),
exit = slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }), exit = slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }),
modifier = Modifier modifier = Modifier
@@ -1200,12 +1393,14 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
modifier = Modifier modifier = Modifier
.align(Alignment.BottomCenter) .align(Alignment.BottomCenter)
.fillMaxWidth() .fillMaxWidth()
.background( brush = Brush.verticalGradient( .background(
brush = Brush.verticalGradient(
colors = listOf( colors = listOf(
Color.Transparent, Color.Transparent,
Color.Black.copy(alpha = 0.4f) Color.Black.copy(alpha = 0.4f)
) )
)) )
)
.padding(horizontal = 36.dp) .padding(horizontal = 36.dp)
) { ) {
Text( Text(
@@ -1223,7 +1418,10 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
onValueChange = { value -> onValueChange = { value ->
exoPlayer.seekTo((exoPlayer.duration * value).toLong()) exoPlayer.seekTo((exoPlayer.duration * value).toLong())
}, },
modifier = Modifier.height(16.dp).fillMaxWidth().padding(bottom = 8.dp) modifier = Modifier
.height(16.dp)
.fillMaxWidth()
.padding(bottom = 8.dp)
) )
Row( Row(
modifier = Modifier modifier = Modifier
@@ -1259,11 +1457,12 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel)
} }
@Composable @Composable
fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit, imageLoader: ImageLoader) fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit, imageLoader: ImageLoader) {
{
var isImageLoaded by remember { mutableStateOf(false) } var isImageLoaded by remember { mutableStateOf(false) }
Card( Card(
modifier = modifier.height(80.dp).fillMaxWidth(), modifier = modifier
.height(80.dp)
.fillMaxWidth(),
colors = CardColors( colors = CardColors(
containerColor = Color.Transparent, containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onSurface, contentColor = MaterialTheme.colorScheme.onSurface,
@@ -1288,14 +1487,19 @@ fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit, imageLo
.build(), .build(),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.width(128.dp).fillMaxHeight() .width(128.dp)
.fillMaxHeight()
.clip(RoundedCornerShape(8.dp)), .clip(RoundedCornerShape(8.dp)),
contentScale = ContentScale.Crop, contentScale = ContentScale.Crop,
imageLoader = imageLoader imageLoader = imageLoader
) )
Column( Column(
modifier = Modifier.padding(horizontal = 8.dp).fillMaxHeight().fillMaxWidth().align(Alignment.CenterVertically), modifier = Modifier
.padding(horizontal = 8.dp)
.fillMaxHeight()
.fillMaxWidth()
.align(Alignment.CenterVertically),
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) )
{ {

View File

@@ -71,7 +71,7 @@ class VideoPlayerViewModel @Inject constructor(
var thumbUp by mutableIntStateOf(0) var thumbUp by mutableIntStateOf(0)
var thumbDown by mutableIntStateOf(0) var thumbDown by mutableIntStateOf(0)
var star by mutableStateOf(false) var star by mutableStateOf(false)
var locked by mutableStateOf(false)
private var _init: Boolean = false; private var _init: Boolean = false;
var startPlaying by mutableStateOf(false) var startPlaying by mutableStateOf(false)
var renderedFirst = false var renderedFirst = false