Add item history functionality to controls and volume cli command

This commit is contained in:
NGnius (Graham) 2023-08-20 18:23:20 -04:00
parent 2cee3dd223
commit 99f853e47d
5 changed files with 222 additions and 13 deletions

View file

@ -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(())
});

View file

@ -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,

View file

@ -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 },
}

View file

@ -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.";

View file

@ -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);
}
}