main.rs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  1. use futures::sync::mpsc::UnboundedReceiver;
  2. use futures::{Async, Future, Poll, Stream};
  3. use log::{error, info, trace, warn};
  4. use sha1::{Digest, Sha1};
  5. use std::env;
  6. use std::io::{self, stderr, Write};
  7. use std::mem;
  8. use std::path::PathBuf;
  9. use std::process::exit;
  10. use std::str::FromStr;
  11. use tokio_core::reactor::{Core, Handle};
  12. use tokio_io::IoStream;
  13. use url::Url;
  14. use std::time::Instant;
  15. use librespot::core::authentication::{get_credentials, Credentials};
  16. use librespot::core::cache::Cache;
  17. use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig};
  18. use librespot::core::session::Session;
  19. use librespot::core::version;
  20. use librespot::connect::discovery::{discovery, DiscoveryStream};
  21. use librespot::connect::spirc::{Spirc, SpircTask};
  22. use librespot::playback::audio_backend::{self, Sink, BACKENDS};
  23. use librespot::playback::config::{Bitrate, PlayerConfig};
  24. use librespot::playback::mixer::{self, Mixer, MixerConfig};
  25. use librespot::playback::player::{Player, PlayerEvent};
  26. mod player_event_handler;
  27. use crate::player_event_handler::run_program_on_events;
  28. fn device_id(name: &str) -> String {
  29. hex::encode(Sha1::digest(name.as_bytes()))
  30. }
  31. fn usage(program: &str, opts: &getopts::Options) -> String {
  32. let brief = format!("Usage: {} [options]", program);
  33. opts.usage(&brief)
  34. }
  35. fn setup_logging(verbose: bool) {
  36. let mut builder = env_logger::Builder::new();
  37. match env::var("RUST_LOG") {
  38. Ok(config) => {
  39. builder.parse_filters(&config);
  40. builder.init();
  41. if verbose {
  42. warn!("`--verbose` flag overidden by `RUST_LOG` environment variable");
  43. }
  44. }
  45. Err(_) => {
  46. if verbose {
  47. builder.parse_filters("mdns=info,librespot=trace");
  48. } else {
  49. builder.parse_filters("mdns=info,librespot=info");
  50. }
  51. builder.init();
  52. }
  53. }
  54. }
  55. fn list_backends() {
  56. println!("Available Backends : ");
  57. for (&(name, _), idx) in BACKENDS.iter().zip(0..) {
  58. if idx == 0 {
  59. println!("- {} (default)", name);
  60. } else {
  61. println!("- {}", name);
  62. }
  63. }
  64. }
  65. #[derive(Clone)]
  66. struct Setup {
  67. backend: fn(Option<String>) -> Box<dyn Sink>,
  68. device: Option<String>,
  69. mixer: fn(Option<MixerConfig>) -> Box<dyn Mixer>,
  70. cache: Option<Cache>,
  71. player_config: PlayerConfig,
  72. session_config: SessionConfig,
  73. connect_config: ConnectConfig,
  74. mixer_config: MixerConfig,
  75. credentials: Option<Credentials>,
  76. enable_discovery: bool,
  77. zeroconf_port: u16,
  78. player_event_program: Option<String>,
  79. }
  80. fn setup(args: &[String]) -> Setup {
  81. let mut opts = getopts::Options::new();
  82. opts.optopt(
  83. "c",
  84. "cache",
  85. "Path to a directory where files will be cached.",
  86. "CACHE",
  87. ).optflag("", "disable-audio-cache", "Disable caching of the audio data.")
  88. .reqopt("n", "name", "Device name", "NAME")
  89. .optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
  90. .optopt(
  91. "b",
  92. "bitrate",
  93. "Bitrate (96, 160 or 320). Defaults to 160",
  94. "BITRATE",
  95. )
  96. .optopt(
  97. "",
  98. "onevent",
  99. "Run PROGRAM when playback is about to begin.",
  100. "PROGRAM",
  101. )
  102. .optflag("v", "verbose", "Enable verbose output")
  103. .optopt("u", "username", "Username to sign in with", "USERNAME")
  104. .optopt("p", "password", "Password", "PASSWORD")
  105. .optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY")
  106. .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")
  107. .optflag("", "disable-discovery", "Disable discovery mode")
  108. .optopt(
  109. "",
  110. "backend",
  111. "Audio backend to use. Use '?' to list options",
  112. "BACKEND",
  113. )
  114. .optopt(
  115. "",
  116. "device",
  117. "Audio device to use. Use '?' to list options if using portaudio or alsa",
  118. "DEVICE",
  119. )
  120. .optopt("", "mixer", "Mixer to use (alsa or softmixer)", "MIXER")
  121. .optopt(
  122. "m",
  123. "mixer-name",
  124. "Alsa mixer name, e.g \"PCM\" or \"Master\". Defaults to 'PCM'",
  125. "MIXER_NAME",
  126. )
  127. .optopt(
  128. "",
  129. "mixer-card",
  130. "Alsa mixer card, e.g \"hw:0\" or similar from `aplay -l`. Defaults to 'default' ",
  131. "MIXER_CARD",
  132. )
  133. .optopt(
  134. "",
  135. "mixer-index",
  136. "Alsa mixer index, Index of the cards mixer. Defaults to 0",
  137. "MIXER_INDEX",
  138. )
  139. .optopt(
  140. "",
  141. "initial-volume",
  142. "Initial volume in %, once connected (must be from 0 to 100)",
  143. "VOLUME",
  144. )
  145. .optopt(
  146. "",
  147. "zeroconf-port",
  148. "The port the internal server advertised over zeroconf uses.",
  149. "ZEROCONF_PORT",
  150. )
  151. .optflag(
  152. "",
  153. "enable-volume-normalisation",
  154. "Play all tracks at the same volume",
  155. )
  156. .optopt(
  157. "",
  158. "normalisation-pregain",
  159. "Pregain (dB) applied by volume normalisation",
  160. "PREGAIN",
  161. )
  162. .optflag(
  163. "",
  164. "linear-volume",
  165. "increase volume linear instead of logarithmic.",
  166. )
  167. .optflag(
  168. "",
  169. "autoplay",
  170. "autoplay similar songs when your music ends.",
  171. );
  172. let matches = match opts.parse(&args[1..]) {
  173. Ok(m) => m,
  174. Err(f) => {
  175. writeln!(
  176. stderr(),
  177. "error: {}\n{}",
  178. f.to_string(),
  179. usage(&args[0], &opts)
  180. )
  181. .unwrap();
  182. exit(1);
  183. }
  184. };
  185. let verbose = matches.opt_present("verbose");
  186. setup_logging(verbose);
  187. info!(
  188. "librespot {} ({}). Built on {}. Build ID: {}",
  189. version::short_sha(),
  190. version::commit_date(),
  191. version::short_now(),
  192. version::build_id()
  193. );
  194. let backend_name = matches.opt_str("backend");
  195. if backend_name == Some("?".into()) {
  196. list_backends();
  197. exit(0);
  198. }
  199. let backend = audio_backend::find(backend_name).expect("Invalid backend");
  200. let device = matches.opt_str("device");
  201. if device == Some("?".into()) {
  202. backend(device);
  203. exit(0);
  204. }
  205. let mixer_name = matches.opt_str("mixer");
  206. let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
  207. let mixer_config = MixerConfig {
  208. card: matches
  209. .opt_str("mixer-card")
  210. .unwrap_or(String::from("default")),
  211. mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
  212. index: matches
  213. .opt_str("mixer-index")
  214. .map(|index| index.parse::<u32>().unwrap())
  215. .unwrap_or(0),
  216. };
  217. let use_audio_cache = !matches.opt_present("disable-audio-cache");
  218. let cache = matches
  219. .opt_str("c")
  220. .map(|cache_location| Cache::new(PathBuf::from(cache_location), use_audio_cache));
  221. let initial_volume = matches
  222. .opt_str("initial-volume")
  223. .map(|volume| {
  224. let volume = volume.parse::<u16>().unwrap();
  225. if volume > 100 {
  226. panic!("Initial volume must be in the range 0-100");
  227. }
  228. (volume as i32 * 0xFFFF / 100) as u16
  229. })
  230. .or_else(|| cache.as_ref().and_then(Cache::volume))
  231. .unwrap_or(0x8000);
  232. let zeroconf_port = matches
  233. .opt_str("zeroconf-port")
  234. .map(|port| port.parse::<u16>().unwrap())
  235. .unwrap_or(0);
  236. let name = matches.opt_str("name").unwrap();
  237. let credentials = {
  238. let cached_credentials = cache.as_ref().and_then(Cache::credentials);
  239. let password = |username: &String| -> String {
  240. write!(stderr(), "Password for {}: ", username).unwrap();
  241. stderr().flush().unwrap();
  242. rpassword::read_password().unwrap()
  243. };
  244. get_credentials(
  245. matches.opt_str("username"),
  246. matches.opt_str("password"),
  247. cached_credentials,
  248. password,
  249. )
  250. };
  251. let session_config = {
  252. let device_id = device_id(&name);
  253. SessionConfig {
  254. user_agent: version::version_string(),
  255. device_id: device_id,
  256. proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map(
  257. |s| {
  258. match Url::parse(&s) {
  259. Ok(url) => {
  260. if url.host().is_none() || url.port().is_none() {
  261. panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed");
  262. }
  263. if url.scheme() != "http" {
  264. panic!("Only unsecure http:// proxies are supported");
  265. }
  266. url
  267. },
  268. Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err)
  269. }
  270. },
  271. ),
  272. ap_port: matches
  273. .opt_str("ap-port")
  274. .map(|port| port.parse::<u16>().expect("Invalid port")),
  275. }
  276. };
  277. let player_config = {
  278. let bitrate = matches
  279. .opt_str("b")
  280. .as_ref()
  281. .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
  282. .unwrap_or(Bitrate::default());
  283. PlayerConfig {
  284. bitrate: bitrate,
  285. normalisation: matches.opt_present("enable-volume-normalisation"),
  286. normalisation_pregain: matches
  287. .opt_str("normalisation-pregain")
  288. .map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
  289. .unwrap_or(PlayerConfig::default().normalisation_pregain),
  290. }
  291. };
  292. let connect_config = {
  293. let device_type = matches
  294. .opt_str("device-type")
  295. .as_ref()
  296. .map(|device_type| DeviceType::from_str(device_type).expect("Invalid device type"))
  297. .unwrap_or(DeviceType::default());
  298. ConnectConfig {
  299. name: name,
  300. device_type: device_type,
  301. volume: initial_volume,
  302. linear_volume: matches.opt_present("linear-volume"),
  303. autoplay: matches.opt_present("autoplay"),
  304. }
  305. };
  306. let enable_discovery = !matches.opt_present("disable-discovery");
  307. Setup {
  308. backend: backend,
  309. cache: cache,
  310. session_config: session_config,
  311. player_config: player_config,
  312. connect_config: connect_config,
  313. credentials: credentials,
  314. device: device,
  315. enable_discovery: enable_discovery,
  316. zeroconf_port: zeroconf_port,
  317. mixer: mixer,
  318. mixer_config: mixer_config,
  319. player_event_program: matches.opt_str("onevent"),
  320. }
  321. }
  322. struct Main {
  323. cache: Option<Cache>,
  324. player_config: PlayerConfig,
  325. session_config: SessionConfig,
  326. connect_config: ConnectConfig,
  327. backend: fn(Option<String>) -> Box<dyn Sink>,
  328. device: Option<String>,
  329. mixer: fn(Option<MixerConfig>) -> Box<dyn Mixer>,
  330. mixer_config: MixerConfig,
  331. handle: Handle,
  332. discovery: Option<DiscoveryStream>,
  333. signal: IoStream<()>,
  334. spirc: Option<Spirc>,
  335. spirc_task: Option<SpircTask>,
  336. connect: Box<dyn Future<Item = Session, Error = io::Error>>,
  337. shutdown: bool,
  338. last_credentials: Option<Credentials>,
  339. auto_connect_times: Vec<Instant>,
  340. player_event_channel: Option<UnboundedReceiver<PlayerEvent>>,
  341. player_event_program: Option<String>,
  342. }
  343. impl Main {
  344. fn new(handle: Handle, setup: Setup) -> Main {
  345. let mut task = Main {
  346. handle: handle.clone(),
  347. cache: setup.cache,
  348. session_config: setup.session_config,
  349. player_config: setup.player_config,
  350. connect_config: setup.connect_config,
  351. backend: setup.backend,
  352. device: setup.device,
  353. mixer: setup.mixer,
  354. mixer_config: setup.mixer_config,
  355. connect: Box::new(futures::future::empty()),
  356. discovery: None,
  357. spirc: None,
  358. spirc_task: None,
  359. shutdown: false,
  360. last_credentials: None,
  361. auto_connect_times: Vec::new(),
  362. signal: Box::new(tokio_signal::ctrl_c().flatten_stream()),
  363. player_event_channel: None,
  364. player_event_program: setup.player_event_program,
  365. };
  366. if setup.enable_discovery {
  367. let config = task.connect_config.clone();
  368. let device_id = task.session_config.device_id.clone();
  369. task.discovery =
  370. Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
  371. }
  372. if let Some(credentials) = setup.credentials {
  373. task.credentials(credentials);
  374. }
  375. task
  376. }
  377. fn credentials(&mut self, credentials: Credentials) {
  378. self.last_credentials = Some(credentials.clone());
  379. let config = self.session_config.clone();
  380. let handle = self.handle.clone();
  381. let connection = Session::connect(config, credentials, self.cache.clone(), handle);
  382. self.connect = connection;
  383. self.spirc = None;
  384. let task = mem::replace(&mut self.spirc_task, None);
  385. if let Some(task) = task {
  386. self.handle.spawn(task);
  387. }
  388. }
  389. }
  390. impl Future for Main {
  391. type Item = ();
  392. type Error = ();
  393. fn poll(&mut self) -> Poll<(), ()> {
  394. loop {
  395. let mut progress = false;
  396. if let Some(Async::Ready(Some(creds))) =
  397. self.discovery.as_mut().map(|d| d.poll().unwrap())
  398. {
  399. if let Some(ref spirc) = self.spirc {
  400. spirc.shutdown();
  401. }
  402. self.auto_connect_times.clear();
  403. self.credentials(creds);
  404. progress = true;
  405. }
  406. if let Async::Ready(session) = self.connect.poll().unwrap() {
  407. self.connect = Box::new(futures::future::empty());
  408. let mixer_config = self.mixer_config.clone();
  409. let mixer = (self.mixer)(Some(mixer_config));
  410. let player_config = self.player_config.clone();
  411. let connect_config = self.connect_config.clone();
  412. let audio_filter = mixer.get_audio_filter();
  413. let backend = self.backend;
  414. let device = self.device.clone();
  415. let (player, event_channel) =
  416. Player::new(player_config, session.clone(), audio_filter, move || {
  417. (backend)(device)
  418. });
  419. let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
  420. self.spirc = Some(spirc);
  421. self.spirc_task = Some(spirc_task);
  422. self.player_event_channel = Some(event_channel);
  423. progress = true;
  424. }
  425. if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
  426. trace!("Ctrl-C received");
  427. if !self.shutdown {
  428. if let Some(ref spirc) = self.spirc {
  429. spirc.shutdown();
  430. } else {
  431. return Ok(Async::Ready(()));
  432. }
  433. self.shutdown = true;
  434. } else {
  435. return Ok(Async::Ready(()));
  436. }
  437. progress = true;
  438. }
  439. let mut drop_spirc_and_try_to_reconnect = false;
  440. if let Some(ref mut spirc_task) = self.spirc_task {
  441. if let Async::Ready(()) = spirc_task.poll().unwrap() {
  442. if self.shutdown {
  443. return Ok(Async::Ready(()));
  444. } else {
  445. warn!("Spirc shut down unexpectedly");
  446. drop_spirc_and_try_to_reconnect = true;
  447. }
  448. progress = true;
  449. }
  450. }
  451. if drop_spirc_and_try_to_reconnect {
  452. self.spirc_task = None;
  453. while (!self.auto_connect_times.is_empty()) && ((Instant::now() - self.auto_connect_times[0]).as_secs() > 600) {
  454. let _ = self.auto_connect_times.remove(0);
  455. }
  456. if let Some(credentials) = self.last_credentials.clone() {
  457. if self.auto_connect_times.len() >= 5 {
  458. warn!("Spirc shut down too often. Not reconnecting automatically.");
  459. } else {
  460. self.auto_connect_times.push(Instant::now());
  461. self.credentials(credentials);
  462. }
  463. }
  464. }
  465. if let Some(ref mut player_event_channel) = self.player_event_channel {
  466. if let Async::Ready(Some(event)) = player_event_channel.poll().unwrap() {
  467. if let Some(ref program) = self.player_event_program {
  468. let child = run_program_on_events(event, program)
  469. .expect("program failed to start")
  470. .map(|status| {
  471. if !status.success() {
  472. error!("child exited with status {:?}", status.code());
  473. }
  474. })
  475. .map_err(|e| error!("failed to wait on child process: {}", e));
  476. self.handle.spawn(child);
  477. }
  478. }
  479. }
  480. if !progress {
  481. return Ok(Async::NotReady);
  482. }
  483. }
  484. }
  485. }
  486. fn main() {
  487. if env::var("RUST_BACKTRACE").is_err() {
  488. env::set_var("RUST_BACKTRACE", "full")
  489. }
  490. let mut core = Core::new().unwrap();
  491. let handle = core.handle();
  492. let args: Vec<String> = std::env::args().collect();
  493. core.run(Main::new(handle, setup(&args))).unwrap()
  494. }