Media3 Transformer is actively under development and we are looking to hear from you! We welcome your feedback, feature requests and bug reports in the issue tracker. Follow the ExoPlayer blog for the latest updates.

Getting started

Getting started with Transformer consists of the following steps:

  1. Add Media3 Transformer as a dependency in your project.
  2. Build a TransformationRequest describing the transformations, such as conversions or edits, you want to apply. Then build a Transformer, passing in the TransformationRequest and a listener for completion and error events.
  3. Start the transformation, passing in the input MediaItem to edit and an output path. During transformation, you can query the current progress or cancel the operation.
  4. When the transformation ends, process the output as needed. For example, sharing to another app or uploading to the server.

Read on for more detail about these steps, and see TransformerActivity in the transformer demo app for a complete example.

Add Media3 Transformer as a dependency

The easiest way to get started using Transformer is to add gradle dependencies on the library in the build.gradle file of your app module:

implementation "androidx.media3:media3-transformer:1.0.2"
implementation "androidx.media3:media3-effect:1.0.2"
implementation "androidx.media3:media3-common:1.0.2"

where 1.0.2 is your preferred version. The latest version can be found by consulting the release notes.

More information on the library modules that are available can be found on the Google Maven AndroidX Media3 page.

Turn on Java 8 support

If not enabled already, you need to turn on Java 8 support in all build.gradle files that depend on Transformer by adding the following to the android section:

compileOptions {
  targetCompatibility JavaVersion.VERSION_1_8
}

Start a transformation

Here's an example of creating and configuring a Transformer instance to remove audio and output H.265/HEVC video, then starting a transformation on inputMediaItem and outputting the result to outputPath.

Kotlin

val transformer = Transformer.Builder(context)
    .setTransformationRequest(
        TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H265).build())
    .setRemoveAudio(true)
    .addListener(transformerListener)
    .build()
val inputMediaItem = MediaItem.fromUri("path_to_input_file")
transformer.startTransformation(inputMediaItem, outputPath)

Java

Transformer transformer =
    new Transformer.Builder(context)
        .setTransformationRequest(
            new TransformationRequest.Builder().setVideoMimeType(MimeTypes.VIDEO_H265).build())
        .setRemoveAudio(true)
        .addListener(transformerListener)
        .build();
MediaItem inputMediaItem = MediaItem.fromUri("path_to_input_file");
transformer.startTransformation(inputMediaItem, outputPath);

For more information about MediaItem, see the ExoPlayer media items page. The input can be a progressive or an adaptive stream, but the output is always a progressive stream. For adaptive inputs, the highest-resolution tracks are always selected for the transformation. The input can be of any container format supported by ExoPlayer, but the output is always an MP4 file.

Multiple transformations can be executed sequentially with the same Transformer instance, but concurrent transformations with the same instance are not supported.

A note on threading

Transformer instances must be accessed from a single application thread, and the listener methods are called on the same thread. For the majority of cases, the application thread can just be the main thread of the application. Internally, Transformer does its work in the background and posts its calls to listener methods on the application thread.

Listen to events

The startTransformation method is asynchronous. It returns immediately and the app is notified of events through the listener passed to the Transformer builder.

Kotlin

val transformerListener: Transformer.Listener =
  object : Transformer.Listener {
    override fun onTransformationCompleted(inputMediaItem: MediaItem, result: TransformationResult) {
      playOutput()
    }

    override fun onTransformationError(inputMediaItem: MediaItem, exception: TransformationException) {
      displayError(exception)
    }
  }

Java

Transformer.Listener transformerListener =
   new Transformer.Listener() {
     @Override
     public void onTransformationCompleted(MediaItem inputMediaItem, TransformationResult result) {
       playOutput();
     }

     @Override
     public void onTransformationError(MediaItem inputMediaItem, TransformationException exception) {
       displayError(exception);
     }
   };

TransformationResult includes information about the output file, including the file size and average bitrates for audio and video, as applicable.

Get progress updates

Call Transformer.getProgress to query the current progress of a transformation. The returned value indicates the progress state. If the progress state is PROGRESS_STATE_AVAILABLE, then the provided ProgressHolder is updated with the current progress percentage. The following example shows how to periodically query the progress of a transformation, where the updateProgressInUi method can be implemented to update a progress bar.

Kotlin

transformer.startTransformation(inputMediaItem, outputPath)
val progressHolder = ProgressHolder()
mainHandler.post(
  object : Runnable {
    override fun run() {
      @ProgressState val progressState = transformer.getProgress(progressHolder)
      updateProgressInUi(progressState, progressHolder)
      if (progressState != Transformer.PROGRESS_STATE_NO_TRANSFORMATION) {
        mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500)
      }
    }
  }
)

Java

transformer.startTransformation(inputMediaItem, outputPath);
ProgressHolder progressHolder = new ProgressHolder();
mainHandler.post(
   new Runnable() {
     @Override
     public void run() {
       @ProgressState int progressState = transformer.getProgress(progressHolder);
       updateProgressInUi(progressState, progressHolder);
       if (progressState != PROGRESS_STATE_NO_TRANSFORMATION) {
         mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500);
       }
     }
   });

Cancel a transformation

If the user chooses to back out of an export flow, cancel the transformation with Transformer.cancel. Resources like hardware video codecs are limited, especially on lower-end devices, so it's important to cancel transformations to free up resources if the output isn't needed.