Media3 1.11.0 graduated to stable on August 5, 2026. I covered the alpha01 milestone back when PlayerPool and the Compose player suite landed, and beta01 when HLS track selection priorities and DASH CDN fallback arrived. Stable is a different kind of post — it's the one where you actually decide whether to ship the upgrade, and that decision needs a clear picture of what's genuinely new in stable (beyond what I already wrote) and what will break in your existing code.
Short version: the upgrade is worth doing, and there's less breakage than the long changelog suggests. But there are a handful of renamed and relocated classes that will cause compilation failures if you don't catch them first.
What's New in Stable Beyond Alpha and Beta
If you read the alpha01 and beta01 posts, you already know about PlayerPool, the Compose player UI suite, dynamic scheduling as default, Format.selectionPriority from HLS SCORE, DASH multi-Location CDN fallback, and the Cast MediaRouteButtonState additions. Those all graduated to stable unchanged. Here's what didn't get coverage yet.
OggMuxer and WavMuxer — Media3 Can Now Write Audio
Media3 has always been about playing and processing media. Muxing — writing encoded audio or video into a container — has historically meant reaching for a separate library or building around MediaMuxer directly. Stable 1.11.0 ships two new muxers that change that equation for audio recording use cases.
OggMuxer writes OPUS or VORBIS audio streams into an OGG container. WavMuxer writes integer and floating-point PCM audio into WAV files. Both follow the same muxer contract as the existing Mp4Muxer and FragmentedMp4Muxer, so if you've integrated those, the API is familiar:
// Recording OPUS audio to OGG
val muxer = OggMuxer(outputStream)
val trackIndex = muxer.addTrack(
MediaFormat.createAudioFormat(MimeTypes.AUDIO_OPUS, sampleRate, channelCount)
)
muxer.writeSampleData(trackIndex, encodedBuffer, bufferInfo)
muxer.release()
// Writing raw PCM to WAV (e.g., from AudioRecord)
val muxer = WavMuxer(outputStream)
val trackIndex = muxer.addTrack(
MediaFormat.createAudioFormat(MimeTypes.AUDIO_RAW, 44100, 2)
)
muxer.writeSampleData(trackIndex, pcmBuffer, bufferInfo)
muxer.release()
In Musist, I've been writing recorded audio clips to a temporary file using MediaMuxer with OutputFormat.MUXER_OUTPUT_MPEG_4 because MP4 was the only practical option without a custom wrapper. For apps that need OGG output — podcast recording, game audio capture, anything targeting a pipeline that expects Ogg Vorbis — OggMuxer removes that workaround entirely. The WAV path is even more useful for apps that hand off audio to post-processing pipelines where PCM is expected rather than compressed.
MP4 Chapter Metadata Extraction
Media3 can now extract chapter metadata from MP4 files in both Nero and QuickTime chapter formats. The chapters surface through MediaMetadata and are accessible from Player.currentMediaItem.mediaMetadata after the item loads.
This sounds like a niche feature, but it's surprisingly relevant for podcast apps, audiobook players, and long-form video content. Any MP4 that was created with chapter markers — which includes a large fraction of podcasts converted from MP3-with-chapters and most video exports from tools like DaVinci Resolve or Final Cut Pro — now exposes those chapters without you doing any custom extraction logic. For HailUp, which plays user-uploaded content including long recordings, this opens up the possibility of a chapter-skip UI without requiring uploaders to provide a separate chapter manifest.
// Chapters arrive in MediaMetadata after item loads
player.addListener(object : Player.Listener {
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
val chapters = mediaMetadata.extras?.getParcelableArrayList<Chapter>("chapters")
// render chapter list or seek bar markers
}
})
enablePerStreamMediaProgression() — Reducing Cold-Start Between Playlist Items
This is marked @ExperimentalApi, but it addresses a real problem worth knowing about. When a playlist transitions between items, ExoPlayer has always had a small but perceptible pause as it sets up the next stream — even when preloading is enabled. The underlying issue is that stream progression (the transition from one item's metadata phase to another's buffering phase) has latency baked in by design for correctness reasons.
ExoPlayer.Builder.enablePerStreamMediaProgression() opts into a new scheduling path that reduces this inter-item gap, at the cost of slightly higher complexity in edge cases around mid-transition seeks. Google describes it as "reduces startup latency between playlist items" without quantifying by how much, which is the honest answer — it's workload-dependent.
val player = ExoPlayer.Builder(context)
.setMediaSourceFactory(
DefaultMediaSourceFactory(context).setDataSourceFactory(dataSourceFactory)
)
.enablePerStreamMediaProgression() // @ExperimentalApi
.build()
In a vertical feed like Samachar's, where the playlist is essentially infinite and every item transition is a cold start for the viewer, this is worth experimenting with. Enable it behind a feature flag, run a Macrobenchmark measuring time-from-swipe-to-first-frame, and compare. If you're not on a playlist-heavy architecture, skip it for now.
HLS Content Steering and Pathway Cloning
Content Steering is an HLS spec extension that lets a steering server dynamically redirect a player from one CDN pathway to another during playback — without interrupting the stream. Pathway Cloning extends this by allowing a server to describe new pathways derived from existing ones rather than having to enumerate every CDN option upfront in the manifest.
Media3 1.11.0 implements both. If your HLS infrastructure uses a steering server (common in large-scale live streaming, e.g., sports events), you get automatic pathway switching based on server-side decisions: geographic routing, CDN load balancing, or CDN failure fallback. Zero client code changes required — the player reads the EXT-X-CONTENT-STEERING tag from the manifest and handles the steering protocol internally.
For Nodat's live room features this would have been relevant — we ended up building a manual CDN fallback that watches for HTTP 5xx errors and swaps the source URL. Content Steering automates exactly that, but driven by the server rather than client-side error detection.
New Compose Player State Composables
The alpha01 Compose player suite (MiniController, PlayerDefaults, ErrorState) now comes with two additional state holders that make it easier to wire up metadata-driven UIs without writing your own collectAsState loops.
CurrentMediaItemState and rememberCurrentMediaItemState expose the current item's metadata, artwork, and title as observable Compose state — useful for now-playing displays, lock screen controls, and notification-style overlays that need to stay in sync with the player without boilerplate.
PlaylistState and rememberPlaylistState expose the full playlist as Compose state, with current-item highlighting already computed. This is the composable-native equivalent of the listener pattern most teams have been writing manually since Media3 launched its Compose UI artifacts.
@Composable
fun NowPlayingBar(player: Player) {
val currentItem by rememberCurrentMediaItemState(player)
val playlist by rememberPlaylistState(player)
Column {
Text(currentItem.mediaMetadata.title ?: "")
Text("${playlist.currentItemIndex + 1} / ${playlist.mediaItems.size}")
LinearProgressIndicator(progress = { currentItem.durationProgress })
}
}
IAMF Audio and Channel Mask Support
Mp4Muxer and FragmentedMp4Muxer now support IAMF (Immersive Audio Model and Formats) — the open standard for object-based spatial audio adopted by the MPEG group. If your pipeline produces IAMF-encoded audio (relevant for XR and VR content, where spatial audio is table stakes), you can now mux it directly into MP4 containers without format conversion.
On the playback side, Format.channelMask explicitly exposes the audio channel mask rather than relying on callers to infer it from channel count. If you've written code that converts between channel count and Android's AudioFormat.CHANNEL_OUT_* constants manually, Util.getAudioTrackChannelConfig(Format) now handles that conversion safely and consistently.
The Migration Checklist
These are the changes that will cause your build to fail or your runtime to behave differently after bumping to 1.11.0. Go through them before you submit the upgrade to code review.
| Old API | New API | Type |
|---|---|---|
androidx.media3.exoplayer.MetadataRetriever |
androidx.media3.inspector.MetadataRetriever |
Moved |
androidx.media3.exoplayer.MediaExtractorCompat |
androidx.media3.inspector.MediaExtractorCompat |
Moved |
DummyTrackOutput |
DiscardingTrackOutput |
Renamed |
DummyExtractorOutput |
NoOpExtractorOutput |
Renamed |
C.generateAudioSessionIdV21() |
Util.generateAudioSessionId(context) |
Removed |
Mp4Extractor.FLAG_READ_MOTION_PHOTO_METADATA |
Not replaced — remove usages | Removed |
The two package moves (MetadataRetriever and MediaExtractorCompat into media3-inspector) will cause import-level compile errors if your code references them directly. Add the media3-inspector artifact to your dependencies if you use either class:
dependencies {
val media3Version = "1.11.0"
implementation("androidx.media3:media3-exoplayer:$media3Version")
implementation("androidx.media3:media3-exoplayer-hls:$media3Version")
implementation("androidx.media3:media3-exoplayer-dash:$media3Version")
implementation("androidx.media3:media3-ui-compose:$media3Version")
implementation("androidx.media3:media3-session:$media3Version")
implementation("androidx.media3:media3-datasource-ktor:$media3Version") // if using Ktor
implementation("androidx.media3:media3-inspector:$media3Version") // if using MetadataRetriever
}
Kotlin version bump: 1.11.0 upgrades its internal Kotlin dependency from 2.0.20 to 2.2.0. If your project pins Kotlin at 2.0.x via the kotlin-gradle-plugin, you should align with 2.2.x for a clean build. The K2 compiler is stable in 2.2.0 and brings meaningful compile-time improvements — there's no good reason to stay on 2.0.x at this point.
MediaSession Threading Gets Stricter
The release notes call out stricter threading requirements for MediaSession. Specifically, onConnect default behavior now gives untrusted controllers read-only access by default rather than the previous more permissive stance. If your session was relying on the old default to allow untrusted controllers to issue playback commands (which was a footgun, not a feature), you'll need to explicitly override MediaSession.Callback.onConnect() and grant the capabilities you actually want.
The new MediaSession.Callback.onConnectAsync() is worth adopting at the same time — it lets you do asynchronous work (permission checks, analytics initialization) during connection without blocking the calling thread.
class MySessionCallback : MediaSession.Callback {
override fun onConnectAsync(
session: MediaSession,
controller: MediaSession.ControllerInfo
): ListenableFuture<SessionResult> {
return Futures.immediateFuture(
if (controller.isTrusted) {
SessionResult(SessionResult.RESULT_SUCCESS)
} else {
// grant read-only access
SessionResult(SessionResult.RESULT_ERROR_PERMISSION_DENIED)
}
)
}
}
Should You Upgrade Now?
Yes, with one caveat: run through the migration table above before you touch your version catalog. The compile-time changes are mechanical — import updates and dependency additions — and they'll surface immediately as build failures rather than hiding until runtime. An hour of careful find-and-replace is all it takes.
For media-heavy apps like Musist or HailUp: the dynamic scheduling default (from alpha01), the DASH CDN fallback (from beta01), and the session threading improvements alone are worth the upgrade. The new muxer support and chapter metadata are things you'll build on in future features, not something you need to integrate immediately.
The two things I'd defer: enablePerStreamMediaProgression() is still experimental, so keep it behind a flag until Google promotes it. And the new Compose player state composables (CurrentMediaItemState, PlaylistState) are stable API, but if you've already built custom state holders for your player UI, there's no urgency to migrate — the new composables are the better long-term home, but they're not a breaking replacement for what you have.
Media3 has earned production trust at this point. 1.11.0 stable isn't a risky upgrade — it's the consolidation of several months of careful alpha and beta work. Bump the version, fix the imports, run your existing tests, and ship it.
No comments yet. Be the first to leave one!