diff --git a/Cargo.lock b/Cargo.lock index 30fafcaf..87ace96b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,7 +25,7 @@ dependencies = [ "protobuf_macros 0.6.0 (git+https://github.com/plietar/rust-protobuf-macros)", "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", "rpassword 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "rust-crypto 0.2.36 (git+https://github.com/awmath/rust-crypto.git?branch=avx2)", "serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -528,7 +528,7 @@ dependencies = [ [[package]] name = "rust-crypto" version = "0.2.36" -source = "registry+https://github.com/rust-lang/crates.io-index" +source = "git+https://github.com/awmath/rust-crypto.git?branch=avx2#394c247254dbe2ac5d44483232cf335d10cf0260" dependencies = [ "gcc 0.3.45 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.22 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1019,7 +1019,7 @@ dependencies = [ "checksum regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4278c17d0f6d62dfef0ab00028feb45bd7d2102843f80763474eeb1be8a10c01" "checksum regex-syntax 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9191b1f57603095f105d317e375d19b1c9c5c3185ea9633a99a6dcbed04457" "checksum rpassword 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ec4bdede957362ec6fdd550f7e79c6d14cad2bc26b2d062786234c6ee0cb27bb" -"checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" +"checksum rust-crypto 0.2.36 (git+https://github.com/awmath/rust-crypto.git?branch=avx2)" = "" "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" "checksum rustc_version 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "c5f5376ea5e30ce23c03eb77cbe4962b988deead10910c372b226388b594c084" "checksum scoped-tls 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f417c22df063e9450888a7561788e9bd46d3bb3c1466435b4eccb903807f147d" diff --git a/src/authentication/discovery.rs b/src/authentication/discovery.rs index 8c5b0050..a5732aed 100644 --- a/src/authentication/discovery.rs +++ b/src/authentication/discovery.rs @@ -19,19 +19,20 @@ use url; use authentication::Credentials; use util; +use config::ConnectConfig; #[derive(Clone)] struct Discovery(Arc); struct DiscoveryInner { + config: ConnectConfig, + device_id: String, private_key: BigUint, public_key: BigUint, - device_id: String, - device_name: String, tx: mpsc::UnboundedSender, } impl Discovery { - pub fn new(device_name: String, device_id: String) + pub fn new(config: ConnectConfig, device_id: String) -> (Discovery, mpsc::UnboundedReceiver) { let (tx, rx) = mpsc::unbounded(); @@ -41,8 +42,8 @@ impl Discovery { let public_key = util::powm(&DH_GENERATOR, &private_key, &DH_PRIME); let discovery = Discovery(Arc::new(DiscoveryInner { - device_name: device_name.to_owned(), - device_id: device_id.to_owned(), + config: config, + device_id: device_id, private_key: private_key, public_key: public_key, tx: tx, @@ -65,10 +66,10 @@ impl Discovery { "spotifyError": 0, "version": "2.1.0", "deviceID": (self.0.device_id), - "remoteName": (self.0.device_name), + "remoteName": (self.0.config.name), "activeUser": "", "publicKey": (public_key), - "deviceType": "UNKNOWN", + "deviceType": (self.0.config.device_type.to_string().to_uppercase()), "libraryVersion": "0.1.0", "accountReq": "PREMIUM", "brandDisplayName": "librespot", @@ -206,10 +207,10 @@ pub struct DiscoveryStream { task: Box>, } -pub fn discovery(handle: &Handle, device_name: String, device_id: String) +pub fn discovery(handle: &Handle, config: ConnectConfig, device_id: String) -> io::Result { - let (discovery, creds_rx) = Discovery::new(device_name.clone(), device_id); + let (discovery, creds_rx) = Discovery::new(config.clone(), device_id); let listener = TcpListener::bind(&"0.0.0.0:0".parse().unwrap(), handle)?; let addr = listener.local_addr()?; @@ -224,7 +225,7 @@ pub fn discovery(handle: &Handle, device_name: String, device_id: String) let responder = mdns::Responder::spawn(&handle)?; let svc = responder.register( "_spotify-connect._tcp".to_owned(), - device_name, + config.name, addr.port(), &["VERSION=1.0", "CPath=/"]); diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 00000000..6325d1a1 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,123 @@ +use uuid::Uuid; +use std::str::FromStr; +use std::fmt; + +use version; + +#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum Bitrate { + Bitrate96, + Bitrate160, + Bitrate320, +} + +impl FromStr for Bitrate { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "96" => Ok(Bitrate::Bitrate96), + "160" => Ok(Bitrate::Bitrate160), + "320" => Ok(Bitrate::Bitrate320), + _ => Err(()), + } + } +} + +impl Default for Bitrate { + fn default() -> Bitrate { + Bitrate::Bitrate160 + } +} + +#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)] +pub enum DeviceType { + Unknown = 0, + Computer = 1, + Tablet = 2, + Smartphone = 3, + Speaker = 4, + TV = 5, + AVR = 6, + STB = 7, + AudioDongle = 8, +} + +impl FromStr for DeviceType { + type Err = (); + fn from_str(s: &str) -> Result { + use self::DeviceType::*; + match s.to_lowercase().as_ref() { + "computer" => Ok(Computer), + "tablet" => Ok(Tablet), + "smartphone" => Ok(Smartphone), + "speaker" => Ok(Speaker), + "tv" => Ok(TV), + "avr" => Ok(AVR), + "stb" => Ok(STB), + "audiodongle" => Ok(AudioDongle), + _ => Err(()), + } + } +} + +impl fmt::Display for DeviceType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use self::DeviceType::*; + match *self { + Unknown => f.write_str("Unknown"), + Computer => f.write_str("Computer"), + Tablet => f.write_str("Tablet"), + Smartphone => f.write_str("Smartphone"), + Speaker => f.write_str("Speaker"), + TV => f.write_str("TV"), + AVR => f.write_str("AVR"), + STB => f.write_str("STB"), + AudioDongle => f.write_str("AudioDongle"), + } + } +} + +impl Default for DeviceType { + fn default() -> DeviceType { + DeviceType::Speaker + } +} + +#[derive(Clone,Debug)] +pub struct SessionConfig { + pub user_agent: String, + pub device_id: String, +} + +impl Default for SessionConfig { + fn default() -> SessionConfig { + let device_id = Uuid::new_v4().hyphenated().to_string(); + SessionConfig { + user_agent: version::version_string(), + device_id: device_id, + } + } +} + +#[derive(Clone,Debug)] +pub struct PlayerConfig { + pub bitrate: Bitrate, + pub onstart: Option, + pub onstop: Option, +} + +impl Default for PlayerConfig { + fn default() -> PlayerConfig { + PlayerConfig { + bitrate: Bitrate::default(), + onstart: None, + onstop: None, + } + } +} + +#[derive(Clone,Debug)] +pub struct ConnectConfig { + pub name: String, + pub device_type: DeviceType, +} diff --git a/src/lib.rs b/src/lib.rs index 2a502497..a6d66a45 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,14 +61,15 @@ pub mod audio_key; pub mod authentication; pub mod cache; pub mod channel; +pub mod config; pub mod diffie_hellman; +pub mod keymaster; pub mod mercury; pub mod metadata; +pub mod mixer; pub mod player; pub mod session; pub mod util; pub mod version; -pub mod mixer; -pub mod keymaster; include!(concat!(env!("OUT_DIR"), "/lib.rs")); diff --git a/src/main.rs b/src/main.rs index 04e21803..8f90c3b8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,7 +26,8 @@ use librespot::authentication::discovery::{discovery, DiscoveryStream}; use librespot::audio_backend::{self, Sink, BACKENDS}; use librespot::cache::Cache; use librespot::player::Player; -use librespot::session::{Bitrate, Config, Session}; +use librespot::session::Session; +use librespot::config::{Bitrate, DeviceType, PlayerConfig, SessionConfig, ConnectConfig}; use librespot::mixer::{self, Mixer}; use librespot::version; @@ -76,9 +77,10 @@ struct Setup { mixer: fn() -> Box, - name: String, cache: Option, - config: Config, + player_config: PlayerConfig, + session_config: SessionConfig, + connect_config: ConnectConfig, credentials: Option, enable_discovery: bool, } @@ -88,6 +90,7 @@ fn setup(args: &[String]) -> Setup { 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("", "onstart", "Run PROGRAM when playback is about to begin.", "PROGRAM") .optopt("", "onstop", "Run PROGRAM when playback has ended.", "PROGRAM") @@ -125,45 +128,69 @@ fn setup(args: &[String]) -> Setup { 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 bitrate = matches.opt_str("b").as_ref() - .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate")) - .unwrap_or(Bitrate::Bitrate160); - let name = matches.opt_str("name").unwrap(); - let device_id = librespot::session::device_id(&name); 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 cached_credentials = cache.as_ref().and_then(Cache::credentials); + let credentials = { + let cached_credentials = cache.as_ref().and_then(Cache::credentials); - let credentials = get_credentials(matches.opt_str("username"), - matches.opt_str("password"), - cached_credentials); + get_credentials( + matches.opt_str("username"), + matches.opt_str("password"), + cached_credentials + ) + }; + + let session_config = { + let device_id = librespot::session::device_id(&name); + + SessionConfig { + user_agent: version::version_string(), + device_id: device_id, + } + }; + + 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, + onstart: matches.opt_str("onstart"), + onstop: matches.opt_str("onstop"), + } + }; + + 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, + } + }; let enable_discovery = !matches.opt_present("disable-discovery"); - let config = Config { - user_agent: version::version_string(), - device_id: device_id, - bitrate: bitrate, - onstart: matches.opt_str("onstart"), - onstop: matches.opt_str("onstop"), - }; - - let device = matches.opt_str("device"); - Setup { - name: name, backend: backend, cache: cache, - config: config, + session_config: session_config, + player_config: player_config, + connect_config: connect_config, credentials: credentials, device: device, enable_discovery: enable_discovery, @@ -172,9 +199,10 @@ fn setup(args: &[String]) -> Setup { } struct Main { - name: String, cache: Option, - config: Config, + player_config: PlayerConfig, + session_config: SessionConfig, + connect_config: ConnectConfig, backend: fn(Option) -> Box, device: Option, mixer: fn() -> Box, @@ -191,22 +219,16 @@ struct Main { } impl Main { - fn new(handle: Handle, - name: String, - config: Config, - cache: Option, - backend: fn(Option) -> Box, - device: Option, - mixer: fn() -> Box) -> Main - { - Main { + fn new(handle: Handle, setup: Setup) -> Main { + let mut task = Main { handle: handle.clone(), - name: name, - cache: cache, - config: config, - backend: backend, - device: device, - mixer: mixer, + 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, @@ -214,18 +236,24 @@ impl Main { spirc_task: None, shutdown: false, signal: tokio_signal::ctrl_c(&handle).flatten_stream().boxed(), + }; + + 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).unwrap()); } - } - fn discovery(&mut self) { - let device_id = self.config.device_id.clone(); - let name = self.name.clone(); + if let Some(credentials) = setup.credentials { + task.credentials(credentials); + } - self.discovery = Some(discovery(&self.handle, name, device_id).unwrap()); + task } fn credentials(&mut self, credentials: Credentials) { - let config = self.config.clone(); + let config = self.session_config.clone(); let handle = self.handle.clone(); let connection = Session::connect(config, credentials, self.cache.clone(), handle); @@ -260,14 +288,16 @@ impl Future for Main { 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 = Player::new(session.clone(), audio_filter, move || { + let player = Player::new(player_config, session.clone(), audio_filter, move || { (backend)(device) }); - let (spirc, spirc_task) = Spirc::new(self.name.clone(), session, player, mixer); + let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer); self.spirc = Some(spirc); self.spirc_task = Some(spirc_task); @@ -309,16 +339,7 @@ fn main() { let handle = core.handle(); let args: Vec = std::env::args().collect(); - let Setup { name, backend, config, device, cache, enable_discovery, credentials, mixer } = setup(&args); - let mut task = Main::new(handle, name, config, cache, backend, device, mixer); - if enable_discovery { - task.discovery(); - } - if let Some(credentials) = credentials { - task.credentials(credentials); - } - - core.run(task).unwrap() + core.run(Main::new(handle, setup(&args))).unwrap() } diff --git a/src/player.rs b/src/player.rs index 2b7b83c1..86009eaf 100644 --- a/src/player.rs +++ b/src/player.rs @@ -12,9 +12,10 @@ use audio_backend::Sink; use audio_decrypt::AudioDecrypt; use audio_file::AudioFile; use metadata::{FileFormat, Track}; -use session::{Bitrate, Session}; +use session::Session; use mixer::AudioFilter; use util::{self, SpotifyId, Subfile}; +use config::{Bitrate, PlayerConfig}; #[derive(Clone)] pub struct Player { @@ -23,6 +24,7 @@ pub struct Player { struct PlayerInternal { session: Session, + config: PlayerConfig, commands: std::sync::mpsc::Receiver, state: PlayerState, @@ -39,8 +41,11 @@ enum PlayerCommand { } impl Player { - pub fn new(session: Session, audio_filter: Option>, sink_builder: F) -> Player - where F: FnOnce() -> Box + Send + 'static { + pub fn new(config: PlayerConfig, session: Session, + audio_filter: Option>, + sink_builder: F) -> Player + where F: FnOnce() -> Box + Send + 'static + { let (cmd_tx, cmd_rx) = std::sync::mpsc::channel(); thread::spawn(move || { @@ -48,6 +53,7 @@ impl Player { let internal = PlayerInternal { session: session, + config: config, commands: cmd_rx, state: PlayerState::Stopped, @@ -314,13 +320,13 @@ impl PlayerInternal { } fn run_onstart(&self) { - if let Some(ref program) = self.session.config().onstart { + if let Some(ref program) = self.config.onstart { util::run_program(program) } } fn run_onstop(&self) { - if let Some(ref program) = self.session.config().onstop { + if let Some(ref program) = self.config.onstop { util::run_program(program) } } @@ -353,7 +359,7 @@ impl PlayerInternal { } }; - let format = match self.session.config().bitrate { + let format = match self.config.bitrate { Bitrate::Bitrate96 => FileFormat::OGG_VORBIS_96, Bitrate::Bitrate160 => FileFormat::OGG_VORBIS_160, Bitrate::Bitrate320 => FileFormat::OGG_VORBIS_320, diff --git a/src/session.rs b/src/session.rs index 86162bd9..88daf639 100644 --- a/src/session.rs +++ b/src/session.rs @@ -3,20 +3,17 @@ use crypto::sha1::Sha1; use futures::sync::mpsc; use futures::{Future, Stream, BoxFuture, IntoFuture, Poll, Async}; use std::io; -use std::result::Result; -use std::str::FromStr; use std::sync::{RwLock, Arc, Weak}; use tokio_core::io::EasyBuf; use tokio_core::reactor::{Handle, Remote}; use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; -use uuid::Uuid; use apresolve::apresolve_or_fallback; use authentication::Credentials; use cache::Cache; use component::Lazy; use connection; -use version; +use config::SessionConfig; use audio_key::AudioKeyManager; use channel::ChannelManager; @@ -24,53 +21,13 @@ use mercury::MercuryManager; use metadata::MetadataManager; use audio_file::AudioFileManager; -#[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] -pub enum Bitrate { - Bitrate96, - Bitrate160, - Bitrate320, -} -impl FromStr for Bitrate { - type Err = String; - fn from_str(s: &str) -> Result { - match s { - "96" => Ok(Bitrate::Bitrate96), - "160" => Ok(Bitrate::Bitrate160), - "320" => Ok(Bitrate::Bitrate320), - _ => Err(s.into()), - } - } -} - -#[derive(Clone)] -pub struct Config { - pub user_agent: String, - pub device_id: String, - pub bitrate: Bitrate, - pub onstart: Option, - pub onstop: Option, -} - -impl Default for Config { - fn default() -> Config { - let device_id = Uuid::new_v4().hyphenated().to_string(); - Config { - user_agent: version::version_string(), - device_id: device_id, - bitrate: Bitrate::Bitrate160, - onstart: None, - onstop: None, - } - } -} - pub struct SessionData { country: String, canonical_username: String, } pub struct SessionInternal { - config: Config, + config: SessionConfig, data: RwLock, tx_connection: mpsc::UnboundedSender<(u8, Vec)>, @@ -99,7 +56,7 @@ pub fn device_id(name: &str) -> String { } impl Session { - pub fn connect(config: Config, credentials: Credentials, + pub fn connect(config: SessionConfig, credentials: Credentials, cache: Option, handle: Handle) -> Box> { @@ -135,7 +92,7 @@ impl Session { } fn create(handle: &Handle, transport: connection::Transport, - config: Config, cache: Option, username: String) + config: SessionConfig, cache: Option, username: String) -> (Session, BoxFuture<(), io::Error>) { let (sink, stream) = transport.split(); @@ -240,7 +197,7 @@ impl Session { self.0.cache.as_ref() } - pub fn config(&self) -> &Config { + pub fn config(&self) -> &SessionConfig { &self.0.config } diff --git a/src/spirc.rs b/src/spirc.rs index 803a4135..2b877939 100644 --- a/src/spirc.rs +++ b/src/spirc.rs @@ -5,9 +5,10 @@ use futures::sync::{oneshot, mpsc}; use futures::{Future, Stream, Sink, Async, Poll, BoxFuture}; use protobuf::{self, Message}; +use config::ConnectConfig; use mercury::MercuryError; -use player::Player; use mixer::Mixer; +use player::Player; use session::Session; use util::{now_ms, SpotifyId, SeqGenerator}; use version; @@ -59,13 +60,13 @@ fn initial_state() -> State { }) } -fn initial_device_state(name: String, volume: u16) -> DeviceState { +fn initial_device_state(config: ConnectConfig, volume: u16) -> DeviceState { protobuf_init!(DeviceState::new(), { sw_version: version::version_string(), is_active: false, can_play: true, volume: volume as u32, - name: name, + name: config.name, capabilities => [ @{ typ: protocol::spirc::CapabilityType::kCanBePlayer, @@ -73,7 +74,7 @@ fn initial_device_state(name: String, volume: u16) -> DeviceState { }, @{ typ: protocol::spirc::CapabilityType::kDeviceType, - intValue => [5] + intValue => [config.device_type as i64] }, @{ typ: protocol::spirc::CapabilityType::kGaiaEqConnectId, @@ -118,7 +119,7 @@ fn initial_device_state(name: String, volume: u16) -> DeviceState { } impl Spirc { - pub fn new(name: String, session: Session, player: Player, mixer: Box) + pub fn new(config: ConnectConfig, session: Session, player: Player, mixer: Box) -> (Spirc, SpircTask) { debug!("new Spirc[{}]", session.session_id()); @@ -141,7 +142,7 @@ impl Spirc { let (cmd_tx, cmd_rx) = mpsc::unbounded(); let volume = 0xFFFF; - let device = initial_device_state(name, volume); + let device = initial_device_state(config, volume); mixer.set_volume(volume); let mut task = SpircTask {