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
MediaSessionService: the platform session keeps every player command and publishes the whole queue until the session is added, e.g. after a sticky restart #3490
Media3 pre-release (alpha, beta or RC not in this list)
More version details
Media3 1.12.0-alpha01. Also reproduced with 1.11.1.
Devices that reproduce the issue
Android emulator, API 30 (Android 11). Not tried on other devices; nothing in the path described below depends on the API level.
Devices that do not reproduce the issue
No response
Reproducible in the demo app?
Not tested
Reproduction steps
The documented pattern builds the MediaSession in MediaSessionService.onCreate() and returns it from onGetSession(). The platform session takes its commands from the media notification controller, so removing Player.COMMAND_GET_TIMELINE from that controller in MediaSession.Callback.onConnect keeps the queue out of the platform session (1.2.0 release notes).
That restriction only takes effect once MediaSessionService.addSession() has run, because only then does MediaNotificationManager connect the notification controller. Until then the legacy stub keeps DEFAULT_PLAYER_COMMANDS. When the system restarts a sticky service after its process was killed while playing, onStartCommand(null) returns START_STICKY without adding the session, while the platform session is already active. The next headset key then reaches the platform session directly, onPlaybackResumption restores the queue, and the platform session publishes it.
A MediaSessionService that builds its session in onCreate, removes COMMAND_GET_TIMELINE from the media notification controller in onConnect, and restores a queue in onPlaybackResumption.
Play, then adb shell kill -9 <pid> while playing. The system schedules a restart of the crashed service.
Once it has restarted, adb shell input keyevent KEYCODE_MEDIA_PLAY.
adb shell dumpsys media_session
Expected result
The platform session follows the media notification controller's commands from the start, so it never holds the queue, and playback resumed this way runs in the foreground under the media notification.
Calling addSession(session) in onCreate, right after building the session, works around it: LordDetson/piplayer@f502ab7
Actual result
The platform session publishes the whole queue (dumpsys media_session shows its size) until any Media3 controller connects; opening the app's activity clears it. With a large queue that is every item on every timeline change.
Playback runs without the media notification and without the foreground service.
On Android 11 the queue appearing and disappearing again also hits a race in the Bluetooth AVRCP service: MediaPlayerWrapper.isMetadataSynced calls getQueue() twice, and com.android.bluetooth crashed once with a NullPointerException in our runs (fixed in Android 12 by "AVRCP: Cache queue in isMetadataSynced").
Do I understand correctly this can only be reproduced when the service is restarted after a crash with a media key?
Can you confirm that the remove timeline command applies correctly in the platform session when:
a) The service is started normally by building the first controller, that starts the service and binds to it (normal startup)
b) The service is started by a media key event (for instance BT) to trigger playback resumption without the previous service instance having been crashed.
As the code path that exposes the wrong behaviour goes through onStartCommand, I'm specifically interested whether b) that goes through onStartCommand works as expected. From my analysis this happens in the failure scenario only after a crash as you describe it. The reason is that addSession is not called correctly in this scenario. Your workaround seems to confirm this.
Thanks for looking into it! I checked both cases on an API 30 emulator with Media3 1.12.0-alpha01 and the workaround removed (no addSession() in onCreate):
a) Normal startup, a MediaController that starts and binds the service: COMMAND_GET_TIMELINE removal applies, the platform session has no queue.
b) A media button intent (ACTION_MEDIA_BUTTON with KEYCODE_MEDIA_PLAY, sent with startForegroundService to a stopped service, no crash before): also correct, playback resumes, the notification is shown and the platform session has no queue.
Only the restart fails: the system recreates the sticky service with onStartCommand(null), so addSession is never called, and a later media key reaches the already active platform session directly. It does not need a crash as such: any death of the process while the service is started does it (I used adb shell kill -9, as a low-memory kill would), and the key comes from adb shell input keyevent KEYCODE_MEDIA_PLAY after the restart. So I agree with your analysis.
Thanks a lot for checking! That is inline with what we see.
In the case you describe, when the system restarts a START_STICKY service and the system passes a null Intent we don't have a media button event nor a connected controller. In the two conventional startup cases (that you tested above), we have either one or the other. What we can do is to create a fallback controller, then call onGetSession(fallbackControllerInfo) and call addSession for the session we get. This requires an app to accept that controller in onConnectAync(session, fallbackControllerInfo) for that controller and the return the new session in onGetSession(fallbackControllerInfo) to allow us to recover from that state. This is the option I see. We will make this controller recognizable in some ways (eg. connection hints) and document this on DAC and in JavaDoc accordingly.
We are working on a fix for this and will update this issue when we have news. Thanks again for reporting and testing!
Activity
marcbaechinger commentedon Oct 26, 2026
Thanks for reporting! We take a look into this.
Do I understand correctly this can only be reproduced when the service is restarted after a crash with a media key?
Can you confirm that the remove timeline command applies correctly in the platform session when:
a) The service is started normally by building the first controller, that starts the service and binds to it (normal startup)
b) The service is started by a media key event (for instance BT) to trigger playback resumption without the previous service instance having been crashed.
As the code path that exposes the wrong behaviour goes through
onStartCommand, I'm specifically interested whether b) that goes throughonStartCommandworks as expected. From my analysis this happens in the failure scenario only after a crash as you describe it. The reason is thataddSessionis not called correctly in this scenario. Your workaround seems to confirm this.LordDetson commentedon Oct 26, 2026
Thanks for looking into it! I checked both cases on an API 30 emulator with Media3 1.12.0-alpha01 and the workaround removed (no
addSession()inonCreate):a) Normal startup, a
MediaControllerthat starts and binds the service:COMMAND_GET_TIMELINEremoval applies, the platform session has no queue.b) A media button intent (
ACTION_MEDIA_BUTTONwithKEYCODE_MEDIA_PLAY, sent withstartForegroundServiceto a stopped service, no crash before): also correct, playback resumes, the notification is shown and the platform session has no queue.Only the restart fails: the system recreates the sticky service with
onStartCommand(null), soaddSessionis never called, and a later media key reaches the already active platform session directly. It does not need a crash as such: any death of the process while the service is started does it (I usedadb shell kill -9, as a low-memory kill would), and the key comes fromadb shell input keyevent KEYCODE_MEDIA_PLAYafter the restart. So I agree with your analysis.marcbaechinger commentedon Oct 26, 2026
Thanks a lot for checking! That is inline with what we see.
In the case you describe, when the system restarts a START_STICKY service and the system passes a null
Intentwe don't have a media button event nor a connected controller. In the two conventional startup cases (that you tested above), we have either one or the other. What we can do is to create a fallback controller, then callonGetSession(fallbackControllerInfo)and calladdSessionfor the session we get. This requires an app to accept that controller inonConnectAync(session, fallbackControllerInfo)for that controller and the return the new session inonGetSession(fallbackControllerInfo)to allow us to recover from that state. This is the option I see. We will make this controller recognizable in some ways (eg. connection hints) and document this on DAC and in JavaDoc accordingly.We are working on a fix for this and will update this issue when we have news. Thanks again for reporting and testing!