Add item history functionality to controls and volume cli command
This commit is contained in:
parent
2cee3dd223
commit
99f853e47d
5 changed files with 222 additions and 13 deletions
|
@ -130,7 +130,7 @@ impl SystemControlWrapper {
|
|||
control_clone
|
||||
.send(ControlAction::SetVolume {
|
||||
ack: false,
|
||||
volume: (v * (u32::MAX as f64)) as _,
|
||||
volume: (v * 100.0).round() as _,
|
||||
})
|
||||
.unwrap_or(())
|
||||
});
|
||||
|
|
|
@ -28,6 +28,8 @@ pub struct Player<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>>
|
|||
output_handle: OutputStreamHandle,
|
||||
#[cfg(feature = "mpd")]
|
||||
mpd_connection: Option<Client<std::net::TcpStream>>,
|
||||
history: Vec<Item>,
|
||||
history_index: usize, // distance from start of history Vec, 1-indexed (0 denotes history is exhausted)
|
||||
}
|
||||
|
||||
impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
||||
|
@ -41,6 +43,8 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
output_handle: output_handle,
|
||||
#[cfg(feature = "mpd")]
|
||||
mpd_connection: None,
|
||||
history: Vec::new(),
|
||||
history_index: 0,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -104,6 +108,23 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
pub fn enqueue(&mut self, count: usize) -> Result<Vec<Item>, PlayerError> {
|
||||
let mut items_left = count;
|
||||
let mut enqueued = Vec::with_capacity(count);
|
||||
while !self.history_depleted() && items_left != 0 {
|
||||
self.history_index += 1;
|
||||
let music = self.history[self.history_index - 1].clone();
|
||||
if let Some(filename) =
|
||||
music.field("filename").and_then(|x| x.to_owned().to_str())
|
||||
{
|
||||
enqueued.push(music.clone());
|
||||
self.append_source(&filename)?;
|
||||
items_left -= 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlayerError::from_err_playback(
|
||||
"Field `filename` does not exist on item",
|
||||
))
|
||||
}?;
|
||||
}
|
||||
self.history_index = self.history.len();
|
||||
if items_left == 0 {
|
||||
return Ok(enqueued);
|
||||
}
|
||||
|
@ -113,6 +134,8 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
if let Some(filename) =
|
||||
music.field("filename").and_then(|x| x.to_owned().to_str())
|
||||
{
|
||||
self.history.push(music.clone());
|
||||
self.history_index += 1;
|
||||
enqueued.push(music.clone());
|
||||
self.append_source(&filename)?;
|
||||
items_left -= 1;
|
||||
|
@ -143,6 +166,23 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
) -> Result<Vec<Item>, PlayerError> {
|
||||
let mut items_left = count;
|
||||
let mut enqueued = Vec::with_capacity(count);
|
||||
while !self.history_depleted() && items_left != 0 {
|
||||
self.history_index += 1;
|
||||
let music = self.history[self.history_index - 1].clone();
|
||||
if let Some(filename) =
|
||||
music.field("filename").and_then(|x| x.to_owned().to_str())
|
||||
{
|
||||
enqueued.push(music.clone());
|
||||
self.append_source_modified(&filename, &|x| modify(x, music.clone()))?;
|
||||
items_left -= 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlayerError::from_err_playback(
|
||||
"Field `filename` does not exist on item",
|
||||
))
|
||||
}?;
|
||||
}
|
||||
self.history_index = self.history.len();
|
||||
if items_left == 0 {
|
||||
return Ok(enqueued);
|
||||
}
|
||||
|
@ -152,6 +192,8 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
if let Some(filename) =
|
||||
music.field("filename").and_then(|x| x.to_owned().to_str())
|
||||
{
|
||||
self.history.push(music.clone());
|
||||
self.history_index += 1;
|
||||
enqueued.push(music.clone());
|
||||
self.append_source_modified(&filename, &|x| modify(x, music.clone()))?;
|
||||
items_left -= 1;
|
||||
|
@ -172,6 +214,70 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
Ok(enqueued)
|
||||
}
|
||||
|
||||
pub fn enqueue_previous(&mut self, count: usize, ignore_current_item: bool,) -> Result<Vec<Item>, PlayerError> {
|
||||
let mut items_left = count;
|
||||
let mut enqueued = Vec::with_capacity(count);
|
||||
while self.history_available(ignore_current_item) && items_left != 0 {
|
||||
let index = if ignore_current_item {
|
||||
self.history_index -= 1;
|
||||
self.history_index - 1
|
||||
} else {
|
||||
self.history_index - 1
|
||||
};
|
||||
let music = self.history[index].clone();
|
||||
if let Some(filename) =
|
||||
music.field("filename").and_then(|x| x.to_owned().to_str())
|
||||
{
|
||||
enqueued.push(music.clone());
|
||||
self.append_source(&filename)?;
|
||||
items_left -= 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlayerError::from_err_playback(
|
||||
"Field `filename` does not exist on item",
|
||||
))
|
||||
}?;
|
||||
}
|
||||
//println!("Enqueued {} items", count - items_left);
|
||||
Ok(enqueued)
|
||||
}
|
||||
|
||||
pub fn enqueue_previous_modified(
|
||||
&mut self,
|
||||
count: usize,
|
||||
modify: &dyn Fn(
|
||||
Decoder<io::BufReader<fs::File>>,
|
||||
Item,
|
||||
) -> Box<dyn Source<Item = i16> + Send>,
|
||||
ignore_current_item: bool,
|
||||
) -> Result<Vec<Item>, PlayerError> {
|
||||
let mut items_left = count;
|
||||
let mut enqueued = Vec::with_capacity(count);
|
||||
while self.history_available(ignore_current_item) && items_left != 0 {
|
||||
let index = if ignore_current_item {
|
||||
self.history_index -= 1;
|
||||
self.history_index - 1
|
||||
} else {
|
||||
self.history_index - 1
|
||||
};
|
||||
let music = self.history[index].clone();
|
||||
if let Some(filename) =
|
||||
music.field("filename").and_then(|x| x.to_owned().to_str())
|
||||
{
|
||||
enqueued.push(music.clone());
|
||||
self.append_source_modified(&filename, &|x| modify(x, music.clone()))?;
|
||||
items_left -= 1;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(PlayerError::from_err_playback(
|
||||
"Field `filename` does not exist on item",
|
||||
))
|
||||
}?;
|
||||
}
|
||||
//println!("Enqueued {} items", count - items_left);
|
||||
Ok(enqueued)
|
||||
}
|
||||
|
||||
pub fn resume(&self) {
|
||||
self.sink.play()
|
||||
}
|
||||
|
@ -196,6 +302,19 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> Player<I> {
|
|||
self.sink.empty()
|
||||
}
|
||||
|
||||
pub fn history_len(&self) -> usize {
|
||||
self.history_index - 1
|
||||
}
|
||||
|
||||
pub fn history_available(&self, ignore_current_item: bool) -> bool {
|
||||
!self.history.is_empty() && ((ignore_current_item && self.history_index > 1)
|
||||
|| (!ignore_current_item && self.history_index != 0))
|
||||
}
|
||||
|
||||
pub fn history_depleted(&self) -> bool {
|
||||
self.history.is_empty() || self.history_index >= self.history.len()
|
||||
}
|
||||
|
||||
pub fn save_m3u8<W: io::Write>(&mut self, w: &mut W) -> Result<(), PlayerError> {
|
||||
let mut playlist = MediaPlaylist {
|
||||
version: 6,
|
||||
|
|
|
@ -106,7 +106,7 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
|
|||
}
|
||||
}
|
||||
|
||||
fn enqeue_some(&mut self, count: usize) {
|
||||
fn enqueue_some(&mut self, count: usize) {
|
||||
//println!("Enqueuing up to {} items", count);
|
||||
match self.player.enqueue_modified(count, &self.modify()) {
|
||||
Err(e) => {
|
||||
|
@ -121,6 +121,21 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
|
|||
}
|
||||
}
|
||||
|
||||
fn enqueue_previous_some(&mut self, count: usize, ignore_last_item: bool) {
|
||||
//println!("Enqueuing up to {} items", count);
|
||||
match self.player.enqueue_previous_modified(count, &self.modify(), ignore_last_item) {
|
||||
Err(e) => {
|
||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||
}
|
||||
Ok(items) => {
|
||||
for item in items {
|
||||
// notify of new items that have been enqueued
|
||||
self.playback.send(PlaybackAction::Enqueued(item)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_empty(&self) {
|
||||
self.event.send(PlayerAction::Empty).unwrap();
|
||||
self.playback.send(PlaybackAction::Empty).unwrap();
|
||||
|
@ -134,7 +149,7 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
|
|||
fn run_loop(&mut self) {
|
||||
// this can panic since it's not on the main thread
|
||||
// initial queue fill
|
||||
self.enqeue_some(1);
|
||||
self.enqueue_some(1);
|
||||
let mut is_empty = self.player.queue_len() == 0;
|
||||
loop {
|
||||
let command = self.control.recv().unwrap();
|
||||
|
@ -151,10 +166,19 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
|
|||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||
}
|
||||
if !self.player.is_paused() {
|
||||
self.enqeue_some(1);
|
||||
self.enqueue_some(1);
|
||||
}
|
||||
}
|
||||
ControlAction::Previous { .. } => {
|
||||
let is_playing = !self.player.queue_empty();
|
||||
//println!("Executing previous command (queue_len: {}, is_playing? {}, history_depleted? {}, history_available? {})", self.player.queue_len(), is_playing, self.player.history_depleted(), self.player.history_available(is_playing));
|
||||
if self.player.history_available(is_playing) && !self.player.is_paused() {
|
||||
if let Err(e) = self.player.new_sink() {
|
||||
self.event.send(PlayerAction::Exception(e)).unwrap();
|
||||
}
|
||||
self.enqueue_previous_some(1, is_playing);
|
||||
}
|
||||
}
|
||||
ControlAction::Previous { .. } => {} // TODO
|
||||
ControlAction::Play { .. } => self.player.resume(),
|
||||
ControlAction::Pause { .. } => self.player.pause(),
|
||||
ControlAction::PlayPause { .. } => {
|
||||
|
@ -170,11 +194,11 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
|
|||
is_exiting = true;
|
||||
}
|
||||
ControlAction::Enqueue { amount, .. } => {
|
||||
self.enqeue_some(amount);
|
||||
self.enqueue_some(amount);
|
||||
}
|
||||
ControlAction::NoOp { .. } => {} // empty by design
|
||||
ControlAction::SetVolume { volume, .. } => {
|
||||
self.player.set_volume((volume as f32) / (u32::MAX as f32));
|
||||
self.player.set_volume((volume as f32) / 100.0);
|
||||
}
|
||||
ControlAction::CheckEmpty { .. } => {
|
||||
check_empty = true;
|
||||
|
@ -183,7 +207,7 @@ impl<I: std::iter::Iterator<Item = Result<Item, InterpreterError>>> PlayerServer
|
|||
|
||||
// keep queue full (while playing music)
|
||||
if self.player.queue_len() == 0 && !self.player.is_paused() && !is_exiting {
|
||||
self.enqeue_some(1);
|
||||
self.enqueue_some(1);
|
||||
if self.player.queue_len() == 0 {
|
||||
// no more music to add
|
||||
is_exiting = !self.keep_alive || is_exiting;
|
||||
|
@ -256,7 +280,7 @@ pub enum ControlAction {
|
|||
Exit { ack: bool },
|
||||
Enqueue { amount: usize, ack: bool },
|
||||
NoOp { ack: bool },
|
||||
SetVolume { ack: bool, volume: u32 },
|
||||
SetVolume { ack: bool, volume: u32 }, // volume as a percent (100% is """normal""")
|
||||
CheckEmpty { ack: bool },
|
||||
}
|
||||
|
||||
|
|
|
@ -191,7 +191,10 @@ REPL-specific operations to help with writing Muss scripts: ?command
|
|||
Display the currently-playing item.
|
||||
|
||||
next
|
||||
Skip the rest of the currently-playing item and proceed to the next item.
|
||||
previous
|
||||
play
|
||||
pause
|
||||
Immediate song control actions to apply to the current item.
|
||||
|
||||
verbose
|
||||
Toggle verbose messages, like extra information on items in ?list and other goodies.";
|
||||
|
|
69
src/repl.rs
69
src/repl.rs
|
@ -751,7 +751,7 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) {
|
|||
}
|
||||
"?commands" => {
|
||||
writeln!(state.terminal, "{}", super::help::REPL_COMMANDS).expect(TERMINAL_WRITE_ERROR)
|
||||
},
|
||||
}
|
||||
"?now" => {
|
||||
let data = state.controller_debug.read().expect("Failed to get read lock for debug player data");
|
||||
if let Some(item) = &data.now_playing {
|
||||
|
@ -763,7 +763,7 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) {
|
|||
} else {
|
||||
writeln!(state.terminal, "Nothing playing").expect(TERMINAL_WRITE_ERROR)
|
||||
}
|
||||
},
|
||||
}
|
||||
"?next" => {
|
||||
state.controller_debug
|
||||
.read()
|
||||
|
@ -773,8 +773,71 @@ fn repl_commands(command_str: &str, state: &mut ReplState, args: &CliArgs) {
|
|||
.lock()
|
||||
.expect("Failed to get lock for control action sender")
|
||||
.send(muss_player::ControlAction::Next { ack: false })
|
||||
.unwrap_or(());
|
||||
.expect("Failed to send control action");
|
||||
},
|
||||
"?previous" => {
|
||||
state.controller_debug
|
||||
.read()
|
||||
.expect("Failed to get read lock for debug player data")
|
||||
.control_tx.as_ref()
|
||||
.expect("Control action sender shouldn't be None")
|
||||
.lock()
|
||||
.expect("Failed to get lock for control action sender")
|
||||
.send(muss_player::ControlAction::Previous { ack: false })
|
||||
.expect("Failed to send control action");
|
||||
},
|
||||
"?pause" => {
|
||||
state.controller_debug
|
||||
.read()
|
||||
.expect("Failed to get read lock for debug player data")
|
||||
.control_tx.as_ref()
|
||||
.expect("Control action sender shouldn't be None")
|
||||
.lock()
|
||||
.expect("Failed to get lock for control action sender")
|
||||
.send(muss_player::ControlAction::Pause { ack: false })
|
||||
.expect("Failed to send control action");
|
||||
}
|
||||
"?play" => {
|
||||
state.controller_debug
|
||||
.read()
|
||||
.expect("Failed to get read lock for debug player data")
|
||||
.control_tx.as_ref()
|
||||
.expect("Control action sender shouldn't be None")
|
||||
.lock()
|
||||
.expect("Failed to get lock for control action sender")
|
||||
.send(muss_player::ControlAction::Play { ack: false })
|
||||
.expect("Failed to send control action");
|
||||
}
|
||||
"?volume" => volume_cmd(&words, state),
|
||||
"?add-to" => add_to_playlist_cmd(&words, state),
|
||||
_ => writeln!(state.terminal, "Unknown command, try ?help").expect(TERMINAL_WRITE_ERROR),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_to_playlist_cmd(cmd_args: &[&str], state: &mut ReplState) {
|
||||
if let Some(file_arg) = cmd_args.get(1) {
|
||||
writeln!(state.terminal, "Got ?add-to {}, doing nothing (this command is not implemented)", file_arg).expect(TERMINAL_WRITE_ERROR);
|
||||
} else {
|
||||
writeln!(state.terminal, "Missing ?add-to parameter, usage: ?add-to <playlist file>").expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
fn volume_cmd(cmd_args: &[&str], state: &mut ReplState) {
|
||||
if let Some(volume_arg) = cmd_args.get(1) {
|
||||
let volume_result: Result<f32, _> = volume_arg.parse();
|
||||
match volume_result {
|
||||
Ok(vol) => state.controller_debug
|
||||
.read()
|
||||
.expect("Failed to get read lock for debug player data")
|
||||
.control_tx.as_ref()
|
||||
.expect("Control action sender shouldn't be None")
|
||||
.lock()
|
||||
.expect("Failed to get lock for control action sender")
|
||||
.send(muss_player::ControlAction::SetVolume { ack: false, volume: (vol * 100.0).round() as _ })
|
||||
.expect("Failed to send control action"),
|
||||
Err(e) => writeln!(state.terminal, "Error parsing ?volume <float> parameter: {}", e).expect(TERMINAL_WRITE_ERROR)
|
||||
}
|
||||
} else {
|
||||
writeln!(state.terminal, "Missing ?volume parameter, usage: ?volume <float>").expect(TERMINAL_WRITE_ERROR);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue