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
6 changes: 4 additions & 2 deletions docs/guide/data/data-channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,15 @@ config.id = -1; // Let WebRTC assign an ID
// Configure sub-protocol (default: null)
config.protocol = "my-protocol"; // Optional sub-protocol name

// Configure priority (default: LOW)
config.priority = RTCPriorityType.LOW;
// Configure priority (default: LOW), announced to the remote peer
config.priority = RTCPriorityType.HIGH;

// Create the data channel with this configuration
RTCDataChannel dataChannel = peerConnection.createDataChannel("myChannel", config);
```

`getPriority()` returns a channel's priority; a channel the remote peer opened has the priority it announced. WebRTC only uses the priority to share the connection between channels when SCTP message interleaving is on, which needs the field trial `WebRTC-DataChannelMessageInterleaving/Enabled/` on both peers (see [Field Trials](/guide/advanced/field-trials)). Without it, channels take turns regardless of priority.

## Handling Data Channel Events

To receive events from a data channel, you need to implement the `RTCDataChannelObserver` interface and register it with the data channel:
Expand Down
48 changes: 48 additions & 0 deletions docs/guide/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,54 @@ signalingChannel.onIceCandidate(candidateMessage -> {
});
```

### Letting the Peer Connection Create the Description

`setLocalDescription` can also be called with only an observer. It then creates the offer or the answer, whichever the signaling state calls for, and applies it in one step, so nothing can change in between. That makes it the simpler way to implement "perfect negotiation". Read the result with `getLocalDescription()` once the observer reports success:

```java
peerConnection.setLocalDescription(new SetSessionDescriptionObserver() {
@Override
public void onSuccess() {
signalingChannel.send(peerConnection.getLocalDescription());
}

@Override
public void onFailure(String error) {
System.err.println("Failed to set local description: " + error);
}
});
```

### Adding Candidates and Knowing the Outcome

`addIceCandidate(candidate)` reports nothing and fails silently when the remote description is not set yet. The form that takes an `AddIceCandidateObserver` is queued behind any pending offer/answer operation, as the standard requires, so a candidate can be added right after `setRemoteDescription()` without waiting for it, and the observer says whether it was accepted:

```java
peerConnection.addIceCandidate(candidate, new AddIceCandidateObserver() {
@Override
public void onSuccess() {
}

@Override
public void onFailure(String error) {
// e.g. "[INVALID_STATE] The remote description was null"
System.err.println("Candidate rejected: " + error);
}
});
```

A local candidate can also go away, for example when its network interface does. The observer's `onIceCandidateRemoved(RTCIceCandidate)` reports it; send it to the remote peer, which passes it to `removeIceCandidate(candidate)`.

Once ICE has picked the candidate pair it uses, `onSelectedCandidatePairChanged(RTCCandidatePairChangeEvent)` reports both candidates of the pair and why it was chosen. It fires again whenever ICE switches pairs:

```java
@Override
public void onSelectedCandidatePairChanged(RTCCandidatePairChangeEvent event) {
System.out.println("Using " + event.getLocal().sdp + " <-> " + event.getRemote().sdp
+ " (" + event.getReason() + ")");
}
```

## Media Streams

### Accessing Media Devices
Expand Down
8 changes: 3 additions & 5 deletions docs/guide/media/directionality.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,10 @@ transceiver.setDirection(RTCRtpTransceiverDirection.INACTIVE);
transceiver.setDirection(RTCRtpTransceiverDirection.SEND_RECV);

