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
22 changes: 22 additions & 0 deletions docs/guide/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,28 @@ RTCRtpSender videoSender = peerConnection.addTrack(videoTrack, streamIds);
RTCRtpSender audioSender = peerConnection.addTrack(audioTrack, streamIds);
```

### Receiving Tracks

A remote track arrives in `onTrack` with the transceiver that carries it. To learn when its media actually starts flowing, for example to show a video only once there is something to show, set an `RTCRtpReceiverObserver`; an `RTCRtpSenderObserver` does the same for the first packet a sender sends. Either is called right away when that has already happened:

```java
@Override
public void onTrack(RTCRtpTransceiver transceiver) {
RTCRtpReceiver receiver = transceiver.getReceiver();

receiver.setObserver(mediaType -> System.out.println("First " + mediaType + " packet received"));

MediaStreamTrack track = receiver.getTrack();

if (track instanceof AudioTrack) {
// Playout volume of this remote track, 0 to 10; 1 plays it as received.
((AudioTrack) track).setVolume(0.5);
}
}
```

An observer belongs to the `RTCRtpReceiver` or `RTCRtpSender` object it was set through, and is removed when that object is disposed. `getId()` identifies a sender or receiver, and `RTCRtpSender.getStreams()` returns the stream IDs its track was added with.

## Cleanup

Always properly dispose of resources when you're done:
Expand Down
8 changes: 8 additions & 0 deletions webrtc-jni/src/main/cpp/include/JNI_AudioTrack.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions webrtc-jni/src/main/cpp/include/JNI_RTCRtpReceiver.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions webrtc-jni/src/main/cpp/include/JNI_RTCRtpSender.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions webrtc-jni/src/main/cpp/include/api/PeerConnectionObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,6 @@ namespace jni
};

private:
// Reports a Java exception thrown by a callback without letting it
// unwind through WebRTC.
void ReportException(JNIEnv * env) noexcept;

JavaGlobalRef<jobject> observer;

const std::shared_ptr<JavaPeerConnectionObserverClass> javaClass;
Expand Down
55 changes: 55 additions & 0 deletions webrtc-jni/src/main/cpp/include/api/RTCRtpReceiverObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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_API_RTC_RTP_RECEIVER_OBSERVER_H_
#define JNI_WEBRTC_API_RTC_RTP_RECEIVER_OBSERVER_H_

#include "JavaClass.h"
#include "JavaRef.h"

#include "api/rtp_receiver_interface.h"

#include <jni.h>
#include <memory>

namespace jni
{
class RTCRtpReceiverObserver : public webrtc::RtpReceiverObserverInterface
{
public:
RTCRtpReceiverObserver(JNIEnv * env, const JavaGlobalRef<jobject> & observer);
~RTCRtpReceiverObserver() = default;

// RtpReceiverObserverInterface implementation.
void OnFirstPacketReceived(webrtc::MediaType media_type) override;

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

jmethodID onFirstPacketReceived;
};

private:
JavaGlobalRef<jobject> observer;

const std::shared_ptr<JavaRTCRtpReceiverObserverClass> javaClass;
};
}

#endif
55 changes: 55 additions & 0 deletions webrtc-jni/src/main/cpp/include/api/RTCRtpSenderObserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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_API_RTC_RTP_SENDER_OBSERVER_H_
#define JNI_WEBRTC_API_RTC_RTP_SENDER_OBSERVER_H_

#include "JavaClass.h"
#include "JavaRef.h"

#include "api/rtp_sender_interface.h"

#include <jni.h>
#include <memory>

namespace jni
{
class RTCRtpSenderObserver : public webrtc::RtpSenderObserverInterface
{
public:
RTCRtpSenderObserver(JNIEnv * env, const JavaGlobalRef<jobject> & observer);
~RTCRtpSenderObserver() = default;

// RtpSenderObserverInterface implementation.
void OnFirstPacketSent(webrtc::MediaType media_type) override;

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

jmethodID onFirstPacketSent;
};

private:
JavaGlobalRef<jobject> observer;

const std::shared_ptr<JavaRTCRtpSenderObserverClass> javaClass;
};
}

#endif
8 changes: 7 additions & 1 deletion webrtc-jni/src/main/cpp/include/api/WebRTCUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,13 @@ namespace jni
// uncaught exception handler, as the JVM does for an exception escaping a
// Java thread. Callbacks that WebRTC invokes use it, because a C++
// exception must not unwind through WebRTC's frames.
void ReportUncaughtException(JNIEnv * env, jthrowable exception);
void ReportUncaughtException(JNIEnv * env, jthrowable exception) noexcept;

// Clears the Java exception pending on the calling thread, if any, and
// reports it as uncaught. A callback that WebRTC invokes ends with this
// instead of ExceptionCheck(), which throws, after turning any C++
// exception of its own into a Java one with ThrowCxxJavaException().
void ReportPendingException(JNIEnv * env) noexcept;
}

#endif
15 changes: 15 additions & 0 deletions webrtc-jni/src/main/cpp/src/JNI_AudioTrack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_audio_AudioTrack_removeSinkI
}
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_audio_AudioTrack_setVolumeInternal
(JNIEnv * env, jobject caller, jdouble volume)
{
webrtc::AudioTrackInterface * track = GetHandle<webrtc::AudioTrackInterface>(env, caller);
CHECK_HANDLE(track);

// Only the source of a remote track, a RemoteAudioSource, applies the
// volume; the source of a local track ignores it.
webrtc::AudioSourceInterface * source = track->GetSource();

if (source != nullptr) {
source->SetVolume(static_cast<double>(volume));
}
}

JNIEXPORT jint JNICALL Java_dev_onvoid_webrtc_media_audio_AudioTrack_getSignalLevel
(JNIEnv * env, jobject caller)
{
Expand Down
26 changes: 18 additions & 8 deletions webrtc-jni/src/main/cpp/src/JNI_CustomVideoSource.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,15 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_CustomVideoSource_push
CHECK_HANDLE(source);

if (javaFrame != nullptr) {
auto frame = jni::JavaLocalRef<jobject>(env, javaFrame);
webrtc::VideoFrame nativeFrame = jni::VideoFrame::toNative(env, frame);

source->PushFrame(nativeFrame);
try {
auto frame = jni::JavaLocalRef<jobject>(env, javaFrame);
webrtc::VideoFrame nativeFrame = jni::VideoFrame::toNative(env, frame);

source->PushFrame(nativeFrame);
}
catch (...) {
ThrowCxxJavaException(env);
}
}
}

Expand All @@ -81,9 +86,14 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_media_video_CustomVideoSource_push
CHECK_HANDLE(source);

if (javaFrame != nullptr) {
auto frame = jni::JavaLocalRef<jobject>(env, javaFrame);
webrtc::VideoFrame nativeFrame = jni::VideoFrame::toNative(env, frame);

source->PushFrame(nativeFrame, timestampUs);
try {
auto frame = jni::JavaLocalRef<jobject>(env, javaFrame);
webrtc::VideoFrame nativeFrame = jni::VideoFrame::toNative(env, frame);

source->PushFrame(nativeFrame, timestampUs);
}
catch (...) {
ThrowCxxJavaException(env);
}
}
}
43 changes: 43 additions & 0 deletions webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

#include "JNI_RTCRtpReceiver.h"
#include "api/RTCRtpParameters.h"
#include "api/RTCRtpReceiverObserver.h"
#include "api/RTCRtpContributingSource.h"
#include "api/RTCRtpSynchronizationSource.h"
#include "JavaFactories.h"
#include "JavaList.h"
#include "JavaString.h"
#include "JavaUtils.h"

#include "api/EncodedFrameTransformer.h"
Expand Down Expand Up @@ -149,12 +151,53 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_requestKeyFrame
}
}

JNIEXPORT jstring JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_getId
(JNIEnv * env, jobject caller)
{
webrtc::RtpReceiverInterface * receiver = GetHandle<webrtc::RtpReceiverInterface>(env, caller);
CHECK_HANDLEV(receiver, nullptr);

return jni::JavaString::toJava(env, receiver->id()).release();
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_setObserver
(JNIEnv * env, jobject caller, jobject jObserver)
{
webrtc::RtpReceiverInterface * receiver = GetHandle<webrtc::RtpReceiverInterface>(env, caller);
CHECK_HANDLE(receiver);

try {
jni::RTCRtpReceiverObserver * observer = nullptr;

if (jObserver != nullptr) {
observer = new jni::RTCRtpReceiverObserver(env, jni::JavaGlobalRef<jobject>(env, jObserver));
}

// Runs on the signaling thread, which is also the thread that calls
// the observer, so the previous one is out of use once this returns.
receiver->SetObserver(observer);

ReplaceNativeObserver(env, caller, "observerHandle", observer);
}
catch (...) {
ThrowCxxJavaException(env);
}
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_dispose
(JNIEnv * env, jobject caller)
{
webrtc::RtpReceiverInterface * receiver = GetHandle<webrtc::RtpReceiverInterface>(env, caller);
CHECK_HANDLE(receiver);

// WebRTC must let go of an observer set through this object before it is
// deleted.
if (GetHandle<jni::RTCRtpReceiverObserver>(env, caller, "observerHandle") != nullptr) {
receiver->SetObserver(nullptr);

ClearNativeObserver<jni::RTCRtpReceiverObserver>(env, caller, "observerHandle");
}

// Unlike e.g. MediaStreamTrack, an RTCRtpReceiver is not exclusively
// owned by one Java wrapper: the owning RtpTransceiver keeps its own
// reference, and other Java wrappers may have been obtained via separate
Expand Down
Loading