dioxus_audio/components/
devices.rs1use dioxus::prelude::*;
2
3use crate::AudioInputId;
4use crate::devices::{AudioInputDevices, DeviceListStatus, MicrophonePermission};
5use crate::recorder::{MicrophoneStatus, RecorderStatus};
6
7#[component]
8pub fn AudioInputSelector(
9 devices: AudioInputDevices,
10 #[props(default = false)] disabled: bool,
11 #[props(default)] label: Option<String>,
12) -> Element {
13 let available = devices.devices()();
14 let selected = devices.selected()();
15 let status = devices.status()();
16 let unavailable = matches!(
17 status,
18 DeviceListStatus::Unsupported | DeviceListStatus::Loading
19 );
20 let label = label.unwrap_or_else(|| "Audio input".to_string());
21
22 rsx! {
23 label { class: "dioxus-audio dioxus-audio__device",
24 span { class: "dioxus-audio__device-label",
25 {label.clone()}
26 }
27 select {
28 class: "dioxus-audio__device-select",
29 value: selected.as_ref().map(AudioInputId::as_str).unwrap_or(""),
30 disabled: disabled || unavailable,
31 aria_label: label,
32 onchange: move |event| {
33 let value = event.value();
34 devices.select(if value.is_empty() {
35 None
36 } else {
37 Some(AudioInputId::new(value))
38 });
39 },
40 option { value: "", "System default" }
41 for (index, device) in available.iter().enumerate() {
42 option {
43 key: "{device.id}",
44 value: "{device.id}",
45 selected: selected.as_ref() == Some(&device.id),
46 {device.display_label(index)}
47 }
48 }
49 }
50 match status {
51 DeviceListStatus::Loading => rsx! {
52 span { class: "dioxus-audio__device-help", role: "status", "Finding microphones..." }
53 },
54 DeviceListStatus::Unsupported => rsx! {
55 span { class: "dioxus-audio__device-help", "Audio inputs are unavailable" }
56 },
57 DeviceListStatus::Failed(ref error) => rsx! {
58 span { class: "dioxus-audio__device-help dioxus-audio__device-help--error",
59 role: "alert",
60 "{error}"
61 }
62 },
63 DeviceListStatus::Ready => rsx! {},
64 }
65 }
66 }
67}
68
69#[component]
70pub fn MicrophoneStatusIndicator(
71 status: ReadSignal<MicrophoneStatus>,
72 #[props(default)] on_retry: Option<EventHandler<()>>,
73) -> Element {
74 let status = status();
75 let (state, message) = microphone_message(&status);
76
77 rsx! {
78 div {
79 class: "dioxus-audio dioxus-audio__microphone-status",
80 role: "status",
81 aria_live: "polite",
82 "data-state": state,
83 span { class: "dioxus-audio__microphone-dot" }
84 span { class: "dioxus-audio__microphone-message", "{message}" }
85 if matches!(status.permission, MicrophonePermission::Denied)
86 || matches!(status.recorder, RecorderStatus::Failed(_))
87 {
88 if let Some(on_retry) = on_retry {
89 button {
90 class: "dioxus-audio__retry",
91 r#type: "button",
92 onclick: move |_| on_retry.call(()),
93 "Try again"
94 }
95 }
96 }
97 }
98 }
99}
100
101fn microphone_message(status: &MicrophoneStatus) -> (&'static str, String) {
102 if status.permission == MicrophonePermission::Unsupported {
103 return ("unsupported", "Microphone unavailable".to_string());
104 }
105 if status.permission == MicrophonePermission::Denied {
106 return ("error", "Microphone access denied".to_string());
107 }
108 if status.muted {
109 return ("warning", "Microphone muted by the device".to_string());
110 }
111
112 match &status.recorder {
113 RecorderStatus::Idle if status.permission == MicrophonePermission::Granted => {
114 ("ready", "Microphone ready".to_string())
115 }
116 RecorderStatus::Idle => ("idle", "Microphone not requested".to_string()),
117 RecorderStatus::Preparing => ("pending", "Preparing recording".to_string()),
118 RecorderStatus::Recording => ("recording", "Recording".to_string()),
119 RecorderStatus::Paused => ("paused", "Recording paused".to_string()),
120 RecorderStatus::Stopping => ("pending", "Finishing recording".to_string()),
121 RecorderStatus::Failed(error) => ("error", error.to_string()),
122 }
123}