librespot/src/main.rs

373 lines
12 KiB
Rust
Raw Normal View History

2017-01-10 16:31:12 +00:00
#[macro_use] extern crate log;
extern crate env_logger;
2017-01-18 18:41:22 +00:00
extern crate futures;
2017-02-21 23:25:04 +00:00
extern crate getopts;
extern crate librespot;
2017-01-18 18:41:22 +00:00
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_signal;
2015-04-25 20:32:07 +00:00
2017-01-10 16:31:12 +00:00
use env_logger::LogBuilder;
use futures::{Future, Async, Poll, Stream};
2017-01-10 16:31:12 +00:00
use std::env;
use std::io::{self, stderr, Write};
2017-01-10 16:31:12 +00:00
use std::path::PathBuf;
2017-02-21 23:25:04 +00:00
use std::process::exit;
2017-01-10 16:31:12 +00:00
use std::str::FromStr;
use tokio_core::reactor::{Handle, Core};
use tokio_io::IoStream;
use std::mem;
2017-08-03 18:58:44 +00:00
use librespot::core::authentication::{get_credentials, Credentials};
use librespot::core::cache::Cache;
use librespot::core::config::{Bitrate, DeviceType, PlayerConfig, SessionConfig, ConnectConfig};
use librespot::core::session::Session;
use librespot::core::version;
2017-01-18 18:41:22 +00:00
use librespot::audio_backend::{self, Sink, BACKENDS};
2017-08-03 18:58:44 +00:00
use librespot::discovery::{discovery, DiscoveryStream};
2017-01-25 21:49:18 +00:00
use librespot::mixer::{self, Mixer};
2017-08-03 18:58:44 +00:00
use librespot::player::Player;
use librespot::spirc::{Spirc, SpircTask};
2016-03-16 00:05:05 +00:00
fn usage(program: &str, opts: &getopts::Options) -> String {
let brief = format!("Usage: {} [options]", program);
2017-01-29 16:25:09 +00:00
opts.usage(&brief)
}
2017-01-10 16:31:12 +00:00
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)]
2017-01-18 18:41:22 +00:00
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,
2018-01-30 20:38:54 +00:00
zeroconf_port: u16,
2017-01-18 18:41:22 +00:00
}
fn setup(args: &[String]) -> Setup {
2017-01-10 16:31:12 +00:00
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.")
2017-01-10 16:31:12 +00:00
.reqopt("n", "name", "Device name", "NAME")
.optopt("", "device-type", "Displayed device type", "DEVICE_TYPE")
2017-01-10 16:31:12 +00:00
.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")
.optflag("v", "verbose", "Enable verbose output")
.optopt("u", "username", "Username to sign in with", "USERNAME")
.optopt("p", "password", "Password", "PASSWORD")
.optflag("", "disable-discovery", "Disable discovery mode")
2017-01-10 16:31:12 +00:00
.optopt("", "backend", "Audio backend to use. Use '?' to list options", "BACKEND")
2017-01-25 21:49:18 +00:00
.optopt("", "device", "Audio device to use. Use '?' to list options", "DEVICE")
2017-12-06 13:37:34 +00:00
.optopt("", "mixer", "Mixer to use", "MIXER")
2018-01-30 20:38:54 +00:00
.optopt("", "initial-volume", "Initial volume in %, once connected (must be from 0 to 100)", "VOLUME")
.optopt("z", "zeroconf-port", "The port the internal server advertised over zeroconf uses.", "ZEROCONF_PORT");
let matches = match opts.parse(&args[1..]) {
2016-01-02 15:19:39 +00:00
Ok(m) => m,
Err(f) => {
writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
exit(1);
2016-04-24 11:15:53 +00:00
}
};
2017-01-10 16:31:12 +00:00
let verbose = matches.opt_present("verbose");
setup_logging(verbose);
info!("librespot {} ({}). Built on {}. Build ID: {}",
2017-01-10 16:31:12 +00:00
version::short_sha(),
version::commit_date(),
version::short_now(),
version::build_id());
2017-01-10 16:31:12 +00:00
let backend_name = matches.opt_str("backend");
if backend_name == Some("?".into()) {
list_backends();
exit(0);
}
2017-04-28 22:24:55 +00:00
let backend = audio_backend::find(backend_name)
2017-01-10 16:31:12 +00:00
.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");
2018-01-30 23:05:54 +00:00
let initial_volume: i32;
if matches.opt_present("initial-volume") && matches.opt_str("initial-volume").unwrap().parse::<i32>().is_ok() {
let iv = matches.opt_str("initial-volume").unwrap().parse::<i32>().unwrap();
match iv {
iv if iv >= 0 && iv <= 100 => { initial_volume = iv * 0xFFFF / 100 }
_ => {
debug!("Volume needs to be a value from 0-100; set volume level to 50%");
initial_volume = 0x8000;
}
}
2018-01-30 23:05:54 +00:00
} else {
initial_volume = 0x8000;
}
2018-01-30 20:38:54 +00:00
let zeroconf_port: u16;
if matches.opt_present("zeroconf-port") && matches.opt_str("zeroconf-port").unwrap().parse::<u16>().is_ok() {
let z = matches.opt_str("zeroconf-port").unwrap().parse::<u16>().unwrap();
match z {
z if z >= 1024 => { zeroconf_port = z }
_ => { zeroconf_port = 0 }
}
} else {
zeroconf_port = 0
}
2017-01-18 18:41:22 +00:00
let name = matches.opt_str("name").unwrap();
let use_audio_cache = !matches.opt_present("disable-audio-cache");
2017-01-18 17:07:20 +00:00
let cache = matches.opt_str("c").map(|cache_location| {
Cache::new(PathBuf::from(cache_location), use_audio_cache)
});
2017-01-18 17:07:20 +00:00
let credentials = {
let cached_credentials = cache.as_ref().and_then(Cache::credentials);
2017-01-18 17:07:20 +00:00
get_credentials(
matches.opt_str("username"),
matches.opt_str("password"),
cached_credentials
)
};
2017-01-18 17:07:20 +00:00
let session_config = {
2017-08-03 18:58:44 +00:00
let device_id = librespot::core::session::device_id(&name);
SessionConfig {
user_agent: version::version_string(),
device_id: device_id,
}
2017-01-10 16:31:12 +00:00
};
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,
2017-12-06 13:37:34 +00:00
volume: initial_volume,
}
};
let enable_discovery = !matches.opt_present("disable-discovery");
2017-01-18 18:41:22 +00:00
Setup {
backend: backend,
cache: cache,
session_config: session_config,
player_config: player_config,
connect_config: connect_config,
2017-01-18 18:41:22 +00:00
credentials: credentials,
device: device,
enable_discovery: enable_discovery,
2018-01-30 20:38:54 +00:00
zeroconf_port: zeroconf_port,
mixer: mixer,
2017-01-18 18:41:22 +00:00
}
2017-01-10 16:31:12 +00:00
}
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,
2017-01-18 18:41:22 +00:00
discovery: Option<DiscoveryStream>,
signal: IoStream<()>,
spirc: Option<Spirc>,
spirc_task: Option<SpircTask>,
connect: Box<Future<Item=Session, Error=io::Error>>,
shutdown: bool,
}
2017-01-18 18:41:22 +00:00
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()),
};
if setup.enable_discovery {
let config = task.connect_config.clone();
let device_id = task.session_config.device_id.clone();
2018-01-30 20:38:54 +00:00
task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
}
2017-01-18 18:41:22 +00:00
if let Some(credentials) = setup.credentials {
task.credentials(credentials);
}
2017-04-28 22:24:55 +00:00
task
}
2017-01-18 18:41:22 +00:00
fn credentials(&mut self, credentials: Credentials) {
let config = self.session_config.clone();
let handle = self.handle.clone();
2017-02-22 14:28:09 +00:00
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;
2017-02-21 23:25:04 +00:00
}
2017-01-18 18:41:22 +00:00
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 = 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);
progress = true;
}
if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
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 !progress {
return Ok(Async::NotReady);
}
}
}
}
fn main() {
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()
2015-07-01 17:49:03 +00:00
}