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
18 changes: 18 additions & 0 deletions .changeset/enable_warp_by_default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
libwebrtc: minor
livekit: minor
webrtc-sys: patch
livekit-ffi: patch
---

feat: enable WARP (SPED + SNAP) by default, gated by the server

WARP is now always enabled on the client and negotiated with the SFU: SPED
(DTLS-in-STUN) via the `WebRTC-IceHandshakeDtls` field trial, and SNAP
(SCTP-INIT-in-SDP) via the `RtcConfiguration.enable_sctp_snap` field. When the
server does not enable WARP it is not advertised and the connection falls back to
plain DTLS/SCTP, so there is no client-side toggle.

BREAKING CHANGE: `libwebrtc::RtcConfiguration` is now `#[non_exhaustive]` and has a
new `enable_sctp_snap` field. Construct it from `RtcConfiguration::default()` and set
the fields you need instead of a struct literal.
1 change: 1 addition & 0 deletions libwebrtc/src/native/peer_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ impl From<RtcConfiguration> for sys_pc::ffi::RtcConfiguration {
ice_servers: value.ice_servers.into_iter().map(Into::into).collect(),
continual_gathering_policy: value.continual_gathering_policy.into(),
ice_transport_type: value.ice_transport_type.into(),
enable_sctp_snap: value.enable_sctp_snap,
}
}
}
Expand Down
14 changes: 14 additions & 0 deletions libwebrtc/src/native/peer_connection_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ impl PeerConnectionFactory {
Self { sys_handle }
}

/// Creates a [`PeerConnectionFactory`] with the given runtime options.
/// `zero_playout_delay` enables the WebRTC-ForcePlayoutDelay field trial;
/// `enable_warp` enables WARP (SPED via the WebRTC-IceHandshakeDtls field
/// trial; SNAP is carried on the RtcConfiguration, not a field trial). The
/// two are independent and may be combined.
pub fn with_options(zero_playout_delay: bool, enable_warp: bool) -> Self {
ensure_log_sink();
let sys_handle = sys_pcf::ffi::create_peer_connection_factory_with_options(
zero_playout_delay,
enable_warp,
);
Self { sys_handle }
}

#[cfg(test)]
pub(crate) fn zero_playout_delay_enabled(&self) -> bool {
self.sys_handle.zero_playout_delay_enabled()
Expand Down
1 change: 1 addition & 0 deletions libwebrtc/src/peer_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ mod tests {
}],
continual_gathering_policy: ContinualGatheringPolicy::GatherOnce,
ice_transport_type: IceTransportsType::All,
enable_sctp_snap: false,
};

let bob = factory.create_peer_connection(config.clone()).unwrap();
Expand Down
26 changes: 26 additions & 0 deletions libwebrtc/src/peer_connection_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,26 @@ pub enum IceTransportsType {
All,
}

/// Configuration for a [`PeerConnection`].
///
/// This type is `#[non_exhaustive]`: construct it from [`RtcConfiguration::default`]
/// and set the fields you need, e.g.
/// ```
/// # use libwebrtc::peer_connection_factory::{IceTransportsType, RtcConfiguration};
/// let mut cfg = RtcConfiguration::default();
/// cfg.ice_transport_type = IceTransportsType::Relay;
/// ```
/// New fields may be added in future releases without a breaking change.
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct RtcConfiguration {
pub ice_servers: Vec<IceServer>,
pub continual_gathering_policy: ContinualGatheringPolicy,
pub ice_transport_type: IceTransportsType,
/// WARP: enable SNAP (SCTP-INIT-in-SDP). Maps to the immutable
/// `enable_sctp_snap` RTCConfiguration field, so it must be set the same at
/// PeerConnection creation and every set_configuration.
pub enable_sctp_snap: bool,
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
}

impl Default for RtcConfiguration {
Expand All @@ -52,6 +67,7 @@ impl Default for RtcConfiguration {
ice_servers: vec![],
continual_gathering_policy: ContinualGatheringPolicy::GatherContinually,
ice_transport_type: IceTransportsType::All,
enable_sctp_snap: false,
}
}
}
Expand All @@ -74,6 +90,16 @@ impl PeerConnectionFactory {
Self { handle: imp_pcf::PeerConnectionFactory::with_zero_playout_delay() }
}

