Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/guide/video/camera-capture.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,8 @@ public class MyPeerConnectionObserver implements PeerConnectionObserver {

// The receiver and transceiver are query results you own; dispose
// them once the track has been retrieved. The track itself is
// unaffected and keeps delivering frames to its sink.
// unaffected and keeps delivering frames to its sink. It is a view
// of the receiver's track and needs no dispose() of its own.
receiver.dispose();
transceiver.dispose();
}
Expand Down
80 changes: 80 additions & 0 deletions webrtc-jni/src/main/cpp/include/media/MediaStreamTrackView.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2026 Alex Andres
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#ifndef JNI_WEBRTC_MEDIA_STREAM_TRACK_VIEW_H_
#define JNI_WEBRTC_MEDIA_STREAM_TRACK_VIEW_H_

#include "Exception.h"
#include "JavaClass.h"
#include "JavaFactories.h"
#include "JavaRef.h"

#include "api/media_stream_interface.h"
#include "api/scoped_refptr.h"

#include <jni.h>
#include <typeinfo>
#include <vector>

namespace jni
{
// A Java MediaStreamTrack made for a track that something else owns (a
// sender, a receiver, a stream, or the track a listener is called for)
// holds no reference to it. It is marked as a view, so that disposing it
// detaches the Java object without releasing a reference it never had.
// Tracks the factory creates hand their reference to the Java object and
// are not views.
class MediaStreamTrackView
{
public:
static JavaLocalRef<jobject> create(JNIEnv * env, const webrtc::MediaStreamTrackInterface * track);

template <class T>
static JavaLocalRef<jobjectArray> createArray(JNIEnv * env, const std::vector<webrtc::scoped_refptr<T>> & tracks)
{
jsize size = static_cast<jsize>(tracks.size());

JavaLocalRef<jobjectArray> array = JavaFactories::createArray<T>(env, size);

if (array.get() == nullptr) {
throw Exception("Create track array of type [%s] failed", typeid(T).name());
}

for (jsize i = 0; i < size; i++) {
JavaLocalRef<jobject> view = create(env, tracks[i].get());

env->SetObjectArrayElement(array, i, view.get());
}

return array;
}

static bool isView(JNIEnv * env, jobject javaTrack);

private:
static JavaLocalRef<jobject> markView(JNIEnv * env, JavaLocalRef<jobject> && javaTrack);

class JavaMediaStreamTrackClass : public JavaClass
{
public:
explicit JavaMediaStreamTrackClass(JNIEnv * env);

jfieldID view;
};
};
}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ namespace jni

private:
void addDevice(const std::string & name, const std::string & descriptor);
void removeDevice(const std::string & name, const std::string & descriptor);
void removeDevice(const std::string & descriptor);
void run();
bool checkDevice(const std::string & descriptor);
int ioctlDevice(int fh, int request, void * arg);
Expand Down
5 changes: 3 additions & 2 deletions webrtc-jni/src/main/cpp/src/JNI_MediaStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "JavaFactories.h"
#include "JavaString.h"
#include "JavaUtils.h"
#include "media/MediaStreamTrackView.h"

#include "api/media_stream_interface.h"
#include "rtc_base/logging.h"
Expand All @@ -42,7 +43,7 @@ JNIEXPORT jobjectArray JNICALL Java_dev_onvoid_webrtc_media_MediaStream_getAudio
jni::JavaLocalRef<jobjectArray> objectArray;

