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:
@@ -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(),
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user