add global play/pause/stop buttons + necessary state logic

This commit is contained in:
Henrik Friedrichsen
2019-03-02 18:37:56 +01:00
parent 70d68f8854
commit 3d1673a7a4
3 changed files with 84 additions and 15 deletions

View File

@@ -1,8 +1,11 @@
use crossbeam_channel::{unbounded, Receiver, Sender, TryIter}; use crossbeam_channel::{unbounded, Receiver, Sender, TryIter};
use cursive::{CbFunc, Cursive}; use cursive::{CbFunc, Cursive};
use spotify::PlayerState;
pub enum Event { pub enum Event {
QueueUpdate, QueueUpdate,
PlayState(PlayerState),
} }
pub type EventSender = Sender<Event>; pub type EventSender = Sender<Event>;

View File

@@ -87,12 +87,28 @@ fn main() {
let queue = Arc::new(Mutex::new(queue::Queue::new(event_manager.clone()))); 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(),
cfg.username, cfg.username,
cfg.password, cfg.password,
cfg.client_id, cfg.client_id,
queue.clone(), queue.clone(),
)); ));
// global player keybindings (play, pause, stop)
{
let spotify = spotify.clone();
cursive.add_global_callback('P', move |_s| {
spotify.toggleplayback();
});
}
{
let spotify = spotify.clone();
cursive.add_global_callback('S', move |_s| {
spotify.stop();
});
}
let track = TextView::new("Track Title"); let track = TextView::new("Track Title");
let pos = TextView::new("[0:00/0:00]"); let pos = TextView::new("[0:00/0:00]");
let status = LinearLayout::horizontal().child(track).child(pos); let status = LinearLayout::horizontal().child(track).child(pos);
@@ -133,6 +149,7 @@ fn main() {
trace!("event received"); trace!("event received");
match event { match event {
Event::QueueUpdate => queue.redraw(&mut cursive), Event::QueueUpdate => queue.redraw(&mut cursive),
Event::PlayState(state) => spotify.updatestate(state),
} }
} }
} }

View File

