//! 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, /// 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, /// 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, 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 { 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) }