Since 1.9.0, skip buttons in SLOT_BACK / SLOT_FORWARD reach legacy controllers as custom actions
Maintainers usually reply within 4 days
@marcbaechinger is already working on this.
Since Oct 28, 2026.
Assessment
This issue has not been assessed yet.
Description
Version
Media3 1.11.1
More version details
The behaviour starts in 1.9.0 and is the same on main. 1.8.x does not have it.
The session sets media button preferences with setMediaButtonPreferences. They include a CommandButton with player command COMMAND_SEEK_TO_PREVIOUS in SLOT_BACK and one with COMMAND_SEEK_TO_NEXT in SLOT_FORWARD.
1.9.0 and later. Commit 8b334e45a9 "Use standard commands in custom layout conversion" (in 1.9.0 as cherry-pick 0527f5a2de) makes this intentional. The 1.9.0 release note:
Add backwards-compatibility for
CommandButtoninstances usingPlayer.Commandor non-customSessionCommandto define their action. These are now correctly represented in platform media sessions, for example for System UI or Android Auto.
CommandButton.getCustomLayoutFromMediaButtonPreferences now accepts any button that can execute its action, and converts it with convertToPredefinedCustomCommandButton. That method names it after the player command (CommandButton.java#L1635-L1637):
} else {
customCommandName = CUSTOM_COMMAND_PLAYER_COMMAND_PREFIX + playerCommand;
}
with (CommandButton.java#L506-L507):
private static final String CUSTOM_COMMAND_PLAYER_COMMAND_PREFIX =
"androidx.media3.session.PLAYER_COMMAND_";
MediaSessionLegacyStub.createPlaybackStateCompat then removes the standard skip action for a filled slot (MediaSessionLegacyStub.java#L1923-L1930):
if (!mediaButtonPreferences.isEmpty()
&& CommandButton.containsButtonForSlot(customLayout, CommandButton.SLOT_BACK)) {
actions &= ~PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
if (!mediaButtonPreferences.isEmpty()
&& CommandButton.containsButtonForSlot(customLayout, CommandButton.SLOT_FORWARD)) {
actions &= ~PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
}
So the legacy session publishes custom actions androidx.media3.session.PLAYER_COMMAND_7 and androidx.media3.session.PLAYER_COMMAND_9, in that order (Previous, then Next). It does not publish ACTION_SKIP_TO_PREVIOUS or ACTION_SKIP_TO_NEXT.
1.8.x. getCustomLayoutFromMediaButtonPreferences skips a button without a custom session command (CommandButton.java#L1592-L1597):
CommandButton button = mediaButtonPreferences.get(i);
if (!button.isEnabled
|| button.sessionCommand == null
|| button.sessionCommand.commandCode != COMMAND_CODE_CUSTOM) {
continue;
}
The skip buttons do not fill a slot, so the session keeps the standard skip actions. The same app code gives a correct car layout on 1.8.x.
The Media3 guide for mobile surfaces documents the new result: when the media button preferences contain a button for SLOT_BACK, that slot shows "Custom".
Devices that reproduce the issue
- Android Automotive OS emulator,
android-35-ext15automotive Play Store image, car media app - Android Auto (Desktop Head Unit)
- Phone system media controls
Devices that do not reproduce the issue
None known.
Reproducible in the demo app?
Not tested.
Reproduction steps
- In a
MediaLibraryService, callsetMediaButtonPreferenceswith:- a
CommandButtonwithPlayer.COMMAND_SEEK_TO_PREVIOUS,ICON_PREVIOUS, slotSLOT_BACK - a
CommandButtonwithPlayer.COMMAND_SEEK_TO_NEXT,ICON_NEXT, slotSLOT_FORWARD
- a
- Give the playing
MediaItema heart rating (MediaMetadata.Builder.setUserRating(HeartRating(false))), as an app with favorites does. - Start playback.
- Read
MediaControllerCompat.getPlaybackState()from a legacy controller. Check the actions and the custom actions. - Open the playback view in the Android Automotive OS car media app.
Expected result
A button whose command is the standard skip command, in its own slot, is published as ACTION_SKIP_TO_PREVIOUS / ACTION_SKIP_TO_NEXT, as before 1.9.0. It is not published as a custom action.
Actual result
The playback state has no skip actions. It has custom actions PLAYER_COMMAND_7 and PLAYER_COMMAND_9, in that order.
On Android Automotive OS the order on screen is wrong. The cause is in the AOSP car media app (packages/apps/Car/libs, car-media-common), not in Media3's order:
PlaybackViewModel.getCustomActionsputs the rating action first, for aRATING_HEARTsession, then the session's custom actions.MediaButtonController.updateCustomActionsfills slots by list position. With no standard skip actions, the first custom action goes in the left slot, the second in the right slot, and the rest outside.
With the heart rating from step 2, the bar on the emulator reads:
Next, rating star, play/pause, Previous
Without a rating, Previous and Next land left and right of play/pause. The order then looks right, but the buttons are still custom actions.
On phone system media controls and Android Auto, the layout is right. The only difference is the icons: they draw Media3's icons, not the surface's own skip icons.
Workaround: leave the skip buttons out of setMediaButtonPreferences. The session then keeps the standard skip actions, and each surface draws its own skip buttons. This works on Android Auto (DHU), Android 13 and 16 phone media controls, the Android 12L notification, and the Android Automotive OS emulator. The slots are not always reserved: the seek bar workaround in MediaSessionLegacyStub (needsButtonReservationWorkaroundForSeekbar) never reserves the next slot.
The mobile surfaces table shows Previous and Next when the back and forward slots are empty, so apps can leave skip out. But placing skip in its own slot is a natural reading of the slot API, and since 1.9.0 it gives a wrong layout on car UIs.
Request: for a skip button in its own slot, please publish the standard skip action, as in 1.8.x. If that is not possible, please document that apps should leave skip out of the media button preferences.
Media
Not applicable — no specific media needed.
Bug Report
Will provide on request.
- Dominant language
- Java
- Stars
- 3.1k
- Forks
- 998
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 2
Getting set up
- No Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from androidx/media
-
Waive ads when seek/start at the beginningPossibly taken @microkatz claimed this 1 day ago. Openenhancement
androidx/media#3496 · 2 comments · 1 assignee ·
Maintainers usually reply within 4 days
-
Allow configuring asset-list prefetch timing per HLS interstitialPossibly taken @marcbaechinger claimed this 1 day ago. Openenhancement needs triage
androidx/media#3494 · 1 assignee ·
Maintainers usually reply within 4 days
-
Offloaded audio left paused keeps AudioFlinger's AudioOffload wake lock forever, device never suspendsPossibly taken @microkatz claimed this 1 day ago. Open
androidx/media#3493 · 1 assignee ·
Maintainers usually reply within 4 days
-
Allow opting into frame-index seeking for MP3s without reliable seek metadataPossibly taken @icbaker claimed this 1 day ago. Open
Difficulty 4/5 3-5 days Newbie friendliness 48/100
androidx/media#3492 · 1 assignee ·
Maintainers usually reply within 4 days
-
MediaSessionService: the platform session keeps every player command and publishes the whole queue until the session is added, e.g. after a sticky restartPossibly taken @marcbaechinger claimed this 3 days ago. Openbug
Difficulty 4/5 3-5 days Newbie friendliness 45/100
androidx/media#3490 · 3 comments · 1 assignee ·
Maintainers usually reply within 4 days
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
oracle/graalvm-reachability-metadata#11472 ·
Maintainers usually reply within 1 day
-
status: waiting-for-triage
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
spring-projects/spring-ai#7269 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 89/100
objectionary/hone-maven-plugin#1353 ·
Maintainers usually reply within 9 days
-
ClassNotFoundException: io.netty.channel.kqueue.KQueueEventLoop stack trace printed during native build on main (Netty 4.2 removed the class)Possibly taken @gastaldi claimed this today. Openarea/netty kind/bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
quarkusio/quarkus#57939 · 1 comment ·
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
conductor-oss/conductor#1777 ·
Maintainers usually reply within 4 days