add global play/pause/stop buttons + necessary state logic
This commit is contained in:
@@ -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>;
|
||||||
|
|||||||
17
src/main.rs
17
src/main.rs
@@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user