dioxus_audio/
components.rs1use dioxus::prelude::*;
4
5mod devices;
6mod player;
7mod recorder;
8mod visualizer;
9mod waveform;
10
11pub use devices::{AudioInputSelector, MicrophoneStatusIndicator};
12pub use player::{
13 AudioPlayer, BoundedPlaybackAnnouncementLabels, PlaybackAnnouncementLabels,
14 PlaybackAudibilitySlider, PlaybackMuteButton, PlaybackPlayPauseButton, PlaybackRateButton,
15 PlaybackRepeatButton, PlaybackSeekSlider, PlaybackSkipButton, PlaybackStatusAnnouncer,
16 PlaybackStopButton,
17};
18pub use recorder::{
19 RecorderAnnouncementLabels, RecorderCancelButton, RecorderClearButton, RecorderControls,
20 RecorderPauseResumeButton, RecorderStartButton, RecorderStatusAnnouncer, RecorderStopButton,
21};
22pub use visualizer::{LevelMeter, LiveWaveform, SpectrumVisualizer};
23pub use waveform::{
24 InteractiveWaveform, NavigableWaveform, Waveform, WaveformPreview, WaveformRangeSelector,
25};
26
27pub static STYLESHEET: Asset = asset!("/assets/dioxus-audio.css");
32
33#[component]
38pub fn AudioStyles() -> Element {
39 rsx! { document::Stylesheet { href: STYLESHEET } }
40}
41
42#[component]
43pub fn AudioScrubber(
44 position_secs: f64,
45 duration_secs: f64,
46 on_seek: EventHandler<f64>,
47 #[props(default = false)] disabled: bool,
48 #[props(default)] aria_label: Option<String>,
49 #[props(default)] value_text: Option<String>,
50) -> Element {
51 let duration_secs = finite_non_negative(duration_secs);
52 let position_secs = finite_non_negative(position_secs).min(duration_secs);
53 let progress = if duration_secs > 0.0 {
54 position_secs / duration_secs * 100.0
55 } else {
56 0.0
57 };
58 let aria_label = aria_label.unwrap_or_else(|| "Seek audio".to_string());
59 let value_text = value_text.unwrap_or_else(|| {
60 format!(
61 "{} of {}",
62 format_accessible_duration(position_secs),
63 format_accessible_duration(duration_secs)
64 )
65 });
66
67 rsx! {
68 div { class: "dioxus-audio dioxus-audio__scrubber",
69 div { class: "dioxus-audio__scrubber-track",
70 div {
71 class: "dioxus-audio__scrubber-fill",
72 style: "width: {progress}%",
73 }
74 }
75 input {
76 class: "dioxus-audio__scrubber-input",
77 r#type: "range",
78 min: "0",
79 max: "{duration_secs}",
80 step: "0.01",
81 value: "{position_secs}",
82 disabled,
83 aria_label,
84 aria_valuetext: value_text,
85 oninput: move |event| {
86 if let Ok(value) = event.value().parse::<f64>() {
87 on_seek.call(value);
88 }
89 },
90 }
91 }
92 }
93}
94
95fn finite_non_negative(value: f64) -> f64 {
96 if value.is_finite() {
97 value.max(0.0)
98 } else {
99 0.0
100 }
101}
102
103fn format_accessible_duration(seconds: f64) -> String {
104 let minutes = (seconds / 60.0).floor() as u64;
105 let seconds = seconds - minutes as f64 * 60.0;
106 let seconds = format_number(seconds);
107
108 if minutes == 0 {
109 return format!(
110 "{seconds} {}",
111 if seconds == "1" { "second" } else { "seconds" }
112 );
113 }
114
115 format!(
116 "{minutes} {}, {seconds} {}",
117 if minutes == 1 { "minute" } else { "minutes" },
118 if seconds == "1" { "second" } else { "seconds" }
119 )
120}
121
122fn format_number(value: f64) -> String {
123 let rounded = (value * 100.0).round() / 100.0;
124 rounded.to_string()
125}