1use std::{
2 any::Any,
3 env, fmt, mem, ops,
4 path::{Path, PathBuf},
5 sync::Arc,
6 time::Duration,
7};
8
9use zng_app::view_process::ViewAudioHandle;
10use zng_task::channel::{IpcBytes, IpcBytesCast};
11use zng_txt::Txt;
12use zng_unit::{ByteLength, ByteUnits};
13use zng_var::{Var, VarEq, impl_from_and_into_var};
14use zng_view_api::{
15 api_extension::{ApiExtensionId, ApiExtensionPayload},
16 audio::{AudioDecoded, AudioMetadata},
17};
18
19pub use zng_app::view_process::AudioOutputId;
20pub use zng_view_api::audio::{AudioDataFormat, AudioFormat, AudioFormatCapability, AudioTracksMode};
21
22pub trait AudiosExtension: Send + Sync + Any {
28 fn audio(&mut self, limits: &AudioLimits, source: &mut AudioSource, options: &mut AudioOptions) {
38 let _ = (limits, source, options);
39 }
40
41 #[allow(clippy::too_many_arguments)]
54 fn audio_data(
55 &mut self,
56 max_decoded_len: ByteLength,
57 key: &AudioHash,
58 data: &IpcBytes,
59 format: &AudioDataFormat,
60 options: &AudioOptions,
61 ) -> Option<AudioVar> {
62 let _ = (max_decoded_len, key, data, format, options);
63 None
64 }
65
66 fn remove(&mut self, key: &mut AudioHash, purge: &mut bool) -> bool {
73 let _ = (key, purge);
74 true
75 }
76
77 fn clear(&mut self, purge: bool) {
84 let _ = purge;
85 }
86
87 fn available_formats(&self, formats: &mut Vec<AudioFormat>) {
94 let _ = formats;
95 }
96}
97
98pub type AudioVar = Var<AudioTrack>;
104
105#[derive(Debug, Clone)]
109pub struct AudioTrack {
110 pub(crate) cache_key: Option<AudioHash>,
111
112 pub(crate) handle: ViewAudioHandle,
113 pub(crate) meta: AudioMetadata,
114 pub(crate) data: AudioDecoded,
115 tracks: Vec<VarEq<AudioTrack>>,
116
117 error: Txt,
118}
119impl PartialEq for AudioTrack {
120 fn eq(&self, other: &Self) -> bool {
121 self.handle == other.handle
122 && self.cache_key == other.cache_key
123 && self.error == other.error
124 && self.meta == other.meta
125 && self.data == other.data
126 && self.tracks == other.tracks
127 }
128}
129impl AudioTrack {
130 pub fn new_loading() -> Self {
136 Self::new_error(Txt::from_static(""))
137 }
138
139 pub fn new_error(error: Txt) -> Self {
143 let mut s = Self::new(None, ViewAudioHandle::dummy(), AudioMetadata::default(), AudioDecoded::default());
144 s.error = error;
145 s
146 }
147
148 pub(crate) fn new(cache_key: Option<AudioHash>, handle: ViewAudioHandle, meta: AudioMetadata, data: AudioDecoded) -> Self {
149 Self {
150 cache_key,
151 handle,
152 meta,
153 data,
154 tracks: vec![],
155 error: Txt::from_static(""),
156 }
157 }
158
159 pub fn is_loading(&self) -> bool {
161 self.error.is_empty() && !self.data.is_full
162 }
163
164 pub fn is_loaded(&self) -> bool {
168 !self.is_loading()
169 }
170
171 pub fn can_cue(&self) -> bool {
175 !self.view_handle().is_dummy() && !self.is_error()
176 }
177
178 pub fn is_error(&self) -> bool {
180 !self.error.is_empty()
181 }
182
183 pub fn error(&self) -> Option<Txt> {
185 if self.error.is_empty() { None } else { Some(self.error.clone()) }
186 }
187
188 pub fn total_duration(&self) -> Option<Duration> {
194 self.meta.total_duration
195 }
196
197 pub fn channel_count(&self) -> u16 {
199 self.meta.channel_count
200 }
201
202 pub fn sample_rate(&self) -> u32 {
209 self.meta.sample_rate
210 }
211
212 pub fn chunk(&self) -> IpcBytesCast<f32> {
219 self.data.chunk.clone()
220 }
221
222 pub fn chunk_offset(&self) -> usize {
226 self.data.offset
227 }
228
229 pub fn has_tracks(&self) -> bool {
233 !self.tracks.is_empty()
234 }
235
236 pub fn tracks(&self) -> Vec<AudioVar> {
238 self.tracks.iter().map(|e| e.read_only()).collect()
239 }
240
241 pub fn flat_tracks(&self) -> Var<Vec<(VarEq<AudioTrack>, usize)>> {
249 let update_signal = zng_var::var(());
255
256 let mut out = vec![];
258 let mut update_handles = vec![];
259 self.flat_tracks_init(&mut out, update_signal.clone(), &mut update_handles);
260 let out = zng_var::var(out);
261
262 let self_ = self.clone();
264 let signal_weak = update_signal.downgrade();
265 update_signal
266 .bind_modify(&out, move |_, out| {
267 out.clear();
268 update_handles.clear();
269 self_.flat_tracks_init(&mut *out, signal_weak.upgrade().unwrap(), &mut update_handles);
270 })
271 .perm();
272 out.hold(update_signal).perm();
273 out.read_only()
274 }
275 fn flat_tracks_init(&self, out: &mut Vec<(VarEq<AudioTrack>, usize)>, update_signal: Var<()>, handles: &mut Vec<zng_var::VarHandle>) {
276 for track in self.tracks.iter() {
277 Self::flat_tracks_recursive_init(track.clone(), out, update_signal.clone(), handles);
278 }
279 }
280 fn flat_tracks_recursive_init(
281 aud: VarEq<AudioTrack>,
282 out: &mut Vec<(VarEq<AudioTrack>, usize)>,
283 signal: Var<()>,
284 handles: &mut Vec<zng_var::VarHandle>,
285 ) {
286 handles.push(aud.hook(zng_clone_move::clmv!(signal, |_| {
287 signal.update();
288 true
289 })));
290 let i = out.len();
291 out.push((aud.clone(), 0));
292 aud.with(move |aud| {
293 for track in aud.tracks.iter() {
294 Self::flat_tracks_recursive_init(track.clone(), out, signal.clone(), handles);
295 }
296 let len = out.len() - i;
297 out[i].1 = len;
298 });
299 }
300
301 pub fn track_index(&self) -> usize {
303 match &self.meta.parent {
304 Some(p) => p.index,
305 None => 0,
306 }
307 }
308
309 pub fn view_handle(&self) -> &ViewAudioHandle {
311 &self.handle
312 }
313
314 pub fn insert_track(&mut self, track: AudioVar) {
318 let i = track.with(|i| i.track_index());
319 let i = self
320 .tracks
321 .iter()
322 .position(|v| {
323 let track_i = v.with(|i| i.track_index());
324 track_i > i
325 })
326 .unwrap_or(self.tracks.len());
327 self.tracks.insert(i, VarEq(track));
328 }
329
330 pub fn extensions(&self) -> &[(ApiExtensionId, ApiExtensionPayload)] {
332 &self.meta.extensions
333 }
334}
335
336#[derive(Clone, Copy)]
344pub struct AudioHash([u8; 32]);
345impl AudioHash {
346 pub fn compute(data: &[u8]) -> Self {
348 let mut h = Self::hasher();
349 h.update(data);
350 h.finish()
351 }
352
353 pub fn hasher() -> AudioHasher {
355 AudioHasher::default()
356 }
357}
358impl fmt::Debug for AudioHash {
359 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
360 if f.alternate() {
361 f.debug_tuple("AudioHash").field(&self.0).finish()
362 } else {
363 use base64::*;
364 write!(f, "{}", base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(self.0))
365 }
366 }
367}
368impl fmt::Display for AudioHash {
369 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
370 write!(f, "{self:?}")
371 }
372}
373impl std::hash::Hash for AudioHash {
374 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
375 let h64 = [
376 self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5], self.0[6], self.0[7],
377 ];
378 state.write_u64(u64::from_ne_bytes(h64))
379 }
380}
381impl PartialEq for AudioHash {
382 fn eq(&self, other: &Self) -> bool {
383 self.0 == other.0
384 }
385}
386impl Eq for AudioHash {}
387
388pub struct AudioHasher(sha2::Sha512_256);
390impl Default for AudioHasher {
391 fn default() -> Self {
392 use sha2::Digest;
393 Self(sha2::Sha512_256::new())
394 }
395}
396impl AudioHasher {
397 pub fn new() -> Self {
399 Self::default()
400 }
401
402 pub fn update(&mut self, data: &[u8]) {
404 use sha2::Digest;
405
406 const NUM_SAMPLES: usize = 1000;
409 const SAMPLE_CHUNK_SIZE: usize = 1024;
410
411 let total_size = data.len();
412 if total_size == 0 {
413 return;
414 }
415 if total_size < 1000 * 1000 * 4 {
416 return self.0.update(data);
417 }
418
419 let step_size = total_size.checked_div(NUM_SAMPLES).unwrap_or(total_size);
420 for n in 0..NUM_SAMPLES {
421 let start_index = n * step_size;
422 if start_index >= total_size {
423 break;
424 }
425 let end_index = (start_index + SAMPLE_CHUNK_SIZE).min(total_size);
426 let s = &data[start_index..end_index];
427 self.0.update(s);
428 }
429 }
430
431 pub fn finish(self) -> AudioHash {
433 use sha2::Digest;
434 AudioHash(self.0.finalize().as_slice().try_into().unwrap())
438 }
439}
440impl std::hash::Hasher for AudioHasher {
441 fn finish(&self) -> u64 {
442 tracing::warn!("Hasher::finish called for AudioHasher");
443
444 use sha2::Digest;
445 let hash = self.0.clone().finalize();
446 u64::from_le_bytes(hash[..8].try_into().unwrap())
447 }
448
449 fn write(&mut self, bytes: &[u8]) {
450 self.update(bytes);
451 }
452}
453
454#[derive(Clone)]
456#[non_exhaustive]
457pub enum AudioSource {
458 Read(PathBuf),
462 #[cfg(feature = "http")]
468 Download(zng_task::http::Uri, Option<Txt>),
469 Data(AudioHash, IpcBytes, AudioDataFormat),
477
478 Audio(AudioVar),
482}
483impl AudioSource {
484 pub fn from_data(data: IpcBytes, format: AudioDataFormat) -> Self {
486 let mut hasher = AudioHasher::default();
487 hasher.update(&data[..]);
488 let hash = hasher.finish();
489 Self::Data(hash, data, format)
490 }
491
492 pub fn hash128(&self, options: &AudioOptions) -> Option<AudioHash> {
494 match self {
495 AudioSource::Read(p) => Some(Self::hash128_read(p, options)),
496 #[cfg(feature = "http")]
497 AudioSource::Download(u, a) => Some(Self::hash128_download(u, a, options)),
498 AudioSource::Data(h, _, _) => Some(Self::hash128_data(*h, options)),
499 AudioSource::Audio(_) => None,
500 }
501 }
502
503 pub fn hash128_data(data_hash: AudioHash, options: &AudioOptions) -> AudioHash {
507 if !options.tracks.is_empty() {
508 use std::hash::Hash;
509 let mut h = AudioHash::hasher();
510 data_hash.0.hash(&mut h);
511 options.tracks.hash(&mut h);
512 h.finish()
513 } else {
514 data_hash
515 }
516 }
517
518 pub fn hash128_read(path: &Path, options: &AudioOptions) -> AudioHash {
522 use std::hash::Hash;
523 let mut h = AudioHash::hasher();
524 0u8.hash(&mut h);
525 path.hash(&mut h);
526 options.tracks.hash(&mut h);
527 h.finish()
528 }
529
530 #[cfg(feature = "http")]
534 pub fn hash128_download(uri: &zng_task::http::Uri, accept: &Option<Txt>, options: &AudioOptions) -> AudioHash {
535 use std::hash::Hash;
536 let mut h = AudioHash::hasher();
537 1u8.hash(&mut h);
538 uri.hash(&mut h);
539 accept.hash(&mut h);
540 options.tracks.hash(&mut h);
541 h.finish()
542 }
543}
544
545impl PartialEq for AudioSource {
546 fn eq(&self, other: &Self) -> bool {
547 match (self, other) {
548 (Self::Read(l), Self::Read(r)) => l == r,
549 #[cfg(feature = "http")]
550 (Self::Download(lu, la), Self::Download(ru, ra)) => lu == ru && la == ra,
551 (Self::Audio(l), Self::Audio(r)) => l.var_eq(r),
552 (l, r) => {
553 let l_hash = match l {
554 AudioSource::Data(h, _, _) => h,
555 _ => return false,
556 };
557 let r_hash = match r {
558 AudioSource::Data(h, _, _) => h,
559 _ => return false,
560 };
561
562 l_hash == r_hash
563 }
564 }
565 }
566}
567impl fmt::Debug for AudioSource {
568 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
569 if f.alternate() {
570 write!(f, "AudioSource::")?;
571 }
572 match self {
573 AudioSource::Read(p) => f.debug_tuple("Read").field(p).finish(),
574 #[cfg(feature = "http")]
575 AudioSource::Download(u, a) => f.debug_tuple("Download").field(u).field(a).finish(),
576 AudioSource::Data(key, bytes, fmt) => f.debug_tuple("Data").field(key).field(bytes).field(fmt).finish(),
577
578 AudioSource::Audio(_) => write!(f, "Audio(_)"),
579 }
580 }
581}
582
583#[cfg(feature = "http")]
584impl_from_and_into_var! {
585 fn from(uri: zng_task::http::Uri) -> AudioSource {
586 AudioSource::Download(uri, None)
587 }
588 fn from((uri, accept): (zng_task::http::Uri, &'static str)) -> AudioSource {
590 AudioSource::Download(uri, Some(accept.into()))
591 }
592
593 fn from(s: &str) -> AudioSource {
599 use zng_task::http::*;
600 if let Ok(uri) = Uri::try_from(s)
601 && let Some(scheme) = uri.scheme()
602 {
603 if scheme == &uri::Scheme::HTTPS || scheme == &uri::Scheme::HTTP {
604 return AudioSource::Download(uri, None);
605 } else if scheme.as_str() == "file" {
606 return PathBuf::from(uri.path()).into();
607 }
608 }
609 PathBuf::from(s).into()
610 }
611}
612
613#[cfg(not(feature = "http"))]
614impl_from_and_into_var! {
615 fn from(s: &str) -> AudioSource {
619 PathBuf::from(s).into()
620 }
621}
622
623impl_from_and_into_var! {
624 fn from(audio: AudioVar) -> AudioSource {
625 AudioSource::Audio(audio)
626 }
627 fn from(path: PathBuf) -> AudioSource {
628 AudioSource::Read(path)
629 }
630 fn from(path: &Path) -> AudioSource {
631 path.to_owned().into()
632 }
633
634 fn from(s: String) -> AudioSource {
636 s.as_str().into()
637 }
638 fn from(s: Txt) -> AudioSource {
640 s.as_str().into()
641 }
642 fn from(data: &[u8]) -> AudioSource {
646 AudioSource::Data(
647 AudioHash::compute(data),
648 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
649 AudioDataFormat::Unknown,
650 )
651 }
652 fn from<const N: usize>(data: &[u8; N]) -> AudioSource {
656 (&data[..]).into()
657 }
658 fn from(data: IpcBytes) -> AudioSource {
662 AudioSource::Data(AudioHash::compute(&data[..]), data, AudioDataFormat::Unknown)
663 }
664 fn from(data: Vec<u8>) -> AudioSource {
668 IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes").into()
669 }
670 fn from<F: Into<AudioDataFormat>>((data, format): (&[u8], F)) -> AudioSource {
672 AudioSource::Data(
673 AudioHash::compute(data),
674 IpcBytes::from_slice_blocking(data).expect("cannot allocate IpcBytes"),
675 format.into(),
676 )
677 }
678 fn from<F: Into<AudioDataFormat>, const N: usize>((data, format): (&[u8; N], F)) -> AudioSource {
680 (&data[..], format).into()
681 }
682 fn from<F: Into<AudioDataFormat>>((data, format): (Vec<u8>, F)) -> AudioSource {
684 (IpcBytes::from_vec_blocking(data).expect("cannot allocate IpcBytes"), format).into()
685 }
686 fn from<F: Into<AudioDataFormat>>((data, format): (IpcBytes, F)) -> AudioSource {
688 AudioSource::Data(AudioHash::compute(&data[..]), data, format.into())
689 }
690}
691
692#[derive(Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
696pub enum AudioCacheMode {
697 Ignore,
699 Cache,
701 Retry,
703 Reload,
707}
708impl fmt::Debug for AudioCacheMode {
709 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710 if f.alternate() {
711 write!(f, "CacheMode::")?;
712 }
713 match self {
714 Self::Ignore => write!(f, "Ignore"),
715 Self::Cache => write!(f, "Cache"),
716 Self::Retry => write!(f, "Retry"),
717 Self::Reload => write!(f, "Reload"),
718 }
719 }
720}
721impl_from_and_into_var! {
722 fn from(cache: bool) -> AudioCacheMode {
723 if cache { AudioCacheMode::Cache } else { AudioCacheMode::Ignore }
724 }
725}
726
727#[derive(Clone)]
729pub enum AudioSourceFilter<U> {
730 BlockAll,
732 AllowAll,
734 Custom(Arc<dyn Fn(&U) -> bool + Send + Sync>),
736}
737impl<U> AudioSourceFilter<U> {
738 pub fn custom(allow: impl Fn(&U) -> bool + Send + Sync + 'static) -> Self {
742 Self::Custom(Arc::new(allow))
743 }
744
745 pub fn and(self, other: Self) -> Self
754 where
755 U: 'static,
756 {
757 use AudioSourceFilter::*;
758 match (self, other) {
759 (BlockAll, _) | (_, BlockAll) => BlockAll,
760 (AllowAll, _) | (_, AllowAll) => AllowAll,
761 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) && c1(u))),
762 }
763 }
764
765 pub fn or(self, other: Self) -> Self
774 where
775 U: 'static,
776 {
777 use AudioSourceFilter::*;
778 match (self, other) {
779 (AllowAll, _) | (_, AllowAll) => AllowAll,
780 (BlockAll, _) | (_, BlockAll) => BlockAll,
781 (Custom(c0), Custom(c1)) => Custom(Arc::new(move |u| c0(u) || c1(u))),
782 }
783 }
784
785 pub fn allows(&self, item: &U) -> bool {
787 match self {
788 AudioSourceFilter::BlockAll => false,
789 AudioSourceFilter::AllowAll => true,
790 AudioSourceFilter::Custom(f) => f(item),
791 }
792 }
793}
794impl<U> fmt::Debug for AudioSourceFilter<U> {
795 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
796 match self {
797 Self::BlockAll => write!(f, "BlockAll"),
798 Self::AllowAll => write!(f, "AllowAll"),
799 Self::Custom(_) => write!(f, "Custom(_)"),
800 }
801 }
802}
803impl<U: 'static> ops::BitAnd for AudioSourceFilter<U> {
804 type Output = Self;
805
806 fn bitand(self, rhs: Self) -> Self::Output {
807 self.and(rhs)
808 }
809}
810impl<U: 'static> ops::BitOr for AudioSourceFilter<U> {
811 type Output = Self;
812
813 fn bitor(self, rhs: Self) -> Self::Output {
814 self.or(rhs)
815 }
816}
817impl<U: 'static> ops::BitAndAssign for AudioSourceFilter<U> {
818 fn bitand_assign(&mut self, rhs: Self) {
819 *self = mem::replace(self, Self::BlockAll).and(rhs);
820 }
821}
822impl<U: 'static> ops::BitOrAssign for AudioSourceFilter<U> {
823 fn bitor_assign(&mut self, rhs: Self) {
824 *self = mem::replace(self, Self::BlockAll).or(rhs);
825 }
826}
827impl<U> PartialEq for AudioSourceFilter<U> {
828 fn eq(&self, other: &Self) -> bool {
829 match (self, other) {
830 (Self::Custom(l0), Self::Custom(r0)) => Arc::ptr_eq(l0, r0),
831 _ => core::mem::discriminant(self) == core::mem::discriminant(other),
832 }
833 }
834}
835
836pub type PathFilter = AudioSourceFilter<PathBuf>;
846impl PathFilter {
847 pub fn allow_dir(dir: impl AsRef<Path>) -> Self {
849 let dir = absolute_path(dir.as_ref(), || env::current_dir().expect("could not access current dir"), true);
850 PathFilter::custom(move |r| r.starts_with(&dir))
851 }
852
853 pub fn allow_ext(ext: impl Into<std::ffi::OsString>) -> Self {
855 let ext = ext.into();
856 PathFilter::custom(move |r| r.extension().map(|e| e == ext).unwrap_or(false))
857 }
858
859 pub fn allow_current_dir() -> Self {
867 PathFilter::custom(|r| env::current_dir().map(|d| r.starts_with(d)).unwrap_or(false))
868 }
869
870 pub fn allow_exe_dir() -> Self {
872 if let Ok(mut p) = env::current_exe().and_then(dunce::canonicalize)
873 && p.pop()
874 {
875 return Self::allow_dir(p);
876 }
877
878 Self::custom(|_| false)
880 }
881
882 pub fn allow_res() -> Self {
886 Self::allow_dir(zng_env::res(""))
887 }
888}
889
890#[cfg(feature = "http")]
894pub type UriFilter = AudioSourceFilter<zng_task::http::Uri>;
895#[cfg(feature = "http")]
896impl UriFilter {
897 pub fn allow_host(host: impl Into<Txt>) -> Self {
899 let host = host.into();
900 UriFilter::custom(move |u| u.authority().map(|a| a.host() == host).unwrap_or(false))
901 }
902}
903
904impl<F: Fn(&PathBuf) -> bool + Send + Sync + 'static> From<F> for PathFilter {
905 fn from(custom: F) -> Self {
906 PathFilter::custom(custom)
907 }
908}
909
910#[cfg(feature = "http")]
911impl<F: Fn(&zng_task::http::Uri) -> bool + Send + Sync + 'static> From<F> for UriFilter {
912 fn from(custom: F) -> Self {
913 UriFilter::custom(custom)
914 }
915}
916
917#[derive(Clone, Debug, PartialEq)]
919#[non_exhaustive]
920pub struct AudioLimits {
921 pub max_encoded_len: ByteLength,
929 pub max_decoded_len: ByteLength,
933
934 pub allow_path: PathFilter,
936
937 #[cfg(feature = "http")]
939 pub allow_uri: UriFilter,
940}
941impl AudioLimits {
942 pub fn none() -> Self {
944 AudioLimits {
945 max_encoded_len: ByteLength::MAX,
946 max_decoded_len: ByteLength::MAX,
947 allow_path: PathFilter::AllowAll,
948 #[cfg(feature = "http")]
949 allow_uri: UriFilter::AllowAll,
950 }
951 }
952
953 pub fn with_max_encoded_len(mut self, max_encoded_len: impl Into<ByteLength>) -> Self {
957 self.max_encoded_len = max_encoded_len.into();
958 self
959 }
960
961 pub fn with_max_decoded_len(mut self, max_decoded_len: impl Into<ByteLength>) -> Self {
965 self.max_decoded_len = max_decoded_len.into();
966 self
967 }
968
969 pub fn with_allow_path(mut self, allow_path: impl Into<PathFilter>) -> Self {
973 self.allow_path = allow_path.into();
974 self
975 }
976
977 #[cfg(feature = "http")]
981 pub fn with_allow_uri(mut self, allow_url: impl Into<UriFilter>) -> Self {
982 self.allow_uri = allow_url.into();
983 self
984 }
985}
986impl Default for AudioLimits {
987 fn default() -> Self {
991 Self {
992 max_encoded_len: 100.megabytes(),
993 max_decoded_len: 4096.megabytes(),
994 allow_path: PathFilter::allow_res(),
995 #[cfg(feature = "http")]
996 allow_uri: UriFilter::BlockAll,
997 }
998 }
999}
1000impl_from_and_into_var! {
1001 fn from(some: AudioLimits) -> Option<AudioLimits>;
1002}
1003
1004#[derive(Debug, Clone, PartialEq)]
1008#[non_exhaustive]
1009pub struct AudioOptions {
1010 pub cache_mode: AudioCacheMode,
1012 pub tracks: AudioTracksMode,
1014}
1015
1016impl AudioOptions {
1017 pub fn new(cache_mode: AudioCacheMode, tracks: AudioTracksMode) -> Self {
1019 Self { cache_mode, tracks }
1020 }
1021
1022 pub fn cache() -> Self {
1024 Self::new(AudioCacheMode::Cache, AudioTracksMode::empty())
1025 }
1026
1027 pub fn none() -> Self {
1029 Self::new(AudioCacheMode::Ignore, AudioTracksMode::empty())
1030 }
1031}
1032
1033fn absolute_path(path: &Path, base: impl FnOnce() -> PathBuf, allow_escape: bool) -> PathBuf {
1034 if path.is_absolute() {
1035 normalize_path(path)
1036 } else {
1037 let mut dir = base();
1038 if allow_escape {
1039 dir.push(path);
1040 normalize_path(&dir)
1041 } else {
1042 dir.push(normalize_path(path));
1043 dir
1044 }
1045 }
1046}
1047fn normalize_path(path: &Path) -> PathBuf {
1051 use std::path::Component;
1052
1053 let mut components = path.components().peekable();
1054 let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() {
1055 components.next();
1056 PathBuf::from(c.as_os_str())
1057 } else {
1058 PathBuf::new()
1059 };
1060
1061 for component in components {
1062 match component {
1063 Component::Prefix(..) => unreachable!(),
1064 Component::RootDir => {
1065 ret.push(component.as_os_str());
1066 }
1067 Component::CurDir => {}
1068 Component::ParentDir => {
1069 ret.pop();
1070 }
1071 Component::Normal(c) => {
1072 ret.push(c);
1073 }
1074 }
1075 }
1076 ret
1077}