Skip to content

Expose RtpReceiver::SetJitterBufferMinimumDelay on Obj-C and Java - #235

Open
doracawl wants to merge 5 commits into
webrtc-sdk:m144_releasefrom
doracawl:feature/expose-set-jitter-buffer-minimum-delay
Open

Expose RtpReceiver::SetJitterBufferMinimumDelay on Obj-C and Java#235
doracawl wants to merge 5 commits into
webrtc-sdk:m144_releasefrom
doracawl:feature/expose-set-jitter-buffer-minimum-delay

Conversation

@doracawl

@doracawl doracawl commented May 4, 2026

Copy link
Copy Markdown

Summary

The C++ RtpReceiverInterface::SetJitterBufferMinimumDelay has been part of the WebRTC public API for a long time (it backs the W3C RTCRtpReceiver.jitterBufferTarget attribute), but neither the Obj-C nor Java wrappers in this fork expose it. Apps building on the prebuilt SDKs therefore have no way to control receive-side jitter buffer minimum delay — i.e. trade latency for smoothness on weak networks — without dropping out to a custom build.

This PR adds:

  • Obj-C: -[RTCRtpReceiver setJitterBufferMinimumDelay:] taking a nullable NSNumber *, where nil restores default behaviour.
  • Java: RtpReceiver.setJitterBufferMinimumDelay(@Nullable Double), where null restores default behaviour.

Both forward straight to the existing C++ entry point with std::optional<double>, mirroring how the browser exposes the property. The public setter rejects non-finite input; the native boundaries (JNI / Obj-C) defensively treat a non-finite value as "clear" — see Design notes.

Files changed

  • sdk/objc/api/peerconnection/RTCRtpReceiver.h — protocol declaration
  • sdk/objc/api/peerconnection/RTCRtpReceiver.mm — implementation
  • sdk/android/api/org/webrtc/RtpReceiver.java — public Java API + native decls
  • sdk/android/src/jni/pc/rtp_receiver.cc — JNI bindings

Additive only; no behavioural change to existing APIs. The C++ symbol is unchanged and the change is binary-compatible with the current ABI.

Why this matters downstream

We are building a multi-tenant IoT/video-surveillance mobile app on the prebuilt SDKs from this fork (m144). On weak networks our users see frequent video stutter that a 200–400 ms minimum jitter buffer cleanly absorbs. The C++ method is exactly what we need; without this binding we would have to carry a private SDK build forever. Exposing it once here removes that burden.

Design notes

  1. Why a nullable Double on Java? It matches this repo's existing convention for optional numeric setters (EncodedImage.setQp(@Nullable Integer), the @Nullable Integer parameters on VideoSource), keeps the Java surface symmetric with the Obj-C nullable NSNumber *, and maps directly onto the C++ std::optional<double> (null/nilstd::nullopt).
  2. Why a method on Obj-C, not a property? The underlying C++ has no getter — a property would lie about being readable.
  3. Why std::optional? m144 already uses std::optional throughout api/rtp_receiver_interface.h, so we match the existing surface.
  4. Non-finite input. A NaN reaches a fatal RTC_CHECK in JitterBufferDelay::GetMs (saturated_cast<int> of delay * 1000). The Java setter rejects NaN/Inf with IllegalArgumentException; the JNI and Obj-C boundaries treat non-finite as std::nullopt so a caller reaching native directly cannot crash the engine. Validation uses Double.isNaN/isInfinite rather than the API 24+ Double.isFinite, matching this module's min_sdk_version of 23.

Testing

  • All four files compile against the m144 source tree.
  • Verified the symbol naming on the JNI side matches the Java native method names (the auto-generated RtpReceiver_jni.h derives JNI_RtpReceiver_<MethodName> from the Java nativeXxx declarations).
  • We will be running this against real iOS arm64 devices and Android arm64 devices in our app once a tagged build is available; happy to follow up with stat numbers from RTCInboundRtpStreamStats.jitterBufferDelay if useful.

doracawl added 2 commits May 4, 2026 12:32
The C++ RtpReceiverInterface has supported SetJitterBufferMinimumDelay
since long ago (it backs the W3C jitterBufferTarget attribute), but
neither the Java nor the Objective-C API surfaced it. Apps building on
the prebuilt SDKs therefore had no way to trade latency for smoothness
on weak networks without dropping out to a custom build.

