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}