181 lines
7.5 KiB
Rust
181 lines
7.5 KiB
Rust
impl UpstreamMediaRuntime {
|
|
/// Keeps `activate` explicit because it sits on server upstream media scheduling, where timing choices directly affect lip sync.
|
|
fn activate(&self, kind: UpstreamMediaKind) -> UpstreamStreamLease {
|
|
let generation = match kind {
|
|
UpstreamMediaKind::Camera => {
|
|
self.next_camera_generation.fetch_add(1, Ordering::SeqCst) + 1
|
|
}
|
|
UpstreamMediaKind::Microphone => {
|
|
self.next_microphone_generation
|
|
.fetch_add(1, Ordering::SeqCst)
|
|
+ 1
|
|
}
|
|
};
|
|
let mut state = self
|
|
.state
|
|
.lock()
|
|
.expect("upstream media state mutex poisoned");
|
|
if state.active_camera_generation.is_none() && state.active_microphone_generation.is_none()
|
|
{
|
|
state.session_id = self.next_session_id.fetch_add(1, Ordering::SeqCst) + 1;
|
|
reset_session_state(&mut state);
|
|
state.session_started_at = Some(Instant::now());
|
|
state.phase = UpstreamSyncPhase::Acquiring;
|
|
state.last_reason = "v2 upstream session acquiring media".to_string();
|
|
}
|
|
match kind {
|
|
UpstreamMediaKind::Camera => state.active_camera_generation = Some(generation),
|
|
UpstreamMediaKind::Microphone => state.active_microphone_generation = Some(generation),
|
|
}
|
|
UpstreamStreamLease {
|
|
session_id: state.session_id,
|
|
generation,
|
|
}
|
|
}
|
|
|
|
/// Starts a bundled camera+microphone generation with a fresh shared playout epoch.
|
|
fn activate_bundled(&self) -> UpstreamBundledLeases {
|
|
let camera_generation = self.next_camera_generation.fetch_add(1, Ordering::SeqCst) + 1;
|
|
let microphone_generation =
|
|
self.next_microphone_generation
|
|
.fetch_add(1, Ordering::SeqCst)
|
|
+ 1;
|
|
let mut state = self
|
|
.state
|
|
.lock()
|
|
.expect("upstream media state mutex poisoned");
|
|
state.session_id = self.next_session_id.fetch_add(1, Ordering::SeqCst) + 1;
|
|
reset_session_state(&mut state);
|
|
state.session_started_at = Some(Instant::now());
|
|
state.phase = UpstreamSyncPhase::Acquiring;
|
|
state.last_reason = "v2 bundled upstream session acquiring media".to_string();
|
|
state.active_camera_generation = Some(camera_generation);
|
|
state.active_microphone_generation = Some(microphone_generation);
|
|
UpstreamBundledLeases {
|
|
camera: UpstreamStreamLease {
|
|
session_id: state.session_id,
|
|
generation: camera_generation,
|
|
},
|
|
microphone: UpstreamStreamLease {
|
|
session_id: state.session_id,
|
|
generation: microphone_generation,
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Keeps `is_active` explicit because it sits on server upstream media scheduling, where timing choices directly affect lip sync.
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
|
fn is_active(&self, kind: UpstreamMediaKind, generation: u64) -> bool {
|
|
let state = self
|
|
.state
|
|
.lock()
|
|
.expect("upstream media state mutex poisoned");
|
|
match kind {
|
|
UpstreamMediaKind::Camera => state.active_camera_generation == Some(generation),
|
|
UpstreamMediaKind::Microphone => state.active_microphone_generation == Some(generation),
|
|
}
|
|
}
|
|
|
|
/// Keeps `close` explicit because it sits on server upstream media scheduling, where timing choices directly affect lip sync.
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
|
fn close(&self, kind: UpstreamMediaKind, generation: u64) {
|
|
let mut state = self
|
|
.state
|
|
.lock()
|
|
.expect("upstream media state mutex poisoned");
|
|
match kind {
|
|
UpstreamMediaKind::Camera if state.active_camera_generation == Some(generation) => {
|
|
state.active_camera_generation = None
|
|
}
|
|
UpstreamMediaKind::Microphone
|
|
if state.active_microphone_generation == Some(generation) =>
|
|
{
|
|
state.active_microphone_generation = None
|
|
}
|
|
_ => return,
|
|
}
|
|
if state.active_camera_generation.is_none() && state.active_microphone_generation.is_none()
|
|
{
|
|
reset_session_state(&mut state);
|
|
}
|
|
}
|
|
|
|
fn plan_legacy_pts(
|
|
&self,
|
|
kind: UpstreamMediaKind,
|
|
remote_pts_us: u64,
|
|
min_step_us: u64,
|
|
) -> UpstreamPlanDecision {
|
|
self.plan_rebased_pts(kind, remote_pts_us, min_step_us.max(1), None, None)
|
|
}
|
|
|
|
/// Keeps `plan_rebased_pts` explicit because it sits on server upstream media scheduling, where timing choices directly affect lip sync.
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
|
fn plan_rebased_pts(
|
|
&self,
|
|
kind: UpstreamMediaKind,
|
|
remote_pts_us: u64,
|
|
min_step_us: u64,
|
|
explicit_base: Option<u64>,
|
|
explicit_epoch: Option<Instant>,
|
|
) -> UpstreamPlanDecision {
|
|
let mut state = self
|
|
.state
|
|
.lock()
|
|
.expect("upstream media state mutex poisoned");
|
|
match kind {
|
|
UpstreamMediaKind::Camera => state.latest_camera_remote_pts_us = Some(remote_pts_us),
|
|
UpstreamMediaKind::Microphone => {
|
|
state.latest_microphone_remote_pts_us = Some(remote_pts_us)
|
|
}
|
|
}
|
|
let base = match explicit_base {
|
|
Some(base) => *state.base_remote_pts_us.get_or_insert(base),
|
|
None => *state.base_remote_pts_us.get_or_insert(remote_pts_us),
|
|
};
|
|
let epoch = match explicit_epoch {
|
|
Some(epoch) => *state.playout_epoch.get_or_insert(epoch),
|
|
None => *state
|
|
.playout_epoch
|
|
.get_or_insert(Instant::now() + upstream_playout_delay()),
|
|
};
|
|
let mut local_pts_us = remote_pts_us.saturating_sub(base);
|
|
let last_slot = match kind {
|
|
UpstreamMediaKind::Camera => &mut state.last_video_local_pts_us,
|
|
UpstreamMediaKind::Microphone => &mut state.last_audio_local_pts_us,
|
|
};
|
|
if let Some(last_pts_us) = *last_slot
|
|
&& local_pts_us <= last_pts_us
|
|
{
|
|
local_pts_us = last_pts_us.saturating_add(min_step_us.max(1));
|
|
}
|
|
*last_slot = Some(local_pts_us);
|
|
state.phase = UpstreamSyncPhase::Syncing;
|
|
state.last_reason = "v2 legacy packet mapped without cross-stream planner".to_string();
|
|
let due_at = apply_offset(
|
|
epoch + Duration::from_micros(local_pts_us),
|
|
self.playout_offset_us(kind),
|
|
);
|
|
let late_by = Instant::now()
|
|
.checked_duration_since(due_at)
|
|
.unwrap_or_default();
|
|
UpstreamPlanDecision::Play(PlannedUpstreamPacket {
|
|
local_pts_us,
|
|
due_at,
|
|
late_by,
|
|
source_lag: Duration::ZERO,
|
|
})
|
|
}
|
|
|
|
/// Keeps `playout_offset_us` explicit because it sits on server upstream media scheduling, where timing choices directly affect lip sync.
|
|
/// Inputs are the typed parameters; output is the return value or side effect.
|
|
fn playout_offset_us(&self, kind: UpstreamMediaKind) -> i64 {
|
|
match kind {
|
|
UpstreamMediaKind::Camera => self.camera_playout_offset_us.load(Ordering::Relaxed),
|
|
UpstreamMediaKind::Microphone => {
|
|
self.microphone_playout_offset_us.load(Ordering::Relaxed)
|
|
}
|
|
}
|
|
}
|
|
}
|