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:
parent
d2713ff2f5
commit
46bf78aac4
2 changed files with 27 additions and 1 deletions
|
|
@ -122,7 +122,11 @@ class MusicService
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
if (mediaSession == null) {
|
if (mediaSession == null) {
|
||||||
Timber.i("Starting music MediaSession")
|
Timber.i("Starting music MediaSession")
|
||||||
mediaSession = MediaSession.Builder(context, player).build()
|
mediaSession =
|
||||||
|
MediaSession
|
||||||
|
.Builder(context, player)
|
||||||
|
.setCallback(TrustAllControllersCallback)
|
||||||
|
.build()
|
||||||
activityTracker =
|
activityTracker =
|
||||||
TrackActivityPlaybackListener(api, player) {
|
TrackActivityPlaybackListener(api, player) {
|
||||||
state.value.currentItemId?.let { itemId ->
|
state.value.currentItemId?.let { itemId ->
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,8 @@ import com.github.damontecres.wholphin.util.DolbyVisionProfile7ExtractorsFactory
|
||||||
import com.github.damontecres.wholphin.util.DolbyVisionProfile7Filter
|
import com.github.damontecres.wholphin.util.DolbyVisionProfile7Filter
|
||||||
import com.github.damontecres.wholphin.util.Hdr10PlusMaskingFilter
|
import com.github.damontecres.wholphin.util.Hdr10PlusMaskingFilter
|
||||||
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
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 dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import io.github.peerless2012.ass.media.AssHandler
|
import io.github.peerless2012.ass.media.AssHandler
|
||||||
import io.github.peerless2012.ass.media.factory.AssRenderersFactory
|
import io.github.peerless2012.ass.media.factory.AssRenderersFactory
|
||||||
|
|
@ -269,9 +271,29 @@ class PlayerFactory
|
||||||
fun createMediaSession(player: Player) =
|
fun createMediaSession(player: Player) =
|
||||||
MediaSession
|
MediaSession
|
||||||
.Builder(context, player)
|
.Builder(context, player)
|
||||||
|
.setCallback(TrustAllControllersCallback)
|
||||||
.build()
|
.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
|
val Player.isReleased: Boolean
|
||||||
get() {
|
get() {
|
||||||
return when (this) {
|
return when (this) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue