use bevy::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AudioDevice {
pub id: String,
pub name: String,
pub is_default: bool,
pub sample_rate: Option<u32>,
pub channels: Option<u16>,
}
impl AudioDevice {
pub fn usable(&self) -> bool {
self.sample_rate.is_some()
}
pub fn detail(&self) -> String {
match (self.sample_rate, self.channels) {
(Some(rate), Some(channels)) => format!(
"{rate} Hz · {}",
match channels {
1 => "mono".to_string(),
2 => "stereo".to_string(),
n => format!("{n} channels"),
}
),
(Some(rate), None) => format!("{rate} Hz"),
_ => "cannot be opened".to_string(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CameraDevice {
pub id: String,
pub name: String,
pub is_default: bool,
pub detail: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum Permission {
#[default]
Idle,
Asking,
Ready,
Denied(String),
}
#[derive(Resource, Debug, Default)]
pub struct AudioDevices {
pub microphones: Vec<AudioDevice>,
pub speakers: Vec<AudioDevice>,
pub permission: Permission,
pub(crate) scan_wanted: bool,
}
impl AudioDevices {
pub fn rescan(&mut self) {
self.scan_wanted = true;
}
pub fn request(&mut self) {
if self.permission == Permission::Idle {
self.scan_wanted = true;
}
}
pub fn microphone(&self, id: &str) -> Option<&AudioDevice> {
self.microphones.iter().find(|d| d.id == id)
}
pub fn speaker(&self, id: &str) -> Option<&AudioDevice> {
self.speakers.iter().find(|d| d.id == id)
}
}
#[derive(Resource, Debug, Default)]
pub struct CameraDevices {
pub cameras: Vec<CameraDevice>,
pub permission: Permission,
pub(crate) scan_wanted: bool,
}
impl CameraDevices {
pub fn rescan(&mut self) {
self.scan_wanted = true;
}
pub fn request(&mut self) {
if self.permission == Permission::Idle {
self.scan_wanted = true;
}
}
pub fn camera(&self, id: &str) -> Option<&CameraDevice> {
self.cameras.iter().find(|d| d.id == id)
}
}
pub(crate) fn scan_audio(mut devices: ResMut<AudioDevices>) {
if !devices.scan_wanted {
return;
}
devices.scan_wanted = false;
#[cfg(not(target_arch = "wasm32"))]
{
devices.microphones = super::native::microphones();
devices.speakers = super::native::speakers();
devices.permission = Permission::Ready;
info!(
"bevy_iroh: {} microphone(s), {} speaker(s)",
devices.microphones.len(),
devices.speakers.len()
);
}
#[cfg(target_arch = "wasm32")]
super::web::devices::request_audio(&mut devices);
}
pub(crate) fn scan_cameras(mut devices: ResMut<CameraDevices>) {
if !devices.scan_wanted {
return;
}
devices.scan_wanted = false;
#[cfg(all(feature = "v4l2", target_os = "linux"))]
{
devices.cameras = super::v4l2::cameras();
devices.permission = Permission::Ready;
info!("bevy_iroh: {} camera(s)", devices.cameras.len());
}
#[cfg(target_arch = "wasm32")]
super::web::devices::request_cameras(&mut devices);
#[cfg(not(any(all(feature = "v4l2", target_os = "linux"), target_arch = "wasm32")))]
{
devices.permission = Permission::Ready;
}
}
#[cfg(target_arch = "wasm32")]
pub(crate) fn poll_web_devices(
mut audio: ResMut<AudioDevices>,
mut cameras: ResMut<CameraDevices>,
) {
super::web::devices::poll(
audio.bypass_change_detection(),
cameras.bypass_change_detection(),
);
if super::web::devices::audio_arrived() {
audio.set_changed();
}
if super::web::devices::cameras_arrived() {
cameras.set_changed();
}
}