transform simple queue to a preserving, more complex kind
this is a pretty big but necessary change and might not be stable yet. some key points: - the queue is now responsible for playback controls and track management, as this was scattered between the queue and spotify objects. - because the queue is now retained, it should be easier to save it as a spotify playlist closes #12
This commit is contained in:
@@ -1,15 +1,13 @@
|
|||||||
use crossbeam_channel::{unbounded, Receiver, Sender, TryIter};
|
use crossbeam_channel::{unbounded, Receiver, Sender, TryIter};
|
||||||
use cursive::{CbFunc, Cursive};
|
use cursive::{CbFunc, Cursive};
|
||||||
|
|
||||||
use queue::QueueChange;
|
use queue::QueueEvent;
|
||||||
use spotify::PlayerStatus;
|
use spotify::PlayerEvent;
|
||||||
use track::Track;
|
|
||||||
use ui::playlist::PlaylistEvent;
|
use ui::playlist::PlaylistEvent;
|
||||||
|
|
||||||
pub enum Event {
|
pub enum Event {
|
||||||
Queue(QueueChange),
|
Queue(QueueEvent),
|
||||||
PlayerStatus(PlayerStatus),
|
Player(PlayerEvent),
|
||||||
PlayerTrack(Option<Track>),
|
|
||||||
Playlist(PlaylistEvent),
|
Playlist(PlaylistEvent),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
40
src/main.rs
40
src/main.rs
@@ -39,7 +39,8 @@ mod track;
|
|||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use events::{Event, EventManager};
|
use events::{Event, EventManager};
|
||||||
use queue::QueueChange;
|
use queue::QueueEvent;
|
||||||
|
use spotify::PlayerEvent;
|
||||||
use ui::playlist::PlaylistEvent;
|
use ui::playlist::PlaylistEvent;
|
||||||
|
|
||||||
fn init_logger(content: TextContent) {
|
fn init_logger(content: TextContent) {
|
||||||
@@ -106,28 +107,37 @@ fn main() {
|
|||||||
cursive.set_theme(theme::default());
|
cursive.set_theme(theme::default());
|
||||||
cursive.set_autorefresh(true);
|
cursive.set_autorefresh(true);
|
||||||
|
|
||||||
let queue = Arc::new(Mutex::new(queue::Queue::new(event_manager.clone())));
|
|
||||||
|
|
||||||
let spotify = Arc::new(spotify::Spotify::new(
|
let spotify = Arc::new(spotify::Spotify::new(
|
||||||
event_manager.clone(),
|
event_manager.clone(),
|
||||||
cfg.username,
|
cfg.username,
|
||||||
cfg.password,
|
cfg.password,
|
||||||
config::CLIENT_ID.to_string(),
|
config::CLIENT_ID.to_string(),
|
||||||
queue.clone(),
|
|
||||||
));
|
));
|
||||||
|
|
||||||
|
let queue = Arc::new(Mutex::new(queue::Queue::new(
|
||||||
|
event_manager.clone(),
|
||||||
|
spotify.clone(),
|
||||||
|
)));
|
||||||
|
|
||||||
// global player keybindings (play, pause, stop)
|
// global player keybindings (play, pause, stop)
|
||||||
{
|
{
|
||||||
let spotify = spotify.clone();
|
let queue = queue.clone();
|
||||||
cursive.add_global_callback('P', move |_s| {
|
cursive.add_global_callback('P', move |_s| {
|
||||||
spotify.toggleplayback();
|
queue.lock().expect("could not lock queue").toggleplayback();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let spotify = spotify.clone();
|
let queue = queue.clone();
|
||||||
cursive.add_global_callback('S', move |_s| {
|
cursive.add_global_callback('S', move |_s| {
|
||||||
spotify.stop();
|
queue.lock().expect("could not lock queue").stop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let queue = queue.clone();
|
||||||
|
cursive.add_global_callback('>', move |_s| {
|
||||||
|
queue.lock().expect("could not lock queue").next();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,12 +145,12 @@ fn main() {
|
|||||||
|
|
||||||
let mut playlists = ui::playlist::PlaylistView::new(queue.clone(), spotify.clone());
|
let mut playlists = ui::playlist::PlaylistView::new(queue.clone(), spotify.clone());
|
||||||
|
|
||||||
let mut queueview = ui::queue::QueueView::new(queue.clone(), spotify.clone());
|
let mut queueview = ui::queue::QueueView::new(queue.clone());
|
||||||
|
|
||||||
let logview_scroller = ScrollView::new(logview).scroll_strategy(ScrollStrategy::StickToBottom);
|
let logview_scroller = ScrollView::new(logview).scroll_strategy(ScrollStrategy::StickToBottom);
|
||||||
let logpanel = Panel::new(logview_scroller).title("Log");
|
let logpanel = Panel::new(logview_scroller).title("Log");
|
||||||
|
|
||||||
let status = ui::statusbar::StatusBar::new(spotify.clone());
|
let status = ui::statusbar::StatusBar::new(queue.clone(), spotify.clone());
|
||||||
|
|
||||||
let layout = ui::layout::Layout::new(status)
|
let layout = ui::layout::Layout::new(status)
|
||||||
.view("search", BoxView::with_full_height(search.view))
|
.view("search", BoxView::with_full_height(search.view))
|
||||||
@@ -162,7 +172,7 @@ fn main() {
|
|||||||
s.call_on_id("main", |v: &mut ui::layout::Layout| {
|
s.call_on_id("main", |v: &mut ui::layout::Layout| {
|
||||||
v.set_view("queue");
|
v.set_view("queue");
|
||||||
});
|
});
|
||||||
ev.send(Event::Queue(QueueChange::Show));
|
ev.send(Event::Queue(QueueEvent::Show));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,8 +199,12 @@ fn main() {
|
|||||||
trace!("event received");
|
trace!("event received");
|
||||||
match event {
|
match event {
|
||||||
Event::Queue(ev) => queueview.handle_ev(&mut cursive, ev),
|
Event::Queue(ev) => queueview.handle_ev(&mut cursive, ev),
|
||||||
Event::PlayerStatus(state) => spotify.update_status(state),
|
Event::Player(state) => {
|
||||||
Event::PlayerTrack(track) => spotify.update_track(track),
|
if state == PlayerEvent::FinishedTrack {
|
||||||
|
queue.lock().expect("could not lock queue").next();
|
||||||
|
}
|
||||||
|
spotify.update_status(state);
|
||||||
|
}
|
||||||
Event::Playlist(event) => playlists.handle_ev(&mut cursive, event),
|
Event::Playlist(event) => playlists.handle_ev(&mut cursive, event),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
125
src/queue.rs
125
src/queue.rs
@@ -1,57 +1,124 @@
|
|||||||
use std::collections::vec_deque::Iter;
|
use std::slice::Iter;
|
||||||
use std::collections::VecDeque;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use track::Track;
|
|
||||||
|
|
||||||
use events::{Event, EventManager};
|
use events::{Event, EventManager};
|
||||||
|
use spotify::Spotify;
|
||||||
|
use track::Track;
|
||||||
|
|
||||||
pub struct Queue {
|
pub struct Queue {
|
||||||
queue: VecDeque<Track>,
|
// TODO: put this in an RwLock instead of locking the whole Queue struct
|
||||||
|
queue: Vec<Track>,
|
||||||
|
current_track: Option<usize>,
|
||||||
|
spotify: Arc<Spotify>,
|
||||||
ev: EventManager,
|
ev: EventManager,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum QueueChange {
|
pub enum QueueEvent {
|
||||||
Dequeue,
|
Add(usize),
|
||||||
Enqueue,
|
|
||||||
Remove(usize),
|
Remove(usize),
|
||||||
Show,
|
Show,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Queue {
|
impl Queue {
|
||||||
pub fn new(ev: EventManager) -> Queue {
|
pub fn new(ev: EventManager, spotify: Arc<Spotify>) -> Queue {
|
||||||
Queue {
|
Queue {
|
||||||
queue: VecDeque::new(),
|
queue: Vec::new(),
|
||||||
|
current_track: None,
|
||||||
|
spotify: spotify,
|
||||||
ev: ev,
|
ev: ev,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn remove(&mut self, index: usize) -> Option<Track> {
|
|
||||||
match self.queue.remove(index) {
|
pub fn next_index(&self) -> Option<usize> {
|
||||||
Some(track) => {
|
match self.current_track {
|
||||||
debug!("Removed from queue: {}", &track);
|
Some(index) => {
|
||||||
self.ev.send(Event::Queue(QueueChange::Remove(index)));
|
let next_index = index + 1;
|
||||||
Some(track)
|
if next_index < self.queue.len() {
|
||||||
|
Some(next_index)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn enqueue(&mut self, track: Track) {
|
|
||||||
debug!("Queued: {}", &track);
|
pub fn get(&self, index: usize) -> &Track {
|
||||||
self.queue.push_back(track);
|
&self.queue[index]
|
||||||
self.ev.send(Event::Queue(QueueChange::Enqueue));
|
|
||||||
}
|
}
|
||||||
pub fn dequeue(&mut self) -> Option<Track> {
|
|
||||||
match self.queue.pop_front() {
|
pub fn get_current(&self) -> Option<&Track> {
|
||||||
Some(track) => {
|
match self.current_track {
|
||||||
debug!("Dequeued : {}", track);
|
Some(index) => Some(&self.queue[index]),
|
||||||
self.ev.send(Event::Queue(QueueChange::Dequeue));
|
|
||||||
Some(track)
|
|
||||||
}
|
|
||||||
None => None,
|
None => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn peek(&self) -> Option<&Track> {
|
|
||||||
self.queue.get(0)
|
pub fn append(&mut self, track: &Track) {
|
||||||
|
self.queue.push(track.clone());
|
||||||
|
self.ev
|
||||||
|
.send(Event::Queue(QueueEvent::Add(self.queue.len())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn append_next(&mut self, track: &Track) -> usize {
|
||||||
|
if let Some(next_index) = self.next_index() {
|
||||||
|
self.queue.insert(next_index, track.clone());
|
||||||
|
self.ev.send(Event::Queue(QueueEvent::Add(next_index)));
|
||||||
|
next_index
|
||||||
|
} else {
|
||||||
|
self.queue.push(track.clone());
|
||||||
|
self.ev
|
||||||
|
.send(Event::Queue(QueueEvent::Add(self.queue.len() - 1)));
|
||||||
|
self.queue.len() - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&mut self, index: usize) {
|
||||||
|
self.queue.remove(index);
|
||||||
|
self.ev.send(Event::Queue(QueueEvent::Remove(index)));
|
||||||
|
|
||||||
|
// if the queue is empty or we are at the end of the queue, stop
|
||||||
|
// playback
|
||||||
|
if self.queue.len() == 0 || index == self.queue.len() {
|
||||||
|
self.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we are deleting the currently playing track, play the track with
|
||||||
|
// the same index again, because the next track is now at the position
|
||||||
|
// of the one we deleted
|
||||||
|
if let Some(current_track) = self.current_track {
|
||||||
|
if current_track == index {
|
||||||
|
self.play(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn play(&mut self, index: usize) {
|
||||||
|
let track = &self.queue[index];
|
||||||
|
self.spotify.load(&track);
|
||||||
|
self.current_track = Some(index);
|
||||||
|
self.spotify.play();
|
||||||
|
self.spotify.update_track();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggleplayback(&self) {
|
||||||
|
self.spotify.toggleplayback();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(&mut self) {
|
||||||
|
self.spotify.stop();
|
||||||
|
self.current_track = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&mut self) {
|
||||||
|
if let Some(next_index) = self.next_index() {
|
||||||
|
self.play(next_index);
|
||||||
|
} else {
|
||||||
|
self.spotify.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn iter(&self) -> Iter<Track> {
|
pub fn iter(&self) -> Iter<Track> {
|
||||||
self.queue.iter()
|
self.queue.iter()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,11 @@ use futures::Future;
|
|||||||
use futures::Stream;
|
use futures::Stream;
|
||||||
use tokio_core::reactor::Core;
|
use tokio_core::reactor::Core;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::sync::Mutex;
|
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
use events::{Event, EventManager};
|
use events::{Event, EventManager};
|
||||||
use queue::Queue;
|
|
||||||
use track::Track;
|
use track::Track;
|
||||||
|
|
||||||
enum WorkerCommand {
|
enum WorkerCommand {
|
||||||
@@ -41,21 +38,20 @@ enum WorkerCommand {
|
|||||||
Stop,
|
Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub enum PlayerStatus {
|
pub enum PlayerEvent {
|
||||||
Playing,
|
Playing,
|
||||||
Paused,
|
Paused,
|
||||||
Stopped,
|
Stopped,
|
||||||
|
FinishedTrack,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Spotify {
|
pub struct Spotify {
|
||||||
status: RwLock<PlayerStatus>,
|
status: RwLock<PlayerEvent>,
|
||||||
track: RwLock<Option<Track>>,
|
|
||||||
pub api: SpotifyAPI,
|
pub api: SpotifyAPI,
|
||||||
elapsed: RwLock<Option<Duration>>,
|
elapsed: RwLock<Option<Duration>>,
|
||||||
since: RwLock<Option<SystemTime>>,
|
since: RwLock<Option<SystemTime>>,
|
||||||
channel: mpsc::UnboundedSender<WorkerCommand>,
|
channel: mpsc::UnboundedSender<WorkerCommand>,
|
||||||
events: EventManager,
|
|
||||||
user: String,
|
user: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +60,6 @@ struct Worker {
|
|||||||
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
||||||
player: Player,
|
player: Player,
|
||||||
play_task: Box<futures::Future<Item = (), Error = oneshot::Canceled>>,
|
play_task: Box<futures::Future<Item = (), Error = oneshot::Canceled>>,
|
||||||
queue: Arc<Mutex<Queue>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Worker {
|
impl Worker {
|
||||||
@@ -72,14 +67,12 @@ impl Worker {
|
|||||||
events: EventManager,
|
events: EventManager,
|
||||||
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
||||||
player: Player,
|
player: Player,
|
||||||
queue: Arc<Mutex<Queue>>,
|
|
||||||
) -> Worker {
|
) -> Worker {
|
||||||
Worker {
|
Worker {
|
||||||
events: events,
|
events: events,
|
||||||
commands: commands,
|
commands: commands,
|
||||||
player: player,
|
player: player,
|
||||||
play_task: Box::new(futures::empty()),
|
play_task: Box::new(futures::empty()),
|
||||||
queue: queue,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,21 +92,19 @@ impl futures::Future for Worker {
|
|||||||
match cmd {
|
match cmd {
|
||||||
WorkerCommand::Load(track) => {
|
WorkerCommand::Load(track) => {
|
||||||
self.play_task = Box::new(self.player.load(track.id, false, 0));
|
self.play_task = Box::new(self.player.load(track.id, false, 0));
|
||||||
info!("player loading track..");
|
info!("player loading track: {:?}", track);
|
||||||
self.events.send(Event::PlayerTrack(Some(track)));
|
|
||||||
}
|
}
|
||||||
WorkerCommand::Play => {
|
WorkerCommand::Play => {
|
||||||
self.player.play();
|
self.player.play();
|
||||||
self.events.send(Event::PlayerStatus(PlayerStatus::Playing));
|
self.events.send(Event::Player(PlayerEvent::Playing));
|
||||||
}
|
}
|
||||||
WorkerCommand::Pause => {
|
WorkerCommand::Pause => {
|
||||||
self.player.pause();
|
self.player.pause();
|
||||||
self.events.send(Event::PlayerStatus(PlayerStatus::Paused));
|
self.events.send(Event::Player(PlayerEvent::Paused));
|
||||||
}
|
}
|
||||||
WorkerCommand::Stop => {
|
WorkerCommand::Stop => {
|
||||||
self.player.stop();
|
self.player.stop();
|
||||||
self.events.send(Event::PlayerTrack(None));
|
self.events.send(Event::Player(PlayerEvent::Stopped));
|
||||||
self.events.send(Event::PlayerStatus(PlayerStatus::Stopped));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,19 +112,7 @@ impl futures::Future for Worker {
|
|||||||
Ok(Async::Ready(())) => {
|
Ok(Async::Ready(())) => {
|
||||||
debug!("end of track!");
|
debug!("end of track!");
|
||||||
progress = true;
|
progress = true;
|
||||||
|
self.events.send(Event::Player(PlayerEvent::FinishedTrack));
|
||||||
let mut queue = self.queue.lock().unwrap();
|
|
||||||
if let Some(track) = queue.dequeue() {
|
|
||||||
debug!("next track in queue: {}", track);
|
|
||||||
self.play_task = Box::new(self.player.load(track.id, false, 0));
|
|
||||||
self.player.play();
|
|
||||||
|
|
||||||
self.events.send(Event::PlayerTrack(Some(track)));
|
|
||||||
self.events.send(Event::PlayerStatus(PlayerStatus::Playing));
|
|
||||||
} else {
|
|
||||||
self.events.send(Event::PlayerTrack(None));
|
|
||||||
self.events.send(Event::PlayerStatus(PlayerStatus::Stopped));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Err(oneshot::Canceled) => {
|
Err(oneshot::Canceled) => {
|
||||||
@@ -152,13 +131,7 @@ impl futures::Future for Worker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Spotify {
|
impl Spotify {
|
||||||
pub fn new(
|
pub fn new(events: EventManager, user: String, password: String, client_id: String) -> Spotify {
|
||||||
events: EventManager,
|
|
||||||
user: String,
|
|
||||||
password: String,
|
|
||||||
client_id: String,
|
|
||||||
queue: Arc<Mutex<Queue>>,
|
|
||||||
) -> Spotify {
|
|
||||||
let session_config = SessionConfig::default();
|
let session_config = SessionConfig::default();
|
||||||
let player_config = PlayerConfig {
|
let player_config = PlayerConfig {
|
||||||
bitrate: Bitrate::Bitrate320,
|
bitrate: Bitrate::Bitrate320,
|
||||||
@@ -180,7 +153,6 @@ impl Spotify {
|
|||||||
player_config,
|
player_config,
|
||||||
credentials,
|
credentials,
|
||||||
client_id,
|
client_id,
|
||||||
queue,
|
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -190,13 +162,11 @@ impl Spotify {
|
|||||||
let api = SpotifyAPI::default().access_token(&token.access_token);
|
let api = SpotifyAPI::default().access_token(&token.access_token);
|
||||||
|
|
||||||
Spotify {
|
Spotify {
|
||||||
status: RwLock::new(PlayerStatus::Stopped),
|
status: RwLock::new(PlayerEvent::Stopped),
|
||||||
track: RwLock::new(None),
|
|
||||||
api: api,
|
api: api,
|
||||||
elapsed: RwLock::new(None),
|
elapsed: RwLock::new(None),
|
||||||
since: RwLock::new(None),
|
since: RwLock::new(None),
|
||||||
channel: tx,
|
channel: tx,
|
||||||
events: events,
|
|
||||||
user: user,
|
user: user,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -209,7 +179,6 @@ impl Spotify {
|
|||||||
player_config: PlayerConfig,
|
player_config: PlayerConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
client_id: String,
|
client_id: String,
|
||||||
queue: Arc<Mutex<Queue>>,
|
|
||||||
) {
|
) {
|
||||||
let mut core = Core::new().unwrap();
|
let mut core = Core::new().unwrap();
|
||||||
let handle = core.handle();
|
let handle = core.handle();
|
||||||
@@ -227,13 +196,13 @@ impl Spotify {
|
|||||||
let (player, _eventchannel) =
|
let (player, _eventchannel) =
|
||||||
Player::new(player_config, session, None, move || (backend)(None));
|
Player::new(player_config, session, None, move || (backend)(None));
|
||||||
|
|
||||||
let worker = Worker::new(events, commands, player, queue);
|
let worker = Worker::new(events, commands, player);
|
||||||
debug!("worker thread ready.");
|
debug!("worker thread ready.");
|
||||||
core.run(worker).unwrap();
|
core.run(worker).unwrap();
|
||||||
debug!("worker thread finished.");
|
debug!("worker thread finished.");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_status(&self) -> PlayerStatus {
|
pub fn get_current_status(&self) -> PlayerEvent {
|
||||||
let status = self
|
let status = self
|
||||||
.status
|
.status
|
||||||
.read()
|
.read()
|
||||||
@@ -241,14 +210,6 @@ impl Spotify {
|
|||||||
(*status).clone()
|
(*status).clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_track(&self) -> Option<Track> {
|
|
||||||
let track = self
|
|
||||||
.track
|
|
||||||
.read()
|
|
||||||
.expect("could not acquire read lock on current track");
|
|
||||||
(*track).clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current_progress(&self) -> Duration {
|
pub fn get_current_progress(&self) -> Duration {
|
||||||
self.get_elapsed().unwrap_or(Duration::from_secs(0))
|
self.get_elapsed().unwrap_or(Duration::from_secs(0))
|
||||||
+ self
|
+ self
|
||||||
@@ -306,23 +267,23 @@ impl Spotify {
|
|||||||
.user_playlist_tracks(&self.user, playlist_id, None, 50, 0, None)
|
.user_playlist_tracks(&self.user, playlist_id, None, 50, 0, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&self, track: Track) {
|
pub fn load(&self, track: &Track) {
|
||||||
info!("loading track: {:?}", track);
|
info!("loading track: {:?}", track);
|
||||||
self.channel
|
self.channel
|
||||||
.unbounded_send(WorkerCommand::Load(track))
|
.unbounded_send(WorkerCommand::Load(track.clone()))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_status(&self, new_status: PlayerStatus) {
|
pub fn update_status(&self, new_status: PlayerEvent) {
|
||||||
match new_status {
|
match new_status {
|
||||||
PlayerStatus::Paused => {
|
PlayerEvent::Paused => {
|
||||||
self.set_elapsed(Some(self.get_current_progress()));
|
self.set_elapsed(Some(self.get_current_progress()));
|
||||||
self.set_since(None);
|
self.set_since(None);
|
||||||
}
|
}
|
||||||
PlayerStatus::Playing => {
|
PlayerEvent::Playing => {
|
||||||
self.set_since(Some(SystemTime::now()));
|
self.set_since(Some(SystemTime::now()));
|
||||||
}
|
}
|
||||||
PlayerStatus::Stopped => {
|
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => {
|
||||||
self.set_elapsed(None);
|
self.set_elapsed(None);
|
||||||
self.set_since(None);
|
self.set_since(None);
|
||||||
}
|
}
|
||||||
@@ -335,15 +296,9 @@ impl Spotify {
|
|||||||
*status = new_status;
|
*status = new_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_track(&self, new_track: Option<Track>) {
|
pub fn update_track(&self) {
|
||||||
self.set_elapsed(None);
|
self.set_elapsed(None);
|
||||||
self.set_since(None);
|
self.set_since(None);
|
||||||
|
|
||||||
let mut track = self
|
|
||||||
.track
|
|
||||||
.write()
|
|
||||||
.expect("could not acquire write lock on current track");
|
|
||||||
*track = new_track;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play(&self) {
|
pub fn play(&self) {
|
||||||
@@ -357,8 +312,8 @@ impl Spotify {
|
|||||||
.read()
|
.read()
|
||||||
.expect("could not acquire read lock on player state");
|
.expect("could not acquire read lock on player state");
|
||||||
match *status {
|
match *status {
|
||||||
PlayerStatus::Playing => self.pause(),
|
PlayerEvent::Playing => self.pause(),
|
||||||
PlayerStatus::Paused => self.play(),
|
PlayerEvent::Paused => self.play(),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use cursive::direction::Orientation;
|
use cursive::direction::Orientation;
|
||||||
|
use cursive::event::Key;
|
||||||
use cursive::traits::Boxable;
|
use cursive::traits::Boxable;
|
||||||
use cursive::traits::Identifiable;
|
use cursive::traits::Identifiable;
|
||||||
use cursive::views::*;
|
use cursive::views::*;
|
||||||
@@ -41,23 +42,46 @@ impl PlaylistView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn create_button(&self, playlist: &SimplifiedPlaylist) -> SplitButton {
|
fn create_button(&self, playlist: &SimplifiedPlaylist) -> SplitButton {
|
||||||
let spotify_ref = self.spotify.clone();
|
|
||||||
let queue_ref = self.queue.clone();
|
|
||||||
|
|
||||||
let id = playlist.id.clone();
|
|
||||||
let collab = match playlist.collaborative {
|
let collab = match playlist.collaborative {
|
||||||
true => "collaborative",
|
true => "collaborative",
|
||||||
false => "",
|
false => "",
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut button = SplitButton::new(&playlist.name, collab);
|
let mut button = SplitButton::new(&playlist.name, collab);
|
||||||
button.add_callback(' ', move |_s| {
|
|
||||||
let tracks = spotify_ref.user_playlist_tracks(&id).unwrap().items;
|
// <enter> plays the selected playlist
|
||||||
let mut locked_queue = queue_ref.lock().expect("Could not aquire lock");
|
{
|
||||||
for playlist_track in tracks {
|
let id = playlist.id.clone();
|
||||||
locked_queue.enqueue(Track::new(&playlist_track.track));
|
let spotify_ref = self.spotify.clone();
|
||||||
}
|
let queue_ref = self.queue.clone();
|
||||||
});
|
button.add_callback(Key::Enter, move |_s| {
|
||||||
|
let tracks = spotify_ref.user_playlist_tracks(&id).unwrap().items;
|
||||||
|
let mut locked_queue = queue_ref.lock().expect("Could not aquire lock");
|
||||||
|
|
||||||
|
let mut first_played = false;
|
||||||
|
for playlist_track in tracks {
|
||||||
|
let index = locked_queue.append_next(&Track::new(&playlist_track.track));
|
||||||
|
if !first_played {
|
||||||
|
locked_queue.play(index);
|
||||||
|
first_played = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// <space> queues the selected playlist
|
||||||
|
{
|
||||||
|
let id = playlist.id.clone();
|
||||||
|
let spotify_ref = self.spotify.clone();
|
||||||
|
let queue_ref = self.queue.clone();
|
||||||
|
button.add_callback(' ', move |_s| {
|
||||||
|
let tracks = spotify_ref.user_playlist_tracks(&id).unwrap().items;
|
||||||
|
let mut locked_queue = queue_ref.lock().expect("Could not aquire lock");
|
||||||
|
for playlist_track in tracks {
|
||||||
|
locked_queue.append(&Track::new(&playlist_track.track));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
button
|
button
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ use cursive::Cursive;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use queue::{Queue, QueueChange};
|
use queue::{Queue, QueueEvent};
|
||||||
use spotify::Spotify;
|
|
||||||
use track::Track;
|
use track::Track;
|
||||||
use ui::splitbutton::SplitButton;
|
use ui::splitbutton::SplitButton;
|
||||||
use ui::trackbutton::TrackButton;
|
use ui::trackbutton::TrackButton;
|
||||||
@@ -17,11 +16,10 @@ use ui::trackbutton::TrackButton;
|
|||||||
pub struct QueueView {
|
pub struct QueueView {
|
||||||
pub view: Option<Panel<BoxView<BoxView<ScrollView<IdView<LinearLayout>>>>>>, // FIXME: wow
|
pub view: Option<Panel<BoxView<BoxView<ScrollView<IdView<LinearLayout>>>>>>, // FIXME: wow
|
||||||
queue: Arc<Mutex<Queue>>,
|
queue: Arc<Mutex<Queue>>,
|
||||||
spotify: Arc<Spotify>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl QueueView {
|
impl QueueView {
|
||||||
pub fn new(queue: Arc<Mutex<Queue>>, spotify: Arc<Spotify>) -> QueueView {
|
pub fn new(queue: Arc<Mutex<Queue>>) -> QueueView {
|
||||||
let queuelist = LinearLayout::new(Orientation::Vertical).with_id("queue_list");
|
let queuelist = LinearLayout::new(Orientation::Vertical).with_id("queue_list");
|
||||||
let scrollable = ScrollView::new(queuelist).full_width().full_height();
|
let scrollable = ScrollView::new(queuelist).full_width().full_height();
|
||||||
let panel = Panel::new(scrollable).title("Queue");
|
let panel = Panel::new(scrollable).title("Queue");
|
||||||
@@ -29,7 +27,6 @@ impl QueueView {
|
|||||||
QueueView {
|
QueueView {
|
||||||
view: Some(panel),
|
view: Some(panel),
|
||||||
queue: queue,
|
queue: queue,
|
||||||
spotify: spotify,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,33 +38,28 @@ impl QueueView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cb_play(cursive: &mut Cursive, queue: &mut Queue, spotify: &Spotify) {
|
fn cb_play(cursive: &mut Cursive, queue: &mut Queue) {
|
||||||
let view_ref: Option<ViewRef<LinearLayout>> = cursive.find_id("queue_list");
|
let view_ref: Option<ViewRef<LinearLayout>> = cursive.find_id("queue_list");
|
||||||
if let Some(queuelist) = view_ref {
|
if let Some(queuelist) = view_ref {
|
||||||
let index = queuelist.get_focus_index();
|
let index = queuelist.get_focus_index();
|
||||||
let track = queue.remove(index).expect("could not dequeue track");
|
queue.play(index);
|
||||||
spotify.load(track);
|
|
||||||
spotify.play();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_ev(&self, cursive: &mut Cursive, ev: QueueChange) {
|
pub fn handle_ev(&self, cursive: &mut Cursive, ev: QueueEvent) {
|
||||||
let view_ref: Option<ViewRef<LinearLayout>> = cursive.find_id("queue_list");
|
let view_ref: Option<ViewRef<LinearLayout>> = cursive.find_id("queue_list");
|
||||||
if let Some(mut queuelist) = view_ref {
|
if let Some(mut queuelist) = view_ref {
|
||||||
match ev {
|
match ev {
|
||||||
QueueChange::Enqueue => {
|
QueueEvent::Add(index) => {
|
||||||
let queue = self.queue.lock().expect("could not lock queue");
|
let queue = self.queue.lock().expect("could not lock queue");
|
||||||
let track = queue.peek().expect("queue is empty");
|
let track = queue.get(index);
|
||||||
let button = self.create_button(&track);
|
let button = self.create_button(&track);
|
||||||
queuelist.insert_child(0, button);
|
queuelist.insert_child(index, button);
|
||||||
}
|
}
|
||||||
QueueChange::Dequeue => {
|
QueueEvent::Remove(index) => {
|
||||||
queuelist.remove_child(0);
|
|
||||||
}
|
|
||||||
QueueChange::Remove(index) => {
|
|
||||||
queuelist.remove_child(index);
|
queuelist.remove_child(index);
|
||||||
}
|
}
|
||||||
QueueChange::Show => self.populate(&mut queuelist),
|
QueueEvent::Show => self.populate(&mut queuelist),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,15 +77,13 @@ impl QueueView {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// <enter> dequeues the selected track
|
// <enter> plays the selected track
|
||||||
{
|
{
|
||||||
let queue_ref = self.queue.clone();
|
let queue_ref = self.queue.clone();
|
||||||
let spotify = self.spotify.clone();
|
|
||||||
button.add_callback(Key::Enter, move |cursive| {
|
button.add_callback(Key::Enter, move |cursive| {
|
||||||
Self::cb_play(
|
Self::cb_play(
|
||||||
cursive,
|
cursive,
|
||||||
&mut queue_ref.lock().expect("could not lock queue"),
|
&mut queue_ref.lock().expect("could not lock queue"),
|
||||||
&spotify,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -107,7 +97,7 @@ impl QueueView {
|
|||||||
|
|
||||||
let queue = self.queue.lock().expect("could not lock queue");
|
let queue = self.queue.lock().expect("could not lock queue");
|
||||||
for track in queue.iter() {
|
for track in queue.iter() {
|
||||||
let button = self.create_button(&track);
|
let button = self.create_button(track);
|
||||||
queuelist.add_child(button);
|
queuelist.add_child(button);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use ui::trackbutton::TrackButton;
|
|||||||
|
|
||||||
pub struct SearchView {
|
pub struct SearchView {
|
||||||
pub view: Panel<LinearLayout>,
|
pub view: Panel<LinearLayout>,
|
||||||
queue: Arc<Mutex<Queue>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchView {
|
impl SearchView {
|
||||||
@@ -32,23 +31,28 @@ impl SearchView {
|
|||||||
if let Ok(tracks) = tracks {
|
if let Ok(tracks) = tracks {
|
||||||
for search_track in tracks.tracks.items {
|
for search_track in tracks.tracks.items {
|
||||||
let track = Track::new(&search_track);
|
let track = Track::new(&search_track);
|
||||||
|
|
||||||
let s = spotify.clone();
|
|
||||||
let mut button = TrackButton::new(&track);
|
let mut button = TrackButton::new(&track);
|
||||||
|
|
||||||
// <enter> plays the selected track
|
// <enter> plays the selected track
|
||||||
let t = track.clone();
|
{
|
||||||
button.add_callback(Key::Enter, move |_cursive| {
|
let queue = queue.clone();
|
||||||
s.load(t.clone());
|
let track = track.clone();
|
||||||
s.play();
|
button.add_callback(Key::Enter, move |_cursive| {
|
||||||
});
|
let mut queue = queue.lock().unwrap();
|
||||||
|
let index = queue.append_next(&track);
|
||||||
|
queue.play(index);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// <space> queues the selected track
|
// <space> queues the selected track
|
||||||
let queue = queue.clone();
|
{
|
||||||
button.add_callback(' ', move |_cursive| {
|
let queue = queue.clone();
|
||||||
let mut queue = queue.lock().unwrap();
|
let track = track.clone();
|
||||||
queue.enqueue(track.clone());
|
button.add_callback(' ', move |_cursive| {
|
||||||
});
|
let mut queue = queue.lock().unwrap();
|
||||||
|
queue.append(&track);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
results.add_child("", button);
|
results.add_child("", button);
|
||||||
}
|
}
|
||||||
@@ -72,9 +76,6 @@ impl SearchView {
|
|||||||
.child(searchfield)
|
.child(searchfield)
|
||||||
.child(scrollable);
|
.child(scrollable);
|
||||||
let rootpanel = Panel::new(layout).title("Search");
|
let rootpanel = Panel::new(layout).title("Search");
|
||||||
return SearchView {
|
return SearchView { view: rootpanel };
|
||||||
view: rootpanel,
|
|
||||||
queue: queue,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use cursive::align::HAlign;
|
use cursive::align::HAlign;
|
||||||
use cursive::theme::ColorStyle;
|
use cursive::theme::ColorStyle;
|
||||||
@@ -7,15 +7,20 @@ use cursive::vec::Vec2;
|
|||||||
use cursive::Printer;
|
use cursive::Printer;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
use spotify::{PlayerStatus, Spotify};
|
use queue::Queue;
|
||||||
|
use spotify::{PlayerEvent, Spotify};
|
||||||
|
|
||||||
pub struct StatusBar {
|
pub struct StatusBar {
|
||||||
|
queue: Arc<Mutex<Queue>>,
|
||||||
spotify: Arc<Spotify>,
|
spotify: Arc<Spotify>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StatusBar {
|
impl StatusBar {
|
||||||
pub fn new(spotify: Arc<Spotify>) -> StatusBar {
|
pub fn new(queue: Arc<Mutex<Queue>>, spotify: Arc<Spotify>) -> StatusBar {
|
||||||
StatusBar { spotify: spotify }
|
StatusBar {
|
||||||
|
queue: queue,
|
||||||
|
spotify: spotify,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +45,9 @@ impl View for StatusBar {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let state_icon = match self.spotify.get_current_status() {
|
let state_icon = match self.spotify.get_current_status() {
|
||||||
PlayerStatus::Playing => " ▶ ",
|
PlayerEvent::Playing => " ▶ ",
|
||||||
PlayerStatus::Paused => " ▮▮ ",
|
PlayerEvent::Paused => " ▮▮ ",
|
||||||
PlayerStatus::Stopped => " ◼ ",
|
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => " ◼ ",
|
||||||
}
|
}
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
@@ -50,7 +55,12 @@ impl View for StatusBar {
|
|||||||
printer.print((0, 1), &state_icon);
|
printer.print((0, 1), &state_icon);
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(ref t) = self.spotify.get_current_track() {
|
if let Some(ref t) = self
|
||||||
|
.queue
|
||||||
|
.lock()
|
||||||
|
.expect("could not lock queue")
|
||||||
|
.get_current()
|
||||||
|
{
|
||||||
let elapsed = self.spotify.get_current_progress();
|
let elapsed = self.spotify.get_current_progress();
|
||||||
let formatted_elapsed = format!(
|
let formatted_elapsed = format!(
|
||||||
"{:02}:{:02}",
|
"{:02}:{:02}",
|
||||||
|
|||||||
Reference in New Issue
Block a user