// After changing directions, create a new offer and perform negotiation.
RTCOfferOptions opts = new RTCOfferOptions();
pc.createOffer(opts, new CreateSessionDescriptionObserver() {
pc.setLocalDescription(new SetSessionDescriptionObserver() {
@Override
public void onSuccess(RTCSessionDescription description) {
pc.setLocalDescription(description, /* observer */ null);
// Send to remote and await/set remote answer accordingly
public void onSuccess() {
// Send pc.getLocalDescription() to the remote peer and set its answer
}
@Override
public void onFailure(String error) { /* handle error */ }
Expand Down
18 changes: 17 additions & 1 deletion docs/guide/networking/peer-connection-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,33 @@ config.cryptoOptions = crypto;

`cryptexPolicy` encrypts the RTP header extensions and CSRCs as a whole (RFC 9335) where the peer supports it (`NEGOTIATE`) or always (`REQUIRE`).

`certificates` takes the DTLS certificate a peer connection authenticates with, as an `RTCCertificatePEM`; without one, WebRTC generates a certificate per connection. A certificate that does not parse makes `createPeerConnection()` and `setConfiguration()` throw.

## Signaling

| Setting | Effect |
|---|---|
| `offerExtmapAllowMixed` | Allows one- and two-byte header extensions to be mixed in offers. Default true. |
| `enableImplicitRollback` | Rolls a pending local offer back when a remote offer arrives, as perfect negotiation needs. Default false. |
| `enableImplicitRollback` | Rolls a pending local offer back when a remote offer arrives, as perfect negotiation needs. Default false. Pair it with `setLocalDescription(observer)`, which creates the offer or answer itself. |
| `alwaysNegotiateDataChannels` | Includes data channels in offers before any is created. Default false. |

## Changing a Running Connection

A few controls are methods of `RTCPeerConnection` rather than configuration fields, and can be used at any time.

`setBitrate(min, current, max)` limits the bandwidth all streams sent by the connection may use together, in bits per second. A `null` value leaves that limit as it is, and `current` resets the bandwidth estimate to that value. Limits from the SDP and from each sender's encoding parameters still apply. Values that are negative or not ordered as min ≤ current ≤ max make it throw.

```java
// Start from 1 Mbps and never use more than 2.5 Mbps.
peerConnection.setBitrate(null, 1_000_000, 2_500_000);
```

`setAudioPlayout(false)` stops the audio device from playing received audio; the audio is still decoded and its statistics still updated. `setAudioRecording(false)` stops the audio device from recording, so tracks fed by it send nothing, while tracks fed by a `CustomAudioSource` keep sending. Both are on by default and act on the audio device module, which all connections of a `PeerConnectionFactory` share.

## Related API

- `RTCConfiguration` — the configuration of a peer connection.
- `RTCPeerConnection.getConfiguration()`, `setConfiguration()` — read and change it.
- `RTCPeerConnection.setBitrate()`, `setAudioPlayout()`, `setAudioRecording()` — controls of a running connection.
- `RTCCryptoOptions` — the SRTP cipher suites.
- `PortAllocatorConfig` — ports and interfaces, see [Port Allocator Configuration](/guide/networking/port-allocator-config).
8 changes: 8 additions & 0 deletions webrtc-jni/src/main/cpp/include/JNI_RTCDataChannel.h

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

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

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

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

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

#include "api/rtc_error.h"

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

namespace jni
{
// Reports the outcome of PeerConnectionInterface::AddIceCandidate to a
// Java AddIceCandidateObserver, exactly once.
class AddIceCandidateObserver
{
public:
AddIceCandidateObserver(JNIEnv * env, const JavaGlobalRef<jobject> & observer);
~AddIceCandidateObserver();

AddIceCandidateObserver(const AddIceCandidateObserver &) = delete;
AddIceCandidateObserver & operator=(const AddIceCandidateObserver &) = delete;

void OnComplete(webrtc::RTCError error) noexcept;

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

jmethodID onSuccess;
jmethodID onFailure;
};

void Notify(const char * error) noexcept;

JavaGlobalRef<jobject> observer;

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

#endif
6 changes: 6 additions & 0 deletions webrtc-jni/src/main/cpp/include/api/PeerConnectionObserver.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ namespace jni
void OnIceGatheringChange(webrtc::PeerConnectionInterface::IceGatheringState state) override;
void OnIceCandidate(const webrtc::IceCandidateInterface * candidate) override;
void OnIceCandidateError(const std::string & address, int port, const std::string & url, int error_code, const std::string & error_text) override;
void OnIceCandidateRemoved(const webrtc::IceCandidate * candidate) override;
void OnIceConnectionReceivingChange(bool receiving) override;
void OnIceSelectedCandidatePairChanged(const webrtc::CandidatePairChangeEvent & event) override;

Expand All @@ -65,11 +66,16 @@ namespace jni
jmethodID onIceGatheringChange;
jmethodID onIceCandidate;
jmethodID onIceCandidateError;
jmethodID onIceCandidateRemoved;
jmethodID onIceConnectionReceivingChange;
jmethodID onSelectedCandidatePairChanged;
};

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

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

#include "p2p/base/port.h"

#include <jni.h>

namespace jni
{
namespace RTCCandidatePairChangeEvent
{
class JavaRTCCandidatePairChangeEventClass : public JavaClass
{
public:
explicit JavaRTCCandidatePairChangeEventClass(JNIEnv * env);

jclass cls;
jmethodID ctor;
};

JavaLocalRef<jobject> toJava(JNIEnv * env, const webrtc::CandidatePairChangeEvent & event);
};
}

#endif
6 changes: 6 additions & 0 deletions webrtc-jni/src/main/cpp/include/api/WebRTCUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ namespace jni
}

std::string RTCErrorToString(const webrtc::RTCError & error);

// Hands an exception thrown by a Java callback to the calling thread's
// 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);
}

#endif
11 changes: 10 additions & 1 deletion webrtc-jni/src/main/cpp/src/JNI_PeerConnectionFactory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,16 @@ JNIEXPORT jobject JNICALL Java_dev_onvoid_webrtc_PeerConnectionFactory_createPee
webrtc::PeerConnectionFactoryInterface * factory = GetHandle<webrtc::PeerConnectionFactoryInterface>(env, caller);
CHECK_HANDLEV(factory, nullptr);

webrtc::PeerConnectionInterface::RTCConfiguration configuration = jni::RTCConfiguration::toNative(env, jni::JavaLocalRef<jobject>(env, jConfig));
webrtc::PeerConnectionInterface::RTCConfiguration configuration;

try {
configuration = jni::RTCConfiguration::toNative(env, jni::JavaLocalRef<jobject>(env, jConfig));
}
catch (...) {
ThrowCxxJavaException(env);
return nullptr;
}

webrtc::PeerConnectionObserver * observer = new jni::PeerConnectionObserver(env, jni::JavaGlobalRef<jobject>(env, jobserver));
webrtc::PeerConnectionDependencies dependencies(observer);

Expand Down
Loading
Loading