dioxus-audio 0.2.1

Audio recording, playback, analysis, and UI components for Dioxus
Documentation
use dioxus::prelude::*;

use crate::AudioInputId;
use crate::devices::{AudioInputDevices, DeviceListStatus, MicrophonePermission};
use crate::recorder::{MicrophoneStatus, RecorderStatus};

#[component]
pub fn AudioInputSelector(
    devices: AudioInputDevices,
    #[props(default = false)] disabled: bool,
    #[props(default)] label: Option<String>,
) -> Element {
    let available = devices.devices()();
    let selected = devices.selected()();
    let status = devices.status()();
    let unavailable = matches!(
        status,
        DeviceListStatus::Unsupported | DeviceListStatus::Loading
    );
    let label = label.unwrap_or_else(|| "Audio input".to_string());

    rsx! {
        label { class: "dioxus-audio dioxus-audio__device",
            span { class: "dioxus-audio__device-label",
                {label.clone()}
            }
            select {
                class: "dioxus-audio__device-select",
                value: selected.as_ref().map(AudioInputId::as_str).unwrap_or(""),
                disabled: disabled || unavailable,
                aria_label: label,
                onchange: move |event| {
                    let value = event.value();
                    devices.select(if value.is_empty() {
                        None
                    } else {
                        Some(AudioInputId::new(value))
                    });
                },
                option { value: "", "System default" }
                for (index, device) in available.iter().enumerate() {
                    option {
                        key: "{device.id}",
                        value: "{device.id}",
                        selected: selected.as_ref() == Some(&device.id),
                        {device.display_label(index)}
                    }
                }
            }
            match status {
                DeviceListStatus::Loading => rsx! {
                    span { class: "dioxus-audio__device-help", role: "status", "Finding microphones..." }
                },
                DeviceListStatus::Unsupported => rsx! {
                    span { class: "dioxus-audio__device-help", "Audio inputs are unavailable" }
                },
                DeviceListStatus::Failed(ref error) => rsx! {
                    span { class: "dioxus-audio__device-help dioxus-audio__device-help--error",
                        role: "alert",
                        "{error}"
                    }
                },
                DeviceListStatus::Ready => rsx! {},
            }
        }
    }
}

#[component]
pub fn MicrophoneStatusIndicator(
    status: ReadSignal<MicrophoneStatus>,
    #[props(default)] on_retry: Option<EventHandler<()>>,
) -> Element {
    let status = status();
    let (state, message) = microphone_message(&status);

    rsx! {
        div {
            class: "dioxus-audio dioxus-audio__microphone-status",
            role: "status",
            aria_live: "polite",
            "data-state": state,
            span { class: "dioxus-audio__microphone-dot" }
            span { class: "dioxus-audio__microphone-message", "{message}" }
            if matches!(status.permission, MicrophonePermission::Denied)
                || matches!(status.recorder, RecorderStatus::Failed(_))
            {
                if let Some(on_retry) = on_retry {
                    button {
                        class: "dioxus-audio__retry",
                        r#type: "button",
                        onclick: move |_| on_retry.call(()),
                        "Try again"
                    }
                }
            }
        }
    }
}

fn microphone_message(status: &MicrophoneStatus) -> (&'static str, String) {
    if status.permission == MicrophonePermission::Unsupported {
        return ("unsupported", "Microphone unavailable".to_string());
    }
    if status.permission == MicrophonePermission::Denied {
        return ("error", "Microphone access denied".to_string());
    }
    if status.muted {
        return ("warning", "Microphone muted by the device".to_string());
    }

    match &status.recorder {
        RecorderStatus::Idle if status.permission == MicrophonePermission::Granted => {
            ("ready", "Microphone ready".to_string())
        }
        RecorderStatus::Idle => ("idle", "Microphone not requested".to_string()),
        RecorderStatus::Preparing => ("pending", "Preparing recording".to_string()),
        RecorderStatus::Recording => ("recording", "Recording".to_string()),
        RecorderStatus::Paused => ("paused", "Recording paused".to_string()),
        RecorderStatus::Stopping => ("pending", "Finishing recording".to_string()),
        RecorderStatus::Failed(error) => ("error", error.to_string()),
    }
}