From 49751c55d9b3e2cc141ea1eb775ce7d75e4154cc Mon Sep 17 00:00:00 2001 From: acite <1498045907@qq.com> Date: Tue, 23 Sep 2025 02:53:42 +0800 Subject: [PATCH] [feat] Video Player lock --- .../com/acitelight/aether/view/VideoPlayer.kt | 528 ++++++++++++------ .../aether/viewModel/VideoPlayerViewModel.kt | 2 +- 2 files changed, 367 insertions(+), 163 deletions(-) diff --git a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt index 5632085..7bb5439 100644 --- a/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt +++ b/app/src/main/java/com/acitelight/aether/view/VideoPlayer.kt @@ -60,13 +60,18 @@ import androidx.compose.material.icons.filled.Brightness4 import androidx.compose.material.icons.filled.FastForward import androidx.compose.material.icons.filled.Fullscreen 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.Star import androidx.compose.material.icons.filled.ThumbDown import androidx.compose.material.icons.filled.ThumbUp 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.CardColors +import androidx.compose.material3.CardDefaults import androidx.compose.material3.DividerDefaults import androidx.compose.material3.ExperimentalMaterial3Api 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.nestedScroll 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.onGloballyPositioned import androidx.compose.ui.platform.LocalConfiguration @@ -230,7 +236,9 @@ fun SubtitleOverlay( cornerRadius: Dp = 6.dp, 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 val textAlign = when (cues.firstOrNull()?.textAlignment) { @@ -281,15 +289,45 @@ fun VideoPlayer( videoId: String, navController: NavHostController ) { + val colorScheme = MaterialTheme.colorScheme videoPlayerViewModel.init(videoId) - if(videoPlayerViewModel.startPlaying) - { + if (videoPlayerViewModel.startPlaying) { if (isLandscape()) { - VideoPlayerLandscape(videoPlayerViewModel) - } - else - { + Box { + 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 { VideoPlayerPortal(videoPlayerViewModel, navController) } } @@ -297,15 +335,18 @@ fun VideoPlayer( @androidx.annotation.OptIn(UnstableApi::class) @Composable -fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel, cover: Float) -{ +fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel, cover: Float) { val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!; val context = LocalContext.current val activity = context as? Activity val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager } 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) { audioManager.setStreamVolume( @@ -335,10 +376,10 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo .pointerInput(Unit) { detectDragGestures( onDragStart = { offset -> - if(offset.x < size.width / 2) - { + if (videoPlayerViewModel.locked) return@detectDragGestures + if (offset.x < size.width / 2) { videoPlayerViewModel.draggingPurpose = -1; - }else{ + } else { videoPlayerViewModel.draggingPurpose = -2; } }, @@ -349,31 +390,36 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo videoPlayerViewModel.draggingPurpose = -1; }, onDrag = { change, dragAmount -> - if(abs(dragAmount.x) > abs(dragAmount.y) && - (videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2)) - { + if (videoPlayerViewModel.locked) return@detectDragGestures + if (abs(dragAmount.x) > abs(dragAmount.y) && + (videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2) + ) { videoPlayerViewModel.draggingPurpose = 0 videoPlayerViewModel.planeVisibility = true exoPlayer.pause() - } - else if(videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose = 1 - else if(videoPlayerViewModel.draggingPurpose == -2) videoPlayerViewModel.draggingPurpose = 2 + } else if (videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose = + 1 + 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()) - videoPlayerViewModel.playProcess = exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat() - }else if(videoPlayerViewModel.draggingPurpose == 2) - { + videoPlayerViewModel.playProcess = + exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat() + } else if (videoPlayerViewModel.draggingPurpose == 2) { val cu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - volFactor = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat() - if(dragAmount.y < 0) - setVolume( cu + 1); - else if(dragAmount.y > 0) - setVolume( cu - 1); - }else if(videoPlayerViewModel.draggingPurpose == 1) - { - videoPlayerViewModel.brit = (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn(0f, 1f); + volFactor = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + .toFloat() / maxVolume.toFloat() + if (dragAmount.y < 0) + setVolume(cu + 1); + else if (dragAmount.y > 0) + setVolume(cu - 1); + } else if (videoPlayerViewModel.draggingPurpose == 1) { + videoPlayerViewModel.brit = + (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn( + 0f, + 1f + ); activity?.window?.attributes = activity.window.attributes.apply { screenBrightness = videoPlayerViewModel.brit.coerceIn(0f, 1f) @@ -387,14 +433,17 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo .pointerInput(Unit) { detectTapGestures( onDoubleTap = { + if (videoPlayerViewModel.locked) return@detectTapGestures videoPlayerViewModel.isPlaying = !videoPlayerViewModel.isPlaying if (videoPlayerViewModel.isPlaying) exoPlayer.play() else exoPlayer.pause() }, onTap = { + if (videoPlayerViewModel.locked) return@detectTapGestures videoPlayerViewModel.planeVisibility = !videoPlayerViewModel.planeVisibility }, onLongPress = { + if (videoPlayerViewModel.locked) return@detectTapGestures videoPlayerViewModel.isLongPressing = true exoPlayer.playbackParameters = exoPlayer.playbackParameters .withSpeed(3.0f) @@ -419,19 +468,29 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo .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( imageVector = Icons.Filled.FastForward, contentDescription = "Fast Forward", 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 = "3X Speed...", - modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically), + modifier = Modifier + .padding(4.dp) + .align(Alignment.CenterVertically), fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color(0xFFFFFFFF) @@ -474,13 +533,17 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo 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( imageVector = Icons.AutoMirrored.Filled.VolumeUp, contentDescription = "Vol", tint = Color.White, - modifier = Modifier.size(48.dp).padding(8.dp) + modifier = Modifier + .size(48.dp) + .padding(8.dp) .align(Alignment.CenterVertically) ) BiliMiniSlider( @@ -505,13 +568,17 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo 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( imageVector = Icons.Default.Brightness4, contentDescription = "Brightness", tint = Color.White, - modifier = Modifier.size(48.dp).padding(8.dp) + modifier = Modifier + .size(48.dp) + .padding(8.dp) .align(Alignment.CenterVertically) ) BiliMiniSlider( @@ -525,18 +592,22 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo } } - if(cover > 0.0f) - Spacer(Modifier.background(MaterialTheme.colorScheme.primary.copy(cover)).fillMaxSize()) + if (cover > 0.0f) + Spacer(Modifier + .background(MaterialTheme.colorScheme.primary.copy(cover)) + .fillMaxSize()) androidx.compose.animation.AnimatedVisibility( - visible = !videoPlayerViewModel.planeVisibility, + visible = (!videoPlayerViewModel.planeVisibility) || videoPlayerViewModel.locked, enter = fadeIn( initialAlpha = 0f, ), exit = fadeOut( targetAlpha = 0f ), - modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter) + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) ) { BiliMiniSlider( value = videoPlayerViewModel.playProcess, @@ -548,26 +619,31 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo } androidx.compose.animation.AnimatedVisibility( - visible = videoPlayerViewModel.planeVisibility, + visible = videoPlayerViewModel.planeVisibility && (!videoPlayerViewModel.locked), enter = fadeIn( initialAlpha = 0f, ), exit = fadeOut( targetAlpha = 0f ), - modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter).height(42.dp) + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .height(42.dp) ) { Row( modifier = Modifier .fillMaxWidth() - .align(Alignment.BottomCenter).background( + .align(Alignment.BottomCenter) + .background( brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black.copy(alpha = 0.4f), + colors = listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.4f), + ) ) - )), + ), horizontalArrangement = Arrangement.SpaceBetween, ) { IconButton( @@ -636,13 +712,16 @@ fun PortalCorePlayer(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewMo } @Composable -fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: NavHostController) -{ +fun VideoPlayerPortal( + videoPlayerViewModel: VideoPlayerViewModel, + navController: NavHostController +) { + val colorScheme = MaterialTheme.colorScheme val configuration = LocalConfiguration.current val screenHeight = configuration.screenHeightDp.dp; val minHeight = 42.dp - var coverAlpha by remember{ mutableFloatStateOf(0.0f) } + var coverAlpha by remember { mutableFloatStateOf(0.0f) } var maxHeight = remember { screenHeight * 0.65f } var posed = remember { false } val dens = LocalDensity.current @@ -662,7 +741,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: playerHeight = newHeight val consumedPx = with(dens) { consumedDp.toPx() } Offset(0f, consumedPx) - } else if(deltaY > 0 && playerHeight < maxHeight && listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0) { + } else if (deltaY > 0 && playerHeight < maxHeight && listState.firstVisibleItemIndex == 0 && listState.firstVisibleItemScrollOffset == 0) { val newHeight = (playerHeight + deltaDp).coerceIn(minHeight, maxHeight) val consumedDp = newHeight - playerHeight playerHeight = newHeight @@ -673,7 +752,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: } val dh = playerHeight - minHeight; - coverAlpha = (if(dh > 10.dp) + coverAlpha = (if (dh > 10.dp) 0f else (10.dp.value - dh.value) / 10.0f) @@ -684,38 +763,73 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: } ToggleFullScreen(false) - Column(Modifier.nestedScroll(nestedScrollConnection).fillMaxHeight()) + Column(Modifier + .nestedScroll(nestedScrollConnection) + .fillMaxHeight()) { - PortalCorePlayer( - Modifier - .padding(top = 42.dp) - .heightIn(max = playerHeight) - .onGloballyPositioned { layoutCoordinates -> - if(!posed && videoPlayerViewModel.renderedFirst) - { - maxHeight = with(dens) {layoutCoordinates.size.height.toDp()} - playerHeight = maxHeight - posed = true - } - }, - videoPlayerViewModel = videoPlayerViewModel, coverAlpha) + Box { + PortalCorePlayer( + Modifier + .padding(top = 32.dp) + .heightIn(max = playerHeight) + .onGloballyPositioned { layoutCoordinates -> + if (!posed && videoPlayerViewModel.renderedFirst) { + maxHeight = with(dens) { layoutCoordinates.size.height.toDp() } + playerHeight = maxHeight + posed = true + } + }, + 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() { - TabRow ( + TabRow( selectedTabIndex = videoPlayerViewModel.tabIndex, modifier = Modifier.height(38.dp) ) { Tab( selected = videoPlayerViewModel.tabIndex == 0, - onClick = { videoPlayerViewModel.tabIndex = 0 }, + onClick = { videoPlayerViewModel.tabIndex = 0 }, text = { Text(text = "Introduction", maxLines = 1) }, modifier = Modifier.height(38.dp) ) Tab( selected = videoPlayerViewModel.tabIndex == 1, - onClick = { videoPlayerViewModel.tabIndex = 1 }, + onClick = { videoPlayerViewModel.tabIndex = 1 }, text = { Text(text = "Comment", maxLines = 1) }, modifier = Modifier.height(38.dp) ) @@ -723,16 +837,22 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: } LazyColumn(state = listState, modifier = Modifier.fillMaxWidth()) { - item{ + item { 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 ?: "", fontSize = 16.sp, maxLines = 2, 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( modifier = Modifier.padding(horizontal = 8.dp), text = videoPlayerViewModel.video?.klass ?: "", @@ -752,16 +872,20 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: 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) HorizontalGallery(videoPlayerViewModel) HorizontalDivider(Modifier.padding(vertical = 8.dp), 1.dp, DividerDefaults.color) - for(i in Global.sameClassVideos ?: listOf()) - { - if(i.id == videoPlayerViewModel.video?.id) continue + for (i in Global.sameClassVideos ?: listOf()) { + if (i.id == videoPlayerViewModel.video?.id) continue MiniVideoCard( modifier = Modifier @@ -770,10 +894,17 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: { videoPlayerViewModel.isPlaying = false 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) - }, videoPlayerViewModel.imageLoader!!) - HorizontalDivider(Modifier.padding(vertical = 8.dp).alpha(0.25f), 1.dp, DividerDefaults.color) + }, videoPlayerViewModel.imageLoader!! + ) + HorizontalDivider( + Modifier + .padding(vertical = 8.dp) + .alpha(0.25f), + 1.dp, + DividerDefaults.color + ) } } } @@ -781,8 +912,7 @@ fun VideoPlayerPortal(videoPlayerViewModel: VideoPlayerViewModel, navController: } @Composable -fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) -{ +fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) { val colorScheme = MaterialTheme.colorScheme Row( modifier, @@ -791,8 +921,11 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) { Column(modifier = Modifier.padding(horizontal = 12.dp)) { IconButton( - onClick = { }, - modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), + onClick = { }, + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterHorizontally) + .size(36.dp), ) { Icon( modifier = Modifier.size(28.dp), @@ -807,13 +940,17 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) text = videoPlayerViewModel.thumbUp.toString(), fontSize = 12.sp, maxLines = 1, - fontWeight = FontWeight.Bold) + fontWeight = FontWeight.Bold + ) } Column(modifier = Modifier.padding(horizontal = 12.dp)) { IconButton( - onClick = { }, - modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), + onClick = { }, + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterHorizontally) + .size(36.dp), ) { Icon( modifier = Modifier.size(28.dp), @@ -828,19 +965,23 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) text = videoPlayerViewModel.thumbDown.toString(), fontSize = 12.sp, maxLines = 1, - fontWeight = FontWeight.Bold) + fontWeight = FontWeight.Bold + ) } Column(modifier = Modifier.padding(horizontal = 12.dp)) { IconButton( - onClick = { videoPlayerViewModel.star = !videoPlayerViewModel.star }, - modifier = Modifier.padding(horizontal = 4.dp).align(Alignment.CenterHorizontally).size(36.dp), + onClick = { videoPlayerViewModel.star = !videoPlayerViewModel.star }, + modifier = Modifier + .padding(horizontal = 4.dp) + .align(Alignment.CenterHorizontally) + .size(36.dp), ) { Icon( modifier = Modifier.size(28.dp), imageVector = Icons.Filled.Star, contentDescription = "Star", - tint = if(videoPlayerViewModel.star) colorScheme.primary else Color.Gray + tint = if (videoPlayerViewModel.star) colorScheme.primary else Color.Gray ) } } @@ -848,7 +989,10 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) Column(modifier = Modifier.padding(horizontal = 12.dp)) { IconButton( 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( modifier = Modifier.size(28.dp), @@ -862,7 +1006,10 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) Column(modifier = Modifier.padding(horizontal = 12.dp)) { IconButton( 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( modifier = Modifier.size(28.dp), @@ -876,10 +1023,11 @@ fun SocialPanel(modifier: Modifier, videoPlayerViewModel: VideoPlayerViewModel) } @Composable -fun HorizontalGallery(videoPlayerViewModel: VideoPlayerViewModel) -{ +fun HorizontalGallery(videoPlayerViewModel: VideoPlayerViewModel) { LazyRow( - modifier = Modifier.fillMaxWidth().height(120.dp), + modifier = Modifier + .fillMaxWidth() + .height(120.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues(horizontal = 24.dp) ) { @@ -908,15 +1056,18 @@ fun SingleImageItem(img: KeyImage, imageLoader: ImageLoader) { @androidx.annotation.OptIn(UnstableApi::class) @Composable -fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) -{ +fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) { val context = LocalContext.current val activity = context as? Activity val exoPlayer: ExoPlayer = videoPlayerViewModel._player!!; val audioManager = remember { context.getSystemService(Context.AUDIO_SERVICE) as AudioManager } 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) { audioManager.setStreamVolume( @@ -935,7 +1086,8 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) { Box( modifier = Modifier - .background(Color.Black).align(Alignment.Center) + .background(Color.Black) + .align(Alignment.Center) ) { AndroidView( @@ -955,10 +1107,10 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) .pointerInput(Unit) { detectDragGestures( onDragStart = { offset -> - if(offset.x < size.width / 2) - { + if (videoPlayerViewModel.locked) return@detectDragGestures + if (offset.x < size.width / 2) { videoPlayerViewModel.draggingPurpose = -1; - }else{ + } else { videoPlayerViewModel.draggingPurpose = -2; } }, @@ -969,35 +1121,43 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) videoPlayerViewModel.draggingPurpose = -1; }, onDrag = { change, dragAmount -> - if(abs(dragAmount.x) > abs(dragAmount.y) && - (videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2)) - { + if (videoPlayerViewModel.locked) return@detectDragGestures + if (abs(dragAmount.x) > abs(dragAmount.y) && + (videoPlayerViewModel.draggingPurpose == -1 || videoPlayerViewModel.draggingPurpose == -2) + ) { videoPlayerViewModel.draggingPurpose = 0 videoPlayerViewModel.planeVisibility = true exoPlayer.pause() - } - else if(videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose = 1 - else if(videoPlayerViewModel.draggingPurpose == -2) videoPlayerViewModel.draggingPurpose = 2 + } else if (videoPlayerViewModel.draggingPurpose == -1) videoPlayerViewModel.draggingPurpose = + 1 + 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()) - videoPlayerViewModel.playProcess = exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat() - }else if(videoPlayerViewModel.draggingPurpose == 2) - { + videoPlayerViewModel.playProcess = + exoPlayer.currentPosition.toFloat() / exoPlayer.duration.toFloat() + } else if (videoPlayerViewModel.draggingPurpose == 2) { val cu = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) - volFactor = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC).toFloat() / maxVolume.toFloat() - if(dragAmount.y < 0) - setVolume( cu + 1); - else if(dragAmount.y > 0) - setVolume( cu - 1); - }else if(videoPlayerViewModel.draggingPurpose == 1) - { - videoPlayerViewModel.brit = (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn(0f, 1f); + volFactor = + audioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + .toFloat() / maxVolume.toFloat() + if (dragAmount.y < 0) + setVolume(cu + 1); + else if (dragAmount.y > 0) + setVolume(cu - 1); + } else if (videoPlayerViewModel.draggingPurpose == 1) { + videoPlayerViewModel.brit = + (videoPlayerViewModel.brit - dragAmount.y * 0.002f).coerceIn( + 0f, + 1f + ); - activity?.window?.attributes = activity.window.attributes.apply { - screenBrightness = videoPlayerViewModel.brit.coerceIn(0f, 1f) - } + activity?.window?.attributes = + activity.window.attributes.apply { + screenBrightness = + videoPlayerViewModel.brit.coerceIn(0f, 1f) + } activity?.window?.setAttributes(activity.window.attributes) } @@ -1007,14 +1167,20 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) .pointerInput(Unit) { detectTapGestures( onDoubleTap = { + if (videoPlayerViewModel.locked) return@detectTapGestures + videoPlayerViewModel.isPlaying = !videoPlayerViewModel.isPlaying if (videoPlayerViewModel.isPlaying) exoPlayer.play() else exoPlayer.pause() }, onTap = { + if (videoPlayerViewModel.locked) return@detectTapGestures + videoPlayerViewModel.planeVisibility = !videoPlayerViewModel.planeVisibility }, onLongPress = { + if (videoPlayerViewModel.locked) return@detectTapGestures + videoPlayerViewModel.isLongPressing = true exoPlayer.playbackParameters = exoPlayer.playbackParameters .withSpeed(3.0f) @@ -1065,13 +1231,17 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) 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( imageVector = Icons.AutoMirrored.Filled.VolumeUp, contentDescription = "Vol", tint = Color.White, - modifier = Modifier.size(48.dp).padding(8.dp) + modifier = Modifier + .size(48.dp) + .padding(8.dp) .align(Alignment.CenterVertically) ) BiliMiniSlider( @@ -1096,13 +1266,17 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) 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( imageVector = Icons.Default.Brightness4, contentDescription = "Brightness", tint = Color.White, - modifier = Modifier.size(48.dp).padding(8.dp) + modifier = Modifier + .size(48.dp) + .padding(8.dp) .align(Alignment.CenterVertically) ) BiliMiniSlider( @@ -1124,19 +1298,29 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) .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( imageVector = Icons.Filled.FastForward, contentDescription = "Fast Forward", 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 = "3X Speed...", - modifier = Modifier.padding(4.dp).align(Alignment.CenterVertically), + modifier = Modifier + .padding(4.dp) + .align(Alignment.CenterVertically), fontSize = 16.sp, fontWeight = FontWeight.Bold, color = Color(0xFFFFFFFF) @@ -1146,7 +1330,7 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) } AnimatedVisibility( - visible = videoPlayerViewModel.planeVisibility, + visible = videoPlayerViewModel.planeVisibility && (!videoPlayerViewModel.locked), enter = slideInVertically(initialOffsetY = { fullHeight -> -fullHeight }), exit = slideOutVertically(targetOffsetY = { fullHeight -> -fullHeight }), modifier = Modifier @@ -1154,21 +1338,28 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) .fillMaxWidth() ) { - Row(Modifier - .align(Alignment.TopStart) - .padding(horizontal = 32.dp).background( - brush = Brush.verticalGradient( - colors = listOf( - Color.Black.copy(alpha = 0.4f), - Color.Transparent, + Row( + Modifier + .align(Alignment.TopStart) + .padding(horizontal = 42.dp) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Black.copy(alpha = 0.4f), + Color.Transparent, + ) + ) ) - ))) + ) { IconButton( 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( modifier = Modifier.size(36.dp), @@ -1181,14 +1372,16 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) Text( text = "${videoPlayerViewModel.video?.video?.name}", fontWeight = FontWeight.Bold, - modifier = Modifier.padding(horizontal = 12.dp).align(Alignment.CenterVertically), + modifier = Modifier + .padding(horizontal = 12.dp) + .align(Alignment.CenterVertically), fontSize = 18.sp ) } } AnimatedVisibility( - visible = videoPlayerViewModel.planeVisibility, + visible = videoPlayerViewModel.planeVisibility && (!videoPlayerViewModel.locked), enter = slideInVertically(initialOffsetY = { fullHeight -> fullHeight }), exit = slideOutVertically(targetOffsetY = { fullHeight -> fullHeight }), modifier = Modifier @@ -1200,12 +1393,14 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) modifier = Modifier .align(Alignment.BottomCenter) .fillMaxWidth() - .background( brush = Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black.copy(alpha = 0.4f) + .background( + brush = Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.4f) + ) ) - )) + ) .padding(horizontal = 36.dp) ) { Text( @@ -1223,7 +1418,10 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) onValueChange = { value -> 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( modifier = Modifier @@ -1259,11 +1457,12 @@ fun VideoPlayerLandscape(videoPlayerViewModel: VideoPlayerViewModel) } @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) } Card( - modifier = modifier.height(80.dp).fillMaxWidth(), + modifier = modifier + .height(80.dp) + .fillMaxWidth(), colors = CardColors( containerColor = Color.Transparent, contentColor = MaterialTheme.colorScheme.onSurface, @@ -1283,19 +1482,24 @@ fun MiniVideoCard(modifier: Modifier, video: Video, onClick: () -> Unit, imageLo .listener( onStart = { }, onSuccess = { _, _ -> isImageLoaded = true }, - onError = { _, _ -> } + onError = { _, _ -> } ) .build(), contentDescription = null, modifier = Modifier - .width(128.dp).fillMaxHeight() + .width(128.dp) + .fillMaxHeight() .clip(RoundedCornerShape(8.dp)), contentScale = ContentScale.Crop, imageLoader = imageLoader ) - Column ( - modifier = Modifier.padding(horizontal = 8.dp).fillMaxHeight().fillMaxWidth().align(Alignment.CenterVertically), + Column( + modifier = Modifier + .padding(horizontal = 8.dp) + .fillMaxHeight() + .fillMaxWidth() + .align(Alignment.CenterVertically), verticalArrangement = Arrangement.Center ) { diff --git a/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt b/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt index 4e80b2d..f1490b6 100644 --- a/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt +++ b/app/src/main/java/com/acitelight/aether/viewModel/VideoPlayerViewModel.kt @@ -71,7 +71,7 @@ class VideoPlayerViewModel @Inject constructor( var thumbUp by mutableIntStateOf(0) var thumbDown by mutableIntStateOf(0) var star by mutableStateOf(false) - + var locked by mutableStateOf(false) private var _init: Boolean = false; var startPlaying by mutableStateOf(false) var renderedFirst = false