You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[ui-compose] PresentationState crashes after replacing MediaItem on MediaController during live stream playback #3264
Create a MediaSessionService hosting an ExoPlayer.
Connect to it from the UI using a MediaController.
Use Media3 Compose UI (media3-ui-compose) and create a PresentationState using the MediaController.
playback of a live HLS/DASH stream.
Replace the currently playing MediaItem with another live stream URL (e.g. quality switch from Low → Medium).
For reference I am using same MediaSessionService setup as done in AdaptiveJetStream sample, and for me the crash is triggered when I do player.replaceMediaItem
Expected result
The Media plays successfully when switched through player.replaceMediaItem
Actual result
Playback crashses with following stack trace: java.lang.UnsupportedOperationException at androidx.media3.common.Timeline$RemotableTimeline.getIndexOfPeriod(Timeline.java:1691) at androidx.media3.ui.compose.state.PresentationState.shouldKeepSurfaceVisible(PresentationState.kt:175) at androidx.media3.ui.compose.state.PresentationState.observe$lambda$0(PresentationState.kt:117) at androidx.media3.ui.compose.state.PresentationState$$ExternalSyntheticLambda0.invoke(D8$$SyntheticClass:0) at androidx.media3.common.PlayerListener.onEvents(PlayerExtensions.kt:147) at androidx.media3.session.MediaControllerImplBase.lambda$new$0$androidx-media3-session-MediaControllerImplBase(MediaControllerImplBase.java:185) at androidx.media3.session.MediaControllerImplBase$$ExternalSyntheticLambda86.invoke(D8$$SyntheticClass:0) at androidx.media3.common.util.ListenerSet$ListenerHolder.iterationFinished(ListenerSet.java:477) at androidx.media3.common.util.ListenerSet.handleMessage(ListenerSet.java:421) at androidx.media3.common.util.ListenerSet.$r8$lambda$rFcF5Pkb99AL585p5-2u78YfNkY(Unknown Source:0) at androidx.media3.common.util.ListenerSet$$ExternalSyntheticLambda0.handleMessage(D8$$SyntheticClass:0) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:246) at android.app.ActivityThread.main(ActivityThread.java:8653) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)
MediaController is operating across an IPC boundary and to prevent exceeding Binder transaction limits, Media3 strips granular data (like Period UIDs) and passes a specialized RemotableTimeline to the controller. By design, RemotableTimeline drops UIDs and explicitly throws an UnsupportedOperationException whenever getIndexOfPeriod(Object uid) is invoked.
I see 2 ways forward:
Just use the index of the currently playing period (player.currentPeriodIndex)
If the Compose state is unnecessarily extracting the UID just to find out where we are in the timeline. MediaController natively syncs this value, bypassing the timeline UID lookup entirely and avoiding the crash.
Add a fallback for the MediaController case.
privatefunshouldKeepSurfaceVisible(player:Player): Boolean {
// ... val index =try {
player.currentTimeline.getIndexOfPeriod(cachedPeriodUid)
} catch (e:UnsupportedOperationException) {
// Fallback for MediaController / RemotableTimeline where UIDs are stripped.// We cannot verify precise period identity, so we fallback to checking if the // MediaItem identity has remained the same, or if the player still has // an active video track.val hasVideoTrack = player.currentTracks.isTypeSupported(C.TRACK_TYPE_VIDEO)
val isSameMediaItem = player.currentMediaItem?.mediaId == cachedMediaId
return isSameMediaItem && hasVideoTrack
}
return index !=C.INDEX_UNSET
}
I will try out different strategies and get back to you with our solution. I'm surprised it hasn't ever crashed on the Views side, because there is nothing that Compose is adding in terms of layering.
Activity
oceanjules commentedon Jun 5, 2026
Thanks Nikhil!
You are right, the bug comes from this chunk
media/libraries/ui_compose/src/main/java/androidx/media3/ui/compose/state/PresentationState.kt
Lines 156 to 176 in 5fb3064
.. which came from the
PlayerView:media/libraries/ui/src/main/java/androidx/media3/ui/PlayerView.java
Lines 1915 to 1926 in 5fb3064
MediaController is operating across an IPC boundary and to prevent exceeding Binder transaction limits, Media3 strips granular data (like Period UIDs) and passes a specialized
RemotableTimelineto the controller. By design,RemotableTimelinedrops UIDs and explicitly throws anUnsupportedOperationExceptionwhenevergetIndexOfPeriod(Object uid)is invoked.I see 2 ways forward:
Just use the index of the currently playing period (
player.currentPeriodIndex)If the Compose state is unnecessarily extracting the UID just to find out where we are in the timeline. MediaController natively syncs this value, bypassing the timeline UID lookup entirely and avoiding the crash.
Add a fallback for the MediaController case.
I will try out different strategies and get back to you with our solution. I'm surprised it hasn't ever crashed on the Views side, because there is nothing that
Composeis adding in terms of layering.[ui-compose] Add a presentation state test with unsupported exception
Map and propagate Timeline UIDs across the IPC boundary