Description
Audio device changes on certain Android TV boxes cause a fatal crash:
java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread.
This happens because AudioManager$AudioMonitorHdmiThread delivers
AudioDeviceCallback.onAudioDevicesAdded/onAudioDevicesRemoved directly on a
background thread, ignoring the main-looper Handler passed to
AudioManager.registerAudioDeviceCallback(). The callback then calls
MethodChannel.invokeMethod(), which is annotated @UiThread, from that
background thread.
The crash is rare per device but affects specific low-end TV boxes:
- 99% SkyworthDigital TV — XStream-Smart-Box-002 — Android 10
- <1% Inspur TV — STB6252C — Android 11
- Also reported on Amazon Fire TV devices
Steps to reproduce
Cannot reproduce in the emulator or on most hardware. It only occurs when the
OS delivers audio device change callbacks on AudioManager$AudioMonitorHdmiThread.
This thread only exists on devices with HDMI audio (TV boxes), which is why it is
not seen on phones.
Code sample (if applicable)
The relevant code path in this package:
// AndroidAudioManager.kt
override fun onAudioDevicesAdded(addedDevices: Array<AudioDeviceInfo>) {
invokeMethod("onAudioDevicesAdded", encodeAudioDevices(addedDevices))
}
where `invokeMethod` calls `MethodChannel.invokeMethod` directly.
__Error stack trace__
```javascript
Fatal Exception: java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread. Current thread: Thread-5
at io.flutter.embedding.engine.FlutterJNI.ensureRunningOnMainThread(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:37)
at io.flutter.embedding.engine.FlutterJNI.dispatchPlatformMessage(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:1)
at io.flutter.embedding.engine.dart.DartMessenger.send(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:8)
at io.flutter.embedding.engine.dart.DartExecutor$DefaultBinaryMessenger.send(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:2)
at io.flutter.embedding.engine.dart.DartExecutor.send(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:2)
at io.flutter.plugin.common.MethodChannel.invokeMethod(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:5)
at io.flutter.plugin.common.MethodChannel.invokeMethod(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:1)
at f6.d.A(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:40)
at f6.d$b.onAudioDevicesAdded(r8-map-id-06f4a25a21f5b6d8c6d7e8dc6be290926624f2defb1b30c7d4ceb8c8b94b8cc7:20)
at android.media.AudioManager$AudioMonitorHdmiThread.run(AudioManager.java:5040)
Device / OS
- SkyworthDigital TV — XStream-Smart-Box-002 — Android 10
- Inspur TV — STB6252C — Android 11
- Amazon Fire TV devices (low-end)
Logs and outputs
See stack trace above. onAudioDevicesAdded is the failing frame; the same crash class also applies to onAudioDevicesRemoved.
Additional context
- A minimal, low-risk fix is ready — see the attached PR.
Description
Audio device changes on certain Android TV boxes cause a fatal crash:
java.lang.RuntimeException: Methods marked with @UiThread must be executed on the main thread.This happens because
AudioManager$AudioMonitorHdmiThreaddeliversAudioDeviceCallback.onAudioDevicesAdded/onAudioDevicesRemoveddirectly on abackground thread, ignoring the main-looper
Handlerpassed toAudioManager.registerAudioDeviceCallback(). The callback then callsMethodChannel.invokeMethod(), which is annotated@UiThread, from thatbackground thread.
The crash is rare per device but affects specific low-end TV boxes:
Steps to reproduce
Cannot reproduce in the emulator or on most hardware. It only occurs when the
OS delivers audio device change callbacks on
AudioManager$AudioMonitorHdmiThread.This thread only exists on devices with HDMI audio (TV boxes), which is why it is
not seen on phones.
Code sample (if applicable)
The relevant code path in this package:
Device / OS
Logs and outputs
See stack trace above.
onAudioDevicesAddedis the failing frame; the same crash class also applies toonAudioDevicesRemoved.Additional context