try {
objectArray = jni::createObjectArray(env, stream->GetAudioTracks());
objectArray = jni::MediaStreamTrackView::createArray(env, stream->GetAudioTracks());
}
catch (...) {
ThrowCxxJavaException(env);
Expand All @@ -60,7 +61,7 @@ JNIEXPORT jobjectArray JNICALL Java_dev_onvoid_webrtc_media_MediaStream_getVideo
jni::JavaLocalRef<jobjectArray> objectArray;

try {
objectArray = jni::createObjectArray(env, stream->GetVideoTracks());
objectArray = jni::MediaStreamTrackView::createArray(env, stream->GetVideoTracks());
}
catch (...) {
ThrowCxxJavaException(env);
Expand Down
8 changes: 8 additions & 0 deletions webrtc-jni/src/main/cpp/src/JNI_MediaStreamTrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "WebRTCContext.h"

#include "media/MediaStreamTrackObserver.h"
#include "media/MediaStreamTrackView.h"

#include "api/media_stream_interface.h"
#include "rtc_base/logging.h"
Expand All @@ -33,6 +34,13 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_MediaStreamTrack_dispose
webrtc::MediaStreamTrackInterface * track = GetHandle<webrtc::MediaStreamTrackInterface>(env, caller);
CHECK_HANDLE(track);

// A view holds no reference to the track and does not own its listeners:
// the track lives on with whatever owns it, so only this object detaches.
if (jni::MediaStreamTrackView::isView(env, caller)) {
SetHandle<std::nullptr_t>(env, caller, nullptr);
return;
}

// Listeners still registered are taken off the track before it goes. Their
// entries are keyed by this track's address, which a later track can be
// given, and an observer left registered would be freed with its entry
Expand Down
11 changes: 3 additions & 8 deletions webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include "api/media_stream_interface.h"
#include "api/rtp_receiver_interface.h"
#include "media/MediaStreamTrackView.h"

#include <algorithm>

Expand All @@ -37,14 +38,8 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_getTrack

webrtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track = receiver->track();

if (webrtc::AudioTrackInterface * t = dynamic_cast<webrtc::AudioTrackInterface *>(track.get())) {
return jni::JavaFactories::create(env, t).release();
}
else if (webrtc::VideoTrackInterface * t = dynamic_cast<webrtc::VideoTrackInterface *>(track.get())) {
return jni::JavaFactories::create(env, t).release();
}

return nullptr;
// The receiver keeps the track; the Java object is only a view of it.
return jni::MediaStreamTrackView::create(env, track.get()).release();
}

JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_getTransport
Expand Down
11 changes: 3 additions & 8 deletions webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "api/EncodedFrameTransformer.h"
#include "api/RTCDtmfSender.h"
#include "api/rtp_sender_interface.h"
#include "media/MediaStreamTrackView.h"

JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getTrack
(JNIEnv * env, jobject caller)
Expand All @@ -35,14 +36,8 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getTrack

webrtc::scoped_refptr<webrtc::MediaStreamTrackInterface> track = sender->track();

if (webrtc::AudioTrackInterface * t = dynamic_cast<webrtc::AudioTrackInterface *>(track.get())) {
return jni::JavaFactories::create(env, t).release();
}
else if (webrtc::VideoTrackInterface * t = dynamic_cast<webrtc::VideoTrackInterface *>(track.get())) {
return jni::JavaFactories::create(env, t).release();
}

return nullptr;
// The sender keeps the track; the Java object is only a view of it.
return jni::MediaStreamTrackView::create(env, track.get()).release();
}

JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getTransport
Expand Down
11 changes: 2 additions & 9 deletions webrtc-jni/src/main/cpp/src/media/MediaStreamTrackObserver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

#include "media/MediaStreamTrackObserver.h"
#include "media/MediaStreamTrackView.h"
#include "api/WebRTCUtils.h"
#include "JavaFactories.h"
#include "JavaUtils.h"
Expand Down Expand Up @@ -70,15 +71,7 @@ namespace jni

JavaLocalRef<jobject> MediaStreamTrackObserver::createJavaTrack(JNIEnv * env)
{
if (const webrtc::AudioTrackInterface * t = dynamic_cast<const webrtc::AudioTrackInterface *>(track)) {
return jni::JavaFactories::create(env, t);
}
else if (const webrtc::VideoTrackInterface * t = dynamic_cast<const webrtc::VideoTrackInterface *>(track)) {
return jni::JavaFactories::create(env, t);
}
else {
return jni::JavaLocalRef<jobject>(env, nullptr);
}
return MediaStreamTrackView::create(env, track);
}

MediaStreamTrackObserver::JavaMediaStreamTrackListenerClass::JavaMediaStreamTrackListenerClass(JNIEnv * env)
Expand Down
60 changes: 60 additions & 0 deletions webrtc-jni/src/main/cpp/src/media/MediaStreamTrackView.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2026 Alex Andres
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "media/MediaStreamTrackView.h"
#include "JavaClasses.h"
#include "JavaUtils.h"
#include "JNI_WebRTC.h"

#include <utility>

