Tag Archives: exoplayer

Media3 1.6.0 — what’s new?

Posted by Andrew Lewis – Software Engineer

This article is cross-published on Medium

Media3 1.6.0 is now available!

This release includes a host of bug fixes, performance improvements and new features. Read on to find out more, and as always please check out the full release notes for a comprehensive overview of changes in this release.


Playback, MediaSession and UI

ExoPlayer now supports HLS interstitials for ad insertion in HLS streams. To play these ads using ExoPlayer's built-in playlist support, pass an HlsInterstitialsAdsLoader.AdsMediaSourceFactory as the media source factory when creating the player. For more information see the official documentation.

This release also includes experimental support for 'pre-warming' decoders. Without pre-warming, transitions from one playlist item to the next may not be seamless in some cases, for example, we may need to switch codecs, or decode some video frames to reach the start position of the new media item. With pre-warming enabled, a secondary video renderer can start decoding the new media item earlier, giving near-seamless transitions. You can try this feature out by enabling it on the DefaultRenderersFactory. We're actively working on further improvements to the way we interact with decoders, including adding a 'fast seeking mode' so stay tuned for updates in this area.

Media3 1.6.0 introduces a new media3-ui-compose module that contains functionality for building Compose UIs for playback. You can find a reference implementation in the Media3 Compose demo and learn more in Getting started with Compose-based UI. At this point we're providing a first set of foundational state classes that link to the Player, in addition to some basic composable building blocks. You can use these to build your own customized UI widgets. We plan to publish default Material-themed composables in a later release.

Some other improvements in this release include: moving system calls off the application's main thread to the background (which should reduce ANRs), a new decoder module wrapping libmpegh (for bundling object-based audio decoding in your app), and a fix for the Cast extension for apps targeting API 34+. There are also fixes across MPEG-TS and WebVTT extraction, DRM, downloading/caching, MediaSession and more.

Media extraction and frame retrieval

The new MediaExtractorCompat is a drop-in replacement for the framework MediaExtractor but implemented using Media3's extractors. If you're using the Android framework MediaExtractor, consider migrating to get consistent behavior across devices and reduce crashes.

We've also added experimental support for retrieving video frames in a new class ExperimentalFrameExtractor, which can act as a replacement for the MediaMetadataRetriever getFrameAtTime methods. There are a few benefits over the framework implementation: HDR input is supported (by default tonemapping down to SDR, but with the option to produce HLG bitmaps from Android 14 onwards), Media3 effects can be applied (including Presentation to scale the output to a desired size) and it runs faster on some devices due to moving color space conversion to the GPU. Here's an example of using the new API:

val bitmap =
    withContext(Dispatchers.IO) {
        val configuration =
            ExperimentalFrameExtractor.Configuration
                .Builder()
                .setExtractHdrFrames(true)
                .build()
        val frameExtractor =
            ExperimentalFrameExtractor(
                context,
                configuration,
            )

        frameExtractor.setMediaItem(mediaItem, /*effects*/ listOf())

        val frame = frameExtractor.getFrame(timestamps).await()
        frameExtractor.release()
        frame.bitmap
    }

Editing, transcoding and export

Media3 1.6.0 includes performance, stability and functional improvements in Transformer. Highlights include: support for transcoding/transmuxing Dolby Vision streams on devices that support this format and a new MediaProjectionAssetLoader for recording from the screen, which you can try out in the Transformer demo app.

Check out Common media processing operations with Jetpack Media3 Transformer for some code snippets showing how to process media with Transformer, and tips to reduce latency.

This release also includes a new Kotlin-based demo app showcasing Media3's video effects framework. You can select from a variety of video effects and preview them via ExoPlayer.setVideoEffects.

Media3 video effect animation
Animation showing contrast adjustment and a confetti effect in the new demo app

Get started sith Media3 1.6.0

Please get in touch via the Media3 issue Tracker if you run into any bugs, or if you have questions or feature requests. We look forward to hearing from you!

Media3 1.5.0 — what’s new?

Posted by Kristina Simakova – Engineering Manager

This article is cross-published on Medium

Media3 1.5.0 is now available!

Transformer now supports motion photos and faster image encoding. We’ve also simplified the setup for DefaultPreloadManager and ExoPlayer, making it easier to use. But that’s not all! We’ve included a new IAMF decoder, a Kotlin listener extension, and easier Player optimization through delegation.

To learn more about all new APIs and bug fixes, check out the full release notes.

Transformer improvements

Motion photo support

Transformer now supports exporting motion photos. The motion photo’s image is exported if the corresponding MediaItem’s image duration is set (see MediaItem.Builder().setImageDurationMs()) Otherwise, the motion photo’s video is exported. Note that the EditedMediaItem’s duration should not be set in either case as it will automatically be set to the corresponding MediaItem’s image duration.

