Files
songbird/src/input/mod.rs

384 lines
15 KiB
Rust

//! 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<u8>`,
//! * [`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<u8>`,
/// * [`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<Input>`) 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<u8>`, 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<dyn Compose>,
),
/// 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<Box<dyn Compose>>,
),
}
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<AuxMetadata, AuxMetadataError> {
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<Metadata<'_>, 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<Self, AudioStreamError> {
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<dyn Error + Send + Sync> =
"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<Self, AudioStreamError> {
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<dyn Error + Send + Sync> =
"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<Self, MakePlayableError> {
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<Self, MakePlayableError> {
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<T: AsRef<[u8]> + Send + Sync + 'static> From<T> 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)
}
}