Skip to content

Commit be5438e

Browse files
committed
Merge branch 'main' into theo/agent-error-disconnect
2 parents c5b90bf + 2827707 commit be5438e

3 files changed

Lines changed: 691 additions & 562 deletions

File tree

livekit/src/rtc_engine/peer_transport.rs

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -158,11 +158,13 @@ impl PeerTransport {
158158
Some(start_kbps.min(ultimate_kbps))
159159
}
160160

161-
/// Munge SDP to change a=inactive to a=recvonly for audio m-lines in single PC mode
162-
/// This is needed because WebRTC sometimes generates inactive direction even when
163-
/// we set the transceiver to recvonly
164-
/// We only fix the FIRST inactive audio m-line (the one we need for receiving)
165-
fn munge_inactive_to_recvonly_for_audio(sdp: &str) -> String {
161+
/// Munge SDP to change a=inactive to a=recvonly for RTP media m-lines in single PC mode.
162+
/// This is needed because WebRTC can generate inactive direction even when transceivers
163+
/// were configured as recvonly.
164+
///
165+
/// We intentionally limit this to RTP m-sections, so non-RTP sections (for example
166+
/// data-channel `m=application` sections) are not rewritten.
167+
fn munge_inactive_to_recvonly_for_media(sdp: &str) -> String {
166168
// Detect what line ending the original SDP uses
167169
let uses_crlf = sdp.contains("\r\n");
168170
let eol = if uses_crlf { "\r\n" } else { "\n" };
@@ -171,23 +173,23 @@ impl PeerTransport {
171173
if uses_crlf { sdp.split("\r\n").collect() } else { sdp.split('\n').collect() };
172174

173175
let mut out: Vec<String> = Vec::with_capacity(lines.len());
174-
let mut in_audio_section = false;
175-
let mut fixed_one = false;
176+
let mut in_rtp_media_section = false;
176177

177178
for line in lines {
178179
let l = line.trim();
179180

180-
// Track which media section we're in
181-
if l.starts_with("m=audio") {
182-
in_audio_section = true;
183-
} else if l.starts_with("m=") {
184-
in_audio_section = false;
181+
// Track whether the current m-section is RTP-based.
182+
if l.starts_with("m=") {
183+
// Example RTP m-line:
184+
// m=audio 9 UDP/TLS/RTP/SAVPF 111
185+
// Example data channel m-line:
186+
// m=application 9 UDP/DTLS/SCTP webrtc-datachannel
187+
in_rtp_media_section = l.contains("RTP/");
185188
}
186189

187-
// Change a=inactive to a=recvonly for the first inactive audio section only
188-
if in_audio_section && l == "a=inactive" && !fixed_one {
190+
// Change inactive to recvonly for RTP media m-sections.
191+
if in_rtp_media_section && l == "a=inactive" {
189192
out.push("a=recvonly".to_string());
190-
fixed_one = true;
191193
} else {
192194
out.push(line.to_string());
193195
}
@@ -352,9 +354,9 @@ impl PeerTransport {
352354
let mut sdp = offer.to_string();
353355

354356
if inner.single_pc_mode {
355-
// Fix inactive audio m-lines to recvonly for single PC mode
356-
// WebRTC sometimes generates a=inactive even when transceiver is set to recvonly
357-
let recvonly_munged = Self::munge_inactive_to_recvonly_for_audio(&sdp);
357+
// Fix inactive media m-lines to recvonly for single PC mode.
358+
// WebRTC can generate a=inactive even when transceivers are recvonly.
359+
let recvonly_munged = Self::munge_inactive_to_recvonly_for_media(&sdp);
358360
if recvonly_munged != sdp {
359361
match SessionDescription::parse(&recvonly_munged, offer.sdp_type()) {
360362
Ok(parsed) => {
@@ -542,7 +544,7 @@ a=fmtp:98 profile-id=0;x-google-start-bitrate=1000\n";
542544
}
543545

544546
#[test]
545-
fn inactive_audio_is_munged_to_recvonly_once() {
547+
fn inactive_media_is_munged_to_recvonly_for_all_rtp_sections() {
546548
let sdp = "v=0\n\
547549
o=- 0 0 IN IP4 127.0.0.1\n\
548550
s=-\n\
@@ -552,12 +554,30 @@ a=inactive\n\
552554
a=rtpmap:111 opus/48000/2\n\
553555
m=video 9 UDP/TLS/RTP/SAVPF 96\n\
554556
a=inactive\n\
557+
m=text 9 UDP/TLS/RTP/SAVPF 98\n\
558+
a=inactive\n\
559+
m=audio 9 UDP/TLS/RTP/SAVPF 111\n\
560+
a=inactive\n";
561+
let out = PeerTransport::munge_inactive_to_recvonly_for_media(sdp);
562+
assert!(out.contains("m=audio 9 UDP/TLS/RTP/SAVPF 111\na=recvonly\n"));
563+
assert!(out.contains("m=text 9 UDP/TLS/RTP/SAVPF 98\na=recvonly\n"));
564+
assert_eq!(out.matches("a=recvonly").count(), 4);
565+
assert_eq!(out.matches("a=inactive").count(), 0);
566+
}
567+
568+
#[test]
569+
fn inactive_application_section_is_not_munged() {
570+
let sdp = "v=0\n\
571+
o=- 0 0 IN IP4 127.0.0.1\n\
572+
s=-\n\
573+
t=0 0\n\
555574
m=audio 9 UDP/TLS/RTP/SAVPF 111\n\
575+
a=inactive\n\
576+
m=application 9 UDP/DTLS/SCTP webrtc-datachannel\n\
556577
a=inactive\n";
557-
let out = PeerTransport::munge_inactive_to_recvonly_for_audio(sdp);
578+
let out = PeerTransport::munge_inactive_to_recvonly_for_media(sdp);
558579
assert!(out.contains("m=audio 9 UDP/TLS/RTP/SAVPF 111\na=recvonly\n"));
559-
assert_eq!(out.matches("a=recvonly").count(), 1);
560-
assert_eq!(out.matches("a=inactive").count(), 2);
580+
assert!(out.contains("m=application 9 UDP/DTLS/SCTP webrtc-datachannel\na=inactive\n"));
561581
}
562582

563583
#[test]

livekit/tests/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,15 @@ cargo test -p livekit --features "__lk-e2e-test,native-tls" --test peer_connecti
5151

5252
```sh
5353
# On localhost (V0 will work, V1 falls back to V0)
54+
# Note, suggest running with --test-threads=1 to avoid flakiness
5455
livekit-server --dev
55-
cargo test -p livekit --features "__lk-e2e-test,native-tls" --test peer_connection_signaling_test -- --nocapture
56+
cargo test -p livekit --features "__lk-e2e-test,native-tls" --test peer_connection_signaling_test -- --nocapture --test-threads=1
5657

5758
# On LiveKit Cloud (both V0 and V1 work correctly)
5859
export LIVEKIT_URL="wss://your-project.livekit.cloud"
5960
export LIVEKIT_API_KEY="your-api-key"
6061
export LIVEKIT_API_SECRET="your-api-secret"
61-
cargo test -p livekit --features "__lk-e2e-test,native-tls" --test peer_connection_signaling_test -- --nocapture
62+
cargo test -p livekit --features "__lk-e2e-test,native-tls" --test peer_connection_signaling_test -- --nocapture --test-threads=1
6263
```
6364

6465
## VS Code Integration

0 commit comments

Comments
 (0)