//! Raw audio input data streams and sources. //! //! [`Input`]s in Songbird are based on [symphonia], which provides demuxing, //! decoding and management of synchronous byte sources (i.e., any items which //! `impl` [`Read`]). //! //! Songbird adds support for the Opus codec to symphonia via [`OpusDecoder`], //! the [DCA1] file format via [`DcaReader`], and a simple PCM adapter via [`RawReader`]; //! the [format] and [codec registries] in [`codecs`] install these on top of those //! enabled in your `Cargo.toml` when you include symphonia. //! //! ## Common sources //! * Any owned byte slice: `&'static [u8]`, `Bytes`, or `Vec`, //! * [`File`] offers a lazy way to open local audio files, //! * [`HttpRequest`] streams a given file from a URL using the reqwest HTTP library, //! * [`YoutubeDl`] uses `yt-dlp` (or any other `youtube-dl`-like program) to scrape //! a target URL for a usable audio stream, before opening an [`HttpRequest`]. //! //! ## Adapters //! Songbird includes several adapters to make developing your own inputs easier: //! * [`cached::*`], which allow seeking and shared caching of an input stream (storing //! it in memory in a variety of formats), //! * [`ChildContainer`] for managing audio given by a process chain, //! * [`RawAdapter`], for feeding in a synchronous `f32`-PCM stream, and //! * [`AsyncAdapterStream`], for passing bytes from an `AsyncRead` (`+ AsyncSeek`) stream //! into the mixer. //! //! ## Opus frame passthrough. //! Some sources, such as [`Compressed`] or any WebM/Opus/DCA file, support //! direct frame passthrough to the driver. This lets you directly send the //! audio data you have *without decoding, re-encoding, or mixing*. In many //! cases, this can greatly reduce the CPU cost required by the driver. //! //! This functionality requires that: //! * only one track is active (including paused tracks), //! * that track's input supports direct Opus frame reads, //! * this input's frames are all sized to 20ms. //! * and that track's volume is set to `1.0`. //! //! [`Input`]s which are almost suitable but which have **any** illegal frames will be //! blocked from passthrough to prevent glitches such as repeated encoder frame gaps. //! //! [symphonia]: https://docs.rs/symphonia //! [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html //! [`Compressed`]: cached::Compressed //! [DCA1]: https://github.com/bwmarrin/dca //! [`registry::*`]: registry //! [`cached::*`]: cached //! [`OpusDecoder`]: codecs::OpusDecoder //! [`DcaReader`]: codecs::DcaReader //! [`RawReader`]: codecs::RawReader //! [format]: codecs::get_probe //! [codec registries]: codecs::get_codec_registry mod adapters; mod audiostream; pub mod codecs; mod compose; mod error; #[cfg(test)] pub mod input_tests; mod live_input; mod metadata; mod parsed; mod sources; pub mod utils; pub use self::{ adapters::*, audiostream::*, compose::*, error::*, live_input::*, metadata::*, parsed::*, sources::*, }; pub use symphonia_core as core; use std::{error::Error, io::Cursor}; use symphonia_core::{codecs::CodecRegistry, probe::Probe}; use tokio::runtime::Handle as TokioHandle; /// An audio source, which can be live or lazily initialised. /// /// This can be created from a wide variety of sources: /// * Any owned byte slice: `&'static [u8]`, `Bytes`, or `Vec`, /// * [`File`] offers a lazy way to open local audio files, /// * [`HttpRequest`] streams a given file from a URL using the reqwest HTTP library, /// * [`YoutubeDl`] uses `yt-dlp` (or any other `youtube-dl`-like program) to scrape /// a target URL for a usable audio stream, before opening an [`HttpRequest`]. /// /// Any [`Input`] (or struct with `impl Into`) can also be made into a [`Track`] via /// `From`/`Into`. /// /// # Example /// /// ``` /// # use tokio::runtime; /// # /// # let basic_rt = runtime::Builder::new_current_thread().enable_io().build().unwrap(); /// # basic_rt.block_on(async { /// use songbird::{ /// driver::Driver, /// input::{codecs::*, Compose, Input, MetadataError, YoutubeDl}, /// tracks::Track, /// }; /// // Inputs are played using a `Driver`, or `Call`. /// let mut driver = Driver::new(Default::default()); /// /// // Lazy inputs take very little resources, and don't occupy any resources until we /// // need to play them (by default). /// let mut lazy = YoutubeDl::new( /// reqwest::Client::new(), /// // Referenced under CC BY-NC-SA 3.0 -- https://creativecommons.org/licenses/by-nc-sa/3.0/ /// "https://cloudkicker.bandcamp.com/track/94-days".to_string(), /// ); /// let lazy_c = lazy.clone(); /// /// // With sources like `YoutubeDl`, we can get metadata from, e.g., a track's page. /// let aux_metadata = lazy.aux_metadata().await.unwrap(); /// assert_eq!(aux_metadata.track, Some("94 Days".to_string())); /// /// // Once we pass an `Input` to the `Driver`, we can only remotely control it via /// // a `TrackHandle`. /// let handle = driver.play_input(lazy.into()); /// /// // We can also modify some of its initial state via `Track`s. /// let handle = driver.play(Track::from(lazy_c).volume(0.5).pause()); /// /// // In-memory sources like `Vec`, or `&'static [u8]` are easy to use, and only take a /// // little time for the mixer to parse their headers. /// // You can also use the adapters in `songbird::input::cached::*`to keep a source /// // from the Internet, HTTP, or a File in-memory *and* share it among calls. /// let in_memory = include_bytes!("../../resources/ting.mp3"); /// let mut in_memory_input = in_memory.into(); /// /// // This source is live... /// assert!(matches!(in_memory_input, Input::Live(..))); /// // ...but not yet playable, and we can't access its `Metadata`. /// assert!(!in_memory_input.is_playable()); /// assert!(matches!(in_memory_input.metadata(), Err(MetadataError::NotParsed))); /// /// // If we want to inspect metadata (and we can't use AuxMetadata for any reason), we have /// // to parse the track ourselves. /// // /// // We can access it on a live track using `TrackHandle::action()`. /// in_memory_input = in_memory_input /// .make_playable_async(get_codec_registry(), get_probe()) /// .await /// .expect("WAV support is included, and this file is good!"); /// /// // Symphonia's metadata can be difficult to use: prefer `AuxMetadata` when you can! /// use symphonia_core::meta::{StandardTagKey, Value}; /// let mut metadata = in_memory_input.metadata(); /// let meta = metadata.as_mut().unwrap(); /// let mut probed = meta.probe.get().unwrap(); /// /// let track_name = probed /// .current().unwrap() /// .tags().iter().filter(|v| v.std_key == Some(StandardTagKey::TrackTitle)) /// .next().unwrap(); /// if let Value::String(s) = &track_name.value { /// assert_eq!(s, "Ting!"); /// } else { panic!() }; /// /// // ...and these are played like any other input. /// let handle = driver.play_input(in_memory_input); /// # }); /// ``` /// /// [`Track`]: crate::tracks::Track pub enum Input { /// A byte source which is not yet initialised. /// /// When a parent track is either played or explicitly readied, the inner [`Compose`] /// is used to create an [`Input::Live`]. Lazy( /// A trait object which can be used to (re)create a usable byte stream. Box, ), /// An initialised byte source. /// /// This contains a raw byte stream, the lazy initialiser that was used, /// as well as any symphonia-specific format data and/or hints. Live( /// The byte source, plus symphonia-specific data. LiveInput, /// The struct used to initialise this source, if available. /// /// This is used to recreate the stream when a source does not support /// backward seeking, if present. Option>, ), } impl Input { /// Requests auxiliary metadata which can be accessed without parsing the file. /// /// This method will never be called by songbird but allows, for instance, access to metadata /// which might only be visible to a web crawler, e.g., uploader or source URL. /// /// This requires that the [`Input`] has a [`Compose`] available to use, otherwise it /// will always fail with [`AudioStreamError::Unsupported`]. pub async fn aux_metadata(&mut self) -> Result { match self { Self::Lazy(ref mut composer) | Self::Live(_, Some(ref mut composer)) => composer.aux_metadata().await.map_err(Into::into), Self::Live(_, None) => Err(AuxMetadataError::NoCompose), } } /// Tries to get any information about this audio stream acquired during parsing. /// /// Only exists when this input is both [`Self::Live`] and has been fully parsed. /// In general, you probably want to use [`Self::aux_metadata`]. pub fn metadata(&mut self) -> Result, MetadataError> { if let Self::Live(live, _) = self { live.metadata() } else { Err(MetadataError::NotLive) } } /// Initialises (but does not parse) an [`Input::Lazy`] into an [`Input::Live`], /// placing blocking I/O on the current thread. /// /// This requires a [`TokioHandle`] to a tokio runtime to spawn any `async` sources. /// /// *This is a blocking operation. If you wish to use this from an async task, you /// must do so via [`Self::make_live_async`].* /// /// This is a no-op for an [`Input::Live`]. pub fn make_live(self, handle: &TokioHandle) -> Result { if let Self::Lazy(mut lazy) = self { let (created, lazy) = if lazy.should_create_async() { let (tx, rx) = flume::bounded(1); handle.spawn(async move { let out = lazy.create_async().await; drop(tx.send_async((out, lazy))); }); rx.recv().map_err(|_| { let err_msg: Box = "async Input create handler panicked".into(); AudioStreamError::Fail(err_msg) })? } else { (lazy.create(), lazy) }; Ok(Self::Live(LiveInput::Raw(created?), Some(lazy))) } else { Ok(self) } } /// Initialises (but does not parse) an [`Input::Lazy`] into an [`Input::Live`], /// placing blocking I/O on the a `spawn_blocking` executor. /// /// This is a no-op for an [`Input::Live`]. pub async fn make_live_async(self) -> Result { if let Self::Lazy(mut lazy) = self { let (created, lazy) = if lazy.should_create_async() { (lazy.create_async().await, lazy) } else { tokio::task::spawn_blocking(move || (lazy.create(), lazy)) .await .map_err(|_| { let err_msg: Box = "synchronous Input create handler panicked".into(); AudioStreamError::Fail(err_msg) })? }; Ok(Self::Live(LiveInput::Raw(created?), Some(lazy))) } else { Ok(self) } } /// Initialises and parses an [`Input::Lazy`] into an [`Input::Live`], /// placing blocking I/O on the current thread. /// /// This requires a [`TokioHandle`] to a tokio runtime to spawn any `async` sources. /// If you can't access one, then consider manually using [`LiveInput::promote`]. /// /// *This is a blocking operation. Symphonia uses standard library I/O (e.g., [`Read`], [`Seek`]). /// If you wish to use this from an async task, you must do so within `spawn_blocking`.* /// /// [`Read`]: https://doc.rust-lang.org/std/io/trait.Read.html /// [`Seek`]: https://doc.rust-lang.org/std/io/trait.Seek.html pub fn make_playable( self, codecs: &CodecRegistry, probe: &Probe, handle: &TokioHandle, ) -> Result { let out = self.make_live(handle)?; match out { Self::Lazy(_) => unreachable!(), Self::Live(input, lazy) => { let promoted = input.promote(codecs, probe)?; Ok(Self::Live(promoted, lazy)) }, } } /// Initialises and parses an [`Input::Lazy`] into an [`Input::Live`], /// placing blocking I/O on a tokio blocking thread. pub async fn make_playable_async( self, codecs: &'static CodecRegistry, probe: &'static Probe, ) -> Result { let out = self.make_live_async().await?; match out { Self::Lazy(_) => unreachable!(), Self::Live(input, lazy) => { let promoted = tokio::task::spawn_blocking(move || input.promote(codecs, probe)) .await .map_err(|_| MakePlayableError::Panicked)??; Ok(Self::Live(promoted, lazy)) }, } } /// Returns whether this audio stream is full initialised, parsed, and /// ready to play (e.g., `Self::Live(LiveInput::Parsed(p), _)`). #[must_use] pub fn is_playable(&self) -> bool { if let Self::Live(input, _) = self { input.is_playable() } else { false } } /// Returns a reference to the live input, if it has been created via /// [`Self::make_live`] or [`Self::make_live_async`]. #[must_use] pub fn live(&self) -> Option<&LiveInput> { if let Self::Live(input, _) = self { Some(input) } else { None } } /// Returns a mutable reference to the live input, if it been created via /// [`Self::make_live`] or [`Self::make_live_async`]. pub fn live_mut(&mut self) -> Option<&mut LiveInput> { if let Self::Live(ref mut input, _) = self { Some(input) } else { None } } /// Returns a reference to the data parsed from this input stream, if it has /// been made available via [`Self::make_playable`] or [`LiveInput::promote`]. #[must_use] pub fn parsed(&self) -> Option<&Parsed> { self.live().and_then(LiveInput::parsed) } /// Returns a mutable reference to the data parsed from this input stream, if it /// has been made available via [`Self::make_playable`] or [`LiveInput::promote`]. pub fn parsed_mut(&mut self) -> Option<&mut Parsed> { self.live_mut().and_then(LiveInput::parsed_mut) } } impl + Send + Sync + 'static> From for Input { fn from(val: T) -> Self { let raw_src = LiveInput::Raw(AudioStream { input: Box::new(Cursor::new(val)), hint: None, }); Input::Live(raw_src, None) } }