Files
songbird/src/tracks/queue.rs
2021-06-14 11:42:37 +01:00

332 lines
9.7 KiB
Rust

use crate::{
driver::Driver,
events::{Event, EventContext, EventData, EventHandler, TrackEvent},
input::Input,
tracks::{self, Track, TrackHandle, TrackResult},
};
use async_trait::async_trait;
use parking_lot::Mutex;
use std::{collections::VecDeque, ops::Deref, sync::Arc, time::Duration};
use tracing::{info, warn};
/// A simple queue for several audio sources, designed to
/// play in sequence.
///
/// This makes use of [`TrackEvent`]s to determine when the current
/// song or audio file has finished before playing the next entry.
///
/// One of these is automatically included via [`Driver::queue`] when
/// the `"builtin-queue"` feature is enabled.
///
/// `examples/serenity/voice_events_queue` demonstrates how a user might manage,
/// track and use this to run a song queue in many guilds in parallel.
/// This code is trivial to extend if extra functionality is needed.
///
/// # Example
///
/// ```rust,no_run
/// use songbird::{
/// driver::Driver,
/// id::GuildId,
/// ffmpeg,
/// tracks::{create_player, TrackQueue},
/// };
/// use std::collections::HashMap;
///
/// # async {
/// let guild = GuildId(0);
/// // A Call is also valid here!
/// let mut driver: Driver = Default::default();
///
/// let mut queues: HashMap<GuildId, TrackQueue> = Default::default();
///
/// let source = ffmpeg("../audio/my-favourite-song.mp3")
/// .await
/// .expect("This might fail: handle this error!");
///
/// // We need to ensure that this guild has a TrackQueue created for it.
/// let queue = queues.entry(guild)
/// .or_default();
///
/// // Queueing a track is this easy!
/// queue.add_source(source, &mut driver);
/// # };
/// ```
///
/// [`TrackEvent`]: crate::events::TrackEvent
/// [`Driver::queue`]: crate::driver::Driver
#[derive(Clone, Debug, Default)]
pub struct TrackQueue {
// NOTE: the choice of a parking lot mutex is quite deliberate
inner: Arc<Mutex<TrackQueueCore>>,
}
/// Reference to a track which is known to be part of a queue.
///
/// Instances *should not* be moved from one queue to another.
#[derive(Debug)]
pub struct Queued(TrackHandle);
impl Deref for Queued {
type Target = TrackHandle;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl Queued {
/// Clones the inner handle
pub fn handle(&self) -> TrackHandle {
self.0.clone()
}
}
#[derive(Debug, Default)]
/// Inner portion of a [`TrackQueue`].
///
/// This abstracts away thread-safety from the user,
/// and offers a convenient location to store further state if required.
///
/// [`TrackQueue`]: TrackQueue
struct TrackQueueCore {
tracks: VecDeque<Queued>,
}
struct QueueHandler {
remote_lock: Arc<Mutex<TrackQueueCore>>,
}
#[async_trait]
impl EventHandler for QueueHandler {
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
let mut inner = self.remote_lock.lock();
// Due to possibility that users might remove, reorder,
// or dequeue+stop tracks, we need to verify that the FIRST
// track is the one who has ended.
match ctx {
EventContext::Track(ts) => {
// This slice should have exactly one entry.
// If the ended track has same id as the queue head, then
// we can progress the queue.
if inner.tracks.front()?.uuid() != ts.first()?.1.uuid() {
return None;
}
},
_ => return None,
}
let _old = inner.tracks.pop_front();
info!("Queued track ended: {:?}.", ctx);
info!("{} tracks remain.", inner.tracks.len());
// Keep going until we find one track which works, or we run out.
while let Some(new) = inner.tracks.front() {
if new.play().is_err() {
// Discard files which cannot be used for whatever reason.
warn!("Track in Queue couldn't be played...");
inner.tracks.pop_front();
} else {
break;
}
}
None
}
}
struct SongPreloader {
remote_lock: Arc<Mutex<TrackQueueCore>>,
}
#[async_trait]
impl EventHandler for SongPreloader {
async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
let inner = self.remote_lock.lock();
if let Some(track) = inner.tracks.get(1) {
let _ = track.0.make_playable();
}
None
}
}
impl TrackQueue {
/// Create a new, empty, track queue.
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(TrackQueueCore {
tracks: VecDeque::new(),
})),
}
}
/// Adds an audio source to the queue, to be played in the channel managed by `handler`.
pub fn add_source(&self, source: Input, handler: &mut Driver) {
let (audio, _) = tracks::create_player(source);
self.add(audio, handler);
}
/// Adds a [`Track`] object to the queue, to be played in the channel managed by `handler`.
///
/// This is used with [`create_player`] if additional configuration or event handlers
/// are required before enqueueing the audio track.
///
/// [`Track`]: Track
/// [`create_player`]: super::create_player
pub fn add(&self, mut track: Track, handler: &mut Driver) {
self.add_raw(&mut track);
handler.play(track);
}
#[inline]
pub(crate) fn add_raw(&self, track: &mut Track) {
info!("Track added to queue.");
let remote_lock = self.inner.clone();
let mut inner = self.inner.lock();
let track_handle = track.handle.clone();
if !inner.tracks.is_empty() {
track.pause();
}
track
.events
.as_mut()
.expect("Queue inspecting EventStore on new Track: did not exist.")
.add_event(
EventData::new(Event::Track(TrackEvent::End), QueueHandler { remote_lock }),
track.position,
);
// Attempts to start loading the next track before this one ends.
// Idea is to provide as close to gapless playback as possible,
// while minimising memory use.
if let Some(time) = track.source.metadata.duration {
let preload_time = time.checked_sub(Duration::from_secs(5)).unwrap_or_default();
let remote_lock = self.inner.clone();
track
.events
.as_mut()
.expect("Queue inspecting EventStore on new Track: did not exist.")
.add_event(
EventData::new(Event::Delayed(preload_time), SongPreloader { remote_lock }),
track.position,
);
}
inner.tracks.push_back(Queued(track_handle));
}
/// Returns a handle to the currently playing track.
pub fn current(&self) -> Option<TrackHandle> {
let inner = self.inner.lock();
inner.tracks.front().map(|h| h.handle())
}
/// Attempts to remove a track from the specified index.
///
/// The returned entry can be readded to *this* queue via [`modify_queue`].
///
/// [`modify_queue`]: TrackQueue::modify_queue
pub fn dequeue(&self, index: usize) -> Option<Queued> {
self.modify_queue(|vq| vq.remove(index))
}
/// Returns the number of tracks currently in the queue.
pub fn len(&self) -> usize {
let inner = self.inner.lock();
inner.tracks.len()
}
/// Returns whether there are no tracks currently in the queue.
pub fn is_empty(&self) -> bool {
let inner = self.inner.lock();
inner.tracks.is_empty()
}
/// Allows modification of the inner queue (i.e., deletion, reordering).
///
/// Users must be careful to `stop` removed tracks, so as to prevent
/// resource leaks.
pub fn modify_queue<F, O>(&self, func: F) -> O
where
F: FnOnce(&mut VecDeque<Queued>) -> O,
{
let mut inner = self.inner.lock();
func(&mut inner.tracks)
}
/// Pause the track at the head of the queue.
pub fn pause(&self) -> TrackResult<()> {
let inner = self.inner.lock();
if let Some(handle) = inner.tracks.front() {
handle.pause()
} else {
Ok(())
}
}
/// Resume the track at the head of the queue.
pub fn resume(&self) -> TrackResult<()> {
let inner = self.inner.lock();
if let Some(handle) = inner.tracks.front() {
handle.play()
} else {
Ok(())
}
}
/// Stop the currently playing track, and clears the queue.
pub fn stop(&self) {
let mut inner = self.inner.lock();
for track in inner.tracks.drain(..) {
// Errors when removing tracks don't really make
// a difference: an error just implies it's already gone.
let _ = track.stop();
}
}
/// Skip to the next track in the queue, if it exists.
pub fn skip(&self) -> TrackResult<()> {
let inner = self.inner.lock();
inner.stop_current()
}
/// Returns a list of currently queued tracks.
///
/// Does not allow for modification of the queue, instead returns a snapshot of the queue at the time of calling.
///
/// Use [`modify_queue`] for direct modification of the queue.
///
/// [`modify_queue`]: TrackQueue::modify_queue
pub fn current_queue(&self) -> Vec<TrackHandle> {
let inner = self.inner.lock();
inner.tracks.iter().map(|q| q.handle()).collect()
}
}
impl TrackQueueCore {
/// Skip to the next track in the queue, if it exists.
fn stop_current(&self) -> TrackResult<()> {
if let Some(handle) = self.tracks.front() {
handle.stop()
} else {
Ok(())
}
}
}