Add shuffle and repeat

This commit is contained in:
KoffeinFlummi
2019-03-25 18:31:39 +01:00
parent e6a7d8a693
commit 45062bd89e
7 changed files with 270 additions and 50 deletions

View File

@@ -30,6 +30,7 @@ tokio-core = "0.1"
tokio-timer = "0.2" tokio-timer = "0.2"
unicode-width = "0.1.5" unicode-width = "0.1.5"
dbus = { version = "0.6.4", optional = true } dbus = { version = "0.6.4", optional = true }
rand = "0.6.5"
[dependencies.librespot] [dependencies.librespot]
git = "https://github.com/librespot-org/librespot.git" git = "https://github.com/librespot-org/librespot.git"

View File

@@ -5,7 +5,7 @@ use cursive::event::{Event, Key};
use cursive::Cursive; use cursive::Cursive;
use playlists::{Playlist, Playlists}; use playlists::{Playlist, Playlists};
use queue::Queue; use queue::{Queue, RepeatSetting};
use spotify::Spotify; use spotify::Spotify;
use track::Track; use track::Track;
use ui::layout::Layout; use ui::layout::Layout;
@@ -84,7 +84,7 @@ impl CommandManager {
"next", "next",
Vec::new(), Vec::new(),
Box::new(move |_s, _args| { Box::new(move |_s, _args| {
queue.next(); queue.next(true);
Ok(None) Ok(None)
}), }),
); );
@@ -240,7 +240,7 @@ impl CommandManager {
{ {
let queue = queue.clone(); let queue = queue.clone();
s.call_on_id("queue_list", |v: &mut ListView<Track>| { s.call_on_id("queue_list", |v: &mut ListView<Track>| {
queue.play(v.get_selected_index()); queue.play(v.get_selected_index(), true);
}); });
} }
@@ -248,8 +248,8 @@ impl CommandManager {
let queue = queue.clone(); let queue = queue.clone();
s.call_on_id("list", |v: &mut ListView<Track>| { s.call_on_id("list", |v: &mut ListView<Track>| {
v.with_selected(Box::new(move |t| { v.with_selected(Box::new(move |t| {
let index = queue.append_next(t); let index = queue.append_next(vec![t]);
queue.play(index); queue.play(index, true);
})); }));
}); });
} }
@@ -258,11 +258,8 @@ impl CommandManager {
let queue = queue.clone(); let queue = queue.clone();
s.call_on_id("list", |v: &mut ListView<Playlist>| { s.call_on_id("list", |v: &mut ListView<Playlist>| {
v.with_selected(Box::new(move |pl| { v.with_selected(Box::new(move |pl| {
let indices: Vec<usize> = let index = queue.append_next(pl.tracks.iter().collect());
pl.tracks.iter().map(|t| queue.append_next(t)).collect(); queue.play(index, true);
if let Some(i) = indices.get(0) {
queue.play(*i)
}
})); }));
}); });
} }
@@ -298,6 +295,57 @@ impl CommandManager {
); );
} }
{
let queue = queue.clone();
self.register(
"shuffle",
Vec::new(),
Box::new(move |_s, args| {
if let Some(arg) = args.get(0) {
queue.set_shuffle(match arg.as_ref() {
"on" => true,
"off" => false,
_ => {
return Err("Unknown shuffle setting.".to_string());
}
});
} else {
queue.set_shuffle(!queue.get_shuffle());
}
Ok(None)
}),
);
}
{
let queue = queue.clone();
self.register(
"repeat",
vec!["loop"],
Box::new(move |_s, args| {
if let Some(arg) = args.get(0) {
queue.set_repeat(match arg.as_ref() {
"list" | "playlist" | "queue" => RepeatSetting::RepeatPlaylist,
"track" | "once" => RepeatSetting::RepeatTrack,
"none" | "off" => RepeatSetting::None,
_ => {
return Err("Unknown loop setting.".to_string());
}
});
} else {
queue.set_repeat(match queue.get_repeat() {
RepeatSetting::None => RepeatSetting::RepeatPlaylist,
RepeatSetting::RepeatPlaylist => RepeatSetting::RepeatTrack,
RepeatSetting::RepeatTrack => RepeatSetting::None,
});
}
Ok(None)
}),
);
}
{ {
let spotify = spotify.clone(); let spotify = spotify.clone();
self.register( self.register(
@@ -387,6 +435,8 @@ impl CommandManager {
kb.insert("/".into(), "search".into()); kb.insert("/".into(), "search".into());
kb.insert(".".into(), "seek +500".into()); kb.insert(".".into(), "seek +500".into());
kb.insert(",".into(), "seek -500".into()); kb.insert(",".into(), "seek -500".into());
kb.insert("r".into(), "repeat".into());
kb.insert("z".into(), "shuffle".into());
kb.insert("F1".into(), "queue".into()); kb.insert("F1".into(), "queue".into());
kb.insert("F2".into(), "search".into()); kb.insert("F2".into(), "search".into());

View File

@@ -12,6 +12,7 @@ pub struct Config {
pub password: String, pub password: String,
pub keybindings: Option<HashMap<String, String>>, pub keybindings: Option<HashMap<String, String>>,
pub theme: Option<ConfigTheme>, pub theme: Option<ConfigTheme>,
pub use_nerdfont: Option<bool>,
} }
#[derive(Serialize, Deserialize, Debug, Default, Clone)] #[derive(Serialize, Deserialize, Debug, Default, Clone)]

View File

@@ -25,6 +25,8 @@ extern crate log;
extern crate chrono; extern crate chrono;
extern crate fern; extern crate fern;
extern crate rand;
use std::process; use std::process;
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
@@ -122,8 +124,8 @@ fn main() {
let spotify = Arc::new(spotify::Spotify::new( let spotify = Arc::new(spotify::Spotify::new(
event_manager.clone(), event_manager.clone(),
cfg.username, cfg.username.clone(),
cfg.password, cfg.password.clone(),
)); ));
let queue = Arc::new(queue::Queue::new(spotify.clone())); let queue = Arc::new(queue::Queue::new(spotify.clone()));
@@ -154,7 +156,7 @@ fn main() {
let queueview = ui::queue::QueueView::new(queue.clone(), playlists.clone()); let queueview = ui::queue::QueueView::new(queue.clone(), playlists.clone());
let status = ui::statusbar::StatusBar::new(queue.clone(), spotify.clone()); let status = ui::statusbar::StatusBar::new(queue.clone(), spotify.clone(), &cfg);
let mut layout = ui::layout::Layout::new(status, &event_manager, theme) let mut layout = ui::layout::Layout::new(status, &event_manager, theme)
.view("search", search.with_id("search"), "Search") .view("search", search.with_id("search"), "Search")
@@ -196,7 +198,7 @@ fn main() {
match event { match event {
Event::Player(state) => { Event::Player(state) => {
if state == PlayerEvent::FinishedTrack { if state == PlayerEvent::FinishedTrack {
queue.next(); queue.next(false);
} }
spotify.update_status(state); spotify.update_status(state);

View File

@@ -7,7 +7,7 @@ use dbus::stdintf::org_freedesktop_dbus::PropertiesPropertiesChanged;
use dbus::tree::{Access, Factory}; use dbus::tree::{Access, Factory};
use dbus::{Path, SignalArgs}; use dbus::{Path, SignalArgs};
use queue::Queue; use queue::{Queue, RepeatSetting};
use spotify::{PlayerEvent, Spotify}; use spotify::{PlayerEvent, Spotify};
fn get_playbackstatus(spotify: Arc<Spotify>) -> String { fn get_playbackstatus(spotify: Arc<Spotify>) -> String {
@@ -176,13 +176,19 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
}) })
}; };
let property_loopstatus = f let property_loopstatus = {
.property::<String, _>("LoopStatus", ()) let queue = queue.clone();
f.property::<String, _>("LoopStatus", ())
.access(Access::Read) .access(Access::Read)
.on_get(|iter, _| { .on_get(move |iter, _| {
iter.append("None".to_string()); // TODO iter.append(match queue.get_repeat() {
RepeatSetting::None => "None",
RepeatSetting::RepeatTrack => "Track",
RepeatSetting::RepeatPlaylist => "Playlist",
}.to_string());
Ok(()) Ok(())
}); })
};
let property_metadata = { let property_metadata = {
let queue = queue.clone(); let queue = queue.clone();
@@ -287,6 +293,16 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
Ok(()) Ok(())
}); });
let property_shuffle = {
let queue = queue.clone();
f.property::<bool, _>("Shuffle", ())
.access(Access::Read)
.on_get(move |iter, _| {
iter.append(queue.get_shuffle());
Ok(())
})
};
let method_playpause = { let method_playpause = {
let spotify = spotify.clone(); let spotify = spotify.clone();
f.method("PlayPause", (), move |m| { f.method("PlayPause", (), move |m| {
@@ -322,7 +338,7 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
let method_next = { let method_next = {
let queue = queue.clone(); let queue = queue.clone();
f.method("Next", (), move |m| { f.method("Next", (), move |m| {
queue.next(); queue.next(true);
Ok(vec![m.msg.method_return()]) Ok(vec![m.msg.method_return()])
}) })
}; };
@@ -335,7 +351,7 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
}) })
}; };
// TODO: Seek, SetPosition, Shuffle, OpenUri (?) // TODO: Seek, SetPosition, OpenUri (?)
// https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html // https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html
let interface_player = f let interface_player = f
@@ -354,6 +370,7 @@ fn run_dbus_server(spotify: Arc<Spotify>, queue: Arc<Queue>, rx: mpsc::Receiver<
.add_p(property_cancontrol) .add_p(property_cancontrol)
.add_p(property_cangonext) .add_p(property_cangonext)
.add_p(property_cangoprevious) .add_p(property_cangoprevious)
.add_p(property_shuffle)
.add_m(method_playpause) .add_m(method_playpause)
.add_m(method_play) .add_m(method_play)
.add_m(method_pause) .add_m(method_pause)

View File

@@ -1,11 +1,22 @@
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use rand::prelude::*;
use spotify::Spotify; use spotify::Spotify;
use track::Track; use track::Track;
#[derive(Clone, Copy, PartialEq)]
pub enum RepeatSetting {
None,
RepeatPlaylist,
RepeatTrack,
}
pub struct Queue { pub struct Queue {
pub queue: Arc<RwLock<Vec<Track>>>, pub queue: Arc<RwLock<Vec<Track>>>,
random_order: RwLock<Option<Vec<usize>>>,
current_track: RwLock<Option<usize>>, current_track: RwLock<Option<usize>>,
repeat: RwLock<RepeatSetting>,
spotify: Arc<Spotify>, spotify: Arc<Spotify>,
} }
@@ -14,15 +25,26 @@ impl Queue {
Queue { Queue {
queue: Arc::new(RwLock::new(Vec::new())), queue: Arc::new(RwLock::new(Vec::new())),
current_track: RwLock::new(None), current_track: RwLock::new(None),
repeat: RwLock::new(RepeatSetting::None),
random_order: RwLock::new(None),
spotify: spotify, spotify: spotify,
} }
} }
pub fn next_index(&self) -> Option<usize> { pub fn next_index(&self) -> Option<usize> {
match *self.current_track.read().unwrap() { match *self.current_track.read().unwrap() {
Some(index) => { Some(mut index) => {
let next_index = index + 1; let random_order = self.random_order.read().unwrap();
if let Some(order) = random_order.as_ref() {
index = order.iter().position(|&i| i == index).unwrap();
}
let mut next_index = index + 1;
if next_index < self.queue.read().unwrap().len() { if next_index < self.queue.read().unwrap().len() {
if let Some(order) = random_order.as_ref() {
next_index = order[next_index];
}
Some(next_index) Some(next_index)
} else { } else {
None None
@@ -34,9 +56,19 @@ impl Queue {
pub fn previous_index(&self) -> Option<usize> { pub fn previous_index(&self) -> Option<usize> {
match *self.current_track.read().unwrap() { match *self.current_track.read().unwrap() {
Some(index) => { Some(mut index) => {
let random_order = self.random_order.read().unwrap();
if let Some(order) = random_order.as_ref() {
index = order.iter().position(|&i| i == index).unwrap();
}
if index > 0 { if index > 0 {
Some(index - 1) let mut next_index = index - 1;
if let Some(order) = random_order.as_ref() {
next_index = order[next_index];
}
Some(next_index)
} else { } else {
None None
} }
@@ -53,21 +85,38 @@ impl Queue {
} }
pub fn append(&self, track: &Track) { pub fn append(&self, track: &Track) {
let mut random_order = self.random_order.write().unwrap();
if let Some(order) = random_order.as_mut() {
let index = order.len() - 1;
order.push(index);
}
let mut q = self.queue.write().unwrap(); let mut q = self.queue.write().unwrap();
q.push(track.clone()); q.push(track.clone());
} }
pub fn append_next(&self, track: &Track) -> usize { pub fn append_next(&self, tracks: Vec<&Track>) -> usize {
let next = self.next_index();
let mut q = self.queue.write().unwrap(); let mut q = self.queue.write().unwrap();
if let Some(next_index) = next { {
q.insert(next_index, track.clone()); let mut random_order = self.random_order.write().unwrap();
next_index if let Some(order) = random_order.as_mut() {
} else { order.extend((q.len() - 1)..(q.len() + tracks.len()));
q.push(track.clone()); }
q.len() - 1
} }
let first = match *self.current_track.read().unwrap() {
Some(index) => index + 1,
None => q.len()
};
let mut i = first;
for track in tracks {
q.insert(i, track.clone());
i += 1;
}
first
} }
pub fn remove(&self, index: usize) { pub fn remove(&self, index: usize) {
@@ -90,12 +139,14 @@ impl Queue {
let current = *self.current_track.read().unwrap(); let current = *self.current_track.read().unwrap();
if let Some(current_track) = current { if let Some(current_track) = current {
if index == current_track { if index == current_track {
self.play(index); self.play(index, false);
} else if index < current_track { } else if index < current_track {
let mut current = self.current_track.write().unwrap(); let mut current = self.current_track.write().unwrap();
current.replace(current_track - 1); current.replace(current_track - 1);
} }
} }
self.generate_random_order();
} }
pub fn clear(&self) { pub fn clear(&self) {
@@ -103,9 +154,12 @@ impl Queue {
let mut q = self.queue.write().unwrap(); let mut q = self.queue.write().unwrap();
q.clear(); q.clear();
let mut random_order = self.random_order.write().unwrap();
random_order.as_mut().map(|o| o.clear());
} }
pub fn play(&self, index: usize) { pub fn play(&self, index: usize, reshuffle: bool) {
if let Some(track) = &self.queue.read().unwrap().get(index) { if let Some(track) = &self.queue.read().unwrap().get(index) {
self.spotify.load(&track); self.spotify.load(&track);
let mut current = self.current_track.write().unwrap(); let mut current = self.current_track.write().unwrap();
@@ -113,6 +167,10 @@ impl Queue {
self.spotify.play(); self.spotify.play();
self.spotify.update_track(); self.spotify.update_track();
} }
if reshuffle {
self.generate_random_order()
}
} }
pub fn toggleplayback(&self) { pub fn toggleplayback(&self) {
@@ -125,9 +183,20 @@ impl Queue {
self.spotify.stop(); self.spotify.stop();
} }
pub fn next(&self) { pub fn next(&self, manual: bool) {
if let Some(index) = self.next_index() { let q = self.queue.read().unwrap();
self.play(index); let current = *self.current_track.read().unwrap();
let repeat = *self.repeat.read().unwrap();
if repeat == RepeatSetting::RepeatTrack && !manual {
if let Some(index) = current {
self.play(index, false);
}
} else if let Some(index) = self.next_index() {
self.play(index, false);
} else if repeat == RepeatSetting::RepeatPlaylist && q.len() > 0 {
let random_order = self.random_order.read().unwrap();
self.play(random_order.as_ref().map(|o| o[0]).unwrap_or(0), false);
} else { } else {
self.spotify.stop(); self.spotify.stop();
} }
@@ -135,9 +204,51 @@ impl Queue {
pub fn previous(&self) { pub fn previous(&self) {
if let Some(index) = self.previous_index() { if let Some(index) = self.previous_index() {
self.play(index); self.play(index, false);
} else { } else {
self.spotify.stop(); self.spotify.stop();
} }
} }
pub fn get_repeat(&self) -> RepeatSetting {
let repeat = self.repeat.read().unwrap();
*repeat
}
pub fn set_repeat(&self, new: RepeatSetting) {
let mut repeat = self.repeat.write().unwrap();
*repeat = new;
}
pub fn get_shuffle(&self) -> bool {
let random_order = self.random_order.read().unwrap();
random_order.is_some()
}
fn generate_random_order(&self) {
let q = self.queue.read().unwrap();
let mut order: Vec<usize> = Vec::with_capacity(q.len());
let mut random: Vec<usize> = (0..q.len()).collect();
if let Some(current) = *self.current_track.read().unwrap() {
order.push(current);
random.remove(current);
}
let mut rng = rand::thread_rng();
random.shuffle(&mut rng);
order.extend(random);
let mut random_order = self.random_order.write().unwrap();
*random_order = Some(order);
}
pub fn set_shuffle(&self, new: bool) {
if new {
self.generate_random_order();
} else {
let mut random_order = self.random_order.write().unwrap();
*random_order = None;
}
}
} }

View File

@@ -8,21 +8,24 @@ use cursive::vec::Vec2;
use cursive::Printer; use cursive::Printer;
use unicode_width::UnicodeWidthStr; use unicode_width::UnicodeWidthStr;
use queue::Queue; use config::Config;
use queue::{Queue, RepeatSetting};
use spotify::{PlayerEvent, Spotify}; use spotify::{PlayerEvent, Spotify};
pub struct StatusBar { pub struct StatusBar {
queue: Arc<Queue>, queue: Arc<Queue>,
spotify: Arc<Spotify>, spotify: Arc<Spotify>,
last_size: Vec2, last_size: Vec2,
use_nerdfont: bool,
} }
impl StatusBar { impl StatusBar {
pub fn new(queue: Arc<Queue>, spotify: Arc<Spotify>) -> StatusBar { pub fn new(queue: Arc<Queue>, spotify: Arc<Spotify>, cfg: &Config) -> StatusBar {
StatusBar { StatusBar {
queue: queue, queue: queue,
spotify: spotify, spotify: spotify,
last_size: Vec2::new(0, 0), last_size: Vec2::new(0, 0),
use_nerdfont: cfg.use_nerdfont.unwrap_or(false)
} }
} }
} }
@@ -53,17 +56,45 @@ impl View for StatusBar {
); );
}); });
let state_icon = match self.spotify.get_current_status() { let state_icon = if self.use_nerdfont {
PlayerEvent::Playing => "", match self.spotify.get_current_status() {
PlayerEvent::Paused => "▮▮", PlayerEvent::Playing => "\u{f909} ",
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => " ", PlayerEvent::Paused => "\u{f8e3} ",
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => "\u{f9da} ",
}
} else {
match self.spotify.get_current_status() {
PlayerEvent::Playing => "",
PlayerEvent::Paused => "▮▮",
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => "",
}
} }
.to_string(); .to_string();
printer.with_color(style, |printer| { printer.with_color(style, |printer| {
printer.print((0, 1), &state_icon); printer.print((1, 1), &state_icon);
}); });
let repeat = if self.use_nerdfont {
match self.queue.get_repeat() {
RepeatSetting::None => "",
RepeatSetting::RepeatPlaylist => "\u{f955} ",
RepeatSetting::RepeatTrack => "\u{f957} ",
}
} else {
match self.queue.get_repeat() {
RepeatSetting::None => "",
RepeatSetting::RepeatPlaylist => "[R] ",
RepeatSetting::RepeatTrack => "[R1] ",
}
}.to_string();
let shuffle = if self.use_nerdfont {
if self.queue.get_shuffle() { "\u{f99c} " } else { "" }
} else {
if self.queue.get_shuffle() { "[Z] " } else { "" }
}.to_string();
if let Some(ref t) = self.queue.get_current() { if let Some(ref t) = self.queue.get_current() {
let elapsed = self.spotify.get_current_progress(); let elapsed = self.spotify.get_current_progress();
let elapsed_ms = elapsed.as_secs() as u32 * 1000 + elapsed.subsec_millis(); let elapsed_ms = elapsed.as_secs() as u32 * 1000 + elapsed.subsec_millis();
@@ -74,12 +105,12 @@ impl View for StatusBar {
elapsed.as_secs() % 60 elapsed.as_secs() % 60
); );
let duration = format!("{} / {} ", formatted_elapsed, t.duration_str()); let right = repeat + &shuffle + &format!("{} / {} ", formatted_elapsed, t.duration_str());
let offset = HAlign::Right.get_offset(duration.width(), printer.size.x); let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
printer.with_color(style, |printer| { printer.with_color(style, |printer| {
printer.print((4, 1), &t.to_string()); printer.print((4, 1), &t.to_string());
printer.print((offset, 1), &duration); printer.print((offset, 1), &right);
}); });
printer.with_color(style_bar, |printer| { printer.with_color(style_bar, |printer| {
@@ -89,6 +120,13 @@ impl View for StatusBar {
printer.print((0, 0), &format!("{}{}", "=".repeat(duration_width), ">")); printer.print((0, 0), &format!("{}{}", "=".repeat(duration_width), ">"));
}); });
} else { } else {
let right = repeat + &shuffle;
let offset = HAlign::Right.get_offset(right.width(), printer.size.x);
printer.with_color(style, |printer| {
printer.print((offset, 1), &right);
});
printer.with_color(style_bar, |printer| { printer.with_color(style_bar, |printer| {
printer.print((0, 0), &"".repeat(printer.size.x)); printer.print((0, 0), &"".repeat(printer.size.x));
}); });