use std::net::IpAddr;
use cameras::CameraSource;
use cameras::discover::{
self, DiscoverConfig, DiscoverEvent, DiscoveredCamera, Discovery, try_next_event,
};
use egui::Ui;
pub struct DiscoverySession {
pub(crate) inner: Discovery,
pub cameras: Vec<DiscoveredCamera>,
pub unmatched_hosts: Vec<(IpAddr, String)>,
pub scanned: usize,
pub total: usize,
pub done: bool,
}
pub fn start_discovery(config: DiscoverConfig) -> Result<DiscoverySession, cameras::Error> {
let inner = discover::discover(config)?;
Ok(DiscoverySession {
inner,
cameras: Vec::new(),
unmatched_hosts: Vec::new(),
scanned: 0,
total: 0,
done: false,
})
}
pub fn cancel_discovery(session: DiscoverySession) {
drop(session);
}
pub fn poll_discovery(session: &mut DiscoverySession) {
while let Some(event) = try_next_event(&session.inner) {
match event {
DiscoverEvent::CameraFound(camera) => session.cameras.push(camera),
DiscoverEvent::HostUnmatched { host, server } => {
session.unmatched_hosts.push((host, server));
}
DiscoverEvent::Progress { scanned, total } => {
session.scanned = scanned;
session.total = total;
}
DiscoverEvent::Done => {
session.done = true;
return;
}
DiscoverEvent::HostFound { .. } => {}
_ => {}
}
}
}
pub fn show_discovery(session: &DiscoverySession, ui: &mut Ui) -> Option<DiscoveredCamera> {
show_discovery_status(session, ui);
show_discovery_results(session, ui)
}
pub fn show_discovery_status(session: &DiscoverySession, ui: &mut Ui) {
if session.total > 0 {
ui.label(format!(
"scanned {}/{}",
session.scanned.min(session.total),
session.total
));
} else if session.done {
ui.label("scan finished");
} else {
ui.label("scanning...");
}
}
pub fn show_discovery_results(session: &DiscoverySession, ui: &mut Ui) -> Option<DiscoveredCamera> {
let mut clicked: Option<DiscoveredCamera> = None;
egui::ScrollArea::vertical()
.auto_shrink([false, true])
.max_height(240.0)
.show(ui, |ui| {
if session.cameras.is_empty() {
ui.weak("no cameras yet");
return;
}
for camera in &session.cameras {
let label = format_camera_row(camera);
if ui.button(label).clicked() {
clicked = Some(camera.clone());
}
}
});
clicked
}
fn format_camera_row(camera: &DiscoveredCamera) -> String {
let vendor = camera.vendor.as_deref().unwrap_or("?");
let channel = match camera.channel {
Some(channel) => format!("ch{channel}"),
None => "ch?".to_string(),
};
let url = match &camera.source {
CameraSource::Rtsp { url, .. } => url.as_str(),
_ => "(non-rtsp)",
};
format!("{} [{}] {} ยท {}", camera.host, vendor, channel, url)
}