Skip to content

AudioCapabilitiesReceiver invokes audio capability listener on a looper-less OEM thread #3386

Description

@ajay-jg

Version

Media3 1.7.1 (same as 1.6.1)

More version details

Media3 1.7.1

Devices that reproduce the issue

  • Jio STB devices running Android 10 or 11.
  • SkyworthDigital devices running Android 10 or 11.

Both onAudioDevicesAdded() and onAudioDevicesRemoved() appear in the crash reports.

Devices that do not reproduce the issue

We do not have a controlled negative-device test. No other device families have confirmed reports.

Reproducible in the demo app?

No

Reproduction steps

Reported production flow on both Jio STB and SkyworthDigital devices:

  1. Start or resume video playback.
  2. While playback is starting or active, connect, reconnect, wake, switch, or
    renegotiate the HDMI/audio route.
  3. Observe the crash.

Deterministic unit-test reproduction:

  1. Create an AudioCapabilitiesReceiver using a dedicated playback
    HandlerThread with a prepared Looper.
  2. Capture the registered AudioDeviceCallback.
  3. Invoke onAudioDevicesAdded() from a plain thread named
    AudioMonitorHdmiThread without calling Looper.prepare().
  4. Repeat with onAudioDevicesRemoved().
  5. Observe that the callback reaches the listener on the foreign thread.

The test uses a real playback HandlerThread and a plain callback thread with
no Looper. It does not require an affected device, HDMI cable, or media file.

Expected result

Capability evaluation, state changes, and listener delivery should run on the
handler/Looper captured by AudioCapabilitiesReceiver, regardless of the
callback thread used by the framework or OEM.

No exception is thrown.

Actual result

AudioCapabilitiesReceiverexecutes the callback body directly on the OEM callback thread. When the capabilities change, the listener is synchronously called from that thread.Looper.myLooper()isnull, while the audio sink's playback Looper is main`, causing the process to crash:

Fatal Exception: java.lang.IllegalStateException:
Current looper (null) is not the playback looper (main)
    at androidx.media3.exoplayer.audio.DefaultAudioSink.onAudioCapabilitiesChanged(DefaultAudioSink.java:1600)
    at androidx.media3.exoplayer.audio.AudioCapabilitiesReceiver.onNewAudioCapabilities(AudioCapabilitiesReceiver.java:199)
    at androidx.media3.exoplayer.audio.AudioCapabilitiesReceiver$AudioDeviceCallbackV23.onAudioDevicesAdded(AudioCapabilitiesReceiver.java:246)
    at android.media.AudioManager$AudioMonitorHdmiThread.run(AudioManager.java:5692)

The removal path has the same failure:

Fatal Exception: java.lang.IllegalStateException:
Current looper (null) is not the playback looper (main)
    at androidx.media3.exoplayer.audio.DefaultAudioSink.onAudioCapabilitiesChanged(DefaultAudioSink.java:1600)
    at androidx.media3.exoplayer.audio.AudioCapabilitiesReceiver.onNewAudioCapabilities(AudioCapabilitiesReceiver.java:199)
    at androidx.media3.exoplayer.audio.AudioCapabilitiesReceiver$AudioDeviceCallbackV23.onAudioDevicesRemoved(AudioCapabilitiesReceiver.java:255)
    at android.media.AudioManager$AudioMonitorHdmiThread.run(AudioManager.java:5047)

The primary local assertion against the unmodified implementation is:

expected: ExoPlayer:Playback
but was : AudioMonitorHdmiThread

Media

Not applicable. The crash is caused by audio-device callback threading and does not depend on the media being played.

Bug Report

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions