Fix enqueuing and song skip playback issues
This commit is contained in:
parent
03318a0ef5
commit
9b6472b072
4 changed files with 59 additions and 27 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -868,9 +868,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rusqlite"
|
name = "rusqlite"
|
||||||
version = "0.26.1"
|
version = "0.26.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a82b0b91fad72160c56bf8da7a549b25d7c31109f52cc1437eac4c0ad2550a7"
|
checksum = "4ba4d3462c8b2e4d7f4fcfcf2b296dc6b65404fbbc7b63daa37fd485c149daf7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"fallible-iterator",
|
"fallible-iterator",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
|
|
||||||
use rodio::{decoder::Decoder, OutputStream, Sink};
|
use rodio::{decoder::Decoder, OutputStream, Sink, OutputStreamHandle};
|
||||||
|
|
||||||
use m3u8_rs::{MediaPlaylist, MediaSegment};
|
use m3u8_rs::{MediaPlaylist, MediaSegment};
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ pub struct MpsPlayer<T: MpsTokenReader> {
|
||||||
sink: Sink,
|
sink: Sink,
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
output_stream: OutputStream, // this is required for playback, so it must live as long as this struct instance
|
output_stream: OutputStream, // this is required for playback, so it must live as long as this struct instance
|
||||||
|
output_handle: OutputStreamHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: MpsTokenReader> MpsPlayer<T> {
|
impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
|
@ -23,6 +24,7 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
runner: runner,
|
runner: runner,
|
||||||
sink: Sink::try_new(&output_handle).map_err(PlaybackError::from_err)?,
|
sink: Sink::try_new(&output_handle).map_err(PlaybackError::from_err)?,
|
||||||
output_stream: stream,
|
output_stream: stream,
|
||||||
|
output_handle: output_handle
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +55,7 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
let stream = io::BufReader::new(file);
|
let stream = io::BufReader::new(file);
|
||||||
let source = Decoder::new(stream).map_err(PlaybackError::from_err)?;
|
let source = Decoder::new(stream).map_err(PlaybackError::from_err)?;
|
||||||
self.sink.append(source);
|
self.sink.append(source);
|
||||||
self.sink.play(); // idk if this is necessary
|
//self.sink.play(); // idk if this is necessary
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => Err(PlaybackError::from_err(e))
|
Err(e) => Err(PlaybackError::from_err(e))
|
||||||
|
@ -64,20 +66,22 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
|
|
||||||
pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> {
|
pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> {
|
||||||
let mut items_left = count;
|
let mut items_left = count;
|
||||||
for item in &mut self.runner {
|
|
||||||
if items_left == 0 { return Ok(()); }
|
if items_left == 0 { return Ok(()); }
|
||||||
|
for item in &mut self.runner {
|
||||||
match item {
|
match item {
|
||||||
Ok(music) => {
|
Ok(music) => {
|
||||||
|
//println!("Enqueuing {}", music.filename);
|
||||||
let file = fs::File::open(music.filename).map_err(PlaybackError::from_err)?;
|
let file = fs::File::open(music.filename).map_err(PlaybackError::from_err)?;
|
||||||
let stream = io::BufReader::new(file);
|
let stream = io::BufReader::new(file);
|
||||||
let source = Decoder::new(stream).map_err(PlaybackError::from_err)?;
|
let source = Decoder::new(stream).map_err(PlaybackError::from_err)?;
|
||||||
self.sink.append(source);
|
self.sink.append(source);
|
||||||
self.sink.play(); // idk if this is necessary
|
//self.sink.play(); // idk if this is necessary
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
Err(e) => Err(PlaybackError::from_err(e))
|
Err(e) => Err(PlaybackError::from_err(e))
|
||||||
}?;
|
}?;
|
||||||
items_left -= 1;
|
items_left -= 1;
|
||||||
|
if items_left == 0 { break; }
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -102,6 +106,10 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
self.sink.len()
|
self.sink.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn empty(&self) -> bool {
|
||||||
|
self.sink.empty()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn save_m3u8<W: io::Write>(&mut self, w: &mut W) -> Result<(), PlaybackError> {
|
pub fn save_m3u8<W: io::Write>(&mut self, w: &mut W) -> Result<(), PlaybackError> {
|
||||||
let mut playlist = MediaPlaylist {
|
let mut playlist = MediaPlaylist {
|
||||||
version: 6,
|
version: 6,
|
||||||
|
@ -133,6 +141,20 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
|
||||||
pub fn set_volume(&self, volume: f32) {
|
pub fn set_volume(&self, volume: f32) {
|
||||||
self.sink.set_volume(volume);
|
self.sink.set_volume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_sink(&mut self) -> Result<(), PlaybackError> {
|
||||||
|
let is_paused = self.sink.is_paused();
|
||||||
|
let volume = self.sink.volume();
|
||||||
|
|
||||||
|
self.stop();
|
||||||
|
self.sink = Sink::try_new(&self.output_handle).map_err(PlaybackError::from_err)?;
|
||||||
|
|
||||||
|
if is_paused {
|
||||||
|
self.sink.pause();
|
||||||
|
}
|
||||||
|
self.sink.set_volume(volume);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -6,8 +6,6 @@ use mps_interpreter::tokens::MpsTokenReader;
|
||||||
use super::MpsPlayer;
|
use super::MpsPlayer;
|
||||||
use super::PlaybackError;
|
use super::PlaybackError;
|
||||||
|
|
||||||
const DEFAULT_QUEUE_SIZE: usize = 1;
|
|
||||||
|
|
||||||
pub struct MpsPlayerServer<T: MpsTokenReader> {
|
pub struct MpsPlayerServer<T: MpsTokenReader> {
|
||||||
player: MpsPlayer<T>,
|
player: MpsPlayer<T>,
|
||||||
control: Receiver<ControlAction>,
|
control: Receiver<ControlAction>,
|
||||||
|
@ -25,29 +23,23 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
|
|
||||||
fn run_loop(&mut self) {
|
fn run_loop(&mut self) {
|
||||||
// this can panic since it's not on the main thread
|
// this can panic since it's not on the main thread
|
||||||
if let Err(e) = self.player.enqueue_all() {
|
// initial queue full
|
||||||
|
if let Err(e) = self.player.enqueue(1) {
|
||||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
}
|
}
|
||||||
loop {
|
loop {
|
||||||
let command = self.control.recv().unwrap();
|
let command = self.control.recv().unwrap();
|
||||||
|
|
||||||
// keep queue full
|
let mut is_exiting = false;
|
||||||
if self.player.queue_len() < DEFAULT_QUEUE_SIZE {
|
|
||||||
if let Err(e) = self.player.enqueue(DEFAULT_QUEUE_SIZE - self.player.queue_len()) {
|
|
||||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
|
||||||
}
|
|
||||||
if self.player.queue_len() == 0 { // no more music to add
|
|
||||||
self.event.send(PlayerAction::End).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// process command
|
// process command
|
||||||
let mut is_exiting = false;
|
|
||||||
match command {
|
match command {
|
||||||
ControlAction::Next{..} => {
|
ControlAction::Next{..} => {
|
||||||
if self.player.queue_len() <= 1 {
|
//println!("Executing next command (queue_len: {})", self.player.queue_len());
|
||||||
self.player.stop();
|
if let Err(e) = self.player.new_sink() {
|
||||||
//self.player.resume();
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
|
}
|
||||||
|
if !self.player.is_paused() {
|
||||||
self.player.enqueue(1).unwrap();
|
self.player.enqueue(1).unwrap();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -76,12 +68,24 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
self.player.set_volume((volume as f32) / (u32::MAX as f32));
|
self.player.set_volume((volume as f32) / (u32::MAX as f32));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep queue full (while playing music)
|
||||||
|
if self.player.queue_len() == 0 && !self.player.is_paused() && !is_exiting {
|
||||||
|
if let Err(e) = self.player.enqueue(1) {
|
||||||
|
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||||
|
}
|
||||||
|
if self.player.queue_len() == 0 { // no more music to add
|
||||||
|
is_exiting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if command.needs_ack() {
|
if command.needs_ack() {
|
||||||
self.event.send(PlayerAction::Acknowledge(command)).unwrap();
|
self.event.send(PlayerAction::Acknowledge(command)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if is_exiting { break; }
|
if is_exiting { break; }
|
||||||
}
|
}
|
||||||
|
self.event.send(PlayerAction::End).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn<F: FnOnce() -> MpsPlayer<T> + Send + 'static>(
|
pub fn spawn<F: FnOnce() -> MpsPlayer<T> + Send + 'static>(
|
||||||
|
@ -111,7 +115,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
|
||||||
|
|
||||||
/// Action the controller wants the player to perform
|
/// Action the controller wants the player to perform
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
#[derive(Clone, PartialEq, Eq)]
|
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||||
pub enum ControlAction {
|
pub enum ControlAction {
|
||||||
Next{ack: bool},
|
Next{ack: bool},
|
||||||
Previous{ack: bool},
|
Previous{ack: bool},
|
||||||
|
@ -143,7 +147,7 @@ impl ControlAction {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Action the player has performed/encountered
|
/// Action the player has performed/encountered
|
||||||
#[derive(Clone)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum PlayerAction {
|
pub enum PlayerAction {
|
||||||
Acknowledge(ControlAction),
|
Acknowledge(ControlAction),
|
||||||
Exception(PlaybackError),
|
Exception(PlaybackError),
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -13,11 +13,17 @@ fn play_cursor() -> Result<(), PlaybackError> {
|
||||||
fn main() {
|
fn main() {
|
||||||
//play_cursor().unwrap();
|
//play_cursor().unwrap();
|
||||||
let ctrl = MpsController::create(|| {
|
let ctrl = MpsController::create(|| {
|
||||||
let cursor = io::Cursor::<&'static str>::new("sql(`SELECT * FROM songs JOIN artists ON songs.artist = artists.artist_id WHERE artists.name like 'thundercat'`);");
|
//let cursor = io::Cursor::<&'static str>::new("sql(`SELECT * FROM songs JOIN artists ON songs.artist = artists.artist_id WHERE artists.name like 'thundercat'`);");
|
||||||
|
let cursor = io::Cursor::<&'static str>::new(
|
||||||
|
"sql(`SELECT * FROM songs JOIN artists ON songs.artist = artists.artist_id WHERE artists.name like 'thundercat'`);
|
||||||
|
artist(`gwen`);"
|
||||||
|
);
|
||||||
let runner = MpsRunner::with_stream(cursor);
|
let runner = MpsRunner::with_stream(cursor);
|
||||||
MpsPlayer::new(runner).unwrap()
|
let player = MpsPlayer::new(runner).unwrap();
|
||||||
|
player.set_volume(0.8);
|
||||||
|
player
|
||||||
});
|
});
|
||||||
|
|
||||||
ctrl.wait_for_done().unwrap();
|
ctrl.wait_for_done().unwrap();
|
||||||
ctrl.exit().unwrap();
|
//ctrl.exit().unwrap(); // don't use both
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue