From 6aab32d9b4addbc8f00600370430cd47541d7625 Mon Sep 17 00:00:00 2001 From: Thalys Matias Carrara Date: Thu, 29 Jun 2023 18:52:59 -0400 Subject: [PATCH] feat: use VolumeControl composable Closes #52 --- .../components/EnhancedVideoPlayer.kt | 24 ++++- .../playerOverlay/BrightnessControl.kt | 87 ------------------- .../playerOverlay/MiddleControls.kt | 23 ++++- .../playerOverlay/PlayerControls.kt | 35 ++++++-- .../components/playerOverlay/PlayerIcons.kt | 30 +++++++ .../sideControls/BrightnessControl.kt | 57 ++++++++++++ 6 files changed, 156 insertions(+), 100 deletions(-) delete mode 100644 androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/BrightnessControl.kt create mode 100644 androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/sideControls/BrightnessControl.kt diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/EnhancedVideoPlayer.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/EnhancedVideoPlayer.kt index e013ace9..9d66cadb 100644 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/EnhancedVideoPlayer.kt +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/EnhancedVideoPlayer.kt @@ -40,6 +40,7 @@ import com.profusion.androidenhancedvideoplayer.utils.TimeoutEffect import com.profusion.androidenhancedvideoplayer.utils.TrackQualityAuto import com.profusion.androidenhancedvideoplayer.utils.TrackQualityItemListSaver import com.profusion.androidenhancedvideoplayer.utils.TrackQualityItemSaver +import com.profusion.androidenhancedvideoplayer.utils.VolumeController import com.profusion.androidenhancedvideoplayer.utils.fillMaxSizeOnLandscape import com.profusion.androidenhancedvideoplayer.utils.generateTrackQualityOptions import com.profusion.androidenhancedvideoplayer.utils.getDeviceRealSizeDp @@ -81,9 +82,9 @@ fun EnhancedVideoPlayer( settingsControlsCustomization: SettingsControlsCustomization = SettingsControlsCustomization() ) { val context = LocalContext.current - val configuration = LocalConfiguration.current val orientation = configuration.orientation + val volumeController = remember { VolumeController(context) } var isPlaying by remember { mutableStateOf(exoPlayer.isPlaying) } var isBuffering by remember { @@ -104,8 +105,11 @@ fun EnhancedVideoPlayer( } var currentOffsetXPreview by remember { mutableStateOf(INITIAL_PREVIEW_OFFSET) } + var deviceVolume by remember { mutableStateOf(volumeController.getDeviceVolume()) } val brightnessMutableInteractionSource = remember { MutableInteractionSource() } val isBrightnessSliderDragged by brightnessMutableInteractionSource.collectIsDraggedAsState() + val volumeMutableInteractionSource = remember { MutableInteractionSource() } + val isVolumeSliderDragged by volumeMutableInteractionSource.collectIsDraggedAsState() val timeBarMutableInteractionSource = remember { MutableInteractionSource() } val isTimeBarDragged by timeBarMutableInteractionSource.collectIsDraggedAsState() @@ -142,6 +146,7 @@ fun EnhancedVideoPlayer( currentTime = player.contentPosition totalDuration = player.duration loop = player.repeatMode == ExoPlayer.REPEAT_MODE_ALL + deviceVolume = player.deviceVolume super.onEvents(player, events) } @@ -188,13 +193,20 @@ fun EnhancedVideoPlayer( } } - LaunchedEffect(isControlsVisible, isPlaying, isBrightnessSliderDragged, isTimeBarDragged) { + LaunchedEffect( + isControlsVisible, + isPlaying, + isBrightnessSliderDragged, + isVolumeSliderDragged, + isTimeBarDragged + ) { if ( isControlsVisible && isPlaying && controlsVisibilityDurationInMs > 0 && !isBrightnessSliderDragged && - !isTimeBarDragged + !isTimeBarDragged && + !isVolumeSliderDragged ) { delay(controlsVisibilityDurationInMs) isControlsVisible = false @@ -268,8 +280,10 @@ fun EnhancedVideoPlayer( isFullScreen = isFullScreen, isBrightnessSliderDragged = isBrightnessSliderDragged, isTimeBarDragged = isTimeBarDragged, + isVolumeSliderDragged = isVolumeSliderDragged, hasEnded = hasEnded, brightnessMutableInteractionSource = brightnessMutableInteractionSource, + volumeMutableInteractionSource = volumeMutableInteractionSource, timeBarMutableInteractionSource = timeBarMutableInteractionSource, totalDuration = totalDuration, currentTime = { currentTime }, @@ -298,6 +312,10 @@ fun EnhancedVideoPlayer( currentImagePreview = previewThumbnailBuilder(it) } }, + deviceVolume = { deviceVolume }, + maxVolumeValue = { volumeController.getMaxVolumeValue() }, + setDeviceVolume = { volumeController.setDeviceVolume(it) }, + shouldShowVolumeControl = !volumeController.deviceHasVolumeFixedPolicy, customization = controlsCustomization ) } diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/BrightnessControl.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/BrightnessControl.kt deleted file mode 100644 index e717d48f..00000000 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/BrightnessControl.kt +++ /dev/null @@ -1,87 +0,0 @@ -package com.profusion.androidenhancedvideoplayer.components.playerOverlay - -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import com.profusion.androidenhancedvideoplayer.styling.Dimensions -import com.profusion.androidenhancedvideoplayer.utils.getActivityBrightnessOrDefault -import com.profusion.androidenhancedvideoplayer.utils.setActivityBrightness - -private val LOW_RANGE = 0.0f..0.33f -private val HIGH_RANGE = 0.66f..1.0f - -enum class BrightnessIconState { - HIGH, - MED, - LOW -} - -fun mapBrightnessRangeToState(brightness: Float): BrightnessIconState { - return when (brightness) { - in LOW_RANGE -> BrightnessIconState.LOW - in HIGH_RANGE -> BrightnessIconState.HIGH - else -> BrightnessIconState.MED - } -} - -fun mapBrightnessIconStateToIcon( - state: BrightnessIconState, - customization: ControlsCustomization -): @Composable () -> Unit { - return when (state) { - BrightnessIconState.LOW -> customization.brightnessLowIconContent - BrightnessIconState.MED -> customization.brightnessMedIconContent - BrightnessIconState.HIGH -> customization.brightnessHighIconContent - } -} - -@Composable -fun BrightnessControl( - modifier: Modifier = Modifier, - isFullScreen: Boolean, - customization: ControlsCustomization, - brightnessMutableInteractionSource: MutableInteractionSource -) { - if (isFullScreen) { - val context = LocalContext.current - var brightnessIconState by remember { - mutableStateOf( - mapBrightnessRangeToState( - context.getActivityBrightnessOrDefault() - ) - ) - } - - val brightnessIcon = mapBrightnessIconStateToIcon(brightnessIconState, customization) - - fun onSliderChange(brightness: Float) { - context.setActivityBrightness(brightness) - brightnessIconState = mapBrightnessRangeToState(brightness) - } - - Column( - modifier = modifier.fillMaxHeight(), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally - ) { - brightnessIcon() - Spacer(modifier = Modifier.height(Dimensions.xxlarge)) - VerticalSlider( - initialValue = context.getActivityBrightnessOrDefault(), - onValueChange = ::onSliderChange, - interactionSource = brightnessMutableInteractionSource - ) - } - } -} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/MiddleControls.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/MiddleControls.kt index 688ce44a..aa3c4cfe 100644 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/MiddleControls.kt +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/MiddleControls.kt @@ -10,11 +10,14 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag +import com.profusion.androidenhancedvideoplayer.components.playerOverlay.sideControls.BrightnessControl +import com.profusion.androidenhancedvideoplayer.components.playerOverlay.sideControls.VolumeControl @Composable fun MiddleControls( modifier: Modifier = Modifier, shouldShowContent: Boolean = true, + shouldShowVolumeControl: Boolean, isPlaying: Boolean, isBuffering: Boolean, isFullScreen: Boolean, @@ -22,19 +25,23 @@ fun MiddleControls( hasEnded: Boolean, customization: ControlsCustomization, brightnessMutableInteractionSource: MutableInteractionSource, + volumeMutableInteractionSource: MutableInteractionSource, onPreviousClick: () -> Unit, onNextClick: () -> Unit, - onPauseToggle: () -> Unit + onPauseToggle: () -> Unit, + deviceVolume: () -> Int, + maxVolumeValue: () -> Int, + setDeviceVolume: (Int) -> Unit ) { Box( modifier = modifier .fillMaxWidth(), contentAlignment = Alignment.Center ) { - if (!isTimeBarDragged) { + val shouldShowSideControls = !isTimeBarDragged && isFullScreen + if (shouldShowSideControls) { BrightnessControl( modifier = Modifier.align(Alignment.CenterStart), - isFullScreen = isFullScreen, customization = customization, brightnessMutableInteractionSource = brightnessMutableInteractionSource ) @@ -66,5 +73,15 @@ fun MiddleControls( } } } + if (shouldShowVolumeControl && shouldShowSideControls) { + VolumeControl( + modifier = Modifier.align(Alignment.CenterEnd), + volume = deviceVolume, + maxVolumeValue = maxVolumeValue, + setDeviceVolume = setDeviceVolume, + customization = customization, + volumeMutableInteractionSource = volumeMutableInteractionSource + ) + } } } diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControls.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControls.kt index 6a16680c..93c48682 100644 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControls.kt +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControls.kt @@ -23,7 +23,10 @@ data class ControlsCustomization( val brightnessHighIconContent: @Composable () -> Unit = { BrightnessHighIcon() }, val brightnessMedIconContent: @Composable () -> Unit = { BrightnessMedIcon() }, val forwardIconContent: @Composable (modifier: Modifier) -> Unit = { ForwardIcon(it) }, - val rewindIconContent: @Composable (modifier: Modifier) -> Unit = { RewindIcon(it) } + val rewindIconContent: @Composable (modifier: Modifier) -> Unit = { RewindIcon(it) }, + val volumeMedIconContent: @Composable () -> Unit = { VolumeMedIcon() }, + val volumeHighIconContent: @Composable () -> Unit = { VolumeHighIcon() }, + val volumeOffIconContent: @Composable () -> Unit = { VolumeOffIcon() } ) @Composable @@ -36,9 +39,14 @@ fun PlayerControls( isFullScreen: Boolean, isBrightnessSliderDragged: Boolean, isTimeBarDragged: Boolean, + isVolumeSliderDragged: Boolean, hasEnded: Boolean, + shouldShowVolumeControl: Boolean, brightnessMutableInteractionSource: MutableInteractionSource, + volumeMutableInteractionSource: MutableInteractionSource, timeBarMutableInteractionSource: MutableInteractionSource, + deviceVolume: () -> Int, + maxVolumeValue: () -> Int, currentTime: () -> Long, bufferedPosition: () -> Long, totalDuration: Long, @@ -49,8 +57,10 @@ fun PlayerControls( onSettingsToggle: () -> Unit, onSeekBarValueFinished: (Long) -> Unit, onSeekBarValueChange: (Long) -> Unit, + setDeviceVolume: (Int) -> Unit, customization: ControlsCustomization ) { + val shouldShowContent = !isBrightnessSliderDragged && !isVolumeSliderDragged PlayerControlsScaffold( modifier = modifier.testTag("PlayerControlsParent"), isVisible = isVisible, @@ -60,13 +70,13 @@ fun PlayerControls( TopControls( modifier = it, title = title, - shouldShowContent = !isBrightnessSliderDragged + shouldShowContent = shouldShowContent ) }, bottomContent = { BottomControls( modifier = it, - shouldShowContent = !isBrightnessSliderDragged, + shouldShowContent = shouldShowContent, isFullScreen = isFullScreen, currentTime = currentTime, bufferedPosition = bufferedPosition, @@ -82,17 +92,22 @@ fun PlayerControls( ) { MiddleControls( modifier = it, - shouldShowContent = !isBrightnessSliderDragged && !isTimeBarDragged, + shouldShowContent = shouldShowContent && !isTimeBarDragged, isTimeBarDragged = isTimeBarDragged, + shouldShowVolumeControl = shouldShowVolumeControl, isPlaying = isPlaying, isBuffering = isBuffering, isFullScreen = isFullScreen, hasEnded = hasEnded, customization = customization, brightnessMutableInteractionSource = brightnessMutableInteractionSource, + volumeMutableInteractionSource = volumeMutableInteractionSource, onPreviousClick = onPreviousClick, onNextClick = onNextClick, - onPauseToggle = onPauseToggle + onPauseToggle = onPauseToggle, + deviceVolume = deviceVolume, + maxVolumeValue = maxVolumeValue, + setDeviceVolume = setDeviceVolume ) } } @@ -102,6 +117,7 @@ fun PlayerControls( private fun PreviewPlayerControls() { PlayerControls( title = "Really long title that should be truncated", + isBuffering = false, isVisible = true, isPlaying = true, hasEnded = false, @@ -111,7 +127,8 @@ private fun PreviewPlayerControls() { currentTime = { 0L }, bufferedPosition = { 50L }, totalDuration = 100L, - brightnessMutableInteractionSource = MutableInteractionSource(), + isVolumeSliderDragged = false, + deviceVolume = { 4 }, onPreviousClick = {}, onPauseToggle = {}, onNextClick = {}, @@ -119,8 +136,12 @@ private fun PreviewPlayerControls() { onSettingsToggle = {}, onSeekBarValueFinished = {}, onSeekBarValueChange = {}, + maxVolumeValue = { 15 }, + setDeviceVolume = {}, + shouldShowVolumeControl = true, customization = ControlsCustomization(), timeBarMutableInteractionSource = MutableInteractionSource(), - isBuffering = false + volumeMutableInteractionSource = MutableInteractionSource(), + brightnessMutableInteractionSource = MutableInteractionSource() ) } diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerIcons.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerIcons.kt index 746476bd..711b9665 100644 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerIcons.kt +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerIcons.kt @@ -176,3 +176,33 @@ fun BrightnessMedIcon(modifier: Modifier = Modifier) { modifier = modifier.testTag("BrightnessMedIcon") ) } + +@Composable +fun VolumeMedIcon() { + Icon( + painter = painterResource(id = R.drawable.ic_volume_med), + tint = Color.White, + contentDescription = stringResource(R.string.controls_volume_med), + modifier = Modifier.testTag("VolumeMedIcon") + ) +} + +@Composable +fun VolumeHighIcon() { + Icon( + painter = painterResource(id = R.drawable.ic_volume_high), + tint = Color.White, + contentDescription = stringResource(R.string.controls_volume_high), + modifier = Modifier.testTag("VolumeHighIcon") + ) +} + +@Composable +fun VolumeOffIcon() { + Icon( + painter = painterResource(id = R.drawable.ic_volume_off), + tint = Color.White, + contentDescription = stringResource(R.string.controls_volume_off), + modifier = Modifier.testTag("VolumeOffIcon") + ) +} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/sideControls/BrightnessControl.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/sideControls/BrightnessControl.kt new file mode 100644 index 00000000..e54935ab --- /dev/null +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/sideControls/BrightnessControl.kt @@ -0,0 +1,57 @@ +package com.profusion.androidenhancedvideoplayer.components.playerOverlay.sideControls + +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.profusion.androidenhancedvideoplayer.components.playerOverlay.ControlsCustomization +import com.profusion.androidenhancedvideoplayer.utils.getActivityBrightnessOrDefault +import com.profusion.androidenhancedvideoplayer.utils.setActivityBrightness + +private val MED_RANGE = 0.33f..0.65f +private val HIGH_RANGE = 0.66f..1.0f + +fun mapBrightnessIconStateToIcon( + state: IconState, + customization: ControlsCustomization +): @Composable () -> Unit { + return when (state) { + IconState.OFF -> customization.brightnessLowIconContent + IconState.MED -> customization.brightnessMedIconContent + IconState.HIGH -> customization.brightnessHighIconContent + } +} + +@Composable +fun BrightnessControl( + modifier: Modifier = Modifier, + customization: ControlsCustomization, + brightnessMutableInteractionSource: MutableInteractionSource +) { + val context = LocalContext.current + var sliderValue by remember { mutableStateOf(context.getActivityBrightnessOrDefault()) } + var brightnessIconState = remember(sliderValue) { + mapRangeToIconState( + value = context.getActivityBrightnessOrDefault(), + lowRange = MED_RANGE, + highRange = HIGH_RANGE + ) + } + + val brightnessIcon = mapBrightnessIconStateToIcon(brightnessIconState, customization) + + SliderControl( + modifier = modifier, + sliderValue = sliderValue, + onSliderValueChange = { + context.setActivityBrightness(it) + sliderValue = it + }, + sliderInteractionSource = brightnessMutableInteractionSource, + topIcon = brightnessIcon + ) +}