use std::{
collections::BTreeSet,
sync::{
Arc,
atomic::{AtomicBool, Ordering::Relaxed},
},
thread::{self, JoinHandle},
time::Duration,
};
use gled_protocol::state::Devices;
use kanal::{Receiver, Sender, unbounded};
use tracing::warn;
use super::ClientId;
use crate::{
audio::{audio_device_labels, device_id::audio_device_id, scan_audio_inputs},
pipeline::output_sender::enttec_usb_pro,
};
const SCAN_INTERVAL: Duration = Duration::from_secs(1);
#[derive(Debug)]
pub(super) struct DeviceScan {
watchers: BTreeSet<ClientId>,
scanning: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
results: (Sender<Devices>, Receiver<Devices>),
latest: Devices,
}
impl Default for DeviceScan {
fn default() -> Self {
Self {
watchers: BTreeSet::new(),
scanning: Arc::default(),
thread: None,
results: unbounded(),
latest: Devices::default(),
}
}
}
impl DeviceScan {
pub(super) fn watch(&mut self, id: ClientId, watch: bool) {
if watch {
self.watchers.insert(id);
} else {
self.watchers.remove(&id);
}
self.update();
}
pub(super) fn disconnect(&mut self, id: ClientId) {
if self.watchers.remove(&id) {
self.update();
}
}
fn update(&mut self) {
let wanted = !self.watchers.is_empty();
self.scanning.store(wanted, Relaxed);
let running = self
.thread
.as_ref()
.is_some_and(|thread| !thread.is_finished());
if wanted && !running {
let scanning = self.scanning.clone();
let results = self.results.0.clone();
self.thread = thread::Builder::new()
.name("gled:devices".to_string())
.spawn(move || scan(&scanning, &results))
.map_err(|err| warn!("Could not start the device scan: {err}"))
.ok();
}
}
pub(super) fn devices(&mut self) -> Devices {
while let Ok(Some(devices)) = self.results.1.try_recv() {
self.latest = devices;
}
if self.watchers.is_empty() {
return Devices::default();
}
Devices {
enttec: enttec_usb_pro::serial_numbers(),
..self.latest.clone()
}
}
}
fn scan(scanning: &AtomicBool, results: &Sender<Devices>) {
while scanning.load(Relaxed) {
let audio_inputs = audio_device_labels(&scan_audio_inputs())
.into_iter()
.map(|(id, label)| (audio_device_id(&id), label))
.collect();
let devices = Devices {
audio_inputs,
..Devices::default()
};
if results.send(devices).is_err() {
return;
}
thread::sleep(SCAN_INTERVAL);
}
}