/// Creates a native peer connection factory with the given runtime options.
/// `zero_playout_delay` and `enable_warp` (SPED + SNAP) are independent and
/// may be combined.
#[cfg(not(target_arch = "wasm32"))]
pub fn with_options(zero_playout_delay: bool, enable_warp: bool) -> Self {
Self {
handle: imp_pcf::PeerConnectionFactory::with_options(zero_playout_delay, enable_warp),
}
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.

pub fn create_peer_connection(
&self,
config: RtcConfiguration,
Expand Down
23 changes: 12 additions & 11 deletions livekit-ffi/src/conversion/room.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,19 +237,20 @@ impl From<proto::IceServer> for IceServer {

impl From<proto::RtcConfig> for RtcConfiguration {
fn from(value: proto::RtcConfig) -> Self {
let default = RoomOptions::default().rtc_config; // Always use RoomOptions as the default reference
// Start from RoomOptions defaults; RtcConfiguration is #[non_exhaustive]
// so it must be built from Default rather than a struct literal.
let mut config = RoomOptions::default().rtc_config;

Self {
ice_transport_type: value.ice_transport_type.map_or(default.ice_transport_type, |x| {
config.ice_transport_type =
value.ice_transport_type.map_or(config.ice_transport_type, |x| {
proto::IceTransportType::try_from(x).unwrap().into()
}),
continual_gathering_policy: value
.continual_gathering_policy
.map_or(default.continual_gathering_policy, |x| {
proto::ContinualGatheringPolicy::try_from(x).unwrap().into()
}),
ice_servers: value.ice_servers.into_iter().map(Into::into).collect(),
}
});
config.continual_gathering_policy =
value.continual_gathering_policy.map_or(config.continual_gathering_policy, |x| {
proto::ContinualGatheringPolicy::try_from(x).unwrap().into()
});
config.ice_servers = value.ice_servers.into_iter().map(Into::into).collect();
config
}
}

Expand Down
14 changes: 3 additions & 11 deletions livekit/src/room/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ use bmrng::unbounded::UnboundedRequestReceiver;
use futures_util::StreamExt;
use libwebrtc::{
native::frame_cryptor::EncryptionState,
prelude::{
ContinualGatheringPolicy, IceTransportsType, MediaStream, MediaStreamTrack,
RtcConfiguration,
},
prelude::{MediaStream, MediaStreamTrack, RtcConfiguration},
rtp_transceiver::RtpTransceiver,
RtcError,
};
Expand Down Expand Up @@ -467,13 +464,8 @@ impl Default for RoomOptions {
e2ee: None,
encryption: None,

// Explicitly set the default values
rtc_config: RtcConfiguration {
ice_servers: vec![], /* When empty, this will automatically be filled by the
* JoinResponse */
continual_gathering_policy: ContinualGatheringPolicy::GatherContinually,
ice_transport_type: IceTransportsType::All,
},
// Defaults; ice_servers is empty here and filled from the JoinResponse.
rtc_config: RtcConfiguration::default(),
join_retries: 3,
sdk_options: RoomSdkOptions::default(),
single_peer_connection: true,
Expand Down
11 changes: 6 additions & 5 deletions livekit/src/rtc_engine/lk_runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,13 @@ impl LkRuntime {
} else {
log::debug!("LkRuntime::new()");
let zero_playout_delay = state.zero_playout_delay;
// WARP (SPED + SNAP) is always enabled. SPED is the factory field
// trial enabled here; SNAP is carried on the RtcConfiguration (set
// in RtcSession). zero_playout_delay is independent and composed
// alongside WARP. Whether WARP actually engages is negotiated with
// the server — it degrades to plain DTLS/SCTP if the peer opts out.
#[cfg(not(target_arch = "wasm32"))]
let pc_factory = if zero_playout_delay {
PeerConnectionFactory::with_zero_playout_delay()
} else {
PeerConnectionFactory::default()
};
let pc_factory = PeerConnectionFactory::with_options(zero_playout_delay, true);
#[cfg(target_arch = "wasm32")]
let pc_factory = PeerConnectionFactory::default();
let new_runtime = Arc::new(Self { pc_factory, zero_playout_delay });
Expand Down
8 changes: 3 additions & 5 deletions livekit/src/rtc_engine/peer_transport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,9 @@ mod tests {
use livekit_protocol as proto;

let factory = PeerConnectionFactory::default();
let config = RtcConfiguration {
ice_servers: vec![],
continual_gathering_policy: ContinualGatheringPolicy::GatherOnce,
ice_transport_type: IceTransportsType::All,
};
let mut config = RtcConfiguration::default();
config.continual_gathering_policy = ContinualGatheringPolicy::GatherOnce;
config.enable_sctp_snap = true;

let alice_pc = factory.create_peer_connection(config.clone()).unwrap();
let bob_pc = factory.create_peer_connection(config).unwrap();
Expand Down
4 changes: 4 additions & 0 deletions livekit/src/rtc_engine/rtc_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,10 @@ impl RtcSession {
let (emitter, session_events) = mpsc::unbounded_channel();

let lk_runtime = LkRuntime::instance();

let mut options = options;
options.rtc_config.enable_sctp_snap = true;

let use_single_pc = options.signal_options.single_peer_connection;

let mut publisher_offer = None;
Expand Down
6 changes: 6 additions & 0 deletions webrtc-sys/include/livekit/peer_connection_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class PeerConnectionFactory {
explicit PeerConnectionFactory(std::shared_ptr<RtcRuntime> rtc_runtime);
PeerConnectionFactory(std::shared_ptr<RtcRuntime> rtc_runtime,
bool zero_playout_delay);
PeerConnectionFactory(std::shared_ptr<RtcRuntime> rtc_runtime,
bool zero_playout_delay,
bool enable_warp);
~PeerConnectionFactory();

std::shared_ptr<PeerConnection> create_peer_connection(
Expand Down Expand Up @@ -85,4 +88,7 @@ class PeerConnectionFactory {
std::shared_ptr<PeerConnectionFactory> create_peer_connection_factory();
std::shared_ptr<PeerConnectionFactory>
create_peer_connection_factory_with_zero_playout_delay();
std::shared_ptr<PeerConnectionFactory>
create_peer_connection_factory_with_options(bool zero_playout_delay,
bool enable_warp);
} // namespace livekit_ffi
2 changes: 2 additions & 0 deletions webrtc-sys/src/peer_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ webrtc::PeerConnectionInterface::RTCConfiguration to_native_rtc_configuration(
static_cast<webrtc::PeerConnectionInterface::IceTransportsType>(
config.ice_transport_type);

rtc_config.enable_sctp_snap = config.enable_sctp_snap;

return rtc_config;
}

Expand Down
3 changes: 3 additions & 0 deletions webrtc-sys/src/peer_connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ pub mod ffi {
pub ice_servers: Vec<IceServer>,
pub continual_gathering_policy: ContinualGatheringPolicy,
pub ice_transport_type: IceTransportsType,
// WARP/SNAP: enable SCTP-INIT-in-SDP. Must be carried consistently across
// create + set_configuration (it is an immutable RTCConfiguration field).
pub enable_sctp_snap: bool,
}

extern "C++" {
Expand Down
98 changes: 92 additions & 6 deletions webrtc-sys/src/peer_connection_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <memory>
#include <utility>
#include <vector>

#include "api/audio_codecs/builtin_audio_decoder_factory.h"
#include "api/audio_codecs/builtin_audio_encoder_factory.h"
Expand Down Expand Up @@ -64,12 +65,77 @@ class ZeroPlayoutDelayFieldTrials final : public webrtc::FieldTrialsView {
}
};

webrtc::Environment CreateEnvironment(bool zero_playout_delay) {
// Enables SPED (DTLS-in-STUN) via the WebRTC-IceHandshakeDtls field trial.
// SNAP (SCTP-INIT-in-SDP) is intentionally NOT set here: it maps to the
// immutable enable_sctp_snap RTCConfiguration field, so it must be carried on
// the RtcConfiguration (see RtcConfiguration.enable_sctp_snap) to stay
// consistent across create + set_configuration. Enabling it via a field trial
// makes set_configuration fail ("Modifying the configuration in an unsupported
// way").
class EnableWarpFieldTrials final : public webrtc::FieldTrialsView {
public:
std::string Lookup(absl::string_view key) const override {
if (key == "WebRTC-IceHandshakeDtls") {
return "Enabled";
}
return "";
}

std::unique_ptr<webrtc::FieldTrialsView> CreateCopy() const override {
return std::make_unique<EnableWarpFieldTrials>();
}
};

// An Environment accepts a single FieldTrialsView, so to enable several
// independent trial groups (e.g. zero-playout-delay AND WARP) we combine their
// views into one: Lookup delegates to each sub-view and returns the first
// non-empty result. The groups' keys are disjoint, so ordering is irrelevant.
class CompositeFieldTrials final : public webrtc::FieldTrialsView {
public:
explicit CompositeFieldTrials(
std::vector<std::unique_ptr<webrtc::FieldTrialsView>> views)
: views_(std::move(views)) {}

std::string Lookup(absl::string_view key) const override {
for (const auto& view : views_) {
std::string value = view->Lookup(key);
if (!value.empty()) {
return value;
}
}
return "";
}

std::unique_ptr<webrtc::FieldTrialsView> CreateCopy() const override {
std::vector<std::unique_ptr<webrtc::FieldTrialsView>> copies;
copies.reserve(views_.size());
for (const auto& view : views_) {
copies.push_back(view->CreateCopy());
}
return std::make_unique<CompositeFieldTrials>(std::move(copies));
}

private:
std::vector<std::unique_ptr<webrtc::FieldTrialsView>> views_;
};

// zero_playout_delay and enable_warp are independent and may both be enabled;
// their field-trial views are composed into one.
webrtc::Environment CreateEnvironment(bool zero_playout_delay,
bool enable_warp) {
std::vector<std::unique_ptr<webrtc::FieldTrialsView>> views;
if (zero_playout_delay) {
return webrtc::CreateEnvironment(
std::make_unique<ZeroPlayoutDelayFieldTrials>());
views.push_back(std::make_unique<ZeroPlayoutDelayFieldTrials>());
}
if (enable_warp) {
views.push_back(std::make_unique<EnableWarpFieldTrials>());
}
return webrtc::CreateEnvironment();

if (views.empty()) {
return webrtc::CreateEnvironment();
}
return webrtc::CreateEnvironment(
std::make_unique<CompositeFieldTrials>(std::move(views)));
}

} // namespace
Expand All @@ -78,13 +144,19 @@ class PeerConnectionObserver;

PeerConnectionFactory::PeerConnectionFactory(
std::shared_ptr<RtcRuntime> rtc_runtime)
: PeerConnectionFactory(std::move(rtc_runtime), false) {}
: PeerConnectionFactory(std::move(rtc_runtime), false, false) {}

PeerConnectionFactory::PeerConnectionFactory(
std::shared_ptr<RtcRuntime> rtc_runtime,
bool zero_playout_delay)
: PeerConnectionFactory(std::move(rtc_runtime), zero_playout_delay, false) {}

PeerConnectionFactory::PeerConnectionFactory(
std::shared_ptr<RtcRuntime> rtc_runtime,
bool zero_playout_delay,
bool enable_warp)
: rtc_runtime_(rtc_runtime),
env_(CreateEnvironment(zero_playout_delay)) {
env_(CreateEnvironment(zero_playout_delay, enable_warp)) {
webrtc::PeerConnectionFactoryDependencies dependencies;
dependencies.network_thread = rtc_runtime_->network_thread();
dependencies.worker_thread = rtc_runtime_->worker_thread();
Expand All @@ -98,6 +170,12 @@ PeerConnectionFactory::PeerConnectionFactory(
<< kForcePlayoutDelayFieldTrial;
}

if (enable_warp) {
RTC_LOG(LS_INFO) << "WebRTC WARP: SPED enabled via field trial "
"WebRTC-IceHandshakeDtls/Enabled/ (SNAP via "
"RtcConfiguration.enable_sctp_snap)";
}

// Create AdmProxy - it creates and initializes Platform ADM internally
adm_proxy_ = rtc_runtime_->worker_thread()->BlockingCall([&] {
return webrtc::make_ref_counted<livekit_ffi::AdmProxy>(
Expand Down Expand Up @@ -215,4 +293,12 @@ create_peer_connection_factory_with_zero_playout_delay() {
return std::make_shared<PeerConnectionFactory>(RtcRuntime::create(), true);
}

std::shared_ptr<PeerConnectionFactory>
create_peer_connection_factory_with_options(bool zero_playout_delay,
bool enable_warp) {
return std::make_shared<PeerConnectionFactory>(RtcRuntime::create(),
zero_playout_delay,
enable_warp);
}

} // namespace livekit_ffi
4 changes: 4 additions & 0 deletions webrtc-sys/src/peer_connection_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ pub mod ffi {
fn create_peer_connection_factory() -> SharedPtr<PeerConnectionFactory>;
fn create_peer_connection_factory_with_zero_playout_delay(
) -> SharedPtr<PeerConnectionFactory>;
fn create_peer_connection_factory_with_options(
zero_playout_delay: bool,
enable_warp: bool,
) -> SharedPtr<PeerConnectionFactory>;

fn zero_playout_delay_enabled(self: &PeerConnectionFactory) -> bool;

Expand Down
Loading