diff --git a/src/cli/clap.rs b/src/cli/clap.rs index f7b7483c..200e39de 100644 --- a/src/cli/clap.rs +++ b/src/cli/clap.rs @@ -48,6 +48,7 @@ can be used together format_arg() .default_value("%f %s %t - %a") .default_value_ifs(&[ + ("seek", None, "%f %s %t - %a %r"), ("volume", None, "%v% %f %s %t - %a"), ("transfer", None, "%f %s %t - %a on %d"), ]), @@ -119,6 +120,18 @@ jump to the previous song though, so you can use the previous flag twice: `spt p two songs back, you can use `spt pb -ppp` and so on.", ), ) + .arg( + Arg::with_name("seek") + .long("seek") + .takes_value(true) + .value_name("±SECONDS") + .allow_hyphen_values(true) + .help("Jumps SECONDS forwards (+) or backwards (-)") + .long_help( + "For example: `spt pb --seek +10` jumps ten second forwards, `spt pb --seek -10` ten \ +seconds backwards and `spt pb --seek 10` to the tenth second of the track.", + ), + ) .arg( Arg::with_name("volume") .short("v") diff --git a/src/cli/cli_app.rs b/src/cli/cli_app.rs index 13dcbb80..543dfdfa 100644 --- a/src/cli/cli_app.rs +++ b/src/cli/cli_app.rs @@ -271,6 +271,68 @@ impl<'a> CliApp<'a> { } } + pub async fn seek(&mut self, seconds_str: String) -> Result<()> { + let seconds = match seconds_str.parse::() { + Ok(s) => s.abs() as u32, + Err(_) => return Err(anyhow!("failed to convert seconds to i32")), + }; + + let (current_pos, duration) = { + self + .net + .handle_network_event(IoEvent::GetCurrentPlayback) + .await; + let app = self.net.app.lock().await; + if let Some(CurrentlyPlaybackContext { + progress_ms: Some(ms), + item: Some(item), + .. + }) = &app.current_playback_context + { + let duration = match item { + PlayingItem::Track(track) => track.duration_ms, + PlayingItem::Episode(episode) => episode.duration_ms, + }; + + (*ms as u32, duration) + } else { + return Err(anyhow!("no context available")); + } + }; + + // Convert secs to ms + let ms = seconds * 1000; + // Calculate new positon + let position_to_seek = if seconds_str.starts_with('+') { + current_pos + ms + } else if seconds_str.starts_with('-') { + // Jump to the beginning if the position_to_seek would be + // negative, must be checked before the calculation to avoid + // an 'underflow' + if ms > current_pos { + 0u32 + } else { + current_pos - ms + } + } else { + // Absolute value of the track + seconds * 1000 + }; + + // Check if position_to_seek is greater than duration (next track) + if position_to_seek > duration { + self.jump(&JumpDirection::Next).await; + } else { + // This seeks to a position in the current song + self + .net + .handle_network_event(IoEvent::Seek(position_to_seek)) + .await; + } + + Ok(()) + } + // spt playback --like / --shuffle / --repeat pub async fn mark(&mut self, flag: Flag) -> Result<()> { let c = { @@ -339,7 +401,10 @@ impl<'a> CliApp<'a> { let mut hs = match playing_item { PlayingItem::Track(track) => { let id = track.id.clone().unwrap_or_default(); - let mut hs = Format::from_type(FormatType::Track(Box::new(track))); + let mut hs = Format::from_type(FormatType::Track(Box::new(track.clone()))); + if let Some(ms) = context.progress_ms { + hs.push(Format::Position((ms, track.duration_ms))) + } hs.push(Format::Flags(( context.repeat_state, context.shuffle_state, @@ -348,7 +413,10 @@ impl<'a> CliApp<'a> { hs } PlayingItem::Episode(episode) => { - let mut hs = Format::from_type(FormatType::Episode(Box::new(episode))); + let mut hs = Format::from_type(FormatType::Episode(Box::new(episode.clone()))); + if let Some(ms) = context.progress_ms { + hs.push(Format::Position((ms, episode.duration_ms))) + } hs.push(Format::Flags(( context.repeat_state, context.shuffle_state, diff --git a/src/cli/handle.rs b/src/cli/handle.rs index 53bbb386..1e964a87 100644 --- a/src/cli/handle.rs +++ b/src/cli/handle.rs @@ -82,6 +82,9 @@ pub async fn handle_matches( if let Some(vol) = matches.value_of("volume") { cli.volume(vol.to_string()).await?; } + if let Some(secs) = matches.value_of("seek") { + cli.seek(secs.to_string()).await?; + } // Print out the status if no errors were found cli.get_status(format.to_string()).await diff --git a/src/cli/util.rs b/src/cli/util.rs index 71304c7d..22e6f177 100644 --- a/src/cli/util.rs +++ b/src/cli/util.rs @@ -144,6 +144,8 @@ pub enum Format { Uri(String), Device(String), Volume(u32), + // Current position, duration + Position((u32, u32)), // This is a bit long, should it be splitted up? Flags((RepeatState, bool, bool)), Playing(bool), @@ -206,6 +208,9 @@ impl Format { // Because this match statements // needs to return a &String, I have to do it this way Self::Volume(s) => s.to_string(), + Self::Position((curr, duration)) => { + crate::ui::util::display_track_progress(*curr as u128, *duration) + } Self::Flags((r, s, l)) => { let like = if *l { conf.behavior.liked_icon @@ -252,6 +257,7 @@ impl Format { Self::Uri(_) => "%u", Self::Device(_) => "%d", Self::Volume(_) => "%v", + Self::Position(_) => "%r", Self::Flags(_) => "%f", Self::Playing(_) => "%s", }