1use std::fmt;
4use std::sync::Arc;
5use std::time::Duration;
6
7use dioxus::prelude::*;
8
9use crate::AudioData;
10use crate::AudioError;
11use crate::AudioErrorKind;
12use crate::analysis::AudioAnalyser;
13use crate::analysis::WaveformSelection;
14
15#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
16mod web;
17
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
20#[non_exhaustive]
21pub enum PlaybackLoadingPolicy {
22 #[default]
24 Eager,
25 OnPlay,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum PlaybackSourceCrossOrigin {
33 Anonymous,
35 UseCredentials,
37}
38
39#[derive(Clone, Debug, PartialEq, Eq)]
41pub struct PlaybackSourceAlternative {
42 url: String,
43 media_type: Option<String>,
44 cross_origin: Option<PlaybackSourceCrossOrigin>,
45}
46
47impl PlaybackSourceAlternative {
48 pub fn new(url: impl Into<String>) -> Result<Self, AudioError> {
53 let url = url.into();
54 if url.trim().is_empty() || url.chars().any(char::is_control) {
55 return Err(AudioError::new(
56 AudioErrorKind::InvalidConfiguration,
57 "Playback Source URL must be non-empty and contain no control characters",
58 ));
59 }
60
61 Ok(Self {
62 url,
63 media_type: None,
64 cross_origin: None,
65 })
66 }
67
68 pub fn with_media_type(mut self, media_type: impl Into<String>) -> Result<Self, AudioError> {
70 let media_type = media_type.into();
71 if media_type.trim().is_empty() || media_type.chars().any(char::is_control) {
72 return Err(AudioError::new(
73 AudioErrorKind::InvalidConfiguration,
74 "Playback Source media type must be non-empty and contain no control characters",
75 ));
76 }
77 self.media_type = Some(media_type);
78 Ok(self)
79 }
80
81 pub fn url(&self) -> &str {
82 &self.url
83 }
84
85 pub fn media_type(&self) -> Option<&str> {
86 self.media_type.as_deref()
87 }
88
89 pub fn with_cross_origin(mut self, cross_origin: PlaybackSourceCrossOrigin) -> Self {
94 self.cross_origin = Some(cross_origin);
95 self
96 }
97
98 pub fn cross_origin(&self) -> Option<PlaybackSourceCrossOrigin> {
99 self.cross_origin
100 }
101
102 pub fn is_graph_eligible(&self) -> bool {
105 self.cross_origin == Some(PlaybackSourceCrossOrigin::Anonymous)
106 }
107}
108
109#[derive(Clone, Debug, PartialEq, Eq)]
110enum PlaybackSourceInput {
111 AudioData(Arc<AudioData>),
112 Url(Arc<[PlaybackSourceAlternative]>),
113}
114
115#[derive(Clone, Debug, PartialEq, Eq)]
117pub struct PlaybackSource {
118 input: PlaybackSourceInput,
119 loading_policy: PlaybackLoadingPolicy,
120}
121
122impl PlaybackSource {
123 pub fn audio_data(audio: AudioData) -> Self {
124 Self {
125 input: PlaybackSourceInput::AudioData(Arc::new(audio)),
126 loading_policy: PlaybackLoadingPolicy::Eager,
127 }
128 }
129
130 pub fn url(alternative: PlaybackSourceAlternative) -> Self {
131 Self {
132 input: PlaybackSourceInput::Url(Arc::from([alternative])),
133 loading_policy: PlaybackLoadingPolicy::Eager,
134 }
135 }
136
137 pub fn url_alternatives(
139 alternatives: impl IntoIterator<Item = PlaybackSourceAlternative>,
140 ) -> Result<Self, AudioError> {
141 let alternatives: Arc<[PlaybackSourceAlternative]> = alternatives.into_iter().collect();
142 if alternatives.is_empty() {
143 return Err(AudioError::new(
144 AudioErrorKind::InvalidConfiguration,
145 "a URL Playback Source must contain at least one alternative",
146 ));
147 }
148
149 Ok(Self {
150 input: PlaybackSourceInput::Url(alternatives),
151 loading_policy: PlaybackLoadingPolicy::Eager,
152 })
153 }
154
155 pub fn alternatives(&self) -> Option<&[PlaybackSourceAlternative]> {
157 match &self.input {
158 PlaybackSourceInput::AudioData(_) => None,
159 PlaybackSourceInput::Url(alternatives) => Some(alternatives),
160 }
161 }
162
163 pub fn with_loading_policy(mut self, loading_policy: PlaybackLoadingPolicy) -> Self {
164 self.loading_policy = loading_policy;
165 self
166 }
167
168 pub fn loading_policy(&self) -> PlaybackLoadingPolicy {
169 self.loading_policy
170 }
171}
172
173impl From<AudioData> for PlaybackSource {
174 fn from(audio: AudioData) -> Self {
175 Self::audio_data(audio)
176 }
177}
178
179#[derive(Clone, Debug, PartialEq, Eq)]
180#[non_exhaustive]
181pub enum PlaybackStatus {
182 Empty,
183 Loading,
184 Ready,
185 Playing,
186 Paused,
187 Ended,
188 Failed(AudioError),
189}
190
191#[derive(Clone, Debug, PartialEq, Eq)]
193#[non_exhaustive]
194pub enum PlaybackSourceLifecycle {
195 Empty,
196 Dormant,
197 Loading,
198 Playable,
199 Failed,
200}
201
202#[derive(Clone, Copy, Debug, PartialEq, Eq)]
204#[non_exhaustive]
205pub enum PlaybackTransport {
206 Idle,
207 PlayPending,
208 Playing,
209 Paused,
210 Ended,
211}
212
213#[derive(Clone, Copy, Debug, PartialEq, Eq)]
215#[non_exhaustive]
216pub enum PlaybackReadiness {
217 Unavailable,
218 LoadingMetadata,
219 Metadata,
220 Playable,
221 Waiting,
222}
223
224#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
226#[non_exhaustive]
227pub enum PlaybackNetworkActivity {
228 #[default]
230 Inactive,
231 Unknown,
233 Loading,
235 Idle,
237 Stalled,
239}
240
241#[derive(Clone, Copy, Debug, PartialEq, Eq)]
246pub struct PlaybackTimeRange {
247 start: Duration,
248 end: Duration,
249}
250
251impl PlaybackTimeRange {
252 pub fn new(start: Duration, end: Duration) -> Result<Self, PlaybackCommandError> {
253 if end <= start {
254 return Err(PlaybackCommandError(
255 "Playback time range end must be after its start",
256 ));
257 }
258
259 Ok(Self { start, end })
260 }
261
262 pub fn start(self) -> Duration {
263 self.start
264 }
265
266 pub fn end(self) -> Duration {
267 self.end
268 }
269}
270
271#[derive(Clone, Debug, PartialEq, Eq)]
273#[non_exhaustive]
274pub enum PlaybackSourceFailure {
275 GraphIneligible(AudioError),
277 Unsupported(AudioError),
278 Network(AudioError),
279 Decode(AudioError),
280 Unknown(AudioError),
281}
282
283impl PlaybackSourceFailure {
284 pub fn error(&self) -> &AudioError {
285 match self {
286 Self::GraphIneligible(error)
287 | Self::Unsupported(error)
288 | Self::Network(error)
289 | Self::Decode(error)
290 | Self::Unknown(error) => error,
291 }
292 }
293
294 pub fn kind(&self) -> PlaybackSourceFailureKind {
295 match self {
296 Self::GraphIneligible(_) => PlaybackSourceFailureKind::GraphIneligible,
297 Self::Unsupported(_) => PlaybackSourceFailureKind::Unsupported,
298 Self::Network(_) => PlaybackSourceFailureKind::Network,
299 Self::Decode(_) => PlaybackSourceFailureKind::Decode,
300 Self::Unknown(_) => PlaybackSourceFailureKind::Unknown,
301 }
302 }
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307#[non_exhaustive]
308pub enum PlaybackSourceFailureKind {
309 GraphIneligible,
310 Unsupported,
311 Network,
312 Decode,
313 Unknown,
314}
315
316#[derive(Clone, Debug, PartialEq, Eq)]
318pub struct PlaybackAlternativeFailure {
319 alternative: PlaybackSourceAlternative,
320 kind: PlaybackSourceFailureKind,
321}
322
323impl PlaybackAlternativeFailure {
324 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
325 fn new(alternative: PlaybackSourceAlternative, failure: &PlaybackSourceFailure) -> Self {
326 Self {
327 alternative,
328 kind: failure.kind(),
329 }
330 }
331
332 pub fn alternative(&self) -> &PlaybackSourceAlternative {
333 &self.alternative
334 }
335
336 pub fn kind(&self) -> PlaybackSourceFailureKind {
337 self.kind
338 }
339}
340
341#[derive(Clone, Debug, PartialEq, Eq)]
343#[non_exhaustive]
344pub enum PlaybackPlayFailure {
345 InteractionRequired(AudioError),
346 Unknown(AudioError),
347}
348
349#[derive(Clone, Copy, Debug, PartialEq, Eq)]
351#[non_exhaustive]
352pub enum BoundedPlaybackMode {
353 Once,
355 Loop,
357}
358
359#[derive(Clone, Copy, Debug, PartialEq, Eq)]
361#[non_exhaustive]
362pub enum BoundedPlaybackFailure {
363 SeekTimedOut,
365 ActivationRejected,
367 PauseRejected,
369}
370
371#[derive(Clone, Copy, Debug, PartialEq, Eq)]
373#[non_exhaustive]
374pub enum BoundedPlaybackPhase {
375 Seeking,
377 Activating,
379 Active,
381 Paused,
383 Retargeting,
385 Wrapping,
387 Completed,
389 Cancelled,
391 Failed(BoundedPlaybackFailure),
393}
394
395#[derive(Clone, Copy, Debug, PartialEq, Eq)]
397#[non_exhaustive]
398pub enum BoundedPlaybackEvent {
399 Started(BoundedPlaybackMode),
400 Completed,
401 Cancelled,
402 Wrapping,
403 Failed,
404}
405
406#[derive(Clone, Copy, Debug, PartialEq, Eq)]
408#[non_exhaustive]
409pub enum BoundedPlaybackRetarget {
410 PreservePosition,
411 SeekToStart,
412}
413
414impl BoundedPlaybackPhase {
415 pub fn is_enforcing(self) -> bool {
417 matches!(
418 self,
419 Self::Seeking
420 | Self::Activating
421 | Self::Active
422 | Self::Paused
423 | Self::Retargeting
424 | Self::Wrapping
425 )
426 }
427}
428
429#[derive(Clone, Copy, Debug, PartialEq, Eq)]
431pub struct BoundedPlaybackSnapshot {
432 pub range: WaveformSelection,
433 pub mode: BoundedPlaybackMode,
434 pub phase: BoundedPlaybackPhase,
435}
436
437impl PlaybackPlayFailure {
438 pub fn error(&self) -> &AudioError {
439 match self {
440 Self::InteractionRequired(error) | Self::Unknown(error) => error,
441 }
442 }
443}
444
445#[derive(Clone, Copy, Debug, PartialEq, Eq)]
447#[non_exhaustive]
448pub enum PlaybackAudibilityCapability {
449 EffectiveGraphGain,
451 BestEffortMediaElement,
453 Unavailable,
455}
456
457#[derive(Clone, Copy, Debug, PartialEq, Eq)]
459#[non_exhaustive]
460pub enum PlaybackGraphState {
461 NotRequested,
463 AwaitingSource,
465 Preparing,
467 Suspended,
469 Running,
471 InteractionRequired,
473 Unavailable,
475}
476
477#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
479pub struct PlaybackOptions {
480 graph_backed: bool,
481}
482
483impl PlaybackOptions {
484 pub const fn graph_backed() -> Self {
486 Self { graph_backed: true }
487 }
488}
489
490#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
492pub struct PlaybackAudibilityLevel(f64);
493
494impl PlaybackAudibilityLevel {
495 pub const SILENT: Self = Self(0.0);
496 pub const FULL: Self = Self(1.0);
497
498 pub fn new(value: f64) -> Result<Self, PlaybackCommandError> {
500 if !value.is_finite() || !(0.0..=1.0).contains(&value) {
501 return Err(PlaybackCommandError(
502 "audibility level must be finite and between 0 and 1",
503 ));
504 }
505
506 Ok(Self(value))
507 }
508
509 pub fn value(self) -> f64 {
510 self.0
511 }
512}
513
514impl Default for PlaybackAudibilityLevel {
515 fn default() -> Self {
516 Self::FULL
517 }
518}
519
520impl Eq for PlaybackAudibilityLevel {}
522
523#[derive(Clone, Debug, PartialEq, Eq)]
525#[non_exhaustive]
526pub struct PlaybackSnapshot {
527 pub source: PlaybackSourceLifecycle,
528 pub transport: PlaybackTransport,
529 pub readiness: PlaybackReadiness,
530 pub network: PlaybackNetworkActivity,
532 pub buffered: Arc<[PlaybackTimeRange]>,
534 pub seekable: Arc<[PlaybackTimeRange]>,
536 pub selected_alternative: Option<PlaybackSourceAlternative>,
538 pub source_failure: Option<PlaybackSourceFailure>,
540 pub alternative_failures: Arc<[PlaybackAlternativeFailure]>,
542 pub play_failure: Option<PlaybackPlayFailure>,
543 pub bounded: Option<BoundedPlaybackSnapshot>,
545 pub bounded_event: Option<BoundedPlaybackEvent>,
547 pub repeat: bool,
549 pub muted: bool,
551 pub audibility_level: PlaybackAudibilityLevel,
553 pub audibility_capability: PlaybackAudibilityCapability,
555 pub graph: PlaybackGraphState,
557}
558
559impl Default for PlaybackSnapshot {
560 fn default() -> Self {
561 Self {
562 source: PlaybackSourceLifecycle::Empty,
563 transport: PlaybackTransport::Idle,
564 readiness: PlaybackReadiness::Unavailable,
565 network: PlaybackNetworkActivity::Inactive,
566 buffered: Arc::from([]),
567 seekable: Arc::from([]),
568 selected_alternative: None,
569 source_failure: None,
570 alternative_failures: Arc::from([]),
571 play_failure: None,
572 bounded: None,
573 bounded_event: None,
574 repeat: false,
575 muted: false,
576 audibility_level: PlaybackAudibilityLevel::FULL,
577 audibility_capability: PlaybackAudibilityCapability::BestEffortMediaElement,
578 graph: PlaybackGraphState::NotRequested,
579 }
580 }
581}
582
583#[derive(Clone, Debug, PartialEq, Eq)]
584pub struct PlaybackCommandError(&'static str);
585
586impl fmt::Display for PlaybackCommandError {
587 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
588 formatter.write_str(self.0)
589 }
590}
591
592impl std::error::Error for PlaybackCommandError {}
593
594#[derive(Debug)]
595pub struct PlaybackLifecycle {
596 status: PlaybackStatus,
597 snapshot: PlaybackSnapshot,
598 graph_backed: bool,
599}
600
601impl Default for PlaybackLifecycle {
602 fn default() -> Self {
603 Self::new(PlaybackOptions::default())
604 }
605}
606
607impl PlaybackLifecycle {
608 pub fn new(options: PlaybackOptions) -> Self {
609 let mut snapshot = PlaybackSnapshot::default();
610 if options.graph_backed {
611 snapshot.graph = PlaybackGraphState::AwaitingSource;
612 snapshot.audibility_capability = PlaybackAudibilityCapability::EffectiveGraphGain;
613 }
614 Self {
615 status: PlaybackStatus::Empty,
616 snapshot,
617 graph_backed: options.graph_backed,
618 }
619 }
620
621 pub fn status(&self) -> &PlaybackStatus {
622 &self.status
623 }
624
625 pub fn snapshot(&self) -> &PlaybackSnapshot {
626 &self.snapshot
627 }
628
629 pub fn source(&self) -> &PlaybackSourceLifecycle {
630 &self.snapshot.source
631 }
632
633 pub fn transport(&self) -> PlaybackTransport {
634 self.snapshot.transport
635 }
636
637 pub fn readiness(&self) -> PlaybackReadiness {
638 self.snapshot.readiness
639 }
640
641 pub fn network_activity(&self) -> PlaybackNetworkActivity {
642 self.snapshot.network
643 }
644
645 pub fn play_failure(&self) -> Option<&PlaybackPlayFailure> {
646 self.snapshot.play_failure.as_ref()
647 }
648
649 pub fn bounded(&self) -> Option<&BoundedPlaybackSnapshot> {
650 self.snapshot.bounded.as_ref()
651 }
652
653 pub fn start_bounded_once(
655 &mut self,
656 range: WaveformSelection,
657 duration_secs: f64,
658 ) -> Result<(), PlaybackCommandError> {
659 self.start_bounded(range, duration_secs, BoundedPlaybackMode::Once)
660 }
661
662 pub fn start_bounded_loop(
664 &mut self,
665 range: WaveformSelection,
666 duration_secs: f64,
667 ) -> Result<(), PlaybackCommandError> {
668 self.start_bounded(range, duration_secs, BoundedPlaybackMode::Loop)
669 }
670
671 fn start_bounded(
672 &mut self,
673 range: WaveformSelection,
674 duration_secs: f64,
675 mode: BoundedPlaybackMode,
676 ) -> Result<(), PlaybackCommandError> {
677 self.validate_bounded_range(range, duration_secs)?;
678
679 self.snapshot.bounded = Some(BoundedPlaybackSnapshot {
680 range,
681 mode,
682 phase: BoundedPlaybackPhase::Seeking,
683 });
684 self.snapshot.bounded_event = Some(BoundedPlaybackEvent::Started(mode));
685 Ok(())
686 }
687
688 fn validate_bounded_range(
689 &self,
690 range: WaveformSelection,
691 duration_secs: f64,
692 ) -> Result<(), PlaybackCommandError> {
693 if self.snapshot.source != PlaybackSourceLifecycle::Playable {
694 return Err(PlaybackCommandError("audio is not loaded"));
695 }
696 if !duration_secs.is_finite() || duration_secs <= 0.0 {
697 return Err(PlaybackCommandError(
698 "authoritative audio duration is unavailable",
699 ));
700 }
701 if range.is_collapsed() {
702 return Err(PlaybackCommandError("Waveform Selection is collapsed"));
703 }
704 if !range.is_playable_within(duration_secs) {
705 return Err(PlaybackCommandError(
706 "Waveform Selection is outside the Playback duration",
707 ));
708 }
709 Ok(())
710 }
711
712 pub fn retarget_bounded(
718 &mut self,
719 range: WaveformSelection,
720 duration_secs: f64,
721 current_position: f64,
722 ) -> Result<BoundedPlaybackRetarget, PlaybackCommandError> {
723 let previous = self
724 .snapshot
725 .bounded
726 .filter(|bounded| bounded.phase.is_enforcing())
727 .ok_or(PlaybackCommandError("Bounded Playback is not active"))?;
728 self.validate_bounded_range(range, duration_secs)?;
729
730 let preserved = current_position.is_finite()
731 && current_position >= range.start()
732 && current_position < range.end();
733 let phase = if preserved {
734 match self.snapshot.transport {
735 PlaybackTransport::Playing => BoundedPlaybackPhase::Active,
736 PlaybackTransport::PlayPending => BoundedPlaybackPhase::Activating,
737 _ => BoundedPlaybackPhase::Paused,
738 }
739 } else {
740 BoundedPlaybackPhase::Retargeting
741 };
742 self.snapshot.bounded = Some(BoundedPlaybackSnapshot {
743 range,
744 mode: previous.mode,
745 phase,
746 });
747 self.snapshot.bounded_event = None;
748 Ok(if preserved {
749 BoundedPlaybackRetarget::PreservePosition
750 } else {
751 BoundedPlaybackRetarget::SeekToStart
752 })
753 }
754
755 pub fn bounded_seeking(&mut self) {
756 if let Some(bounded) = self.snapshot.bounded.as_mut()
757 && bounded.phase.is_enforcing()
758 {
759 bounded.phase = BoundedPlaybackPhase::Seeking;
760 }
761 }
762
763 pub fn bounded_activating(&mut self) {
764 if let Some(bounded) = self.snapshot.bounded.as_mut()
765 && bounded.phase.is_enforcing()
766 {
767 bounded.phase = BoundedPlaybackPhase::Activating;
768 self.snapshot.bounded_event = None;
769 }
770 }
771
772 pub fn bounded_active(&mut self) {
773 if let Some(bounded) = self.snapshot.bounded.as_mut()
774 && bounded.phase == BoundedPlaybackPhase::Activating
775 {
776 bounded.phase = BoundedPlaybackPhase::Active;
777 self.snapshot.bounded_event = None;
778 }
779 }
780
781 pub fn bounded_paused(&mut self) {
782 if let Some(bounded) = self.snapshot.bounded.as_mut()
783 && bounded.phase.is_enforcing()
784 {
785 bounded.phase = BoundedPlaybackPhase::Paused;
786 self.snapshot.bounded_event = None;
787 }
788 }
789
790 pub fn bounded_retargeting(&mut self) {
791 if let Some(bounded) = self.snapshot.bounded.as_mut()
792 && bounded.phase.is_enforcing()
793 {
794 bounded.phase = BoundedPlaybackPhase::Retargeting;
795 self.snapshot.bounded_event = None;
796 }
797 }
798
799 pub fn bounded_wrapping(&mut self) {
800 if let Some(bounded) = self.snapshot.bounded.as_mut()
801 && bounded.mode == BoundedPlaybackMode::Loop
802 && bounded.phase.is_enforcing()
803 {
804 bounded.phase = BoundedPlaybackPhase::Wrapping;
805 self.snapshot.bounded_event = Some(BoundedPlaybackEvent::Wrapping);
806 }
807 }
808
809 pub fn bounded_completed(&mut self) {
810 if let Some(bounded) = self.snapshot.bounded.as_mut()
811 && bounded.mode == BoundedPlaybackMode::Once
812 && bounded.phase.is_enforcing()
813 {
814 bounded.phase = BoundedPlaybackPhase::Completed;
815 self.status = PlaybackStatus::Paused;
816 self.snapshot.transport = PlaybackTransport::Paused;
817 self.snapshot.play_failure = None;
818 self.snapshot.bounded_event = Some(BoundedPlaybackEvent::Completed);
819 }
820 }
821
822 pub fn bounded_failed(&mut self, failure: BoundedPlaybackFailure) {
823 if let Some(bounded) = self.snapshot.bounded.as_mut()
824 && bounded.phase.is_enforcing()
825 {
826 bounded.phase = BoundedPlaybackPhase::Failed(failure);
827 if self.snapshot.transport != PlaybackTransport::Playing {
828 self.snapshot.transport = PlaybackTransport::Paused;
829 self.status = PlaybackStatus::Paused;
830 }
831 self.snapshot.play_failure = None;
832 self.snapshot.bounded_event = Some(BoundedPlaybackEvent::Failed);
833 }
834 }
835
836 pub fn cancel_bounded(&mut self) {
837 let cancelled = self
838 .snapshot
839 .bounded
840 .is_some_and(|bounded| bounded.phase.is_enforcing());
841 self.snapshot.bounded = None;
842 self.snapshot.bounded_event = cancelled.then_some(BoundedPlaybackEvent::Cancelled);
843 }
844
845 pub fn bounded_cancelled(&mut self) {
846 if let Some(bounded) = self.snapshot.bounded.as_mut()
847 && bounded.phase.is_enforcing()
848 {
849 bounded.phase = BoundedPlaybackPhase::Cancelled;
850 self.snapshot.bounded_event = Some(BoundedPlaybackEvent::Cancelled);
851 }
852 }
853
854 pub fn selected_alternative(&self) -> Option<&PlaybackSourceAlternative> {
855 self.snapshot.selected_alternative.as_ref()
856 }
857
858 pub fn source_failure(&self) -> Option<&PlaybackSourceFailure> {
859 self.snapshot.source_failure.as_ref()
860 }
861
862 pub fn repeat(&self) -> bool {
863 self.snapshot.repeat
864 }
865
866 pub fn set_repeat(&mut self, repeat: bool) {
867 self.snapshot.repeat = repeat;
868 }
869
870 pub fn toggle_repeat(&mut self) {
871 self.snapshot.repeat = !self.snapshot.repeat;
872 }
873
874 pub fn muted(&self) -> bool {
875 self.snapshot.muted
876 }
877
878 pub fn set_muted(&mut self, muted: bool) {
879 self.snapshot.muted = muted;
880 }
881
882 pub fn toggle_muted(&mut self) {
883 self.snapshot.muted = !self.snapshot.muted;
884 }
885
886 pub fn audibility_level(&self) -> PlaybackAudibilityLevel {
887 self.snapshot.audibility_level
888 }
889
890 pub fn set_audibility_level(&mut self, level: f64) -> Result<(), PlaybackCommandError> {
891 self.set_validated_audibility_level(PlaybackAudibilityLevel::new(level)?);
892 Ok(())
893 }
894
895 fn set_validated_audibility_level(&mut self, level: PlaybackAudibilityLevel) {
896 self.snapshot.audibility_level = level;
897 }
898
899 pub fn audibility_capability(&self) -> PlaybackAudibilityCapability {
900 self.snapshot.audibility_capability
901 }
902
903 pub fn graph_state(&self) -> PlaybackGraphState {
904 self.snapshot.graph
905 }
906
907 pub fn graph_preparing(&mut self) {
908 if self.graph_backed && self.snapshot.graph != PlaybackGraphState::Unavailable {
909 self.snapshot.graph = PlaybackGraphState::Preparing;
910 self.snapshot.audibility_capability = PlaybackAudibilityCapability::EffectiveGraphGain;
911 }
912 }
913
914 pub fn graph_awaiting_source(&mut self) {
915 if self.graph_backed && self.snapshot.graph != PlaybackGraphState::Unavailable {
916 self.snapshot.graph = PlaybackGraphState::AwaitingSource;
917 self.snapshot.audibility_capability = PlaybackAudibilityCapability::EffectiveGraphGain;
918 }
919 }
920
921 pub fn direct_audibility(&mut self) {
922 if self.snapshot.graph != PlaybackGraphState::Unavailable {
923 self.snapshot.audibility_capability =
924 PlaybackAudibilityCapability::BestEffortMediaElement;
925 }
926 }
927
928 pub fn graph_suspended(&mut self) {
929 if self.graph_backed
930 && !matches!(
931 self.snapshot.graph,
932 PlaybackGraphState::InteractionRequired | PlaybackGraphState::Unavailable
933 )
934 {
935 self.snapshot.graph = PlaybackGraphState::Suspended;
936 }
937 }
938
939 pub fn graph_running(&mut self) {
940 if self.graph_backed
941 && !matches!(
942 self.snapshot.graph,
943 PlaybackGraphState::InteractionRequired | PlaybackGraphState::Unavailable
944 )
945 {
946 self.snapshot.graph = PlaybackGraphState::Running;
947 }
948 }
949
950 pub fn graph_interaction_required(&mut self, error: AudioError) {
951 if !self.graph_backed
952 || self.snapshot.graph == PlaybackGraphState::Unavailable
953 || !matches!(
954 self.snapshot.source,
955 PlaybackSourceLifecycle::Loading | PlaybackSourceLifecycle::Playable
956 )
957 || !matches!(
958 self.snapshot.transport,
959 PlaybackTransport::PlayPending | PlaybackTransport::Playing
960 )
961 {
962 return;
963 }
964
965 self.status = PlaybackStatus::Failed(error.clone());
966 self.snapshot.transport = if self.snapshot.source == PlaybackSourceLifecycle::Loading {
967 PlaybackTransport::Idle
968 } else {
969 PlaybackTransport::Paused
970 };
971 if self.snapshot.readiness == PlaybackReadiness::Waiting {
972 self.snapshot.readiness = PlaybackReadiness::Metadata;
973 }
974 self.snapshot.play_failure = Some(PlaybackPlayFailure::InteractionRequired(error));
975 self.snapshot.graph = PlaybackGraphState::InteractionRequired;
976 }
977
978 pub fn graph_unavailable(&mut self) {
979 if self.graph_backed {
980 self.snapshot.graph = PlaybackGraphState::Unavailable;
981 self.snapshot.audibility_capability =
982 PlaybackAudibilityCapability::BestEffortMediaElement;
983 }
984 }
985
986 fn clear_source_failures(&mut self) {
987 self.snapshot.source_failure = None;
988 self.snapshot.alternative_failures = Arc::from([]);
989 }
990
991 fn clear_source_observations(&mut self) {
992 self.snapshot.selected_alternative = None;
993 self.clear_source_failures();
994 self.snapshot.play_failure = None;
995 self.clear_attempt_observations();
996 }
997
998 fn clear_attempt_observations(&mut self) {
999 self.snapshot.network = PlaybackNetworkActivity::Inactive;
1000 self.snapshot.buffered = Arc::from([]);
1001 self.snapshot.seekable = Arc::from([]);
1002 }
1003
1004 fn clear_bounded_for_lifecycle_change(&mut self) {
1005 if let Some(bounded) = self.snapshot.bounded.take() {
1006 self.snapshot.bounded_event = bounded
1007 .phase
1008 .is_enforcing()
1009 .then_some(BoundedPlaybackEvent::Cancelled);
1010 }
1011 }
1012
1013 pub fn loading(&mut self) {
1014 self.status = PlaybackStatus::Loading;
1015 self.snapshot.source = PlaybackSourceLifecycle::Loading;
1016 self.snapshot.transport = PlaybackTransport::Idle;
1017 self.snapshot.readiness = PlaybackReadiness::LoadingMetadata;
1018 self.clear_bounded_for_lifecycle_change();
1019 self.clear_source_observations();
1020 }
1021
1022 pub fn dormant(&mut self) {
1023 self.status = PlaybackStatus::Ready;
1024 self.snapshot.source = PlaybackSourceLifecycle::Dormant;
1025 self.snapshot.transport = PlaybackTransport::Idle;
1026 self.snapshot.readiness = PlaybackReadiness::Unavailable;
1027 self.clear_bounded_for_lifecycle_change();
1028 self.clear_source_observations();
1029 }
1030
1031 fn mark_ready_unless_play_rejected(&mut self) {
1038 self.status = match self.snapshot.play_failure.as_ref() {
1039 Some(failure) => PlaybackStatus::Failed(failure.error().clone()),
1040 None => PlaybackStatus::Ready,
1041 };
1042 }
1043
1044 pub fn loaded(&mut self) {
1045 self.mark_ready_unless_play_rejected();
1046 self.snapshot.source = PlaybackSourceLifecycle::Playable;
1047 self.snapshot.readiness = PlaybackReadiness::Metadata;
1048 self.snapshot.bounded_event = None;
1049 self.clear_source_failures();
1050 }
1051
1052 pub fn metadata_loaded(&mut self) {
1053 if self.snapshot.source == PlaybackSourceLifecycle::Loading {
1054 self.snapshot.readiness = PlaybackReadiness::Metadata;
1055 }
1056 }
1057
1058 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1059 fn loading_alternative(&mut self) {
1060 if self.snapshot.source == PlaybackSourceLifecycle::Loading {
1061 self.status = PlaybackStatus::Loading;
1062 self.snapshot.readiness = PlaybackReadiness::LoadingMetadata;
1063 self.clear_source_failures();
1064 self.clear_attempt_observations();
1065 }
1066 }
1067
1068 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1069 fn source_attempt_started(&mut self, url_addressable: bool) {
1070 if self.snapshot.source == PlaybackSourceLifecycle::Loading {
1071 self.snapshot.network = if url_addressable {
1072 PlaybackNetworkActivity::Unknown
1073 } else {
1074 PlaybackNetworkActivity::Inactive
1075 };
1076 self.snapshot.buffered = Arc::from([]);
1077 self.snapshot.seekable = Arc::from([]);
1078 }
1079 }
1080
1081 pub fn url_playable(&mut self, alternative: PlaybackSourceAlternative) {
1082 if self.snapshot.source == PlaybackSourceLifecycle::Loading {
1083 self.mark_ready_unless_play_rejected();
1084 self.snapshot.source = PlaybackSourceLifecycle::Playable;
1085 self.snapshot.readiness = PlaybackReadiness::Playable;
1086 self.snapshot.selected_alternative = Some(alternative);
1087 self.snapshot.bounded_event = None;
1088 self.clear_source_failures();
1089 }
1090 }
1091
1092 pub fn request_play(&mut self) -> Result<(), PlaybackCommandError> {
1093 if !matches!(
1094 self.snapshot.transport,
1095 PlaybackTransport::Idle | PlaybackTransport::Paused | PlaybackTransport::Ended
1096 ) {
1097 return Err(PlaybackCommandError("audio is not ready to play"));
1098 }
1099
1100 match self.snapshot.source {
1101 PlaybackSourceLifecycle::Dormant => {
1102 self.status = PlaybackStatus::Loading;
1103 self.snapshot.source = PlaybackSourceLifecycle::Loading;
1104 self.snapshot.readiness = PlaybackReadiness::LoadingMetadata;
1105 }
1106 PlaybackSourceLifecycle::Loading => {
1107 self.status = PlaybackStatus::Loading;
1108 }
1109 PlaybackSourceLifecycle::Playable => {
1110 if self.snapshot.play_failure.is_some() {
1111 self.status = PlaybackStatus::Ready;
1112 }
1113 }
1114 PlaybackSourceLifecycle::Empty | PlaybackSourceLifecycle::Failed => {
1115 return Err(PlaybackCommandError("audio is not ready to play"));
1116 }
1117 }
1118 self.snapshot.play_failure = None;
1119 self.snapshot.bounded_event = None;
1120 if self.snapshot.graph == PlaybackGraphState::InteractionRequired {
1121 self.snapshot.graph = PlaybackGraphState::Suspended;
1122 }
1123 self.snapshot.transport = PlaybackTransport::PlayPending;
1124 Ok(())
1125 }
1126
1127 pub fn playing(&mut self) {
1128 if !matches!(self.snapshot.source, PlaybackSourceLifecycle::Playable)
1129 || !matches!(
1130 self.snapshot.transport,
1131 PlaybackTransport::PlayPending | PlaybackTransport::Playing
1132 )
1133 {
1134 return;
1135 }
1136 self.status = PlaybackStatus::Playing;
1137 self.snapshot.transport = PlaybackTransport::Playing;
1138 self.snapshot.readiness = PlaybackReadiness::Playable;
1139 self.snapshot.play_failure = None;
1140 self.snapshot.bounded_event = None;
1141 }
1142
1143 pub fn waiting(&mut self) {
1144 if matches!(self.snapshot.source, PlaybackSourceLifecycle::Playable)
1145 && matches!(
1146 self.snapshot.transport,
1147 PlaybackTransport::PlayPending | PlaybackTransport::Playing
1148 )
1149 {
1150 self.snapshot.readiness = PlaybackReadiness::Waiting;
1151 }
1152 }
1153
1154 pub fn playable(&mut self) {
1155 if matches!(self.snapshot.source, PlaybackSourceLifecycle::Playable) {
1156 self.snapshot.readiness = PlaybackReadiness::Playable;
1157 }
1158 }
1159
1160 pub fn network_observed(&mut self, activity: PlaybackNetworkActivity) {
1161 if matches!(
1162 self.snapshot.source,
1163 PlaybackSourceLifecycle::Loading | PlaybackSourceLifecycle::Playable
1164 ) {
1165 self.snapshot.network = activity;
1166 }
1167 }
1168
1169 pub fn ranges_changed(
1174 &mut self,
1175 buffered: impl IntoIterator<Item = PlaybackTimeRange>,
1176 seekable: impl IntoIterator<Item = PlaybackTimeRange>,
1177 ) {
1178 if matches!(
1179 self.snapshot.source,
1180 PlaybackSourceLifecycle::Loading | PlaybackSourceLifecycle::Playable
1181 ) {
1182 self.snapshot.buffered = normalize_time_ranges(buffered);
1183 self.snapshot.seekable = normalize_time_ranges(seekable);
1184 }
1185 }
1186
1187 pub fn paused(&mut self) {
1188 if self.snapshot.bounded.is_none() {
1189 self.snapshot.bounded_event = None;
1190 }
1191 if matches!(
1192 self.snapshot.transport,
1193 PlaybackTransport::PlayPending | PlaybackTransport::Playing
1194 ) {
1195 if self.snapshot.source == PlaybackSourceLifecycle::Loading {
1196 self.snapshot.transport = PlaybackTransport::Idle;
1197 self.status = PlaybackStatus::Loading;
1198 } else {
1199 self.snapshot.transport = PlaybackTransport::Paused;
1200 self.status = PlaybackStatus::Paused;
1201 }
1202 }
1203 }
1204
1205 pub fn stop(&mut self) -> Result<(), PlaybackCommandError> {
1207 if !matches!(self.snapshot.source, PlaybackSourceLifecycle::Playable) {
1208 return Err(PlaybackCommandError("audio is not loaded"));
1209 }
1210
1211 self.status = PlaybackStatus::Ready;
1212 self.snapshot.transport = PlaybackTransport::Idle;
1213 self.snapshot.play_failure = None;
1214 self.clear_bounded_for_lifecycle_change();
1215 Ok(())
1216 }
1217
1218 pub fn ended(&mut self) {
1219 if self.snapshot.transport != PlaybackTransport::Playing {
1220 return;
1221 }
1222 self.status = PlaybackStatus::Ended;
1223 self.snapshot.transport = PlaybackTransport::Ended;
1224 self.snapshot.readiness = PlaybackReadiness::Playable;
1225 }
1226
1227 pub fn play_rejected(&mut self, failure: PlaybackPlayFailure) {
1228 if !matches!(
1229 self.snapshot.source,
1230 PlaybackSourceLifecycle::Loading | PlaybackSourceLifecycle::Playable
1231 ) || !matches!(self.snapshot.transport, PlaybackTransport::PlayPending)
1232 {
1233 return;
1234 }
1235
1236 self.status = PlaybackStatus::Failed(failure.error().clone());
1237 self.snapshot.transport = if self.snapshot.source == PlaybackSourceLifecycle::Loading {
1238 PlaybackTransport::Idle
1239 } else {
1240 PlaybackTransport::Paused
1241 };
1242 if self.snapshot.readiness == PlaybackReadiness::Waiting {
1243 self.snapshot.readiness = PlaybackReadiness::Metadata;
1244 }
1245 self.snapshot.play_failure = Some(failure);
1246 }
1247
1248 pub fn seeked(&mut self, position: f64, duration: f64) {
1249 if matches!(
1250 self.status,
1251 PlaybackStatus::Empty | PlaybackStatus::Loading | PlaybackStatus::Failed(_)
1252 ) {
1253 return;
1254 }
1255 if duration.is_finite() && duration > 0.0 && position >= duration {
1256 self.status = PlaybackStatus::Ended;
1257 self.snapshot.transport = PlaybackTransport::Ended;
1258 } else if matches!(self.status, PlaybackStatus::Ended) {
1259 self.status = PlaybackStatus::Paused;
1260 self.snapshot.transport = PlaybackTransport::Paused;
1261 }
1262 }
1263
1264 pub fn failed(&mut self, error: AudioError) {
1265 self.source_failed(PlaybackSourceFailure::Unknown(error));
1266 }
1267
1268 pub fn source_failed(&mut self, failure: PlaybackSourceFailure) {
1269 let error = failure.error().clone();
1270 self.status = PlaybackStatus::Failed(error.clone());
1271 self.snapshot.source = PlaybackSourceLifecycle::Failed;
1272 self.snapshot.transport = PlaybackTransport::Idle;
1273 self.snapshot.readiness = PlaybackReadiness::Unavailable;
1274 self.snapshot.source_failure = Some(failure);
1275 self.snapshot.alternative_failures = Arc::from([]);
1276 self.snapshot.play_failure = None;
1277 self.clear_bounded_for_lifecycle_change();
1278 self.clear_attempt_observations();
1279 }
1280
1281 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1282 fn source_exhausted(
1283 &mut self,
1284 failure: PlaybackSourceFailure,
1285 alternative_failures: Vec<PlaybackAlternativeFailure>,
1286 ) {
1287 self.source_failed(failure);
1288 self.snapshot.alternative_failures = alternative_failures.into();
1289 }
1290
1291 pub fn unload(&mut self) {
1292 self.status = PlaybackStatus::Empty;
1293 self.snapshot.source = PlaybackSourceLifecycle::Empty;
1294 self.snapshot.transport = PlaybackTransport::Idle;
1295 self.snapshot.readiness = PlaybackReadiness::Unavailable;
1296 self.clear_bounded_for_lifecycle_change();
1297 self.clear_source_observations();
1298 if self.graph_backed && self.snapshot.graph != PlaybackGraphState::Unavailable {
1299 self.snapshot.graph = PlaybackGraphState::AwaitingSource;
1300 }
1301 }
1302}
1303
1304fn normalize_time_ranges(
1305 ranges: impl IntoIterator<Item = PlaybackTimeRange>,
1306) -> Arc<[PlaybackTimeRange]> {
1307 let mut ranges: Vec<_> = ranges.into_iter().collect();
1308 ranges.sort_unstable_by_key(|range| range.start);
1309
1310 let mut normalized: Vec<PlaybackTimeRange> = Vec::with_capacity(ranges.len());
1311 for range in ranges {
1312 if let Some(last) = normalized.last_mut()
1313 && range.start <= last.end
1314 {
1315 last.end = last.end.max(range.end);
1316 } else {
1317 normalized.push(range);
1318 }
1319 }
1320 normalized.into()
1321}
1322
1323pub fn clamp_seek(position: f64, duration: f64) -> f64 {
1325 if !position.is_finite() || !duration.is_finite() || duration <= 0.0 {
1326 return 0.0;
1327 }
1328 position.clamp(0.0, duration)
1329}
1330
1331#[derive(Clone, Copy, PartialEq)]
1332pub struct AudioPlayerController {
1333 status: ReadSignal<PlaybackStatus>,
1334 snapshot: ReadSignal<PlaybackSnapshot>,
1335 position: ReadSignal<Duration>,
1336 duration: ReadSignal<Duration>,
1337 rate: ReadSignal<f64>,
1338 analyser: ReadSignal<Option<AudioAnalyser>>,
1339 play: Callback<(), Result<(), PlaybackCommandError>>,
1340 pause: Callback<(), Result<(), PlaybackCommandError>>,
1341 stop: Callback<(), Result<(), PlaybackCommandError>>,
1342 seek: Callback<Duration>,
1343 skip: Callback<f64>,
1344 play_bounded:
1345 Callback<(WaveformSelection, BoundedPlaybackMode), Result<(), PlaybackCommandError>>,
1346 retarget_bounded: Callback<WaveformSelection, Result<(), PlaybackCommandError>>,
1347 cancel_bounded: Callback<()>,
1348 set_rate: Callback<f64, Result<(), PlaybackCommandError>>,
1349 set_repeat: Callback<bool>,
1350 set_muted: Callback<bool>,
1351 set_audibility_level: Callback<f64, Result<(), PlaybackCommandError>>,
1352}
1353
1354impl AudioPlayerController {
1355 pub fn status(self) -> ReadSignal<PlaybackStatus> {
1356 self.status
1357 }
1358
1359 pub fn snapshot(self) -> ReadSignal<PlaybackSnapshot> {
1360 self.snapshot
1361 }
1362
1363 pub fn position(self) -> ReadSignal<Duration> {
1364 self.position
1365 }
1366
1367 pub fn duration(self) -> ReadSignal<Duration> {
1368 self.duration
1369 }
1370
1371 pub fn rate(self) -> ReadSignal<f64> {
1372 self.rate
1373 }
1374
1375 pub fn analyser(self) -> ReadSignal<Option<AudioAnalyser>> {
1377 self.analyser
1378 }
1379
1380 pub fn repeat(self) -> bool {
1381 self.snapshot.read().repeat
1382 }
1383
1384 pub fn muted(self) -> bool {
1385 self.snapshot.read().muted
1386 }
1387
1388 pub fn audibility_level(self) -> PlaybackAudibilityLevel {
1389 self.snapshot.read().audibility_level
1390 }
1391
1392 pub fn audibility_capability(self) -> PlaybackAudibilityCapability {
1393 self.snapshot.read().audibility_capability
1394 }
1395
1396 pub fn play(self) -> Result<(), PlaybackCommandError> {
1397 self.play.call(())
1398 }
1399
1400 pub fn pause(self) -> Result<(), PlaybackCommandError> {
1401 self.pause.call(())
1402 }
1403
1404 pub fn stop(self) -> Result<(), PlaybackCommandError> {
1406 self.stop.call(())
1407 }
1408
1409 pub fn seek(self, position: Duration) {
1410 self.seek.call(position);
1411 }
1412
1413 pub fn skip(self, seconds: f64) {
1414 self.skip.call(seconds);
1415 }
1416
1417 pub fn play_bounded_once(
1422 self,
1423 selection: WaveformSelection,
1424 ) -> Result<(), PlaybackCommandError> {
1425 self.play_bounded
1426 .call((selection, BoundedPlaybackMode::Once))
1427 }
1428
1429 pub fn play_bounded_loop(
1434 self,
1435 selection: WaveformSelection,
1436 ) -> Result<(), PlaybackCommandError> {
1437 self.play_bounded
1438 .call((selection, BoundedPlaybackMode::Loop))
1439 }
1440
1441 pub fn retarget_bounded_playback(
1446 self,
1447 selection: WaveformSelection,
1448 ) -> Result<(), PlaybackCommandError> {
1449 self.retarget_bounded.call(selection)
1450 }
1451
1452 pub(crate) fn retarget_bounded_after_selection_commit(self, selection: WaveformSelection) {
1453 let enforcing = self
1454 .snapshot
1455 .read()
1456 .bounded
1457 .is_some_and(|bounded| bounded.phase.is_enforcing());
1458 if enforcing && self.retarget_bounded_playback(selection).is_err() {
1459 self.cancel_bounded_playback();
1460 }
1461 }
1462
1463 pub fn cancel_bounded_playback(self) {
1465 self.cancel_bounded.call(());
1466 }
1467
1468 pub fn set_rate(self, rate: f64) -> Result<(), PlaybackCommandError> {
1469 self.set_rate.call(rate)
1470 }
1471
1472 pub fn set_repeat(self, repeat: bool) {
1474 self.set_repeat.call(repeat);
1475 }
1476
1477 pub fn toggle_repeat(self) {
1479 self.set_repeat(!self.repeat());
1480 }
1481
1482 pub fn set_muted(self, muted: bool) {
1484 self.set_muted.call(muted);
1485 }
1486
1487 pub fn toggle_muted(self) {
1489 self.set_muted(!self.muted());
1490 }
1491
1492 pub fn set_audibility_level(self, level: f64) -> Result<(), PlaybackCommandError> {
1497 self.set_audibility_level.call(level)
1498 }
1499}
1500
1501pub fn use_audio_player(
1502 source: ReadSignal<Option<PlaybackSource>>,
1503 initial_duration: Duration,
1504) -> AudioPlayerController {
1505 use_audio_player_with_options(source, initial_duration, PlaybackOptions::default())
1506}
1507
1508pub fn use_audio_player_with_options(
1510 source: ReadSignal<Option<PlaybackSource>>,
1511 initial_duration: Duration,
1512 options: PlaybackOptions,
1513) -> AudioPlayerController {
1514 #[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
1515 {
1516 web::use_web_audio_player(source, initial_duration, options)
1517 }
1518
1519 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1520 {
1521 let _ = source;
1522 use_unsupported_audio_player(initial_duration, options)
1523 }
1524}
1525
1526#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
1527fn use_unsupported_audio_player(
1528 initial_duration: Duration,
1529 options: PlaybackOptions,
1530) -> AudioPlayerController {
1531 let initial_lifecycle = PlaybackLifecycle::new(options);
1532 let mut status = use_signal(|| PlaybackStatus::Empty);
1533 let mut snapshot = use_signal(|| initial_lifecycle.snapshot().clone());
1534 let position = use_signal(|| Duration::ZERO);
1535 let mut duration = use_signal(|| initial_duration);
1536 let rate = use_signal(|| 1.0);
1537 let analyser = use_signal(|| None::<AudioAnalyser>);
1538 let initial_duration = use_memo(use_reactive!(|(initial_duration,)| initial_duration));
1539 use_effect(move || {
1540 duration.set(initial_duration());
1541 let error = AudioError::unsupported();
1542 let preferences = snapshot.peek().clone();
1543 status.set(PlaybackStatus::Failed(error.clone()));
1544 snapshot.set(PlaybackSnapshot {
1545 source: PlaybackSourceLifecycle::Failed,
1546 transport: PlaybackTransport::Idle,
1547 readiness: PlaybackReadiness::Unavailable,
1548 network: PlaybackNetworkActivity::Inactive,
1549 buffered: Arc::from([]),
1550 seekable: Arc::from([]),
1551 selected_alternative: None,
1552 source_failure: Some(PlaybackSourceFailure::Unsupported(error)),
1553 alternative_failures: Arc::from([]),
1554 play_failure: None,
1555 bounded: None,
1556 bounded_event: None,
1557 repeat: preferences.repeat,
1558 muted: preferences.muted,
1559 audibility_level: preferences.audibility_level,
1560 audibility_capability: PlaybackAudibilityCapability::Unavailable,
1561 graph: if preferences.graph == PlaybackGraphState::NotRequested {
1562 PlaybackGraphState::NotRequested
1563 } else {
1564 PlaybackGraphState::Unavailable
1565 },
1566 });
1567 });
1568 let unsupported: Callback<(), Result<(), PlaybackCommandError>> =
1569 use_callback(|()| Err(PlaybackCommandError("audio playback is unsupported")));
1570 let seek = use_callback(|_: Duration| {});
1571 let skip = use_callback(|_: f64| {});
1572 let play_bounded: Callback<
1573 (WaveformSelection, BoundedPlaybackMode),
1574 Result<(), PlaybackCommandError>,
1575 > = use_callback(|_: (WaveformSelection, BoundedPlaybackMode)| {
1576 Err(PlaybackCommandError("audio playback is unsupported"))
1577 });
1578 let retarget_bounded: Callback<WaveformSelection, Result<(), PlaybackCommandError>> =
1579 use_callback(|_: WaveformSelection| {
1580 Err(PlaybackCommandError("audio playback is unsupported"))
1581 });
1582 let cancel_bounded = use_callback(|()| {});
1583 let set_rate: Callback<f64, Result<(), PlaybackCommandError>> =
1584 use_callback(|_: f64| Err(PlaybackCommandError("audio playback is unsupported")));
1585 let mut snapshot_for_repeat = snapshot;
1586 let set_repeat = use_callback(move |repeat: bool| {
1587 snapshot_for_repeat.write().repeat = repeat;
1588 });
1589 let mut snapshot_for_muted = snapshot;
1590 let set_muted = use_callback(move |muted: bool| {
1591 snapshot_for_muted.write().muted = muted;
1592 });
1593 let set_audibility_level: Callback<f64, Result<(), PlaybackCommandError>> =
1594 use_callback(|_: f64| Err(PlaybackCommandError("audio playback is unsupported")));
1595 AudioPlayerController {
1596 status: status.into(),
1597 snapshot: snapshot.into(),
1598 position: position.into(),
1599 duration: duration.into(),
1600 rate: rate.into(),
1601 analyser: analyser.into(),
1602 play: unsupported,
1603 pause: unsupported,
1604 stop: unsupported,
1605 seek,
1606 skip,
1607 play_bounded,
1608 retarget_bounded,
1609 cancel_bounded,
1610 set_rate,
1611 set_repeat,
1612 set_muted,
1613 set_audibility_level,
1614 }
1615}