Skip to main content

media_seek/
lib.rs

1#![doc = include_str!("../README.md")]
2
3pub(crate) mod audio;
4mod detect;
5pub(crate) mod video;
6
7pub mod error;
8pub mod index;
9
10use std::future::Future;
11
12pub use error::{Error, Result};
13pub use index::{ByteRange, ContainerIndex};
14
15/// A source capable of fetching an arbitrary byte range from a remote stream.
16///
17/// Implementors provide the HTTP (or other) transport. `media-seek` calls this
18/// only for container formats where the seek index lies outside the initial probe
19/// (e.g. WebM Cues deep in the file, OGG page bisection, AVI idx1 at EOF).
20///
21/// # Examples
22///
23/// ```rust,no_run
24/// use media_seek::RangeFetcher;
25///
26/// struct HttpFetcher {
27///     url: String,
28/// }
29///
30/// impl RangeFetcher for HttpFetcher {
31///     type Error = std::io::Error;
32///
33///     async fn fetch(&self, start: u64, end: u64) -> std::result::Result<Vec<u8>, Self::Error> {
34///         // Issue a Range: bytes={start}-{end} HTTP request and return the body.
35///         todo!()
36///     }
37/// }
38/// ```
39pub trait RangeFetcher {
40    /// The error type produced when a fetch fails.
41    type Error: std::error::Error + Send + Sync + 'static;
42
43    /// Fetches bytes `[start, end]` (inclusive) from the remote stream.
44    ///
45    /// # Arguments
46    ///
47    /// * `start` - First byte to fetch (inclusive).
48    /// * `end` - Last byte to fetch (inclusive).
49    ///
50    /// # Errors
51    ///
52    /// Returns `Self::Error` when the underlying transport fails.
53    ///
54    /// # Returns
55    ///
56    /// The fetched bytes as an owned `Vec<u8>`.
57    fn fetch(&self, start: u64, end: u64) -> impl Future<Output = std::result::Result<Vec<u8>, Self::Error>> + Send;
58}
59
60/// Parses the container index for a stream and returns a [`ContainerIndex`].
61///
62/// Detects the container format from the magic bytes in `probe`, then dispatches
63/// to the appropriate parser. Formats that require additional byte ranges beyond
64/// the probe (WebM Cues outside the initial window, OGG page bisection, AVI idx1,
65/// MPEG-TS PCR binary search) call `fetcher.fetch()` as needed.
66///
67/// # Arguments
68///
69/// * `probe` - The leading bytes of the stream. 512 KB is recommended for reliable
70///   format detection and, where possible, index parsing without extra fetches.
71/// * `total_size` - Total stream size in bytes. Required for binary-search formats
72///   (OGG, AVI, MPEG-TS) and for certain PCM-derived calculations.
73/// * `fetcher` - Provides additional byte ranges when the probe is insufficient.
74///
75/// # Errors
76///
77/// - [`Error::UnsupportedFormat`] — container is MHTML, `None`, or not recognised.
78/// - [`Error::ParseFailed`] — the container index could not be parsed.
79/// - [`Error::FetchFailed`] — a required extra Range request failed.
80///
81/// # Returns
82///
83/// A [`ContainerIndex`] from which [`ContainerIndex::find_byte_range`] returns
84/// the `(content_start_byte, content_end_byte)` byte range covering the
85/// requested time window, plus [`ContainerIndex::init_end_byte`] for the
86/// init-segment prefix.
87pub async fn parse<F: RangeFetcher>(probe: &[u8], total_size: Option<u64>, fetcher: &F) -> Result<ContainerIndex> {
88    tracing::debug!(
89        probe_len = probe.len(),
90        total_size = ?total_size,
91        "⚙️ Parsing container index"
92    );
93
94    let format = detect::detect(probe).ok_or(Error::UnsupportedFormat)?;
95
96    tracing::debug!(format = ?format, "⚙️ Detected container format");
97
98    let result = match format {
99        detect::Format::Mp4 => video::mp4::parse(probe, total_size, fetcher).await,
100        detect::Format::Webm => video::webm::parse(probe, total_size, fetcher).await,
101        detect::Format::Mp3 => audio::mp3::parse(probe, total_size, fetcher).await,
102        detect::Format::Ogg => audio::ogg::parse(probe, total_size, fetcher).await,
103        detect::Format::Flac => audio::flac::parse(probe, total_size),
104        detect::Format::Wav => audio::pcm::parse_wav(probe),
105        detect::Format::Aiff => audio::pcm::parse_aiff(probe),
106        detect::Format::Adts => audio::adts::parse(probe),
107        detect::Format::Flv => video::flv::parse(probe),
108        detect::Format::Avi => video::avi::parse(probe, total_size, fetcher).await,
109        detect::Format::Ts => video::ts::parse(probe, total_size, fetcher).await,
110    };
111
112    if result.is_ok() {
113        tracing::debug!("✅ Container index parsed");
114    }
115    result
116}