Images

ExoPlayer supports the following image formats. See Image Loading Libraries for how to integrate with external libraries that may provide support for a different set of formats.

Image format Supported Notes
BMP YES
GIF NO No Extractor support
JPEG YES
JPEG Motion Photo YES Still image and video supported
JPEG Ultra HDR YES Falls back to SDR before Android 14 or on non-HDR displays
PNG YES
WebP YES
HEIF/HEIC YES
HEIC Motion Photo Partially Only still image supported*
AVIF (baseline) YES Decoded on Android 14+ only

* The video part of HEIC motion photos can be obtained with MetadataRetriever and played as a standalone file.

Using MediaItem

To play an image as part of a playlist, create a MediaItem with the image URI and pass it to the player. The MediaItem must have a imageDurationMs to specify for how long the image should be displayed.

Kotlin

// Create a player instance.
val player = ExoPlayer.Builder(context).build()
// Set the media item to be played with the desired duration.
player.setMediaItem(
    MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build())
// Prepare the player.
player.prepare()

Java

// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media item to be played with the desired duration.
player.setMediaItem(
    new MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build());
// Prepare the player.
player.prepare();

Motion Photos

Motion photos are files combining a still image with a short video.

  • If the image duration is defined with setImageDuration, the motion photo is displayed for the declared duration as a still image.
  • If the image duration is undefined, the motion photo is played as a video.

Using ProgressiveMediaSource

For more customization options, you can create a ProgressiveMediaSource and pass it directly to the player instead of a MediaItem.

Kotlin

// Create a data source factory.
val dataSourceFactory = DefaultHttpDataSource.Factory()
// Create a media item with the image URI and the desired duration.
val mediaItem =
    MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build()
// Create a progressive media source for this media item.
val mediaSource =
    ProgressiveMediaSource.Factory(dataSourceFactory)
        .createMediaSource(mediaItem)
// Create a player instance.
val player = ExoPlayer.Builder(context).build()
// Set the media source to be played.
player.setMediaSource(mediaSource)
// Prepare the player.
player.prepare()

Java

// Create a data source factory.
DataSource.Factory dataSourceFactory = new DefaultHttpDataSource.Factory();
// Create a media item with the image URI and the desired duration.
MediaItem mediaItem =
    new MediaItem.Builder().setUri(imageUri).setImageDurationMs(2000).build();
// Create a progressive media source for this media item.
MediaSource mediaSource =
    new ProgressiveMediaSource.Factory(dataSourceFactory)
        .createMediaSource(mediaItem);
// Create a player instance.
ExoPlayer player = new ExoPlayer.Builder(context).build();
// Set the media source to be played.
player.setMediaSource(mediaSource);
// Prepare the player.
player.prepare();

Customizing playback

ExoPlayer provides multiple ways for you to tailor playback experience to your app's needs. See the Customization page for examples.

Image Loading Libraries

Images are often managed by external image loading libraries, for example Glide or Coil.

Integrating these libraries into the playback pipeline requires 3 steps:

  1. Define a MediaItem with APPLICATION_EXTERNALLY_LOADED_IMAGE MIME type.
  2. Provide an image decoder to retrieve a Bitmap from the image loading library.
  3. Provide an external loader to trigger caching and preloading.

MediaItem with externally loaded image MIME type

The MediaItem added to the Player must define the APPLICATION_EXTERNALLY_LOADED_IMAGE MIME type explicitly to use the image loading library code paths:

Kotlin

val mediaItem =
  MediaItem.Builder()
    .setUri(imageUri)
    .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)
    .build()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setUri(imageUri)
        .setMimeType(MimeTypes.APPLICATION_EXTERNALLY_LOADED_IMAGE)
        .build();

Image decoder using an image loading library

The image renderer needs an ExternallyLoadedImageDecoder to retrieve the Bitmap from the Uri. This decoder can be provided by overriding DefaultRenderersFactory.getImageDecoderFactory.

The following example uses Glide to load an image:

Kotlin

val glideImageDecoderFactory: ImageDecoder.Factory =
  ExternallyLoadedImageDecoder.Factory { request: ExternalImageRequest ->
    GlideFutures.submit(Glide.with(context).asBitmap().load(request.uri))
  }
val player: Player =
  ExoPlayer.Builder(context)
    .setRenderersFactory(
      object : DefaultRenderersFactory(context) {
        override fun getImageDecoderFactory(): ImageDecoder.Factory {
          return glideImageDecoderFactory
        }
      }
    )
    .build()

Java

ImageDecoder.Factory glideImageDecoderFactory =
    new ExternallyLoadedImageDecoder.Factory(
        request -> GlideFutures.submit(
            Glide.with(context).asBitmap().load(request.uri)));
Player player =
    new ExoPlayer.Builder(context)
        .setRenderersFactory(
            new DefaultRenderersFactory(context) {
              @Override
              protected ImageDecoder.Factory getImageDecoderFactory() {
                return glideImageDecoderFactory;
              }
            })
        .build();

Image preloading with an image loading library

During playback, the player requests to preload the next image once the previous item in the playlist has fully loaded. When using an external image loading library, you must specify an ExternalLoader to trigger this preloading. If no preloading is possible or required, this loader still needs to be provided, but can do nothing.

The following example uses Glide to ensure that the requested image is preloaded to disk:

Kotlin

val glidePreloader = ExternalLoader { request: LoadRequest ->
  GlideFutures.submit(
    Glide.with(context)
      .asFile()
      .apply(
        RequestOptions.diskCacheStrategyOf(DiskCacheStrategy.DATA)
          .priority(Priority.HIGH)
          .skipMemoryCache(true)
      )
      .load(request.uri)
  )
}

Java

ExternalLoader glidePreloader =
    request ->
        GlideFutures.submit(
            Glide.with(context)
                .asFile()
                .apply(
                    diskCacheStrategyOf(DiskCacheStrategy.DATA)
                        .priority(Priority.HIGH)
                        .skipMemoryCache(true))
                .load(request.uri));