| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500 | 
							- extern crate crypto;
 
- extern crate env_logger;
 
- extern crate futures;
 
- extern crate getopts;
 
- extern crate librespot;
 
- #[macro_use]
 
- extern crate log;
 
- extern crate rpassword;
 
- extern crate tokio_core;
 
- extern crate tokio_io;
 
- extern crate tokio_process;
 
- extern crate tokio_signal;
 
- extern crate url;
 
- use crypto::digest::Digest;
 
- use crypto::sha1::Sha1;
 
- use env_logger::LogBuilder;
 
- use futures::sync::mpsc::UnboundedReceiver;
 
- use futures::{Async, Future, Poll, Stream};
 
- use std::env;
 
- use std::io::{self, stderr, Write};
 
- use std::mem;
 
- use std::path::PathBuf;
 
- use std::process::exit;
 
- use std::str::FromStr;
 
- use tokio_core::reactor::{Core, Handle};
 
- use tokio_io::IoStream;
 
- use url::Url;
 
- use librespot::core::authentication::{get_credentials, Credentials};
 
- use librespot::core::cache::Cache;
 
- use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig};
 
- use librespot::core::session::Session;
 
- use librespot::core::version;
 
- use librespot::connect::discovery::{discovery, DiscoveryStream};
 
- use librespot::connect::spirc::{Spirc, SpircTask};
 
- use librespot::playback::audio_backend::{self, Sink, BACKENDS};
 
- use librespot::playback::config::{Bitrate, PlayerConfig};
 
- use librespot::playback::mixer::{self, Mixer};
 
- use librespot::playback::player::{Player, PlayerEvent};
 
- mod player_event_handler;
 
- use player_event_handler::run_program_on_events;
 
- fn device_id(name: &str) -> String {
 
-     let mut h = Sha1::new();
 
-     h.input_str(name);
 
-     h.result_str()
 
- }
 
- fn usage(program: &str, opts: &getopts::Options) -> String {
 
-     let brief = format!("Usage: {} [options]", program);
 
-     opts.usage(&brief)
 
- }
 
- fn setup_logging(verbose: bool) {
 
-     let mut builder = LogBuilder::new();
 
-     match env::var("RUST_LOG") {
 
-         Ok(config) => {
 
-             builder.parse(&config);
 
-             builder.init().unwrap();
 
-             if verbose {
 
-                 warn!("`--verbose` flag overidden by `RUST_LOG` environment variable");
 
-             }
 
-         }
 
-         Err(_) => {
 
-             if verbose {
 
-                 builder.parse("mdns=info,librespot=trace");
 
-             } else {
 
-                 builder.parse("mdns=info,librespot=info");
 
-             }
 
-             builder.init().unwrap();
 
-         }
 
-     }
 
- }
 
- fn list_backends() {
 
-     println!("Available Backends : ");
 
-     for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
 
-         if idx == 0 {
 
-             println!("- {} (default)", name);
 
-         } else {
 
-             println!("- {}", name);
 
-         }
 
-     }
 
- }
 
- #[derive(Clone)]
 
- struct Setup {
 
-     backend: fn(Option<String>) -> Box<Sink>,
 
-     device: Option<String>,
 
-     mixer: fn() -> Box<Mixer>,
 
-     cache: Option<Cache>,
 
-     player_config: PlayerConfig,
 
-     session_config: SessionConfig,
 
-     connect_config: ConnectConfig,
 
-     credentials: Option<Credentials>,
 
-     enable_discovery: bool,
 
-     zeroconf_port: u16,
 
-     player_event_program: Option<String>,
 
- }
 
