mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Send player event messages over futures aware channel.
This commit is contained in:
parent
2eb4aa61d3
commit
93af49aadf
4 changed files with 49 additions and 47 deletions
|
@ -1,7 +1,4 @@
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use core::spotify_id::SpotifyId;
|
|
||||||
use std::sync::mpsc::Sender;
|
|
||||||
use player::PlayerEvent;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
pub enum Bitrate {
|
pub enum Bitrate {
|
||||||
|
@ -31,14 +28,12 @@ impl Default for Bitrate {
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayerConfig {
|
pub struct PlayerConfig {
|
||||||
pub bitrate: Bitrate,
|
pub bitrate: Bitrate,
|
||||||
pub event_sender : Option<Sender<PlayerEvent>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlayerConfig {
|
impl Default for PlayerConfig {
|
||||||
fn default() -> PlayerConfig {
|
fn default() -> PlayerConfig {
|
||||||
PlayerConfig {
|
PlayerConfig {
|
||||||
bitrate: Bitrate::default(),
|
bitrate: Bitrate::default(),
|
||||||
event_sender: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use futures::sync::oneshot;
|
use futures::sync::oneshot;
|
||||||
use futures::{future, Future};
|
use futures::{future, Future};
|
||||||
|
use futures::sync::mpsc::{UnboundedReceiver, UnboundedSender, unbounded};
|
||||||
use std;
|
use std;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::io::{Read, Seek, SeekFrom, Result};
|
use std::io::{Read, Seek, SeekFrom, Result};
|
||||||
|
@ -32,6 +33,7 @@ struct PlayerInternal {
|
||||||
sink: Box<Sink>,
|
sink: Box<Sink>,
|
||||||
sink_running: bool,
|
sink_running: bool,
|
||||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||||
|
event_sender: UnboundedSender<PlayerEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlayerCommand {
|
enum PlayerCommand {
|
||||||
|
@ -58,14 +60,15 @@ pub enum PlayerEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PlayerEventChannel = UnboundedReceiver<PlayerEvent>;
|
||||||
impl Player {
|
impl Player {
|
||||||
pub fn new<F>(config: PlayerConfig, session: Session,
|
pub fn new<F>(config: PlayerConfig, session: Session,
|
||||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||||
sink_builder: F) -> Player
|
sink_builder: F) -> (Player, PlayerEventChannel)
|
||||||
where F: FnOnce() -> Box<Sink> + Send + 'static
|
where F: FnOnce() -> Box<Sink> + Send + 'static
|
||||||
{
|
{
|
||||||
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
||||||
|
let (event_sender, event_receiver) = unbounded();
|
||||||
|
|
||||||
let handle = thread::spawn(move || {
|
let handle = thread::spawn(move || {
|
||||||
debug!("new Player[{}]", session.session_id());
|
debug!("new Player[{}]", session.session_id());
|
||||||
|
@ -79,15 +82,14 @@ impl Player {
|
||||||
sink: sink_builder(),
|
sink: sink_builder(),
|
||||||
sink_running: false,
|
sink_running: false,
|
||||||
audio_filter: audio_filter,
|
audio_filter: audio_filter,
|
||||||
|
event_sender: event_sender,
|
||||||
};
|
};
|
||||||
|
|
||||||
internal.run();
|
internal.run();
|
||||||
});
|
});
|
||||||
|
|
||||||
Player {
|
(Player { commands: Some(cmd_tx), thread_handle: Some(handle) },
|
||||||
commands: Some(cmd_tx),
|
event_receiver)
|
||||||
thread_handle: Some(handle),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn command(&self, cmd: PlayerCommand) {
|
fn command(&self, cmd: PlayerCommand) {
|
||||||
|
@ -414,11 +416,9 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_event(&mut self, event: PlayerEvent) {
|
fn send_event(&mut self, event: PlayerEvent) {
|
||||||
if let Some(ref s) = self.config.event_sender {
|
match self.event_sender.unbounded_send(event.clone()) {
|
||||||
match s.send(event.clone()) {
|
Ok(_) => info!("Sent event {:?} to event listener.", event),
|
||||||
Ok(_) => info!("Sent event {:?} to event listener.", event),
|
Err(err) => error!("Failed to send event {:?} to listener: {:?}", event, err)
|
||||||
Err(err) => error!("Failed to send event {:?} to listener: {:?}", event, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
27
src/main.rs
27
src/main.rs
|
@ -9,6 +9,7 @@ extern crate tokio_signal;
|
||||||
|
|
||||||
use env_logger::LogBuilder;
|
use env_logger::LogBuilder;
|
||||||
use futures::{Future, Async, Poll, Stream};
|
use futures::{Future, Async, Poll, Stream};
|
||||||
|
use futures::sync::mpsc::UnboundedReceiver;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{self, stderr, Write};
|
use std::io::{self, stderr, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -28,7 +29,7 @@ use librespot::playback::audio_backend::{self, Sink, BACKENDS};
|
||||||
use librespot::playback::config::{Bitrate, PlayerConfig};
|
use librespot::playback::config::{Bitrate, PlayerConfig};
|
||||||
use librespot::connect::discovery::{discovery, DiscoveryStream};
|
use librespot::connect::discovery::{discovery, DiscoveryStream};
|
||||||
use librespot::playback::mixer::{self, Mixer};
|
use librespot::playback::mixer::{self, Mixer};
|
||||||
use librespot::playback::player::Player;
|
use librespot::playback::player::{Player, PlayerEvent};
|
||||||
use librespot::connect::spirc::{Spirc, SpircTask};
|
use librespot::connect::spirc::{Spirc, SpircTask};
|
||||||
|
|
||||||
mod player_event_handler;
|
mod player_event_handler;
|
||||||
|
@ -86,6 +87,7 @@ struct Setup {
|
||||||
credentials: Option<Credentials>,
|
credentials: Option<Credentials>,
|
||||||
enable_discovery: bool,
|
enable_discovery: bool,
|
||||||
zeroconf_port: u16,
|
zeroconf_port: u16,
|
||||||
|
player_event_program: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(args: &[String]) -> Setup {
|
fn setup(args: &[String]) -> Setup {
|
||||||
|
@ -185,10 +187,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
|
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
|
||||||
.unwrap_or(Bitrate::default());
|
.unwrap_or(Bitrate::default());
|
||||||
|
|
||||||
PlayerConfig {
|
PlayerConfig { bitrate: bitrate }
|
||||||
bitrate: bitrate,
|
|
||||||
event_sender: matches.opt_str("onevent").map(run_program_on_events)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let connect_config = {
|
let connect_config = {
|
||||||
|
@ -216,6 +215,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
enable_discovery: enable_discovery,
|
enable_discovery: enable_discovery,
|
||||||
zeroconf_port: zeroconf_port,
|
zeroconf_port: zeroconf_port,
|
||||||
mixer: mixer,
|
mixer: mixer,
|
||||||
|
player_event_program: matches.opt_str("onevent"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,6 +237,9 @@ struct Main {
|
||||||
connect: Box<Future<Item=Session, Error=io::Error>>,
|
connect: Box<Future<Item=Session, Error=io::Error>>,
|
||||||
|
|
||||||
shutdown: bool,
|
shutdown: bool,
|
||||||
|
|
||||||
|
player_event_channel: Option<UnboundedReceiver<PlayerEvent>>,
|
||||||
|
player_event_program: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Main {
|
impl Main {
|
||||||
|
@ -257,6 +260,9 @@ impl Main {
|
||||||
spirc_task: None,
|
spirc_task: None,
|
||||||
shutdown: false,
|
shutdown: false,
|
||||||
signal: Box::new(tokio_signal::ctrl_c(&handle).flatten_stream()),
|
signal: Box::new(tokio_signal::ctrl_c(&handle).flatten_stream()),
|
||||||
|
|
||||||
|
player_event_channel: None,
|
||||||
|
player_event_program: setup.player_event_program,
|
||||||
};
|
};
|
||||||
|
|
||||||
if setup.enable_discovery {
|
if setup.enable_discovery {
|
||||||
|
@ -314,13 +320,14 @@ impl Future for Main {
|
||||||
|
|
||||||
let audio_filter = mixer.get_audio_filter();
|
let audio_filter = mixer.get_audio_filter();
|
||||||
let backend = self.backend;
|
let backend = self.backend;
|
||||||
let player = Player::new(player_config, session.clone(), audio_filter, move || {
|
let (player, event_channel) = Player::new(player_config, session.clone(), audio_filter, move || {
|
||||||
(backend)(device)
|
(backend)(device)
|
||||||
});
|
});
|
||||||
|
|
||||||
let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
|
let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
|
||||||
self.spirc = Some(spirc);
|
self.spirc = Some(spirc);
|
||||||
self.spirc_task = Some(spirc_task);
|
self.spirc_task = Some(spirc_task);
|
||||||
|
self.player_event_channel = Some(event_channel);
|
||||||
|
|
||||||
progress = true;
|
progress = true;
|
||||||
}
|
}
|
||||||
|
@ -348,6 +355,14 @@ impl Future for Main {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut player_event_channel) = self.player_event_channel {
|
||||||
|
if let Async::Ready(Some(event)) = player_event_channel.poll().unwrap() {
|
||||||
|
if let Some(ref program) = self.player_event_program {
|
||||||
|
run_program_on_events(event, program);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !progress {
|
if !progress {
|
||||||
return Ok(Async::NotReady);
|
return Ok(Async::NotReady);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::mpsc::{channel, Sender};
|
|
||||||
use std::thread;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use librespot::playback::player::PlayerEvent;
|
use librespot::playback::player::PlayerEvent;
|
||||||
|
|
||||||
|
@ -15,28 +13,22 @@ fn run_program(program: &str, env_vars: HashMap<&str, String>) {
|
||||||
info!("Exit status: {}", status);
|
info!("Exit status: {}", status);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_program_on_events(onevent: String) -> Sender<PlayerEvent> {
|
pub fn run_program_on_events(event: PlayerEvent, onevent: &str) {
|
||||||
let (sender, receiver) = channel();
|
let mut env_vars = HashMap::new();
|
||||||
thread::spawn(move || {
|
match event {
|
||||||
while let Ok(msg) = receiver.recv() {
|
PlayerEvent::Changed { old_track_id, new_track_id } => {
|
||||||
let mut env_vars = HashMap::new();
|
env_vars.insert("PLAYER_EVENT", "change".to_string());
|
||||||
match msg {
|
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
||||||
PlayerEvent::Changed { old_track_id, new_track_id } => {
|
env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
||||||
env_vars.insert("PLAYER_EVENT", "change".to_string());
|
},
|
||||||
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base16());
|
PlayerEvent::Started { track_id } => {
|
||||||
env_vars.insert("TRACK_ID", new_track_id.to_base16());
|
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
||||||
},
|
env_vars.insert("TRACK_ID", track_id.to_base16());
|
||||||
PlayerEvent::Started { track_id } => {
|
|
||||||
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base16());
|
|
||||||
}
|
|
||||||
PlayerEvent::Stopped { track_id } => {
|
|
||||||
env_vars.insert("PLAYER_EVENT", "stop".to_string());
|
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base16());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
run_program(&onevent, env_vars);
|
|
||||||
}
|
}
|
||||||
});
|
PlayerEvent::Stopped { track_id } => {
|
||||||
sender
|
env_vars.insert("PLAYER_EVENT", "stop".to_string());
|
||||||
|
env_vars.insert("TRACK_ID", track_id.to_base16());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
run_program(onevent, env_vars);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue