Skip to main content

dioxus_audio/components/
devices.rs

1use 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}