Skip to content

Commit

Permalink
Add ability to seek from the CLI (Rigellute#692)
Browse files Browse the repository at this point in the history
* Add new `--seek` to jump forward, backwards

To jump forward, prepend a + to the number of seconds.
To jump backwards, prepend a -.
To jump to an absolute position, do not prepend anything.

* Add new `%r` to the format options to display the current progress

Utilised by the `--seek` option.

* Add a longer help to the `--seek` option

* Fix formatting

* Add checks to prevent underflows + check to jump to next song if new duration is bigger than the duration of the song

* Fix formatting

* Fix clippy errors

* Add more comments
  • Loading branch information
OrangeFran authored and lanej committed Jul 13, 2021
1 parent f805ad4 commit b81fa70
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 2 deletions.
13 changes: 13 additions & 0 deletions src/cli/clap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
]),
Expand Down Expand Up @@ -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")
Expand Down
72 changes: 70 additions & 2 deletions src/cli/cli_app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,68 @@ impl<'a> CliApp<'a> {
}
}

pub async fn seek(&mut self, seconds_str: String) -> Result<()> {
let seconds = match seconds_str.parse::<i32>() {
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 = {
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions src/cli/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions src/cli/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -252,6 +257,7 @@ impl Format {
Self::Uri(_) => "%u",
Self::Device(_) => "%d",
Self::Volume(_) => "%v",
Self::Position(_) => "%r",
Self::Flags(_) => "%f",
Self::Playing(_) => "%s",
}
Expand Down

0 comments on commit b81fa70

Please sign in to comment.