From 69c182a489bf2e79334d40842070fbcca69fac36 Mon Sep 17 00:00:00 2001 From: ranmaru22 <16521734+ranmaru22@users.noreply.github.com> Date: Wed, 25 Sep 2024 15:56:11 +0200 Subject: [PATCH] feat: add next, previous, and seek functionality (#4) * Implement next, prev, and seek functionality This adds messages to skip to the next and previous sonds in the queue as well as seeking forward or backwards by 5s. No default keybindings are provided. * Implement seek_seconds option Allows modifying the time in seconds seek skips in the config file. Defaults to 5 seconds if not set. * Add into about new functions to CONFIGUTATION.md --------- Co-authored-by: Ranmaru --- CONFIGURATION.md | 8 +++++-- src/config.rs | 5 +++++ src/config/keybind.rs | 4 ++++ src/update.rs | 52 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 61f2623..da82283 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -77,6 +77,10 @@ multiple keybinds. | `left` | move left | `` | d | h | | `right` | move right | `` | n | l | | `toggle_playpause` | toggles between play and pause | p | | | +| `next song` | jumps to the next song in the queue | | | | +| `previous song` | jumps to the previous song in the queue | | | | +| `seek` | seeks forward by 5 seconds | | | | +| `seek_backwards` | seeks backwards by 5 seconds | | | | | `select` | act on the selected entry | `` | | | | `quit` | close the program | q | | | | `switch_to_library` | switch to library screen | 1 | | | @@ -93,8 +97,8 @@ multiple keybinds. | `toggle_single` | toggle single | s | | | | `toggle_consume` | toggle consume | c | | | | `toggle_random` | toggle random | z | | | -| `top` | jump to top | `` | < | g g | -| `bottom` | jump to bottom | `` | > | G | +| `top` | jump to top | `` | < | g g | +| `bottom` | jump to bottom | `` | > | G | Note that the dvorak/qwerty sets *do not* delete the default keybindings. diff --git a/src/config.rs b/src/config.rs index 47fd0a5..f6e9455 100644 --- a/src/config.rs +++ b/src/config.rs @@ -12,6 +12,7 @@ use keybind::{get_message, KeybindMap}; pub struct Config { pub keybindings: KeybindMap, pub theme: Theme, + pub seek_seconds: i64, } impl Config { @@ -19,6 +20,7 @@ impl Config { Config { keybindings: KeybindMap::default(), theme: Theme::new(), + seek_seconds: 5, } } pub fn try_read_config(mut self) -> Self { @@ -31,6 +33,9 @@ impl Config { for (key, value) in toml { match (key.as_str(), value) { ("keybindings", Value::Table(t)) => self.read_keybinds(t), + ("seek_seconds", Value::Integer(k)) if k > 0 => { + self.seek_seconds = k + } ("theme", Value::Table(t)) => self.read_theme(t), ("dvorak_keybindings", Value::Boolean(true)) => { self.keybindings = self.keybindings.with_dvorak_style(); diff --git a/src/config/keybind.rs b/src/config/keybind.rs index 7b1dec2..9c58d95 100644 --- a/src/config/keybind.rs +++ b/src/config/keybind.rs @@ -39,6 +39,10 @@ pub fn get_message(s: &str) -> Option { "toggle_single" => Some(Message::Set(Toggle::Single)), "toggle_consume" => Some(Message::Set(Toggle::Consume)), "toggle_random" => Some(Message::Set(Toggle::Random)), + "next_song" => Some(Message::NextSong), + "previous_song" => Some(Message::PreviousSong), + "seek" => Some(Message::Seek(SeekDirection::Forward)), + "seek_backwards" => Some(Message::Seek(SeekDirection::Backward)), _ => None, } } diff --git a/src/update.rs b/src/update.rs index 56ad03d..244da6f 100644 --- a/src/update.rs +++ b/src/update.rs @@ -4,8 +4,10 @@ use crate::model::proto::Searchable; use crate::model::{Model, Screen, State}; use crate::util::{safe_decrement, safe_increment}; use bitflags::bitflags; +use mpd::status::State as PlayState; use ratatui::crossterm::event::{self, KeyCode, KeyEvent}; use std::option::Option; +use std::time::Duration; pub mod build_library; mod handlers; @@ -55,10 +57,19 @@ pub enum Toggle { Consume, } +#[derive(PartialEq, Clone, Debug)] +pub enum SeekDirection { + Forward, + Backward, +} + #[derive(Clone, Debug)] pub enum Message { Direction(Dirs), PlayPause, + NextSong, + PreviousSong, + Seek(SeekDirection), Select, SwitchState(State), SwitchScreen(Screen), @@ -169,6 +180,47 @@ pub fn handle_msg(model: &mut Model, m: Message) -> Result { model.conn.toggle_pause()?; Ok(Update::STATUS) } + Message::NextSong => match model.status.state { + PlayState::Stop => Ok(Update::empty()), + _ => { + model.conn.next()?; + Ok(Update::CURRENT_SONG | Update::STATUS) + } + }, + Message::PreviousSong => match model.status.state { + PlayState::Stop => Ok(Update::empty()), + _ => { + model.conn.prev()?; + Ok(Update::CURRENT_SONG | Update::STATUS) + } + }, + Message::Seek(direction) => { + let mut update_flags = Update::empty(); + + if let (Some((current_pos, total)), Some(queue_pos)) = + (model.status.time, model.status.song) + { + let delta = Duration::from_secs( + model.config.seek_seconds.unsigned_abs(), + ); + + let new_pos = if direction == SeekDirection::Backward { + (current_pos.checked_sub(delta)) + .unwrap_or(Duration::default()) + } else { + (current_pos.checked_add(delta)).unwrap_or(total).min(total) + }; + + if new_pos >= total { + model.conn.next()?; + update_flags |= Update::CURRENT_SONG | Update::STATUS; + } else { + model.conn.seek(queue_pos.pos, new_pos)?; + update_flags |= Update::STATUS; + } + } + Ok(update_flags) + } Message::Set(t) => { match t { Toggle::Repeat => model.conn.repeat(!model.status.repeat),