Merge branch 'dev' into connection-lost-crash

This commit is contained in:
Sasha Hilton 2020-01-24 01:02:40 +01:00 committed by GitHub
commit cdcb919c08
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
55 changed files with 448 additions and 324 deletions

View file

@ -15,7 +15,8 @@ before_cache:
- rm -rfv target/debug/deps/liblibrespot-* - rm -rfv target/debug/deps/liblibrespot-*
- rm -rfv target/debug/deps/librespot-* - rm -rfv target/debug/deps/librespot-*
- rm -rfv target/debug/{librespot,liblibrespot}.d - rm -rfv target/debug/{librespot,liblibrespot}.d
- cargo clean -p librespot librespot-core librespot-connect librespot-audio librespot-metadata librespot-playback - rm -rfv target/debug/incremental/{build_script_build,librespot,librespot_core,librespot_connect,librespot_audio,librespot_metadata,librespot_playback,librespot_player,librespot_protocol}-*
- cargo clean -p librespot -p librespot-core -p librespot-connect -p librespot-audio -p librespot-metadata -p librespot-playback
addons: addons:
apt: apt:
@ -28,12 +29,14 @@ addons:
- libsdl2-dev - libsdl2-dev
before_script: before_script:
- rustup component add rustfmt
- mkdir -p ~/.cargo - mkdir -p ~/.cargo
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config - echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config - echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
- rustup target add armv7-unknown-linux-gnueabihf - rustup target add armv7-unknown-linux-gnueabihf
script: script:
- cargo fmt --all -- --check
- cargo build --locked --no-default-features - cargo build --locked --no-default-features
- cargo build --locked --examples - cargo build --locked --examples
- cargo build --locked --no-default-features --features "with-tremor" - cargo build --locked --no-default-features --features "with-tremor"

View file

@ -7,6 +7,7 @@ description = "An open source client library for Spotify, with support for Spoti
keywords = ["spotify"] keywords = ["spotify"]
repository = "https://github.com/librespot-org/librespot" repository = "https://github.com/librespot-org/librespot"
readme = "README.md" readme = "README.md"
edition = "2018"
[workspace] [workspace]

View file

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Paul Lietar <paul@lietar.net>"] authors = ["Paul Lietar <paul@lietar.net>"]
description="The audio fetching and processing logic for librespot" description="The audio fetching and processing logic for librespot"
license="MIT" license="MIT"
edition = "2018"
[dependencies.librespot-core] [dependencies.librespot-core]
path = "../core" path = "../core"

View file

