gradle dependencies
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation "androidx.media3:media3-ui:1.2.0"
implementation "androidx.compose.runtime:runtime-livedata:1.5.4"
Variables
var fullScreenDialog: Dialog? = null
fun Context.findActivity(): Activity? = when (this) {
is Activity -> this
is ContextWrapper -> baseContext.findActivity()
else -> null
}
ExoPlayer composable
@Composable
fun ExoPlayer(
viewModel: LearnViewModel,
context: Context,
onFinish: () -> Unit,
) {
val playerView = remember { mutableStateOf(PlayerView(context)) }
val lifecycleOwner = LocalLifecycleOwner.current
var lifecycle by remember { mutableStateOf(Lifecycle.Event.ON_CREATE) }
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
lifecycle = event
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer = observer)
}
}
LaunchedEffect(Unit) {
while (true) {
delay(500)
if (viewModel.player.duration != -1L) {
if (viewModel.player.duration / 100 == viewModel.player.currentPosition / 100) {
exitFullScreen(
playerView = playerView.value,
context = context,
player = viewModel.player
)
if (fullScreenDialog != null)
fullScreenDialog?.dismiss()
onFinish()
playerView.value.player?.pause()
}
}
}
}
Box {
AndroidView(
factory = { context ->
PlayerView(context).also {
it.player = viewModel.player
it.setShowNextButton(false)
it.setShowPreviousButton(false)
it.setShowFastForwardButton(false)
it.setShowRewindButton(false)
playerView.value = it
}
},
update = {
when (lifecycle) {
Lifecycle.Event.ON_PAUSE -> {
it.onPause()
it.player?.pause()
}
Lifecycle.Event.ON_RESUME -> {
it.onResume()
}
else -> Unit
}
},
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16 / 9f, matchHeightConstraintsFirst = true)
)
Image(
painterResource(R.drawable.maximize),
contentDescription = "",
modifier = Modifier
.height(24.dp)
.width(24.dp)
.clickable(
enabled = true,
onClick = {
enterFullScreen(
playerView = playerView.value,
context = context,
player = viewModel.player,
) {
exitFullScreen(
playerView = playerView.value,
context = context,
player = viewModel.player
)
}
}
),
)
}
}
full screen function
private fun enterFullScreen(
playerView: PlayerView,
context: Context,
player: Player,
backPress: () -> Unit
) {
context.findActivity()?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
val fullScreenPlayerView = FullScreenPlayerView(context)
fullScreenDialog =
object : Dialog(context, android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
@Deprecated("Deprecated in Java")
@androidx.annotation.OptIn(UnstableApi::class)
override fun onBackPressed() {
backPress()
super.onBackPressed()
}
}
fullScreenDialog?.addContentView(
fullScreenPlayerView,
ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
)
fullScreenDialog?.show()
fullScreenPlayerView.setShowNextButton(false)
fullScreenPlayerView.setShowPreviousButton(false)
fullScreenPlayerView.setShowFastForwardButton(false)
fullScreenPlayerView.setShowRewindButton(false)
val playbackParameters = PlaybackParameters(1f)
player.playbackParameters = playbackParameters
fullScreenPlayerView.setFullscreenButtonClickListener { full ->
if (full)
exitFullScreen(
playerView = playerView,
context = context,
player = player
)
fullScreenDialog?.dismiss()
}
PlayerView.switchTargetView(player, playerView, fullScreenPlayerView)
}
minimize screen function
private fun exitFullScreen(player: Player, playerView: PlayerView, context: Context) {
context.findActivity()?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
val fullScreenPlayerView = FullScreenPlayerView(context)
PlayerView.switchTargetView(player, fullScreenPlayerView, playerView)
}