use std::collections::BTreeMap;
use std::time::Instant;
use super::speaker::Speaker;
use super::{C1, C2, C3, LEVEL_IDLE_TIMEOUT_MS, MAX_LEVEL, MIN_LEVEL, SPEAKER_IDLE_TIMEOUT_MS};
#[cfg(test)]
mod tests;
#[derive(Debug, Default)]
pub struct ActiveSpeakerDetector {
speakers: BTreeMap<u64, Speaker>,
current_dominant: Option<u64>,
last_level_idle_time: Option<Instant>,
}
impl ActiveSpeakerDetector {
pub fn new() -> Self {
Self::default()
}
pub fn add_peer(&mut self, peer_id: u64, now: Instant) {
self.speakers
.entry(peer_id)
.or_insert_with(|| Speaker::new(now));
}
pub fn remove_peer(&mut self, peer_id: u64) {
self.speakers.remove(&peer_id);
if self.current_dominant == Some(peer_id) {
self.current_dominant = None;
}
}
pub fn record_level(&mut self, peer_id: u64, level_raw: u8, now: Instant) {
let vol = MAX_LEVEL.saturating_sub(level_raw.min(MAX_LEVEL));
self.speakers
.entry(peer_id)
.or_insert_with(|| Speaker::new(now))
.level_changed(vol, now);
}
fn timeout_idle_levels(&mut self, now: Instant) {
let dom = self.current_dominant;
for (&id, sp) in self.speakers.iter_mut() {
let idle = now.duration_since(sp.last_level_change).as_millis() as u64;
if SPEAKER_IDLE_TIMEOUT_MS < idle && dom != Some(id) {
sp.paused = true;
} else if LEVEL_IDLE_TIMEOUT_MS < idle {
sp.level_changed(MIN_LEVEL, now);
}
}
}
pub fn tick(&mut self, now: Instant) -> Option<u64> {
match self.last_level_idle_time {
Some(t) if now.duration_since(t).as_millis() as u64 >= LEVEL_IDLE_TIMEOUT_MS => {
self.timeout_idle_levels(now);
self.last_level_idle_time = Some(now);
}
None => self.last_level_idle_time = Some(now),
_ => {}
}
if self.speakers.is_empty() {
return None;
}
self.calculate_active_speaker()
}
fn calculate_active_speaker(&mut self) -> Option<u64> {
let new_id = if self.speakers.len() == 1 {
self.speakers.keys().next().copied()
} else {
let incumbent = self.current_dominant;
let seed = incumbent.or_else(|| self.speakers.keys().next().copied())?;
if let Some(s) = self.speakers.get_mut(&seed) {
s.eval_scores();
}
let dom = {
let s = self.speakers.get(&seed)?;
[s.score(0), s.score(1), s.score(2)]
};
let mut best_c2 = C2;
let mut winner: Option<u64> = if incumbent.is_none() {
Some(seed)
} else {
None
};
let ids: Vec<u64> = self.speakers.keys().copied().collect();
for id in ids {
if Some(id) == incumbent {
continue;
}
let Some(sp) = self.speakers.get_mut(&id) else {
continue;
};
if sp.paused {
continue;
}
sp.eval_scores();
let c1 = (sp.score(0) / dom[0]).ln();
let c2 = (sp.score(1) / dom[1]).ln();
let c3 = (sp.score(2) / dom[2]).ln();
if c1 > C1 && c2 > C2 && c3 > C3 && c2 > best_c2 {
best_c2 = c2;
winner = Some(id);
}
}
winner
};
match (new_id, self.current_dominant) {
(Some(n), Some(c)) if n == c => None,
(Some(n), _) => {
self.current_dominant = Some(n);
Some(n)
}
_ => None,
}
}
pub fn current_dominant(&self) -> Option<u64> {
self.current_dominant
}
}