@ -1,16 +1,13 @@
use std::io; use std::io;
use aes_ctr::Aes128Ctr;
use aes_ctr::stream_cipher::{
NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek
};
use aes_ctr::stream_cipher::generic_array::GenericArray; use aes_ctr::stream_cipher::generic_array::GenericArray;
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
use aes_ctr::Aes128Ctr;
use librespot_core::audio_key::AudioKey; use librespot_core::audio_key::AudioKey;
const AUDIO_AESIV: [u8; 16] = [ const AUDIO_AESIV: [u8; 16] = [
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
]; ];
pub struct AudioDecrypt<T: io::Read> { pub struct AudioDecrypt<T: io::Read> {
@ -30,7 +27,7 @@ impl<T: io::Read> AudioDecrypt<T> {
impl<T: io::Read> io::Read for AudioDecrypt<T> { impl<T: io::Read> io::Read for AudioDecrypt<T> {
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> { fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
let len = try!(self.reader.read(output)); let len = self.reader.read(output)?;
self.cipher.apply_keystream(&mut output[..len]); self.cipher.apply_keystream(&mut output[..len]);
@ -40,7 +37,7 @@ impl<T: io::Read> io::Read for AudioDecrypt<T> {
impl<T: io::Read + io::Seek> io::Seek for AudioDecrypt<T> { impl<T: io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> { fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
let newpos = try!(self.reader.seek(pos)); let newpos = self.reader.seek(pos)?;
self.cipher.seek(newpos); self.cipher.seek(newpos);

View file

@ -1,9 +1,9 @@
use crate::range_set::{Range, RangeSet};
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use bytes::Bytes; use bytes::Bytes;
use futures::sync::{mpsc, oneshot}; use futures::sync::{mpsc, oneshot};
use futures::Stream; use futures::Stream;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use range_set::{Range, RangeSet};
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::fs; use std::fs;
use std::io::{self, Read, Seek, SeekFrom, Write}; use std::io::{self, Read, Seek, SeekFrom, Write};
@ -446,7 +446,7 @@ impl AudioFile {
channel_tx: None, channel_tx: None,
stream_shared: None, stream_shared: None,
file_size: file.metadata().unwrap().len() as usize, file_size: file.metadata().unwrap().len() as usize,
} };
} }
} }
} }
@ -514,7 +514,10 @@ impl AudioFileFetchDataReceiver {
request_length: usize, request_length: usize,
request_sent_time: Instant, request_sent_time: Instant,
) -> AudioFileFetchDataReceiver { ) -> AudioFileFetchDataReceiver {
let measure_ping_time = shared.number_of_open_requests.load(atomic::Ordering::SeqCst) == 0; let measure_ping_time = shared
.number_of_open_requests
.load(atomic::Ordering::SeqCst)
== 0;
shared shared
.number_of_open_requests .number_of_open_requests
@ -562,7 +565,8 @@ impl Future for AudioFileFetchDataReceiver {
if let Some(request_sent_time) = self.request_sent_time { if let Some(request_sent_time) = self.request_sent_time {
let duration = Instant::now() - request_sent_time; let duration = Instant::now() - request_sent_time;
let duration_ms: u64; let duration_ms: u64;
if 0.001 * (duration.as_millis() as f64) > MAXIMUM_ASSUMED_PING_TIME_SECONDS if 0.001 * (duration.as_millis() as f64)
> MAXIMUM_ASSUMED_PING_TIME_SECONDS
{ {
duration_ms = (MAXIMUM_ASSUMED_PING_TIME_SECONDS * 1000.0) as u64; duration_ms = (MAXIMUM_ASSUMED_PING_TIME_SECONDS * 1000.0) as u64;
} else { } else {
@ -714,8 +718,13 @@ impl AudioFileFetch {
ranges_to_request.subtract_range_set(&download_status.requested); ranges_to_request.subtract_range_set(&download_status.requested);
for range in ranges_to_request.iter() { for range in ranges_to_request.iter() {
let (_headers, data) = let (_headers, data) = request_range(
request_range(&self.session, self.shared.file_id, range.start, range.length).split(); &self.session,
self.shared.file_id,
range.start,
range.length,
)
.split();
download_status.requested.add_range(range); download_status.requested.add_range(range);
@ -749,7 +758,10 @@ impl AudioFileFetch {
// download data from after the current read position first // download data from after the current read position first
let mut tail_end = RangeSet::new(); let mut tail_end = RangeSet::new();
let read_position = self.shared.read_position.load(atomic::Ordering::Relaxed); let read_position = self.shared.read_position.load(atomic::Ordering::Relaxed);
tail_end.add_range(&Range::new(read_position, self.shared.file_size - read_position)); tail_end.add_range(&Range::new(
read_position,
self.shared.file_size - read_position,
));
let tail_end = tail_end.intersection(&missing_data); let tail_end = tail_end.intersection(&missing_data);
if !tail_end.is_empty() { if !tail_end.is_empty() {
@ -794,8 +806,9 @@ impl AudioFileFetch {
let ping_time_ms: usize = match self.network_response_times_ms.len() { let ping_time_ms: usize = match self.network_response_times_ms.len() {
1 => self.network_response_times_ms[0] as usize, 1 => self.network_response_times_ms[0] as usize,
2 => { 2 => {
((self.network_response_times_ms[0] + self.network_response_times_ms[1]) / 2) ((self.network_response_times_ms[0]
as usize + self.network_response_times_ms[1])
/ 2) as usize
} }
3 => { 3 => {
let mut times = self.network_response_times_ms.clone(); let mut times = self.network_response_times_ms.clone();
@ -863,10 +876,12 @@ impl AudioFileFetch {
self.download_range(request.start, request.length); self.download_range(request.start, request.length);
} }
Ok(Async::Ready(Some(StreamLoaderCommand::RandomAccessMode()))) => { Ok(Async::Ready(Some(StreamLoaderCommand::RandomAccessMode()))) => {
*(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::RandomAccess(); *(self.shared.download_strategy.lock().unwrap()) =
DownloadStrategy::RandomAccess();
} }
Ok(Async::Ready(Some(StreamLoaderCommand::StreamMode()))) => { Ok(Async::Ready(Some(StreamLoaderCommand::StreamMode()))) => {
*(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::Streaming(); *(self.shared.download_strategy.lock().unwrap()) =
DownloadStrategy::Streaming();
} }
Ok(Async::Ready(Some(StreamLoaderCommand::Close()))) => { Ok(Async::Ready(Some(StreamLoaderCommand::Close()))) => {
return Ok(Async::Ready(())); return Ok(Async::Ready(()));
@ -908,15 +923,20 @@ impl Future for AudioFileFetch {
} }
if let DownloadStrategy::Streaming() = self.get_download_strategy() { if let DownloadStrategy::Streaming() = self.get_download_strategy() {
let number_of_open_requests = let number_of_open_requests = self
self.shared.number_of_open_requests.load(atomic::Ordering::SeqCst); .shared
.number_of_open_requests
.load(atomic::Ordering::SeqCst);
let max_requests_to_send = let max_requests_to_send =
MAX_PREFETCH_REQUESTS - min(MAX_PREFETCH_REQUESTS, number_of_open_requests); MAX_PREFETCH_REQUESTS - min(MAX_PREFETCH_REQUESTS, number_of_open_requests);
if max_requests_to_send > 0 { if max_requests_to_send > 0 {
let bytes_pending: usize = { let bytes_pending: usize = {
let download_status = self.shared.download_status.lock().unwrap(); let download_status = self.shared.download_status.lock().unwrap();
download_status.requested.minus(&download_status.downloaded).len() download_status
.requested
.minus(&download_status.downloaded)
.len()
}; };
let ping_time_seconds = let ping_time_seconds =
@ -924,9 +944,11 @@ impl Future for AudioFileFetch {
let download_rate = self.session.channel().get_download_rate_estimate(); let download_rate = self.session.channel().get_download_rate_estimate();
let desired_pending_bytes = max( let desired_pending_bytes = max(
(PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * self.shared.stream_data_rate as f64) (PREFETCH_THRESHOLD_FACTOR
* ping_time_seconds
* self.shared.stream_data_rate as f64) as usize,
(FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64)
as usize, as usize,
(FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64) as usize,
); );
if bytes_pending < desired_pending_bytes { if bytes_pending < desired_pending_bytes {
@ -1003,13 +1025,15 @@ impl Read for AudioFileStreaming {
.unwrap() .unwrap()
.0; .0;
} }
let available_length = download_status.downloaded.contained_length_from_value(offset); let available_length = download_status
.downloaded
.contained_length_from_value(offset);
assert!(available_length > 0); assert!(available_length > 0);
drop(download_status); drop(download_status);
self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap(); self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap();
let read_len = min(length, available_length); let read_len = min(length, available_length);
let read_len = try!(self.read_file.read(&mut output[..read_len])); let read_len = self.read_file.read(&mut output[..read_len])?;
if download_message_printed { if download_message_printed {
debug!( debug!(
@ -1031,7 +1055,7 @@ impl Read for AudioFileStreaming {
impl Seek for AudioFileStreaming { impl Seek for AudioFileStreaming {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> { fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.position = try!(self.read_file.seek(pos)); self.position = self.read_file.seek(pos)?;
// Do not seek past EOF // Do not seek past EOF
self.shared self.shared
.read_position .read_position

View file

@ -31,6 +31,6 @@ pub use fetch::{
}; };
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))] #[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
pub use lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket}; pub use crate::lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))] #[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket}; pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};

View file

@ -77,7 +77,7 @@ impl error::Error for VorbisError {
error::Error::description(&self.0) error::Error::description(&self.0)
} }
fn cause(&self) -> Option<&error::Error> { fn source(&self) -> Option<&(dyn error::Error + 'static)> {
error::Error::cause(&self.0) error::Error::source(&self.0)
} }
} }

View file

@ -113,7 +113,8 @@ impl RangeSet {
// the new range starts after anything we already passed and ends before the next range starts (they don't touch) -> insert it. // the new range starts after anything we already passed and ends before the next range starts (they don't touch) -> insert it.
self.ranges.insert(index, range.clone()); self.ranges.insert(index, range.clone());
return; return;
} else if range.start <= self.ranges[index].end() && self.ranges[index].start <= range.end() } else if range.start <= self.ranges[index].end()
&& self.ranges[index].start <= range.end()
{ {
// the new range overlaps (or touches) the first range. They are to be merged. // the new range overlaps (or touches) the first range. They are to be merged.
// In addition we might have to merge further ranges in as well. // In addition we might have to merge further ranges in as well.
@ -161,7 +162,9 @@ impl RangeSet {
if range.end() <= self.ranges[index].start { if range.end() <= self.ranges[index].start {
// the remaining ranges are past the one to subtract. -> we're done. // the remaining ranges are past the one to subtract. -> we're done.
return; return;
} else if range.start <= self.ranges[index].start && self.ranges[index].start < range.end() { } else if range.start <= self.ranges[index].start
&& self.ranges[index].start < range.end()
{
// the range to subtract started before the current range and reaches into the current range // the range to subtract started before the current range and reaches into the current range
// -> we have to remove the beginning of the range or the entire range and do the same for following ranges. // -> we have to remove the beginning of the range or the entire range and do the same for following ranges.
@ -223,8 +226,14 @@ impl RangeSet {
other_index += 1; other_index += 1;
} else { } else {
// the two intervals overlap. Add the union and advance the index of the one that ends first. // the two intervals overlap. Add the union and advance the index of the one that ends first.
let new_start = max(self.ranges[self_index].start, other.ranges[other_index].start); let new_start = max(
let new_end = min(self.ranges[self_index].end(), other.ranges[other_index].end()); self.ranges[self_index].start,
other.ranges[other_index].start,
);
let new_end = min(
self.ranges[self_index].end(),
other.ranges[other_index].end(),
);
assert!(new_start <= new_end); assert!(new_start <= new_end);
result.add_range(&Range::new(new_start, new_end - new_start)); result.add_range(&Range::new(new_start, new_end - new_start));
if self.ranges[self_index].end() <= other.ranges[other_index].end() { if self.ranges[self_index].end() <= other.ranges[other_index].end() {

View file

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Paul Lietar <paul@lietar.net>"] authors = ["Paul Lietar <paul@lietar.net>"]
description="The discovery and Spotify Connect logic for librespot" description="The discovery and Spotify Connect logic for librespot"
license="MIT" license="MIT"
edition = "2018"
[dependencies.librespot-core] [dependencies.librespot-core]
path = "../core" path = "../core"

View file

@ -1,5 +1,5 @@
use crate::protocol::spirc::TrackRef;
use librespot_core::spotify_id::SpotifyId; use librespot_core::spotify_id::SpotifyId;
use protocol::spirc::TrackRef;
use serde; use serde;
@ -69,7 +69,7 @@ fn deserialize_protobuf_TrackRef<'d, D>(de: D) -> Result<Vec<TrackRef>, D::Error
where where
D: serde::Deserializer<'d>, D: serde::Deserializer<'d>,
{ {
let v: Vec<TrackContext> = try!(serde::Deserialize::deserialize(de)); let v: Vec<TrackContext> = serde::Deserialize::deserialize(de)?;
let track_vec = v let track_vec = v
.iter() .iter()
.map(|v| { .map(|v| {

View file

@ -1,13 +1,13 @@
use base64;
use sha1::{Sha1, Digest};
use hmac::{Hmac, Mac};
use aes_ctr::Aes128Ctr;
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
use aes_ctr::stream_cipher::generic_array::GenericArray; use aes_ctr::stream_cipher::generic_array::GenericArray;
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
use aes_ctr::Aes128Ctr;
use base64;
use futures::sync::mpsc; use futures::sync::mpsc;
use futures::{Future, Poll, Stream}; use futures::{Future, Poll, Stream};
use hmac::{Hmac, Mac};
use hyper::server::{Http, Request, Response, Service}; use hyper::server::{Http, Request, Response, Service};
use hyper::{self, Get, Post, StatusCode}; use hyper::{self, Get, Post, StatusCode};
use sha1::{Digest, Sha1};
#[cfg(feature = "with-dns-sd")] #[cfg(feature = "with-dns-sd")]
use dns_sd::DNSService; use dns_sd::DNSService;
@ -114,21 +114,18 @@ impl Discovery {
let base_key = &base_key[..16]; let base_key = &base_key[..16];
let checksum_key = { let checksum_key = {
let mut h = HmacSha1::new_varkey(base_key) let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size");
.expect("HMAC can take key of any size");
h.input(b"checksum"); h.input(b"checksum");
h.result().code() h.result().code()
}; };
let encryption_key = { let encryption_key = {
let mut h = HmacSha1::new_varkey(&base_key) let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size");
.expect("HMAC can take key of any size");
h.input(b"encryption"); h.input(b"encryption");
h.result().code() h.result().code()
}; };
let mut h = HmacSha1::new_varkey(&checksum_key) let mut h = HmacSha1::new_varkey(&checksum_key).expect("HMAC can take key of any size");
.expect("HMAC can take key of any size");
h.input(encrypted); h.input(encrypted);
if let Err(_) = h.verify(cksum) { if let Err(_) = h.verify(cksum) {
warn!("Login error for user {:?}: MAC mismatch", username); warn!("Login error for user {:?}: MAC mismatch", username);
@ -139,7 +136,7 @@ impl Discovery {
}); });
let body = result.to_string(); let body = result.to_string();
return ::futures::finished(Response::new().with_body(body)) return ::futures::finished(Response::new().with_body(body));
} }
let decrypted = { let decrypted = {
@ -152,7 +149,8 @@ impl Discovery {
String::from_utf8(data).unwrap() String::from_utf8(data).unwrap()
}; };
let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id); let credentials =
Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
self.0.tx.unbounded_send(credentials).unwrap(); self.0.tx.unbounded_send(credentials).unwrap();
@ -175,7 +173,7 @@ impl Service for Discovery {
type Request = Request; type Request = Request;
type Response = Response; type Response = Response;
type Error = hyper::Error; type Error = hyper::Error;
type Future = Box<Future<Item = Response, Error = hyper::Error>>; type Future = Box<dyn Future<Item = Response, Error = hyper::Error>>;
fn call(&self, request: Request) -> Self::Future { fn call(&self, request: Request) -> Self::Future {
let mut params = BTreeMap::new(); let mut params = BTreeMap::new();
@ -194,17 +192,18 @@ impl Service for Discovery {
body.fold(Vec::new(), |mut acc, chunk| { body.fold(Vec::new(), |mut acc, chunk| {
acc.extend_from_slice(chunk.as_ref()); acc.extend_from_slice(chunk.as_ref());
Ok::<_, hyper::Error>(acc) Ok::<_, hyper::Error>(acc)
}).map(move |body| { })
params.extend(url::form_urlencoded::parse(&body).into_owned()); .map(move |body| {
params params.extend(url::form_urlencoded::parse(&body).into_owned());
}) params
.and_then( })
move |params| match (method, params.get("action").map(AsRef::as_ref)) { .and_then(move |params| {
(Get, Some("getInfo")) => this.handle_get_info(&params), match (method, params.get("action").map(AsRef::as_ref)) {
(Post, Some("addUser")) => this.handle_add_user(&params), (Get, Some("getInfo")) => this.handle_get_info(&params),
_ => this.not_found(), (Post, Some("addUser")) => this.handle_add_user(&params),
}, _ => this.not_found(),
), }
}),
) )
} }
} }
@ -235,7 +234,8 @@ pub fn discovery(
&format!("0.0.0.0:{}", port).parse().unwrap(), &format!("0.0.0.0:{}", port).parse().unwrap(),
&handle, &handle,
move || Ok(discovery.clone()), move || Ok(discovery.clone()),
).unwrap() )
.unwrap()
}; };
let s_port = serve.incoming_ref().local_addr().port(); let s_port = serve.incoming_ref().local_addr().port();
@ -260,7 +260,8 @@ pub fn discovery(
None, None,
s_port, s_port,
&["VERSION=1.0", "CPath=/"], &["VERSION=1.0", "CPath=/"],
).unwrap(); )
.unwrap();
#[cfg(not(feature = "with-dns-sd"))] #[cfg(not(feature = "with-dns-sd"))]
let responder = libmdns::Responder::spawn(&handle)?; let responder = libmdns::Responder::spawn(&handle)?;

View file

@ -15,10 +15,10 @@ extern crate rand;
extern crate tokio_core; extern crate tokio_core;
extern crate url; extern crate url;
extern crate sha1;
extern crate hmac;
extern crate aes_ctr; extern crate aes_ctr;
extern crate block_modes; extern crate block_modes;
extern crate hmac;
extern crate sha1;
#[cfg(feature = "with-dns-sd")] #[cfg(feature = "with-dns-sd")]
extern crate dns_sd; extern crate dns_sd;

View file

@ -9,7 +9,11 @@ use rand;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use serde_json; use serde_json;
use context::StationContext; use crate::context::StationContext;
use crate::playback::mixer::Mixer;
use crate::playback::player::Player;
use crate::protocol;
use crate::protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
use librespot_core::config::ConnectConfig; use librespot_core::config::ConnectConfig;
use librespot_core::mercury::MercuryError; use librespot_core::mercury::MercuryError;
use librespot_core::session::Session; use librespot_core::session::Session;
@ -17,14 +21,10 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError};
use librespot_core::util::SeqGenerator; use librespot_core::util::SeqGenerator;
use librespot_core::version; use librespot_core::version;
use librespot_core::volume::Volume; use librespot_core::volume::Volume;
use playback::mixer::Mixer;
use playback::player::Player;
use protocol;
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
pub struct SpircTask { pub struct SpircTask {
player: Player, player: Player,
mixer: Box<Mixer>, mixer: Box<dyn Mixer>,
config: SpircTaskConfig, config: SpircTaskConfig,
sequence: SeqGenerator<u32>, sequence: SeqGenerator<u32>,
@ -33,15 +33,15 @@ pub struct SpircTask {
device: DeviceState, device: DeviceState,
state: State, state: State,
subscription: Box<Stream<Item = Frame, Error = MercuryError>>, subscription: Box<dyn Stream<Item = Frame, Error = MercuryError>>,
sender: Box<Sink<SinkItem = Frame, SinkError = MercuryError>>, sender: Box<dyn Sink<SinkItem = Frame, SinkError = MercuryError>>,
commands: mpsc::UnboundedReceiver<SpircCommand>, commands: mpsc::UnboundedReceiver<SpircCommand>,
end_of_track: Box<Future<Item = (), Error = oneshot::Canceled>>, end_of_track: Box<dyn Future<Item = (), Error = oneshot::Canceled>>,
shutdown: bool, shutdown: bool,
session: Session, session: Session,
context_fut: Box<Future<Item = serde_json::Value, Error = MercuryError>>, context_fut: Box<dyn Future<Item = serde_json::Value, Error = MercuryError>>,
autoplay_fut: Box<Future<Item = String, Error = MercuryError>>, autoplay_fut: Box<dyn Future<Item = String, Error = MercuryError>>,
context: Option<StationContext>, context: Option<StationContext>,
} }
@ -221,7 +221,7 @@ impl Spirc {
config: ConnectConfig, config: ConnectConfig,
session: Session, session: Session,
player: Player, player: Player,
mixer: Box<Mixer>, mixer: Box<dyn Mixer>,
) -> (Spirc, SpircTask) { ) -> (Spirc, SpircTask) {
debug!("new Spirc[{}]", session.session_id()); debug!("new Spirc[{}]", session.session_id());
@ -530,7 +530,8 @@ impl SpircTask {
if self.state.get_track().len() > 0 { if self.state.get_track().len() > 0 {
let now = self.now_ms(); let now = self.now_ms();
self.state.set_position_ms(frame.get_state().get_position_ms()); self.state
.set_position_ms(frame.get_state().get_position_ms());
self.state.set_position_measured_at(now as u64); self.state.set_position_measured_at(now as u64);
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay; let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
@ -693,7 +694,8 @@ impl SpircTask {
tracks_len - new_index < CONTEXT_FETCH_THRESHOLD tracks_len - new_index < CONTEXT_FETCH_THRESHOLD
); );
let context_uri = self.state.get_context_uri().to_owned(); let context_uri = self.state.get_context_uri().to_owned();
if (context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:")) if (context_uri.starts_with("spotify:station:")
|| context_uri.starts_with("spotify:dailymix:"))
&& ((self.state.get_track().len() as u32) - new_index) < CONTEXT_FETCH_THRESHOLD && ((self.state.get_track().len() as u32) - new_index) < CONTEXT_FETCH_THRESHOLD
{ {
self.context_fut = self.resolve_station(&context_uri); self.context_fut = self.resolve_station(&context_uri);
@ -789,18 +791,28 @@ impl SpircTask {
self.state.get_position_ms() + diff as u32 self.state.get_position_ms() + diff as u32
} }
fn resolve_station(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> { fn resolve_station(
&self,
uri: &str,
) -> Box<dyn Future<Item = serde_json::Value, Error = MercuryError>> {
let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri); let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri);
self.resolve_uri(&radio_uri) self.resolve_uri(&radio_uri)
} }
fn resolve_autoplay_uri(&self, uri: &str) -> Box<Future<Item = String, Error = MercuryError>> { fn resolve_autoplay_uri(
&self,
uri: &str,
) -> Box<dyn Future<Item = String, Error = MercuryError>> {
let query_uri = format!("hm://autoplay-enabled/query?uri={}", uri); let query_uri = format!("hm://autoplay-enabled/query?uri={}", uri);
let request = self.session.mercury().get(query_uri); let request = self.session.mercury().get(query_uri);
Box::new(request.and_then(move |response| { Box::new(request.and_then(move |response| {
if response.status_code == 200 { if response.status_code == 200 {
let data = response.payload.first().expect("Empty autoplay uri").to_vec(); let data = response
.payload
.first()
.expect("Empty autoplay uri")
.to_vec();
let autoplay_uri = String::from_utf8(data).unwrap(); let autoplay_uri = String::from_utf8(data).unwrap();
Ok(autoplay_uri) Ok(autoplay_uri)
} else { } else {
@ -810,11 +822,17 @@ impl SpircTask {
})) }))
} }
fn resolve_uri(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> { fn resolve_uri(
&self,
uri: &str,
) -> Box<dyn Future<Item = serde_json::Value, Error = MercuryError>> {
let request = self.session.mercury().get(uri); let request = self.session.mercury().get(uri);
Box::new(request.and_then(move |response| { Box::new(request.and_then(move |response| {
let data = response.payload.first().expect("Empty payload on context uri"); let data = response
.payload
.first()
.expect("Empty payload on context uri");
let response: serde_json::Value = serde_json::from_slice(&data).unwrap(); let response: serde_json::Value = serde_json::from_slice(&data).unwrap();
Ok(response) Ok(response)
@ -832,7 +850,8 @@ impl SpircTask {
track_vec.drain(0..head); track_vec.drain(0..head);
} }
track_vec.extend_from_slice(&new_tracks); track_vec.extend_from_slice(&new_tracks);
self.state.set_track(protobuf::RepeatedField::from_vec(track_vec)); self.state
.set_track(protobuf::RepeatedField::from_vec(track_vec));
// Update playing index // Update playing index
if let Some(new_index) = self if let Some(new_index) = self
@ -853,7 +872,9 @@ impl SpircTask {
let context_uri = frame.get_state().get_context_uri().to_owned(); let context_uri = frame.get_state().get_context_uri().to_owned();
let tracks = frame.get_state().get_track(); let tracks = frame.get_state().get_track();
debug!("Frame has {:?} tracks", tracks.len()); debug!("Frame has {:?} tracks", tracks.len());
if context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:") { if context_uri.starts_with("spotify:station:")
|| context_uri.starts_with("spotify:dailymix:")
{
self.context_fut = self.resolve_station(&context_uri); self.context_fut = self.resolve_station(&context_uri);
} else if self.config.autoplay { } else if self.config.autoplay {
info!("Fetching autoplay context uri"); info!("Fetching autoplay context uri");
@ -893,7 +914,8 @@ impl SpircTask {
let track = { let track = {
let mut track_ref = self.state.get_track()[index as usize].clone(); let mut track_ref = self.state.get_track()[index as usize].clone();
let mut track_id = self.get_spotify_id_for_track(&track_ref); let mut track_id = self.get_spotify_id_for_track(&track_ref);
while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable { while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable
{
warn!( warn!(
"Skipping track <{:?}> at position [{}] of {}", "Skipping track <{:?}> at position [{}] of {}",
track_ref.get_uri(), track_ref.get_uri(),

View file

@ -5,6 +5,7 @@ authors = ["Paul Lietar <paul@lietar.net>"]
build = "build.rs" build = "build.rs"
description="The core functionality provided by librespot" description="The core functionality provided by librespot"
license="MIT" license="MIT"
edition = "2018"
[dependencies.librespot-protocol] [dependencies.librespot-protocol]
path = "../protocol" path = "../protocol"

View file

@ -10,7 +10,7 @@ use std::str::FromStr;
use tokio_core::reactor::Handle; use tokio_core::reactor::Handle;
use url::Url; use url::Url;
error_chain!{} error_chain! {}
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct APResolveData { pub struct APResolveData {
@ -21,7 +21,7 @@ fn apresolve(
handle: &Handle, handle: &Handle,
proxy: &Option<Url>, proxy: &Option<Url>,
ap_port: &Option<u16>, ap_port: &Option<u16>,
) -> Box<Future<Item = String, Error = Error>> { ) -> Box<dyn Future<Item = String, Error = Error>> {
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL"); let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
let use_proxy = proxy.is_some(); let use_proxy = proxy.is_some();
@ -52,19 +52,20 @@ fn apresolve(
}) })
}); });
let body = body.then(|result| result.chain_err(|| "HTTP error")); let body = body.then(|result| result.chain_err(|| "HTTP error"));
let body = body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response")); let body =
body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response"));
let data = let data = body
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON")); .and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
let p = ap_port.clone(); let p = ap_port.clone();
let ap = data.and_then(move |data| { let ap = data.and_then(move |data| {
let mut aps = data.ap_list.iter().filter(|ap| { let mut aps = data.ap_list.iter().filter(|ap| {
if p.is_some() { if p.is_some() {
Uri::from_str(ap) Uri::from_str(ap).ok().map_or(false, |uri| {
.ok() uri.port().map_or(false, |port| port == p.unwrap())
.map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap())) })
} else if use_proxy { } else if use_proxy {
// It is unlikely that the proxy will accept CONNECT on anything other than 443. // It is unlikely that the proxy will accept CONNECT on anything other than 443.
Uri::from_str(ap) Uri::from_str(ap)
@ -86,7 +87,7 @@ pub(crate) fn apresolve_or_fallback<E>(
handle: &Handle, handle: &Handle,
proxy: &Option<Url>, proxy: &Option<Url>,
ap_port: &Option<u16>, ap_port: &Option<u16>,
) -> Box<Future<Item = String, Error = E>> ) -> Box<dyn Future<Item = String, Error = E>>
where where
E: 'static, E: 'static,
{ {

View file

@ -5,8 +5,8 @@ use futures::{Async, Future, Poll};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use spotify_id::{FileId, SpotifyId}; use crate::spotify_id::{FileId, SpotifyId};
use util::SeqGenerator; use crate::util::SeqGenerator;
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
pub struct AudioKey(pub [u8; 16]); pub struct AudioKey(pub [u8; 16]);
@ -35,7 +35,11 @@ impl AudioKeyManager {
let _ = sender.send(Ok(AudioKey(key))); let _ = sender.send(Ok(AudioKey(key)));
} }
0xe => { 0xe => {
warn!("error audio key {:x} {:x}", data.as_ref()[0], data.as_ref()[1]); warn!(
"error audio key {:x} {:x}",
data.as_ref()[0],
data.as_ref()[1]
);
let _ = sender.send(Err(AudioKeyError)); let _ = sender.send(Err(AudioKeyError));
} }
_ => (), _ => (),

View file

@ -1,18 +1,18 @@
use aes::Aes192;
use base64; use base64;
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use aes::Aes192;
use hmac::Hmac; use hmac::Hmac;
use sha1::{Sha1, Digest};
use pbkdf2::pbkdf2; use pbkdf2::pbkdf2;
use protobuf::ProtobufEnum; use protobuf::ProtobufEnum;
use serde; use serde;
use serde_json; use serde_json;
use sha1::{Digest, Sha1};
use std::fs::File; use std::fs::File;
use std::io::{self, Read, Write}; use std::io::{self, Read, Write};
use std::ops::FnOnce; use std::ops::FnOnce;
use std::path::Path; use std::path::Path;
use protocol::authentication::AuthenticationType; use crate::protocol::authentication::AuthenticationType;
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Credentials { pub struct Credentials {
@ -40,24 +40,24 @@ impl Credentials {
pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials { pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials {
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> { fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
let mut data = [0u8]; let mut data = [0u8];
try!(stream.read_exact(&mut data)); stream.read_exact(&mut data)?;
Ok(data[0]) Ok(data[0])
} }
fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> { fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
let lo = try!(read_u8(stream)) as u32; let lo = read_u8(stream)? as u32;
if lo & 0x80 == 0 { if lo & 0x80 == 0 {
return Ok(lo); return Ok(lo);
} }
let hi = try!(read_u8(stream)) as u32; let hi = read_u8(stream)? as u32;
Ok(lo & 0x7f | hi << 7) Ok(lo & 0x7f | hi << 7)
} }
fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> { fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
let length = try!(read_int(stream)); let length = read_int(stream)?;
let mut data = vec![0u8; length as usize]; let mut data = vec![0u8; length as usize];
try!(stream.read_exact(&mut data)); stream.read_exact(&mut data)?;
Ok(data) Ok(data)
} }
@ -76,9 +76,9 @@ impl Credentials {
// decrypt data using ECB mode without padding // decrypt data using ECB mode without padding
let blob = { let blob = {
use aes::block_cipher_trait::BlockCipher;
use aes::block_cipher_trait::generic_array::GenericArray;
use aes::block_cipher_trait::generic_array::typenum::Unsigned; use aes::block_cipher_trait::generic_array::typenum::Unsigned;
use aes::block_cipher_trait::generic_array::GenericArray;
use aes::block_cipher_trait::BlockCipher;
let mut data = base64::decode(encrypted_blob).unwrap(); let mut data = base64::decode(encrypted_blob).unwrap();
let cipher = Aes192::new(GenericArray::from_slice(&key)); let cipher = Aes192::new(GenericArray::from_slice(&key));
@ -148,7 +148,7 @@ where
T: ProtobufEnum, T: ProtobufEnum,
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let v: i32 = try!(serde::Deserialize::deserialize(de)); let v: i32 = serde::Deserialize::deserialize(de)?;
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value")) T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
} }
@ -164,7 +164,7 @@ fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
where where
D: serde::Deserializer<'de>, D: serde::Deserializer<'de>,
{ {
let v: String = try!(serde::Deserialize::deserialize(de)); let v: String = serde::Deserialize::deserialize(de)?;
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string())) base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
} }
@ -181,9 +181,10 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
Some(credentials.clone()) Some(credentials.clone())
} }
(Some(username), None, _) => { (Some(username), None, _) => Some(Credentials::with_password(
Some(Credentials::with_password(username.clone(), prompt(&username))) username.clone(),
} prompt(&username),
)),
(None, _, Some(credentials)) => Some(credentials), (None, _, Some(credentials)) => Some(credentials),

View file

@ -5,9 +5,9 @@ use std::io::Read;
use std::path::Path; use std::path::Path;
use std::path::PathBuf; use std::path::PathBuf;
use authentication::Credentials; use crate::authentication::Credentials;
use spotify_id::FileId; use crate::spotify_id::FileId;
use volume::Volume; use crate::volume::Volume;
#[derive(Clone)] #[derive(Clone)]
pub struct Cache { pub struct Cache {
@ -80,7 +80,7 @@ impl Cache {
File::open(self.file_path(file)).ok() File::open(self.file_path(file)).ok()
} }
pub fn save_file(&self, file: FileId, contents: &mut Read) { pub fn save_file(&self, file: FileId, contents: &mut dyn Read) {
if self.use_audio_cache { if self.use_audio_cache {
let path = self.file_path(file); let path = self.file_path(file);

View file

@ -5,7 +5,7 @@ use futures::{Async, Poll, Stream};
use std::collections::HashMap; use std::collections::HashMap;
use std::time::Instant; use std::time::Instant;
use util::SeqGenerator; use crate::util::SeqGenerator;
component! { component! {
ChannelManager : ChannelManagerInner { ChannelManager : ChannelManagerInner {

View file

@ -3,7 +3,7 @@ use std::str::FromStr;
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
use version; use crate::version;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SessionConfig { pub struct SessionConfig {

View file

@ -88,7 +88,8 @@ impl Decoder for APCodec {
let mut payload = buf.split_to(size + MAC_SIZE); let mut payload = buf.split_to(size + MAC_SIZE);
self.decode_cipher.decrypt(&mut payload.get_mut(..size).unwrap()); self.decode_cipher
.decrypt(&mut payload.get_mut(..size).unwrap());
let mac = payload.split_off(size); let mac = payload.split_off(size);
self.decode_cipher.check_mac(mac.as_ref())?; self.decode_cipher.check_mac(mac.as_ref())?;

View file

@ -1,9 +1,9 @@
use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use hmac::{Hmac, Mac};
use sha1::Sha1;
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use hmac::{Hmac, Mac};
use protobuf::{self, Message}; use protobuf::{self, Message};
use rand::thread_rng; use rand::thread_rng;
use sha1::Sha1;
use std::io::{self, Read}; use std::io::{self, Read};
use std::marker::PhantomData; use std::marker::PhantomData;
use tokio_codec::{Decoder, Framed}; use tokio_codec::{Decoder, Framed};
@ -11,10 +11,10 @@ use tokio_io::io::{read_exact, write_all, ReadExact, Window, WriteAll};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use super::codec::APCodec; use super::codec::APCodec;
use diffie_hellman::DHLocalKeys; use crate::diffie_hellman::DHLocalKeys;
use protocol; use crate::protocol;
use protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext}; use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
use util; use crate::util;
pub struct Handshake<T> { pub struct Handshake<T> {
keys: DHLocalKeys, keys: DHLocalKeys,
@ -62,7 +62,8 @@ impl<T: AsyncRead + AsyncWrite> Future for Handshake<T> {
.to_owned(); .to_owned();
let shared_secret = self.keys.shared_secret(&remote_key); let shared_secret = self.keys.shared_secret(&remote_key);
let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator); let (challenge, send_key, recv_key) =
compute_keys(&shared_secret, &accumulator);
let codec = APCodec::new(&send_key, &recv_key); let codec = APCodec::new(&send_key, &recv_key);
let write = client_response(connection, challenge); let write = client_response(connection, challenge);
@ -92,7 +93,10 @@ fn client_hello<T: AsyncWrite>(connection: T, gc: Vec<u8>) -> WriteAll<T, Vec<u8
packet packet
.mut_cryptosuites_supported() .mut_cryptosuites_supported()
.push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON); .push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON);
packet.mut_login_crypto_hello().mut_diffie_hellman().set_gc(gc); packet
.mut_login_crypto_hello()
.mut_diffie_hellman()
.set_gc(gc);
packet packet
.mut_login_crypto_hello() .mut_login_crypto_hello()
.mut_diffie_hellman() .mut_diffie_hellman()
@ -190,15 +194,13 @@ fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<
let mut data = Vec::with_capacity(0x64); let mut data = Vec::with_capacity(0x64);
for i in 1..6 { for i in 1..6 {
let mut mac = HmacSha1::new_varkey(&shared_secret) let mut mac = HmacSha1::new_varkey(&shared_secret).expect("HMAC can take key of any size");
.expect("HMAC can take key of any size");
mac.input(packets); mac.input(packets);
mac.input(&[i]); mac.input(&[i]);
data.extend_from_slice(&mac.result().code()); data.extend_from_slice(&mac.result().code());
} }
let mut mac = HmacSha1::new_varkey(&data[..0x14]) let mut mac = HmacSha1::new_varkey(&data[..0x14]).expect("HMAC can take key of any size");
.expect("HMAC can take key of any size");;
mac.input(packets); mac.input(packets);
( (

View file

@ -8,15 +8,15 @@ use futures::{Future, Sink, Stream};
use protobuf::{self, Message}; use protobuf::{self, Message};
use std::io; use std::io;
use std::net::ToSocketAddrs; use std::net::ToSocketAddrs;
use tokio_codec::Framed;
use tokio_core::net::TcpStream; use tokio_core::net::TcpStream;
use tokio_core::reactor::Handle; use tokio_core::reactor::Handle;
use tokio_codec::Framed;
use url::Url; use url::Url;
use authentication::Credentials; use crate::authentication::Credentials;
use version; use crate::version;
use proxytunnel; use crate::proxytunnel;
pub type Transport = Framed<TcpStream, APCodec>; pub type Transport = Framed<TcpStream, APCodec>;
@ -24,7 +24,7 @@ pub fn connect(
addr: String, addr: String,
handle: &Handle, handle: &Handle,
proxy: &Option<Url>, proxy: &Option<Url>,
) -> Box<Future<Item = Transport, Error = io::Error>> { ) -> Box<dyn Future<Item = Transport, Error = io::Error>> {
let (addr, connect_url) = match *proxy { let (addr, connect_url) = match *proxy {
Some(ref url) => { Some(ref url) => {
info!("Using proxy \"{}\"", url); info!("Using proxy \"{}\"", url);
@ -48,23 +48,31 @@ pub fn authenticate(
transport: Transport, transport: Transport,
credentials: Credentials, credentials: Credentials,
device_id: String, device_id: String,
) -> Box<Future<Item = (Transport, Credentials), Error = io::Error>> { ) -> Box<dyn Future<Item = (Transport, Credentials), Error = io::Error>> {
use protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os}; use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
use protocol::keyexchange::APLoginFailed; use crate::protocol::keyexchange::APLoginFailed;
let mut packet = ClientResponseEncrypted::new(); let mut packet = ClientResponseEncrypted::new();
packet.mut_login_credentials().set_username(credentials.username); packet
packet.mut_login_credentials().set_typ(credentials.auth_type); .mut_login_credentials()
.set_username(credentials.username);
packet
.mut_login_credentials()
.set_typ(credentials.auth_type);
packet packet
.mut_login_credentials() .mut_login_credentials()
.set_auth_data(credentials.auth_data); .set_auth_data(credentials.auth_data);
packet.mut_system_info().set_cpu_family(CpuFamily::CPU_UNKNOWN); packet
.mut_system_info()
.set_cpu_family(CpuFamily::CPU_UNKNOWN);
packet.mut_system_info().set_os(Os::OS_UNKNOWN); packet.mut_system_info().set_os(Os::OS_UNKNOWN);
packet.mut_system_info().set_system_information_string(format!( packet
"librespot_{}_{}", .mut_system_info()
version::short_sha(), .set_system_information_string(format!(
version::build_id() "librespot_{}_{}",
)); version::short_sha(),
version::build_id()
));
packet.mut_system_info().set_device_id(device_id); packet.mut_system_info().set_device_id(device_id);
packet.set_version_string(version::version_string()); packet.set_version_string(version::version_string());
@ -77,7 +85,8 @@ pub fn authenticate(
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err)) .and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
.and_then(|(packet, transport)| match packet { .and_then(|(packet, transport)| match packet {
Some((0xac, data)) => { Some((0xac, data)) => {
let welcome_data: APWelcome = protobuf::parse_from_bytes(data.as_ref()).unwrap(); let welcome_data: APWelcome =
protobuf::parse_from_bytes(data.as_ref()).unwrap();
let reusable_credentials = Credentials { let reusable_credentials = Credentials {
username: welcome_data.get_canonical_username().to_owned(), username: welcome_data.get_canonical_username().to_owned(),
@ -89,7 +98,8 @@ pub fn authenticate(
} }
Some((0xad, data)) => { Some((0xad, data)) => {
let error_data: APLoginFailed = protobuf::parse_from_bytes(data.as_ref()).unwrap(); let error_data: APLoginFailed =
protobuf::parse_from_bytes(data.as_ref()).unwrap();
panic!( panic!(
"Authentication failed with reason: {:?}", "Authentication failed with reason: {:?}",
error_data.get_error_code() error_data.get_error_code()

View file

@ -2,17 +2,18 @@ use num_bigint::BigUint;
use num_traits::FromPrimitive; use num_traits::FromPrimitive;
use rand::Rng; use rand::Rng;
use util; use crate::util;
lazy_static! { lazy_static! {
pub static ref DH_GENERATOR: BigUint = BigUint::from_u64(0x2).unwrap(); pub static ref DH_GENERATOR: BigUint = BigUint::from_u64(0x2).unwrap();
pub static ref DH_PRIME: BigUint = BigUint::from_bytes_be(&[ pub static ref DH_PRIME: BigUint = BigUint::from_bytes_be(&[
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2,
0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67,
0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37, 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5,
0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
]); ]);
} }
@ -39,7 +40,11 @@ impl DHLocalKeys {
} }
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> { pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
let shared_key = util::powm(&BigUint::from_bytes_be(remote_key), &self.private_key, &DH_PRIME); let shared_key = util::powm(
&BigUint::from_bytes_be(remote_key),
&self.private_key,
&DH_PRIME,
);
shared_key.to_bytes_be() shared_key.to_bytes_be()
} }
} }

View file

@ -1,8 +1,8 @@
use futures::Future; use futures::Future;
use serde_json; use serde_json;
use mercury::MercuryError; use crate::mercury::MercuryError;
use session::Session; use crate::session::Session;
#[derive(Deserialize, Debug, Clone)] #[derive(Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -17,7 +17,7 @@ pub fn get_token(
session: &Session, session: &Session,
client_id: &str, client_id: &str,
scopes: &str, scopes: &str,
) -> Box<Future<Item = Token, Error = MercuryError>> { ) -> Box<dyn Future<Item = Token, Error = MercuryError>> {
let url = format!( let url = format!(
"hm://keymaster/token/authenticated?client_id={}&scope={}", "hm://keymaster/token/authenticated?client_id={}&scope={}",
client_id, scopes client_id, scopes

View file

@ -11,29 +11,29 @@ extern crate log;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
extern crate aes;
extern crate base64; extern crate base64;
extern crate byteorder; extern crate byteorder;
extern crate bytes; extern crate bytes;
extern crate hmac;
extern crate httparse; extern crate httparse;
extern crate hyper; extern crate hyper;
extern crate hyper_proxy; extern crate hyper_proxy;
extern crate num_bigint; extern crate num_bigint;
extern crate num_integer; extern crate num_integer;
extern crate num_traits; extern crate num_traits;
extern crate pbkdf2;
extern crate protobuf; extern crate protobuf;
extern crate rand; extern crate rand;
extern crate serde; extern crate serde;
extern crate serde_json; extern crate serde_json;
extern crate sha1;
extern crate shannon; extern crate shannon;
extern crate tokio_codec; extern crate tokio_codec;
extern crate tokio_core; extern crate tokio_core;
extern crate tokio_io; extern crate tokio_io;
extern crate url; extern crate url;
extern crate uuid; extern crate uuid;
extern crate sha1;
extern crate hmac;
extern crate pbkdf2;
extern crate aes;
extern crate librespot_protocol as protocol; extern crate librespot_protocol as protocol;

View file

@ -1,13 +1,13 @@
use crate::protocol;
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use bytes::Bytes; use bytes::Bytes;
use futures::sync::{mpsc, oneshot}; use futures::sync::{mpsc, oneshot};
use futures::{Async, Future, Poll}; use futures::{Async, Future, Poll};
use protobuf; use protobuf;
use protocol;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem; use std::mem;
use util::SeqGenerator; use crate::util::SeqGenerator;
mod types; mod types;
pub use self::types::*; pub use self::types::*;
@ -100,7 +100,8 @@ impl MercuryManager {
pub fn subscribe<T: Into<String>>( pub fn subscribe<T: Into<String>>(
&self, &self,
uri: T, uri: T,
) -> Box<Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>> { ) -> Box<dyn Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>>
{
let uri = uri.into(); let uri = uri.into();
let request = self.request(MercuryRequest { let request = self.request(MercuryRequest {
method: MercuryMethod::SUB, method: MercuryMethod::SUB,

View file

@ -2,7 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt};
use protobuf::Message; use protobuf::Message;
use std::io::Write; use std::io::Write;
use protocol; use crate::protocol;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum MercuryMethod { pub enum MercuryMethod {
@ -37,7 +37,8 @@ impl ToString for MercuryMethod {
MercuryMethod::SUB => "SUB", MercuryMethod::SUB => "SUB",
MercuryMethod::UNSUB => "UNSUB", MercuryMethod::UNSUB => "UNSUB",
MercuryMethod::SEND => "SEND", MercuryMethod::SEND => "SEND",
}.to_owned() }
.to_owned()
} }
} }

View file

@ -57,7 +57,9 @@ impl<T: AsyncRead + AsyncWrite> Future for ProxyTunnel<T> {
let mut response = httparse::Response::new(&mut headers); let mut response = httparse::Response::new(&mut headers);
let status = match response.parse(&buf) { let status = match response.parse(&buf) {
Ok(status) => status, Ok(status) => status,
Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err.description())), Err(err) => {
return Err(io::Error::new(io::ErrorKind::Other, err.description()));
}
}; };
if status.is_complete() { if status.is_complete() {
@ -102,7 +104,8 @@ fn proxy_connect<T: AsyncWrite>(connection: T, connect_url: &str) -> WriteAll<T,
\r\n", \r\n",
uri.host().expect(&format!("No host in {}", uri)), uri.host().expect(&format!("No host in {}", uri)),
uri.port().expect(&format!("No port in {}", uri)) uri.port().expect(&format!("No port in {}", uri))
).into_bytes(); )
.into_bytes();
write_all(connection, buffer) write_all(connection, buffer)
} }

View file

@ -1,23 +1,23 @@
use std::io; use std::io;
use std::sync::{Arc, RwLock, Weak};
use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::{Arc, RwLock, Weak};
use std::time::{SystemTime, UNIX_EPOCH}; use std::time::{SystemTime, UNIX_EPOCH};
use byteorder::{BigEndian, ByteOrder}; use byteorder::{BigEndian, ByteOrder};
use bytes::Bytes; use bytes::Bytes;
use futures::{Async, Future, IntoFuture, Poll, Stream};
use futures::sync::mpsc; use futures::sync::mpsc;
use futures::{Async, Future, IntoFuture, Poll, Stream};
use tokio_core::reactor::{Handle, Remote}; use tokio_core::reactor::{Handle, Remote};
use apresolve::apresolve_or_fallback; use crate::apresolve::apresolve_or_fallback;
use audio_key::AudioKeyManager; use crate::audio_key::AudioKeyManager;
use authentication::Credentials; use crate::authentication::Credentials;
use cache::Cache; use crate::cache::Cache;
use channel::ChannelManager; use crate::channel::ChannelManager;
use component::Lazy; use crate::component::Lazy;
use config::SessionConfig; use crate::config::SessionConfig;
use connection; use crate::connection;
use mercury::MercuryManager; use crate::mercury::MercuryManager;
struct SessionData { struct SessionData {
country: String, country: String,
@ -53,8 +53,9 @@ impl Session {
credentials: Credentials, credentials: Credentials,
cache: Option<Cache>, cache: Option<Cache>,
handle: Handle, handle: Handle,
) -> Box<Future<Item = Session, Error = io::Error>> { ) -> Box<dyn Future<Item = Session, Error = io::Error>> {
let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port); let access_point =
apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
let handle_ = handle.clone(); let handle_ = handle.clone();
let proxy = config.proxy.clone(); let proxy = config.proxy.clone();
@ -64,8 +65,9 @@ impl Session {
}); });
let device_id = config.device_id.clone(); let device_id = config.device_id.clone();
let authentication = connection let authentication = connection.and_then(move |connection| {
.and_then(move |connection| connection::authenticate(connection, credentials, device_id)); connection::authenticate(connection, credentials, device_id)
});
let result = authentication.map(move |(transport, reusable_credentials)| { let result = authentication.map(move |(transport, reusable_credentials)| {
info!("Authenticated as \"{}\" !", reusable_credentials.username); info!("Authenticated as \"{}\" !", reusable_credentials.username);
@ -97,7 +99,7 @@ impl Session {
config: SessionConfig, config: SessionConfig,
cache: Option<Cache>, cache: Option<Cache>,
username: String, username: String,
) -> (Session, Box<Future<Item = (), Error = io::Error>>) { ) -> (Session, Box<dyn Future<Item = (), Error = io::Error>>) {
let (sink, stream) = transport.split(); let (sink, stream) = transport.split();
let (sender_tx, sender_rx) = mpsc::unbounded(); let (sender_tx, sender_rx) = mpsc::unbounded();
@ -133,7 +135,11 @@ impl Session {
.map(|_| ()); .map(|_| ());
let receiver_task = DispatchTask(stream, session.weak()); let receiver_task = DispatchTask(stream, session.weak());
let task = Box::new((receiver_task, sender_task).into_future().map(|((), ())| ())); let task = Box::new(
(receiver_task, sender_task)
.into_future()
.map(|((), ())| ()),
);
(session, task) (session, task)
} }
@ -197,7 +203,7 @@ impl Session {
0x9 | 0xa => self.channel().dispatch(cmd, data), 0x9 | 0xa => self.channel().dispatch(cmd, data),
0xd | 0xe => self.audio_key().dispatch(cmd, data), 0xd | 0xe => self.audio_key().dispatch(cmd, data),
0xb2...0xb6 => self.mercury().dispatch(cmd, data), 0xb2..=0xb6 => self.mercury().dispatch(cmd, data),
_ => (), _ => (),
} }
} }

View file

@ -17,7 +17,8 @@ pub struct SpotifyId {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct SpotifyIdError; pub struct SpotifyIdError;
const BASE62_DIGITS: &'static [u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const BASE62_DIGITS: &'static [u8] =
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef"; const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
impl SpotifyId { impl SpotifyId {

View file

@ -6,7 +6,10 @@ use std::mem;
use std::ops::{Mul, Rem, Shr}; use std::ops::{Mul, Rem, Shr};
pub fn rand_vec<G: Rng>(rng: &mut G, size: usize) -> Vec<u8> { pub fn rand_vec<G: Rng>(rng: &mut G, size: usize) -> Vec<u8> {
::std::iter::repeat(()).map(|()| rng.gen()).take(size).collect() ::std::iter::repeat(())
.map(|()| rng.gen())
.take(size)
.collect()
} }
pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {

View file

@ -21,7 +21,9 @@ impl Volume {
// write volume to file // write volume to file
fn save_to_writer<W: Write>(&self, writer: &mut W) { fn save_to_writer<W: Write>(&self, writer: &mut W) {
writer.write_all(self.volume.to_string().as_bytes()).unwrap(); writer
.write_all(self.volume.to_string().as_bytes())
.unwrap();
} }
pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) { pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {

View file

@ -1,6 +1,3 @@
extern crate librespot;
extern crate tokio_core;
use std::env; use std::env;
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
@ -37,7 +34,9 @@ fn main() {
.run(Session::connect(session_config, credentials, None, handle)) .run(Session::connect(session_config, credentials, None, handle))
.unwrap(); .unwrap();
let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None)); let (player, _) = Player::new(player_config, session.clone(), None, move || {
(backend)(None)
});
println!("Playing..."); println!("Playing...");
core.run(player.load(track, true, 0)).unwrap(); core.run(player.load(track, true, 0)).unwrap();

View file

@ -1,11 +1,4 @@
extern crate log; use env_logger;
extern crate env_logger;
extern crate librespot;
extern crate tokio_core;
extern crate tokio_io;
extern crate futures;
use std::env; use std::env;
use tokio_core::reactor::Core; use tokio_core::reactor::Core;
@ -13,7 +6,7 @@ use librespot::core::authentication::Credentials;
use librespot::core::config::SessionConfig; use librespot::core::config::SessionConfig;
use librespot::core::session::Session; use librespot::core::session::Session;
use librespot::core::spotify_id::SpotifyId; use librespot::core::spotify_id::SpotifyId;
use librespot::metadata::{Metadata, Track, Playlist}; use librespot::metadata::{Metadata, Playlist, Track};
fn main() { fn main() {
env_logger::init(); env_logger::init();
@ -32,16 +25,16 @@ fn main() {
let uri_split = args[3].split(":"); let uri_split = args[3].split(":");
let uri_parts: Vec<&str> = uri_split.collect(); let uri_parts: Vec<&str> = uri_split.collect();
println!("{}, {}, {}",uri_parts[0], uri_parts[1], uri_parts[2]); println!("{}, {}, {}", uri_parts[0], uri_parts[1], uri_parts[2]);
let plist_uri = SpotifyId::from_base62(uri_parts[2]).unwrap(); let plist_uri = SpotifyId::from_base62(uri_parts[2]).unwrap();
let session = core let session = core
.run(Session::connect(session_config, credentials, None, handle)) .run(Session::connect(session_config, credentials, None, handle))
.unwrap(); .unwrap();
let plist = core.run(Playlist::get(&session, plist_uri)).unwrap(); let plist = core.run(Playlist::get(&session, plist_uri)).unwrap();
println!("{:?}",plist); println!("{:?}", plist);
for track_id in plist.tracks { for track_id in plist.tracks {
let plist_track = core.run(Track::get(&session, track_id)).unwrap(); let plist_track = core.run(Track::get(&session, track_id)).unwrap();
println!("track: {} ", plist_track.name); println!("track: {} ", plist_track.name);

View file

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Paul Lietar <paul@lietar.net>"] authors = ["Paul Lietar <paul@lietar.net>"]
description="The metadata logic for librespot" description="The metadata logic for librespot"
license="MIT" license="MIT"
edition = "2018"
[dependencies] [dependencies]
byteorder = "1.3" byteorder = "1.3"

View file

@ -19,7 +19,7 @@ use librespot_core::mercury::MercuryError;
use librespot_core::session::Session; use librespot_core::session::Session;
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId}; use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
pub use protocol::metadata::AudioFile_Format as FileFormat; pub use crate::protocol::metadata::AudioFile_Format as FileFormat;
fn countrylist_contains(list: &str, country: &str) -> bool { fn countrylist_contains(list: &str, country: &str) -> bool {
list.chunks(2).any(|cc| cc == country) list.chunks(2).any(|cc| cc == country)
@ -301,7 +301,6 @@ impl Metadata for Playlist {
} }
fn parse(msg: &Self::Message, _: &Session) -> Self { fn parse(msg: &Self::Message, _: &Session) -> Self {
let tracks = msg let tracks = msg
.get_contents() .get_contents()
.get_items() .get_items()
@ -312,9 +311,13 @@ impl Metadata for Playlist {
SpotifyId::from_base62(uri_parts[2]).unwrap() SpotifyId::from_base62(uri_parts[2]).unwrap()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if tracks.len() != msg.get_length() as usize { if tracks.len() != msg.get_length() as usize {
warn!("Got {} tracks, but the playlist should contain {} tracks.", tracks.len(), msg.get_length()); warn!(
"Got {} tracks, but the playlist should contain {} tracks.",
tracks.len(),
msg.get_length()
);
} }
Playlist { Playlist {

View file

@ -4,6 +4,7 @@ version = "0.1.0"
authors = ["Sasha Hilton <sashahilton00@gmail.com>"] authors = ["Sasha Hilton <sashahilton00@gmail.com>"]
description="The audio playback logic for librespot" description="The audio playback logic for librespot"
license="MIT" license="MIT"
edition = "2018"
[dependencies.librespot-audio] [dependencies.librespot-audio]
path = "../audio" path = "../audio"

View file

@ -64,7 +64,8 @@ impl Open for AlsaSink {
} }
Some(device) => device, Some(device) => device,
None => "default", None => "default",
}.to_string(); }
.to_string();
AlsaSink(None, name) AlsaSink(None, name)
} }

View file

@ -1,7 +1,7 @@
use super::{Open, Sink}; use super::{Open, Sink};
use jack::prelude::{ use jack::prelude::{
client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, ProcessHandler, client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port,
ProcessScope, ProcessHandler, ProcessScope,
}; };
use std::io; use std::io;
use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
@ -45,9 +45,14 @@ impl Open for JackSink {
info!("Using jack sink!"); info!("Using jack sink!");
let client_name = client_name.unwrap_or("librespot".to_string()); let client_name = client_name.unwrap_or("librespot".to_string());
let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap(); let (client, _status) =
let ch_r = client.register_port("out_0", AudioOutSpec::default()).unwrap(); Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap(); let ch_r = client
.register_port("out_0", AudioOutSpec::default())
.unwrap();
let ch_l = client
.register_port("out_1", AudioOutSpec::default())
.unwrap();
// buffer for samples from librespot (~10ms) // buffer for samples from librespot (~10ms)
let (tx, rx) = sync_channel(2 * 1024 * 4); let (tx, rx) = sync_channel(2 * 1024 * 4);
let jack_data = JackData { let jack_data = JackData {

View file

@ -1,7 +1,7 @@
use std::io; use std::io;
pub trait Open { pub trait Open {
fn open(Option<String>) -> Self; fn open(_: Option<String>) -> Self;
} }
pub trait Sink { pub trait Sink {
@ -10,7 +10,7 @@ pub trait Sink {
fn write(&mut self, data: &[i16]) -> io::Result<()>; fn write(&mut self, data: &[i16]) -> io::Result<()>;
} }
fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<Sink> { fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<dyn Sink> {
Box::new(S::open(device)) Box::new(S::open(device))
} }
@ -46,7 +46,7 @@ use self::sdl::SdlSink;
mod pipe; mod pipe;
use self::pipe::StdoutSink; use self::pipe::StdoutSink;
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] = &[ pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<dyn Sink>)] = &[
#[cfg(feature = "alsa-backend")] #[cfg(feature = "alsa-backend")]
("alsa", mk_sink::<AlsaSink>), ("alsa", mk_sink::<AlsaSink>),
#[cfg(feature = "portaudio-backend")] #[cfg(feature = "portaudio-backend")]
@ -62,7 +62,7 @@ pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] =
("pipe", mk_sink::<StdoutSink>), ("pipe", mk_sink::<StdoutSink>),
]; ];
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<Sink>> { pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<dyn Sink>> {
if let Some(name) = name { if let Some(name) = name {
BACKENDS BACKENDS
.iter() .iter()

View file

@ -4,7 +4,7 @@ use std::io::{self, Write};
use std::mem; use std::mem;
use std::slice; use std::slice;
pub struct StdoutSink(Box<Write>); pub struct StdoutSink(Box<dyn Write>);
impl Open for StdoutSink { impl Open for StdoutSink {
fn open(path: Option<String>) -> StdoutSink { fn open(path: Option<String>) -> StdoutSink {
@ -28,7 +28,10 @@ impl Sink for StdoutSink {
fn write(&mut self, data: &[i16]) -> io::Result<()> { fn write(&mut self, data: &[i16]) -> io::Result<()> {
let data: &[u8] = unsafe { let data: &[u8] = unsafe {
slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::<i16>()) slice::from_raw_parts(
data.as_ptr() as *const u8,
data.len() * mem::size_of::<i16>(),
)
}; };
self.0.write_all(data)?; self.0.write_all(data)?;

View file

@ -11,7 +11,7 @@ pub struct PortAudioSink<'a>(
StreamParameters<i16>, StreamParameters<i16>,
); );
fn output_devices() -> Box<Iterator<Item = (DeviceIndex, DeviceInfo)>> { fn output_devices() -> Box<dyn Iterator<Item = (DeviceIndex, DeviceInfo)>> {
let count = portaudio_rs::device::get_count().unwrap(); let count = portaudio_rs::device::get_count().unwrap();
let devices = (0..count) let devices = (0..count)
.filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info))) .filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info)))
@ -51,7 +51,8 @@ impl<'a> Open for PortAudioSink<'a> {
} }
Some(device) => find_output(device), Some(device) => find_output(device),
None => get_default_output_index(), None => get_default_output_index(),
}.expect("Could not find device"); }
.expect("Could not find device");
let info = portaudio_rs::device::get_info(device_idx); let info = portaudio_rs::device::get_info(device_idx);
let latency = match info { let latency = match info {
@ -81,8 +82,9 @@ impl<'a> Sink for PortAudioSink<'a> {
FRAMES_PER_BUFFER_UNSPECIFIED, FRAMES_PER_BUFFER_UNSPECIFIED,
StreamFlags::empty(), StreamFlags::empty(),
None, None,
).unwrap(), )
);; .unwrap(),
);
} }
self.0.as_mut().unwrap().start().unwrap(); self.0.as_mut().unwrap().start().unwrap();

View file

@ -14,7 +14,11 @@ pub struct PulseAudioSink {
desc: CString, desc: CString,
} }
fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result<T> fn call_pulseaudio<T, F, FailCheck>(
f: F,
fail_check: FailCheck,
kind: io::ErrorKind,
) -> io::Result<T>
where where
T: Copy, T: Copy,
F: Fn(*mut libc::c_int) -> T, F: Fn(*mut libc::c_int) -> T,

View file

@ -1,8 +1,8 @@
use super::{Open, Sink}; use super::{Open, Sink};
extern crate rodio;
extern crate cpal; extern crate cpal;
use std::{io, thread, time}; extern crate rodio;
use std::process::exit; use std::process::exit;
use std::{io, thread, time};
pub struct RodioSink { pub struct RodioSink {
rodio_sink: rodio::Sink, rodio_sink: rodio::Sink,
@ -14,7 +14,7 @@ fn list_formats(ref device: &rodio::Device) {
Err(e) => { Err(e) => {
warn!("Error getting default rodio::Sink format: {:?}", e); warn!("Error getting default rodio::Sink format: {:?}", e);
return; return;
}, }
}; };
let mut output_formats = match device.supported_output_formats() { let mut output_formats = match device.supported_output_formats() {
@ -22,13 +22,16 @@ fn list_formats(ref device: &rodio::Device) {
Err(e) => { Err(e) => {
warn!("Error getting supported rodio::Sink formats: {:?}", e); warn!("Error getting supported rodio::Sink formats: {:?}", e);
return; return;
}, }
}; };
if output_formats.peek().is_some() { if output_formats.peek().is_some() {
debug!(" Available formats:"); debug!(" Available formats:");
for format in output_formats { for format in output_formats {
let s = format!("{}ch, {:?}, min {:?}, max {:?}", format.channels, format.data_type, format.min_sample_rate, format.max_sample_rate); let s = format!(
"{}ch, {:?}, min {:?}, max {:?}",
format.channels, format.data_type, format.min_sample_rate, format.max_sample_rate
);
if format == default_fmt { if format == default_fmt {
debug!(" (default) {}", s); debug!(" (default) {}", s);
} else { } else {
@ -79,9 +82,7 @@ impl Open for RodioSink {
} }
let sink = rodio::Sink::new(&rodio_device); let sink = rodio::Sink::new(&rodio_device);
RodioSink { RodioSink { rodio_sink: sink }
rodio_sink: sink,
}
} }
} }

View file

@ -10,13 +10,17 @@ pub struct AlsaMixer {
} }
impl AlsaMixer { impl AlsaMixer {
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> { fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<dyn Error>> {
let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);
let selem = mixer let selem = mixer.find_selem(&sid).expect(
.find_selem(&sid) format!(
.expect(format!("Couldn't find simple mixer control for {}", self.config.mixer).as_str()); "Couldn't find simple mixer control for {}",
self.config.mixer
)
.as_str(),
);
let (min, max) = selem.get_playback_volume_range(); let (min, max) = selem.get_playback_volume_range();
let range = (max - min) as f64; let range = (max - min) as f64;
@ -72,7 +76,7 @@ impl Mixer for AlsaMixer {
} }
} }
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> { fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
None None
} }
} }

View file

@ -1,12 +1,12 @@
pub trait Mixer: Send { pub trait Mixer: Send {
fn open(Option<MixerConfig>) -> Self fn open(_: Option<MixerConfig>) -> Self
where where
Self: Sized; Self: Sized;
fn start(&self); fn start(&self);
fn stop(&self); fn stop(&self);
fn set_volume(&self, volume: u16); fn set_volume(&self, volume: u16);
fn volume(&self) -> u16; fn volume(&self) -> u16;
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> { fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
None None
} }
} }
@ -28,10 +28,11 @@ pub struct MixerConfig {
} }
impl Default for MixerConfig { impl Default for MixerConfig {
fn default() -> MixerConfig { MixerConfig { fn default() -> MixerConfig {
card: String::from("default"), MixerConfig {
mixer: String::from("PCM"), card: String::from("default"),
index: 0, mixer: String::from("PCM"),
index: 0,
} }
} }
} }
@ -39,11 +40,11 @@ impl Default for MixerConfig {
pub mod softmixer; pub mod softmixer;
use self::softmixer::SoftMixer; use self::softmixer::SoftMixer;
fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<Mixer> { fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<dyn Mixer> {
Box::new(M::open(device)) Box::new(M::open(device))
} }
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<Mixer>> { pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<dyn Mixer>> {
match name.as_ref().map(AsRef::as_ref) { match name.as_ref().map(AsRef::as_ref) {
None | Some("softvol") => Some(mk_sink::<SoftMixer>), None | Some("softvol") => Some(mk_sink::<SoftMixer>),
#[cfg(feature = "alsa-backend")] #[cfg(feature = "alsa-backend")]

View file

@ -23,7 +23,7 @@ impl Mixer for SoftMixer {
fn set_volume(&self, volume: u16) { fn set_volume(&self, volume: u16) {
self.volume.store(volume as usize, Ordering::Relaxed); self.volume.store(volume as usize, Ordering::Relaxed);
} }
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> { fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
Some(Box::new(SoftVolumeApplier { Some(Box::new(SoftVolumeApplier {
volume: self.volume.clone(), volume: self.volume.clone(),
})) }))

View file

@ -11,19 +11,19 @@ use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use config::{Bitrate, PlayerConfig}; use crate::config::{Bitrate, PlayerConfig};
use librespot_core::session::Session; use librespot_core::session::Session;
use librespot_core::spotify_id::SpotifyId; use librespot_core::spotify_id::SpotifyId;
use audio::{AudioDecrypt, AudioFile, StreamLoaderController}; use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
use audio::{VorbisDecoder, VorbisPacket}; use crate::audio::{VorbisDecoder, VorbisPacket};
use audio::{ use crate::audio::{
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS, READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS, READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
}; };
use audio_backend::Sink; use crate::audio_backend::Sink;
use metadata::{AudioItem, FileFormat}; use crate::metadata::{AudioItem, FileFormat};
use mixer::AudioFilter; use crate::mixer::AudioFilter;
pub struct Player { pub struct Player {
commands: Option<std::sync::mpsc::Sender<PlayerCommand>>, commands: Option<std::sync::mpsc::Sender<PlayerCommand>>,
@ -36,9 +36,9 @@ struct PlayerInternal {
commands: std::sync::mpsc::Receiver<PlayerCommand>, commands: std::sync::mpsc::Receiver<PlayerCommand>,
state: PlayerState, state: PlayerState,
sink: Box<Sink>, sink: Box<dyn Sink>,
sink_running: bool, sink_running: bool,
audio_filter: Option<Box<AudioFilter + Send>>, audio_filter: Option<Box<dyn AudioFilter + Send>>,
event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>, event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>,
} }
@ -98,8 +98,10 @@ impl NormalisationData {
} }
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 { fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
let mut normalisation_factor = let mut normalisation_factor = f32::powf(
f32::powf(10.0, (data.track_gain_db + config.normalisation_pregain) / 20.0); 10.0,
(data.track_gain_db + config.normalisation_pregain) / 20.0,
);
if normalisation_factor * data.track_peak > 1.0 { if normalisation_factor * data.track_peak > 1.0 {
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid."); warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
@ -117,11 +119,11 @@ impl Player {
pub fn new<F>( pub fn new<F>(
config: PlayerConfig, config: PlayerConfig,
session: Session, session: Session,
audio_filter: Option<Box<AudioFilter + Send>>, audio_filter: Option<Box<dyn AudioFilter + Send>>,
sink_builder: F, sink_builder: F,
) -> (Player, PlayerEventChannel) ) -> (Player, PlayerEventChannel)
where where
F: FnOnce() -> Box<Sink> + Send + 'static, F: FnOnce() -> Box<dyn Sink> + Send + 'static,
{ {
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel(); let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded(); let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
@ -238,7 +240,12 @@ impl PlayerState {
use self::PlayerState::*; use self::PlayerState::*;
match *self { match *self {
Stopped | EndOfTrack { .. } => None, Stopped | EndOfTrack { .. } => None,
Paused { ref mut decoder, .. } | Playing { ref mut decoder, .. } => Some(decoder), Paused {
ref mut decoder, ..
}
| Playing {
ref mut decoder, ..
} => Some(decoder),
Invalid => panic!("invalid state"), Invalid => panic!("invalid state"),
} }
} }
@ -518,7 +525,10 @@ impl PlayerInternal {
if let Some(stream_loader_controller) = self.state.stream_loader_controller() { if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
stream_loader_controller.set_stream_mode(); stream_loader_controller.set_stream_mode();
} }
if let PlayerState::Playing { bytes_per_second, .. } = self.state { if let PlayerState::Playing {
bytes_per_second, ..
} = self.state
{
if let Some(stream_loader_controller) = self.state.stream_loader_controller() { if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
// Request our read ahead range // Request our read ahead range
let request_data_length = max( let request_data_length = max(
@ -592,7 +602,10 @@ impl PlayerInternal {
.iter() .iter()
.map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id)); .map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id));
let alternatives = future::join_all(alternatives).wait().unwrap(); let alternatives = future::join_all(alternatives).wait().unwrap();
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned) alternatives
.into_iter()
.find(|alt| alt.available)
.map(Cow::Owned)
} else { } else {
None None
} }
@ -676,8 +689,12 @@ impl PlayerInternal {
let play_from_beginning = position == 0; let play_from_beginning = position == 0;
let key = self.session.audio_key().request(spotify_id, file_id); let key = self.session.audio_key().request(spotify_id, file_id);
let encrypted_file = let encrypted_file = AudioFile::open(
AudioFile::open(&self.session, file_id, bytes_per_second, play_from_beginning); &self.session,
file_id,
bytes_per_second,
play_from_beginning,
);
let encrypted_file = match encrypted_file.wait() { let encrypted_file = match encrypted_file.wait() {
Ok(encrypted_file) => encrypted_file, Ok(encrypted_file) => encrypted_file,
@ -708,7 +725,9 @@ impl PlayerInternal {
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file); let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) { let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) {
Ok(normalisation_data) => NormalisationData::get_factor(&self.config, normalisation_data), Ok(normalisation_data) => {
NormalisationData::get_factor(&self.config, normalisation_data)
}
Err(_) => { Err(_) => {
warn!("Unable to extract normalisation data, using default value."); warn!("Unable to extract normalisation data, using default value.");
1.0 as f32 1.0 as f32
@ -787,7 +806,7 @@ impl<T: Read + Seek> Seek for Subfile<T> {
x => x, x => x,
}; };
let newpos = try!(self.stream.seek(pos)); let newpos = self.stream.seek(pos)?;
if newpos > self.offset { if newpos > self.offset {
Ok(newpos - self.offset) Ok(newpos - self.offset)
} else { } else {

View file

@ -5,6 +5,7 @@ authors = ["Paul Liétar <paul@lietar.net>"]
build = "build.rs" build = "build.rs"
description="The protobuf logic for communicating with Spotify servers" description="The protobuf logic for communicating with Spotify servers"
license="MIT" license="MIT"
edition = "2018"
[dependencies] [dependencies]
protobuf = "2.8.1" protobuf = "2.8.1"

View file

@ -1,14 +1,16 @@
extern crate protobuf_codegen; // Does the business extern crate protobuf_codegen; // Does the business
extern crate protobuf_codegen_pure; // Helper function extern crate protobuf_codegen_pure; // Helper function
use std::path::Path;
use std::fs::{read_to_string, write}; use std::fs::{read_to_string, write};
use std::path::Path;
use protobuf_codegen_pure::Customize;
use protobuf_codegen_pure::parse_and_typecheck; use protobuf_codegen_pure::parse_and_typecheck;
use protobuf_codegen_pure::Customize;
fn main() { fn main() {
let customizations = Customize { ..Default::default() }; let customizations = Customize {
..Default::default()
};
let lib_str = read_to_string("src/lib.rs").unwrap(); let lib_str = read_to_string("src/lib.rs").unwrap();
@ -21,10 +23,9 @@ fn main() {
let name; let name;
if line.starts_with("pub mod ") { if line.starts_with("pub mod ") {
name = &line[8..len-1]; // Remove keywords and semi-colon name = &line[8..len - 1]; // Remove keywords and semi-colon
} } else {
else { name = &line[4..len - 1]; // Remove keywords and semi-colon
name = &line[4..len-1]; // Remove keywords and semi-colon
} }
// Build the paths to relevant files. // Build the paths to relevant files.
@ -44,11 +45,7 @@ fn main() {
let p = parse_and_typecheck(&["proto"], &[src]).expect("protoc"); let p = parse_and_typecheck(&["proto"], &[src]).expect("protoc");
// But generate them with the protobuf-codegen crate directly. // But generate them with the protobuf-codegen crate directly.
// Then we can keep the result in-memory. // Then we can keep the result in-memory.
let result = protobuf_codegen::gen( let result = protobuf_codegen::gen(&p.file_descriptors, &p.relative_paths, &customizations);
&p.file_descriptors,
&p.relative_paths,
&customizations,
);
// Protoc result as a byte array. // Protoc result as a byte array.
let new = &result.first().unwrap().content; let new = &result.first().unwrap().content;
// Convert to utf8 to compare with existing. // Convert to utf8 to compare with existing.

View file

@ -1,4 +1,3 @@
max_width = 105 # max_width = 105
reorder_imports = true reorder_imports = true
reorder_imports_in_group = true
reorder_modules = true reorder_modules = true

View file

@ -1,15 +1,6 @@
#![crate_name = "librespot"] #![crate_name = "librespot"]
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))] #![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
extern crate base64;
extern crate futures;
extern crate hyper;
extern crate num_bigint;
extern crate protobuf;
extern crate rand;
extern crate tokio_core;
extern crate url;
pub extern crate librespot_audio as audio; pub extern crate librespot_audio as audio;
pub extern crate librespot_connect as connect; pub extern crate librespot_connect as connect;
pub extern crate librespot_core as core; pub extern crate librespot_core as core;

View file

@ -1,20 +1,6 @@
extern crate env_logger;
extern crate futures;
extern crate getopts;
extern crate librespot;
#[macro_use]
extern crate log;
extern crate hex;
extern crate rpassword;
extern crate sha1;
extern crate tokio_core;
extern crate tokio_io;
extern crate tokio_process;
extern crate tokio_signal;
extern crate url;
use futures::sync::mpsc::UnboundedReceiver; use futures::sync::mpsc::UnboundedReceiver;
use futures::{Async, Future, Poll, Stream}; use futures::{Async, Future, Poll, Stream};
use log::{error, info, trace, warn};
use sha1::{Digest, Sha1}; use sha1::{Digest, Sha1};
use std::env; use std::env;
use std::io::{self, stderr, Write}; use std::io::{self, stderr, Write};
@ -41,7 +27,7 @@ use librespot::playback::mixer::{self, Mixer, MixerConfig};
use librespot::playback::player::{Player, PlayerEvent}; use librespot::playback::player::{Player, PlayerEvent};
mod player_event_handler; mod player_event_handler;
use player_event_handler::run_program_on_events; use crate::player_event_handler::run_program_on_events;
fn device_id(name: &str) -> String { fn device_id(name: &str) -> String {
hex::encode(Sha1::digest(name.as_bytes())) hex::encode(Sha1::digest(name.as_bytes()))
@ -87,10 +73,10 @@ fn list_backends() {
#[derive(Clone)] #[derive(Clone)]
struct Setup { struct Setup {
backend: fn(Option<String>) -> Box<Sink>, backend: fn(Option<String>) -> Box<dyn Sink>,
device: Option<String>, device: Option<String>,
mixer: fn(Option<MixerConfig>) -> Box<Mixer>, mixer: fn(Option<MixerConfig>) -> Box<dyn Mixer>,
cache: Option<Cache>, cache: Option<Cache>,
player_config: PlayerConfig, player_config: PlayerConfig,
@ -199,7 +185,13 @@ fn setup(args: &[String]) -> Setup {
let matches = match opts.parse(&args[1..]) { let matches = match opts.parse(&args[1..]) {
Ok(m) => m, Ok(m) => m,
Err(f) => { Err(f) => {
writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap(); writeln!(
stderr(),
"error: {}\n{}",
f.to_string(),
usage(&args[0], &opts)
)
.unwrap();
exit(1); exit(1);
} }
}; };
@ -233,7 +225,9 @@ fn setup(args: &[String]) -> Setup {
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer"); let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
let mixer_config = MixerConfig { let mixer_config = MixerConfig {
card: matches.opt_str("mixer-card").unwrap_or(String::from("default")), card: matches
.opt_str("mixer-card")
.unwrap_or(String::from("default")),
mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")), mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
index: matches index: matches
.opt_str("mixer-index") .opt_str("mixer-index")
@ -368,9 +362,9 @@ struct Main {
player_config: PlayerConfig, player_config: PlayerConfig,
session_config: SessionConfig, session_config: SessionConfig,
connect_config: ConnectConfig, connect_config: ConnectConfig,
backend: fn(Option<String>) -> Box<Sink>, backend: fn(Option<String>) -> Box<dyn Sink>,
device: Option<String>, device: Option<String>,
mixer: fn(Option<MixerConfig>) -> Box<Mixer>, mixer: fn(Option<MixerConfig>) -> Box<dyn Mixer>,
mixer_config: MixerConfig, mixer_config: MixerConfig,
handle: Handle, handle: Handle,
@ -379,7 +373,7 @@ struct Main {
spirc: Option<Spirc>, spirc: Option<Spirc>,
spirc_task: Option<SpircTask>, spirc_task: Option<SpircTask>,
connect: Box<Future<Item = Session, Error = io::Error>>, connect: Box<dyn Future<Item = Session, Error = io::Error>>,
shutdown: bool, shutdown: bool,
last_credentials: Option<Credentials>, last_credentials: Option<Credentials>,
@ -419,7 +413,8 @@ impl Main {
let config = task.connect_config.clone(); let config = task.connect_config.clone();
let device_id = task.session_config.device_id.clone(); let device_id = task.session_config.device_id.clone();
task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap()); task.discovery =
Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
} }
if let Some(credentials) = setup.credentials { if let Some(credentials) = setup.credentials {
@ -453,7 +448,9 @@ impl Future for Main {
loop { loop {
let mut progress = false; let mut progress = false;
if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) { if let Some(Async::Ready(Some(creds))) =
self.discovery.as_mut().map(|d| d.poll().unwrap())
{
if let Some(ref spirc) = self.spirc { if let Some(ref spirc) = self.spirc {
spirc.shutdown(); spirc.shutdown();
} }

View file

@ -1,8 +1,9 @@
use librespot::playback::player::PlayerEvent; use librespot::playback::player::PlayerEvent;
use tokio_process::{Child, CommandExt}; use log::info;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
use std::process::Command; use std::process::Command;
use tokio_process::{Child, CommandExt};
fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result<Child> { fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result<Child> {
let mut v: Vec<&str> = program.split_whitespace().collect(); let mut v: Vec<&str> = program.split_whitespace().collect();