How to Create an Animation Video with Android Media3 Effects

Artem Bolgov
AIBY
Published in
3 min readNov 7, 2023

With the introduction of the Media3 library in Android, it’s finally possible to edit videos using standard animation and bitmap. You can also apply individual effects directly to the video. Unfortunately, there’s no way to create a video of any length directly on the device using Media3, so you need to use a blank video or any video from the device. So let’s start by creating a blank video. You can add FFmpeg to the project and create a video of your desired length if necessary.

We need a video with empty black frames. First, we need to choose a frame rate: 30 fps, 60 fps, 90 fps, etc. It’s worth considering that not all devices will cope, even with 60 fps. I created a video with a resolution of 1280x1280 at 30 fps so that the device would mount it faster.

Next, let’s create a simple animation lasting a few seconds. For example, we’ll rotate the image.

private fun initAnimation() {
animatorSetTest = AnimatorSet().apply {
val animation = ValueAnimator.ofFloat(0f, 360f).apply {
duration = 2000L
interpolator = LinearInterpolator()
addUpdateListener {
val value = it.animatedValue as Float
animationData = animationData.copy(rotation = value)
}
}
play(animation)
}
}

Let’s add a class to store the rotation value:

data class AnimationData(
val rotation: Float
)

private var animationData: AnimationData = AnimationData(0f)
set(value) {
if (field != value) {
field = value
}
}

To initialize Media3, we need to send the path of the result file to start the transformation of the video:

val transformer = Transformer.Builder(this)
.setTransformationRequest(
TransformationRequest.Builder()
.setVideoMimeType(MimeTypes.VIDEO_H264)
.build()
)
.addListener(object : Transformer.Listener {
override fun onCompleted(composition: Composition, exportResult: ExportResult) {
super.onCompleted(composition, exportResult)
val video = findViewById<VideoView>(R.id.video)
video.post {
video.setVideoPath(pathResult.absolutePath)
video.start()
}
}

override fun onError(composition: Composition, exportResult: ExportResult, exportException: ExportException) {
super.onError(composition, exportResult, exportException)
exportException.printStackTrace()
}
})
.build()


val bitmapOverlay = object : BitmapOverlay() {
override fun getBitmap(presentationTimeUs: Long): Bitmap {
return drawFrame(presentationTimeUs)
}
}

val overlaysBuilder = ImmutableList.Builder<TextureOverlay>()
overlaysBuilder.add(bitmapOverlay)
val overlays: ImmutableList<TextureOverlay> = overlaysBuilder.build()

val inputMediaItem = MediaItem.fromUri(path)
val editedMediaItem = EditedMediaItem.Builder(inputMediaItem)
.setEffects(
Effects(
listOf(),
listOf(OverlayEffect(overlays))
)
)
.build()

transformer.start(editedMediaItem, pathResult.toString())

This is the mechanism we’ll use to draw the animation:

val bitmapOverlay = object : BitmapOverlay() {
override fun getBitmap(presentationTimeUs: Long): Bitmap {
return drawFrame(presentationTimeUs)
}
}

Let’s use a function that will return each frame of the video as a bitmap, draw on each bitmap an animation tied to that millisecond, and then return the modified frame to the video again.

override fun getBitmap(presentationTimeUs: Long): Bitmap {
return drawFrame(presentationTimeUs)
}
private fun drawFrame(presentationTimeUs: Long): Bitmap {
if (animatorSetTest == null) {
initAnimation()
}

val correctTime = TimeUnit.MICROSECONDS.toMillis(presentationTimeUs)
if (correctTime <= 2000) {
animatorSetTest?.currentPlayTime = correctTime
}

val animation = animationData

val mainBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(mainBitmap)

val rotator = Matrix()
rotator.postRotate(animation.rotation, width / 2f, height / 2f)

canvas.drawBitmap(
bitmap1,
rotator,
null
)

return mainBitmap
}

And finally, we get a video of our animation.

Follow the link to see the results: https://github.com/teemiik/VideoEditingWithMedia3/blob/main/test_result.mp4

Thanks for reading, I hope the article will be useful to you in your development!

The full example is available via the link:

--

--