Skip to main content

cranpose_services/
media_source.rs

1use std::{
2    fs::File,
3    io::{self, Read, Seek, SeekFrom},
4    path::Path,
5    sync::Arc,
6};
7
8#[cfg(not(target_arch = "wasm32"))]
9use crate::media::PLATFORM_MEDIA_SOURCE;
10
11/// A media stream the platform opened, and what it knows about it.
12pub struct MediaSourceHandle {
13    stream: File,
14    len: Option<u64>,
15    _access: Option<Arc<dyn Send + Sync>>,
16}
17
18impl MediaSourceHandle {
19    /// Wraps a file descriptor and its reported length.
20    pub fn new(stream: File, len: Option<u64>) -> Self {
21        Self {
22            stream,
23            len,
24            _access: None,
25        }
26    }
27
28    /// Keeps a platform access grant alive until the stream closes.
29    pub fn with_access(self, access: impl Send + Sync + 'static) -> Self {
30        Self {
31            _access: Some(Arc::new(access)),
32            ..self
33        }
34    }
35
36    /// Returns the provider's reported byte length, without reading the stream.
37    pub fn byte_len(&self) -> Option<u64> {
38        self.len
39    }
40}
41
42impl std::fmt::Debug for MediaSourceHandle {
43    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        formatter
45            .debug_struct("MediaSourceHandle")
46            .field("len", &self.len)
47            .finish_non_exhaustive()
48    }
49}
50
51impl Read for MediaSourceHandle {
52    fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
53        self.stream.read(output)
54    }
55}
56
57impl Seek for MediaSourceHandle {
58    fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
59        self.stream.seek(position)
60    }
61}
62
63/// Opens a media URI the decoder cannot open for itself.
64///
65/// A `file:` URI is a path and needs nobody, which is why
66/// [`crate::media::open_media_source`] answers those without asking. A `content://` document
67/// belongs to an Android provider and only the platform layer can ask that
68/// provider for a descriptor, so the platform layer registers this and the
69/// decode thread calls it.
70///
71/// A descriptor rather than a reader because that is what both sides really
72/// have: a real file is seekable, a provider that streams hands back a pipe,
73/// and the decoder tells them apart by trying to seek.
74pub trait MediaSourceOpener: Send + Sync {
75    /// Opens `uri` for reading.
76    fn open(&self, uri: &str) -> io::Result<MediaSourceHandle>;
77
78    /// Retains the platform access grant needed to reopen a selected file.
79    fn reference(&self, path: &Path) -> io::Result<String> {
80        Ok(crate::media::uri_for_path(path))
81    }
82
83    /// Derives a child reference from a previously retained folder grant.
84    /// `relative` contains only normal path components inside that folder.
85    fn child_reference(&self, folder: &str, relative: &Path) -> io::Result<String> {
86        let root = crate::media::path_from_uri(folder)
87            .ok_or_else(|| io::Error::new(io::ErrorKind::Unsupported, "folder media references"))?;
88        Ok(crate::media::uri_for_path(&root.join(relative)))
89    }
90}
91
92/// Shared handle to the platform media source opener.
93pub type MediaSourceOpenerRef = Arc<dyn MediaSourceOpener>;
94
95#[cfg(not(target_arch = "wasm32"))]
96pub(crate) fn media_reference(path: &Path) -> io::Result<String> {
97    match PLATFORM_MEDIA_SOURCE.get() {
98        Some(opener) => opener.reference(path),
99        None => Ok(crate::media::uri_for_path(path)),
100    }
101}
102
103#[cfg(not(target_arch = "wasm32"))]
104pub(crate) fn media_child_reference(folder: &str, relative: &Path) -> io::Result<String> {
105    if relative
106        .components()
107        .any(|part| !matches!(part, std::path::Component::Normal(_)))
108    {
109        return Err(io::Error::other(
110            "media path is outside the selected folder",
111        ));
112    }
113    match PLATFORM_MEDIA_SOURCE.get() {
114        Some(opener) => opener.child_reference(folder, relative),
115        None => {
116            let root = crate::media::path_from_uri(folder)
117                .ok_or_else(|| io::Error::other("invalid folder media reference"))?;
118            Ok(crate::media::uri_for_path(&root.join(relative)))
119        }
120    }
121}