Skip to content

fix: keep camera hotplug and track views from crashing or releasing shared tracks - #341

Merged
devopvoid merged 2 commits into
mainfrom
fix/v4l2-name-and-receiver-track
Oct 10, 2026
Merged

devopvoid merged 2 commits into
mainfrom
fix/v4l2-name-and-receiver-track

Conversation

@devopvoid

Copy link
Copy Markdown
Owner

Two fixes found while reviewing #337. They are separate commits and touch separate code.

Linux camera hotplug no longer aborts the process

V4l2VideoDeviceManager's hotplug thread made a std::string from udev's ID_V4L_PRODUCT property, which only udev's v4l_id rule sets. Where that rule is missing (minimal systems, containers) the property is null, and the first camera plugged in aborted the process. A null subsystem, action or device node would have crashed strcmp() and open() the same way.

  • Such events are skipped; a camera without a product name is named by its device node.
  • A removed camera is found by its node alone. Its name came from the driver at enumeration (vcap.card) and from udev at hotplug, which need not agree, so comparing names could leave an unplugged camera listed with no event.
  • An enumerated device without a node is skipped instead of being opened with null.

Disposing a track you did not create no longer releases someone else's reference

RTCRtpSender.getTrack(), RTCRtpReceiver.getTrack(), MediaStream.getAudioTracks()/getVideoTracks() and the track passed to a track listener wrapped the native track without taking a reference, while MediaStreamTrack.dispose() released one. Disposing such a track dropped the sender's, receiver's or stream's reference and could free the track while WebRTC still used it.

  • These objects are now marked as views (MediaStreamTrackView, a new native helper). Disposing a view only detaches the Java object: it releases nothing, throws nothing, and leaves the listeners registered on the track alone.
  • Tracks created by the factory are unchanged, including the error when something still holds them on dispose.
  • Views deliberately take no reference of their own: nothing in the tests, examples or docs disposes them, and a reference per view (one per listener event, too) would keep tracks alive and make their owner's dispose() fail.
  • The Javadoc of MediaStreamTrack.dispose() and of the four methods returning views, and the onTrack example in the camera capture guide, say so.

Testing

Windows x64: mvn -pl webrtc-jni,webrtc verify builds, and the webrtc suite passes (258 tests, 0 failures, 2 skipped), also under -Pjni-check with no fatal JNI errors.

MediaStreamTrackViewTests disposes the view of a sender's track, of a receiver's track, and the track given to a listener. With the view check taken out of dispose(), the sender and receiver tests crashed the JVM. The stream path has no test of its own, as the factory cannot create a MediaStream; it goes through the same helper.

The V4L2 change is Linux-only and was not built locally; this PR's CI is its first build. Hotplug cannot be triggered from a test.

The V4L2 hotplug thread made a std::string from udev's ID_V4L_PRODUCT
property, which only udev's v4l_id rule sets. Where that rule is missing, as
on minimal systems and in containers, the property is null and the thread,
and the process with it, aborted on the first camera plugged in. A null
subsystem, action or device node would have crashed strcmp() and open() the
same way, and an enumerated device without a node was opened with null too.

Such events are now skipped, and a device without a product name is named by
its node. A removed device is found by its node alone: its name came from
the driver at enumeration and from udev at hotplug, which need not agree, so
comparing names could leave an unplugged camera listed with no event.
… view

RTCRtpSender.getTrack(), RTCRtpReceiver.getTrack(), MediaStream's track
arrays and the track passed to a track listener wrapped the native track
without taking a reference, while MediaStreamTrack.dispose() released one.
Disposing such a track dropped a reference that the sender, receiver or
stream held, and it could free the track while WebRTC still used it; the
dispose of the factory track it came from failed or crashed later.

These objects are now marked as views (MediaStreamTrackView). Disposing a
view only detaches the Java object: it releases nothing, throws nothing, and
leaves the listeners registered on the track alone. Tracks created by the
factory are unchanged, including the error when something still holds them
on dispose. Views stay without a reference of their own on purpose: callers
do not dispose them today, and a reference held by every view, including one
per listener event, would keep tracks alive and make their owner's dispose
fail.

MediaStreamTrackViewTests disposes the view of a sender's track, of a
receiver's track, and the track given to a listener. With the view check
taken out of dispose(), the first two crashed the JVM.
@devopvoid
devopvoid merged commit 69e0029 into main Oct 10, 2026
9 of 16 checks passed
@devopvoid
devopvoid deleted the fix/v4l2-name-and-receiver-track branch October 10, 2026 12:55
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.

1 participant