Video playing in Android is such a common case it’s hard to avoid working with, let’s take a look at the Compose way of displaying it!
You’ll learn how to:
- Use
ExoPlayer
in Jetpack Compose - Use dedicated Compose
SurfaceView instead of
AndroidView
- Create VideoPlayer in the Compose way
Let’s get started! For those who want to get the final source code, here’s a Github link: AndroidComposeExoPlayerExample
1. Declare the dependencies
In libs.versions.toml
add the dependencies (you can find the most recent Media3 version here):
[versions]
...
media3 = "1.4.1"
[libraries]
...
androidx-media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
2. Apply the dependency
Inside your desired module build.gradle.kts
:
dependencies {
...
implementation(libs.androidx.media3.exoplayer)
}
3. Setup the Composable
For simplicity of this tutorial, the app will start by playing this video displaying “Success” text.
private const val VIDEO_URL = "https://cdn.pixabay.com/video/2015/08/20/468-136808389_large.mp4" | |
@Composable | |
fun PlayerScreen() { | |
val context = LocalContext.current | |
val exoPlayer = remember { | |
ExoPlayer.Builder(context).build().apply { | |
// Configure the player | |
// here I'm making the video loop | |
repeatMode = ExoPlayer.REPEAT_MODE_ALL | |
playWhenReady = true | |
setMediaItem(MediaItem.fromUri(VIDEO_URL)) | |
prepare() | |
} | |
} | |
VideoSurface(modifier = Modifier.fillMaxSize(), exoPlayer = exoPlayer) | |
} | |
@Composable | |
fun VideoSurface(modifier: Modifier = Modifier, exoPlayer: ExoPlayer) { | |
AndroidExternalSurface( | |
modifier = modifier, | |
onInit = { | |
onSurface { surface, _, _ -> | |
exoPlayer.setVideoSurface(surface) | |
surface.onDestroyed { exoPlayer.setVideoSurface(null) } | |
} | |
} | |
) | |
} |
Now, start the app, which will display the VideoSurface
:
We currently have 1 problem: we can only view the video and need to add some controls!
4. Adding controls
Creating custom controls is very easy, what is hard is synchronizing the state with it. Normally, it would be a good idea to place the callbacks inside ViewModel
, for simplicity of this tutorial, I’ll place it inside the Composable:
Job Offers
@Composable | |
fun PlayerScreen() { | |
... | |
var isPlaying by remember { mutableStateOf(true) } | |
Box(modifier = Modifier.fillMaxSize()) { | |
VideoSurface(modifier = Modifier.fillMaxSize(), exoPlayer = exoPlayer) | |
VideoControls( | |
modifier = Modifier.align(Alignment.Center), | |
isPlaying = isPlaying, | |
onClick = { | |
if (isPlaying) { | |
exoPlayer.pause() | |
} else { | |
exoPlayer.play() | |
} | |
isPlaying = !isPlaying | |
} | |
) | |
} | |
} | |
@Composable | |
fun VideoControls( | |
modifier: Modifier = Modifier, | |
isPlaying: Boolean, | |
onClick: () -> Unit, | |
) { | |
IconButton( | |
modifier = modifier, | |
onClick = onClick, | |
) { | |
Icon( | |
painter = painterResource( | |
id = if (isPlaying) { | |
R.drawable.ic_pause | |
} else { | |
R.drawable.ic_play | |
} | |
), | |
contentDescription = null, | |
tint = Color.White, | |
) | |
} | |
} |
Here’s what you’ll get:
It’s easy to extend and add your controls depending on your needs.
Thanks for reading! Please clap and follow me for more!
You can find all the source code here:
This article is previously published by proandroiddev.com