Main goal: a lot of nested future/result folding. This mainly modifies error handling for Tracks and TrackHandles to be more consistent, and hides the underlying channel result passing in get_info. Errors returned should be far clearer, and are domain specific rather than falling back to a very opaque use of the underlying channel error. It should be clearer to users why their handle commands failed, or why they can't make a ytdl track loop or similar. Also fixed/cleaned up Songbird::join(_gateway) to return in a single await, sparing the user from the underlying channel details and repeated Errs. I was trying for some time to extend the same graces to `Call`, but could not figure out a sane way to get a 'static version of the first future in the chain (i.e., the gateway send) so that the whole thing could happen after dropping the lock around the Call. I really wanted to fix this to happen as a single folded await too, but I think this might need some crazy hack or redesign.
379 lines
13 KiB
Rust
379 lines
13 KiB
Rust
//! Live, controllable audio instances.
|
|
//!
|
|
//! Tracks add control and event data around the bytestreams offered by [`Input`],
|
|
//! where each represents a live audio source inside of the driver's mixer.
|
|
//!
|
|
//! To prevent locking and stalling of the driver, tracks are controlled from your bot using a
|
|
//! [`TrackHandle`]. These handles remotely send commands from your bot's (a)sync
|
|
//! context to control playback, register events, and execute synchronous closures.
|
|
//!
|
|
//! If you want a new track from an [`Input`], i.e., for direct control before
|
|
//! playing your source on the driver, use [`create_player`].
|
|
//!
|
|
//! [`Input`]: ../input/struct.Input.html
|
|
//! [`TrackHandle`]: struct.TrackHandle.html
|
|
//! [`create_player`]: fn.create_player.html
|
|
|
|
mod command;
|
|
mod error;
|
|
mod handle;
|
|
mod looping;
|
|
mod mode;
|
|
mod queue;
|
|
mod state;
|
|
|
|
pub use self::{command::*, error::*, handle::*, looping::*, mode::*, queue::*, state::*};
|
|
|
|
use crate::{constants::*, driver::tasks::message::*, events::EventStore, input::Input};
|
|
use std::time::Duration;
|
|
use tokio::sync::mpsc::{self, error::TryRecvError, UnboundedReceiver};
|
|
use uuid::Uuid;
|
|
|
|
/// Control object for audio playback.
|
|
///
|
|
/// Accessed by both commands and the playback code -- as such, access from user code is
|
|
/// almost always guarded via a [`TrackHandle`]. You should expect to receive
|
|
/// access to a raw object of this type via [`create_player`], for use in
|
|
/// [`Driver::play`] or [`Driver::play_only`].
|
|
///
|
|
/// # Example
|
|
///
|
|
/// ```rust,no_run
|
|
/// use songbird::{driver::Driver, ffmpeg, tracks::create_player};
|
|
///
|
|
/// # async {
|
|
/// // A Call is also valid here!
|
|
/// let mut handler: Driver = Default::default();
|
|
/// let source = ffmpeg("../audio/my-favourite-song.mp3")
|
|
/// .await
|
|
/// .expect("This might fail: handle this error!");
|
|
/// let (mut audio, audio_handle) = create_player(source);
|
|
///
|
|
/// audio.set_volume(0.5);
|
|
///
|
|
/// handler.play_only(audio);
|
|
///
|
|
/// // Future access occurs via audio_handle.
|
|
/// # };
|
|
/// ```
|
|
///
|
|
/// [`Driver::play_only`]: crate::driver::Driver::play_only
|
|
/// [`Driver::play`]: crate::driver::Driver::play
|
|
/// [`TrackHandle`]: TrackHandle
|
|
/// [`create_player`]: create_player
|
|
#[derive(Debug)]
|
|
pub struct Track {
|
|
/// Whether or not this sound is currently playing.
|
|
///
|
|
/// Can be controlled with [`play`] or [`pause`] if chaining is desired.
|
|
///
|
|
/// [`play`]: Track::play
|
|
/// [`pause`]: Track::pause
|
|
pub(crate) playing: PlayMode,
|
|
|
|
/// The desired volume for playback.
|
|
///
|
|
/// Sensible values fall between `0.0` and `1.0`.
|
|
///
|
|
/// Can be controlled with [`volume`] if chaining is desired.
|
|
///
|
|
/// [`volume`]: Track::volume
|
|
pub(crate) volume: f32,
|
|
|
|
/// Underlying data access object.
|
|
///
|
|
/// *Calling code is not expected to use this.*
|
|
pub(crate) source: Input,
|
|
|
|
/// The current playback position in the track.
|
|
pub(crate) position: Duration,
|
|
|
|
/// The total length of time this track has been active.
|
|
pub(crate) play_time: Duration,
|
|
|
|
/// List of events attached to this audio track.
|
|
///
|
|
/// This may be used to add additional events to a track
|
|
/// before it is sent to the audio context for playing.
|
|
pub events: Option<EventStore>,
|
|
|
|
/// Channel from which commands are received.
|
|
///
|
|
/// Track commands are sent in this manner to ensure that access
|
|
/// occurs in a thread-safe manner, without allowing any external
|
|
/// code to lock access to audio objects and block packet generation.
|
|
pub(crate) commands: UnboundedReceiver<TrackCommand>,
|
|
|
|
/// Handle for safe control of this audio track from other threads.
|
|
///
|
|
/// Typically, this is used by internal code to supply context information
|
|
/// to event handlers, though more may be cloned from this handle.
|
|
pub handle: TrackHandle,
|
|
|
|
/// Count of remaining loops.
|
|
pub loops: LoopState,
|
|
|
|
/// Unique identifier for this track.
|
|
pub(crate) uuid: Uuid,
|
|
}
|
|
|
|
impl Track {
|
|
/// Create a new track directly from an input, command source,
|
|
/// and handle.
|
|
///
|
|
/// In general, you should probably use [`create_player`].
|
|
///
|
|
/// [`create_player`]: fn.create_player.html
|
|
pub fn new_raw(
|
|
source: Input,
|
|
commands: UnboundedReceiver<TrackCommand>,
|
|
handle: TrackHandle,
|
|
) -> Self {
|
|
let uuid = handle.uuid();
|
|
|
|
Self {
|
|
playing: Default::default(),
|
|
volume: 1.0,
|
|
source,
|
|
position: Default::default(),
|
|
play_time: Default::default(),
|
|
events: Some(EventStore::new_local()),
|
|
commands,
|
|
handle,
|
|
loops: LoopState::Finite(0),
|
|
uuid,
|
|
}
|
|
}
|
|
|
|
/// Sets a track to playing if it is paused.
|
|
pub fn play(&mut self) -> &mut Self {
|
|
self.set_playing(PlayMode::Play)
|
|
}
|
|
|
|
/// Pauses a track if it is playing.
|
|
pub fn pause(&mut self) -> &mut Self {
|
|
self.set_playing(PlayMode::Pause)
|
|
}
|
|
|
|
/// Manually stops a track.
|
|
///
|
|
/// This will cause the audio track to be removed, with any relevant events triggered.
|
|
/// Stopped/ended tracks cannot be restarted.
|
|
pub fn stop(&mut self) -> &mut Self {
|
|
self.set_playing(PlayMode::Stop)
|
|
}
|
|
|
|
pub(crate) fn end(&mut self) -> &mut Self {
|
|
self.set_playing(PlayMode::End)
|
|
}
|
|
|
|
#[inline]
|
|
fn set_playing(&mut self, new_state: PlayMode) -> &mut Self {
|
|
self.playing = self.playing.change_to(new_state);
|
|
|
|
self
|
|
}
|
|
|
|
/// Returns the current play status of this track.
|
|
pub fn playing(&self) -> PlayMode {
|
|
self.playing
|
|
}
|
|
|
|
/// Sets [`volume`] in a manner that allows method chaining.
|
|
///
|
|
/// [`volume`]: Track::volume
|
|
pub fn set_volume(&mut self, volume: f32) -> &mut Self {
|
|
self.volume = volume;
|
|
|
|
self
|
|
}
|
|
|
|
/// Returns the current playback position.
|
|
pub fn volume(&self) -> f32 {
|
|
self.volume
|
|
}
|
|
|
|
/// Returns the current playback position.
|
|
pub fn position(&self) -> Duration {
|
|
self.position
|
|
}
|
|
|
|
/// Returns the total length of time this track has been active.
|
|
pub fn play_time(&self) -> Duration {
|
|
self.play_time
|
|
}
|
|
|
|
/// Set an audio track to loop a set number of times.
|
|
///
|
|
/// If the underlying [`Input`] does not support seeking,
|
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
|
///
|
|
/// [`Input`]: crate::input::Input
|
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
|
pub fn set_loops(&mut self, loops: LoopState) -> TrackResult<()> {
|
|
if self.source.is_seekable() {
|
|
self.loops = loops;
|
|
Ok(())
|
|
} else {
|
|
Err(TrackError::SeekUnsupported)
|
|
}
|
|
}
|
|
|
|
pub(crate) fn do_loop(&mut self) -> bool {
|
|
match self.loops {
|
|
LoopState::Infinite => true,
|
|
LoopState::Finite(0) => false,
|
|
LoopState::Finite(ref mut n) => {
|
|
*n -= 1;
|
|
true
|
|
},
|
|
}
|
|
}
|
|
|
|
/// Steps playback location forward by one frame.
|
|
pub(crate) fn step_frame(&mut self) {
|
|
self.position += TIMESTEP_LENGTH;
|
|
self.play_time += TIMESTEP_LENGTH;
|
|
}
|
|
|
|
/// Receives and acts upon any commands forwarded by TrackHandles.
|
|
///
|
|
/// *Used internally*, this should not be exposed to users.
|
|
pub(crate) fn process_commands(&mut self, index: usize, ic: &Interconnect) {
|
|
// Note: disconnection and an empty channel are both valid,
|
|
// and should allow the audio object to keep running as intended.
|
|
|
|
// Note that interconnect failures are not currently errors.
|
|
// In correct operation, the event thread should never panic,
|
|
// but it receiving status updates is secondary do actually
|
|
// doing the work.
|
|
loop {
|
|
match self.commands.try_recv() {
|
|
Ok(cmd) => {
|
|
use TrackCommand::*;
|
|
match cmd {
|
|
Play => {
|
|
self.play();
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Mode(self.playing),
|
|
));
|
|
},
|
|
Pause => {
|
|
self.pause();
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Mode(self.playing),
|
|
));
|
|
},
|
|
Stop => {
|
|
self.stop();
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Mode(self.playing),
|
|
));
|
|
},
|
|
Volume(vol) => {
|
|
self.set_volume(vol);
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Volume(self.volume),
|
|
));
|
|
},
|
|
Seek(time) =>
|
|
if let Ok(new_time) = self.seek_time(time) {
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Position(new_time),
|
|
));
|
|
},
|
|
AddEvent(evt) => {
|
|
let _ = ic.events.send(EventMessage::AddTrackEvent(index, evt));
|
|
},
|
|
Do(action) => {
|
|
action(self);
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Total(self.state()),
|
|
));
|
|
},
|
|
Request(tx) => {
|
|
let _ = tx.send(Box::new(self.state()));
|
|
},
|
|
Loop(loops) =>
|
|
if self.set_loops(loops).is_ok() {
|
|
let _ = ic.events.send(EventMessage::ChangeState(
|
|
index,
|
|
TrackStateChange::Loops(self.loops, true),
|
|
));
|
|
},
|
|
}
|
|
},
|
|
Err(TryRecvError::Closed) => {
|
|
// this branch will never be visited.
|
|
break;
|
|
},
|
|
Err(TryRecvError::Empty) => {
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Creates a read-only copy of the audio track's state.
|
|
///
|
|
/// The primary use-case of this is sending information across
|
|
/// threads in response to a [`TrackHandle`].
|
|
///
|
|
/// [`TrackHandle`]: TrackHandle
|
|
pub fn state(&self) -> TrackState {
|
|
TrackState {
|
|
playing: self.playing,
|
|
volume: self.volume,
|
|
position: self.position,
|
|
play_time: self.play_time,
|
|
loops: self.loops,
|
|
}
|
|
}
|
|
|
|
/// Seek to a specific point in the track.
|
|
///
|
|
/// If the underlying [`Input`] does not support seeking,
|
|
/// then all calls will fail with [`TrackError::SeekUnsupported`].
|
|
///
|
|
/// [`Input`]: crate::input::Input
|
|
/// [`TrackError::SeekUnsupported`]: TrackError::SeekUnsupported
|
|
pub fn seek_time(&mut self, pos: Duration) -> TrackResult<Duration> {
|
|
if let Some(t) = self.source.seek_time(pos) {
|
|
self.position = t;
|
|
Ok(t)
|
|
} else {
|
|
Err(TrackError::SeekUnsupported)
|
|
}
|
|
}
|
|
|
|
/// Returns this track's unique identifier.
|
|
pub fn uuid(&self) -> Uuid {
|
|
self.uuid
|
|
}
|
|
}
|
|
|
|
/// Creates a [`Track`] object to pass into the audio context, and a [`TrackHandle`]
|
|
/// for safe, lock-free access in external code.
|
|
///
|
|
/// Typically, this would be used if you wished to directly work on or configure
|
|
/// the [`Track`] object before it is passed over to the driver.
|
|
///
|
|
/// [`Track`]: Track
|
|
/// [`TrackHandle`]: TrackHandle
|
|
pub fn create_player(source: Input) -> (Track, TrackHandle) {
|
|
let (tx, rx) = mpsc::unbounded_channel();
|
|
let can_seek = source.is_seekable();
|
|
let metadata = source.metadata.clone();
|
|
let handle = TrackHandle::new(tx, can_seek, Uuid::new_v4(), metadata);
|
|
|
|
let player = Track::new_raw(source, rx, handle.clone());
|
|
|
|
(player, handle)
|
|
}
|