- fn setup(args: &[String]) -> Setup {
 
-     let mut opts = getopts::Options::new();
 
-     opts.optopt(
 
-         "c",
 
-         "cache",
 
-         "Path to a directory where files will be cached.",
 
-         "CACHE",
 
-     ).optflag("", "disable-audio-cache", "Disable caching of the audio data.")
 
-         .reqopt("n", "name", "Device name", "NAME")
 
-         .optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
 
-         .optopt(
 
-             "b",
 
-             "bitrate",
 
-             "Bitrate (96, 160 or 320). Defaults to 160",
 
-             "BITRATE",
 
-         )
 
-         .optopt(
 
-             "",
 
-             "onevent",
 
-             "Run PROGRAM when playback is about to begin.",
 
-             "PROGRAM",
 
-         )
 
-         .optflag("v", "verbose", "Enable verbose output")
 
-         .optopt("u", "username", "Username to sign in with", "USERNAME")
 
-         .optopt("p", "password", "Password", "PASSWORD")
 
-         .optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
 
-         .optopt("", "ap-port", "Connect to AP with specified port. If no AP with that port are present fallback AP will be used. Available ports are usually 80, 443 and 4070", "AP_PORT")
 
-         .optflag("", "disable-discovery", "Disable discovery mode")
 
-         .optopt(
 
-             "",
 
-             "backend",
 
-             "Audio backend to use. Use '?' to list options",
 
-             "BACKEND",
 
-         )
 
-         .optopt(
 
-             "",
 
-             "device",
 
-             "Audio device to use. Use '?' to list options if using portaudio",
 
-             "DEVICE",
 
-         )
 
-         .optopt("", "mixer", "Mixer to use", "MIXER")
 
-         .optopt(
 
-             "",
 
-             "initial-volume",
 
-             "Initial volume in %, once connected (must be from 0 to 100)",
 
-             "VOLUME",
 
-         )
 
-         .optopt(
 
-             "",
 
-             "zeroconf-port",
 
-             "The port the internal server advertised over zeroconf uses.",
 
-             "ZEROCONF_PORT",
 
-         )
 
-         .optflag(
 
-             "",
 
-             "enable-volume-normalisation",
 
-             "Play all tracks at the same volume",
 
-         )
 
-         .optopt(
 
-             "",
 
-             "normalisation-pregain",
 
-             "Pregain (dB) applied by volume normalisation",
 
-             "PREGAIN",
 
-         )
 
-         .optflag(
 
-             "",
 
-             "linear-volume",
 
-             "increase volume linear instead of logarithmic.",
 
-         );
 
-     let matches = match opts.parse(&args[1..]) {
 
-         Ok(m) => m,
 
-         Err(f) => {
 
-             writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
 
-             exit(1);
 
-         }
 
-     };
 
-     let verbose = matches.opt_present("verbose");
 
-     setup_logging(verbose);
 
-     info!(
 
-         "librespot {} ({}). Built on {}. Build ID: {}",
 
-         version::short_sha(),
 
-         version::commit_date(),
 
-         version::short_now(),
 
-         version::build_id()
 
-     );
 
-     let backend_name = matches.opt_str("backend");
 
-     if backend_name == Some("?".into()) {
 
-         list_backends();
 
-         exit(0);
 
-     }
 
-     let backend = audio_backend::find(backend_name).expect("Invalid backend");
 
-     let device = matches.opt_str("device");
 
-     let mixer_name = matches.opt_str("mixer");
 
-     let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
 
-     let use_audio_cache = !matches.opt_present("disable-audio-cache");
 
-     let cache = matches
 
-         .opt_str("c")
 
-         .map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache));
 
-     let initial_volume = matches
 
-         .opt_str("initial-volume")
 
-         .map(|volume| {
 
-             let volume = volume.parse::<u16>().unwrap();
 
-             if volume > 100 {
 
-                 panic!("Initial volume must be in the range 0-100");
 
-             }
 
-             (volume as i32 * 0xFFFF / 100) as u16
 
-         })
 
-         .or_else(|| cache.as_ref().and_then(Cache::volume))
 
-         .unwrap_or(0x8000);
 
-     let zeroconf_port = matches
 
-         .opt_str("zeroconf-port")
 
-         .map(|port| port.parse::<u16>().unwrap())
 
-         .unwrap_or(0);
 
-     let name = matches.opt_str("name").unwrap();
 