@@ -25,8 +25,10 @@ use tokio_core::reactor::Core;
use std::sync::Arc; use std::sync::Arc;
use std::sync::Mutex; use std::sync::Mutex;
use std::sync::RwLock;
use std::thread; use std::thread;
use events::{Event, EventManager};
use queue::Queue; use queue::Queue;
enum WorkerCommand { enum WorkerCommand {
@@ -36,12 +38,21 @@ enum WorkerCommand {
Stop, Stop,
} }
pub enum PlayerState {
Playing,
Paused,
Stopped,
}
pub struct Spotify { pub struct Spotify {
pub state: RwLock<PlayerState>,
pub api: SpotifyAPI, pub api: SpotifyAPI,
channel: mpsc::UnboundedSender<WorkerCommand>, channel: mpsc::UnboundedSender<WorkerCommand>,
events: EventManager,
} }
struct Worker { struct Worker {
events: EventManager,
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>>,
@@ -50,11 +61,13 @@ struct Worker {
impl Worker { impl Worker {
fn new( fn new(
events: EventManager,
commands: mpsc::UnboundedReceiver<WorkerCommand>, commands: mpsc::UnboundedReceiver<WorkerCommand>,
player: Player, player: Player,
queue: Arc<Mutex<Queue>>, queue: Arc<Mutex<Queue>>,
) -> Worker { ) -> Worker {
Worker { Worker {
events: events,
commands: commands, commands: commands,
player: player, player: player,
play_task: Box::new(futures::empty()), play_task: Box::new(futures::empty()),
@@ -80,9 +93,18 @@ impl futures::Future for Worker {
self.play_task = Box::new(self.player.load(track, false, 0)); self.play_task = Box::new(self.player.load(track, false, 0));
info!("player loading track.."); info!("player loading track..");
} }
WorkerCommand::Play => self.player.play(), WorkerCommand::Play => {
WorkerCommand::Pause => self.player.pause(), self.player.play();
WorkerCommand::Stop => self.player.stop(), self.events.send(Event::PlayState(PlayerState::Playing));
},
WorkerCommand::Pause => {
self.player.pause();
self.events.send(Event::PlayState(PlayerState::Paused));
}
WorkerCommand::Stop => {
self.player.stop();
self.events.send(Event::PlayState(PlayerState::Stopped));
}
} }
} }
match self.play_task.poll() { match self.play_task.poll() {
@@ -97,6 +119,11 @@ impl futures::Future for Worker {
SpotifyId::from_base62(&track.id).expect("could not load track"); SpotifyId::from_base62(&track.id).expect("could not load track");
self.play_task = Box::new(self.player.load(trackid, false, 0)); self.play_task = Box::new(self.player.load(trackid, false, 0));
self.player.play(); self.player.play();
self.events.send(Event::PlayState(PlayerState::Playing));
}
else {
self.events.send(Event::PlayState(PlayerState::Stopped));
} }
} }
Ok(Async::NotReady) => (), Ok(Async::NotReady) => (),
@@ -117,6 +144,7 @@ impl futures::Future for Worker {
impl Spotify { impl Spotify {
pub fn new( pub fn new(
events: EventManager,
user: String, user: String,
password: String, password: String,
client_id: String, client_id: String,
@@ -132,29 +160,36 @@ impl Spotify {
let (tx, rx) = mpsc::unbounded(); let (tx, rx) = mpsc::unbounded();
let (p, c) = oneshot::channel(); let (p, c) = oneshot::channel();
thread::spawn(move || { {
Spotify::worker( let events = events.clone();
rx, thread::spawn(move || {
p, Spotify::worker(
session_config, events,
player_config, rx,
credentials, p,
client_id, session_config,
queue, player_config,
) credentials,
}); client_id,
queue,
)
});
}
let token = c.wait().unwrap(); let token = c.wait().unwrap();
debug!("token received: {:?}", token); debug!("token received: {:?}", token);
let api = SpotifyAPI::default().access_token(&token.access_token); let api = SpotifyAPI::default().access_token(&token.access_token);
Spotify { Spotify {
state: RwLock::new(PlayerState::Stopped),
api: api, api: api,
channel: tx, channel: tx,
events: events,
} }
} }
fn worker( fn worker(
events: EventManager,
commands: mpsc::UnboundedReceiver<WorkerCommand>, commands: mpsc::UnboundedReceiver<WorkerCommand>,
token_channel: oneshot::Sender<Token>, token_channel: oneshot::Sender<Token>,
session_config: SessionConfig, session_config: SessionConfig,
@@ -179,7 +214,7 @@ 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(commands, player, queue); let worker = Worker::new(events, commands, player, queue);
debug!("worker thread ready."); debug!("worker thread ready.");
core.run(worker).unwrap(); core.run(worker).unwrap();
debug!("worker thread finished."); debug!("worker thread finished.");
@@ -196,11 +231,25 @@ impl Spotify {
.unwrap(); .unwrap();
} }
pub fn updatestate(&self, newstate: PlayerState) {
let mut state = self.state.write().expect("could not acquire write lock on player state");
*state = newstate;
}
pub fn play(&self) { pub fn play(&self) {
info!("play()"); info!("play()");
self.channel.unbounded_send(WorkerCommand::Play).unwrap(); self.channel.unbounded_send(WorkerCommand::Play).unwrap();
} }
pub fn toggleplayback(&self) {
let state = self.state.read().expect("could not acquire read lock on player state");
match *state {
PlayerState::Playing => self.pause(),
PlayerState::Paused => self.play(),
_ => (),
}
}
pub fn pause(&self) { pub fn pause(&self) {
info!("pause()"); info!("pause()");
self.channel.unbounded_send(WorkerCommand::Pause).unwrap(); self.channel.unbounded_send(WorkerCommand::Pause).unwrap();