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
12 changes: 12 additions & 0 deletions .changeset/simplify-start-bitrate-and-degradation-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"livekit": patch
---

Simplify x-google-start-bitrate logic and update degradation preference defaults

- Start bitrate: use min(90% of target, 1 Mbps) instead of adaptive network hints
- Remove slow connection detection and network quality hints on reconnect
- Default degradation preference by track source:
- Camera: MaintainFramerate (smoother video)
- Screenshare: MaintainResolution (clarity for text/UI)
- Other: Balanced
17 changes: 16 additions & 1 deletion livekit-api/src/signal_client/signal_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
use livekit_protocol as proto;
use livekit_runtime::{JoinHandle, TcpStream};
use prost::Message as ProtoMessage;
use std::{env, io, time::Duration};

Check warning on line 23 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / livekit-api (signal-client (async), cargo test -p livekit-api --no-default-features --features si...

unused imports: `env` and `io`

use tokio::sync::{mpsc, oneshot};

Expand Down Expand Up @@ -157,7 +157,7 @@
let mut proxy_auth_header = None;
if let Some(password) = proxy_url.password() {
let auth = format!("{}:{}", proxy_url.username(), password);
let auth = format!("Basic {}", base64::encode(auth));

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / livekit-api (signal-client (tokio), cargo test -p livekit-api --no-default-features --features si...

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (aarch64-apple-ios-sim)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (aarch64-unknown-linux-gnu)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (aarch64-apple-darwin)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (aarch64-apple-ios)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (x86_64-unknown-linux-gnu)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (x86_64-apple-darwin)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (aarch64-pc-windows-msvc)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Build (x86_64-pc-windows-msvc)

use of deprecated function `base64::encode`: Use Engine::encode

Check warning on line 160 in livekit-api/src/signal_client/signal_stream.rs

View workflow job for this annotation

GitHub Actions / Test (aarch64-apple-darwin)

use of deprecated function `base64::encode`: Use Engine::encode
proxy_auth_header = Some(auth);
}

Expand Down Expand Up @@ -407,7 +407,22 @@
}
Ok(Message::Frame(_)) => {}
Err(WsError::Protocol(ProtocolError::ResetWithoutClosingHandshake)) => {
break; // Ignore
log::debug!("websocket reset without closing handshake");
break; // Treat as normal close
}
Err(WsError::Io(ref io_err))
if io_err.kind() == std::io::ErrorKind::UnexpectedEof =>
{
// TLS connection closed without close_notify - treat as normal close
// This happens when the server closes the connection abruptly
log::debug!(
"websocket closed with unexpected EOF (TLS close without close_notify)"
);
break;
Comment on lines +413 to +421

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Matching on Err(WsError::Io(ref io_err)) may not match wrapped TLS errors

The pattern Err(WsError::Io(ref io_err)) if io_err.kind() == UnexpectedEof at livekit-api/src/signal_client/signal_stream.rs:413-419 assumes the TLS close-without-close_notify surfaces as a direct io::Error with UnexpectedEof kind. Depending on the tungstenite version and TLS backend (native-tls vs rustls), TLS errors may be wrapped differently — e.g., as WsError::Tls(...) rather than WsError::Io(...). If the TLS error doesn't surface as WsError::Io, this handler won't match and the error will fall through to the catch-all _ => branch (which still breaks, just with an error log). Not a correctness bug, but the handler may be less effective than intended.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
Err(WsError::ConnectionClosed) | Err(WsError::AlreadyClosed) => {
log::debug!("websocket connection already closed");
break;
}
_ => {
log::error!("unhandled websocket message {:?}", msg);
Expand Down
52 changes: 26 additions & 26 deletions livekit/src/room/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,14 @@ pub struct TrackPublishOptions {
/// Controls how the encoder trades off between resolution and framerate
/// when bandwidth is constrained.
///
/// - `MaintainResolution`: Prioritizes resolution, drops frames if needed
/// - `MaintainFramerate`: Prioritizes framerate, reduces resolution if needed
/// - `MaintainResolution`: Prioritizes resolution, drops frames if needed
/// - `Balanced`: Balances between both
///
/// If not set, the SDK will use a smart default based on the track source
/// and resolution (MaintainResolution for screenshare or video >= 540p).
/// If not set, the SDK uses defaults based on track source:
/// - Camera: `MaintainFramerate` (smoother video for real-time communication)
/// - Screen share: `MaintainResolution` (clarity is critical for text/UI)
/// - Other/unknown: `Balanced`
pub degradation_preference: Option<DegradationPreference>,
}

Expand Down Expand Up @@ -175,14 +177,10 @@ impl Default for TrackPublishOptions {
/// Returns the appropriate degradation preference for a video track.
///
/// If the user explicitly set a preference in `TrackPublishOptions`, that is returned.
/// Otherwise, defaults to `MaintainResolution` for all video tracks.
///
/// `MaintainResolution` ensures video clarity is preserved during bandwidth constraints
/// by dropping frames rather than reducing resolution. This prevents the "blurry video"
/// issue that users commonly report during initial connection or network fluctuations.
///
/// Users who prefer smoother video over clarity can explicitly set `Balanced` or
/// `MaintainFramerate` in their `TrackPublishOptions`.
/// Otherwise, defaults based on track source:
/// - `MaintainFramerate` for camera tracks (smoother video for real-time communication)
/// - `MaintainResolution` for screen share (clarity is critical for reading text/UI)
/// - `Balanced` for other/unknown sources
pub fn get_default_degradation_preference(
options: &TrackPublishOptions,
_height: u32,
Expand All @@ -192,9 +190,12 @@ pub fn get_default_degradation_preference(
return pref;
}

// Default to MaintainResolution for all video tracks to prevent blurry video
// during bandwidth ramp-up or network constraints
DegradationPreference::MaintainResolution
// Default based on track source
match options.source {
TrackSource::Camera => DegradationPreference::MaintainFramerate,
TrackSource::Screenshare => DegradationPreference::MaintainResolution,
_ => DegradationPreference::Balanced,
}
Comment on lines +193 to +198

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔍 Degradation preference default change is a significant behavioral shift for camera tracks

The default degradation preference for camera tracks changed from MaintainResolution (old code at the base, which returned MaintainResolution for all sources) to MaintainFramerate (livekit/src/room/options.rs:195). This means existing users who relied on the previous default will now see their camera tracks prioritize framerate over resolution during bandwidth constraints — the opposite of the previous behavior. While the new default may be more appropriate for real-time communication, this is a silent behavioral change that could surprise users who were satisfied with the previous clarity-preserving behavior. The _height parameter is now fully unused (livekit/src/room/options.rs:186), confirming the resolution-based logic was removed.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}

impl VideoPreset {
Expand Down Expand Up @@ -571,29 +572,28 @@ mod tests {
}

#[test]
fn degradation_preference_defaults_to_maintain_resolution() {
// All sources should default to MaintainResolution
fn degradation_preference_defaults_by_source() {
// Camera defaults to MaintainFramerate (smoother video)
let camera_options =
TrackPublishOptions { source: TrackSource::Camera, ..Default::default() };
let screenshare_options =
TrackPublishOptions { source: TrackSource::Screenshare, ..Default::default() };
let default_options = TrackPublishOptions::default();

assert_eq!(
get_default_degradation_preference(&camera_options, 1080),
DegradationPreference::MaintainResolution
DegradationPreference::MaintainFramerate
);

// Screenshare defaults to MaintainResolution (clarity for text/UI)
let screenshare_options =
TrackPublishOptions { source: TrackSource::Screenshare, ..Default::default() };
assert_eq!(
get_default_degradation_preference(&screenshare_options, 1080),
DegradationPreference::MaintainResolution
);

// Unknown/default source defaults to Balanced
let default_options = TrackPublishOptions::default();
assert_eq!(
get_default_degradation_preference(&default_options, 720),
DegradationPreference::MaintainResolution
);
assert_eq!(
get_default_degradation_preference(&default_options, 360),
DegradationPreference::MaintainResolution
DegradationPreference::Balanced
);
}

Expand Down
Loading
Loading