Skip to content
Open
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
17 changes: 11 additions & 6 deletions docs/guide/audio/dtmf-sender.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,16 +132,21 @@ int interToneGap = dtmfSender.interToneGap();

## Cleanup

When you're done with the DTMF sender, you should unregister any observers:
Each call to `getDtmfSender()` returns a new `RTCDtmfSender` that holds its own reference to the
native DTMF sender. It stays safe to use after the connection has closed, when `canInsertDtmf()`
returns `false`, until you dispose it. Disposing also unregisters the observer registered through
it:

```java
// Unregister the observer
dtmfSender.unregisterObserver();
dtmfSender.dispose();
```

`RTCDtmfSender` has no `dispose()` method of its own; it is tied to the native sender's lifetime.
The `RTCRtpSender` it came from, however, is not owned by the peer connection, so make sure to
dispose that once you're done with it:
To stop listening without disposing, call `dtmfSender.unregisterObserver()`. It removes only the
observer registered through that instance; one registered since through another `RTCDtmfSender` of
the same sender stays.

The `RTCRtpSender` the DTMF sender came from is not owned by the peer connection either, so make
sure to dispose that once you're done with it:

```java
sender.dispose();
Expand Down
8 changes: 8 additions & 0 deletions webrtc-jni/src/main/cpp/include/JNI_RTCDtmfSender.h

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

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

namespace jni
{
// Which native observer is registered on a WebRTC object that has room for
// one, such as a sender, a receiver or a DTLS transport.
//
// Many Java objects may stand for the same native object, and each owns
// the observer registered through it. Registering through one replaces the
// observer of another, which then must not unregister the one that
// replaced it when it is disposed. The Java objects learn here whether the
// observer they own is still the registered one.
//
// The registry is told before WebRTC is, and its lock is never held while
// WebRTC is called: some observers are called on the thread that runs the
// registration, and their Java code may register again.
namespace RegisteredObservers
{
// Records the observer as the one registered on the owner, in place of
// any other.
void Set(const void * owner, const void * observer);

// Forgets the observer if it is the one registered on the owner, and
// returns whether it was. Only then is it to be unregistered.
bool Remove(const void * owner, const void * observer);
}
}

#endif
41 changes: 28 additions & 13 deletions webrtc-jni/src/main/cpp/src/JNI_RTCDtlsTransport.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

#include "JNI_RTCDtlsTransport.h"
#include "api/RegisteredObservers.h"
#include "api/RTCDtlsTransportObserver.h"
#include "rtc/RTCCertificatePEM.h"
#include "JavaArrayList.h"
Expand Down Expand Up @@ -89,21 +90,41 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_getRemoteCerti
}
}

namespace
{
// Unregisters and frees the observer registered through the Java object.
// WebRTC is told only while that observer is still the registered one:
// another Java object for the same transport may have registered its own
// since, which stays.
void UnregisterOwnObserver(JNIEnv * env, jobject caller, webrtc::DtlsTransportInterface * transport)
{
auto observer = GetHandle<jni::RTCDtlsTransportObserver>(env, caller, "observerHandle");

if (jni::RegisteredObservers::Remove(transport, observer)) {
transport->UnregisterObserver();
}

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

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_registerObserver
(JNIEnv * env, jobject caller, jobject observer)
{
try {
webrtc::DtlsTransportInterface * transport = GetHandle<webrtc::DtlsTransportInterface>(env, caller);
CHECK_HANDLE(transport);

// Unregister and free a previously registered observer before replacing
// it, otherwise it would leak along with its JNI global reference.
transport->UnregisterObserver();

auto newObserver = new jni::RTCDtlsTransportObserver(env, jni::JavaGlobalRef<jobject>(env, observer));
ReplaceNativeObserver(env, caller, "observerHandle", newObserver);

// Registering replaces whatever observer the transport had, also one
// registered through another Java object, so the one registered
// through this object before is out of use and can be freed.
jni::RegisteredObservers::Set(transport, newObserver);

transport->RegisterObserver(newObserver);

ReplaceNativeObserver(env, caller, "observerHandle", newObserver);
}
catch (...) {
ThrowCxxJavaException(env);
Expand All @@ -116,9 +137,7 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_unregisterObserve
webrtc::DtlsTransportInterface * transport = GetHandle<webrtc::DtlsTransportInterface>(env, caller);
CHECK_HANDLE(transport);

transport->UnregisterObserver();

ClearNativeObserver<jni::RTCDtlsTransportObserver>(env, caller, "observerHandle");
UnregisterOwnObserver(env, caller, transport);
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_dispose
Expand All @@ -129,11 +148,7 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtlsTransport_dispose

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

ClearNativeObserver<jni::RTCDtlsTransportObserver>(env, caller, "observerHandle");
}
UnregisterOwnObserver(env, caller, transport);

// The connection, its senders and receivers, and other Java objects keep
// their own references, so this one need not be the last.
Expand Down
51 changes: 44 additions & 7 deletions webrtc-jni/src/main/cpp/src/JNI_RTCDtmfSender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,31 @@
*/

#include "JNI_RTCDtmfSender.h"
#include "api/RegisteredObservers.h"
#include "api/RTCDtmfSenderObserver.h"
#include "JavaString.h"
#include "JavaUtils.h"

#include "api/dtmf_sender_interface.h"

namespace
{
// Unregisters and frees the observer registered through the Java object.
// WebRTC is told only while that observer is still the registered one:
// another Java object for the same DTMF sender may have registered its
// own since, which stays.
void UnregisterOwnObserver(JNIEnv * env, jobject caller, webrtc::DtmfSenderInterface * sender)
{
auto observer = GetHandle<jni::RTCDtmfSenderObserver>(env, caller, "observerHandle");

if (jni::RegisteredObservers::Remove(sender, observer)) {
sender->UnregisterObserver();
}

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

JNIEXPORT jboolean JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_canInsertDtmf
(JNIEnv * env, jobject caller)
{
Expand Down Expand Up @@ -87,14 +106,17 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_registerObserver
webrtc::DtmfSenderInterface * sender = GetHandle<webrtc::DtmfSenderInterface>(env, caller);
CHECK_HANDLE(sender);

// Unregister and free a previously registered observer before replacing
// it, otherwise it would leak along with its JNI global reference.
sender->UnregisterObserver();

auto observer = new jni::RTCDtmfSenderObserver(env, jni::JavaGlobalRef<jobject>(env, jObserver));
ReplaceNativeObserver(env, caller, "observerHandle", observer);

// Registering replaces whatever observer the DTMF sender had, also one
// registered through another Java object. It runs on the signaling
// thread, which is also the thread that calls the observer, so the one
// registered through this object before is out of use once it returns.
jni::RegisteredObservers::Set(sender, observer);

sender->RegisterObserver(observer);

ReplaceNativeObserver(env, caller, "observerHandle", observer);
}
catch (...) {
ThrowCxxJavaException(env);
Expand All @@ -107,7 +129,22 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_unregisterObserver
webrtc::DtmfSenderInterface * sender = GetHandle<webrtc::DtmfSenderInterface>(env, caller);
CHECK_HANDLE(sender);

sender->UnregisterObserver();
UnregisterOwnObserver(env, caller, sender);
}

JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCDtmfSender_dispose
(JNIEnv * env, jobject caller)
{
webrtc::DtmfSenderInterface * sender = GetHandle<webrtc::DtmfSenderInterface>(env, caller);
CHECK_HANDLE(sender);

// WebRTC must let go of an observer registered through this object before
// it is deleted.
UnregisterOwnObserver(env, caller, sender);

// The RTCRtpSender and other Java objects keep their own references, so
// this one need not be the last.
sender->Release();

ClearNativeObserver<jni::RTCDtmfSenderObserver>(env, caller, "observerHandle");
SetHandle<std::nullptr_t>(env, caller, nullptr);
}
23 changes: 17 additions & 6 deletions webrtc-jni/src/main/cpp/src/JNI_RTCRtpReceiver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

#include "JNI_RTCRtpReceiver.h"
#include "api/RegisteredObservers.h"
#include "api/RTCRtpParameters.h"
#include "api/RTCRtpReceiverObserver.h"
#include "api/RTCRtpContributingSource.h"
Expand Down Expand Up @@ -204,10 +205,20 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_setObserver
observer = new jni::RTCRtpReceiverObserver(env, jni::JavaGlobalRef<jobject>(env, jObserver));
}

// Setting replaces whatever observer the receiver had, also one set
// through another Java object. Removing removes only the one set
// through this object, unless another has replaced it since.
if (observer != nullptr) {
jni::RegisteredObservers::Set(receiver, observer);

receiver->SetObserver(observer);
}
else if (jni::RegisteredObservers::Remove(receiver, GetHandle<jni::RTCRtpReceiverObserver>(env, caller, "observerHandle"))) {
receiver->SetObserver(nullptr);
}

// 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 (...) {
Expand All @@ -222,13 +233,13 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpReceiver_dispose
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) {
// deleted. One that another Java object has set since stays.
if (jni::RegisteredObservers::Remove(receiver, GetHandle<jni::RTCRtpReceiverObserver>(env, caller, "observerHandle"))) {
receiver->SetObserver(nullptr);

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

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
27 changes: 20 additions & 7 deletions webrtc-jni/src/main/cpp/src/JNI_RTCRtpSender.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include "JNI_RTCRtpSender.h"
#include "api/EncodedFrameInjectorObserver.h"
#include "api/RegisteredObservers.h"
#include "api/RTCRtpSenderObserver.h"
#include "api/RTCRtpSendParameters.h"
#include "api/WebRTCUtils.h"
Expand Down Expand Up @@ -146,7 +147,9 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_getDtmfSender

auto jDtmfSender = jni::RTCDtmfSender::toJava(env);

SetHandle(env, jDtmfSender, dtmfSender.get());
// The Java object holds a reference until it is disposed, since the
// sender frees its DTMF sender once the connection drops the sender.
SetHandle(env, jDtmfSender, dtmfSender.release());

return jDtmfSender.release();
}
Expand Down Expand Up @@ -320,10 +323,20 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_setObserver
observer = new jni::RTCRtpSenderObserver(env, jni::JavaGlobalRef<jobject>(env, jObserver));
}

// Setting replaces whatever observer the sender had, also one set
// through another Java object. Removing removes only the one set
// through this object, unless another has replaced it since.
if (observer != nullptr) {
jni::RegisteredObservers::Set(sender, observer);

sender->SetObserver(observer);
}
else if (jni::RegisteredObservers::Remove(sender, GetHandle<jni::RTCRtpSenderObserver>(env, caller, "observerHandle"))) {
sender->SetObserver(nullptr);
}

// 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.
sender->SetObserver(observer);

ReplaceNativeObserver(env, caller, "observerHandle", observer);
}
catch (...) {
Expand All @@ -338,13 +351,13 @@ JNIEXPORT void JNICALL Java_dev_onvoid_webrtc_RTCRtpSender_dispose
CHECK_HANDLE(sender);

// WebRTC must let go of an observer set through this object before it is
// deleted.
if (GetHandle<jni::RTCRtpSenderObserver>(env, caller, "observerHandle") != nullptr) {
// deleted. One that another Java object has set since stays.
if (jni::RegisteredObservers::Remove(sender, GetHandle<jni::RTCRtpSenderObserver>(env, caller, "observerHandle"))) {
sender->SetObserver(nullptr);

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

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

// Unlike e.g. MediaStreamTrack, an RTCRtpSender 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
Loading