Skip to content

Conversation

@steveseguin
Copy link

@steveseguin steveseguin commented Jan 15, 2026

Description

Implements pre-offer ICE gathering via HTTP OPTIONS request and trickle ICE for faster WHIP connection times, as specified in the WHIP specification section 4.4.

Motivation and Context

The WHIP specification (section 4.4) describes how endpoints can provide ICE server configurations via Link headers in response to OPTIONS requests. This allows the client to gather ICE candidates before creating the SDP offer, enabling proper NAT traversal for P2P connections.

Additionally, trickle ICE support allows candidates to be sent asynchronously after the initial offer, reducing connection latency.

Changes

  • Add FetchIceServersViaOptions() method to query ICE servers before offer creation
  • Parse Link headers for STUN/TURN server URIs and credentials
  • Implement trickle ICE with async candidate sending via PATCH requests
  • Wait up to 150ms for initial candidates before sending offer
  • Maintain backwards compatibility: falls back gracefully if OPTIONS or trickle ICE is unsupported
  • No behavior change for endpoints that don't provide STUN/TURN servers

How Has This Been Tested?

  • Tested with WHIP endpoints that provide ICE servers via Link headers (vdo.ninja, rtc.ninja)
  • Tested with endpoints that don't support OPTIONS (graceful fallback)
  • Verified P2P connections work when STUN servers are provided
  • Verified trickle ICE reduces connection times
  • Built and tested on Linux and Windows

Types of Changes

  • New feature (non-breaking change which adds functionality)

Checklist

  • My code has been run through clang-format
  • I have read the contributing document
  • My code is not on the master branch
  • The code has been tested
  • All commit messages are properly formatted and commits squashed where appropriate
  • I have included updates to all appropriate documentation

@steveseguin steveseguin force-pushed the master branch 6 times, most recently from c9721be to 395db23 Compare January 15, 2026 04:03
@belthesar
Copy link

Hi there! I was able to test this change, and was able to successfully negotiate a WHIP stream across NAT.

Testing methodology:

  • OBS Studio 32.0.4 on CachyOS
  • swap obs-webrtc.so from my installed version from the PR CI build (CI builds for Linux are unpackaged, but also not built with the portable flag)
  • Published stream to https://whip.vdo.ninja with Bearer Token: belthesar
  • Viewer behind NAT in another location viewed the stream by accessing https://vdo.ninja?whip=belthesar

No issues noted.

@Sean-Der
Copy link
Contributor

Hey @steveseguin makes my day to see you still using this/improving it :D

What do you think of doing this via Trickle ICE instead? #12863 (comment)

Using OPTIONS (and signaling those candidates via Offer) will mean longer connect times.

@steveseguin
Copy link
Author

steveseguin commented Jan 17, 2026

Hey @Sean-Der, I'm open to anything that works.

In my last patch, I hard-coded the STUN server into the plugin. Fast enough, but limited to VDO.Ninja's domain, for user privacy concerns and to avoid UI changes.

Waiting 1 to 5 seconds is more than enough time to get candidates, and bundling them into the offer makes the workflow very simple and pretty universal.

VDO.Ninja specific code in OBS I figured wouldn't be approved, so I switched the patch over to doing OPTIONS to be agnostic. It still follows spec, and it only spends time to do ICE if the options contains STUN/TURN servers. For SFU servers that don't need that, speed is minimally impacted I'd say -- 100ms perhaps. If OPTIONS fails, things continue gracefully.

I originally, last year, tried doing ICE Trickle, so having the peer send ICE candidates async, but the version of libdatachannel used in OBS wasn't handling it. I don't know if that was my own ineptness, or if the version was just too old to support it. I considered updating libdatachannel to a newer version, but my PR was already bloated.

In this case, I didn't bother to check if libdatachannel in OBS could now support trickling. I instead just went with the simplest approach that users using VDO.Ninja for the last 11 months or so have been happy with. Reconnection speeds are a bit slow, but most of that isn't due to ICE, but rather OBS taking time to realize it lost its connection.

If I missed there already being another method implemented that solves this, apologies, I can switch to using that. I frequently get users asking me about the status of this issue though, and maintaining a separate build of OBS for them is no one's preference.

Perhaps we could run with this for now, and edit it to do trickle (vs waiting) in a future update?

Hope you've been well.

@Sean-Der
Copy link
Contributor

Sean-Der commented Jan 19, 2026

@steveseguin It looks to be possible with onLocalCandidate now!

Tell me if you run into anything. I am also happy to take over the PR if you run into any issues :)

The 'blocking for candidates` prevents us from doing cool stuff like ICE Renomination/Roaming in the future. You also have the subjectivity of 'How long do you want for STUN/TURN to be gathered'. Don't have any of that with Trickle.

@Sean-Der
Copy link
Contributor

I completely forgot! This is already implemented #12876

Mind trying that PR and see if it works for you? I don't know what release it will go in, but I think long term that gets us the best WHIP experience possible.

@steveseguin steveseguin force-pushed the master branch 2 times, most recently from e3816c9 to 03fcd10 Compare January 20, 2026 06:10
@steveseguin
Copy link
Author

steveseguin commented Jan 20, 2026

Thank you, @Sean-Der. I appreciate the reply and care; I can only imagine how busy you are.

I've tested the PR you pointed out, and it does work! However, it's a bit of a regression in terms of speed.

Specifically, #12876 gathers candidates, but waits for gathering to complete before sending the offer with all candidates bundled. In my tests, that meant about 25 seconds
before connection:

image

I’ve added some async trickle ICE in my PR #13021 to offer the best of both worlds:

  1. Waits up to 150ms to include candidates in the offer if a STUN server is provided. This increases WHIP server compatibility. The wait is cut short if a candidate arrives sooner.
  2. Trickles candidates, one at a time, as they are gathered.
  3. Includes a final message to indicate when candidate gathering has completed, if a STUN server was provided.

Example as follows:
image

So this update waits up to 150ms instead of waiting for full ICE gathering, sends candidates as they trickle in without delay, and doesn’t change behavior for WHIP connections that don’t advertise STUN/TURN servers. It remains robust enough for simple WHIP peers to still likely work, while not slowing things down if ICE was actually required.

Implements pre-offer ICE gathering via HTTP OPTIONS request and
trickle ICE for faster WHIP connection times.

Changes:
- Add FetchIceServersViaOptions() to query ICE servers before offer
- Parse Link headers for STUN/TURN URIs and credentials
- Implement trickle ICE with async candidate sending
- Wait up to 150ms for initial candidates before sending offer
- Maintain backwards compatibility when OPTIONS/trickle unavailable
@steveseguin steveseguin changed the title obs-webrtc: Add pre-offer ICE gathering via OPTIONS obs-webrtc: Add trickle ICE support for WHIP Jan 20, 2026
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