impl LesavkaClientApp { /*──────────────── mic stream ─────────────────*/ #[cfg(not(coverage))] /// Keeps `voice_loop` explicit because it sits on the live uplink path, where stale media must be dropped instead of queued into latency. /// Inputs are the typed parameters; output is the return value or side effect. async fn voice_loop( ep: Channel, initial_source: Option, telemetry: crate::uplink_telemetry::UplinkTelemetryHandle, media_controls: crate::live_media_control::LiveMediaControls, pause_when_camera_active: bool, ) { let mut delay = Duration::from_secs(1); static FAIL_CNT: AtomicUsize = AtomicUsize::new(0); loop { let state = media_controls.refresh(); if pause_when_camera_active && state.camera { tokio::time::sleep(Duration::from_millis(100)).await; continue; } if !state.microphone { telemetry.record_enabled(false); tokio::time::sleep(Duration::from_millis(100)).await; continue; } let microphone_source_choice = state.microphone_source.clone(); let active_source = microphone_source_choice.resolve(initial_source.as_deref()); let active_audio_codec = state .audio_codec .resolve(lesavka_common::audio_transport::UpstreamAudioCodec::Opus); let active_noise_suppression = state.noise_suppression.resolve(false); let use_default_source = matches!( microphone_source_choice, crate::live_media_control::MediaDeviceChoice::Auto ) && active_source.is_none(); let setup_source = active_source.clone(); let result = tokio::task::spawn_blocking(move || { if use_default_source { MicrophoneCapture::new_default_source_options( active_audio_codec, active_noise_suppression, ) } else { MicrophoneCapture::new_with_source_options( setup_source.as_deref(), active_audio_codec, active_noise_suppression, ) } }) .await; let mic = match result { Ok(Ok(mic)) => Arc::new(mic), Ok(Err(err)) => { telemetry.record_disconnect(format!("microphone uplink setup failed: {err:#}")); warn!( "🎤 microphone uplink setup failed for {:?}: {err:#}", active_source.as_deref().unwrap_or("auto") ); abort_if_required_media_source_failed("microphone", "🎤", active_source.as_deref(), &err); delay = app_support::next_delay(delay); tokio::time::sleep(delay).await; continue; } Err(err) => { telemetry.record_disconnect(format!("microphone uplink setup task failed: {err}")); warn!("🎤 microphone uplink setup task failed before StreamMicrophone could start: {err}"); abort_if_required_media_source_failed( "microphone", "🎤", active_source.as_deref(), &err, ); delay = app_support::next_delay(delay); tokio::time::sleep(delay).await; continue; } }; telemetry.record_reconnect_attempt(); let mut cli = RelayClient::new(ep.clone()); let queue = crate::uplink_fresh_queue::FreshPacketQueue::new(AUDIO_UPLINK_QUEUE); let drop_log = Arc::new(std::sync::Mutex::new(UplinkDropLogLimiter::new( "microphone", "🎤", ))); let queue_stream = queue.clone(); let telemetry_stream = telemetry.clone(); let drop_log_stream = Arc::clone(&drop_log); let outbound = async_stream::stream! { loop { let next = queue_stream.pop_fresh().await; if next.dropped_stale > 0 { telemetry_stream.record_stale_drop(next.dropped_stale); log_uplink_drop( &drop_log_stream, UplinkDropReason::Stale, next.dropped_stale, next.queue_depth, duration_ms(next.delivery_age), ); } if let Some(mut packet) = next.packet { telemetry_stream.record_streamed( queue_depth_u32(next.queue_depth), duration_ms(next.delivery_age), ); attach_audio_queue_metadata( &mut packet, next.queue_depth, next.delivery_age, ); yield packet; continue; } break; } }; match cli.stream_microphone(Request::new(outbound)).await { Ok(mut resp) => { let (stop_tx, stop_rx) = std::sync::mpsc::channel::<()>(); let mic_clone = mic.clone(); let telemetry_thread = telemetry.clone(); let queue_thread = queue.clone(); let drop_log_thread = Arc::clone(&drop_log); let media_controls_thread = media_controls.clone(); let initial_source_thread = initial_source.clone(); let active_source_thread = active_source.clone(); let active_audio_codec_thread = active_audio_codec; let active_noise_suppression_thread = active_noise_suppression; let mic_worker = std::thread::spawn(move || { let mut paused = false; while stop_rx.try_recv().is_err() { let state = media_controls_thread.refresh(); let desired_source = state .microphone_source .resolve(initial_source_thread.as_deref()); let desired_audio_codec = state .audio_codec .resolve(lesavka_common::audio_transport::UpstreamAudioCodec::Opus); let desired_noise_suppression = state.noise_suppression.resolve(false); if pause_when_camera_active && state.camera { tracing::info!( "🎤 microphone-only uplink yielding to bundled webcam A/V" ); break; } if desired_source != active_source_thread || desired_audio_codec != active_audio_codec_thread || desired_noise_suppression != active_noise_suppression_thread { tracing::info!( from = active_source_thread.as_deref().unwrap_or("auto"), to = desired_source.as_deref().unwrap_or("auto"), from_codec = active_audio_codec_thread.as_id(), to_codec = desired_audio_codec.as_id(), from_noise_suppression = active_noise_suppression_thread, to_noise_suppression = desired_noise_suppression, "🎤 microphone route changed; restarting live uplink pipeline" ); break; } if !state.microphone { if !paused { telemetry_thread.record_enabled(false); tracing::info!("🎤 microphone uplink soft-paused"); paused = true; } std::thread::sleep(Duration::from_millis(20)); continue; } if paused { telemetry_thread.record_enabled(true); tracing::info!("🎤 microphone uplink resumed"); paused = false; } if let Some(mut pkt) = mic_clone.pull() { trace!("🎤📤 cli {} bytes → gRPC", pkt.data.len()); let enqueue_age = stamp_audio_timing_metadata_at_enqueue(&mut pkt); let stats = queue_thread.push(pkt, enqueue_age); if stats.dropped_queue_full > 0 { telemetry_thread.record_queue_full_drop(stats.dropped_queue_full); log_uplink_drop( &drop_log_thread, UplinkDropReason::QueueFull, stats.dropped_queue_full, stats.queue_depth, duration_ms(enqueue_age), ); } telemetry_thread.record_enqueue( queue_depth_u32(stats.queue_depth), duration_ms(enqueue_age), 0.0, ); } } }); delay = Duration::from_secs(1); telemetry.record_connected(); while resp.get_mut().message().await.transpose().is_some() {} telemetry.record_disconnect("microphone uplink stream ended"); queue.close(); let _ = stop_tx.send(()); let _ = mic_worker.join(); } Err(e) => { telemetry.record_disconnect(format!("microphone uplink connect failed: {e}")); if FAIL_CNT.fetch_add(1, Ordering::Relaxed) == 0 { warn!("❌🎤 connect failed: {e}"); warn!("⚠️🎤 further microphone‑stream failures will be logged at DEBUG"); } else { debug!("❌🎤 reconnect failed: {e}"); } delay = app_support::next_delay(delay); } } queue.close(); tokio::time::sleep(delay).await; } } }