diff --git a/Cargo.lock b/Cargo.lock index b620a805b7c717e65d134f8c4c740420547a2599..a32b38979b99799089ea549e5dc67a7b72829224 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10003,6 +10003,7 @@ dependencies = [ "tokio", "ui", "util", + "webrtc-sys", "zed-scap", ] diff --git a/Cargo.toml b/Cargo.toml index bc1722718b8ed464b6c78c776699bce890ba223b..b31c088581a65070f348cf55195d0db948b33bb0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -779,6 +779,7 @@ wax = "0.7" which = "6.0.0" wasm-bindgen = "0.2.113" web-time = "1.1.0" +webrtc-sys = "0.3.23" wgpu = { git = "https://github.com/zed-industries/wgpu.git", branch = "v29" } windows-core = "0.61" yawc = "0.2.5" @@ -849,6 +850,7 @@ windows-capture = { git = "https://github.com/zed-industries/windows-capture.git calloop = { git = "https://github.com/zed-industries/calloop" } livekit = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" } libwebrtc = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" } +webrtc-sys = { git = "https://github.com/zed-industries/livekit-rust-sdks", rev = "c1209aa155cbf4543383774f884a46ae7e53ee2e" } [profile.dev] split-debuginfo = "unpacked" diff --git a/crates/livekit_client/Cargo.toml b/crates/livekit_client/Cargo.toml index d4a238fc15997d833df65ac1be459763be6ec782..42c13f094c1893260f474c98f650ba83be832ef0 100644 --- a/crates/livekit_client/Cargo.toml +++ b/crates/livekit_client/Cargo.toml @@ -49,6 +49,7 @@ livekit.workspace = true [target.'cfg(target_os = "linux")'.dependencies] tokio = { workspace = true, features = ["time"] } +webrtc-sys.workspace = true [target.'cfg(any(target_os = "linux", target_os = "freebsd", target_os = "windows"))'.dependencies] scap.workspace = true diff --git a/crates/livekit_client/src/livekit_client/linux.rs b/crates/livekit_client/src/livekit_client/linux.rs index e7bfa7b2ca631636233586cb902b36bac93c9be1..f6fa3dfd20e153c268a87d2a3d62bcf778d78571 100644 --- a/crates/livekit_client/src/livekit_client/linux.rs +++ b/crates/livekit_client/src/livekit_client/linux.rs @@ -10,7 +10,7 @@ use livekit::webrtc::{ }; use std::sync::{ Arc, - atomic::{AtomicBool, AtomicU64, Ordering}, + atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering}, }; static NEXT_WAYLAND_SHARE_ID: AtomicU64 = AtomicU64::new(1); @@ -64,6 +64,19 @@ pub(crate) async fn start_wayland_desktop_capture( }; use libwebrtc::native::yuv_helper::argb_to_nv12; use std::time::Duration; + use webrtc_sys::webrtc::ffi as webrtc_ffi; + + fn webrtc_log_callback(message: String, severity: webrtc_ffi::LoggingSeverity) { + match severity { + webrtc_ffi::LoggingSeverity::Error => log::error!("[webrtc] {}", message.trim()), + webrtc_ffi::LoggingSeverity::Warning => log::warn!("[webrtc] {}", message.trim()), + webrtc_ffi::LoggingSeverity::Info => log::info!("[webrtc] {}", message.trim()), + _ => log::debug!("[webrtc] {}", message.trim()), + } + } + + let _webrtc_log_sink = webrtc_ffi::new_log_sink(webrtc_log_callback); + log::info!("Wayland desktop capture: WebRTC internal logging enabled"); let stop_flag = Arc::new(AtomicBool::new(false)); let (mut video_source_tx, mut video_source_rx) = mpsc::channel::(1); @@ -79,9 +92,13 @@ pub(crate) async fn start_wayland_desktop_capture( })?; let permanent_error = Arc::new(AtomicBool::new(false)); + let temporary_error_count = Arc::new(AtomicU32::new(0)); + let first_frame_received = Arc::new(AtomicBool::new(false)); let stop_cb = stop_flag.clone(); let permanent_error_cb = permanent_error.clone(); + let temporary_error_count_cb = temporary_error_count.clone(); + let first_frame_received_cb = first_frame_received.clone(); capturer.start_capture(None, { let mut video_source: Option = None; let mut current_width: u32 = 0; @@ -94,8 +111,20 @@ pub(crate) async fn start_wayland_desktop_capture( move |result: Result| { let frame = match result { - Ok(frame) => frame, - Err(CaptureError::Temporary) => return, + Ok(frame) => { + if !first_frame_received_cb.swap(true, Ordering::Relaxed) { + let skipped = temporary_error_count_cb.load(Ordering::Relaxed); + log::info!( + "Wayland desktop capture: first frame received \ + (after {skipped} temporary errors)" + ); + } + frame + } + Err(CaptureError::Temporary) => { + temporary_error_count_cb.fetch_add(1, Ordering::Relaxed); + return; + } Err(CaptureError::Permanent) => { log::error!("Wayland desktop capture encountered a permanent error"); permanent_error_cb.store(true, Ordering::Release); @@ -136,13 +165,29 @@ pub(crate) async fn start_wayland_desktop_capture( } }); + log::info!("Wayland desktop capture: starting capture loop"); + let stop = stop_flag.clone(); + let temporary_error_count_loop = temporary_error_count.clone(); + let first_frame_received_loop = first_frame_received.clone(); let tokio_task = gpui_tokio::Tokio::spawn(cx, async move { + let mut poll_count: u64 = 0; loop { if stop.load(Ordering::Acquire) { break; } capturer.capture_frame(); + poll_count += 1; + + if !first_frame_received_loop.load(Ordering::Relaxed) && poll_count % 150 == 0 { + let temporary_errors = temporary_error_count_loop.load(Ordering::Relaxed); + log::warn!( + "Wayland desktop capture: still waiting for first frame \ + ({poll_count} polls, {temporary_errors} temporary errors). \ + PipeWire may not be delivering frames." + ); + } + tokio::time::sleep(Duration::from_millis(33)).await; } drop(capturer); @@ -162,12 +207,21 @@ pub(crate) async fn start_wayland_desktop_capture( let executor = cx.background_executor().clone(); let video_source = video_source_rx .next() - .with_timeout(Duration::from_secs(15), &executor) + .with_timeout(Duration::from_secs(60), &executor) .await .map_err(|_| { + let temporary_errors = temporary_error_count.load(Ordering::Relaxed); + let got_first_frame = first_frame_received.load(Ordering::Relaxed); stop_flag.store(true, Ordering::Relaxed); + log::error!( + "Wayland desktop capture timed out. \ + Diagnostics: temporary_errors={temporary_errors}, \ + first_frame_received={got_first_frame}" + ); anyhow::anyhow!( - "Screen sharing timed out waiting for the first frame. \ + "Screen sharing timed out waiting for the first frame \ + ({temporary_errors} temporary capture errors, \ + first_frame_received={got_first_frame}). \ Check that xdg-desktop-portal and PipeWire are running, \ and that your portal backend matches your compositor." )