namespace jni
{
JavaLocalRef<jobject> MediaStreamTrackView::create(JNIEnv * env, const webrtc::MediaStreamTrackInterface * track)
{
if (const webrtc::AudioTrackInterface * t = dynamic_cast<const webrtc::AudioTrackInterface *>(track)) {
return markView(env, JavaFactories::create(env, t));
}
else if (const webrtc::VideoTrackInterface * t = dynamic_cast<const webrtc::VideoTrackInterface *>(track)) {
return markView(env, JavaFactories::create(env, t));
}

return JavaLocalRef<jobject>(env, nullptr);
}

JavaLocalRef<jobject> MediaStreamTrackView::markView(JNIEnv * env, JavaLocalRef<jobject> && javaTrack)
{
const auto javaClass = JavaClasses::get<JavaMediaStreamTrackClass>(env);

env->SetBooleanField(javaTrack, javaClass->view, JNI_TRUE);

return std::move(javaTrack);
}

bool MediaStreamTrackView::isView(JNIEnv * env, jobject javaTrack)
{
const auto javaClass = JavaClasses::get<JavaMediaStreamTrackClass>(env);

return env->GetBooleanField(javaTrack, javaClass->view) == JNI_TRUE;
}

MediaStreamTrackView::JavaMediaStreamTrackClass::JavaMediaStreamTrackClass(JNIEnv * env)
{
jclass cls = FindClass(env, PKG_MEDIA"MediaStreamTrack");

view = GetFieldID(env, cls, "view", "Z");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,12 @@ namespace jni
}

const char * node = lib.udev_device_get_devnode(dev);

if (!node) {
lib.udev_device_unref(dev);
continue;
}

bool error = false;

int v4l2_fd = open(node, O_RDONLY);
Expand Down Expand Up @@ -238,21 +244,25 @@ namespace jni
}

const char * subsystem = lib.udev_device_get_subsystem(dev);
const char * action = lib.udev_device_get_action(dev);
const char * node = lib.udev_device_get_devnode(dev);

if (strcmp(subsystem, UDEV_SUBSYSTEM) != 0) {
// Any of these can be missing, and a std::string made from
// a null pointer aborts this thread and the process with it.
if (!subsystem || !action || !node || strcmp(subsystem, UDEV_SUBSYSTEM) != 0) {
lib.udev_device_unref(dev);
continue;
}

const char * action = lib.udev_device_get_action(dev);
const char * node = lib.udev_device_get_devnode(dev);
const char * name = lib.udev_device_get_property_value(dev, "ID_V4L_PRODUCT");

if (strcmp(action, UDEV_ADD) == 0 && checkDevice(node)) {
addDevice(name, node);
// The product name is set by udev's v4l_id rule, which
// not every system has.
const char * name = lib.udev_device_get_property_value(dev, "ID_V4L_PRODUCT");

addDevice(name ? name : node, node);
}
else if (strcmp(action, UDEV_REMOVE) == 0) {
removeDevice(name, node);
removeDevice(node);
}

lib.udev_device_unref(dev);
Expand All @@ -270,10 +280,13 @@ namespace jni
notifyDeviceConnected(device);
}

void V4l2VideoDeviceManager::removeDevice(const std::string & name, const std::string & descriptor)
void V4l2VideoDeviceManager::removeDevice(const std::string & descriptor)
{
auto predicate = [name, descriptor](const VideoDevicePtr & dev) {
return dev->getName() == name && dev->getDescriptor() == descriptor;
// The device node identifies the device. Its name may have come
// from the driver (enumeration) or from udev (hotplug), which need
// not agree, so it is not compared.
auto predicate = [descriptor](const VideoDevicePtr & dev) {
return dev->getDescriptor() == descriptor;
};

VideoDevicePtr removed = captureDevices.removeDevice(predicate);
Expand Down
3 changes: 3 additions & 0 deletions webrtc/src/main/java/dev/onvoid/webrtc/RTCRtpReceiver.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ private RTCRtpReceiver() {
* track.stop() is final, although clones are not affected. Since
* receiver.track.stop() does not implicitly stop receiver, Receiver Reports
* continue to be sent.
* <p>
* The returned object is a view of the receiver's track; see
* {@link MediaStreamTrack#dispose()}.
*
* @return The media track associated with this receiver.
*/
Expand Down
3 changes: 3 additions & 0 deletions webrtc/src/main/java/dev/onvoid/webrtc/RTCRtpSender.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ private RTCRtpSender() {
* (video) or a zero-information-content equivalent. In the case of video,
* the RTCRtpSender SHOULD send one black frame per second. If track is
* {@code null} then the RTCRtpSender does not send.
* <p>
* The returned object is a view of the sender's track; see
* {@link MediaStreamTrack#dispose()}.
*
* @return The media track associated with this sender.
*/
Expand Down
6 changes: 4 additions & 2 deletions webrtc/src/main/java/dev/onvoid/webrtc/media/MediaStream.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,17 @@ private MediaStream() {

/**
* Returns an array of MediaStreamTrack objects representing the audio
* tracks in this stream.
* tracks in this stream. They are views of the stream's tracks; see
* {@link MediaStreamTrack#dispose()}.
*
* @return The audio tracks in this stream.
*/
public native AudioTrack[] getAudioTracks();

/**
* Returns an array of MediaStreamTrack objects representing the video
* tracks in this stream.
* tracks in this stream. They are views of the stream's tracks; see
* {@link MediaStreamTrack#dispose()}.
*
* @return The video tracks in this stream.
*/
Expand Down
Loading
Loading