Faster image encoding

This release accelerates image-to-video encoding, thanks to optimizations in DefaultVideoFrameProcessor.queueInputBitmap(). DefaultVideoFrameProcessor now treats the Bitmap given to queueInputBitmap() as immutable. The GL pipeline will resample and color-convert the input Bitmap only once. As a result, Transformer operations that take large (e.g. 12 megapixels) images as input execute faster.

AudioEncoderSettings

Similar to VideoEncoderSettings, Transformer now supports AudioEncoderSettings which can be used to set the desired encoding profile and bitrate.

Edit list support

Transformer now shifts the first video frame to start from 0. This fixes A/V sync issues in some files where an edit list is present.

Unsupported track type logging

This release includes improved logging for unsupported track types, providing more detailed information for troubleshooting and debugging.

Media3 muxer

In one of the previous releases we added a new muxer library which can be used to create MP4 container files. The media3 muxer offers support for a wide range of audio and video codecs, enabling seamless handling of diverse media formats. This new library also brings advanced features including:

    • B-frame support
    • Fragmented MP4 output
    • Edit list support

The muxer library can be included as a gradle dependency:

implementation ("androidx.media3:media3-muxer:1.5.0")

Media3 muxer with Transformer

To use the media3 muxer with Transformer, set an InAppMuxer.Factory (which internally wraps media3 muxer) as the muxer factory when creating a Transformer:

val transformer = Transformer.Builder(context)
    .setMuxerFactory(InAppMuxer.Factory.Builder().build())
    .build()

Simpler setup for DefaultPreloadManager and ExoPlayer

With Media3 1.5.0, we added DefaultPreloadManager.Builder, which makes it much easier to build the preload components and the player. Previously we asked you to instantiate several required components (RenderersFactory, TrackSelectorFactory, LoadControl, BandwidthMeter and preload / playback Looper) first, and be super cautious on correctly sharing those components when injecting them into the DefaultPreloadManager constructor and the ExoPlayer.Builder. With the new DefaultPreloadManager.Builder this becomes a lot simpler:

    • Build a DefaultPreloadManager and ExoPlayer instances with all default components.
val preloadManagerBuilder = DefaultPreloadManager.Builder()
val preloadManager = preloadManagerBuilder.build()
val player = preloadManagerBuilder.buildExoPlayer()

    • Build a DefaultPreloadManager and ExoPlayer instances with custom sharing components.
val preloadManagerBuilder = DefaultPreloadManager.Builder().setRenderersFactory(customRenderersFactory)
// The resulting preloadManager uses customRenderersFactory
val preloadManager = preloadManagerBuilder.build()
// The resulting player uses customRenderersFactory
val player = preloadManagerBuilder.buildExoPlayer()

    • Build a DefaultPreloadManager and ExoPlayer instances, while setting the custom playback-only configurations on the ExoPlayers.
val preloadManagerBuilder = DefaultPreloadManager.Builder()
val preloadManager = preloadManagerBuilder.build()
// Tune the playback-only configurations
val playerBuilder = ExoPlayer.Builder().setFooEnabled()
// The resulting player will have playback feature "Foo" enabled
val player = preloadManagerBuilder.buildExoPlayer(playerBuilder)

Preloading the next playlist item

We’ve added the ability to preload the next item in the playlist of ExoPlayer. By default, playlist preloading is disabled but can be enabled by setting the duration which should be preloaded to memory:

player.preloadConfiguration =
    PreloadConfiguration(/* targetPreloadDurationUs= */ 5_000_000L)

With the PreloadConfiguration above, the player tries to preload five seconds of media for the next item in the playlist. Preloading is only started when no media is being loaded that is required for the ongoing playback. This way preloading doesn’t compete for bandwidth with the primary playback.

When enabled, preloading can help minimize join latency when a user skips to the next item before the playback buffer reaches the next item. The first period of the next window is prepared and video, audio and text samples are preloaded into its sample queues. The preloaded period is later queued into the player with preloaded samples immediately available and ready to be fed to the codec for rendering.

Once opted-in, playlist preloading can be turned off again by using PreloadConfiguration.DEFAULT to disable playlist preloading:

player.preloadConfiguration = PreloadConfiguration.DEFAULT

New IAMF decoder and Kotlin listener extension

The 1.5.0 release includes a new media3-decoder-iamf module, which allows playback of IAMF immersive audio tracks in MP4 files. Apps wanting to try this out will need to build the libiamf decoder locally. See the media3 README for full instructions.

implementation ("androidx.media3:media3-decoder-iamf:1.5.0")

