Skip to content

fix: keep exceptions inside the audio device change callbacks - #333

Merged
devopvoid merged 1 commit into
devopvoid:mainfrom
Yml-0:fix/device-listener-exceptions
Oct 9, 2026
Merged

devopvoid merged 1 commit into
devopvoid:mainfrom
Yml-0:fix/device-listener-exceptions

Conversation

@Yml-0

@Yml-0 Yml-0 commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #332.

What

CoreAudioDeviceManager::deviceListenerProc (macOS) and OnDeviceAdded / OnDeviceRemoved / OnDeviceStateChanged of WindowsAudioDeviceManager (Windows) now catch whatever the refresh throws, log it, and return normally.

Why

These callbacks run on threads the operating system owns — a CoreAudio HAL queue, the MMDevice notification thread. They call createAudioDevice(), which throws jni::Exception when a device's properties cannot be read, and a device that disappears between the device list and that read is an ordinary event (a Bluetooth headset switching profile, an aggregate device torn down, a device unplugged). The exception had no handler above it, so the process aborted. The crash report and the call chain are in #332.

Scope

  • Two files, the four callbacks. Nothing about a successful refresh changes.
  • OnDefaultDeviceChanged on Windows is left alone: it only looks devices up in the lists already held.
  • A finer-grained variant — createAudioDevice() returning nullptr for an unreadable device so the rest of the same refresh still happens — is easy to add if you prefer it. The catch at the boundary is needed either way, since the listeners also call into Java.

Testing

Not built: I do not have the WebRTC checkout the JNI build needs, so this has only been read, not compiled. The macOS path is the one I have a crash report for; the Windows half is from reading the source.

🤖 Generated with Claude Code

CoreAudioDeviceManager::deviceListenerProc and the IMMNotificationClient callbacks of
WindowsAudioDeviceManager run on threads the operating system owns, and call code that
throws jni::Exception when a device's properties cannot be read. A device that disappears
between the device list and that read is an ordinary event, and the exception had no
handler above it: std::terminate, and the JVM process aborted.

The callbacks now log what was thrown and return normally.

Fixes devopvoid#332
@Yml-0
Yml-0 force-pushed the fix/device-listener-exceptions branch from 6bc2840 to 7755766 Compare October 8, 2026 12:06
@devopvoid
devopvoid merged commit 1bbef2a into devopvoid:main Oct 9, 2026
1 check passed
devopvoid added a commit that referenced this pull request Oct 9, 2026
…ate (#337)

Follow-ups to #333, which made the audio device change callbacks catch what
a refresh throws.

- One audio device that cannot be read no longer ends the refresh. On macOS
  it cut onDevicesChanged() short, so new devices were not added and removed
  ones stayed listed with no event; createAudioDevice() now skips it.
  checkDeviceGone() compares with the default the manager holds instead of
  re-reading a device that has gone. On Windows, a device whose transport or
  form factor cannot be read keeps the unknown ones. The default device
  getters return null when there is no such device, on both platforms.
- DeviceChangeListener never throws, and describes and clears what a Java
  listener throws instead of leaving it pending on the OS thread.
- The device listener list is locked, and listeners are called on a copy
  outside the lock.
- JavaContext::removeNativeRef() freed a global reference twice on every
  listener removal, and its registry had no lock.
- Track listeners are found by their track and kind, not by the listener
  object alone, so one listener on two tracks, or one object as both kinds,
  no longer has the wrong registration removed (and its observer freed while
  still in use). A disposed track takes its remaining listeners off first.

AudioTrackTests covers listener removal alone and from eight threads, a
listener on two tracks, one of both kinds, and disposing a track with
listeners.
SendableMetatype added a commit to EduGeyser/webrtc-java that referenced this pull request Oct 10, 2026
Brings in the work upstream added on October 10: the missing peer
connection signaling and control calls (devopvoid#334), callback exceptions kept
from unwinding through WebRTC (devopvoid#335), first-packet observers, sender and
receiver IDs and track volume (devopvoid#338), video processors for camera and
desktop frames (devopvoid#342), and fixes to audio device changes, camera
hotplug and media stream views (devopvoid#333, devopvoid#337, devopvoid#341, devopvoid#343).

Fork-side adaptations:
- setAudioPlayout and setAudioRecording reach the voice engine, so the
  data channels variant leaves them out, like the other audio natives.
- The new tests that need audio or video carry the media tag.
- The Maven publish action stays deleted.
SendableMetatype added a commit to EduGeyser/webrtc-java that referenced this pull request Oct 10, 2026
Brings in the work upstream added on October 10: the missing peer
connection signaling and control calls (devopvoid#334), callback exceptions kept
from unwinding through WebRTC (devopvoid#335), first-packet observers, sender and
receiver IDs and track volume (devopvoid#338), video processors for camera and
desktop frames (devopvoid#342), and fixes to audio device changes, camera
hotplug and media stream views (devopvoid#333, devopvoid#337, devopvoid#341, devopvoid#343).

Fork-side adaptations:
- setBitrate, setAudioPlayout and setAudioRecording reach the call and
  the voice engine, which a factory without a media engine does not
  create, so the data channels variant leaves them out, like the other
  media natives.
- The new tests that need audio or video carry the media tag.
- The Maven publish action stays deleted.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Audio device change callbacks throw across the OS boundary and abort the process (macOS, Windows)

2 participants