-     let credentials = {
 
-         let cached_credentials = cache.as_ref().and_then(Cache::credentials);
 
-         let password = |username: &String| -> String {
 
-             write!(stderr(), "Password for {}: ", username).unwrap();
 
-             stderr().flush().unwrap();
 
-             rpassword::read_password().unwrap()
 
-         };
 
-         get_credentials(
 
-             matches.opt_str("username"),
 
-             matches.opt_str("password"),
 
-             cached_credentials,
 
-             password,
 
-         )
 
-     };
 
-     let session_config = {
 
-         let device_id = device_id(&name);
 
-         SessionConfig {
 
-             user_agent: version::version_string(),
 
-             device_id: device_id,
 
-             proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map(
 
-                 |s| {
 
-                     match Url::parse(&s) {
 
-                         Ok(url) => {
 
-                             if url.host().is_none() || url.port().is_none() {
 
-                                 panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed");
 
-                             }
 
-                             if url.scheme() != "http" {
 
-                                 panic!("Only unsecure http:// proxies are supported");
 
-                             }
 
-                             url
 
-                         },
 
-                     Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err)
 
-                     }
 
-                 },
 
-             ),
 
-             ap_port: matches
 
-                 .opt_str("ap-port")
 
-                 .map(|port| port.parse::<u16>().expect("Invalid port")),
 
-         }
 
-     };
 
-     let player_config = {
 
-         let bitrate = matches
 
-             .opt_str("b")
 
-             .as_ref()
 
-             .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
 
-             .unwrap_or(Bitrate::default());
 
-         PlayerConfig {
 
-             bitrate: bitrate,
 
-             normalisation: matches.opt_present("enable-volume-normalisation"),
 
-             normalisation_pregain: matches
 
-                 .opt_str("normalisation-pregain")
 
-                 .map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
 
-                 .unwrap_or(PlayerConfig::default().normalisation_pregain),
 
-         }
 
-     };
 
-     let connect_config = {
 
-         let device_type = matches
 
-             .opt_str("device-type")
 
-             .as_ref()
 
-             .map(|device_type| DeviceType::from_str(device_type).expect("Invalid device type"))
 
-             .unwrap_or(DeviceType::default());
 
-         ConnectConfig {
 
-             name: name,
 
-             device_type: device_type,
 
-             volume: initial_volume,
 
-             linear_volume: matches.opt_present("linear-volume"),
 
-         }
 
-     };
 
-     let enable_discovery = !matches.opt_present("disable-discovery");
 
-     Setup {
 
-         backend: backend,
 
-         cache: cache,
 
-         session_config: session_config,
 
-         player_config: player_config,
 
-         connect_config: connect_config,
 
-         credentials: credentials,
 
-         device: device,
 
-         enable_discovery: enable_discovery,
 
-         zeroconf_port: zeroconf_port,
 
-         mixer: mixer,
 
-         player_event_program: matches.opt_str("onevent"),
 
-     }
 
- }
 
- struct Main {
 
-     cache: Option<Cache>,
 
-     player_config: PlayerConfig,
 
-     session_config: SessionConfig,
 
-     connect_config: ConnectConfig,
 
-     backend: fn(Option<String>) -> Box<Sink>,
 
-     device: Option<String>,
 
-     mixer: fn() -> Box<Mixer>,
 
-     handle: Handle,
 
-     discovery: Option<DiscoveryStream>,
 
-     signal: IoStream<()>,
 
-     spirc: Option<Spirc>,
 
-     spirc_task: Option<SpircTask>,
 
-     connect: Box<Future<Item = Session, Error = io::Error>>,
 
-     shutdown: bool,
 
-     player_event_channel: Option<UnboundedReceiver<PlayerEvent>>,
 
-     player_event_program: Option<String>,
 
- }
 