This release also includes a new media3-common-ktx module, a home for Kotlin-specific functionality. The first version of this module contains a suspend function that lets the caller listen to Player.Listener.onEvents. This is a building block that’s used by the upcoming media3-ui-compose module (launching with media3 1.6.0) to power a Jetpack Compose playback UI.

implementation ("androidx.media3:media3-common-ktx:1.5.0")

Easier Player customization via delegation

Media3 has provided a ForwardingPlayer implementation since version 1.0.0, and we have previously suggested that apps should use it when they want to customize the way certain Player operations work, by using the decorator pattern. One very common use-case is to allow or disallow certain player commands (in order to show/hide certain buttons in a UI). Unfortunately, doing this correctly with ForwardingPlayer is surprisingly hard and error-prone, because you have to consistently override multiple methods, and handle the listener as well. The example code to demonstrate how fiddly this is too long for this blog, so we’ve put it in a gist instead.

In order to make these sorts of customizations easier, 1.5.0 includes a new ForwardingSimpleBasePlayer, which builds on the consistency guarantees provided by SimpleBasePlayer to make it easier to create consistent Player implementations following the decorator pattern. The same command-modifying Player is now much simpler to implement:

class PlayerWithoutSeekToNext(player: Player) : ForwardingSimpleBasePlayer(player) {
  override fun getState(): State {
    val state = super.getState()
    return state
      .buildUpon()
      .setAvailableCommands(
        state.availableCommands.buildUpon().remove(COMMAND_SEEK_TO_NEXT).build()
      )
      .build()
  }

  // We don't need to override handleSeek, because it is guaranteed not to be called for
  // COMMAND_SEEK_TO_NEXT since we've marked that command unavailable.
}

MediaSession: Command button for media items

Command buttons for media items allow a session app to declare commands supported by certain media items that then can be conveniently displayed and executed by a MediaController or MediaBrowser:

image of command buttons for media items in the Media Center of android Automotive OS
Screenshot: Command buttons for media items in the Media Center of Android Automotive OS.

You'll find the detailed documentation on android.developer.com.

This is the Media3 equivalent of the legacy “custom browse actions” API, with which Media3 is fully interoperable. Unlike the legacy API, command buttons for media items do not require a MediaLibraryService but are a feature of the Media3 MediaSession instead. Hence they are available for MediaController and MediaBrowser in the same way.


If you encounter any issues, have feature requests, or want to share feedback, please let us know using the Media3 issue tracker on GitHub. We look forward to hearing from you!


This blog post is a part of Camera and Media Spotlight Week. We're providing resources – blog posts, videos, sample code, and more – all designed to help you uplevel the media experiences in your app.

To learn more about what Spotlight Week has to offer and how it can benefit you, be sure to read our overview blog post.

Delivering an immersive sound experience with Spatial Audio

Posted by Nevin Mital - Developer Relations Engineer, Android Media

In Android 13 (API level 33), we introduced a new standardized platform architecture for spatial audio, a premium and more engaging sound experience. With spatial audio, your content sounds more realistic to users by making it sound as though they are in the middle of the action. The individual instruments from a band can be separated and “placed” around the user, or the sound from a whale might grow as it approaches from behind and taper off as it swims away. Read on to learn more about Android’s support for spatial audio and how to implement the feature in your app.

Spatial audio on Android

There are two main distinctions of spatial audio:

  • With static spatial audio, the sounds are anchored to the user and move with them. A bird chirping to their left will always be on their left, no matter how they turn or move their head.
  • With spatial audio with head tracking, the sounds are positioned in the environment around the user. By turning their head to the left, the user will now hear the bird chirping in front of them.
ALT TEXT

On Android, only multi-channel audio configured with the right AudioAttributes and AudioFormat is spatialized by default, though OEMs can customize this behavior. On devices where the OEM has integrated a spatializer effect, static spatial audio will work when any headset is connected to the device, though head-tracked spatial audio requires a headset with compatible head tracking sensors. OEMs like Pixel, OnePlus, and Xiaomi have already made these experiences available to their users.

Implementing & testing spatial audio

The easiest way to integrate with this feature is to use ExoPlayer! If you use ExoPlayer from Jetpack Media3 release 1.0.0 or newer, ExoPlayer will configure the decoder to prevent multi-channel audio from being downmixed to stereo and the default track selection behavior will take into account whether or not spatialization is possible. This means your content just needs to include a multi-channel audio track that ExoPlayer can select. ExoPlayer will monitor the device’s state and select a multi-channel track when spatialization is possible, or switch to a stereo track if not.

Android 12L (API level 32) added the new Spatializer class to allow you to query the spatialization capabilities of the device. There are four conditions that must all be true for the device to output spatialized audio:

