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:
Henrik Friedrichsen
2019-03-06 23:56:11 +01:00
parent c3fcb3ce21
commit 0b14fc5da7
8 changed files with 232 additions and 173 deletions

View File

@@ -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),
} }

View File

@@ -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),
} }
} }

View File

@@ -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()
} }

View File

@@ -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(),
_ => (), _ => (),
} }
} }

View File

@@ -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
} }

View File

@@ -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);
} }
} }

View File

@@ -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,
};
} }
} }

View File

@@ -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}",