diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..f2147e70 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +root = true + +[*.{kt,kts}] +#Custom configuration +ktlint_disabled_rules = no-wildcard-imports +insert_final_newline = true diff --git a/androidenhancedvideoplayer/src/androidTest/java/com/profusion/androidenhancedvideoplayer/test/EnhancedVideoPlayerTest.kt b/androidenhancedvideoplayer/src/androidTest/java/com/profusion/androidenhancedvideoplayer/test/EnhancedVideoPlayerTest.kt index de3d7491..5ad0e1a3 100644 --- a/androidenhancedvideoplayer/src/androidTest/java/com/profusion/androidenhancedvideoplayer/test/EnhancedVideoPlayerTest.kt +++ b/androidenhancedvideoplayer/src/androidTest/java/com/profusion/androidenhancedvideoplayer/test/EnhancedVideoPlayerTest.kt @@ -1,9 +1,12 @@ package com.profusion.androidenhancedvideoplayer.test import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.doubleClick import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onAllNodesWithTag import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTouchInput import com.profusion.androidenhancedvideoplayer.components.EnhancedVideoPlayer import org.junit.Rule import org.junit.Test @@ -29,4 +32,38 @@ class EnhancedVideoPlayerTest { composeTestRule.onNodeWithTag("PlayerControlsParent", useUnmergedTree = true) .assertIsDisplayed() } + + @Test + fun enhancedVideoPlayer_WhenDoubleClickHappenOnTheFirstHalfOfScreenVideoShouldShowRewindIcon() { + composeTestRule.setContent { + EnhancedVideoPlayer( + resourceId = R.raw.login_screen_background + ) + } + + composeTestRule.onAllNodesWithTag("SeekClickableArea", useUnmergedTree = true)[0] + .performTouchInput { + doubleClick() + } + + composeTestRule.onNodeWithTag("RewindIcon", useUnmergedTree = true) + .assertIsDisplayed() + } + + @Test + fun enhancedVideoPlayer_WhenDoubleClickHappenOnTheLastHalfOfScreenVideoShouldShowRewindIcon() { + composeTestRule.setContent { + EnhancedVideoPlayer( + resourceId = R.raw.login_screen_background + ) + } + + composeTestRule.onAllNodesWithTag("SeekClickableArea", useUnmergedTree = true)[1] + .performTouchInput { + doubleClick() + } + + composeTestRule.onNodeWithTag("ForwardIcon", useUnmergedTree = true) + .assertIsDisplayed() + } } 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 fd0b1cbe..8249d529 100644 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/EnhancedVideoPlayer.kt +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/EnhancedVideoPlayer.kt @@ -2,10 +2,14 @@ package com.profusion.androidenhancedvideoplayer.components import android.content.res.Configuration import android.net.Uri +import androidx.compose.animation.* +import androidx.compose.animation.core.* +import androidx.compose.foundation.* import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Box +import androidx.compose.material.* +import androidx.compose.runtime.* import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue @@ -14,10 +18,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.* +import androidx.compose.ui.graphics.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.* import androidx.compose.ui.viewinterop.AndroidView import androidx.media3.common.MediaItem import androidx.media3.common.Player @@ -27,6 +34,7 @@ import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView import com.profusion.androidenhancedvideoplayer.components.playerOverlay.ControlsCustomization import com.profusion.androidenhancedvideoplayer.components.playerOverlay.PlayerControls +import com.profusion.androidenhancedvideoplayer.components.playerOverlay.SeekHandler import com.profusion.androidenhancedvideoplayer.components.playerOverlay.SettingsControlsCustomization import com.profusion.androidenhancedvideoplayer.utils.TimeoutEffect import com.profusion.androidenhancedvideoplayer.utils.fillMaxSizeOnLandscape @@ -38,6 +46,8 @@ import com.profusion.androidenhancedvideoplayer.utils.setStatusBarVisibility private const val MAIN_PACKAGE_PATH_PREFIX = "android.resource://" private const val CURRENT_TIME_TICK_IN_MS = 50L +private const val DEFAULT_SEEK_TIME_MS = 10 * 1000L // 10 seconds + @androidx.annotation.OptIn(UnstableApi::class) @Composable fun EnhancedVideoPlayer( @@ -49,7 +59,8 @@ fun EnhancedVideoPlayer( soundOff: Boolean = true, currentTimeTickInMs: Long = CURRENT_TIME_TICK_IN_MS, controlsCustomization: ControlsCustomization = ControlsCustomization(), - settingsControlsCustomization: SettingsControlsCustomization = SettingsControlsCustomization() + settingsControlsCustomization: SettingsControlsCustomization = SettingsControlsCustomization(), + transformSeekIncrementRatio: (tapCount: Int) -> Long = { it -> it * DEFAULT_SEEK_TIME_MS } ) { val context = LocalContext.current val mainPackagePath = "$MAIN_PACKAGE_PATH_PREFIX${context.packageName}/" @@ -65,7 +76,8 @@ fun EnhancedVideoPlayer( soundOff = soundOff, currentTimeTickInMs = currentTimeTickInMs, controlsCustomization = controlsCustomization, - settingsControlsCustomization = settingsControlsCustomization + settingsControlsCustomization = settingsControlsCustomization, + transformSeekIncrementRatio = { transformSeekIncrementRatio(it) } ) } @@ -80,6 +92,7 @@ fun EnhancedVideoPlayer( soundOff: Boolean = true, currentTimeTickInMs: Long = CURRENT_TIME_TICK_IN_MS, controlsCustomization: ControlsCustomization = ControlsCustomization(), + transformSeekIncrementRatio: (tapCount: Int) -> Long = { it -> it * DEFAULT_SEEK_TIME_MS }, settingsControlsCustomization: SettingsControlsCustomization = SettingsControlsCustomization() ) { val context = LocalContext.current @@ -96,7 +109,6 @@ fun EnhancedVideoPlayer( prepare() } } - var isPlaying by remember { mutableStateOf(exoPlayer.isPlaying) } var hasEnded by remember { mutableStateOf(exoPlayer.playbackState == ExoPlayer.STATE_ENDED) } var isControlsVisible by remember { mutableStateOf(false) } @@ -114,6 +126,10 @@ fun EnhancedVideoPlayer( context.setNavigationBarVisibility(shouldShowSystemUi) } + fun setControlsVisibility(visible: Boolean) { + isControlsVisible = visible + } + DisposableEffect(context) { val listener = object : Player.Listener { override fun onEvents(player: Player, events: Player.Events) { @@ -142,11 +158,6 @@ fun EnhancedVideoPlayer( Box( modifier = Modifier - .clickable( - indication = null, - interactionSource = remember { MutableInteractionSource() }, - onClick = { isControlsVisible = !isControlsVisible } - ) .background(Color.Black) .fillMaxSizeOnLandscape(orientation) .testTag("VideoPlayerParent"), @@ -167,6 +178,18 @@ fun EnhancedVideoPlayer( } } ) + Box(modifier = Modifier.matchParentSize()) { + SeekHandler( + disableSeekForward = hasEnded, + isControlsVisible = isControlsVisible, + exoPlayer = exoPlayer, + controlsCustomization = controlsCustomization, + toggleControlsVisibility = { isControlsVisible = !isControlsVisible }, + setControlsVisibility = ::setControlsVisibility, + transformSeekIncrementRatio = transformSeekIncrementRatio + ) + } + PlayerControls( title = title, isVisible = isControlsVisible, 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 446cdce8..56ae75d8 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 @@ -13,11 +13,14 @@ class ControlsCustomization( val nextIconContent: @Composable () -> Unit = { NextIcon() }, val fullScreenIconContent: @Composable () -> Unit = { FullScreenIcon() }, val exitFullScreenIconContent: @Composable () -> Unit = { ExitFullScreenIcon() }, - val settingsIconContent: @Composable () -> Unit = { SettingsIcon() } + val settingsIconContent: @Composable () -> Unit = { SettingsIcon() }, + val forwardIconContent: @Composable (modifier: Modifier) -> Unit = { ForwardIcon(it) }, + val rewindIconContent: @Composable (modifier: Modifier) -> Unit = { RewindIcon(it) } ) @Composable fun PlayerControls( + modifier: Modifier = Modifier, title: String? = null, isVisible: Boolean, isPlaying: Boolean, @@ -33,8 +36,7 @@ fun PlayerControls( onSpeedSelected: (Float) -> Unit, onSeekBarValueChange: (Long) -> Unit, customization: ControlsCustomization, - settingsControlsCustomization: SettingsControlsCustomization, - modifier: Modifier = Modifier + settingsControlsCustomization: SettingsControlsCustomization ) { PlayerControlsScaffold( modifier = modifier.testTag("PlayerControlsParent"), diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControlsScaffold.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControlsScaffold.kt index 7b56cb77..9473c98f 100644 --- a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControlsScaffold.kt +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/PlayerControlsScaffold.kt @@ -10,7 +10,7 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color +import com.profusion.androidenhancedvideoplayer.styling.Colors @Composable fun PlayerControlsScaffold( @@ -25,7 +25,7 @@ fun PlayerControlsScaffold( enter = fadeIn(), exit = fadeOut(), modifier = modifier - .background(Color.Black.copy(alpha = 0.6f)) + .background(Colors.controlsShadow) ) { Column( modifier = Modifier.fillMaxSize(), 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 3077bab4..d729ce49 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 @@ -107,3 +107,23 @@ fun CheckIcon(modifier: Modifier = Modifier) { modifier = modifier ) } + +@Composable +fun ForwardIcon(modifier: Modifier = Modifier) { + Icon( + painter = painterResource(id = R.drawable.ic_forward), + tint = Color.White, + contentDescription = stringResource(R.string.controls_forward_description), + modifier = modifier.testTag("ForwardIcon") + ) +} + +@Composable +fun RewindIcon(modifier: Modifier = Modifier) { + Icon( + painter = painterResource(id = R.drawable.ic_rewind), + tint = Color.White, + contentDescription = stringResource(R.string.controls_rewind_description), + modifier = modifier.testTag("RewindIcon") + ) +} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/SeekClickableArea.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/SeekClickableArea.kt new file mode 100644 index 00000000..e6cadc1b --- /dev/null +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/SeekClickableArea.kt @@ -0,0 +1,81 @@ +package com.profusion.androidenhancedvideoplayer.components.playerOverlay + +import androidx.compose.foundation.gestures.detectTapGestures +import androidx.compose.foundation.indication +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +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.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.scale +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import com.profusion.androidenhancedvideoplayer.R +import com.profusion.androidenhancedvideoplayer.styling.Dimensions + +private const val TEXT_MAX_LINES = 2 + +@Composable +fun SeekClickableArea( + modifier: Modifier = Modifier, + scaleAnimation: Float, + tapCount: Int, + disableSeekClick: Boolean = false, + onSeekSingleTap: () -> Unit, + onSeekDoubleTap: () -> Unit, + checkIfCanToggleIsControlsVisible: () -> Unit, + getSeekTime: () -> Int, + seekIcon: @Composable (modifier: Modifier) -> Unit +) { + val isTapCountGreaterThanZero = tapCount > 0 + Box( + modifier = Modifier + .fillMaxHeight() + .indication( + indication = null, + interactionSource = remember { MutableInteractionSource() } + ) + .pointerInput(tapCount, disableSeekClick) { + detectTapGestures( + onTap = if (isTapCountGreaterThanZero) { + if (disableSeekClick) { + null + } else { { onSeekSingleTap() } } + } else { + { checkIfCanToggleIsControlsVisible() } + }, + onDoubleTap = if (isTapCountGreaterThanZero) { null } else { + if (disableSeekClick) { + null + } else { + { onSeekDoubleTap() } + } + } + ) + } + .testTag("SeekClickableArea") + .then(modifier), + contentAlignment = Alignment.Center + ) { + if (isTapCountGreaterThanZero) { + val timeLabel = getSeekTime() + Column(horizontalAlignment = Alignment.CenterHorizontally) { + seekIcon(modifier = Modifier.scale(scaleAnimation)) + Spacer(modifier = Modifier.height(Dimensions.large)) + Text( + text = "$timeLabel ${stringResource(id = R.string.controls_time_unit)}", + maxLines = TEXT_MAX_LINES, + color = Color.White + ) + } + } + } +} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/SeekHandler.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/SeekHandler.kt new file mode 100644 index 00000000..fb91c24e --- /dev/null +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/components/playerOverlay/SeekHandler.kt @@ -0,0 +1,157 @@ +package com.profusion.androidenhancedvideoplayer.components.playerOverlay + +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.media3.exoplayer.ExoPlayer +import com.profusion.androidenhancedvideoplayer.styling.Colors +import com.profusion.androidenhancedvideoplayer.utils.JobsHolder +import com.profusion.androidenhancedvideoplayer.utils.executeAfterTimeout +import kotlin.math.max +import kotlin.math.min + +private const val ICON_ANIMATION_DURATION_MS = 650 +private const val ICON_INITIAL_SCALE = 0.8f +private const val ICON_TARGET_SCALE = 1.1f +private const val JOB_TIMEOUT = 650L +private const val TRANSITION_LABEL = "scaleSeekIcon" + +@Composable +fun SeekHandler( + disableSeekForward: Boolean, + isControlsVisible: Boolean, + exoPlayer: ExoPlayer, + toggleControlsVisibility: () -> Unit, + setControlsVisibility: (value: Boolean) -> Unit, + transformSeekIncrementRatio: (tapCount: Int) -> Long, + controlsCustomization: ControlsCustomization +) { + val jobs = JobsHolder + val scope = rememberCoroutineScope() + var forwardTapCount by remember { mutableStateOf(0) } + var rewindTapCount by remember { mutableStateOf(0) } + val isRewinding by remember { derivedStateOf { rewindTapCount > 0 } } + val isForwarding by remember { derivedStateOf { forwardTapCount > 0 } } + + val transition = rememberInfiniteTransition(TRANSITION_LABEL) + + val scale = transition.animateFloat( + initialValue = ICON_INITIAL_SCALE, + targetValue = ICON_TARGET_SCALE, + animationSpec = infiniteRepeatable( + animation = tween( + durationMillis = ICON_ANIMATION_DURATION_MS, + easing = FastOutSlowInEasing + ), + repeatMode = RepeatMode.Restart + ) + ) + + LaunchedEffect(forwardTapCount) { + if (forwardTapCount > 0) { + val incrementTime = transformSeekIncrementRatio(forwardTapCount) - + transformSeekIncrementRatio( + forwardTapCount - 1 + ) + val timeToSeek = exoPlayer.currentPosition + incrementTime + exoPlayer.seekTo(min(exoPlayer.duration, timeToSeek)) + } + } + + LaunchedEffect(rewindTapCount) { + if (rewindTapCount > 0) { + val incrementTime = transformSeekIncrementRatio(rewindTapCount) - + transformSeekIncrementRatio( + rewindTapCount - 1 + ) + val timeToSeek = exoPlayer.currentPosition - incrementTime + exoPlayer.seekTo(max(0, timeToSeek)) + } + } + + fun checkIfCanToggleIsControlsVisible() { + if (!isRewinding && !isForwarding) { + toggleControlsVisibility() + } + } + + fun onForwardSingleTap() { + jobs.seekJob = executeAfterTimeout(scope, jobs.seekJob, JOB_TIMEOUT) { + forwardTapCount = 0 + } + forwardTapCount++ + } + + fun onForwardDoubleTap() { + if (isRewinding) return + setControlsVisibility(false) + onForwardSingleTap() + } + + fun onRewindSingleTap() { + jobs.seekJob = executeAfterTimeout(scope, jobs.seekJob, JOB_TIMEOUT) { + rewindTapCount = 0 + } + rewindTapCount++ + } + + fun onRewindDoubleTap() { + if (isForwarding) return + setControlsVisibility(false) + onRewindSingleTap() + } + + Row( + modifier = Modifier + .fillMaxSize() + .background( + if (isRewinding || isForwarding) { + Colors.controlsShadow + } else { + Color.Transparent + } + ) + .clickable { setControlsVisibility(!isControlsVisible) } + ) { + SeekClickableArea( + modifier = Modifier.weight(1f), + tapCount = rewindTapCount, + scaleAnimation = scale.value, + onSeekDoubleTap = ::onRewindDoubleTap, + onSeekSingleTap = ::onRewindSingleTap, + checkIfCanToggleIsControlsVisible = ::checkIfCanToggleIsControlsVisible, + getSeekTime = { (transformSeekIncrementRatio(rewindTapCount) / 1000).toInt() }, + seekIcon = { controlsCustomization.rewindIconContent(it) } + ) + Spacer(modifier = Modifier.weight(0.2f)) + SeekClickableArea( + modifier = Modifier.weight(1f), + tapCount = forwardTapCount, + scaleAnimation = scale.value, + disableSeekClick = disableSeekForward, + onSeekDoubleTap = ::onForwardDoubleTap, + onSeekSingleTap = ::onForwardSingleTap, + checkIfCanToggleIsControlsVisible = ::checkIfCanToggleIsControlsVisible, + getSeekTime = { (transformSeekIncrementRatio(forwardTapCount) / 1000).toInt() }, + seekIcon = { controlsCustomization.forwardIconContent(it) } + ) + } +} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/styling/Colors.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/styling/Colors.kt new file mode 100644 index 00000000..1aaefe6e --- /dev/null +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/styling/Colors.kt @@ -0,0 +1,7 @@ +package com.profusion.androidenhancedvideoplayer.styling + +import androidx.compose.ui.graphics.Color + +object Colors { + val controlsShadow = Color.Black.copy(alpha = 0.5f) +} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/utils/ExecuteAfterTimeout.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/utils/ExecuteAfterTimeout.kt new file mode 100644 index 00000000..b86a0bb5 --- /dev/null +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/utils/ExecuteAfterTimeout.kt @@ -0,0 +1,27 @@ +package com.profusion.androidenhancedvideoplayer.utils + +import android.util.Log +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +private const val TAG = "EXECUTE_AFTER_TIMEOUT" + +fun executeAfterTimeout( + scope: CoroutineScope, + job: Job?, + timeInMillis: Long, + onJobComplete: () -> Unit +): Job { + job?.cancel() + return scope.launch() { + try { + delay(timeInMillis) + onJobComplete() + } catch (e: CancellationException) { + Log.i(TAG, e.stackTraceToString()) + } + } +} diff --git a/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/utils/JobsHolder.kt b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/utils/JobsHolder.kt new file mode 100644 index 00000000..4a39bd73 --- /dev/null +++ b/androidenhancedvideoplayer/src/main/java/com/profusion/androidenhancedvideoplayer/utils/JobsHolder.kt @@ -0,0 +1,7 @@ +package com.profusion.androidenhancedvideoplayer.utils + +import kotlinx.coroutines.Job + +object JobsHolder { + var seekJob: Job? = null +} diff --git a/androidenhancedvideoplayer/src/main/res/drawable/ic_forward.xml b/androidenhancedvideoplayer/src/main/res/drawable/ic_forward.xml new file mode 100644 index 00000000..42a715f8 --- /dev/null +++ b/androidenhancedvideoplayer/src/main/res/drawable/ic_forward.xml @@ -0,0 +1,5 @@ + + + diff --git a/androidenhancedvideoplayer/src/main/res/drawable/ic_rewind.xml b/androidenhancedvideoplayer/src/main/res/drawable/ic_rewind.xml new file mode 100644 index 00000000..66250677 --- /dev/null +++ b/androidenhancedvideoplayer/src/main/res/drawable/ic_rewind.xml @@ -0,0 +1,5 @@ + + + diff --git a/androidenhancedvideoplayer/src/main/res/values-pt-rBR/controls_strings.xml b/androidenhancedvideoplayer/src/main/res/values-pt-rBR/controls_strings.xml index 9bcaeb4a..38ce4ee8 100644 --- a/androidenhancedvideoplayer/src/main/res/values-pt-rBR/controls_strings.xml +++ b/androidenhancedvideoplayer/src/main/res/values-pt-rBR/controls_strings.xml @@ -1,10 +1,13 @@ Sair da Tela Cheia + Avançar Tela Cheia Próximo Pausar Reproduzir Anterior Repetir + Retroceder Configurações + Segundos diff --git a/androidenhancedvideoplayer/src/main/res/values/controls_strings.xml b/androidenhancedvideoplayer/src/main/res/values/controls_strings.xml index 6d510b07..0b71711f 100644 --- a/androidenhancedvideoplayer/src/main/res/values/controls_strings.xml +++ b/androidenhancedvideoplayer/src/main/res/values/controls_strings.xml @@ -1,10 +1,13 @@ Exit Fullscreen + Forward Fullscreen Next Pause Play Previous Replay + Rewind Settings + Seconds