# Optimize media playback and maximize reach with Jetpack Media3 APIs - Speakers: Donovan McMurray (Developer Relations Engineer), Toni Heidenreich (Software Engineer), Kristina Simakova (Engineering Manager) - Track: Apps foundation - Video: https://www.youtube.com/watch?v=zFs0FLMGAeM - Page: https://developer.android.com/events/dev-summit/talks/optimize-media-playback-and-maximize-reach-with-jetpack-media3-apis ## About the talk Media consumption accounts for a large portion of user time spent on mobile devices, which requires the playback experience to be flawless, highly efficient and mindful of battery life. Furthermore, users expect these experiences to seamlessly transition across their devices, from phones to living room TVs and beyond. Explore how architectural updates in Media3 and ExoPlayer power seamless playback across form factors. The discussion centers on crafting responsive interfaces with the Media3 Compose UI toolkit, eliminating buffering in feeds using the new PreloadManager and extending your app to living room screens and Android XR with CastPlayer and spatial audio. ## Summary - Media3 UI Compose provides ready-made player composables and customizable building blocks. You can override specific control slots while letting the library manage player state and transitions. - A newly rewritten CastPlayer replaces the standalone Cast SDK. It implements the standard player interface and automatically delegates playback between local ExoPlayer instances and remote Cast devices. - The rebuilt editing pipeline decouples from OpenGL and manages hardware buffers directly. This zero-copy approach lowers memory overhead, and decoupling lets developers integrate modern graphic frameworks like WebGPU. - Media3 adds out-of-the-box support for Eclipsa Video and Eclipsa Audio on Android 17 and later. Prescriptive HDR metadata adapts highlights to ambient light, while spatial audio handles multiple mixed presentations. - Built-in optimizations like PCM audio offload reduce screen-off power consumption by up to 20%. The upcoming CodecDB uses benchmark metrics to select device-optimal encoder settings. ## Resources - [Media3 GitHub repository](https://github.com/androidx/media) - [Media3 developer documentation](https://goo.gle/media3) ## Topics jetpack-media3, exoplayer, jetpack-compose, google-cast, video-editing, audio, performance ## Transcript ### Introduction to Jetpack Media3 [00:00](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=0s) Okay. [00:05](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=5s) Welcome everyone. [00:07](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=7s) My name is Toni. [00:08](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=8s) I'm an engineer on the Android media team. [00:10](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=10s) And together with Kristina and Donovan, we're going to talk about how Jetpack Media3 can help you with all your media needs on Android. [00:18](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=18s) Media3 is Android's support library for everything media-related, whether it's playback with ExoPlayer, sharing media with media session and Cast, or ed-- editing media with transformer, or inspecting media, like extracting frames or metadata. [00:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=36s) Media3 covers this entire space in a single library group. [00:42](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=42s) As a support library, it's bundled into your app. [00:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=45s) That means we can ensure consistency, so the same code running on all OS versions and all devices. [00:52](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=52s) It also helps with updatability. [00:54](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=54s) Every bug fix we do reaches down to API 24, and it's not tied to any OS release cycle. [01:00](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=60s) And as a support library, we can allow very rich customization options, which would not be possible in the Android framework. [01:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=69s) And as a library on GitHub, we are open-source first. [01:12](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=72s) This means every change we do is immediately available on GitHub. [01:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=77s) We have an active issue tracker, and we are accepting pull requests. [01:22](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=82s) The main thing to highlight here, across all these use cases, we really care about all levels of detail, from high-level UI elements to simple out-of-the-box APIs, but also deep customization options, a wide support for various formats and standards, and the performance and efficiency underpinning it all. [01:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=103s) Media3 covers this entire space. [01:46](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=106s) Today, we're trying to dive deeper in some of these topics. [01:48](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=108s) We're going to talk about our new Compose UI module, the new CastPlayer that makes sharing with media session even easier, a revamped editing pipeline with new advanced effects, new tools to retrieve metadata and frames, some new streaming technology, in particular, Eclipsa video and audio, and a number of quality of experience utilities for preloading, scrubbing, and battery savings. ### Media UI with Jetpack Compose [02:14](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=134s) Now, let's dive straight into the first topic, how Media3 can help build UI elements for media. [02:21](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=141s) We've recently published two new modules: Media3 UI Compose and Media3 UI Compose Material 3. [02:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=151s) Here, you can see a high-level high-level player composable, as it was launched in Media3 1.10 with no further modifications. ```kotlin @Composable fun videoScreen() { Player(viewModel.exoPlayer) } ``` [02:39](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=159s) It works out of the box. [02:41](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=161s) It only needs a Media3 player. [02:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=163s) In the example here, it's an ExoPlayer, which you would usually have in your view model. [02:48](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=168s) But it could be something else, like a CastPlayer, a media controller, or any other player you have in your app. [02:54](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=174s) It's worth highlighting that ExoPlayer here is the source of truth. [02:57](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=177s) It contains all the information, and the player composable takes care of rendering the UI and wiring eve-- everything up. [03:06](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=186s) But what if you want to customize this UI, new buttons, different positions, different styles, text overlays? [03:13](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=193s) The player composable is made out of building blocks. [03:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=197s) You can replace them. [03:18](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=198s) You can customize each indiv-- individual block, or you can use them stand-alone elsewhere in your app. [03:25](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=205s) For example, you can customize the top, center, and `bottomControls` separately. [03:30](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=210s) You can also customize how the shutter looks like, which is shown when videos are not playing, or an error overlay. [03:38](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=218s) But this is just the first layer. [03:39](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=219s) The default center and `bottomControls` have also more detailed slots you can customize. [03:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=224s) For example, you could swap out the central button while leaving everything else as it is, or add elements above or below the slider. [03:53](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=233s) Media3 Compose provides all the building blocks you need to do this. [03:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=238s) For example, we have a number of buttons, a play play/pause button, repeat mode, shuffle mode, mute button, and others. [04:06](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=246s) Then, there are progress indicators, like the slider and the position text shown on the screen. [04:13](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=253s) There are a number of containers and overlays as well. [04:16](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=256s) The high-level player composable I've shown you is one example, but there are more examples, like error overlays or subtitles, and many more. [04:25](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=265s) In addition, we have other useful components, like this mini controller here. [04:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=269s) You can place in a bottom sheet while the user is navigating around your app. [04:34](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=274s) And we are not done yet. [04:35](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=275s) We're actively extending this library further, for example, with new editing controls like a clipping slider coming soon. [04:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=284s) Now, let's see this customization in action to see how these building blocks are used. ```kotlin Player(viewModel.exoPlayer) ``` [04:48](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=288s) Let's say we want to move the position and duration text, which are currently next to the slider, above it. ```kotlin Player( player = viewModel.exoPlayer, bottomControls = { PlayerDefaults.BottomControls( player, left = {}, right = {}, above = { PositionAndDurationText( player, separator = " • ") }, ) }, ) ``` [04:55](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=295s) So, what we can do is we can override these `bottomControls`. [04:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=298s) We can still use the default component, but we set the left and the right slot to empty to make them disappear. [05:04](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=304s) And instead, we place the position and duration text above it in the above slot with a custom separator to make it look more interesting. [05:13](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=313s) And now, let's say we want to remove the `centerControls` overlay entirely and, instead, place a play button right above the slider with a speed control and a mute button. ```kotlin Player( player = viewModel.exoPlayer, centerControls = {}, bottomControls = { ... above = { Row(Modifier.fillMaxWidth()) { PositionAndDurationText(...) Spacer(Modifier.weight(1f)) PlaybackSpeedToggleButton(player) MuteButton(player) PlayPauseButton(player) } }, ) } ``` [05:24](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=324s) And that's exactly the same idea again. [05:26](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=326s) We can set the `centerControls` to empty to make them disappear, and we can expand the slot above the slider with some extra space and the three buttons we want to show. [05:38](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=338s) Note that these these buttons are from the library itself, so you don't have to put any extra code into it. [05:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=344s) That's all you need. [05:46](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=346s) And you can continue down this path for styling these elements as well. [05:51](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=351s) If you want to change the mute button icon, let's say, but keep its functionality, it works in the same way as well. ```kotlin Player( ... bottomControls = { ... above = { ... MuteButton(player, painter = { painterResource( if (showMuted) R.drawable.muted else R.drawable.unmuted )}) } }, ) ``` [05:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=358s) As you can see here, you can def-- define a custom painter for the button and choose the icons yourself. [06:04](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=364s) The component provides with some provides you with some metadata, so all you need to do is, like, look at the state, like `showMuted` in this example, to pick the right icon. [06:14](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=374s) And this is really the main benefit of Media3 Compose. [06:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=377s) You can focus on the look and feel of your app, and Media3 takes care of the rest. [06:21](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=381s) It reads the state from the player, it interacts with the player, and handles all the transitions and animations in between. ### Sharing playback with MediaSession [06:30](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=390s) Now let's move on from UI to sharing media. [06:35](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=395s) You can share your playback state with other apps on the same device or connected to remote devices. [06:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=403s) The central way to publish your ongoing playback is with the `MediaSession`. [06:47](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=407s) It makes the state and the metadata of your playback available to others and lets them control your playback. [06:53](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=413s) This is how Gemini, the system UI media notification, Cast, Wear OS, or Android Auto companion apps talk to your app. [07:01](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=421s) `MediaSession` wraps a Media3 player, and it's super easy to create one once you have a player. ```kotlin MediaSession.Builder(context, player).build() ``` [07:10](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=430s) Again, this could be an ExoPlayer or any other player you have in your app, just like with the Compose UI. [07:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=437s) If you're playing in the background, you would usually host this inside a `MediaSessionService` or a MediaLibraryService, which automatically handles all the life cycle for you as well and things like a notification. ```kotlin class PlaybackService : MediaSessionService() { private lateinit var mediaSession: MediaSession override fun onGetSession( info: ControllerInfo) : MediaSession? = mediaSession } ``` [07:28](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=448s) But if you have a media app, you likely know all of this already. [07:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=451s) The exciting new thing I want to show you is our newly rewritten CastPlayer. [07:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=457s) Google Cast allows you to transfer your local playback to a remote device, like Android TVs, Chromecast, or smart speakers. [07:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=465s) We used to publish a separate Cast SDK, which is now in maintenance mode. [07:50](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=470s) Instead, Media3 Cast replaces it entirely because it provides a much simpler and more streamline streamlined integration. [07:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=478s) The most important element here is the CastPlayer, which implements the same standard player interface like ExoPlayer. [08:05](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=485s) While ExoPlayer is for local playback, CastPlayer is a delegating instance that that can either play locally via ExoPlayer or remotely via the Cast protocol. [08:14](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=494s) And it takes care of everything. [08:16](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=496s) It listens to the output device changes, it establishes the connection, it synchronizes the state, and transfers the playback for you when needed. [08:25](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=505s) And because of that, integrating CastPlayer into your app is super simple. ```kotlin val player = ExoPlayer.Builder(context).build() mediaSession = MediaSession.Builder(context,player) .build() ``` [08:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=509s) Instead of creating an ExoPlayer and sharing it with `MediaSession` like this, you use a CastPlayer instead, and that's it. ```kotlin Cast.getSingletonInstance(context).initialize() val player = CastPlayer.Builder(context).build() mediaSession = MediaSession.Builder(context,player) .build() ``` [08:39](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=519s) The only other thing you need to do is initialize the Cast system before it's used somewhere in your app. [08:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=525s) Note that this CastPlayer builds an ExoPlayer internally for the local playback, so to customize the local player further, you could also build your own ExoPlayer and pass it in as a local player to this CastPlayer. ```kotlin Cast.getSingletonInstance(context).initialize() val exoPlayer = ExoPlayer.Builder(context).build() val player = CastPlayer.Builder(context) .setLocalPlayer(exoPlayer).build() mediaSession = MediaSession.Builder(context,player) .build() ``` [08:59](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=539s) And this is again the same Media3 principle. [09:01](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=541s) We provide the simple out-of-the-box API building blocks, but also all the customization options you need to customize it further. [09:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=551s) And you may be wondering how the user actually switches between local and remote playback in this setup. [09:16](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=556s) It already works out of the box with the system output switcher dialogue in the media notification. [09:21](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=561s) It's the little button in the corner there. [09:26](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=566s) But what if you want to show a cast button in your playback screen to make it more discoverable? [09:32](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=572s) Remember this customized Compose UI we've just built a few minutes ago? ```kotlin Player( exoPlayer, centerControls = {} bottomControls = { /* customized */ }, ) ``` [09:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=576s) Media3 Cast also provides an out-of-the-box button you can place there. [09:40](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=580s) It's as simple as adding the ready-made `MediaRouteButton` there to the `topControls`, for example, and we don't need any further parameters to set up, that's that's it really. ```kotlin Player( castPlayer, centerControls = {} bottomControls = { /* customized */ }, topControls = { PlayerDefaults.TopControls(castPlayer) { MediaRouteButton( Modifier.align(Alignment.TopEnd)) } }, ) ``` [09:50](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=590s) And clicking this button opens the same system output switcher dialogue, and then CastPlayer transfers the playback for you. [10:01](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=601s) And you also note that I connected the CastPlayer to the UI here to make sure whatever happens remotely is reflected in your local UI. [10:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=609s) And with that, I hand over to Kristina to talk about editing use cases. ### Media3 editing APIs [10:16](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=616s) Thank you. [10:19](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=619s) Hi, now let's move to editing and advanced effects. [10:22](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=622s) Media3 APIs consist of multiple components. [10:28](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=628s) First, Transformer is the main entry point for our editing APIs. [10:32](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=632s) Transformer processes, transcodes, and exports media to a file. [10:38](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=638s) Next is CompositionPlayer, which renders real-time previews of your media compositions. [10:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=645s) This lets you preview and fine-tune your edits before your final export. [10:52](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=652s) Then we have effects. [10:54](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=654s) It gives you full control over frame-by-frame processing whenever you're applying simple visual effects or adding graphic overlays. [11:04](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=664s) Lastly, we have Muxer. [11:07](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=667s) It serves as the final output stage in our pipeline, taking processed audio, video, and metadata tracks, and writing them into standardized MP4 containers. [11:20](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=680s) We've spent the past year rethinking and rebuilding our video editing pipeline from the ground up, and today, I'm excited to share the major enhancements. [11:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=691s) Why did we rebuild the pipeline? [11:33](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=693s) So the legacy architecture presented several challenges. [11:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=697s) First, it couldn't propagate metadata across all the processing steps. [11:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=704s) It was deeply coupled to OpenGL, which limited flexibility for modern graphic tools. [11:52](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=712s) And its complex API made customization and maintenance a steep hurdle for developers. [12:00](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=720s) To address these challenges, we built a brand new processing pipeline. [12:05](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=725s) First, assembling complex multi-asset compositions is much simpler now. [12:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=731s) Second, by decoupling from OpenGL, you have full freedom to use modern graphic frameworks, like WebGPU, for example. [12:21](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=741s) And finally, Media3 now manages the decoder and encoder directly, ensuring greater consistency and efficiency across all devices. [12:33](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=753s) Let's take a look under the hood at how this pipeline operates. [12:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=757s) Decoders output frames directly into hardware buffers managed by Media3. [12:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=765s) The Frame Aggregator coordinates frames across multiple sequences, giving your custom frame processor direct access to read and process those buffers from sending them to encoder. [13:02](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=782s) The `FrameProcessor` interface serves as the primary extension point for app-level processing. ```java public interface FrameProcessor extends AutoCloseable { interface Listener { void onWakeup(); void onError( VideoFrameProcessingException exception); void onFrameProcessed( Frame frame, @Nullable SyncFenceWrapper onCompleteFence); } interface Factory { FrameProcessor create( FrameWriter output, Executor listenerExecutor, Listener listener); } boolean queue(List frames); void signalEndOfStream(); } ``` [13:10](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=790s) Through the listener interface, your application notifies Media3 whenever a frame has been processed, passing along synchronization fences, so Media3 knows exactly when buffers can be safely reused. [13:25](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=805s) A note about unified memory: by having Media3 take ownership of hardware buffers, your app can read and modify data directly in place. [13:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=816s) This zero-copy approach reduces memory overhead and keeps processing ultra-fast. [13:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=824s) One developer taking advantage of the new editing APIs is Adobe. [13:50](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=830s) The Adobe Premiere team used Media3 editing APIs as the foundation of their media editing infrastructure to ensure greater compatibility across Android ecosystem, while maintaining the flexibility they need to use their own custom pipeline and WebGPU. [14:10](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=850s) Next, I am excited to announce that we are releasing new effects as part of the Media3 1.12 release. [14:18](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=858s) Starting with video enhancements. [14:23](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=863s) Under the hood, our architecture relies on modular setup. [14:27](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=867s) Your application integrates with Media3 effect Play Services, which interfaces with Play Services Enhancement module. [14:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=877s) The Video Enhance module includes tonemapping, denoising, deblurring, and super resolution capabilities. [14:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=885s) Integrating Video Enhance into your pipeline takes just a few lines of code. ```kotlin val frameProcessorFactory = EnhancementSessionDecorator.Builder(/* context = */ this, baseGlFactory) .setTonemappingEnabled(true) .setDeblurAndDenoiseEnabled(true) .setUpscaleVideoEnabled(true) .build() val transformerBuilder = Transformer.Builder(/* context = */ this) .setFrameProcessorFactory(frameProcessorFactory) ``` [14:51](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=891s) Using `EnhancementSessionDecorator.Builder`, you can selectively enable capabilities like tonemapping, deblurring, or upscaling, and pass the resulting `frameProcessorFactory` and hardware buffer helpers into `Transformer.Builder`. [15:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=909s) Another exciting effect coming in Media3 1.12 is Studio Sound. [15:16](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=916s) Studio Sound automatically analyzes your video audio track and separates it into individual stems like speech, ambient noise, and music. [15:26](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=926s) Creators can easily dial dial down wind or crowd distractions while bringing clear dialogue and music to fore forefront. [15:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=936s) To integrate Studio Sound, pass the Studio Sound audio processor inside an effect object. ```kotlin val editedMediaItem = EditedMediaItem.Builder(MediaItem.fromUri(media.uri)) .setEffects( Effects( listOf( soundSeparationAudioProcessor ), emptyList(), // video effects ) ) .build() ``` [15:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=943s) This attaches the audio processor to your media item. [15:50](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=950s) When creating Studio Sound audio processor, attach the listener callback to receive the analyzed audio audio channel mapping via `onResult`. ```kotlin val soundSeparationAudioProcessor = SoundSeparationAudioProcessor( getApplication(), mainExecutor, object : SoundSeparationAudioProcessor.Listener { override fun onResult(channelToAudioType: SparseIntArray) { for (i in 0 until channelToAudioType.size()) { val channelIndex = channelToAudioType.keyAt(i) val audioType = channelToAudioType.valueAt(i) val label = toAudioTypeName(audioType) detectedChannelVolumes.add(ChannelVolume(channelIndex, audioType, label)) } } }, ) ``` [15:59](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=959s) Iterate through each channel to extract the channel index, identified audio type, and readable label when the result of the operation is available. [16:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=971s) So, let's hear Studio Sound in action. [16:15](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=975s) So, the first effect. Baby, please don't cry. [16:23](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=983s) We're cutting for your dinner. [16:26](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=986s) You can hear the baby crying in the background, and now with the effect applied. [16:30](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=990s) Yeah, as you can hear, there was no baby crying on the background thank you to Studio Sound effect. [16:50](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1010s) So, now let's dive into metadata retrieval. [16:54](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1014s) We'll look into how Media3 makes it easy for you to read container metadata and extract frames. [17:02](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1022s) Starting with `MetadataRetriever`, it integrates seamlessly with Kotlin coroutines for clean, non-blocking code. [17:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1031s) It has cleaner, less error-prone design using one immutable retriever per media item. [17:18](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1038s) It works consistently acro- across all API versions and devices. [17:23](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1043s) And it supports a wide range of media formats out of the box. [17:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1049s) Here's a practical example using `MetadataRetriever`. ```kotlin MetadataRetriever.Builder(context, mediaItem).build().use { retriever -> // 1. Duration (in microseconds) val durationUs = retriever.retrieveDurationUs().await() // 2. Track formats & container metadata (reuses the same preparation pass) val trackGroups = retriever.retrieveTrackGroups().await() for (i in 0 until trackGroups.length) { val format = trackGroups[i].getFormat(0) // Video/Audio properties: format.width, format.height, format.colorInfo … // Rich metadata: SlowMotionData, MotionPhotoMetadata, Mp4TimestampData … val metadata = format.metadata } // 3. Stream structure & manifest info val timeline = retriever.retrieveTimeline().await() } ``` [17:32](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1052s) Using Kotlin coroutines, you can asynchronously retrieve duration, inspect track formats and metadata, all within single, clean block. [17:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1063s) Now, let's talk about `FrameExtractor`. [17:46](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1066s) It makes pulling decoded video frames from media item fast and efficient. [17:52](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1072s) It manages underlying resource like decoders automatically, lets you apply visual effects or HDR tone mapping, and gives you fine-grain seeking control so you can get exact frame you need. [18:06](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1086s) Here's how frame extraction looks in code. ```kotlin FrameExtractor.Builder(context, mediaItem) .setSeekParameters(SeekParameters.CLOSEST_SYNC) // Fast keyframe seeking .setEffects(listOf(Presentation.createForHeight(360))) // GPU downscaling .setExtractHdrFrames(true) // Optional: HLG HDR or UltraHDR .build() .use { extractor -> // Representative thumbnail val thumbnail = extractor.getThumbnail().await() // Frame at specific timestamp (bitmap + actual presentationTimeMs) val frame = extractor.getFrame(positionMs = 5_000L).await() } ``` [18:10](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1090s) You construct an instance with options like seek parameters, GPU downscaling, effects, and HDR support, then asynchronously await thumbnails and specific frames using clean suspending calls. [18:25](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1105s) And now, to Donovan. ### Streaming standards and new media formats [18:34](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1114s) Thank you, Kristina. [18:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1116s) Now moving on, I'd like to talk about some of the underlying streaming tech support that we've added in Media3, including support for formats like Eclipsa Video and Eclipsa Audio. [18:47](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1127s) So, a core effort in Media3 is to stay ahead of streaming standards so that every time a new spec lands, you don't have to rewrite your media pipeline. [18:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1138s) Here are a few examples of things that we've shipped recently. [19:02](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1142s) We have content steering, which allows for dynamically switching of the back end CDN pathways mid-stream. [19:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1149s) We have support for server-guided ad insertion for seamless personalized ad splicing. [19:15](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1155s) We also have introduced AV1 support across multiple entry points, including full AV1 HDR support. [19:24](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1164s) We have support for Android professional video for high-fidelity capture and editing. [19:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1171s) We're also adding support for motion photos in both the JPEG and HEIC format. [19:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1177s) And of course, we're adding support for Eclipsa Video and Eclipsa Audio, which I'll talk more about soon. [19:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1183s) And the good news is that all of this is supported automatically as device support rolls out. [19:51](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1191s) So, now let's take a look at the next evolution of HDR, Eclipsa Video, and the core problem it's trying to solve. [19:59](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1199s) So, previous HDR formats provided descriptive metadata, so they determined how the scene should look, but it didn't really tell the display how to render it. [20:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1211s) So let's ground this first. [20:15](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1215s) For different environments, there are different recommendations for your average screen brightness so that you can view your content clearly and comfortably. [20:23](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1223s) So in a dim room, that range is going to be somewhere between 100 and 300 nits. [20:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1229s) And then, of course, as you get into brighter and brighter environments, the screen has to keep up and get brighter so that you can see your content clearly, all the way to when you're viewing a phone in direct sunlight, the screen might need to get to 1,000, 2,000, or even more nits so that you can view your content. [20:49](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1249s) Traditional SDR operates in that 100 to 300 range. [20:54](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1254s) And of course, HDR isn't meant to globally brighten the screen. [20:59](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1259s) You want to expand the dynamic range, so that you have localized highlights for things like fire and sunlight. [21:07](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1267s) The smartphone, though, introduces a few complications into the mix, cuz we take these with us everywhere into a dark room at our house, or to a bright beach. [21:18](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1278s) That in combination with the fact that many apps are mixing SDR UI elements with SDR content and HDR content in mixed feeds, and that can lead to that jarring, blinding effect that you can get sometimes, especially if you're scrolling late at night. [21:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1296s) And so we can see why HDR is worth it, right? [21:39](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1299s) It makes our content look great in more environments. [21:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1304s) And we've even seen increased user engagement when HDR is turned on. [21:48](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1308s) But the catch is that existing HDR formats just don't take these environments into account. [21:54](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1314s) So that's where Eclipsa Video really shines. [21:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1318s) Pause for laughter. [22:00](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1320s) Thank you. [22:01](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1321s) Thank you. [22:03](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1323s) So, instead of descriptive metadata, Eclipsa Video provides prescriptive metadata. [22:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1329s) And that's really built on three main pillars. [22:12](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1332s) The first pillar is a reference white anchor. [22:15](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1335s) So this locks the standard brightness to the display's ambient-adjusted SDR white point, which will eliminate sudden brightness spikes. [22:24](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1344s) The second pillar is the creator SDR baseline. [22:28](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1348s) This preserves the creator's exact, tone mapping curve back down to SDR. [22:35](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1355s) Third, we have headroom adaptive gain curves, which deterministically scale highlights to match the display's real-time HDR headroom so that the content matches the environment it's viewed in. [22:49](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1369s) And this is available in ExoPlayer out-of-the-box in Media3 1.11 on devices running Android 17 and later. [22:59](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1379s) And Eclipsa Video is built on open standards for cross-industry compatibility. [23:04](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1384s) So there's never been a better time to support HDR. [23:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1389s) On the audio side of things, we have Eclipsa Audio, which is an open-source, royalty-free, 3D spatial audio format. [23:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1397s) It's based on the Alliance for Open Media's IAMF specification. [23:22](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1402s) It combines scene-based Ambisonics with channel-based audio into a single stream with multiple mixed presentations. [23:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1411s) So this allows for object-like manipulations, for things like dialogue enhancement, and it'll work on everything down to stereo headphones up to surround sound setups. [23:42](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1422s) There's actually two playback paths in Media3. [23:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1425s) For Android 17 and up, ExoPlayer and the Android Audio Framework will automatically render Eclipsa Audio. [23:52](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1432s) And for Android 16 and before, you still have an option. [23:56](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1436s) You can enable the IAMF extension via an optional Media3 add-on library. [24:02](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1442s) And on the creation side of things, you can write IAMF audio tracks using Media3 muxers. ### Performance and battery optimizations [24:10](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1450s) But supporting high-quality formats is only half the story. [24:14](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1454s) We have to deliver that quality efficiently. [24:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1457s) So now I'd like to talk about how Media3 is making our pipelines faster and more efficient. [24:24](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1464s) Like many things we've talked about today, most of these performance improvements are available out of the box simply by keeping your Media3 version up to date. [24:35](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1475s) One example of a feature we have is PCM audio offload, which can route audio playback to a low-power processing unit. [24:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1483s) In our initial analysis, we've seen up to 20% power savings for screen-off audio playback. [24:53](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1493s) In addition to PCM audio offload, we have dynamic scheduling, which aligns ExoPlayer's internal rendering loop wake-ups with VSYNC and decoder buffer availability. [25:05](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1505s) We also have renderer prewarming, and this allows you to prewarm a secondary video renderer, which will allow for instant playlist transitions and easier adsplicing. [25:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1517s) We also have stuck player detection and pre-caching, which automatically detects stalled buffers and renderers and bounds pre-cached memory cleanly. [25:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1529s) Another optimal mode we have is scrubbing mode. [25:33](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1533s) And this is for apps where your users are frequently scrubbing through content. [25:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1537s) So when you enable scrubbing mode, it makes dragging the seekbar a lot more responsive and smooth, as you can see here. [25:53](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1553s) This is with scrubbing mode off, by the way. [25:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1558s) And on the right, we see scrubbing mode on. [26:02](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1562s) Thank you. [26:02](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1562s) Thank you. [26:08](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1568s) All right. [26:09](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1569s) Now let's talk about Android System Health. [26:12](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1572s) You know, we need to be respectful of our shared RAM, and CPU, and battery resources, and so as Android tightens its rules around Audio Hardening and foreground services, apps that are managing their own raw services and bitmaps can often run into some errors. [26:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1591s) Some things that you might have seen are foreground service start not allowed exceptions or binder out-of-memory exceptions. [26:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1597s) So we have hardened media service session and media notification manager as well as our bitmap pipelines in a number of ways, including we prevent stale foreground service intents and we bound artwork memory and things like that. [26:55](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1615s) But the takeaway for you is if you're using Media3 and following standard guidance, all of these system health restrictions are handled for you with no impact. [27:07](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1627s) And finally, I'd like to talk about a feature that's coming soon to Media3. [27:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1631s) When it's time to encode or export video, hardware diversity can be one of the biggest challenges. [27:18](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1638s) Many apps use a one-size-fits-all codec configuration, which you know, leads to un-optimal quality and performance. [27:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1649s) So, that's why we built CodecDB. [27:30](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1650s) CodecDB is built on extensive real-world video measurements using standardized quality metrics and hardware benchmarking across Android devices. [27:43](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1663s) So let's see it in action. [27:45](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1665s) On the left, we have a frame of a video that we took on a phone where we just used a very general video configuration that we might use on many devices. [27:56](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1676s) And on the right, we used a device-level optimal configuration that we got from CodecDB. [28:01](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1681s) So where are the differences, right? [28:04](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1684s) If you look in the high detailed areas, you'll see the biggest quality difference. [28:08](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1688s) So look at the clarity in the branches of the trees and the shrubs or the sharpness of the edge of the building. [28:15](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1695s) So what we're getting here is a much better viewing experience that takes advantage of the capabilities of a specific device by leveraging CodecDB. [28:26](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1706s) And using CodecDB in code is pretty simple. ```kotlin val config = CodecDb.onDevice(context) .forEncodingFallingBackTo { constraints -> createFallbackConfig(constraints) } .findOptimalSettings() .forEditing() .withResolution(1920, 1080, /* frameRate= */ 30.0) .withAnyCodec() .targetingQuality(VideoEncoder.QUALITY_LEVEL_VERY_GOOD) .get() ``` [28:29](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1709s) You'll use a fluent stage builder pattern that guides you through every required parameter. [28:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1716s) So you will specify your use case. [28:39](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1719s) Here we're using for editing. [28:41](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1721s) This tells CodecDB you're using Media3 export or transcoding pipeline. [28:46](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1726s) Some other use cases you could set, for instance, are camera capture if that's what you're using it with. [28:52](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1732s) You'll also pass in your target resolution and framerate. [28:58](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1738s) Then, instead of guessing whether that 1080p video needs 8 megabits per second or 15 megabits per second, you tell CodecDB which codec you're willing to use and you ask for a perceptual quality target. [29:11](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1751s) In this case, we're using quality level very good. [29:15](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1755s) Under the hood, CodecDB looks up empirical benchmark curves for that exact SOC and returns the ideal codec and encoder parameters. [29:24](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1764s) We expect CodecDB to launch in Media3 very soon, and when it does, it will use native Media3 types. [29:31](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1771s) So integrating it with your existing Media3 code should be a breeze. ### Summary and conclusion [29:36](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1776s) Okay. [29:37](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1777s) That was a lot of amazing functionality that we've seen today in Media3, so let's recap. [29:44](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1784s) Today, you've learned about our new Compose UI modules, the new CastPlayer, the revamped editing pipeline, and many new effects that you can use in Media3. [29:56](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1796s) We've also seen new tools to retrieve metadata and frames. [30:01](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1801s) And we looked at new streaming technology support and performance enhancement and utilities. [30:08](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1808s) Media3 is an open source library, so you can actually see how we implemented all these features in our GitHub repository. [30:17](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1817s) As of today, the latest stable release of Media3 is 1.11.1. [30:22](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1822s) You can also check out 1.12.0, which is currently in alpha. [30:28](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1828s) And follow this short link, which will take you to our developer documentation on Media3. [30:35](https://www.youtube.com/watch?v=zFs0FLMGAeM&t=1835s) Thank you so much, and enjoy the rest of ADS.