-
-
Save chibaye/2eda6ddaa08f81be0d71048b9d989cb5 to your computer and use it in GitHub Desktop.
Revisions
-
rubenquadros revised this gist
Aug 7, 2022 . 1 changed file with 35 additions and 12 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -40,6 +40,8 @@ private fun VideoPlayer(modifier: Modifier = Modifier) { var bufferedPercentage by remember { mutableStateOf(0) } var playbackState by remember { mutableStateOf(exoPlayer.playbackState) } Box(modifier = modifier) { DisposableEffect(key1 = Unit) { val listener = @@ -53,6 +55,7 @@ private fun VideoPlayer(modifier: Modifier = Modifier) { currentTime = player.currentPosition.coerceAtLeast(0L) bufferedPercentage = player.bufferedPercentage isPlaying = player.isPlaying playbackState = player.playbackState } } @@ -87,16 +90,25 @@ private fun VideoPlayer(modifier: Modifier = Modifier) { isVisible = { shouldShowControls }, isPlaying = { isPlaying }, title = { exoPlayer.mediaMetadata.displayTitle.toString() }, playbackState = { playbackState }, onReplayClick = { exoPlayer.seekBack() }, onForwardClick = { exoPlayer.seekForward() }, onPauseToggle = { when { exoPlayer.isPlaying -> { // pause the video exoPlayer.pause() } exoPlayer.isPlaying.not() && playbackState == STATE_ENDED -> { exoPlayer.seekTo(0) exoPlayer.playWhenReady = true } else -> { // play the video // it's already paused exoPlayer.play() } } isPlaying = isPlaying.not() }, @@ -123,6 +135,7 @@ private fun PlayerControls( totalDuration: () -> Long, currentTime: () -> Long, bufferedPercentage: () -> Int, playbackState: () -> Int, onSeekChanged: (timeMs: Float) -> Unit ) { @@ -145,7 +158,8 @@ private fun PlayerControls( isPlaying = isPlaying, onReplayClick = onReplayClick, onForwardClick = onForwardClick, onPauseToggle = onPauseToggle, playbackState = playbackState ) BottomControls( @@ -191,12 +205,15 @@ private fun TopControl(modifier: Modifier = Modifier, title: () -> String) { private fun CenterControls( modifier: Modifier = Modifier, isPlaying: () -> Boolean, playbackState: () -> Int, onReplayClick: () -> Unit, onPauseToggle: () -> Unit, onForwardClick: () -> Unit ) { val isVideoPlaying = remember(isPlaying()) { isPlaying() } val playerState = remember(playbackState()) { playbackState() } Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceEvenly) { IconButton(modifier = Modifier.size(40.dp), onClick = onReplayClick) { Image( @@ -212,10 +229,16 @@ private fun CenterControls( modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, painter = when { isVideoPlaying -> { painterResource(id = R.drawable.ic_pause) } isVideoPlaying.not() && playerState == STATE_ENDED -> { painterResource(id = R.drawable.ic_replay) } else -> { painterResource(id = R.drawable.ic_play) } }, contentDescription = "Play/Pause" ) -
rubenquadros revised this gist
Aug 7, 2022 . 1 changed file with 12 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,18 +1,8 @@ @Composable private fun VideoPlayer(modifier: Modifier = Modifier) { val context = LocalContext.current val exoPlayer = remember { ExoPlayer.Builder(context) .apply { @@ -40,6 +30,16 @@ fun VideoPlayer(modifier: Modifier = Modifier) { } } var shouldShowControls by remember { mutableStateOf(false) } var isPlaying by remember { mutableStateOf(exoPlayer.isPlaying) } var totalDuration by remember { mutableStateOf(0L) } var currentTime by remember { mutableStateOf(0L) } var bufferedPercentage by remember { mutableStateOf(0) } Box(modifier = modifier) { DisposableEffect(key1 = Unit) { val listener = @@ -52,6 +52,7 @@ fun VideoPlayer(modifier: Modifier = Modifier) { totalDuration = player.duration.coerceAtLeast(0L) currentTime = player.currentPosition.coerceAtLeast(0L) bufferedPercentage = player.bufferedPercentage isPlaying = player.isPlaying } } -
rubenquadros revised this gist
Jul 31, 2022 . 1 changed file with 34 additions and 11 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -11,6 +11,8 @@ fun VideoPlayer(modifier: Modifier = Modifier) { var currentTime by remember { mutableStateOf(0L) } var bufferedPercentage by remember { mutableStateOf(0) } val exoPlayer = remember { ExoPlayer.Builder(context) .apply { @@ -49,6 +51,7 @@ fun VideoPlayer(modifier: Modifier = Modifier) { super.onEvents(player, events) totalDuration = player.duration.coerceAtLeast(0L) currentTime = player.currentPosition.coerceAtLeast(0L) bufferedPercentage = player.bufferedPercentage } } @@ -98,6 +101,7 @@ fun VideoPlayer(modifier: Modifier = Modifier) { }, totalDuration = { totalDuration }, currentTime = { currentTime }, bufferedPercentage = { bufferedPercentage }, onSeekChanged = { timeMs: Float -> exoPlayer.seekTo(timeMs.toLong()) } @@ -117,6 +121,7 @@ private fun PlayerControls( onPauseToggle: () -> Unit, totalDuration: () -> Long, currentTime: () -> Long, bufferedPercentage: () -> Int, onSeekChanged: (timeMs: Float) -> Unit ) { @@ -162,6 +167,7 @@ private fun PlayerControls( ), totalDuration = totalDuration, currentTime = currentTime, bufferedPercentage = bufferedPercentage, onSeekChanged = onSeekChanged ) } @@ -230,25 +236,42 @@ private fun BottomControls( modifier: Modifier = Modifier, totalDuration: () -> Long, currentTime: () -> Long, bufferedPercentage: () -> Int, onSeekChanged: (timeMs: Float) -> Unit ) { val duration = remember(totalDuration()) { totalDuration() } val videoTime = remember(currentTime()) { currentTime() } val buffer = remember(bufferedPercentage()) { bufferedPercentage() } Column(modifier = modifier.padding(bottom = 32.dp)) { Box(modifier = Modifier.fillMaxWidth()) { Slider( value = buffer.toFloat(), enabled = false, onValueChange = { /*do nothing*/}, valueRange = 0f..100f, colors = SliderDefaults.colors( disabledThumbColor = Color.Transparent, disabledActiveTrackColor = Color.Gray ) ) Slider( modifier = Modifier.fillMaxWidth(), value = videoTime.toFloat(), onValueChange = onSeekChanged, valueRange = 0f..duration.toFloat(), colors = SliderDefaults.colors( thumbColor = Purple200, activeTickColor = Purple200 ) ) } Row( modifier = Modifier.fillMaxWidth().padding(top = 16.dp), -
rubenquadros created this gist
Jul 31, 2022 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,293 @@ @Composable fun VideoPlayer(modifier: Modifier = Modifier) { val context = LocalContext.current var shouldShowControls by remember { mutableStateOf(false) } var isPlaying by remember { mutableStateOf(true) } var totalDuration by remember { mutableStateOf(0L) } var currentTime by remember { mutableStateOf(0L) } val exoPlayer = remember { ExoPlayer.Builder(context) .apply { setSeekBackIncrementMs(PLAYER_SEEK_BACK_INCREMENT) setSeekForwardIncrementMs(PLAYER_SEEK_FORWARD_INCREMENT) } .build() .apply { setMediaItem( MediaItem.Builder() .apply { setUri( "https://www.learningcontainer.com/wp-content/uploads/2020/05/sample-mp4-file.mp4" ) setMediaMetadata( MediaMetadata.Builder() .setDisplayTitle("My Video") .build() ) } .build() ) prepare() playWhenReady = true } } Box(modifier = modifier) { DisposableEffect(key1 = Unit) { val listener = object : Player.Listener { override fun onEvents( player: Player, events: Player.Events ) { super.onEvents(player, events) totalDuration = player.duration.coerceAtLeast(0L) currentTime = player.currentPosition.coerceAtLeast(0L) } } exoPlayer.addListener(listener) onDispose { exoPlayer.removeListener(listener) exoPlayer.release() } } AndroidView( modifier = Modifier.clickable { shouldShowControls = shouldShowControls.not() }, factory = { StyledPlayerView(context).apply { player = exoPlayer useController = false layoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) } } ) PlayerControls( modifier = Modifier.fillMaxSize(), isVisible = { shouldShowControls }, isPlaying = { isPlaying }, title = { exoPlayer.mediaMetadata.displayTitle.toString() }, onReplayClick = { exoPlayer.seekBack() }, onForwardClick = { exoPlayer.seekForward() }, onPauseToggle = { if (exoPlayer.isPlaying) { // pause the video exoPlayer.pause() } else { // play the video // it's already paused exoPlayer.play() } isPlaying = isPlaying.not() }, totalDuration = { totalDuration }, currentTime = { currentTime }, onSeekChanged = { timeMs: Float -> exoPlayer.seekTo(timeMs.toLong()) } ) } } @OptIn(ExperimentalAnimationApi::class) @Composable private fun PlayerControls( modifier: Modifier = Modifier, isVisible: () -> Boolean, isPlaying: () -> Boolean, title: () -> String, onReplayClick: () -> Unit, onForwardClick: () -> Unit, onPauseToggle: () -> Unit, totalDuration: () -> Long, currentTime: () -> Long, onSeekChanged: (timeMs: Float) -> Unit ) { val visible = remember(isVisible()) { isVisible() } AnimatedVisibility( modifier = modifier, visible = visible, enter = fadeIn(), exit = fadeOut() ) { Box(modifier = Modifier.background(Color.Black.copy(alpha = 0.6f))) { TopControl( modifier = Modifier.align(Alignment.TopStart).fillMaxWidth(), title = title ) CenterControls( modifier = Modifier.align(Alignment.Center).fillMaxWidth(), isPlaying = isPlaying, onReplayClick = onReplayClick, onForwardClick = onForwardClick, onPauseToggle = onPauseToggle ) BottomControls( modifier = Modifier.align(Alignment.BottomCenter) .fillMaxWidth() .animateEnterExit( enter = slideInVertically( initialOffsetY = { fullHeight: Int -> fullHeight } ), exit = slideOutVertically( targetOffsetY = { fullHeight: Int -> fullHeight } ) ), totalDuration = totalDuration, currentTime = currentTime, onSeekChanged = onSeekChanged ) } } } @Composable private fun TopControl(modifier: Modifier = Modifier, title: () -> String) { val videoTitle = remember(title()) { title() } Text( modifier = modifier.padding(16.dp), text = videoTitle, style = MaterialTheme.typography.h6, color = Purple200 ) } @Composable private fun CenterControls( modifier: Modifier = Modifier, isPlaying: () -> Boolean, onReplayClick: () -> Unit, onPauseToggle: () -> Unit, onForwardClick: () -> Unit ) { val isVideoPlaying = remember(isPlaying()) { isPlaying() } Row(modifier = modifier, horizontalArrangement = Arrangement.SpaceEvenly) { IconButton(modifier = Modifier.size(40.dp), onClick = onReplayClick) { Image( modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, painter = painterResource(id = R.drawable.ic_replay_5), contentDescription = "Replay 5 seconds" ) } IconButton(modifier = Modifier.size(40.dp), onClick = onPauseToggle) { Image( modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, painter = if (isVideoPlaying) { painterResource(id = R.drawable.ic_pause) } else { painterResource(id = R.drawable.ic_play) }, contentDescription = "Play/Pause" ) } IconButton(modifier = Modifier.size(40.dp), onClick = onForwardClick) { Image( modifier = Modifier.fillMaxSize(), contentScale = ContentScale.Crop, painter = painterResource(id = R.drawable.ic_forward_10), contentDescription = "Forward 10 seconds" ) } } } @Composable private fun BottomControls( modifier: Modifier = Modifier, totalDuration: () -> Long, currentTime: () -> Long, onSeekChanged: (timeMs: Float) -> Unit ) { val duration = remember(totalDuration()) { totalDuration() } val videoTime = remember(currentTime()) { currentTime() } Column(modifier = modifier.padding(bottom = 32.dp)) { Slider( modifier = Modifier.fillMaxWidth(), value = videoTime.toFloat(), onValueChange = onSeekChanged, valueRange = 0f..duration.toFloat(), colors = SliderDefaults.colors( thumbColor = Purple200, activeTickColor = Purple200 ) ) Row( modifier = Modifier.fillMaxWidth().padding(top = 16.dp), horizontalArrangement = Arrangement.SpaceBetween ) { Text( modifier = Modifier.padding(horizontal = 16.dp), text = duration.formatMinSec(), color = Purple200 ) IconButton( modifier = Modifier.padding(horizontal = 16.dp), onClick = {} ) { Image( contentScale = ContentScale.Crop, painter = painterResource(id = R.drawable.ic_fullscreen), contentDescription = "Enter/Exit fullscreen" ) } } } } fun Long.formatMinSec(): String { return if (this == 0L) { "..." } else { String.format( "%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(this), TimeUnit.MILLISECONDS.toSeconds(this) - TimeUnit.MINUTES.toSeconds( TimeUnit.MILLISECONDS.toMinutes(this) ) ) } } private const val PLAYER_SEEK_BACK_INCREMENT = 5 * 1000L // 5 seconds private const val PLAYER_SEEK_FORWARD_INCREMENT = 10 * 1000L // 10 seconds