This change adds:

  * Java: RtpReceiver#setJitterBufferMinimumDelay(double) and
    RtpReceiver#clearJitterBufferMinimumDelay(), with matching
    nativeSetJitterBufferMinimumDelay / nativeClearJitterBufferMinimumDelay
    JNI wiring in sdk/android/src/jni/pc/rtp_receiver.cc.
  * ObjC: -[RTCRtpReceiver setJitterBufferMinimumDelay:] taking a
    nullable NSNumber*, where nil restores default behaviour.

Both layers forward straight to the existing C++ entry point with
std::optional<double>, mirroring how the browser exposes the property.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR exposes the existing C++ RtpReceiverInterface::SetJitterBufferMinimumDelay(std::optional<double>) API to mobile wrapper layers so iOS (Obj-C) and Android (Java) apps using the prebuilt SDKs can tune receive-side jitter buffering without a custom build.

Changes:

  • Obj-C: add -[RTCRtpReceiver setJitterBufferMinimumDelay:] accepting nullable NSNumber* (nil clears to default).
  • Java: add RtpReceiver.setJitterBufferMinimumDelay(double) and RtpReceiver.clearJitterBufferMinimumDelay().
  • JNI: add bindings that forward the Java calls into RtpReceiverInterface::SetJitterBufferMinimumDelay.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
sdk/objc/api/peerconnection/RTCRtpReceiver.h Adds Obj-C API surface for setting/clearing the receiver jitter buffer minimum delay.
sdk/objc/api/peerconnection/RTCRtpReceiver.mm Implements the Obj-C method by forwarding into the native RtpReceiverInterface.
sdk/android/api/org/webrtc/RtpReceiver.java Adds public Java methods and corresponding native declarations.
sdk/android/src/jni/pc/rtp_receiver.cc Implements JNI entry points that call the native C++ receiver API.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +104 to +111
- (void)setJitterBufferMinimumDelay:(nullable NSNumber *)delaySeconds {
if (delaySeconds == nil) {
_nativeRtpReceiver->SetJitterBufferMinimumDelay(std::nullopt);
} else {
_nativeRtpReceiver->SetJitterBufferMinimumDelay(
std::optional<double>(delaySeconds.doubleValue));
}
}
Comment on lines +145 to +151
static void JNI_RtpReceiver_SetJitterBufferMinimumDelay(
JNIEnv* jni,
jlong j_rtp_receiver_pointer,
jdouble delay_seconds) {
reinterpret_cast<RtpReceiverInterface*>(j_rtp_receiver_pointer)
->SetJitterBufferMinimumDelay(std::optional<double>(delay_seconds));
}
Comment on lines +96 to +99
public void setJitterBufferMinimumDelay(double delaySeconds) {
checkRtpReceiverExists();
nativeSetJitterBufferMinimumDelay(nativeRtpReceiver, delaySeconds);
}

@hiroshihorie hiroshihorie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution! The implementation looks great, both sides forward the optional exactly like the C++ API expects and the Java setter/clear split keeps the hot path allocation free, nice touch. The guard and naming also match the existing patterns in these wrappers.

I have one small suggestion about the docs, see inline. It is about units, the only thing I would really want fixed before merge.

Comment on lines +90 to +95
/**
* Sets the minimum jitter buffer delay (in seconds) for this receiver.
* The receiver keeps at least this much media buffered before playout,
* trading latency for smoothness on weak networks. Wraps the C++
* RtpReceiverInterface::SetJitterBufferMinimumDelay.
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One small thing on the docs. jitterBufferTarget on the browser side is in milliseconds while this method takes seconds, and I can easily see someone porting browser code getting bitten by a 1000x mistake. A concrete example in the doc should prevent that, and the internal clamp is worth mentioning too since the native side silently caps at 10 seconds.

Suggested change
/**
* Sets the minimum jitter buffer delay (in seconds) for this receiver.
* The receiver keeps at least this much media buffered before playout,
* trading latency for smoothness on weak networks. Wraps the C++
* RtpReceiverInterface::SetJitterBufferMinimumDelay.
*/
/**
* Sets the minimum jitter buffer delay in seconds for this receiver.
* The receiver keeps at least this much media buffered before playout,
* trading latency for smoothness on weak networks. Note the unit: the
* equivalent browser attribute, RTCRtpReceiver.jitterBufferTarget, is
* expressed in milliseconds, so a browser value of 500 corresponds to
* 0.5 here. Values are clamped to [0, 10] seconds internally. Wraps the
* C++ RtpReceiverInterface::SetJitterBufferMinimumDelay.
*/

