A week after the alpha01 drop brought PlayerPool and the full Compose player UI suite, Media3 1.11.0-beta01 landed on July 7th. The version bump is meaningful — beta signals that the public API surface is largely locked, so if you've been sitting on the sideline waiting for alpha to stabilize before evaluating 1.11, that moment is now.
But beta01 isn't just an API freeze. It adds two genuinely new capabilities that weren't in alpha at all — Format.selectionPriority for influencing HLS and DASH adaptive bitrate decisions, and multi-Location fallback in DashMediaSource for CDN resilience — plus a cluster of bug fixes that are embarrassing to hit in production. I've personally been burned by the foreground service crash pattern this release addresses. Let me walk through what actually matters here.
1. Format.selectionPriority: Finally, a Preference Signal for ABR
Adaptive bitrate streaming has always been a black box in ExoPlayer/Media3. The adaptive track selector picks variants based on bandwidth estimates, buffer fullness, and viewport size — but there's been no clean way for the server or the app to say "prefer this variant when conditions are roughly equivalent." Beta01 introduces Format.selectionPriority to fill that gap.
The value is populated from the SCORE attribute of EXT-X-STREAM-INF and EXT-X-I-FRAME-STREAM-INF in HLS manifests. If your HLS manifest declares a score on each stream variant, Media3 now reads it and stores it on the Format object. The adaptive track selector factors this into variant preference when bandwidth allows multiple viable options.
# HLS manifest with SCORE attributes
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.42c01e,mp4a.40.2",SCORE=2.0
/streams/360p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2400000,RESOLUTION=1280x720,CODECS="avc1.4d401f,mp4a.40.2",SCORE=5.0
/streams/720p.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2",SCORE=8.0
/streams/1080p.m3u8
You can also read the value in your own track selection logic. If you're building a custom TrackSelector or running any post-selection filtering:
// After track selection, inspect selectionPriority on each Format
val trackGroups = player.currentTracks.groups
for (group in trackGroups) {
for (i in 0 until group.length) {
val format = group.getTrackFormat(i)
val priority = format.selectionPriority // defaults to 0 if not set in manifest
Log.d("Tracks", "${format.height}p → priority=$priority")
}
}
In practice, this matters most if you control your own CDN and manifest generation. If you serve streams from multiple geographic origins at the same bitrate but different latency profiles, you can now nudge the selector toward the better origin without changing bitrate thresholds. For apps like Musist or Samachar where we source content from a third-party CDN and don't control manifest generation, the immediate impact is lower — but it's a capability worth knowing when you do negotiate CDN agreements or build your own delivery pipeline.
2. DASH Multi-Location Fallback: CDN Resilience You Actually Need
DASH manifests (MPD files) have supported multiple <Location> elements for a long time — the spec allows you to list fallback manifest URLs so that if the primary URL fails, the player tries the next one. Until beta01, DashMediaSource only ever used the first <Location> element and stopped there.
Beta01 fixes this properly. When a manifest fetch fails, DashMediaSource now iterates through all declared Location elements in order until one succeeds. This is a pure bug fix in terms of spec compliance, but the production impact can be significant if you're delivering DASH content through a multi-CDN or multi-region setup:
<!-- MPD with multiple Location fallbacks -->
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" ...>
<Location>https://primary-cdn.example.com/manifest.mpd</Location>
<Location>https://fallback-cdn.example.com/manifest.mpd</Location>
<Location>https://origin.example.com/manifest.mpd</Location>
...
</MPD>
Before this fix, if your primary CDN went down and returned errors during the manifest fetch, the player would immediately throw a load error rather than trying your declared fallbacks. That meant users saw a playback failure when the content was actually available from a healthy origin — just one URL away. Now the fallback chain works as the spec intends.
Worth noting: this fallback applies specifically to manifest loads, not to segment loads. Segment-level CDN failover is handled through different mechanisms (redirect following, retry policies). But manifest availability is often the first failure mode in a CDN incident, so this fix directly reduces the "content unavailable" rate in multi-CDN setups without any app-side changes.
3. Cast Extension: Observing the Route Picker State
The Cast extension gets a small but useful addition: MediaRouteButtonState and the Compose-friendly rememberMediaRouteButtonState(). These let you observe whether the Cast route picker is currently visible — something that was previously impossible without digging into internal Cast SDK state.
@Composable
fun PlayerTopBar(castContext: CastContext) {
val routeButtonState = rememberMediaRouteButtonState(castContext)
Row(verticalAlignment = Alignment.CenterVertically) {
Text("Now Playing")
Spacer(Modifier.weight(1f))
// conditionally show different UI while picker is open
if (routeButtonState.isPickerVisible) {
Text("Selecting device...", color = MaterialTheme.colorScheme.primary)
} else {
MediaRouteButton(
castContext = castContext,
state = routeButtonState,
)
}
}
}
The state parameter on MediaRouteButton is also new in this release — it replaces the previous pattern of managing picker visibility externally. It's a small quality-of-life improvement for Compose Cast UIs, not a critical feature, but it closes a genuine API gap for any app that shows contextual UI around the Cast button.
4. The Bug Fixes That Actually Matter in Production
Beta01's changelog has more bug fixes than features, and a few of them are the kind that make you check your own crash dashboard immediately.
ForegroundServiceStartNotAllowedException — the silent music app killer
This one hits music and podcast apps hard. The crash pattern: your player is running, the user minimizes the app, and at some point an artwork bitmap load finishes asynchronously. The MediaNotificationManager tries to update the notification — which requires starting a foreground service — but the app is now backgrounded. Android throws ForegroundServiceStartNotAllowedException and you get a crash that shows up in your dashboard with zero user-visible symptom before it.
I've seen this pattern in Musist's crash logs. It's particularly insidious because it depends on network timing — if artwork loads quickly, you're fine. If it's slow and finishes after the user backgrounds, you crash. Beta01 fixes this by properly guarding the foreground service start attempt in the async callback path inside MediaNotificationManager.
Track selection concurrency race condition
If a user is rapidly switching quality settings — say, toggling between "Auto" and a specific resolution multiple times quickly — there was a timing-dependent race where some of those updates could be silently lost. The track selector's internal state could end up reflecting an intermediate selection rather than the last one the user made. Beta01 fixes the concurrency issue in the track selection parameter update path. If you've ever had users report "I switched to HD but it went back to low quality by itself," this is likely why.
MediaSession artwork blurriness in notifications
When MediaSession needed to downscale artwork to fit platform limits, it was applying the downscaling twice — once internally, then again when the platform received it. The result was notification artwork that was significantly blurrier than the source image, especially on high-resolution devices where the platform limit is lower than you'd expect. Beta01 fixes the double-downscaling. If your music app's notifications look like they're running through a JPEG compression cycle too many, upgrade and retest.
PositionInfo missing Timeline UIDs
If you're using MediaController to receive playback state from a MediaSession (common in car apps, widget implementations, or any multi-process media setup), the PositionInfo objects you received were missing their Timeline UIDs. This meant you couldn't reliably identify which item in the queue a position update referred to — just its index, which shifts as the queue changes. Beta01 fixes the UID propagation so PositionInfo always carries the stable UID from the originating session's timeline.
HLS playlist refresh leak
In some HLS switching scenarios — particularly when switching between streams in a playlist — the previous playlist's refresh timer wasn't always properly cancelled. Scheduled refreshes could continue firing for a playlist that was no longer being played. In a long-lived player session (think a multi-hour live stream or a radio app), this could accumulate into meaningless background network traffic and subtle memory pressure. Fixed in beta01.
5. MP3 Gapless Playback Gets Tighter
Two MP3-specific fixes are in this release that are worth knowing about if you play local music files or podcast downloads:
- Gapless-aware durations from Xing/Info headers. MP3 files encoded with gapless metadata embed duration information in Xing or Info headers that accounts for encoder delay and padding. Media3 now reads these headers for seeking accuracy, fixing cases where the seek bar and elapsed time were slightly off on gapless-encoded files.
- Encoder delay and padding alignment. The trimming of encoder delay and padding during decoding now matches what the Xing/LAME headers specify. Previously, decoded PCM output could include a few extra frames at the start or end of gapless tracks, causing a faint click or pop at transition points in continuous playback albums.
These are small but meaningful for any app that treats music playback seriously. Gapless is one of those features where a 10ms error is perceptible to anyone with headphones on a quiet album transition. Musist's local playback quality gets a quiet improvement here.
AVI files: there's also a fix for AVI files without keyframe flags on audio tracks causing audio loss and OutOfMemoryError. Unless you're explicitly supporting legacy AVI container playback, this probably doesn't affect you — but if you are, it was a genuine crash-inducing bug before this release.
6. API Cleanup: generateAudioSessionIdV21 Is Gone
Beta01 removes the deprecated androidx.media3.common.C.generateAudioSessionIdV21 method. If you're calling it directly — which would be unusual since it was an internal utility — migrate to androidx.media3.common.util.Util.generateAudioSessionId. The method does the same thing; it just moved to a more logical home. A quick grep across your codebase for generateAudioSessionIdV21 will confirm whether this affects you.
Should You Upgrade to beta01?
Yes, and the reasoning is straightforward. The foreground service crash fix alone is worth it for any app with background playback — that crash pattern is a silent reputation killer because it's timing-dependent and might not appear in your own testing but absolutely appears for users. The track selection race condition fix matters if you offer manual quality controls. The artwork blurriness fix is visible to users in notifications.
The upgrade from 1.10.x is the same path as alpha01 — if you already evaluated that, beta01 should be a clean drop-in:
// libs.versions.toml
[versions]
media3 = "1.11.0-beta01"
[libraries]
media3-exoplayer = { group = "androidx.media3", name = "media3-exoplayer", version.ref = "media3" }
media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3" }
media3-session = { group = "androidx.media3", name = "media3-session", version.ref = "media3" }
# if you use the Cast extension:
media3-cast = { group = "androidx.media3", name = "media3-cast", version.ref = "media3" }
The only mandatory migration from 1.10.x is the removal of C.generateAudioSessionIdV21 if you're calling it. Everything else in beta01 is additive or a bug fix — no API renames, no behavioral changes that require code updates on your end.
Beta01 is also the right moment to evaluate the PlayerPool and Compose player UI APIs from alpha01 if you haven't yet. With beta signaling API stability, you can build features on top of rememberPooledPlayer with more confidence that the interface won't change between now and stable. That's a meaningful upgrade to the confidence level if alpha01's "experimental" label was holding you back.
The 1.11.0 stable release is likely a few weeks out at this pace. Between now and then, the changelog will be mostly bug fixes and documentation. If you're in a position to take betas in your build — and for a library as battle-tested as Media3, you likely should be — there's no good reason to wait on this one.
No comments yet. Be the first to leave one!