diff --git a/src/commands.rs b/src/commands.rs index 402e6c3..c13725d 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -297,6 +297,28 @@ impl CommandManager { }), ); } + + { + let spotify = spotify.clone(); + self.register( + "seek", + Vec::new(), + Box::new(move |_s, args| { + if let Some(arg) = args.get(0) { + match arg.chars().next().unwrap() { + '+' | '-' => { + spotify.seek_relative(arg.parse::().unwrap_or(0)); + }, + _ => { + spotify.seek(arg.parse::().unwrap_or(0)); + } + } + } + + Ok(None) + }), + ); + } } fn handle_aliases(&self, name: &String) -> String { @@ -308,7 +330,7 @@ impl CommandManager { } pub fn handle(&self, s: &mut Cursive, cmd: String) { - let components: Vec = cmd.split(' ').map(|s| s.to_string()).collect(); + let components: Vec = cmd.trim().split(' ').map(|s| s.to_string()).collect(); let result = if let Some(cb) = self.commands.get(&self.handle_aliases(&components[0])) { cb(s, components[1..].to_vec()) @@ -363,6 +385,8 @@ impl CommandManager { kb.insert("Enter".into(), "play selected".into()); kb.insert("d".into(), "delete selected".into()); kb.insert("/".into(), "search".into()); + kb.insert(".".into(), "seek +500".into()); + kb.insert(",".into(), "seek -500".into()); kb.insert("F1".into(), "queue".into()); kb.insert("F2".into(), "search".into()); diff --git a/src/mpris.rs b/src/mpris.rs index 6e0357f..cfdd80f 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -35,7 +35,7 @@ fn get_metadata(queue: Arc) -> HashMap>> { ); hm.insert( "mpris:length".to_string(), - Variant(Box::new(track.map(|t| t.duration * 1_000_000).unwrap_or(0))), + Variant(Box::new(track.map(|t| t.duration * 1_000).unwrap_or(0))), ); hm.insert( "mpris:artUrl".to_string(), diff --git a/src/spotify.rs b/src/spotify.rs index d4bf59e..cfc27c7 100644 --- a/src/spotify.rs +++ b/src/spotify.rs @@ -40,6 +40,7 @@ enum WorkerCommand { Play, Pause, Stop, + Seek(u32), RequestToken(oneshot::Sender), } @@ -135,6 +136,9 @@ impl futures::Future for Worker { self.events.send(Event::Player(PlayerEvent::Stopped)); self.active = false; } + WorkerCommand::Seek(pos) => { + self.player.seek(pos); + } WorkerCommand::RequestToken(sender) => { self.token_task = Spotify::get_token(&self.session, sender); progress = true; @@ -499,4 +503,21 @@ impl Spotify { info!("stop()"); self.channel.unbounded_send(WorkerCommand::Stop).unwrap(); } + + pub fn seek(&self, position_ms: u32) { + self.set_elapsed(Some(Duration::from_millis(position_ms.into()))); + self.set_since(if self.get_current_status() == PlayerEvent::Playing { + Some(SystemTime::now()) + } else { + None + }); + + self.channel.unbounded_send(WorkerCommand::Seek(position_ms)).unwrap(); + } + + pub fn seek_relative(&self, delta: i32) { + let progress = self.get_current_progress(); + let new = (progress.as_secs() * 1000) as i32 + progress.subsec_millis() as i32 + delta; + self.seek(std::cmp::max(0, new) as u32); + } } diff --git a/src/track.rs b/src/track.rs index c337505..e96dbfe 100644 --- a/src/track.rs +++ b/src/track.rs @@ -44,7 +44,7 @@ impl Track { title: track.name.clone(), track_number: track.track_number, disc_number: track.disc_number, - duration: track.duration_ms / 1000, + duration: track.duration_ms, artists: artists, album: track.album.name.clone(), album_artists: album_artists, @@ -54,8 +54,8 @@ impl Track { } pub fn duration_str(&self) -> String { - let minutes = self.duration / 60; - let seconds = self.duration % 60; + let minutes = self.duration / 60_000; + let seconds = (self.duration / 1000) % 60; format!("{:02}:{:02}", minutes, seconds) } } diff --git a/src/ui/layout.rs b/src/ui/layout.rs index f85979f..caec4b8 100644 --- a/src/ui/layout.rs +++ b/src/ui/layout.rs @@ -29,6 +29,7 @@ pub struct Layout { error: Option, error_time: Option, screenchange: bool, + last_size: Vec2, ev: events::EventManager, theme: Theme, } @@ -49,8 +50,9 @@ impl Layout { cmdline_focus: false, error: None, error_time: None, - ev: ev.clone(), screenchange: true, + last_size: Vec2::new(0, 0), + ev: ev.clone(), theme: theme, } } @@ -166,6 +168,30 @@ impl View for Layout { } fn on_event(&mut self, event: Event) -> EventResult { + if let Event::Mouse { + position, + .. + } = event { + let error = self.get_error(); + + let cmdline_visible = self.cmdline.get_content().len() > 0; + let mut cmdline_height = if cmdline_visible { 1 } else { 0 }; + if error.is_some() { + cmdline_height += 1; + } + + if position.y < self.last_size.y - 2 - cmdline_height { + if let Some(ref id) = self.focus { + let screen = self.views.get_mut(id).unwrap(); + screen.view.on_event(event.clone()); + } + } else if position.y < self.last_size.y - cmdline_height { + self.statusbar.on_event(event.relativized(Vec2::new(0, self.last_size.y - 2 - cmdline_height))); + } + + return EventResult::Consumed(None); + } + if self.cmdline_focus { return self.cmdline.on_event(event); } @@ -179,6 +205,10 @@ impl View for Layout { } fn layout(&mut self, size: Vec2) { + self.last_size = size; + + self.statusbar.layout(Vec2::new(size.x, 2)); + self.cmdline.layout(Vec2::new(size.x, 1)); if let Some(ref id) = self.focus { diff --git a/src/ui/statusbar.rs b/src/ui/statusbar.rs index 2e2854d..755718e 100644 --- a/src/ui/statusbar.rs +++ b/src/ui/statusbar.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use cursive::align::HAlign; +use cursive::event::{Event, EventResult, MouseButton, MouseEvent}; use cursive::theme::{ColorStyle, ColorType, PaletteColor}; use cursive::traits::View; use cursive::vec::Vec2; @@ -13,6 +14,7 @@ use spotify::{PlayerEvent, Spotify}; pub struct StatusBar { queue: Arc, spotify: Arc, + last_size: Vec2, } impl StatusBar { @@ -20,6 +22,7 @@ impl StatusBar { StatusBar { queue: queue, spotify: spotify, + last_size: Vec2::new(0, 0), } } } @@ -63,6 +66,8 @@ impl View for StatusBar { if let Some(ref t) = self.queue.get_current() { let elapsed = self.spotify.get_current_progress(); + let elapsed_ms = elapsed.as_secs() as u32 * 1000 + elapsed.subsec_millis(); + let formatted_elapsed = format!( "{:02}:{:02}", elapsed.as_secs() / 60, @@ -80,7 +85,7 @@ impl View for StatusBar { printer.with_color(style_bar, |printer| { printer.print((0, 0), &"—".repeat(printer.size.x)); let duration_width = - (((printer.size.x as u32) * (elapsed.as_secs() as u32)) / t.duration) as usize; + (((printer.size.x as u32) * elapsed_ms) / t.duration) as usize; printer.print((0, 0), &format!("{}{}", "=".repeat(duration_width), ">")); }); } else { @@ -90,7 +95,50 @@ impl View for StatusBar { } } + fn layout(&mut self, size: Vec2) { + self.last_size = size; + } + fn required_size(&mut self, constraint: Vec2) -> Vec2 { Vec2::new(constraint.x, 2) } + + fn on_event(&mut self, event: Event) -> EventResult { + if let Event::Mouse { + offset, + position, + event + } = event { + let position = position - offset; + + if position.y == 0 { + if event == MouseEvent::WheelUp { + self.spotify.seek_relative(-500); + } + + if event == MouseEvent::WheelDown { + self.spotify.seek_relative(500); + } + + if event == MouseEvent::Press(MouseButton::Left) || + event == MouseEvent::Hold(MouseButton::Left) + { + if let Some(ref t) = self.queue.get_current() { + let f: f32 = position.x as f32 / self.last_size.x as f32; + let new = t.duration as f32 * f; + self.spotify.seek(new as u32); + } + + } + } else { + if event == MouseEvent::Press(MouseButton::Left) { + self.queue.toggleplayback(); + } + } + + EventResult::Consumed(None) + } else { + EventResult::Ignored + } + } }