Input: Allow Restartable sources to be lazy

This change is made with queue users in mind. Since sources
of this kind *know* how to (re)create themselves, they can
avoid being created at all until needed.

This also adds machinery to preload tracks *before* they are
needed, for gapless playback on queues and so on. Queues
make use of the event system to do this.
This commit is contained in:
Kyle Simpson
2020-12-28 17:02:10 +00:00
parent c0d3cb3113
commit 03ae0e7628
10 changed files with 368 additions and 101 deletions

View File

@@ -30,6 +30,8 @@ pub enum TrackCommand {
Request(OneshotSender<Box<TrackState>>),
/// Change the loop count/strategy of this track.
Loop(LoopState),
/// Prompts a track's input to become live and usable, if it is not already.
MakePlayable,
}
impl std::fmt::Debug for TrackCommand {
@@ -48,6 +50,7 @@ impl std::fmt::Debug for TrackCommand {
Do(_f) => "Do([function])".to_string(),
Request(tx) => format!("Request({:?})", tx),
Loop(loops) => format!("Loop({:?})", loops),
MakePlayable => "MakePlayable".to_string(),
}
)
}

View File

@@ -74,6 +74,16 @@ impl TrackHandle {
self.send(TrackCommand::Volume(volume))
}
/// Ready a track for playing if it is lazily initialised.
///
/// Currently, only [`Restartable`] sources support lazy setup.
/// This call is a no-op for all others.
///
/// [`Restartable`]: crate::input::restartable::Restartable
pub fn make_playable(&self) -> TrackResult<()> {
self.send(TrackCommand::MakePlayable)
}
/// Denotes whether the underlying [`Input`] stream is compatible with arbitrary seeking.
///
/// If this returns `false`, all calls to [`seek_time`] will fail, and the track is

View File

@@ -307,6 +307,7 @@ impl Track {
TrackStateChange::Loops(self.loops, true),
));
},
MakePlayable => self.make_playable(),
}
},
Err(TryRecvError::Closed) => {
@@ -320,6 +321,16 @@ impl Track {
}
}
/// Ready a track for playing if it is lazily initialised.
///
/// Currently, only [`Restartable`] sources support lazy setup.
/// This call is a no-op for all others.
///
/// [`Restartable`]: crate::input::restartable::Restartable
pub fn make_playable(&mut self) {
self.source.reader.make_playable();
}
/// Creates a read-only copy of the audio track's state.
///
/// The primary use-case of this is sending information across

View File

@@ -6,7 +6,7 @@ use crate::{
};
use async_trait::async_trait;
use parking_lot::Mutex;
use std::{collections::VecDeque, ops::Deref, sync::Arc};
use std::{collections::VecDeque, ops::Deref, sync::Arc, time::Duration};
use tracing::{info, warn};
/// A simple queue for several audio sources, designed to
@@ -145,6 +145,23 @@ impl EventHandler for QueueHandler {
}
}
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 {
@@ -194,6 +211,23 @@ impl TrackQueue {
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));
}