Comment on lines +96 to +101
/** Sets the minimum jitter buffer delay (in seconds) for this receiver.
* Pass nil to restore the default behaviour. The receiver keeps at least
* this much media buffered before playout, trading latency for smoothness
* on weak networks. Mirrors RTCRtpReceiver.jitterBufferTarget in browser
* WebRTC; wraps the C++ RtpReceiverInterface::SetJitterBufferMinimumDelay.
*/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same units note for the ObjC side. Mentioning jitterBufferTarget is great for discoverability, just worth being explicit that the units differ.

Suggested change
/** Sets the minimum jitter buffer delay (in seconds) for this receiver.
* Pass nil to restore the default behaviour. The receiver keeps at least
* this much media buffered before playout, trading latency for smoothness
* on weak networks. Mirrors RTCRtpReceiver.jitterBufferTarget in browser
* WebRTC; wraps the C++ RtpReceiverInterface::SetJitterBufferMinimumDelay.
*/
/** Sets the minimum jitter buffer delay in seconds for this receiver.
* Pass nil to restore the default behaviour. The receiver keeps at least
* this much media buffered before playout, trading latency for smoothness
* on weak networks. Note the unit: the equivalent browser attribute,
* RTCRtpReceiver.jitterBufferTarget, is expressed in milliseconds, so a
* browser value of 500 corresponds to 0.5 here. Values are clamped to
* [0, 10] seconds internally. Wraps the C++
* RtpReceiverInterface::SetJitterBufferMinimumDelay.
*/

@doracawl

Copy link
Copy Markdown
Author

Thanks for the review and the units catch — the ms-vs-seconds gap against jitterBufferTarget is an easy trap, so I've folded your wording (including the [0, 10] s clamp note) into the doc comments on both sides.

While there, I aligned the Java surface with this repo's own convention for optional numeric setters — EncodedImage.setQp(@Nullable Integer) and the @Nullable Integer parameters on VideoSource — by collapsing the previous set(double) + clear() pair into a single setJitterBufferMinimumDelay(@Nullable Double) where null restores the default. That also keeps it symmetric with the Obj-C nullable NSNumber *.

I also adopted the Copilot note on non-finite input — it's a genuine crash path (saturated_cast<int> in JitterBufferDelay::GetMs fatally RTC_CHECKs on NaN). The Java setter now rejects NaN/Inf with IllegalArgumentException, and the JNI / Obj-C boundaries treat non-finite as nullopt for callers reaching native directly. The validation deliberately uses Double.isNaN/isInfinite rather than Double.isFinite, since the latter is API 24+ and this module's min_sdk_version is 23. PR description updated to match — happy to keep the explicit clear() instead if you'd prefer.

@hiroshihorie hiroshihorie left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the latest revision. The units docs are applied on both sides and match the actual behavior in pc/jitter_buffer_delay.cc, including the [0, 10] s clamp. The NaN guard is correct. saturated_cast hits RTC_CHECK_NOTREACHED() on NaN, and the layering (Java throws, JNI and Obj-C map non-finite to nullopt) is sensible. Threading is safe since SetJitterBufferMinimumDelay is proxied to the worker thread, and the JNI naming matches the generated header convention.

One optional nit, not blocking: Java throws on non-finite input while Obj-C silently resets to default, and the Obj-C header doc does not mention that. A one-line addition like "non-finite values are treated as nil" would make it fully self-documenting. Fine to fix in a follow-up or leave as is.

Thanks for addressing the feedback.

@doracawl

doracawl commented Jul 8, 2026

Copy link
Copy Markdown
Author

Added the one-liner to the Obj-C header doc — non-finite values are now documented as being treated as nil (88c4bdb). Thanks for the review!

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.

3 participants