1use std::cell::RefCell;
4use std::fmt;
5use std::rc::Rc;
6use std::time::Duration;
7
8use dioxus::prelude::*;
9#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
10use wasm_bindgen::JsCast;
11
12use crate::AudioError;
13use crate::analysis::AudioAnalyser;
14use crate::devices::MicrophonePermission;
15use crate::{AudioInputId, RecordedAudio, RecordingChunk, RecordingId};
16
17#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
18mod web;
19
20#[derive(Clone)]
26pub struct RecordingSource {
27 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
28 stream: web_sys::MediaStream,
29 shutdown: RecordingSourceShutdown,
30 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
31 _unsupported: (),
32}
33
34impl fmt::Debug for RecordingSource {
35 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
36 formatter
37 .debug_struct("RecordingSource")
38 .field("shutdown", &self.shutdown)
39 .finish_non_exhaustive()
40 }
41}
42
43#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
45pub enum RecordingSourceShutdown {
46 #[default]
48 PreserveTracks,
49 StopAudioTracks,
53}
54
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum RecordingSourceAvailability {
58 Live,
59 Interrupted,
60}
61
62#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
63impl RecordingSource {
64 pub fn from_media_stream(stream: &web_sys::MediaStream) -> Self {
69 Self {
70 stream: <web_sys::MediaStream as AsRef<wasm_bindgen::JsValue>>::as_ref(stream)
71 .clone()
72 .unchecked_into(),
73 shutdown: RecordingSourceShutdown::PreserveTracks,
74 }
75 }
76
77 pub fn with_shutdown(mut self, shutdown: RecordingSourceShutdown) -> Self {
79 self.shutdown = shutdown;
80 self
81 }
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
85#[non_exhaustive]
86pub enum RecorderStatus {
87 Idle,
88 Preparing,
89 Recording,
90 Paused,
91 Stopping,
92 Failed(AudioError),
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq)]
96pub enum CompletionDisposition {
97 Save,
98 Discard,
99}
100
101#[derive(Clone, Copy, Debug, PartialEq, Eq)]
102enum RecordingTerminalIntent {
103 Save(RecordingCompletionCause),
104 Discard,
105}
106
107impl RecordingTerminalIntent {
108 fn disposition(self) -> CompletionDisposition {
109 match self {
110 Self::Save(_) => CompletionDisposition::Save,
111 Self::Discard => CompletionDisposition::Discard,
112 }
113 }
114
115 fn completion_cause(self) -> Option<RecordingCompletionCause> {
116 match self {
117 Self::Save(cause) => Some(cause),
118 Self::Discard => None,
119 }
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub enum RecordingCompletionCause {
126 Requested,
128 SourceEnded,
130 UnexpectedEnd,
132}
133
134#[derive(Clone, Debug, PartialEq, Eq)]
136#[non_exhaustive]
137pub enum RecordingOutcome {
138 Completed {
139 recording_id: RecordingId,
140 cause: RecordingCompletionCause,
141 },
142 Discarded(RecordingId),
143 Failed {
144 recording_id: RecordingId,
145 error: AudioError,
146 },
147}
148
149impl RecordingOutcome {
150 pub fn recording_id(&self) -> RecordingId {
151 match self {
152 Self::Completed { recording_id, .. } | Self::Discarded(recording_id) => *recording_id,
153 Self::Failed { recording_id, .. } => *recording_id,
154 }
155 }
156}
157
158#[derive(Clone, Debug, PartialEq, Eq)]
163pub struct RecordingChunkDeliveryFailure {
164 recording_id: RecordingId,
165 failed_sequence: u64,
166 error: AudioError,
167}
168
169impl RecordingChunkDeliveryFailure {
170 pub fn recording_id(&self) -> RecordingId {
171 self.recording_id
172 }
173
174 pub fn failed_sequence(&self) -> u64 {
175 self.failed_sequence
176 }
177
178 pub fn error(&self) -> &AudioError {
179 &self.error
180 }
181}
182
183#[derive(Clone, Debug, PartialEq, Eq)]
184pub struct RecorderCommandError {
185 message: &'static str,
186}
187
188impl RecorderCommandError {
189 pub fn message(&self) -> &'static str {
190 self.message
191 }
192}
193
194impl fmt::Display for RecorderCommandError {
195 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
196 formatter.write_str(self.message)
197 }
198}
199
200impl std::error::Error for RecorderCommandError {}
201
202#[derive(Debug)]
203pub struct RecorderLifecycle {
204 status: RecorderStatus,
205 generation: u64,
206 active_recording: Option<RecordingId>,
207 next_chunk_sequence: u64,
208 terminal_intent: RecordingTerminalIntent,
209 finalizing: bool,
210 has_unconsumed_recording: bool,
211}
212
213impl Default for RecorderLifecycle {
214 fn default() -> Self {
215 Self {
216 status: RecorderStatus::Idle,
217 generation: 0,
218 active_recording: None,
219 next_chunk_sequence: 0,
220 terminal_intent: RecordingTerminalIntent::Save(RecordingCompletionCause::Requested),
221 finalizing: false,
222 has_unconsumed_recording: false,
223 }
224 }
225}
226
227impl RecorderLifecycle {
228 pub fn status(&self) -> &RecorderStatus {
229 &self.status
230 }
231
232 pub fn start(&mut self) -> Result<RecordingId, RecorderCommandError> {
233 if self.has_unconsumed_recording {
234 return Err(command_error(
235 "clear the completed recording before starting another",
236 ));
237 }
238 if !matches!(
239 self.status,
240 RecorderStatus::Idle | RecorderStatus::Failed(_)
241 ) {
242 return Err(command_error("recording is already active"));
243 }
244
245 self.generation = self.generation.wrapping_add(1);
246 let recording_id = RecordingId::from_generation(self.generation);
247 self.active_recording = Some(recording_id);
248 self.next_chunk_sequence = 0;
249 self.terminal_intent = RecordingTerminalIntent::Save(RecordingCompletionCause::Requested);
250 self.finalizing = false;
251 self.status = RecorderStatus::Preparing;
252 Ok(recording_id)
253 }
254
255 pub fn started(&mut self, recording_id: RecordingId) -> bool {
256 if self.active_recording == Some(recording_id)
257 && matches!(self.status, RecorderStatus::Preparing)
258 {
259 self.status = RecorderStatus::Recording;
260 true
261 } else {
262 false
263 }
264 }
265
266 pub fn stop(&mut self) -> Result<(), RecorderCommandError> {
267 if !matches!(
268 self.status,
269 RecorderStatus::Recording | RecorderStatus::Paused
270 ) {
271 return Err(command_error(
272 "recording cannot be stopped in its current state",
273 ));
274 }
275
276 self.terminal_intent = RecordingTerminalIntent::Save(RecordingCompletionCause::Requested);
277 self.status = RecorderStatus::Stopping;
278 Ok(())
279 }
280
281 pub fn source_ended(&mut self) -> bool {
283 if !matches!(
284 self.status,
285 RecorderStatus::Recording | RecorderStatus::Paused
286 ) {
287 return false;
288 }
289
290 self.terminal_intent = RecordingTerminalIntent::Save(RecordingCompletionCause::SourceEnded);
291 self.status = RecorderStatus::Stopping;
292 true
293 }
294
295 pub fn completion_cause(&self, recording_id: RecordingId) -> Option<RecordingCompletionCause> {
296 (self.active_recording == Some(recording_id))
297 .then(|| self.terminal_intent.completion_cause())
298 .flatten()
299 }
300
301 pub fn pause(&mut self) -> Result<(), RecorderCommandError> {
302 if !matches!(self.status, RecorderStatus::Recording) {
303 return Err(command_error("recording can only be paused while active"));
304 }
305 self.status = RecorderStatus::Paused;
306 Ok(())
307 }
308
309 pub fn resume(&mut self) -> Result<(), RecorderCommandError> {
310 if !matches!(self.status, RecorderStatus::Paused) {
311 return Err(command_error("recording can only be resumed while paused"));
312 }
313 self.status = RecorderStatus::Recording;
314 Ok(())
315 }
316
317 pub fn request_chunk_boundary(&self) -> Result<(), RecorderCommandError> {
318 if !matches!(
319 self.status,
320 RecorderStatus::Recording | RecorderStatus::Paused
321 ) {
322 return Err(command_error(
323 "a chunk boundary can only be requested while recording or paused",
324 ));
325 }
326 Ok(())
327 }
328
329 pub fn next_chunk_sequence(&mut self, recording_id: RecordingId) -> Option<u64> {
331 let accepts_chunk = self.active_recording == Some(recording_id)
332 && !self.finalizing
333 && (matches!(
334 self.status,
335 RecorderStatus::Recording | RecorderStatus::Paused
336 ) || (matches!(self.status, RecorderStatus::Stopping)
337 && matches!(self.terminal_intent, RecordingTerminalIntent::Save(_))));
338 if !accepts_chunk {
339 return None;
340 }
341
342 let sequence = self.next_chunk_sequence;
343 self.next_chunk_sequence = self.next_chunk_sequence.checked_add(1)?;
344 Some(sequence)
345 }
346
347 pub fn cancel(&mut self) -> Result<(), RecorderCommandError> {
348 match self.status {
349 RecorderStatus::Preparing => {
350 self.active_recording = None;
351 self.status = RecorderStatus::Idle;
352 }
353 RecorderStatus::Recording | RecorderStatus::Paused => {
354 self.terminal_intent = RecordingTerminalIntent::Discard;
355 self.status = RecorderStatus::Stopping;
356 }
357 _ => {
358 return Err(command_error(
359 "recording cannot be cancelled in its current state",
360 ));
361 }
362 }
363
364 Ok(())
365 }
366
367 pub fn begin_finalize(&mut self, recording_id: RecordingId) -> Option<CompletionDisposition> {
368 if self.active_recording != Some(recording_id) || self.finalizing {
369 return None;
370 }
371
372 match self.status {
373 RecorderStatus::Stopping => {}
374 RecorderStatus::Recording | RecorderStatus::Paused => {
375 self.terminal_intent =
376 RecordingTerminalIntent::Save(RecordingCompletionCause::UnexpectedEnd);
377 self.status = RecorderStatus::Stopping;
378 }
379 _ => return None,
380 }
381
382 self.finalizing = true;
383 Some(self.terminal_intent.disposition())
384 }
385
386 pub fn complete_finalize(&mut self, recording_id: RecordingId) -> bool {
387 if self.active_recording != Some(recording_id) || !self.finalizing {
388 return false;
389 }
390
391 self.active_recording = None;
392 self.finalizing = false;
393 self.has_unconsumed_recording =
394 matches!(self.terminal_intent, RecordingTerminalIntent::Save(_));
395 self.status = RecorderStatus::Idle;
396 true
397 }
398
399 pub fn clear_completed(&mut self) {
400 self.has_unconsumed_recording = false;
401 }
402
403 pub fn failed(&mut self, recording_id: RecordingId, error: AudioError) -> bool {
404 if self.active_recording != Some(recording_id) {
405 return false;
406 }
407
408 self.active_recording = None;
409 self.finalizing = false;
410 self.status = RecorderStatus::Failed(error);
411 true
412 }
413
414 pub fn configuration_failed(&mut self, error: AudioError) -> bool {
415 if self.active_recording.is_some()
416 || !matches!(
417 self.status,
418 RecorderStatus::Idle | RecorderStatus::Failed(_)
419 )
420 {
421 return false;
422 }
423 self.status = RecorderStatus::Failed(error);
424 true
425 }
426
427 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
428 fn abandon(&mut self) {
429 self.active_recording = None;
430 self.finalizing = false;
431 }
432}
433
434fn command_error(message: &'static str) -> RecorderCommandError {
435 RecorderCommandError { message }
436}
437
438#[derive(Clone, Debug, PartialEq, Eq)]
440pub enum RecordingConstraint<T> {
441 Ideal(T),
443 Exact(T),
445}
446
447#[derive(Clone, Debug, Default, PartialEq, Eq)]
449pub struct RecordingConstraints {
450 pub channel_count: Option<RecordingConstraint<u32>>,
451 pub sample_rate: Option<RecordingConstraint<u32>>,
452 pub echo_cancellation: Option<RecordingConstraint<bool>>,
453 pub noise_suppression: Option<RecordingConstraint<bool>>,
454 pub latency: Option<RecordingConstraint<Duration>>,
455}
456
457#[derive(Clone, Debug, Default, PartialEq, Eq)]
461pub struct RecorderConstraintCapabilities {
462 pub channel_count: bool,
463 pub sample_rate: bool,
464 pub echo_cancellation: bool,
465 pub noise_suppression: bool,
466 pub latency: bool,
467}
468
469#[derive(Clone, Debug, Default, PartialEq, Eq)]
473pub struct RecordingSourceSettings {
474 pub channel_count: Option<u32>,
475 pub sample_rate: Option<u32>,
476 pub echo_cancellation: Option<bool>,
477 pub noise_suppression: Option<bool>,
478 pub latency: Option<Duration>,
479}
480
481type RecordingChunkHandler = Rc<RefCell<Box<dyn FnMut(RecordingChunk)>>>;
482
483#[derive(Clone)]
485pub struct RecordingChunkDelivery {
486 pub cadence: Duration,
490 on_chunk: RecordingChunkHandler,
491}
492
493impl fmt::Debug for RecordingChunkDelivery {
494 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
495 formatter
496 .debug_struct("RecordingChunkDelivery")
497 .field("cadence", &self.cadence)
498 .finish_non_exhaustive()
499 }
500}
501
502impl PartialEq for RecordingChunkDelivery {
503 fn eq(&self, other: &Self) -> bool {
504 self.cadence == other.cadence && Rc::ptr_eq(&self.on_chunk, &other.on_chunk)
505 }
506}
507
508impl RecordingChunkDelivery {
509 pub fn new(cadence: Duration, on_chunk: impl FnMut(RecordingChunk) + 'static) -> Self {
511 Self {
512 cadence,
513 on_chunk: Rc::new(RefCell::new(Box::new(on_chunk))),
514 }
515 }
516
517 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
518 fn call(&self, chunk: RecordingChunk) {
519 (self.on_chunk.borrow_mut())(chunk);
520 }
521
522 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
523 fn time_slice_millis(&self) -> i32 {
524 i32::try_from(self.cadence.as_millis())
525 .expect("validated Recording Chunk cadence fits in an i32")
526 }
527}
528
529#[derive(Clone, Debug, PartialEq)]
530#[non_exhaustive]
531pub struct RecorderOptions {
532 pub fft_size: u32,
533 pub smoothing: f64,
534 pub peak_interval: Duration,
535 pub constraints: RecordingConstraints,
536 pub mime_types: Vec<String>,
537 pub audio_bits_per_second: Option<u32>,
538 pub chunk_delivery: Option<RecordingChunkDelivery>,
539}
540
541impl Default for RecorderOptions {
542 fn default() -> Self {
543 Self {
544 fft_size: 256,
545 smoothing: 0.8,
546 peak_interval: Duration::from_millis(100),
547 constraints: RecordingConstraints::default(),
548 mime_types: vec![
549 "audio/webm;codecs=opus".to_string(),
550 "audio/webm".to_string(),
551 "audio/mp4".to_string(),
552 ],
553 audio_bits_per_second: None,
554 chunk_delivery: None,
555 }
556 }
557}
558
559impl RecorderOptions {
560 pub fn validate(&self) -> Result<(), AudioError> {
561 if !(32..=32768).contains(&self.fft_size) || !self.fft_size.is_power_of_two() {
562 return Err(AudioError::new(
563 crate::AudioErrorKind::InvalidConfiguration,
564 "fft_size must be a power of two between 32 and 32768",
565 ));
566 }
567 if !(0.0..=1.0).contains(&self.smoothing) {
568 return Err(AudioError::new(
569 crate::AudioErrorKind::InvalidConfiguration,
570 "smoothing must be between 0 and 1",
571 ));
572 }
573 if self.peak_interval.is_zero() {
574 return Err(AudioError::new(
575 crate::AudioErrorKind::InvalidConfiguration,
576 "peak_interval must be greater than zero",
577 ));
578 }
579 if let Some(delivery) = &self.chunk_delivery
580 && (delivery.cadence.as_millis() == 0
581 || delivery.cadence.as_millis() > i32::MAX as u128)
582 {
583 return Err(AudioError::new(
584 crate::AudioErrorKind::InvalidConfiguration,
585 "Recording Chunk cadence must be between 1 millisecond and 2147483647 milliseconds",
586 ));
587 }
588 Ok(())
589 }
590}
591
592#[derive(Clone, Debug, PartialEq, Eq)]
593pub struct MicrophoneStatus {
594 pub permission: MicrophonePermission,
595 pub recorder: RecorderStatus,
596 pub input_device: Option<AudioInputId>,
597 pub muted: bool,
598}
599
600#[derive(Clone, Copy, PartialEq)]
601pub struct AudioRecorder {
602 status: ReadSignal<RecorderStatus>,
603 completed: ReadSignal<Option<RecordedAudio>>,
604 analyser: ReadSignal<Option<AudioAnalyser>>,
605 elapsed: ReadSignal<Duration>,
606 source_availability: ReadSignal<Option<RecordingSourceAvailability>>,
607 microphone: ReadSignal<MicrophoneStatus>,
608 requested_constraints: ReadSignal<Option<RecordingConstraints>>,
609 constraint_capabilities: ReadSignal<Option<RecorderConstraintCapabilities>>,
610 settings: ReadSignal<Option<RecordingSourceSettings>>,
611 media_type: ReadSignal<Option<String>>,
612 outcome: ReadSignal<Option<RecordingOutcome>>,
613 chunk_delivery_failure: ReadSignal<Option<RecordingChunkDeliveryFailure>>,
614 start: Callback<(), Result<(), RecorderCommandError>>,
615 start_with_source: Callback<RecordingSource, Result<(), RecorderCommandError>>,
616 pause: Callback<(), Result<(), RecorderCommandError>>,
617 resume: Callback<(), Result<(), RecorderCommandError>>,
618 request_chunk_boundary: Callback<(), Result<(), RecorderCommandError>>,
619 stop: Callback<(), Result<(), RecorderCommandError>>,
620 cancel: Callback<(), Result<(), RecorderCommandError>>,
621 take_completed: Callback<(), Option<RecordedAudio>>,
622 clear_completed: Callback,
623}
624
625impl AudioRecorder {
626 pub fn status(self) -> ReadSignal<RecorderStatus> {
627 self.status
628 }
629
630 pub fn completed(self) -> ReadSignal<Option<RecordedAudio>> {
631 self.completed
632 }
633
634 pub fn analyser(self) -> ReadSignal<Option<AudioAnalyser>> {
635 self.analyser
636 }
637
638 pub fn elapsed(self) -> ReadSignal<Duration> {
639 self.elapsed
640 }
641
642 pub fn source_availability(self) -> ReadSignal<Option<RecordingSourceAvailability>> {
648 self.source_availability
649 }
650
651 pub fn microphone(self) -> ReadSignal<MicrophoneStatus> {
652 self.microphone
653 }
654
655 pub fn requested_constraints(self) -> ReadSignal<Option<RecordingConstraints>> {
657 self.requested_constraints
658 }
659
660 pub fn constraint_capabilities(self) -> ReadSignal<Option<RecorderConstraintCapabilities>> {
664 self.constraint_capabilities
665 }
666
667 pub fn settings(self) -> ReadSignal<Option<RecordingSourceSettings>> {
669 self.settings
670 }
671
672 pub fn media_type(self) -> ReadSignal<Option<String>> {
674 self.media_type
675 }
676
677 pub fn outcome(self) -> ReadSignal<Option<RecordingOutcome>> {
679 self.outcome
680 }
681
682 pub fn chunk_delivery_failure(self) -> ReadSignal<Option<RecordingChunkDeliveryFailure>> {
684 self.chunk_delivery_failure
685 }
686
687 pub fn start(self) -> Result<(), RecorderCommandError> {
688 self.start.call(())
689 }
690
691 pub fn start_with_source(self, source: RecordingSource) -> Result<(), RecorderCommandError> {
696 self.start_with_source.call(source)
697 }
698
699 pub fn pause(self) -> Result<(), RecorderCommandError> {
700 self.pause.call(())
701 }
702
703 pub fn resume(self) -> Result<(), RecorderCommandError> {
704 self.resume.call(())
705 }
706
707 pub fn request_chunk_boundary(self) -> Result<(), RecorderCommandError> {
713 self.request_chunk_boundary.call(())
714 }
715
716 pub fn stop(self) -> Result<(), RecorderCommandError> {
717 self.stop.call(())
718 }
719
720 pub fn cancel(self) -> Result<(), RecorderCommandError> {
721 self.cancel.call(())
722 }
723
724 pub fn clear_completed(self) {
725 self.clear_completed.call(());
726 }
727
728 pub fn take_completed(self) -> Option<RecordedAudio> {
730 self.take_completed.call(())
731 }
732}
733
734pub fn is_recorder_mime_type_supported(mime_type: &str) -> bool {
739 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
740 {
741 web_sys::MediaRecorder::is_type_supported(mime_type)
742 }
743
744 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
745 {
746 let _ = mime_type;
747 false
748 }
749}
750
751pub fn use_audio_recorder(
753 options: RecorderOptions,
754 selected_input: ReadSignal<Option<AudioInputId>>,
755) -> AudioRecorder {
756 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
757 {
758 web::use_web_audio_recorder(options, selected_input)
759 }
760
761 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
762 {
763 let _ = (options, selected_input);
764 use_unsupported_audio_recorder()
765 }
766}
767
768#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
769fn use_unsupported_audio_recorder() -> AudioRecorder {
770 let error = AudioError::unsupported();
771 let mut status = use_signal(|| RecorderStatus::Idle);
772 let completed = use_signal(|| None::<RecordedAudio>);
773 let analyser = use_signal(|| None::<AudioAnalyser>);
774 let elapsed = use_signal(|| Duration::ZERO);
775 let source_availability = use_signal(|| None::<RecordingSourceAvailability>);
776 let requested_constraints = use_signal(|| None::<RecordingConstraints>);
777 let constraint_capabilities = use_signal(|| None::<RecorderConstraintCapabilities>);
778 let settings = use_signal(|| None::<RecordingSourceSettings>);
779 let media_type = use_signal(|| None::<String>);
780 let outcome = use_signal(|| None::<RecordingOutcome>);
781 let chunk_delivery_failure = use_signal(|| None::<RecordingChunkDeliveryFailure>);
782 let mut microphone = use_signal(|| MicrophoneStatus {
783 permission: MicrophonePermission::Unknown,
784 recorder: RecorderStatus::Idle,
785 input_device: None,
786 muted: false,
787 });
788 use_effect(move || {
789 let status_error = RecorderStatus::Failed(error.clone());
790 status.set(status_error.clone());
791 microphone.set(MicrophoneStatus {
792 permission: MicrophonePermission::Unsupported,
793 recorder: status_error,
794 input_device: None,
795 muted: false,
796 });
797 });
798 let unsupported: Callback<(), Result<(), RecorderCommandError>> = use_callback(|()| {
799 Err(command_error(
800 "audio recording is unsupported on this platform",
801 ))
802 });
803 let unsupported_source: Callback<RecordingSource, Result<(), RecorderCommandError>> =
804 use_callback(|_| {
805 Err(command_error(
806 "audio recording is unsupported on this platform",
807 ))
808 });
809 let mut completed_to_clear = completed;
810 let clear_completed = use_callback(move |()| completed_to_clear.set(None));
811 let mut completed_to_take = completed;
812 let take_completed = use_callback(move |()| completed_to_take.write().take());
813
814 AudioRecorder {
815 status: status.into(),
816 completed: completed.into(),
817 analyser: analyser.into(),
818 elapsed: elapsed.into(),
819 source_availability: source_availability.into(),
820 microphone: microphone.into(),
821 requested_constraints: requested_constraints.into(),
822 constraint_capabilities: constraint_capabilities.into(),
823 settings: settings.into(),
824 media_type: media_type.into(),
825 outcome: outcome.into(),
826 chunk_delivery_failure: chunk_delivery_failure.into(),
827 start: unsupported,
828 start_with_source: unsupported_source,
829 pause: unsupported,
830 resume: unsupported,
831 request_chunk_boundary: unsupported,
832 stop: unsupported,
833 cancel: unsupported,
834 take_completed,
835 clear_completed,
836 }
837}