// Get an instance of the Spatializer from the AudioManager val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager val spatializer = audioManager.spatializer if ( // Does the device have a spatializer effect? spatializer.immersiveAudioLevel != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE // Is spatial audio enabled in the settings? && spatializer.isEnabled // Is spatialization available, for example for the current audio output routing? && spatializer.isAvailable // Can audio with the given parameters be spatialized? && spatializer.canBeSpatialized(audioAttributes, audioFormat) ) { // Spatialization is possible, so you can select a multi-channel track for playback with // spatial audio. } else { // Spatialization is not possible, so you may choose to select a stereo track for playback // to preserve bandwidth. }

ExoPlayer performs these checks when deciding which audio track to select. To further check if head tracking is available, you can call the isHeadTrackerAvailable() method. The Spatializer class also includes the following listeners to be able to react to changes in the device’s state:

OnSpatializerStateChangedListener

For changes in whether the spatializer is enabled or available.

OnHeadTrackerAvailableListener

For changes in whether head tracking is available.

With these signals, you can manually adjust your playback for spatial audio. Note that if you are not using ExoPlayer, you should make sure to configure the decoder to output multi-channel audio when possible by setting the max channel count to a large number with MediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, ##). See how ExoPlayer does this on GitHub. There are two ways to prevent spatialization depending on your use-case. If your audio is already spatialized, call setIsContentSpatialized(true) when configuring the AudioAttributes for your audio stream to prevent the audio from being double-processed. In all other cases, you can instead call setSpatializationBehavior(AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER) to disable spatialization altogether.

As mentioned previously, using spatial audio requires a supported device (that is, getImmersiveAudioLevel() does not return SPATIALIZER_IMMERSIVE_LEVEL_NONE) and a connected headset. To test spatial audio, start by making sure the feature is enabled in settings:

  • For wired headsets, go to System settings > Sound & vibration > Spatial audio.
  • For wireless headsets, go to System settings > Connected devices > Gear icon for your wireless device > Spatial audio.

Note that for spatial audio with head tracking, the headset must have head tracking sensors that are compatible with the device, such as Pixel Buds Pro with a Pixel phone, and head tracking must also be enabled in settings.

Next steps

Hearing is believing, so we highly recommend trying out spatial audio for yourself! You can see an example implementation in our sample app, Universal Android Music Player. And for more details on everything discussed here, check out our spatial audio developer guide.

A New Universal Music Player

Posted by Nicole Borrelli, Android Developer, Programs Engineer


Screenshot of UAMP v2's UI showing a pair of albums

The Universal Android Music Player (or "UAMP") is a favorite on GitHub for music app developers with over 9,500 stars and 3,000 forks. Since UAMP was first released, Android development has changed significantly. ExoPlayer has improved, Architecture Components were introduced, and Kotlin became a first-class language for Android developers.

We decided that the best way to integrate the modern features for our beloved music app would be to re-write UAMP.

UAMP v2 was built from the ground up in Kotlin. The UI is built around ViewModels and LiveData. Playback, and particularly integration with MediaSessionCompat, was vastly simplified by utilizing the MediaSession extension of ExoPlayer.

We also added a bunch of new songs by The Kyoto Connection and Kai Engel.

There are some features from UAMP v1 that haven't been integrated into the new code yet. The missing features include Android TV with the Leanback library and remote playback via Google Cast. Even though these features aren't yet included in v2, we wanted to show you the new updates as soon as possible. The old code will continue to be available in the v1 branch on GitHub, so please take a look there to see how to use those features in a music app.

We would love your feedback on which features to add next. We are considering offline playback, improving the integration with Android Auto, and using the upcoming Navigation components of Jetpack for the UI. We'll be creating GitHub issues for features and improvements to help you let us know what is most important to you. Go vote on these features to let us know where we should focus our efforts.

We'd also like to invite you to open pull requests for bug fixes and features that are missing. See the contributions process for more information.

Grab the code from GitHub!

ExoPlayer Releases IMA Extension

Users of ExoPlayer, an extensible, open-source media player for Android, can now easily integrate with the IMA SDK using the new ExoPlayer IMA extension. The IMA extension, released alongside ExoPlayer 2.5, wraps the IMA SDK for Android and provides seamless ad playback.

The extension ensures that ads are integrated into ExoPlayer's video timeline, and UI components are ad-aware. It also reduces buffering by eliminating the need to swap out and rebuffer the video player's source when transitioning between ads and content.

You can find more details on the extension in ExoPlayer's blog post on Medium, and find the extension on GitHub. If you have any questions or issues, please file them on ExoPlayer's issue tracker.