Allow play/pause from untrusted media controllers (#2145)

## Description
- From media3 1.11, controllers that aren't trusted get read-only player
commands by default.
- Google Home sends its commands through
com.google.android.apps.mediashell (Google's Cast service on the TV),
which isn't trusted, so play/pause from it was dropped.
- Logcat showed: MediaSessionLegacyStub: Calling play() omitted due to
COMMAND_PLAY_PAUSE not being available
- The change: a MediaSession.Callback (TrustAllControllersCallback in
PlayerFactory.kt) that gives every controller all player commands again.
It uses onConnectAsync and AcceptedResultBuilder(session, controller),
so untrusted controllers keep the restricted session commands.
- It's applied to the video session (createMediaSession) and the music
session (MusicService).
- It probably started with the media3 1.11 bump in #1814.

### Related issues
<!-- If this is a new feature or a change, there must be a discussion in
an issue first, reference the issue here -->
<!-- If fixing a bug, reference the bug issue here, or describe the bug,
including steps to reproduce -->
- Fixes #2087 

### Testing
- JMGO N1S 4K, Google TV, Android 14, ExoPlayer.
- Before the fix: play/pause from Google Home did nothing, and logcat
showed the "omitted" warning.
- After the fix: pause and play from Google Home work. Logcat shows each
command reaching handleSetPlayWhenReady, and the warning is gone.
- The TV remote still works.
- I didn't test music playback; leave that out or test it first.

## Screenshots
- None; no UI changes.

## AI or LLM usage
- Claude Code read the logcat output, found the media3 1.11
untrusted-controller change, and wrote TrustAllControllersCallback plus
the two setCallback(...) calls.
This commit is contained in:
Ali Alaei 2026-10-31 20:24:07 +01:00 • committed by GitHub
parent d2713ff2f5
commit 46bf78aac4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 27 additions and 1 deletions

View file

@ -122,7 +122,11 @@ class MusicService
mutex.withLock {
if (mediaSession == null) {
Timber.i("Starting music MediaSession")
mediaSession = MediaSession.Builder(context, player).build()
mediaSession =
MediaSession
.Builder(context, player)
.setCallback(TrustAllControllersCallback)
.build()
activityTracker =
TrackActivityPlaybackListener(api, player) {
state.value.currentItemId?.let { itemId ->

View file

@ -40,6 +40,8 @@ import com.github.damontecres.wholphin.util.DolbyVisionProfile7ExtractorsFactory
import com.github.damontecres.wholphin.util.DolbyVisionProfile7Filter
import com.github.damontecres.wholphin.util.Hdr10PlusMaskingFilter
import com.github.damontecres.wholphin.util.WholphinDispatchers
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import dagger.hilt.android.qualifiers.ApplicationContext
import io.github.peerless2012.ass.media.AssHandler
import io.github.peerless2012.ass.media.factory.AssRenderersFactory
@ -269,9 +271,29 @@ class PlayerFactory
fun createMediaSession(player: Player) =
MediaSession
.Builder(context, player)
.setCallback(TrustAllControllersCallback)
.build()
}
/**
* Since media3 1.11, untrusted controllers (e.g. Google Home's Cast bridge, `com.google.android.apps.mediashell`)
* only get read-only player commands by default, so play/pause from them is silently dropped.
* Restore the pre-1.11 behavior of granting all player commands to every controller.
*/
@OptIn(UnstableApi::class)
object TrustAllControllersCallback : MediaSession.Callback {
override fun onConnectAsync(
session: MediaSession,
controller: MediaSession.ControllerInfo,
): ListenableFuture<MediaSession.ConnectionResult> =
Futures.immediateFuture(
MediaSession.ConnectionResult
.AcceptedResultBuilder(session, controller)
.setAvailablePlayerCommands(MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS)
.build(),
)
}
val Player.isReleased: Boolean
get() {
return when (this) {