Merge pull request #47 from KoffeinFlummi/seek-shuffle-repeat
Add seeking, shuffle and repeat
This commit is contained in:
@@ -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"
|
||||||
@@ -41,6 +42,7 @@ version = "0.11.1"
|
|||||||
default-features = false
|
default-features = false
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
pulseaudio_backend = ["librespot/pulseaudio-backend"]
|
||||||
rodio_backend = ["librespot/rodio-backend"]
|
rodio_backend = ["librespot/rodio-backend"]
|
||||||
portaudio_backend = ["librespot/portaudio-backend"]
|
portaudio_backend = ["librespot/portaudio-backend"]
|
||||||
termion_backend = ["cursive/termion-backend"]
|
termion_backend = ["cursive/termion-backend"]
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -297,6 +294,79 @@ 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();
|
||||||
|
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::<i32>().unwrap_or(0));
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
spotify.seek(arg.parse::<u32>().unwrap_or(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_aliases(&self, name: &String) -> String {
|
fn handle_aliases(&self, name: &String) -> String {
|
||||||
@@ -308,7 +378,7 @@ impl CommandManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle(&self, s: &mut Cursive, cmd: String) {
|
pub fn handle(&self, s: &mut Cursive, cmd: String) {
|
||||||
let components: Vec<String> = cmd.split(' ').map(|s| s.to_string()).collect();
|
let components: Vec<String> = cmd.trim().split(' ').map(|s| s.to_string()).collect();
|
||||||
|
|
||||||
let result = if let Some(cb) = self.commands.get(&self.handle_aliases(&components[0])) {
|
let result = if let Some(cb) = self.commands.get(&self.handle_aliases(&components[0])) {
|
||||||
cb(s, components[1..].to_vec())
|
cb(s, components[1..].to_vec())
|
||||||
@@ -363,6 +433,10 @@ impl CommandManager {
|
|||||||
kb.insert("Enter".into(), "play selected".into());
|
kb.insert("Enter".into(), "play selected".into());
|
||||||
kb.insert("d".into(), "delete selected".into());
|
kb.insert("d".into(), "delete selected".into());
|
||||||
kb.insert("/".into(), "search".into());
|
kb.insert("/".into(), "search".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());
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
18
src/main.rs
18
src/main.rs
@@ -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")
|
||||||
@@ -170,6 +172,14 @@ fn main() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
layout.cmdline.set_on_edit(move |s, cmd, _| {
|
||||||
|
s.call_on_id("main", |v: &mut ui::layout::Layout| {
|
||||||
|
if cmd.len() == 0 {
|
||||||
|
v.clear_cmdline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
let ev = event_manager.clone();
|
let ev = event_manager.clone();
|
||||||
layout.cmdline.set_on_submit(move |s, cmd| {
|
layout.cmdline.set_on_submit(move |s, cmd| {
|
||||||
@@ -196,7 +206,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);
|
||||||
|
|
||||||
|
|||||||
35
src/mpris.rs
35
src/mpris.rs
@@ -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 {
|
||||||
@@ -35,7 +35,7 @@ fn get_metadata(queue: Arc<Queue>) -> HashMap<String, Variant<Box<RefArg>>> {
|
|||||||
);
|
);
|
||||||
hm.insert(
|
hm.insert(
|
||||||
"mpris:length".to_string(),
|
"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(
|
hm.insert(
|
||||||
"mpris:artUrl".to_string(),
|
"mpris:artUrl".to_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)
|
||||||
|
|||||||
149
src/queue.rs
149
src/queue.rs
@@ -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,16 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.get_shuffle() {
|
||||||
|
self.generate_random_order();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear(&self) {
|
pub fn clear(&self) {
|
||||||
@@ -103,9 +156,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 +169,10 @@ impl Queue {
|
|||||||
self.spotify.play();
|
self.spotify.play();
|
||||||
self.spotify.update_track();
|
self.spotify.update_track();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if reshuffle && self.get_shuffle() {
|
||||||
|
self.generate_random_order()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn toggleplayback(&self) {
|
pub fn toggleplayback(&self) {
|
||||||
@@ -125,9 +185,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 +206,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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ enum WorkerCommand {
|
|||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
Stop,
|
Stop,
|
||||||
|
Seek(u32),
|
||||||
RequestToken(oneshot::Sender<Token>),
|
RequestToken(oneshot::Sender<Token>),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,6 +136,9 @@ impl futures::Future for Worker {
|
|||||||
self.events.send(Event::Player(PlayerEvent::Stopped));
|
self.events.send(Event::Player(PlayerEvent::Stopped));
|
||||||
self.active = false;
|
self.active = false;
|
||||||
}
|
}
|
||||||
|
WorkerCommand::Seek(pos) => {
|
||||||
|
self.player.seek(pos);
|
||||||
|
}
|
||||||
WorkerCommand::RequestToken(sender) => {
|
WorkerCommand::RequestToken(sender) => {
|
||||||
self.token_task = Spotify::get_token(&self.session, sender);
|
self.token_task = Spotify::get_token(&self.session, sender);
|
||||||
progress = true;
|
progress = true;
|
||||||
@@ -499,4 +503,21 @@ impl Spotify {
|
|||||||
info!("stop()");
|
info!("stop()");
|
||||||
self.channel.unbounded_send(WorkerCommand::Stop).unwrap();
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl Track {
|
|||||||
title: track.name.clone(),
|
title: track.name.clone(),
|
||||||
track_number: track.track_number,
|
track_number: track.track_number,
|
||||||
disc_number: track.disc_number,
|
disc_number: track.disc_number,
|
||||||
duration: track.duration_ms / 1000,
|
duration: track.duration_ms,
|
||||||
artists: artists,
|
artists: artists,
|
||||||
album: track.album.name.clone(),
|
album: track.album.name.clone(),
|
||||||
album_artists: album_artists,
|
album_artists: album_artists,
|
||||||
@@ -54,8 +54,8 @@ impl Track {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn duration_str(&self) -> String {
|
pub fn duration_str(&self) -> String {
|
||||||
let minutes = self.duration / 60;
|
let minutes = self.duration / 60_000;
|
||||||
let seconds = self.duration % 60;
|
let seconds = (self.duration / 1000) % 60;
|
||||||
format!("{:02}:{:02}", minutes, seconds)
|
format!("{:02}:{:02}", minutes, seconds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ pub struct Layout {
|
|||||||
error: Option<String>,
|
error: Option<String>,
|
||||||
error_time: Option<SystemTime>,
|
error_time: Option<SystemTime>,
|
||||||
screenchange: bool,
|
screenchange: bool,
|
||||||
|
last_size: Vec2,
|
||||||
ev: events::EventManager,
|
ev: events::EventManager,
|
||||||
theme: Theme,
|
theme: Theme,
|
||||||
}
|
}
|
||||||
@@ -49,8 +50,9 @@ impl Layout {
|
|||||||
cmdline_focus: false,
|
cmdline_focus: false,
|
||||||
error: None,
|
error: None,
|
||||||
error_time: None,
|
error_time: None,
|
||||||
ev: ev.clone(),
|
|
||||||
screenchange: true,
|
screenchange: true,
|
||||||
|
last_size: Vec2::new(0, 0),
|
||||||
|
ev: ev.clone(),
|
||||||
theme: theme,
|
theme: theme,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,6 +168,30 @@ impl View for Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn on_event(&mut self, event: Event) -> EventResult {
|
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 {
|
if self.cmdline_focus {
|
||||||
return self.cmdline.on_event(event);
|
return self.cmdline.on_event(event);
|
||||||
}
|
}
|
||||||
@@ -179,6 +205,10 @@ impl View for Layout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn layout(&mut self, size: Vec2) {
|
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));
|
self.cmdline.layout(Vec2::new(size.x, 1));
|
||||||
|
|
||||||
if let Some(ref id) = self.focus {
|
if let Some(ref id) = self.focus {
|
||||||
|
|||||||
@@ -1,25 +1,31 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use cursive::align::HAlign;
|
use cursive::align::HAlign;
|
||||||
|
use cursive::event::{Event, EventResult, MouseButton, MouseEvent};
|
||||||
use cursive::theme::{ColorStyle, ColorType, PaletteColor};
|
use cursive::theme::{ColorStyle, ColorType, PaletteColor};
|
||||||
use cursive::traits::View;
|
use cursive::traits::View;
|
||||||
use cursive::vec::Vec2;
|
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,
|
||||||
|
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),
|
||||||
|
use_nerdfont: cfg.use_nerdfont.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,47 +56,127 @@ 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 formatted_elapsed = format!(
|
let formatted_elapsed = format!(
|
||||||
"{:02}:{:02}",
|
"{:02}:{:02}",
|
||||||
elapsed.as_secs() / 60,
|
elapsed.as_secs() / 60,
|
||||||
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| {
|
||||||
printer.print((0, 0), &"—".repeat(printer.size.x));
|
printer.print((0, 0), &"—".repeat(printer.size.x));
|
||||||
let duration_width =
|
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), ">"));
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn layout(&mut self, size: Vec2) {
|
||||||
|
self.last_size = size;
|
||||||
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
|
fn required_size(&mut self, constraint: Vec2) -> Vec2 {
|
||||||
Vec2::new(constraint.x, 2)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user