190 lines
5.2 KiB
Rust
190 lines
5.2 KiB
Rust
#[cfg(not(coverage))]
|
|
fn upstream_stale_drop_budget() -> Duration {
|
|
let drop_ms = std::env::var("LESAVKA_UPSTREAM_STALE_DROP_MS")
|
|
.ok()
|
|
.and_then(|value| value.trim().parse::<u64>().ok())
|
|
.unwrap_or(80);
|
|
Duration::from_millis(drop_ms)
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
/// Keeps only the newest webcam packet when the host path is already behind.
|
|
fn retain_freshest_video_packet(
|
|
pending: &mut std::collections::VecDeque<VideoPacket>,
|
|
) -> usize {
|
|
if pending.len() <= 1 {
|
|
return 0;
|
|
}
|
|
let newest = pending.pop_back().expect("non-empty pending video queue");
|
|
let dropped = pending.len();
|
|
pending.clear();
|
|
pending.push_back(newest);
|
|
dropped
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
const AUDIO_PENDING_LIVE_WINDOW_PACKETS: usize = 8;
|
|
|
|
#[cfg(not(coverage))]
|
|
/// Keeps a tiny newest microphone window so playout can stay smooth without
|
|
/// draining old audio.
|
|
fn retain_freshest_audio_packet(
|
|
pending: &mut std::collections::VecDeque<AudioPacket>,
|
|
) -> usize {
|
|
if pending.len() <= AUDIO_PENDING_LIVE_WINDOW_PACKETS {
|
|
return 0;
|
|
}
|
|
let dropped = pending.len() - AUDIO_PENDING_LIVE_WINDOW_PACKETS;
|
|
pending.drain(..dropped);
|
|
dropped
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
/// Extract client-side timing facts from an upstream microphone packet.
|
|
fn audio_client_timing(pkt: &AudioPacket) -> UpstreamClientTiming {
|
|
let capture_pts_us = if pkt.client_capture_pts_us == 0 {
|
|
pkt.pts
|
|
} else {
|
|
pkt.client_capture_pts_us
|
|
};
|
|
let send_pts_us = if pkt.client_send_pts_us == 0 {
|
|
capture_pts_us
|
|
} else {
|
|
pkt.client_send_pts_us
|
|
};
|
|
UpstreamClientTiming {
|
|
capture_pts_us,
|
|
send_pts_us,
|
|
queue_depth: pkt.client_queue_depth,
|
|
queue_age_ms: pkt.client_queue_age_ms,
|
|
}
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
/// Extract client-side timing facts from an upstream camera packet.
|
|
fn video_client_timing(pkt: &VideoPacket) -> UpstreamClientTiming {
|
|
let capture_pts_us = if pkt.client_capture_pts_us == 0 {
|
|
pkt.pts
|
|
} else {
|
|
pkt.client_capture_pts_us
|
|
};
|
|
let send_pts_us = if pkt.client_send_pts_us == 0 {
|
|
capture_pts_us
|
|
} else {
|
|
pkt.client_send_pts_us
|
|
};
|
|
UpstreamClientTiming {
|
|
capture_pts_us,
|
|
send_pts_us,
|
|
queue_depth: pkt.client_queue_depth,
|
|
queue_age_ms: pkt.client_queue_age_ms,
|
|
}
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum UpstreamStreamCleanupKind {
|
|
Microphone,
|
|
Camera,
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
struct UpstreamStreamCleanup {
|
|
runtime: Arc<UpstreamMediaRuntime>,
|
|
kind: UpstreamStreamCleanupKind,
|
|
generation: u64,
|
|
rpc_id: u64,
|
|
session_id: u64,
|
|
camera_session_id: Option<u64>,
|
|
outcome: &'static str,
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
impl UpstreamStreamCleanup {
|
|
fn microphone(
|
|
runtime: Arc<UpstreamMediaRuntime>,
|
|
generation: u64,
|
|
rpc_id: u64,
|
|
session_id: u64,
|
|
) -> Self {
|
|
Self {
|
|
runtime,
|
|
kind: UpstreamStreamCleanupKind::Microphone,
|
|
generation,
|
|
rpc_id,
|
|
session_id,
|
|
camera_session_id: None,
|
|
outcome: "aborted",
|
|
}
|
|
}
|
|
|
|
fn camera(
|
|
runtime: Arc<UpstreamMediaRuntime>,
|
|
generation: u64,
|
|
rpc_id: u64,
|
|
session_id: u64,
|
|
camera_session_id: u64,
|
|
) -> Self {
|
|
Self {
|
|
runtime,
|
|
kind: UpstreamStreamCleanupKind::Camera,
|
|
generation,
|
|
rpc_id,
|
|
session_id,
|
|
camera_session_id: Some(camera_session_id),
|
|
outcome: "aborted",
|
|
}
|
|
}
|
|
|
|
fn mark_closed(&mut self) {
|
|
self.outcome = "closed";
|
|
}
|
|
|
|
fn mark_superseded(&mut self) {
|
|
self.outcome = "superseded";
|
|
}
|
|
|
|
fn mark_aborted(&mut self) {
|
|
self.outcome = "aborted";
|
|
}
|
|
}
|
|
|
|
#[cfg(not(coverage))]
|
|
impl Drop for UpstreamStreamCleanup {
|
|
/// Closes only the stream generation owned by this RPC lifecycle guard.
|
|
fn drop(&mut self) {
|
|
match self.kind {
|
|
UpstreamStreamCleanupKind::Microphone => {
|
|
self.runtime.close_microphone(self.generation);
|
|
info!(
|
|
rpc_id = self.rpc_id,
|
|
session_id = self.session_id,
|
|
generation = self.generation,
|
|
outcome = self.outcome,
|
|
"🎤 stream_microphone lifecycle ended"
|
|
);
|
|
}
|
|
UpstreamStreamCleanupKind::Camera => {
|
|
self.runtime.close_camera(self.generation);
|
|
info!(
|
|
rpc_id = self.rpc_id,
|
|
session_id = self.session_id,
|
|
camera_session_id = self.camera_session_id.unwrap_or_default(),
|
|
generation = self.generation,
|
|
outcome = self.outcome,
|
|
"🎥 stream_camera lifecycle ended"
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Maps expected remote-audio availability failures onto retryable gRPC status codes.
|
|
fn remote_audio_status(message: String) -> Status {
|
|
if message.contains("remote USB gadget is not attached") {
|
|
Status::unavailable(message)
|
|
} else {
|
|
Status::internal(message)
|
|
}
|
|
}
|