fix(android): PlayerController transport methods dispatch to controller thread
android / Build + lint + test (push) Successful in 4m17s
android / Build + lint + test (push) Successful in 4m17s
After DIDL fix, Sonos accepted SetAVTransportURI + Play, but
playerController.pause() threw IllegalStateException 'method is
called from a wrong thread' because selectUpnp runs the whole
UPnP-selection flow on Dispatchers.Default. UI tap handlers were
fine - they're already on Main - but the cross-thread background
call from OutputPickerController.selectUpnp hit the MediaController's
application-thread guard.
Same fix as the cold-boot resume one earlier today (commit e69a5204
wrapped setQueue): pause / play / seekTo / skipToNext / skipToPrevious
now route through runOnControllerThread, which is a no-op when
already on the application looper and Handler.post otherwise.
Logcat from on-device confirmed Sonos plays after this fix lands -
SetAVTransportURI -> 200, Play -> 200, then the IllegalStateException
was the last failure path.
This commit is contained in:
@@ -130,11 +130,24 @@ class PlayerController @Inject constructor(
|
||||
|
||||
// ── Transport (no-op until the controller is connected) ──────────────
|
||||
|
||||
fun play() { mediaController?.play() }
|
||||
fun pause() { mediaController?.pause() }
|
||||
fun seekTo(positionMs: Long) { mediaController?.seekTo(positionMs) }
|
||||
fun skipToNext() { mediaController?.seekToNextMediaItem() }
|
||||
fun skipToPrevious() { mediaController?.seekToPreviousMediaItem() }
|
||||
// Each transport call must run on the MediaController's
|
||||
// applicationLooper; calling from a background coroutine throws
|
||||
// IllegalStateException (see PlayerController.setQueue's note).
|
||||
// UI tap handlers are already on Main so the in-place branch hits;
|
||||
// the background path only fires for cross-thread callers like
|
||||
// OutputPickerController.selectUpnp (which calls pause() after
|
||||
// handing playback off to a UPnP renderer).
|
||||
fun play() { mediaController?.let { runOnControllerThread(it) { it.play() } } }
|
||||
fun pause() { mediaController?.let { runOnControllerThread(it) { it.pause() } } }
|
||||
fun seekTo(positionMs: Long) {
|
||||
mediaController?.let { runOnControllerThread(it) { it.seekTo(positionMs) } }
|
||||
}
|
||||
fun skipToNext() {
|
||||
mediaController?.let { runOnControllerThread(it) { it.seekToNextMediaItem() } }
|
||||
}
|
||||
fun skipToPrevious() {
|
||||
mediaController?.let { runOnControllerThread(it) { it.seekToPreviousMediaItem() } }
|
||||
}
|
||||
|
||||
/** Flip shuffle on/off. Media3 emits onEvents → uiState reflects. */
|
||||
fun toggleShuffle() {
|
||||
|
||||
Reference in New Issue
Block a user