diff --git a/player/src/os_controls.rs b/player/src/os_controls.rs index b70c4bd..2ee33ff 100644 --- a/player/src/os_controls.rs +++ b/player/src/os_controls.rs @@ -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(()) }); diff --git a/player/src/player.rs b/player/src/player.rs index 1787e23..e830036 100644 --- a/player/src/player.rs +++ b/player/src/player.rs @@ -28,6 +28,8 @@ pub struct Player>> output_handle: OutputStreamHandle, #[cfg(feature = "mpd")] mpd_connection: Option>, + history: Vec, + history_index: usize, // distance from start of history Vec, 1-indexed (0 denotes history is exhausted) } impl>> Player { @@ -41,6 +43,8 @@ impl>> Player { output_handle: output_handle, #[cfg(feature = "mpd")] mpd_connection: None, + history: Vec::new(), + history_index: 0, }) } @@ -104,6 +108,23 @@ impl>> Player { pub fn enqueue(&mut self, count: usize) -> Result, 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>> Player { 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>> Player { ) -> Result, 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>> Player { 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>> Player { Ok(enqueued) } + pub fn enqueue_previous(&mut self, count: usize, ignore_current_item: bool,) -> Result, 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>, + Item, + ) -> Box + Send>, + ignore_current_item: bool, + ) -> Result, 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>> Player { 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(&mut self, w: &mut W) -> Result<(), PlayerError> { let mut playlist = MediaPlaylist { version: 6, diff --git a/player/src/player_wrapper.rs b/player/src/player_wrapper.rs index d9abdcb..cac875a 100644 --- a/player/src/player_wrapper.rs +++ b/player/src/player_wrapper.rs @@ -106,7 +106,7 @@ impl>> 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>> 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>> 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>> 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>> 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>> 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 }, } diff --git a/src/help.rs b/src/help.rs index 27c5442..de88e79 100644 --- a/src/help.rs +++ b/src/help.rs @@ -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."; diff --git a/src/repl.rs b/src/repl.rs index 50c328d..3aa1cf0 100644 --- a/src/repl.rs +++ b/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 ").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 = 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 parameter: {}", e).expect(TERMINAL_WRITE_ERROR) + } + } else { + writeln!(state.terminal, "Missing ?volume parameter, usage: ?volume ").expect(TERMINAL_WRITE_ERROR); + } +}