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 cursive::{CbFunc, Cursive};
|
||||
|
||||
use queue::QueueChange;
|
||||
use spotify::PlayerStatus;
|
||||
use track::Track;
|
||||
use queue::QueueEvent;
|
||||
use spotify::PlayerEvent;
|
||||
use ui::playlist::PlaylistEvent;
|
||||
|
||||
pub enum Event {
|
||||
Queue(QueueChange),
|
||||
PlayerStatus(PlayerStatus),
|
||||
PlayerTrack(Option<Track>),
|
||||
Queue(QueueEvent),
|
||||
Player(PlayerEvent),
|
||||
Playlist(PlaylistEvent),
|
||||
}
|
||||
|
||||
|
||||
40
src/main.rs
40
src/main.rs
@@ -39,7 +39,8 @@ mod track;
|
||||
mod ui;
|
||||
|
||||
use events::{Event, EventManager};
|
||||
use queue::QueueChange;
|
||||
use queue::QueueEvent;
|
||||
use spotify::PlayerEvent;
|
||||
use ui::playlist::PlaylistEvent;
|
||||
|
||||
fn init_logger(content: TextContent) {
|
||||
@@ -106,28 +107,37 @@ fn main() {
|
||||
cursive.set_theme(theme::default());
|
||||
cursive.set_autorefresh(true);
|
||||
|
||||
let queue = Arc::new(Mutex::new(queue::Queue::new(event_manager.clone())));
|
||||
|
||||
let spotify = Arc::new(spotify::Spotify::new(
|
||||
event_manager.clone(),
|
||||
cfg.username,
|
||||
cfg.password,
|
||||
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)
|
||||
{
|
||||
let spotify = spotify.clone();
|
||||
let queue = queue.clone();
|
||||
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| {
|
||||
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 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 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)
|
||||
.view("search", BoxView::with_full_height(search.view))
|
||||
@@ -162,7 +172,7 @@ fn main() {
|
||||
s.call_on_id("main", |v: &mut ui::layout::Layout| {
|
||||
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");
|
||||
match event {
|
||||
Event::Queue(ev) => queueview.handle_ev(&mut cursive, ev),
|
||||
Event::PlayerStatus(state) => spotify.update_status(state),
|
||||
Event::PlayerTrack(track) => spotify.update_track(track),
|
||||
Event::Player(state) => {
|
||||
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),
|
||||
}
|
||||
}
|
||||
|
||||
125
src/queue.rs
125
src/queue.rs
@@ -1,57 +1,124 @@
|
||||
use std::collections::vec_deque::Iter;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use track::Track;
|
||||
use std::slice::Iter;
|
||||
use std::sync::Arc;
|
||||
|
||||
use events::{Event, EventManager};
|
||||
use spotify::Spotify;
|
||||
use track::Track;
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
pub enum QueueChange {
|
||||
Dequeue,
|
||||
Enqueue,
|
||||
pub enum QueueEvent {
|
||||
Add(usize),
|
||||
Remove(usize),
|
||||
Show,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
pub fn new(ev: EventManager) -> Queue {
|
||||
pub fn new(ev: EventManager, spotify: Arc<Spotify>) -> Queue {
|
||||
Queue {
|
||||
queue: VecDeque::new(),
|
||||
queue: Vec::new(),
|
||||
current_track: None,
|
||||
spotify: spotify,
|
||||
ev: ev,
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, index: usize) -> Option<Track> {
|
||||
match self.queue.remove(index) {
|
||||
Some(track) => {
|
||||
debug!("Removed from queue: {}", &track);
|
||||
self.ev.send(Event::Queue(QueueChange::Remove(index)));
|
||||
Some(track)
|
||||
|
||||
pub fn next_index(&self) -> Option<usize> {
|
||||
match self.current_track {
|
||||
Some(index) => {
|
||||
let next_index = index + 1;
|
||||
if next_index < self.queue.len() {
|
||||
Some(next_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
pub fn enqueue(&mut self, track: Track) {
|
||||
debug!("Queued: {}", &track);
|
||||
self.queue.push_back(track);
|
||||
self.ev.send(Event::Queue(QueueChange::Enqueue));
|
||||
|
||||
pub fn get(&self, index: usize) -> &Track {
|
||||
&self.queue[index]
|
||||
}
|
||||
pub fn dequeue(&mut self) -> Option<Track> {
|
||||
match self.queue.pop_front() {
|
||||
Some(track) => {
|
||||
debug!("Dequeued : {}", track);
|
||||
self.ev.send(Event::Queue(QueueChange::Dequeue));
|
||||
Some(track)
|
||||
}
|
||||
|
||||
pub fn get_current(&self) -> Option<&Track> {
|
||||
match self.current_track {
|
||||
Some(index) => Some(&self.queue[index]),
|
||||
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> {
|
||||
self.queue.iter()
|
||||
}
|
||||
|
||||
@@ -24,14 +24,11 @@ use futures::Future;
|
||||
use futures::Stream;
|
||||
use tokio_core::reactor::Core;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::sync::RwLock;
|
||||
use std::thread;
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use events::{Event, EventManager};
|
||||
use queue::Queue;
|
||||
use track::Track;
|
||||
|
||||
enum WorkerCommand {
|
||||
@@ -41,21 +38,20 @@ enum WorkerCommand {
|
||||
Stop,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum PlayerStatus {
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum PlayerEvent {
|
||||
Playing,
|
||||
Paused,
|
||||
Stopped,
|
||||
FinishedTrack,
|
||||
}
|
||||
|
||||
pub struct Spotify {
|
||||
status: RwLock<PlayerStatus>,
|
||||
track: RwLock<Option<Track>>,
|
||||
status: RwLock<PlayerEvent>,
|
||||
pub api: SpotifyAPI,
|
||||
elapsed: RwLock<Option<Duration>>,
|
||||
since: RwLock<Option<SystemTime>>,
|
||||
channel: mpsc::UnboundedSender<WorkerCommand>,
|
||||
events: EventManager,
|
||||
user: String,
|
||||
}
|
||||
|
||||
@@ -64,7 +60,6 @@ struct Worker {
|
||||
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
||||
player: Player,
|
||||
play_task: Box<futures::Future<Item = (), Error = oneshot::Canceled>>,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
}
|
||||
|
||||
impl Worker {
|
||||
@@ -72,14 +67,12 @@ impl Worker {
|
||||
events: EventManager,
|
||||
commands: mpsc::UnboundedReceiver<WorkerCommand>,
|
||||
player: Player,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
) -> Worker {
|
||||
Worker {
|
||||
events: events,
|
||||
commands: commands,
|
||||
player: player,
|
||||
play_task: Box::new(futures::empty()),
|
||||
queue: queue,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,21 +92,19 @@ impl futures::Future for Worker {
|
||||
match cmd {
|
||||
WorkerCommand::Load(track) => {
|
||||
self.play_task = Box::new(self.player.load(track.id, false, 0));
|
||||
info!("player loading track..");
|
||||
self.events.send(Event::PlayerTrack(Some(track)));
|
||||
info!("player loading track: {:?}", track);
|
||||
}
|
||||
WorkerCommand::Play => {
|
||||
self.player.play();
|
||||
self.events.send(Event::PlayerStatus(PlayerStatus::Playing));
|
||||
self.events.send(Event::Player(PlayerEvent::Playing));
|
||||
}
|
||||
WorkerCommand::Pause => {
|
||||
self.player.pause();
|
||||
self.events.send(Event::PlayerStatus(PlayerStatus::Paused));
|
||||
self.events.send(Event::Player(PlayerEvent::Paused));
|
||||
}
|
||||
WorkerCommand::Stop => {
|
||||
self.player.stop();
|
||||
self.events.send(Event::PlayerTrack(None));
|
||||
self.events.send(Event::PlayerStatus(PlayerStatus::Stopped));
|
||||
self.events.send(Event::Player(PlayerEvent::Stopped));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,19 +112,7 @@ impl futures::Future for Worker {
|
||||
Ok(Async::Ready(())) => {
|
||||
debug!("end of track!");
|
||||
progress = true;
|
||||
|
||||
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));
|
||||
}
|
||||
self.events.send(Event::Player(PlayerEvent::FinishedTrack));
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(oneshot::Canceled) => {
|
||||
@@ -152,13 +131,7 @@ impl futures::Future for Worker {
|
||||
}
|
||||
|
||||
impl Spotify {
|
||||
pub fn new(
|
||||
events: EventManager,
|
||||
user: String,
|
||||
password: String,
|
||||
client_id: String,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
) -> Spotify {
|
||||
pub fn new(events: EventManager, user: String, password: String, client_id: String) -> Spotify {
|
||||
let session_config = SessionConfig::default();
|
||||
let player_config = PlayerConfig {
|
||||
bitrate: Bitrate::Bitrate320,
|
||||
@@ -180,7 +153,6 @@ impl Spotify {
|
||||
player_config,
|
||||
credentials,
|
||||
client_id,
|
||||
queue,
|
||||
)
|
||||
});
|
||||
}
|
||||
@@ -190,13 +162,11 @@ impl Spotify {
|
||||
let api = SpotifyAPI::default().access_token(&token.access_token);
|
||||
|
||||
Spotify {
|
||||
status: RwLock::new(PlayerStatus::Stopped),
|
||||
track: RwLock::new(None),
|
||||
status: RwLock::new(PlayerEvent::Stopped),
|
||||
api: api,
|
||||
elapsed: RwLock::new(None),
|
||||
since: RwLock::new(None),
|
||||
channel: tx,
|
||||
events: events,
|
||||
user: user,
|
||||
}
|
||||
}
|
||||
@@ -209,7 +179,6 @@ impl Spotify {
|
||||
player_config: PlayerConfig,
|
||||
credentials: Credentials,
|
||||
client_id: String,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
) {
|
||||
let mut core = Core::new().unwrap();
|
||||
let handle = core.handle();
|
||||
@@ -227,13 +196,13 @@ impl Spotify {
|
||||
let (player, _eventchannel) =
|
||||
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.");
|
||||
core.run(worker).unwrap();
|
||||
debug!("worker thread finished.");
|
||||
}
|
||||
|
||||
pub fn get_current_status(&self) -> PlayerStatus {
|
||||
pub fn get_current_status(&self) -> PlayerEvent {
|
||||
let status = self
|
||||
.status
|
||||
.read()
|
||||
@@ -241,14 +210,6 @@ impl Spotify {
|
||||
(*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 {
|
||||
self.get_elapsed().unwrap_or(Duration::from_secs(0))
|
||||
+ self
|
||||
@@ -306,23 +267,23 @@ impl Spotify {
|
||||
.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);
|
||||
self.channel
|
||||
.unbounded_send(WorkerCommand::Load(track))
|
||||
.unbounded_send(WorkerCommand::Load(track.clone()))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn update_status(&self, new_status: PlayerStatus) {
|
||||
pub fn update_status(&self, new_status: PlayerEvent) {
|
||||
match new_status {
|
||||
PlayerStatus::Paused => {
|
||||
PlayerEvent::Paused => {
|
||||
self.set_elapsed(Some(self.get_current_progress()));
|
||||
self.set_since(None);
|
||||
}
|
||||
PlayerStatus::Playing => {
|
||||
PlayerEvent::Playing => {
|
||||
self.set_since(Some(SystemTime::now()));
|
||||
}
|
||||
PlayerStatus::Stopped => {
|
||||
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => {
|
||||
self.set_elapsed(None);
|
||||
self.set_since(None);
|
||||
}
|
||||
@@ -335,15 +296,9 @@ impl Spotify {
|
||||
*status = new_status;
|
||||
}
|
||||
|
||||
pub fn update_track(&self, new_track: Option<Track>) {
|
||||
pub fn update_track(&self) {
|
||||
self.set_elapsed(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) {
|
||||
@@ -357,8 +312,8 @@ impl Spotify {
|
||||
.read()
|
||||
.expect("could not acquire read lock on player state");
|
||||
match *status {
|
||||
PlayerStatus::Playing => self.pause(),
|
||||
PlayerStatus::Paused => self.play(),
|
||||
PlayerEvent::Playing => self.pause(),
|
||||
PlayerEvent::Paused => self.play(),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cursive::direction::Orientation;
|
||||
use cursive::event::Key;
|
||||
use cursive::traits::Boxable;
|
||||
use cursive::traits::Identifiable;
|
||||
use cursive::views::*;
|
||||
@@ -41,23 +42,46 @@ impl PlaylistView {
|
||||
}
|
||||
|
||||
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 {
|
||||
true => "collaborative",
|
||||
false => "",
|
||||
};
|
||||
|
||||
let mut button = SplitButton::new(&playlist.name, collab);
|
||||
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.enqueue(Track::new(&playlist_track.track));
|
||||
}
|
||||
});
|
||||
|
||||
// <enter> plays the selected playlist
|
||||
{
|
||||
let id = playlist.id.clone();
|
||||
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
|
||||
}
|
||||
|
||||
@@ -8,8 +8,7 @@ use cursive::Cursive;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use queue::{Queue, QueueChange};
|
||||
use spotify::Spotify;
|
||||
use queue::{Queue, QueueEvent};
|
||||
use track::Track;
|
||||
use ui::splitbutton::SplitButton;
|
||||
use ui::trackbutton::TrackButton;
|
||||
@@ -17,11 +16,10 @@ use ui::trackbutton::TrackButton;
|
||||
pub struct QueueView {
|
||||
pub view: Option<Panel<BoxView<BoxView<ScrollView<IdView<LinearLayout>>>>>>, // FIXME: wow
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
spotify: Arc<Spotify>,
|
||||
}
|
||||
|
||||
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 scrollable = ScrollView::new(queuelist).full_width().full_height();
|
||||
let panel = Panel::new(scrollable).title("Queue");
|
||||
@@ -29,7 +27,6 @@ impl QueueView {
|
||||
QueueView {
|
||||
view: Some(panel),
|
||||
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");
|
||||
if let Some(queuelist) = view_ref {
|
||||
let index = queuelist.get_focus_index();
|
||||
let track = queue.remove(index).expect("could not dequeue track");
|
||||
spotify.load(track);
|
||||
spotify.play();
|
||||
queue.play(index);
|
||||
}
|
||||
}
|
||||
|
||||
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");
|
||||
if let Some(mut queuelist) = view_ref {
|
||||
match ev {
|
||||
QueueChange::Enqueue => {
|
||||
QueueEvent::Add(index) => {
|
||||
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);
|
||||
queuelist.insert_child(0, button);
|
||||
queuelist.insert_child(index, button);
|
||||
}
|
||||
QueueChange::Dequeue => {
|
||||
queuelist.remove_child(0);
|
||||
}
|
||||
QueueChange::Remove(index) => {
|
||||
QueueEvent::Remove(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 spotify = self.spotify.clone();
|
||||
button.add_callback(Key::Enter, move |cursive| {
|
||||
Self::cb_play(
|
||||
cursive,
|
||||
&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");
|
||||
for track in queue.iter() {
|
||||
let button = self.create_button(&track);
|
||||
let button = self.create_button(track);
|
||||
queuelist.add_child(button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ use ui::trackbutton::TrackButton;
|
||||
|
||||
pub struct SearchView {
|
||||
pub view: Panel<LinearLayout>,
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
}
|
||||
|
||||
impl SearchView {
|
||||
@@ -32,23 +31,28 @@ impl SearchView {
|
||||
if let Ok(tracks) = tracks {
|
||||
for search_track in tracks.tracks.items {
|
||||
let track = Track::new(&search_track);
|
||||
|
||||
let s = spotify.clone();
|
||||
let mut button = TrackButton::new(&track);
|
||||
|
||||
// <enter> plays the selected track
|
||||
let t = track.clone();
|
||||
button.add_callback(Key::Enter, move |_cursive| {
|
||||
s.load(t.clone());
|
||||
s.play();
|
||||
});
|
||||
{
|
||||
let queue = queue.clone();
|
||||
let track = track.clone();
|
||||
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
|
||||
let queue = queue.clone();
|
||||
button.add_callback(' ', move |_cursive| {
|
||||
let mut queue = queue.lock().unwrap();
|
||||
queue.enqueue(track.clone());
|
||||
});
|
||||
{
|
||||
let queue = queue.clone();
|
||||
let track = track.clone();
|
||||
button.add_callback(' ', move |_cursive| {
|
||||
let mut queue = queue.lock().unwrap();
|
||||
queue.append(&track);
|
||||
});
|
||||
}
|
||||
|
||||
results.add_child("", button);
|
||||
}
|
||||
@@ -72,9 +76,6 @@ impl SearchView {
|
||||
.child(searchfield)
|
||||
.child(scrollable);
|
||||
let rootpanel = Panel::new(layout).title("Search");
|
||||
return SearchView {
|
||||
view: rootpanel,
|
||||
queue: queue,
|
||||
};
|
||||
return SearchView { view: rootpanel };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use cursive::align::HAlign;
|
||||
use cursive::theme::ColorStyle;
|
||||
@@ -7,15 +7,20 @@ use cursive::vec::Vec2;
|
||||
use cursive::Printer;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
use spotify::{PlayerStatus, Spotify};
|
||||
use queue::Queue;
|
||||
use spotify::{PlayerEvent, Spotify};
|
||||
|
||||
pub struct StatusBar {
|
||||
queue: Arc<Mutex<Queue>>,
|
||||
spotify: Arc<Spotify>,
|
||||
}
|
||||
|
||||
impl StatusBar {
|
||||
pub fn new(spotify: Arc<Spotify>) -> StatusBar {
|
||||
StatusBar { spotify: spotify }
|
||||
pub fn new(queue: Arc<Mutex<Queue>>, spotify: Arc<Spotify>) -> StatusBar {
|
||||
StatusBar {
|
||||
queue: queue,
|
||||
spotify: spotify,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +45,9 @@ impl View for StatusBar {
|
||||
});
|
||||
|
||||
let state_icon = match self.spotify.get_current_status() {
|
||||
PlayerStatus::Playing => " ▶ ",
|
||||
PlayerStatus::Paused => " ▮▮ ",
|
||||
PlayerStatus::Stopped => " ◼ ",
|
||||
PlayerEvent::Playing => " ▶ ",
|
||||
PlayerEvent::Paused => " ▮▮ ",
|
||||
PlayerEvent::Stopped | PlayerEvent::FinishedTrack => " ◼ ",
|
||||
}
|
||||
.to_string();
|
||||
|
||||
@@ -50,7 +55,12 @@ impl View for StatusBar {
|
||||
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 formatted_elapsed = format!(
|
||||
"{:02}:{:02}",
|
||||
|
||||
Reference in New Issue
Block a user