lesavka/server/src/main/relay_stream_lifecycle.rs

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)
}
}