- impl Main {
 
-     fn new(handle: Handle, setup: Setup) -> Main {
 
-         let mut task = Main {
 
-             handle: handle.clone(),
 
-             cache: setup.cache,
 
-             session_config: setup.session_config,
 
-             player_config: setup.player_config,
 
-             connect_config: setup.connect_config,
 
-             backend: setup.backend,
 
-             device: setup.device,
 
-             mixer: setup.mixer,
 
-             connect: Box::new(futures::future::empty()),
 
-             discovery: None,
 
-             spirc: None,
 
-             spirc_task: None,
 
-             shutdown: false,
 
-             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 {
 
-             let config = task.connect_config.clone();
 
-             let device_id = task.session_config.device_id.clone();
 
-             task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
 
-         }
 
-         if let Some(credentials) = setup.credentials {
 
-             task.credentials(credentials);
 
-         }
 
-         task
 
-     }
 
-     fn credentials(&mut self, credentials: Credentials) {
 
-         let config = self.session_config.clone();
 
-         let handle = self.handle.clone();
 
-         let connection = Session::connect(config, credentials, self.cache.clone(), handle);
 
-         self.connect = connection;
 
-         self.spirc = None;
 
-         let task = mem::replace(&mut self.spirc_task, None);
 
-         if let Some(task) = task {
 
-             self.handle.spawn(task);
 
-         }
 
-     }
 
- }
 
- impl Future for Main {
 
-     type Item = ();
 
-     type Error = ();
 
-     fn poll(&mut self) -> Poll<(), ()> {
 
-         loop {
 
-             let mut progress = false;
 
-             if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) {
 
-                 if let Some(ref spirc) = self.spirc {
 
-                     spirc.shutdown();
 
-                 }
 
-                 self.credentials(creds);
 
-                 progress = true;
 
-             }
 
-             if let Async::Ready(session) = self.connect.poll().unwrap() {
 
-                 self.connect = Box::new(futures::future::empty());
 
-                 let device = self.device.clone();
 
-                 let mixer = (self.mixer)();
 
-                 let player_config = self.player_config.clone();
 
-                 let connect_config = self.connect_config.clone();
 
-                 let audio_filter = mixer.get_audio_filter();
 
-                 let backend = self.backend;
 
-                 let (player, event_channel) =
 
-                     Player::new(player_config, session.clone(), audio_filter, move || {
 
-                         (backend)(device)
 
-                     });
 
-                 let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
 
-                 self.spirc = Some(spirc);
 
-                 self.spirc_task = Some(spirc_task);
 
-                 self.player_event_channel = Some(event_channel);
 
-                 progress = true;
 
-             }
 
-             if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
 
-                 trace!("Ctrl-C received");
 
-                 if !self.shutdown {
 
-                     if let Some(ref spirc) = self.spirc {
 
-                         spirc.shutdown();
 
-                     }
 
-                     self.shutdown = true;
 
-                 } else {
 
-                     return Ok(Async::Ready(()));
 
-                 }
 
-                 progress = true;
 
-             }
 
-             if let Some(ref mut spirc_task) = self.spirc_task {
 
-                 if let Async::Ready(()) = spirc_task.poll().unwrap() {
 
-                     if self.shutdown {
 
-                         return Ok(Async::Ready(()));
 
-                     } else {
 
-                         panic!("Spirc shut down unexpectedly");
 
-                     }
 
-                 }
 
-             }
 
-             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 {
 
-                         let child = run_program_on_events(event, program)
 
-                             .expect("program failed to start")
 
-                             .map(|status| if !status.success() {
 
-                                 error!("child exited with status {:?}", status.code());
 
-                             })
 
-                             .map_err(|e| error!("failed to wait on child process: {}", e));
 
-                         self.handle.spawn(child);
 
-                     }
 
-                 }
 
-             }
 
-             if !progress {
 
-                 return Ok(Async::NotReady);
 
-             }
 
-         }
 
-     }
 
- }
 
- fn main() {
 
-     if env::var("RUST_BACKTRACE").is_err() {
 
-         env::set_var("RUST_BACKTRACE", "full")
 
-     }
 
-     let mut core = Core::new().unwrap();
 
-     let handle = core.handle();
 
-     let args: Vec<String> = std::env::args().collect();
 
-     core.run(Main::new(handle, setup(&args))).unwrap()
 
- }
 
 
  |