main.rs 20 KB

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