mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge remote-tracking branch 'origin/master' into dynamic-blocks
This commit is contained in:
commit
6545674a63
49 changed files with 5343 additions and 3479 deletions
|
@ -1,6 +1,6 @@
|
|||
language: rust
|
||||
rust:
|
||||
- 1.26.0
|
||||
- 1.32.0
|
||||
- stable
|
||||
- beta
|
||||
- nightly
|
||||
|
@ -14,6 +14,8 @@ addons:
|
|||
- libc6-dev-armhf-cross
|
||||
- libpulse-dev
|
||||
- portaudio19-dev
|
||||
- libasound2-dev
|
||||
- libsdl2-dev
|
||||
|
||||
before_script:
|
||||
- mkdir -p ~/.cargo
|
||||
|
@ -25,9 +27,12 @@ script:
|
|||
- cargo build --locked --no-default-features
|
||||
- cargo build --locked --no-default-features --features "with-tremor"
|
||||
- cargo build --locked --no-default-features --features "with-vorbis"
|
||||
- cargo build --locked --no-default-features --features "alsa-backend"
|
||||
- cargo build --locked --no-default-features --features "portaudio-backend"
|
||||
- cargo build --locked --no-default-features --features "pulseaudio-backend"
|
||||
- cargo build --locked --no-default-features --features "alsa-backend"
|
||||
- cargo build --locked --no-default-features --features "jackaudio-backend"
|
||||
- cargo build --locked --no-default-features --features "rodio-backend"
|
||||
- cargo build --locked --no-default-features --features "sdl-backend"
|
||||
- cargo build --locked --no-default-features --target armv7-unknown-linux-gnueabihf
|
||||
|
||||
notifications:
|
||||
|
|
1754
Cargo.lock
generated
1754
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
45
Cargo.toml
45
Cargo.toml
|
@ -33,45 +33,38 @@ path = "playback"
|
|||
path = "protocol"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.5.0"
|
||||
env_logger = "0.4.0"
|
||||
futures = "0.1.8"
|
||||
getopts = "0.2.14"
|
||||
hyper = "0.11.2"
|
||||
log = "0.3.5"
|
||||
num-bigint = "0.1.35"
|
||||
protobuf = "1.1"
|
||||
rand = "0.6"
|
||||
rpassword = "0.3.0"
|
||||
rust-crypto = "0.2.36"
|
||||
serde = "0.9.6"
|
||||
serde_derive = "0.9.6"
|
||||
serde_json = "0.9.5"
|
||||
tokio-core = "0.1.2"
|
||||
base64 = "0.10"
|
||||
env_logger = "0.6"
|
||||
futures = "0.1"
|
||||
getopts = "0.2"
|
||||
hyper = "0.11"
|
||||
log = "0.4"
|
||||
num-bigint = "0.2"
|
||||
protobuf = "2.8.*"
|
||||
rand = "0.7"
|
||||
rpassword = "3.0"
|
||||
tokio-core = "0.1"
|
||||
tokio-io = "0.1"
|
||||
tokio-process = "0.2.2"
|
||||
tokio-signal = "0.1.2"
|
||||
url = "1.7.0"
|
||||
|
||||
[build-dependencies]
|
||||
rand = "0.6"
|
||||
vergen = "0.1.0"
|
||||
|
||||
[replace]
|
||||
"rust-crypto:0.2.36" = { git = "https://github.com/awmath/rust-crypto.git", branch = "avx2" }
|
||||
tokio-process = "0.2"
|
||||
tokio-signal = "0.2"
|
||||
url = "1.7"
|
||||
sha-1 = "0.8"
|
||||
hex = "0.3"
|
||||
|
||||
[features]
|
||||
alsa-backend = ["librespot-playback/alsa-backend"]
|
||||
portaudio-backend = ["librespot-playback/portaudio-backend"]
|
||||
pulseaudio-backend = ["librespot-playback/pulseaudio-backend"]
|
||||
jackaudio-backend = ["librespot-playback/jackaudio-backend"]
|
||||
rodio-backend = ["librespot-playback/rodio-backend"]
|
||||
sdl-backend = ["librespot-playback/sdl-backend"]
|
||||
|
||||
with-tremor = ["librespot-audio/with-tremor"]
|
||||
with-vorbis = ["librespot-audio/with-vorbis"]
|
||||
|
||||
with-dns-sd = ["librespot-connect/with-dns-sd"]
|
||||
|
||||
default = ["librespot-playback/portaudio-backend"]
|
||||
default = ["librespot-playback/rodio-backend"]
|
||||
|
||||
[package.metadata.deb]
|
||||
maintainer = "librespot-org"
|
||||
|
|
22
README.md
22
README.md
|
@ -1,13 +1,15 @@
|
|||
[![Build Status](https://travis-ci.org/librespot-org/librespot.svg?branch=master)](https://travis-ci.org/librespot-org/librespot)
|
||||
[![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources)
|
||||
|
||||
Current maintainer is @awiouy folks.
|
||||
|
||||
# librespot
|
||||
*librespot* is an open source client library for Spotify. It enables
|
||||
applications to use Spotify's service, without using the official but
|
||||
closed-source libspotify. Additionally, it will provide extra features
|
||||
which are not available in the official library.
|
||||
|
||||
Note: librespot only works with Spotify Premium
|
||||
_Note: librespot only works with Spotify Premium. This will remain the case for the forseeable future, as we are unlikely to work on implementing the features such as limited skips and adverts that would be required to make librespot compliant with free accounts._
|
||||
|
||||
## This fork
|
||||
As the origin by [plietar](https://github.com/plietar/) is no longer actively maintained, this organisation and repository have been set up so that the project may be maintained and upgraded in the future.
|
||||
|
@ -26,27 +28,18 @@ If you wish to learn more about how librespot works overall, the best way is to
|
|||
If you run into a bug when using librespot, please search the existing issues before opening a new one. Chances are, we've encountered it before, and have provided a resolution. If not, please open a new one, and where possible, include the backtrace librespot generates on crashing, along with anything we can use to reproduce the issue, eg. the Spotify URI of the song that caused the crash.
|
||||
|
||||
# Building
|
||||
Rust 1.23.0 or later is required to build librespot.
|
||||
Rust 1.32.0 or later is required to build librespot.
|
||||
|
||||
**If you are building librespot on macOS, the homebrew provided rust may fail due to the way in which homebrew installs rust. In this case, uninstall the homebrew version of rust and use [rustup](https://www.rustup.rs/), and librespot should then build. This should have been fixed in more recent versions of Homebrew, but we're leaving this notice here as a warning.**
|
||||
|
||||
**We strongly suggest you install rust using rustup, for ease of installation and maintenance.**
|
||||
|
||||
It also requires a C, with portaudio.
|
||||
We recently switched to using [Rodio](https://github.com/tomaka/rodio) for audio playback by default, hence for macOS and Windows, you should just be able to clone and build librespot (with the command below). For linux, you will need to run the additional commands below, depending on your distro.
|
||||
|
||||
On debian / ubuntu, the following command will install these dependencies :
|
||||
```shell
|
||||
sudo apt-get install build-essential portaudio19-dev
|
||||
sudo apt-get install build-essential libasound2-dev
|
||||
```
|
||||
|
||||
On Fedora systems, the following command will install these dependencies :
|
||||
```shell
|
||||
sudo dnf install portaudio-devel make gcc
|
||||
```
|
||||
|
||||
On macOS, using homebrew :
|
||||
```shell
|
||||
brew install portaudio
|
||||
sudo dnf install alsa-lib-devel make gcc
|
||||
```
|
||||
|
||||
Once you've cloned this repository you can build *librespot* using `cargo`.
|
||||
|
@ -90,3 +83,4 @@ This is a non exhaustive list of projects that either use or have modified libre
|
|||
- [Spotcontrol](https://github.com/badfortrains/spotcontrol) - A golang implementation of a Spotify Connect controller. No playback
|
||||
functionality.
|
||||
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
|
||||
- [ncspot](https://github.com/hrkfdn/ncspot) - Cross-platform ncurses Spotify client.
|
||||
|
|
|
@ -7,17 +7,15 @@ authors = ["Paul Lietar <paul@lietar.net>"]
|
|||
path = "../core"
|
||||
|
||||
[dependencies]
|
||||
bit-set = "0.4.0"
|
||||
byteorder = "1.0"
|
||||
bytes = "0.4"
|
||||
futures = "0.1.8"
|
||||
lewton = "0.9.3"
|
||||
log = "0.3.5"
|
||||
num-bigint = "0.1.35"
|
||||
num-traits = "0.1.36"
|
||||
rust-crypto = "0.2.36"
|
||||
tempfile = "2.1"
|
||||
tokio = "0.1.2"
|
||||
bit-set = "0.5"
|
||||
byteorder = "1.3"
|
||||
futures = "0.1"
|
||||
lewton = "0.9"
|
||||
log = "0.4"
|
||||
num-bigint = "0.2"
|
||||
num-traits = "0.2"
|
||||
tempfile = "3.1"
|
||||
aes-ctr = "0.3"
|
||||
|
||||
tremor = { git = "https://github.com/plietar/rust-tremor", optional = true }
|
||||
vorbis = { version ="0.1.0", optional = true }
|
||||
|
|
|
@ -1,39 +1,38 @@
|
|||
use crypto::aes;
|
||||
use crypto::symmetriccipher::SynchronousStreamCipher;
|
||||
use num_bigint::BigUint;
|
||||
use num_traits::FromPrimitive;
|
||||
use std::io;
|
||||
use std::ops::Add;
|
||||
|
||||
use core::audio_key::AudioKey;
|
||||
use aes_ctr::Aes128Ctr;
|
||||
use aes_ctr::stream_cipher::{
|
||||
NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek
|
||||
};
|
||||
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
||||
|
||||
const AUDIO_AESIV: &'static [u8] = &[
|
||||
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
|
||||
use librespot_core::audio_key::AudioKey;
|
||||
|
||||
const AUDIO_AESIV: [u8; 16] = [
|
||||
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77,
|
||||
0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
|
||||
];
|
||||
|
||||
pub struct AudioDecrypt<T: io::Read> {
|
||||
cipher: Box<SynchronousStreamCipher + 'static>,
|
||||
key: AudioKey,
|
||||
cipher: Aes128Ctr,
|
||||
reader: T,
|
||||
}
|
||||
|
||||
impl<T: io::Read> AudioDecrypt<T> {
|
||||
pub fn new(key: AudioKey, reader: T) -> AudioDecrypt<T> {
|
||||
let cipher = aes::ctr(aes::KeySize::KeySize128, &key.0, AUDIO_AESIV);
|
||||
AudioDecrypt {
|
||||
cipher: cipher,
|
||||
key: key,
|
||||
reader: reader,
|
||||
}
|
||||
let cipher = Aes128Ctr::new(
|
||||
&GenericArray::from_slice(&key.0),
|
||||
&GenericArray::from_slice(&AUDIO_AESIV),
|
||||
);
|
||||
AudioDecrypt { cipher, reader }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: io::Read> io::Read for AudioDecrypt<T> {
|
||||
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
|
||||
let mut buffer = vec![0u8; output.len()];
|
||||
let len = try!(self.reader.read(&mut buffer));
|
||||
let len = try!(self.reader.read(output));
|
||||
|
||||
self.cipher.process(&buffer[..len], &mut output[..len]);
|
||||
self.cipher.apply_keystream(&mut output[..len]);
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
|
@ -42,17 +41,9 @@ impl<T: io::Read> io::Read for AudioDecrypt<T> {
|
|||
impl<T: io::Read + io::Seek> io::Seek for AudioDecrypt<T> {
|
||||
fn seek(&mut self, pos: io::SeekFrom) -> io::Result<u64> {
|
||||
let newpos = try!(self.reader.seek(pos));
|
||||
let skip = newpos % 16;
|
||||
|
||||
let iv = BigUint::from_bytes_be(AUDIO_AESIV)
|
||||
.add(BigUint::from_u64(newpos / 16).unwrap())
|
||||
.to_bytes_be();
|
||||
self.cipher = aes::ctr(aes::KeySize::KeySize128, &self.key.0, &iv);
|
||||
self.cipher.seek(newpos);
|
||||
|
||||
let buf = vec![0u8; skip as usize];
|
||||
let mut buf2 = vec![0u8; skip as usize];
|
||||
self.cipher.process(&buf, &mut buf2);
|
||||
|
||||
Ok(newpos as u64)
|
||||
Ok(newpos)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@ use std::time::{Duration, Instant};
|
|||
use tempfile::NamedTempFile;
|
||||
use range_set::{Range, RangeSet};
|
||||
|
||||
use core::channel::{Channel, ChannelData, ChannelError, ChannelHeaders};
|
||||
use core::session::Session;
|
||||
use core::spotify_id::FileId;
|
||||
use librespot_core::channel::{Channel, ChannelData, ChannelError, ChannelHeaders};
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::FileId;
|
||||
use futures::sync::mpsc::unbounded;
|
||||
use std::sync::atomic;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
|
|
@ -75,7 +75,7 @@ impl error::Error for VorbisError {
|
|||
error::Error::description(&self.0)
|
||||
}
|
||||
|
||||
fn cause(&self) -> Option<&error::Error> {
|
||||
error::Error::cause(&self.0)
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
error::Error::source(&self.0)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,12 @@ extern crate log;
|
|||
extern crate bit_set;
|
||||
extern crate byteorder;
|
||||
extern crate bytes;
|
||||
extern crate crypto;
|
||||
extern crate num_bigint;
|
||||
extern crate num_traits;
|
||||
extern crate tempfile;
|
||||
extern crate tokio;
|
||||
extern crate aes_ctr;
|
||||
|
||||
extern crate librespot_core as core;
|
||||
extern crate librespot_core;
|
||||
|
||||
mod decrypt;
|
||||
mod fetch;
|
||||
|
@ -31,4 +30,3 @@ pub use fetch::{AudioFile, AudioFileOpen, StreamLoaderController};
|
|||
pub use lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
||||
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||
|
||||
|
|
|
@ -11,19 +11,22 @@ path = "../playback"
|
|||
path = "../protocol"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.5.0"
|
||||
futures = "0.1.8"
|
||||
hyper = "0.11.2"
|
||||
log = "0.3.5"
|
||||
num-bigint = "0.1.35"
|
||||
protobuf = "2.0.5"
|
||||
rand = "0.6"
|
||||
rust-crypto = "0.2.36"
|
||||
serde = "0.9.6"
|
||||
serde_derive = "0.9.6"
|
||||
serde_json = "0.9.5"
|
||||
tokio-core = "0.1.2"
|
||||
url = "1.3"
|
||||
base64 = "0.10"
|
||||
futures = "0.1"
|
||||
hyper = "0.11"
|
||||
log = "0.4"
|
||||
num-bigint = "0.2"
|
||||
protobuf = "2.8.*"
|
||||
rand = "0.7"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
tokio-core = "0.1"
|
||||
url = "1.7"
|
||||
sha-1 = "0.8"
|
||||
hmac = "0.7"
|
||||
aes-ctr = "0.3"
|
||||
block-modes = "0.3"
|
||||
|
||||
dns-sd = { version = "0.1.3", optional = true }
|
||||
mdns = { git = "https://github.com/plietar/rust-mdns", optional = true }
|
||||
|
|
86
connect/src/context.rs
Normal file
86
connect/src/context.rs
Normal file
|
@ -0,0 +1,86 @@
|
|||
use librespot_core::spotify_id::SpotifyId;
|
||||
use protocol::spirc::TrackRef;
|
||||
|
||||
use serde;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct StationContext {
|
||||
pub uri: Option<String>,
|
||||
pub next_page_url: String,
|
||||
#[serde(deserialize_with = "deserialize_protobuf_TrackRef")]
|
||||
pub tracks: Vec<TrackRef>,
|
||||
// Not required for core functionality
|
||||
// pub seeds: Vec<String>,
|
||||
// #[serde(rename = "imageUri")]
|
||||
// pub image_uri: String,
|
||||
// pub subtitle: Option<String>,
|
||||
// pub subtitles: Vec<String>,
|
||||
// #[serde(rename = "subtitleUri")]
|
||||
// pub subtitle_uri: Option<String>,
|
||||
// pub title: String,
|
||||
// #[serde(rename = "titleUri")]
|
||||
// pub title_uri: String,
|
||||
// pub related_artists: Vec<ArtistContext>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct PageContext {
|
||||
pub uri: String,
|
||||
pub next_page_url: String,
|
||||
#[serde(deserialize_with = "deserialize_protobuf_TrackRef")]
|
||||
pub tracks: Vec<TrackRef>,
|
||||
// Not required for core functionality
|
||||
// pub url: String,
|
||||
// // pub restrictions:
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct TrackContext {
|
||||
#[serde(rename = "original_gid")]
|
||||
pub gid: String,
|
||||
pub uri: String,
|
||||
pub uid: String,
|
||||
// Not required for core functionality
|
||||
// pub album_uri: String,
|
||||
// pub artist_uri: String,
|
||||
// pub metadata: MetadataContext,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArtistContext {
|
||||
artist_name: String,
|
||||
artist_uri: String,
|
||||
image_uri: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct MetadataContext {
|
||||
album_title: String,
|
||||
artist_name: String,
|
||||
artist_uri: String,
|
||||
image_url: String,
|
||||
title: String,
|
||||
uid: String,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
fn deserialize_protobuf_TrackRef<'d, D>(de: D) -> Result<Vec<TrackRef>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'d>,
|
||||
{
|
||||
let v: Vec<TrackContext> = try!(serde::Deserialize::deserialize(de));
|
||||
let track_vec = v
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let mut t = TrackRef::new();
|
||||
// This has got to be the most round about way of doing this.
|
||||
t.set_gid(SpotifyId::from_base62(&v.gid).unwrap().to_raw().to_vec());
|
||||
t.set_uri(v.uri.to_owned());
|
||||
|
||||
t
|
||||
})
|
||||
.collect::<Vec<TrackRef>>();
|
||||
|
||||
Ok(track_vec)
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
use base64;
|
||||
use crypto;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::mac::Mac;
|
||||
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 futures::sync::mpsc;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use hyper::server::{Http, Request, Response, Service};
|
||||
|
@ -21,10 +23,12 @@ use std::sync::Arc;
|
|||
use tokio_core::reactor::Handle;
|
||||
use url;
|
||||
|
||||
use core::authentication::Credentials;
|
||||
use core::config::ConnectConfig;
|
||||
use core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
||||
use core::util;
|
||||
use librespot_core::authentication::Credentials;
|
||||
use librespot_core::config::ConnectConfig;
|
||||
use librespot_core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
||||
use librespot_core::util;
|
||||
|
||||
type HmacSha1 = Hmac<Sha1>;
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Discovery(Arc<DiscoveryInner>);
|
||||
|
@ -106,33 +110,27 @@ impl Discovery {
|
|||
let encrypted = &encrypted_blob[16..encrypted_blob.len() - 20];
|
||||
let cksum = &encrypted_blob[encrypted_blob.len() - 20..encrypted_blob.len()];
|
||||
|
||||
let base_key = {
|
||||
let mut data = [0u8; 20];
|
||||
let mut h = crypto::sha1::Sha1::new();
|
||||
h.input(&shared_key.to_bytes_be());
|
||||
h.result(&mut data);
|
||||
data[..16].to_owned()
|
||||
};
|
||||
let base_key = Sha1::digest(&shared_key.to_bytes_be());
|
||||
let base_key = &base_key[..16];
|
||||
|
||||
let checksum_key = {
|
||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
|
||||
let mut h = HmacSha1::new_varkey(base_key)
|
||||
.expect("HMAC can take key of any size");
|
||||
h.input(b"checksum");
|
||||
h.result().code().to_owned()
|
||||
h.result().code()
|
||||
};
|
||||
|
||||
let encryption_key = {
|
||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &base_key);
|
||||
let mut h = HmacSha1::new_varkey(&base_key)
|
||||
.expect("HMAC can take key of any size");
|
||||
h.input(b"encryption");
|
||||
h.result().code().to_owned()
|
||||
h.result().code()
|
||||
};
|
||||
|
||||
let mac = {
|
||||
let mut h = crypto::hmac::Hmac::new(crypto::sha1::Sha1::new(), &checksum_key);
|
||||
let mut h = HmacSha1::new_varkey(&checksum_key)
|
||||
.expect("HMAC can take key of any size");
|
||||
h.input(encrypted);
|
||||
h.result().code().to_owned()
|
||||
};
|
||||
|
||||
if mac != cksum {
|
||||
if let Err(_) = h.verify(cksum) {
|
||||
warn!("Login error for user {:?}: MAC mismatch", username);
|
||||
let result = json!({
|
||||
"status": 102,
|
||||
|
@ -145,10 +143,12 @@ impl Discovery {
|
|||
}
|
||||
|
||||
let decrypted = {
|
||||
let mut data = vec![0u8; encrypted.len()];
|
||||
let mut cipher =
|
||||
crypto::aes::ctr(crypto::aes::KeySize::KeySize128, &encryption_key[0..16], iv);
|
||||
cipher.process(encrypted, &mut data);
|
||||
let mut data = encrypted.to_vec();
|
||||
let mut cipher = Aes128Ctr::new(
|
||||
&GenericArray::from_slice(&encryption_key[0..16]),
|
||||
&GenericArray::from_slice(iv),
|
||||
);
|
||||
cipher.apply_keystream(&mut data);
|
||||
String::from_utf8(data).unwrap()
|
||||
};
|
||||
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
extern crate log;
|
||||
#[macro_use]
|
||||
extern crate serde_json;
|
||||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
extern crate serde;
|
||||
|
||||
extern crate base64;
|
||||
extern crate crypto;
|
||||
extern crate futures;
|
||||
extern crate hyper;
|
||||
extern crate num_bigint;
|
||||
|
@ -13,15 +15,21 @@ extern crate rand;
|
|||
extern crate tokio_core;
|
||||
extern crate url;
|
||||
|
||||
extern crate sha1;
|
||||
extern crate hmac;
|
||||
extern crate aes_ctr;
|
||||
extern crate block_modes;
|
||||
|
||||
#[cfg(feature = "with-dns-sd")]
|
||||
extern crate dns_sd;
|
||||
|
||||
#[cfg(not(feature = "with-dns-sd"))]
|
||||
extern crate mdns;
|
||||
|
||||
extern crate librespot_core as core;
|
||||
extern crate librespot_core;
|
||||
extern crate librespot_playback as playback;
|
||||
extern crate librespot_protocol as protocol;
|
||||
|
||||
pub mod context;
|
||||
pub mod discovery;
|
||||
pub mod spirc;
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
use std;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use futures::future;
|
||||
use futures::sync::{mpsc, oneshot};
|
||||
use futures::{Async, Future, Poll, Sink, Stream};
|
||||
use protobuf::{self, Message};
|
||||
|
||||
use core::config::ConnectConfig;
|
||||
use core::mercury::MercuryError;
|
||||
use core::session::Session;
|
||||
use core::spotify_id::SpotifyId;
|
||||
use core::util::SeqGenerator;
|
||||
use core::version;
|
||||
use core::volume::Volume;
|
||||
|
||||
use protocol;
|
||||
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State};
|
||||
|
||||
use playback::mixer::Mixer;
|
||||
use playback::player::Player;
|
||||
|
||||
use rand;
|
||||
use rand::seq::SliceRandom;
|
||||
use std;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use serde_json;
|
||||
|
||||
use context::StationContext;
|
||||
use librespot_core::config::ConnectConfig;
|
||||
use librespot_core::mercury::MercuryError;
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::SpotifyId;
|
||||
use librespot_core::util::SeqGenerator;
|
||||
use librespot_core::version;
|
||||
use librespot_core::volume::Volume;
|
||||
use playback::mixer::Mixer;
|
||||
use playback::player::Player;
|
||||
use protocol;
|
||||
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State};
|
||||
|
||||
pub struct SpircTask {
|
||||
player: Player,
|
||||
|
@ -40,6 +40,8 @@ pub struct SpircTask {
|
|||
|
||||
shutdown: bool,
|
||||
session: Session,
|
||||
context_fut: Box<Future<Item = serde_json::Value, Error = MercuryError>>,
|
||||
context: Option<StationContext>,
|
||||
}
|
||||
|
||||
pub enum SpircCommand {
|
||||
|
@ -53,18 +55,13 @@ pub enum SpircCommand {
|
|||
Shutdown,
|
||||
}
|
||||
|
||||
const CONTEXT_TRACKS_HISTORY: usize = 10;
|
||||
const CONTEXT_FETCH_THRESHOLD: u32 = 5;
|
||||
|
||||
pub struct Spirc {
|
||||
commands: mpsc::UnboundedSender<SpircCommand>,
|
||||
}
|
||||
|
||||
fn now_ms() -> i64 {
|
||||
let dur = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(dur) => dur,
|
||||
Err(err) => err.duration(),
|
||||
};
|
||||
(dur.as_secs() * 1000 + (dur.subsec_nanos() / 1000_000) as u64) as i64
|
||||
}
|
||||
|
||||
fn initial_state() -> State {
|
||||
let mut frame = protocol::spirc::State::new();
|
||||
frame.set_repeat(false);
|
||||
|
@ -139,6 +136,15 @@ fn initial_device_state(config: ConnectConfig) -> DeviceState {
|
|||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kSupportsPlaylistV2);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(64)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kSupportedContexts);
|
||||
|
@ -176,7 +182,7 @@ fn calc_logarithmic_volume(volume: u16) -> u16 {
|
|||
// Volume conversion taken from https://www.dr-lex.be/info-stuff/volumecontrols.html#ideal2
|
||||
// Convert the given volume [0..0xffff] to a dB gain
|
||||
// We assume a dB range of 60dB.
|
||||
// Use the equatation: a * exp(b * x)
|
||||
// Use the equation: a * exp(b * x)
|
||||
// in which a = IDEAL_FACTOR, b = 1/1000
|
||||
const IDEAL_FACTOR: f64 = 6.908;
|
||||
let normalized_volume = volume as f64 / std::u16::MAX as f64; // To get a value between 0 and 1
|
||||
|
@ -259,6 +265,9 @@ impl Spirc {
|
|||
|
||||
shutdown: false,
|
||||
session: session.clone(),
|
||||
|
||||
context_fut: Box::new(future::empty()),
|
||||
context: None,
|
||||
};
|
||||
|
||||
task.set_volume(volume);
|
||||
|
@ -335,6 +344,39 @@ impl Future for SpircTask {
|
|||
Ok(Async::NotReady) => (),
|
||||
Err(oneshot::Canceled) => self.end_of_track = Box::new(future::empty()),
|
||||
}
|
||||
|
||||
match self.context_fut.poll() {
|
||||
Ok(Async::Ready(value)) => {
|
||||
let r_context = serde_json::from_value::<StationContext>(value.clone());
|
||||
self.context = match r_context {
|
||||
Ok(context) => {
|
||||
info!(
|
||||
"Resolved {:?} tracks from <{:?}>",
|
||||
context.tracks.len(),
|
||||
self.state.get_context_uri(),
|
||||
);
|
||||
Some(context)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Unable to parse JSONContext {:?}\n{:?}", e, value);
|
||||
None
|
||||
}
|
||||
};
|
||||
// It needn't be so verbose - can be as simple as
|
||||
// if let Some(ref context) = r_context {
|
||||
// info!("Got {:?} tracks from <{}>", context.tracks.len(), context.uri);
|
||||
// }
|
||||
// self.context = r_context;
|
||||
|
||||
progress = true;
|
||||
self.context_fut = Box::new(future::empty());
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(err) => {
|
||||
self.context_fut = Box::new(future::empty());
|
||||
error!("ContextError: {:?}", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let poll_sender = self.sender.poll_complete().unwrap();
|
||||
|
@ -352,6 +394,15 @@ impl Future for SpircTask {
|
|||
}
|
||||
|
||||
impl SpircTask {
|
||||
fn now_ms(&mut self) -> i64 {
|
||||
let dur = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(dur) => dur,
|
||||
Err(err) => err.duration(),
|
||||
};
|
||||
((dur.as_secs() as i64 + self.session.time_delta()) * 1000
|
||||
+ (dur.subsec_nanos() / 1000_000) as i64)
|
||||
}
|
||||
|
||||
fn handle_command(&mut self, cmd: SpircCommand) {
|
||||
let active = self.device.get_is_active();
|
||||
match cmd {
|
||||
|
@ -442,19 +493,22 @@ impl SpircTask {
|
|||
|
||||
MessageType::kMessageTypeLoad => {
|
||||
if !self.device.get_is_active() {
|
||||
let now = self.now_ms();
|
||||
self.device.set_is_active(true);
|
||||
self.device.set_became_active_at(now_ms());
|
||||
self.device.set_became_active_at(now);
|
||||
}
|
||||
|
||||
self.update_tracks(&frame);
|
||||
|
||||
if self.state.get_track().len() > 0 {
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_ms(frame.get_state().get_position_ms());
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
|
||||
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
||||
self.load_track(play);
|
||||
} else {
|
||||
info!("No more tracks left in queue");
|
||||
self.state.set_status(PlayStatus::kPlayStatusStop);
|
||||
}
|
||||
|
||||
|
@ -524,8 +578,9 @@ impl SpircTask {
|
|||
MessageType::kMessageTypeSeek => {
|
||||
let position = frame.get_position();
|
||||
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_ms(position);
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
self.player.seek(position);
|
||||
self.notify(None);
|
||||
}
|
||||
|
@ -558,7 +613,8 @@ impl SpircTask {
|
|||
self.mixer.start();
|
||||
self.player.play();
|
||||
self.state.set_status(PlayStatus::kPlayStatusPlay);
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -576,7 +632,7 @@ impl SpircTask {
|
|||
self.mixer.stop();
|
||||
self.state.set_status(PlayStatus::kPlayStatusPause);
|
||||
|
||||
let now = now_ms() as u64;
|
||||
let now = self.now_ms() as u64;
|
||||
let position = self.state.get_position_ms();
|
||||
|
||||
let diff = now - self.state.get_position_measured_at();
|
||||
|
@ -600,13 +656,29 @@ impl SpircTask {
|
|||
fn handle_next(&mut self) {
|
||||
let mut new_index = self.consume_queued_track() as u32;
|
||||
let mut continue_playing = true;
|
||||
debug!(
|
||||
"At track {:?} of {:?} <{:?}> update [{}]",
|
||||
new_index,
|
||||
self.state.get_track().len(),
|
||||
self.state.get_context_uri(),
|
||||
self.state.get_track().len() as u32 - new_index < CONTEXT_FETCH_THRESHOLD
|
||||
);
|
||||
let context_uri = self.state.get_context_uri().to_owned();
|
||||
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.context_fut = self.resolve_station(&context_uri);
|
||||
self.update_tracks_from_context();
|
||||
}
|
||||
|
||||
if new_index >= self.state.get_track().len() as u32 {
|
||||
new_index = 0; // Loop around back to start
|
||||
continue_playing = self.state.get_repeat();
|
||||
}
|
||||
self.state.set_playing_track_index(new_index);
|
||||
self.state.set_position_ms(0);
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
|
||||
self.load_track(continue_playing);
|
||||
}
|
||||
|
@ -642,14 +714,16 @@ impl SpircTask {
|
|||
pos += 1;
|
||||
}
|
||||
|
||||
let now = self.now_ms();
|
||||
self.state.set_playing_track_index(new_index);
|
||||
self.state.set_position_ms(0);
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
|
||||
self.load_track(true);
|
||||
} else {
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_ms(0);
|
||||
self.state.set_position_measured_at(now_ms() as u64);
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
self.player.seek(0);
|
||||
}
|
||||
}
|
||||
|
@ -676,14 +750,59 @@ impl SpircTask {
|
|||
}
|
||||
|
||||
fn position(&mut self) -> u32 {
|
||||
let diff = now_ms() as u64 - self.state.get_position_measured_at();
|
||||
let diff = self.now_ms() as u64 - self.state.get_position_measured_at();
|
||||
self.state.get_position_ms() + diff as u32
|
||||
}
|
||||
|
||||
fn resolve_station(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||
let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri);
|
||||
|
||||
self.resolve_uri(&radio_uri)
|
||||
}
|
||||
|
||||
fn resolve_uri(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||
let request = self.session.mercury().get(uri);
|
||||
|
||||
Box::new(request.and_then(move |response| {
|
||||
let data = response.payload.first().expect("Empty payload on context uri");
|
||||
let response: serde_json::Value = serde_json::from_slice(&data).unwrap();
|
||||
|
||||
Ok(response)
|
||||
}))
|
||||
}
|
||||
|
||||
fn update_tracks_from_context(&mut self) {
|
||||
if let Some(ref context) = self.context {
|
||||
self.context_fut = self.resolve_uri(&context.next_page_url);
|
||||
|
||||
let new_tracks = &context.tracks;
|
||||
debug!("Adding {:?} tracks from context to frame", new_tracks.len());
|
||||
let mut track_vec = self.state.take_track().into_vec();
|
||||
if let Some(head) = track_vec.len().checked_sub(CONTEXT_TRACKS_HISTORY) {
|
||||
track_vec.drain(0..head);
|
||||
}
|
||||
track_vec.extend_from_slice(&new_tracks);
|
||||
self.state.set_track(protobuf::RepeatedField::from_vec(track_vec));
|
||||
|
||||
// Update playing index
|
||||
if let Some(new_index) = self
|
||||
.state
|
||||
.get_playing_track_index()
|
||||
.checked_sub(CONTEXT_TRACKS_HISTORY as u32)
|
||||
{
|
||||
self.state.set_playing_track_index(new_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_tracks(&mut self, frame: &protocol::spirc::Frame) {
|
||||
let index = frame.get_state().get_playing_track_index();
|
||||
let tracks = frame.get_state().get_track();
|
||||
let context_uri = frame.get_state().get_context_uri().to_owned();
|
||||
let tracks = frame.get_state().get_track();
|
||||
debug!("Frame has {:?} tracks", tracks.len());
|
||||
if context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:") {
|
||||
self.context_fut = self.resolve_station(&context_uri);
|
||||
}
|
||||
|
||||
self.state.set_playing_track_index(index);
|
||||
self.state.set_track(tracks.into_iter().cloned().collect());
|
||||
|
@ -693,13 +812,25 @@ impl SpircTask {
|
|||
}
|
||||
|
||||
fn load_track(&mut self, play: bool) {
|
||||
let index = self.state.get_playing_track_index();
|
||||
let track = {
|
||||
let gid = self.state.get_track()[index as usize].get_gid();
|
||||
SpotifyId::from_raw(gid).unwrap()
|
||||
let mut index = self.state.get_playing_track_index();
|
||||
// Check for malformed gid
|
||||
let tracks_len = self.state.get_track().len() as u32;
|
||||
let mut track_ref = &self.state.get_track()[index as usize];
|
||||
while track_ref.get_gid().len() != 16 {
|
||||
warn!(
|
||||
"Skipping track {:?} at position [{}] of {}",
|
||||
track_ref.get_uri(),
|
||||
index,
|
||||
tracks_len
|
||||
);
|
||||
index = if index + 1 < tracks_len { index + 1 } else { 0 };
|
||||
track_ref = &self.state.get_track()[index as usize];
|
||||
}
|
||||
SpotifyId::from_raw(track_ref.get_gid()).unwrap()
|
||||
};
|
||||
let position = self.state.get_position_ms();
|
||||
|
||||
let position = self.state.get_position_ms();
|
||||
let end_of_track = self.player.load(track, play, position);
|
||||
|
||||
if play {
|
||||
|
@ -752,7 +883,7 @@ impl<'a> CommandSender<'a> {
|
|||
frame.set_seq_nr(spirc.sequence.get());
|
||||
frame.set_typ(cmd);
|
||||
frame.set_device_state(spirc.device.clone());
|
||||
frame.set_state_update_id(now_ms());
|
||||
frame.set_state_update_id(spirc.now_ms());
|
||||
CommandSender {
|
||||
spirc: spirc,
|
||||
frame: frame,
|
||||
|
|
|
@ -24,6 +24,7 @@ RUN apt-get update
|
|||
|
||||
RUN apt-get install -y curl git build-essential crossbuild-essential-armhf
|
||||
RUN apt-get install -y libasound2-dev libasound2-dev:armhf
|
||||
RUN apt-get install -y pkg-config
|
||||
|
||||
RUN curl https://sh.rustup.rs -sSf | sh -s -- -y
|
||||
ENV PATH="/root/.cargo/bin/:${PATH}"
|
||||
|
@ -35,9 +36,12 @@ RUN mkdir /.cargo && \
|
|||
RUN mkdir /build
|
||||
ENV CARGO_TARGET_DIR /build
|
||||
ENV CARGO_HOME /build/cache
|
||||
ENV PKG_CONFIG_PATH /usr/lib/arm-linux-gnueabihf/pkgconfig/
|
||||
ENV PKG_CONFIG_ALLOW_CROSS 1
|
||||
|
||||
ADD . /src
|
||||
WORKDIR /src
|
||||
|
||||
RUN cargo build --release --target arm-unknown-linux-gnueabihf --no-default-features --features "alsa-backend"
|
||||
|
||||
|
||||
|
|
|
@ -8,33 +8,36 @@ build = "build.rs"
|
|||
path = "../protocol"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.5.0"
|
||||
byteorder = "1.0"
|
||||
base64 = "0.10"
|
||||
byteorder = "1.3"
|
||||
bytes = "0.4"
|
||||
error-chain = { version = "0.11.0", default_features = false }
|
||||
extprim = "1.5.1"
|
||||
futures = "0.1.8"
|
||||
httparse = "1.2.4"
|
||||
hyper = "0.11.2"
|
||||
hyper-proxy = { version = "0.4.1", default_features = false }
|
||||
lazy_static = "0.2.0"
|
||||
log = "0.3.5"
|
||||
num-bigint = "0.1.35"
|
||||
num-integer = "0.1.32"
|
||||
num-traits = "0.1.36"
|
||||
protobuf = "2.0.5"
|
||||
rand = "0.6"
|
||||
rpassword = "0.3.0"
|
||||
rust-crypto = "0.2.36"
|
||||
serde = "0.9.6"
|
||||
serde_derive = "0.9.6"
|
||||
serde_json = "0.9.5"
|
||||
error-chain = { version = "0.12", default_features = false }
|
||||
extprim = "1.7"
|
||||
futures = "0.1"
|
||||
httparse = "1.3"
|
||||
hyper = "0.11"
|
||||
hyper-proxy = { version = "0.4", default_features = false }
|
||||
lazy_static = "1.3"
|
||||
log = "0.4"
|
||||
num-bigint = "0.2"
|
||||
num-integer = "0.1"
|
||||
num-traits = "0.2"
|
||||
protobuf = "2.8.*"
|
||||
rand = "0.7"
|
||||
serde = "1.0"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
shannon = "0.2.0"
|
||||
tokio-core = "0.1.2"
|
||||
tokio-codec = "0.1"
|
||||
tokio-core = "0.1"
|
||||
tokio-io = "0.1"
|
||||
url = "1.7.0"
|
||||
uuid = { version = "0.4", features = ["v4"] }
|
||||
url = "1.7"
|
||||
uuid = { version = "0.7", features = ["v4"] }
|
||||
sha-1 = "0.8"
|
||||
hmac = "0.7"
|
||||
pbkdf2 = "0.3"
|
||||
aes = "0.3"
|
||||
|
||||
[build-dependencies]
|
||||
rand = "0.6"
|
||||
vergen = "0.1.0"
|
||||
rand = "0.7"
|
||||
vergen = "3.0.4"
|
||||
|
|
|
@ -1,38 +1,19 @@
|
|||
extern crate rand;
|
||||
extern crate vergen;
|
||||
|
||||
use rand::Rng;
|
||||
use rand::distributions::Alphanumeric;
|
||||
use std::env;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
use rand::Rng;
|
||||
use vergen::{generate_cargo_keys, ConstantsFlags};
|
||||
|
||||
fn main() {
|
||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
|
||||
vergen::vergen(vergen::OutputFns::all()).unwrap();
|
||||
let mut flags = ConstantsFlags::all();
|
||||
flags.toggle(ConstantsFlags::REBUILD_ON_HEAD_CHANGE);
|
||||
generate_cargo_keys(ConstantsFlags::all()).expect("Unable to generate the cargo keys!");
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let build_id: String = ::std::iter::repeat(()).map(|()| rng.sample(Alphanumeric)).take(8).collect();
|
||||
|
||||
let mut version_file = OpenOptions::new()
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(&out.join("version.rs"))
|
||||
.unwrap();
|
||||
|
||||
let build_id_fn = format!(
|
||||
"
|
||||
/// Generate a random build id.
|
||||
pub fn build_id() -> &'static str {{
|
||||
\"{}\"
|
||||
}}
|
||||
",
|
||||
build_id
|
||||
);
|
||||
|
||||
if let Err(e) = version_file.write_all(build_id_fn.as_bytes()) {
|
||||
println!("{}", e);
|
||||
}
|
||||
let build_id: String = ::std::iter::repeat(())
|
||||
.map(|()| rng.sample(Alphanumeric))
|
||||
.take(8)
|
||||
.collect();
|
||||
println!("cargo:rustc-env=VERGEN_BUILD_ID={}", build_id);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use base64;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use crypto;
|
||||
use crypto::aes;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::pbkdf2::pbkdf2;
|
||||
use crypto::sha1::Sha1;
|
||||
use aes::Aes192;
|
||||
use hmac::Hmac;
|
||||
use sha1::{Sha1, Digest};
|
||||
use pbkdf2::pbkdf2;
|
||||
use protobuf::ProtobufEnum;
|
||||
use serde;
|
||||
use serde_json;
|
||||
|
@ -24,6 +22,7 @@ pub struct Credentials {
|
|||
#[serde(deserialize_with = "deserialize_protobuf_enum")]
|
||||
pub auth_type: AuthenticationType,
|
||||
|
||||
#[serde(alias = "encoded_auth_blob")]
|
||||
#[serde(serialize_with = "serialize_base64")]
|
||||
#[serde(deserialize_with = "deserialize_base64")]
|
||||
pub auth_data: Vec<u8>,
|
||||
|
@ -63,42 +62,34 @@ impl Credentials {
|
|||
Ok(data)
|
||||
}
|
||||
|
||||
let encrypted_blob = base64::decode(encrypted_blob).unwrap();
|
||||
|
||||
let secret = {
|
||||
let mut data = [0u8; 20];
|
||||
let mut h = crypto::sha1::Sha1::new();
|
||||
h.input(device_id.as_bytes());
|
||||
h.result(&mut data);
|
||||
data
|
||||
};
|
||||
let secret = Sha1::digest(device_id.as_bytes());
|
||||
|
||||
let key = {
|
||||
let mut data = [0u8; 24];
|
||||
let mut mac = Hmac::new(Sha1::new(), &secret);
|
||||
pbkdf2(&mut mac, username.as_bytes(), 0x100, &mut data[0..20]);
|
||||
let mut key = [0u8; 24];
|
||||
pbkdf2::<Hmac<Sha1>>(&secret, username.as_bytes(), 0x100, &mut key[0..20]);
|
||||
|
||||
let mut hash = Sha1::new();
|
||||
hash.input(&data[0..20]);
|
||||
hash.result(&mut data[0..20]);
|
||||
BigEndian::write_u32(&mut data[20..], 20);
|
||||
data
|
||||
let hash = &Sha1::digest(&key[..20]);
|
||||
key[..20].copy_from_slice(hash);
|
||||
BigEndian::write_u32(&mut key[20..], 20);
|
||||
key
|
||||
};
|
||||
|
||||
// decrypt data using ECB mode without padding
|
||||
let blob = {
|
||||
// Anyone know what this block mode is ?
|
||||
let mut data = vec![0u8; encrypted_blob.len()];
|
||||
let mut cipher =
|
||||
aes::ecb_decryptor(aes::KeySize::KeySize192, &key, crypto::blockmodes::NoPadding);
|
||||
cipher
|
||||
.decrypt(
|
||||
&mut crypto::buffer::RefReadBuffer::new(&encrypted_blob),
|
||||
&mut crypto::buffer::RefWriteBuffer::new(&mut data),
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
use aes::block_cipher_trait::BlockCipher;
|
||||
use aes::block_cipher_trait::generic_array::GenericArray;
|
||||
use aes::block_cipher_trait::generic_array::typenum::Unsigned;
|
||||
|
||||
let l = encrypted_blob.len();
|
||||
let mut data = base64::decode(encrypted_blob).unwrap();
|
||||
let cipher = Aes192::new(GenericArray::from_slice(&key));
|
||||
let block_size = <Aes192 as BlockCipher>::BlockSize::to_usize();
|
||||
assert_eq!(data.len() % block_size, 0);
|
||||
// replace to chunks_exact_mut with MSRV bump to 1.31
|
||||
for chunk in data.chunks_mut(block_size) {
|
||||
cipher.decrypt_block(GenericArray::from_mut_slice(chunk));
|
||||
}
|
||||
|
||||
let l = data.len();
|
||||
for i in 0..l - 0x10 {
|
||||
data[l - i - 1] ^= data[l - i - 0x11];
|
||||
}
|
||||
|
@ -152,10 +143,10 @@ where
|
|||
serde::Serialize::serialize(&v.value(), ser)
|
||||
}
|
||||
|
||||
fn deserialize_protobuf_enum<T, D>(de: D) -> Result<T, D::Error>
|
||||
fn deserialize_protobuf_enum<'de, T, D>(de: D) -> Result<T, D::Error>
|
||||
where
|
||||
T: ProtobufEnum,
|
||||
D: serde::Deserializer,
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v: i32 = try!(serde::Deserialize::deserialize(de));
|
||||
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
|
||||
|
@ -169,9 +160,9 @@ where
|
|||
serde::Serialize::serialize(&base64::encode(v.as_ref()), ser)
|
||||
}
|
||||
|
||||
fn deserialize_base64<D>(de: D) -> Result<Vec<u8>, D::Error>
|
||||
fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer,
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v: String = try!(serde::Deserialize::deserialize(de));
|
||||
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||
|
|
|
@ -15,7 +15,7 @@ pub struct SessionConfig {
|
|||
|
||||
impl Default for SessionConfig {
|
||||
fn default() -> SessionConfig {
|
||||
let device_id = Uuid::new_v4().hyphenated().to_string();
|
||||
let device_id = Uuid::new_v4().to_hyphenated().to_string();
|
||||
SessionConfig {
|
||||
user_agent: version::version_string(),
|
||||
device_id: device_id,
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||
use crypto::hmac::Hmac;
|
||||
use crypto::mac::Mac;
|
||||
use crypto::sha1::Sha1;
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha1::Sha1;
|
||||
use futures::{Async, Future, Poll};
|
||||
use protobuf::{self, Message};
|
||||
use rand::thread_rng;
|
||||
use std::io::{self, Read};
|
||||
use std::marker::PhantomData;
|
||||
use tokio_io::codec::Framed;
|
||||
use tokio_codec::{Decoder, Framed};
|
||||
use tokio_io::io::{read_exact, write_all, ReadExact, Window, WriteAll};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
|
@ -73,7 +72,7 @@ impl<T: AsyncRead + AsyncWrite> Future for Handshake<T> {
|
|||
ClientResponse(ref mut codec, ref mut write) => {
|
||||
let (connection, _) = try_ready!(write.poll());
|
||||
let codec = codec.take().unwrap();
|
||||
let framed = connection.framed(codec);
|
||||
let framed = codec.framed(connection);
|
||||
return Ok(Async::Ready(framed));
|
||||
}
|
||||
}
|
||||
|
@ -187,17 +186,19 @@ fn read_into_accumulator<T: AsyncRead>(
|
|||
}
|
||||
|
||||
fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
|
||||
let mut data = Vec::with_capacity(0x64);
|
||||
let mut mac = Hmac::new(Sha1::new(), &shared_secret);
|
||||
type HmacSha1 = Hmac<Sha1>;
|
||||
|
||||
let mut data = Vec::with_capacity(0x64);
|
||||
for i in 1..6 {
|
||||
let mut mac = HmacSha1::new_varkey(&shared_secret)
|
||||
.expect("HMAC can take key of any size");
|
||||
mac.input(packets);
|
||||
mac.input(&[i]);
|
||||
data.extend_from_slice(&mac.result().code());
|
||||
mac.reset();
|
||||
}
|
||||
|
||||
mac = Hmac::new(Sha1::new(), &data[..0x14]);
|
||||
let mut mac = HmacSha1::new_varkey(&data[..0x14])
|
||||
.expect("HMAC can take key of any size");;
|
||||
mac.input(packets);
|
||||
|
||||
(
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::io;
|
|||
use std::net::ToSocketAddrs;
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_core::reactor::Handle;
|
||||
use tokio_io::codec::Framed;
|
||||
use tokio_codec::Framed;
|
||||
use url::Url;
|
||||
|
||||
use authentication::Credentials;
|
||||
|
|
|
@ -14,7 +14,6 @@ extern crate serde_derive;
|
|||
extern crate base64;
|
||||
extern crate byteorder;
|
||||
extern crate bytes;
|
||||
extern crate crypto;
|
||||
extern crate extprim;
|
||||
extern crate httparse;
|
||||
extern crate hyper;
|
||||
|
@ -24,14 +23,18 @@ extern crate num_integer;
|
|||
extern crate num_traits;
|
||||
extern crate protobuf;
|
||||
extern crate rand;
|
||||
extern crate rpassword;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate shannon;
|
||||
extern crate tokio_codec;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate url;
|
||||
extern crate uuid;
|
||||
extern crate sha1;
|
||||
extern crate hmac;
|
||||
extern crate pbkdf2;
|
||||
extern crate aes;
|
||||
|
||||
extern crate librespot_protocol as protocol;
|
||||
|
||||
|
|
|
@ -1,24 +1,27 @@
|
|||
use bytes::Bytes;
|
||||
use futures::sync::mpsc;
|
||||
use futures::{Async, Future, IntoFuture, Poll, Stream};
|
||||
use std::io;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
|
||||
use std::sync::{Arc, RwLock, Weak};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use bytes::Bytes;
|
||||
use futures::{Async, Future, IntoFuture, Poll, Stream};
|
||||
use futures::sync::mpsc;
|
||||
use tokio_core::reactor::{Handle, Remote};
|
||||
|
||||
use apresolve::apresolve_or_fallback;
|
||||
use audio_key::AudioKeyManager;
|
||||
use authentication::Credentials;
|
||||
use cache::Cache;
|
||||
use channel::ChannelManager;
|
||||
use component::Lazy;
|
||||
use config::SessionConfig;
|
||||
use connection;
|
||||
|
||||
use audio_key::AudioKeyManager;
|
||||
use channel::ChannelManager;
|
||||
use mercury::MercuryManager;
|
||||
|
||||
struct SessionData {
|
||||
country: String,
|
||||
time_delta: i64,
|
||||
canonical_username: String,
|
||||
invalid: bool,
|
||||
}
|
||||
|
@ -39,7 +42,7 @@ struct SessionInternal {
|
|||
session_id: usize,
|
||||
}
|
||||
|
||||
static SESSION_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
|
||||
static SESSION_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Session(Arc<SessionInternal>);
|
||||
|
@ -108,6 +111,7 @@ impl Session {
|
|||
country: String::new(),
|
||||
canonical_username: username,
|
||||
invalid: false,
|
||||
time_delta: 0,
|
||||
}),
|
||||
|
||||
tx_connection: sender_tx,
|
||||
|
@ -146,6 +150,10 @@ impl Session {
|
|||
self.0.mercury.get(|| MercuryManager::new(self.weak()))
|
||||
}
|
||||
|
||||
pub fn time_delta(&self) -> i64 {
|
||||
self.0.data.read().unwrap().time_delta
|
||||
}
|
||||
|
||||
pub fn spawn<F, R>(&self, f: F)
|
||||
where
|
||||
F: FnOnce(&Handle) -> R + Send + 'static,
|
||||
|
@ -168,8 +176,17 @@ impl Session {
|
|||
fn dispatch(&self, cmd: u8, data: Bytes) {
|
||||
match cmd {
|
||||
0x4 => {
|
||||
let server_timestamp = BigEndian::read_u32(data.as_ref()) as i64;
|
||||
let timestamp = match SystemTime::now().duration_since(UNIX_EPOCH) {
|
||||
Ok(dur) => dur,
|
||||
Err(err) => err.duration(),
|
||||
}
|
||||
.as_secs() as i64;
|
||||
|
||||
self.0.data.write().unwrap().time_delta = server_timestamp - timestamp;
|
||||
|
||||
self.debug_info();
|
||||
self.send_packet(0x49, data.as_ref().to_owned());
|
||||
self.send_packet(0x49, vec![0, 0, 0, 0]);
|
||||
}
|
||||
0x4a => (),
|
||||
0x1b => {
|
||||
|
|
|
@ -1,5 +1,44 @@
|
|||
include!(concat!(env!("OUT_DIR"), "/version.rs"));
|
||||
|
||||
pub fn version_string() -> String {
|
||||
format!("librespot-{}", short_sha())
|
||||
}
|
||||
|
||||
// Generate a timestamp representing now (UTC) in RFC3339 format.
|
||||
pub fn now() -> &'static str {
|
||||
env!("VERGEN_BUILD_TIMESTAMP")
|
||||
}
|
||||
|
||||
// Generate a timstamp string representing now (UTC).
|
||||
pub fn short_now() -> &'static str {
|
||||
env!("VERGEN_BUILD_DATE")
|
||||
}
|
||||
|
||||
// Generate a SHA string
|
||||
pub fn sha() -> &'static str {
|
||||
env!("VERGEN_SHA")
|
||||
}
|
||||
|
||||
// Generate a short SHA string
|
||||
pub fn short_sha() -> &'static str {
|
||||
env!("VERGEN_SHA_SHORT")
|
||||
}
|
||||
|
||||
// Generate the commit date string
|
||||
pub fn commit_date() -> &'static str {
|
||||
env!("VERGEN_COMMIT_DATE")
|
||||
}
|
||||
|
||||
// Generate the target triple string
|
||||
pub fn target() -> &'static str {
|
||||
env!("VERGEN_TARGET_TRIPLE")
|
||||
}
|
||||
|
||||
// Generate a semver string
|
||||
pub fn semver() -> &'static str {
|
||||
// env!("VERGEN_SEMVER")
|
||||
env!("CARGO_PKG_VERSION")
|
||||
}
|
||||
|
||||
// Generate a random build id.
|
||||
pub fn build_id() -> &'static str {
|
||||
env!("VERGEN_BUILD_ID")
|
||||
}
|
||||
|
|
|
@ -4,10 +4,10 @@ version = "0.1.0"
|
|||
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.0"
|
||||
futures = "0.1.8"
|
||||
linear-map = "1.0"
|
||||
protobuf = "2.0.5"
|
||||
byteorder = "1.3"
|
||||
futures = "0.1"
|
||||
linear-map = "1.2"
|
||||
protobuf = "2.8.*"
|
||||
|
||||
[dependencies.librespot-core]
|
||||
path = "../core"
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use byteorder::{BigEndian, WriteBytesExt};
|
||||
use std::io::Write;
|
||||
|
||||
use core::channel::ChannelData;
|
||||
use core::session::Session;
|
||||
use core::spotify_id::FileId;
|
||||
use librespot_core::channel::ChannelData;
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::FileId;
|
||||
|
||||
pub fn get(session: &Session, file: FileId) -> ChannelData {
|
||||
let (channel_id, channel) = session.channel().allocate();
|
||||
|
|
|
@ -3,7 +3,7 @@ extern crate futures;
|
|||
extern crate linear_map;
|
||||
extern crate protobuf;
|
||||
|
||||
extern crate librespot_core as core;
|
||||
extern crate librespot_core;
|
||||
extern crate librespot_protocol as protocol;
|
||||
|
||||
pub mod cover;
|
||||
|
@ -11,9 +11,9 @@ pub mod cover;
|
|||
use futures::Future;
|
||||
use linear_map::LinearMap;
|
||||
|
||||
use core::mercury::MercuryError;
|
||||
use core::session::Session;
|
||||
use core::spotify_id::{FileId, SpotifyId};
|
||||
use librespot_core::mercury::MercuryError;
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::{FileId, SpotifyId};
|
||||
|
||||
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
||||
|
||||
|
|
|
@ -11,18 +11,23 @@ path = "../core"
|
|||
path = "../metadata"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1.8"
|
||||
log = "0.3.5"
|
||||
byteorder = "1.2.1"
|
||||
futures = "0.1"
|
||||
log = "0.4"
|
||||
byteorder = "1.3"
|
||||
|
||||
alsa = { git = "https://github.com/plietar/rust-alsa", optional = true }
|
||||
alsa = { version = "0.2.1", optional = true }
|
||||
portaudio-rs = { version = "0.3.0", optional = true }
|
||||
libpulse-sys = { version = "0.0.0", optional = true }
|
||||
jack = { version = "0.5.3", optional = true }
|
||||
libc = { version = "0.2", optional = true }
|
||||
rodio = { version = "0.9", optional = true, default-features = false }
|
||||
cpal = { version = "0.8", optional = true }
|
||||
sdl2 = { version = "0.32", optional = true }
|
||||
|
||||
[features]
|
||||
alsa-backend = ["alsa"]
|
||||
portaudio-backend = ["portaudio-rs"]
|
||||
pulseaudio-backend = ["libpulse-sys", "libc"]
|
||||
jackaudio-backend = ["jack"]
|
||||
rodio-backend = ["rodio", "cpal"]
|
||||
sdl-backend = ["sdl2"]
|
||||
|
|
|
@ -1,14 +1,70 @@
|
|||
use super::{Open, Sink};
|
||||
use alsa::{Access, Format, Mode, Stream, PCM};
|
||||
use alsa::device_name::HintIter;
|
||||
use alsa::pcm::{Access, Format, HwParams, PCM};
|
||||
use alsa::{Direction, Error, ValueOr};
|
||||
use std::ffi::CString;
|
||||
use std::io;
|
||||
use std::process::exit;
|
||||
|
||||
pub struct AlsaSink(Option<PCM>, String);
|
||||
|
||||
fn list_outputs() {
|
||||
for t in &["pcm", "ctl", "hwdep"] {
|
||||
println!("{} devices:", t);
|
||||
let i = HintIter::new(None, &*CString::new(*t).unwrap()).unwrap();
|
||||
for a in i {
|
||||
if let Some(Direction::Playback) = a.direction {
|
||||
// mimic aplay -L
|
||||
println!(
|
||||
"{}\n\t{}\n",
|
||||
a.name.unwrap(),
|
||||
a.desc.unwrap().replace("\n", "\n\t")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_device(dev_name: &str) -> Result<(PCM), Box<Error>> {
|
||||
let pcm = PCM::new(dev_name, Direction::Playback, false)?;
|
||||
// http://www.linuxjournal.com/article/6735?page=0,1#N0x19ab2890.0x19ba78d8
|
||||
// latency = period_size * periods / (rate * bytes_per_frame)
|
||||
// For 16 Bit stereo data, one frame has a length of four bytes.
|
||||
// 500ms = buffer_size / (44100 * 4)
|
||||
// buffer_size_bytes = 0.5 * 44100 / 4
|
||||
// buffer_size_frames = 0.5 * 44100 = 22050
|
||||
{
|
||||
// Set hardware parameters: 44100 Hz / Stereo / 16 bit
|
||||
let hwp = HwParams::any(&pcm)?;
|
||||
|
||||
hwp.set_access(Access::RWInterleaved)?;
|
||||
hwp.set_format(Format::s16())?;
|
||||
hwp.set_rate(44100, ValueOr::Nearest)?;
|
||||
hwp.set_channels(2)?;
|
||||
hwp.set_buffer_size_near(22050)?; // ~ 0.5s latency
|
||||
pcm.hw_params(&hwp)?;
|
||||
|
||||
let swp = pcm.sw_params_current()?;
|
||||
swp.set_start_threshold(hwp.get_buffer_size()? - hwp.get_period_size()?)?;
|
||||
pcm.sw_params(&swp)?;
|
||||
}
|
||||
|
||||
Ok(pcm)
|
||||
}
|
||||
|
||||
impl Open for AlsaSink {
|
||||
fn open(device: Option<String>) -> AlsaSink {
|
||||
info!("Using alsa sink");
|
||||
|
||||
let name = device.unwrap_or("default".to_string());
|
||||
let name = match device.as_ref().map(AsRef::as_ref) {
|
||||
Some("?") => {
|
||||
println!("Listing available alsa outputs");
|
||||
list_outputs();
|
||||
exit(0)
|
||||
}
|
||||
Some(device) => device,
|
||||
None => "default",
|
||||
}.to_string();
|
||||
|
||||
AlsaSink(None, name)
|
||||
}
|
||||
|
@ -17,16 +73,9 @@ impl Open for AlsaSink {
|
|||
impl Sink for AlsaSink {
|
||||
fn start(&mut self) -> io::Result<()> {
|
||||
if self.0.is_none() {
|
||||
match PCM::open(
|
||||
&*self.1,
|
||||
Stream::Playback,
|
||||
Mode::Blocking,
|
||||
Format::Signed16,
|
||||
Access::Interleaved,
|
||||
2,
|
||||
44100,
|
||||
) {
|
||||
Ok(f) => self.0 = Some(f),
|
||||
let pcm = open_device(&self.1);
|
||||
match pcm {
|
||||
Ok(p) => self.0 = Some(p),
|
||||
Err(e) => {
|
||||
error!("Alsa error PCM open {}", e);
|
||||
return Err(io::Error::new(
|
||||
|
@ -36,16 +85,28 @@ impl Sink for AlsaSink {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> io::Result<()> {
|
||||
{
|
||||
let pcm = self.0.as_ref().unwrap();
|
||||
pcm.drain().unwrap();
|
||||
}
|
||||
self.0 = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||
self.0.as_mut().unwrap().write_interleaved(&data).unwrap();
|
||||
let pcm = self.0.as_mut().unwrap();
|
||||
let io = pcm.io_i16().unwrap();
|
||||
|
||||
match io.writei(&data) {
|
||||
Ok(_) => (),
|
||||
Err(err) => pcm.try_recover(err, false).unwrap(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,6 +34,15 @@ mod jackaudio;
|
|||
#[cfg(feature = "jackaudio-backend")]
|
||||
use self::jackaudio::JackSink;
|
||||
|
||||
#[cfg(feature = "rodio-backend")]
|
||||
mod rodio;
|
||||
#[cfg(feature = "rodio-backend")]
|
||||
use self::rodio::RodioSink;
|
||||
#[cfg(feature = "sdl-backend")]
|
||||
mod sdl;
|
||||
#[cfg(feature = "sdl-backend")]
|
||||
use self::sdl::SdlSink;
|
||||
|
||||
mod pipe;
|
||||
use self::pipe::StdoutSink;
|
||||
|
||||
|
@ -46,6 +55,10 @@ pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] =
|
|||
("pulseaudio", mk_sink::<PulseAudioSink>),
|
||||
#[cfg(feature = "jackaudio-backend")]
|
||||
("jackaudio", mk_sink::<JackSink>),
|
||||
#[cfg(feature = "rodio-backend")]
|
||||
("rodio", mk_sink::<RodioSink>),
|
||||
#[cfg(feature = "sdl-backend")]
|
||||
("sdl", mk_sink::<SdlSink>),
|
||||
("pipe", mk_sink::<StdoutSink>),
|
||||
];
|
||||
|
||||
|
|
115
playback/src/audio_backend/rodio.rs
Normal file
115
playback/src/audio_backend/rodio.rs
Normal file
|
@ -0,0 +1,115 @@
|
|||
use super::{Open, Sink};
|
||||
extern crate rodio;
|
||||
extern crate cpal;
|
||||
use std::{io, thread, time};
|
||||
use std::process::exit;
|
||||
|
||||
pub struct RodioSink {
|
||||
rodio_sink: rodio::Sink,
|
||||
}
|
||||
|
||||
fn list_formats(ref device: &rodio::Device) {
|
||||
let default_fmt = match device.default_output_format() {
|
||||
Ok(fmt) => cpal::SupportedFormat::from(fmt),
|
||||
Err(e) => {
|
||||
warn!("Error getting default rodio::Sink format: {:?}", e);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
let mut output_formats = match device.supported_output_formats() {
|
||||
Ok(f) => f.peekable(),
|
||||
Err(e) => {
|
||||
warn!("Error getting supported rodio::Sink formats: {:?}", e);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if output_formats.peek().is_some() {
|
||||
debug!(" Available 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);
|
||||
if format == default_fmt {
|
||||
debug!(" (default) {}", s);
|
||||
} else {
|
||||
debug!(" {:?}", format);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn list_outputs() {
|
||||
let default_device = rodio::default_output_device().unwrap();
|
||||
println!("Default Audio Device:\n {}", default_device.name());
|
||||
list_formats(&default_device);
|
||||
|
||||
println!("Other Available Audio Devices:");
|
||||
for device in rodio::output_devices() {
|
||||
if device.name() != default_device.name() {
|
||||
println!(" {}", device.name());
|
||||
list_formats(&device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Open for RodioSink {
|
||||
fn open(device: Option<String>) -> RodioSink {
|
||||
debug!("Using rodio sink");
|
||||
|
||||
let mut rodio_device = rodio::default_output_device().expect("no output device available");
|
||||
if device.is_some() {
|
||||
let device_name = device.unwrap();
|
||||
|
||||
if device_name == "?".to_string() {
|
||||
list_outputs();
|
||||
exit(0)
|
||||
}
|
||||
let mut found = false;
|
||||
for d in rodio::output_devices() {
|
||||
if d.name() == device_name {
|
||||
rodio_device = d;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
println!("No output sink matching '{}' found.", device_name);
|
||||
exit(0)
|
||||
}
|
||||
}
|
||||
let sink = rodio::Sink::new(&rodio_device);
|
||||
|
||||
RodioSink {
|
||||
rodio_sink: sink,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for RodioSink {
|
||||
fn start(&mut self) -> io::Result<()> {
|
||||
// More similar to an "unpause" than "play". Doesn't undo "stop".
|
||||
// self.rodio_sink.play();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> io::Result<()> {
|
||||
// This will immediately stop playback, but the sink is then unusable.
|
||||
// We just have to let the current buffer play till the end.
|
||||
// self.rodio_sink.stop();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||
let source = rodio::buffer::SamplesBuffer::new(2, 44100, data);
|
||||
self.rodio_sink.append(source);
|
||||
|
||||
// Chunk sizes seem to be about 256 to 3000 ish items long.
|
||||
// Assuming they're on average 1628 then a half second buffer is:
|
||||
// 44100 elements --> about 27 chunks
|
||||
while self.rodio_sink.len() > 26 {
|
||||
// sleep and wait for rodio to drain a bit
|
||||
thread::sleep(time::Duration::from_millis(10));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
56
playback/src/audio_backend/sdl.rs
Normal file
56
playback/src/audio_backend/sdl.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use super::{Open, Sink};
|
||||
use sdl2::audio::{AudioQueue, AudioSpecDesired};
|
||||
use std::{io, thread, time};
|
||||
|
||||
type Channel = i16;
|
||||
|
||||
pub struct SdlSink {
|
||||
queue: AudioQueue<Channel>,
|
||||
}
|
||||
|
||||
impl Open for SdlSink {
|
||||
fn open(device: Option<String>) -> SdlSink {
|
||||
debug!("Using SDL sink");
|
||||
|
||||
if device.is_some() {
|
||||
panic!("SDL sink does not support specifying a device name");
|
||||
}
|
||||
|
||||
let ctx = sdl2::init().expect("Could not init SDL");
|
||||
let audio = ctx.audio().expect("Could not init SDL audio subsystem");
|
||||
|
||||
let desired_spec = AudioSpecDesired {
|
||||
freq: Some(44_100),
|
||||
channels: Some(2),
|
||||
samples: None,
|
||||
};
|
||||
let queue = audio
|
||||
.open_queue(None, &desired_spec)
|
||||
.expect("Could not open SDL audio device");
|
||||
|
||||
SdlSink { queue: queue }
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for SdlSink {
|
||||
fn start(&mut self) -> io::Result<()> {
|
||||
self.queue.clear();
|
||||
self.queue.resume();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> io::Result<()> {
|
||||
self.queue.pause();
|
||||
self.queue.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||
while self.queue.size() > (2 * 2 * 44_100) {
|
||||
// sleep and wait for sdl thread to drain the queue a bit
|
||||
thread::sleep(time::Duration::from_millis(10));
|
||||
}
|
||||
self.queue.queue(data);
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -16,11 +16,14 @@ extern crate libpulse_sys;
|
|||
#[cfg(feature = "jackaudio-backend")]
|
||||
extern crate jack;
|
||||
|
||||
#[cfg(feature = "sdl-backend")]
|
||||
extern crate sdl2;
|
||||
|
||||
#[cfg(feature = "libc")]
|
||||
extern crate libc;
|
||||
|
||||
extern crate librespot_audio as audio;
|
||||
extern crate librespot_core as core;
|
||||
extern crate librespot_core;
|
||||
extern crate librespot_metadata as metadata;
|
||||
|
||||
pub mod audio_backend;
|
||||
|
|
78
playback/src/mixer/alsamixer.rs
Normal file
78
playback/src/mixer/alsamixer.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use super::AudioFilter;
|
||||
use super::{Mixer, MixerConfig};
|
||||
use std::error::Error;
|
||||
|
||||
use alsa;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AlsaMixer {
|
||||
config: MixerConfig,
|
||||
}
|
||||
|
||||
impl AlsaMixer {
|
||||
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
|
||||
let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
|
||||
let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index);
|
||||
|
||||
let selem = mixer
|
||||
.find_selem(&sid)
|
||||
.expect(format!("Couldn't find simple mixer control for {}", self.config.mixer).as_str());
|
||||
let (min, max) = selem.get_playback_volume_range();
|
||||
let range = (max - min) as f64;
|
||||
|
||||
let new_vol: u16;
|
||||
|
||||
if let Some(vol) = set_volume {
|
||||
let alsa_volume: i64 = ((vol as f64 / 0xFFFF as f64) * range) as i64 + min;
|
||||
debug!("Mapping volume {:?} ->> alsa {:?}", vol, alsa_volume);
|
||||
selem
|
||||
.set_playback_volume_all(alsa_volume)
|
||||
.expect("Couldn't set alsa volume");
|
||||
new_vol = vol;
|
||||
} else {
|
||||
let cur_vol = selem
|
||||
.get_playback_volume(alsa::mixer::SelemChannelId::mono())
|
||||
.expect("Couldn't get current volume");
|
||||
new_vol = (((cur_vol - min) as f64 / range) * 0xFFFF as f64) as u16;
|
||||
debug!("Mapping volume {:?} <<- alsa {:?}", new_vol, cur_vol);
|
||||
}
|
||||
|
||||
Ok(new_vol)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mixer for AlsaMixer {
|
||||
fn open(config: Option<MixerConfig>) -> AlsaMixer {
|
||||
let config = config.unwrap_or_default();
|
||||
info!(
|
||||
"Setting up new mixer: card:{} mixer:{} index:{}",
|
||||
config.card, config.mixer, config.index
|
||||
);
|
||||
AlsaMixer { config: config }
|
||||
}
|
||||
|
||||
fn start(&self) {}
|
||||
|
||||
fn stop(&self) {}
|
||||
|
||||
fn volume(&self) -> u16 {
|
||||
match self.map_volume(None) {
|
||||
Ok(vol) => vol,
|
||||
Err(e) => {
|
||||
error!("Error getting volume for <{}>, {:?}", self.config.card, e);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_volume(&self, volume: u16) {
|
||||
match self.map_volume(Some(volume)) {
|
||||
Ok(_) => (),
|
||||
Err(e) => error!("Error setting volume for <{}>, {:?}", self.config.card, e),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
||||
None
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
pub trait Mixer: Send {
|
||||
fn open() -> Self
|
||||
fn open(Option<MixerConfig>) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn start(&self);
|
||||
|
@ -15,16 +15,39 @@ pub trait AudioFilter {
|
|||
fn modify_stream(&self, data: &mut [i16]);
|
||||
}
|
||||
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
pub mod alsamixer;
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
use self::alsamixer::AlsaMixer;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MixerConfig {
|
||||
pub card: String,
|
||||
pub mixer: String,
|
||||
pub index: u32,
|
||||
}
|
||||
|
||||
impl Default for MixerConfig {
|
||||
fn default() -> MixerConfig { MixerConfig {
|
||||
card: String::from("default"),
|
||||
mixer: String::from("PCM"),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod softmixer;
|
||||
use self::softmixer::SoftMixer;
|
||||
|
||||
fn mk_sink<M: Mixer + 'static>() -> Box<Mixer> {
|
||||
Box::new(M::open())
|
||||
fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<Mixer> {
|
||||
Box::new(M::open(device))
|
||||
}
|
||||
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn() -> Box<Mixer>> {
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<Mixer>> {
|
||||
match name.as_ref().map(AsRef::as_ref) {
|
||||
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
Some("alsa") => Some(mk_sink::<AlsaMixer>),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::sync::atomic::{AtomicUsize, Ordering};
|
|||
use std::sync::Arc;
|
||||
|
||||
use super::AudioFilter;
|
||||
use super::Mixer;
|
||||
use super::{Mixer, MixerConfig};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SoftMixer {
|
||||
|
@ -10,7 +10,7 @@ pub struct SoftMixer {
|
|||
}
|
||||
|
||||
impl Mixer for SoftMixer {
|
||||
fn open() -> SoftMixer {
|
||||
fn open(_: Option<MixerConfig>) -> SoftMixer {
|
||||
SoftMixer {
|
||||
volume: Arc::new(AtomicUsize::new(0xFFFF)),
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ use std::thread;
|
|||
use std::time::Duration;
|
||||
|
||||
use config::{Bitrate, PlayerConfig};
|
||||
use core::session::Session;
|
||||
use core::spotify_id::SpotifyId;
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::SpotifyId;
|
||||
|
||||
use audio::{AudioDecrypt, AudioFile, StreamLoaderController};
|
||||
use audio::{VorbisDecoder, VorbisPacket};
|
||||
|
|
|
@ -5,7 +5,8 @@ authors = ["Paul Liétar <paul@lietar.net>"]
|
|||
build = "build.rs"
|
||||
|
||||
[dependencies]
|
||||
protobuf = "2.0.5"
|
||||
protobuf = "2.8.*"
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen-pure = "2.0.5"
|
||||
protobuf-codegen-pure = "2.8.*"
|
||||
protobuf-codegen = "*"
|
||||
|
|
|
@ -1,93 +1,54 @@
|
|||
extern crate protobuf_codegen_pure;
|
||||
use protobuf_codegen_pure::Customize;
|
||||
use std::fs::File;
|
||||
use std::io::prelude::*;
|
||||
extern crate protobuf_codegen; // Does the business
|
||||
extern crate protobuf_codegen_pure; // Helper function
|
||||
|
||||
mod files;
|
||||
use std::path::Path;
|
||||
use std::fs::{read_to_string, write};
|
||||
|
||||
use protobuf_codegen_pure::Customize;
|
||||
use protobuf_codegen_pure::parse_and_typecheck;
|
||||
|
||||
fn main() {
|
||||
let mut changed = false;
|
||||
let mut file = File::open("files.rs").unwrap();
|
||||
let mut f_str = String::new();
|
||||
file.read_to_string(&mut f_str).unwrap();
|
||||
drop(file);
|
||||
for &(path, expected_checksum) in files::FILES {
|
||||
let actual = cksum_file(path).unwrap();
|
||||
if expected_checksum != actual {
|
||||
protobuf_codegen_pure::run(protobuf_codegen_pure::Args {
|
||||
out_dir: "src",
|
||||
input: &[path],
|
||||
includes: &["proto"],
|
||||
customize: Customize { ..Default::default() },
|
||||
}).expect("protoc");
|
||||
let new_checksum = cksum_file(path).unwrap();
|
||||
f_str = f_str.replace(&expected_checksum.to_string(), &new_checksum.to_string());
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
// Write new checksums to file
|
||||
let mut file = File::create("files.rs").unwrap();
|
||||
file.write_all(f_str.as_bytes()).unwrap();
|
||||
let customizations = Customize { ..Default::default() };
|
||||
|
||||
let lib_str = read_to_string("src/lib.rs").unwrap();
|
||||
|
||||
// Iterate over the desired module names.
|
||||
for line in lib_str.lines() {
|
||||
if !line.starts_with("pub mod ") {
|
||||
continue;
|
||||
}
|
||||
let len = line.len();
|
||||
let name = &line[8..len-1]; // Remove keywords and semi-colon
|
||||
|
||||
// Build the paths to relevant files.
|
||||
let src = &format!("proto/{}.proto", name);
|
||||
let dest = &format!("src/{}.rs", name);
|
||||
|
||||
// Get the contents of the existing generated file.
|
||||
let mut existing = "".to_string();
|
||||
if Path::new(dest).exists() {
|
||||
// Removing CRLF line endings if present.
|
||||
existing = read_to_string(dest).unwrap().replace("\r\n", "\n");
|
||||
}
|
||||
|
||||
fn cksum_file<T: AsRef<std::path::Path>>(path: T) -> std::io::Result<u32> {
|
||||
let mut file = File::open(path)?;
|
||||
let mut contents = Vec::new();
|
||||
file.read_to_end(&mut contents)?;
|
||||
println!("Regenerating {} from {}", dest, src);
|
||||
|
||||
Ok(cksum(&contents))
|
||||
// Parse the proto files as the protobuf-codegen-pure crate does.
|
||||
let p = parse_and_typecheck(&["proto"], &[src]).expect("protoc");
|
||||
// But generate them with the protobuf-codegen crate directly.
|
||||
// Then we can keep the result in-memory.
|
||||
let result = protobuf_codegen::gen(
|
||||
&p.file_descriptors,
|
||||
&p.relative_paths,
|
||||
&customizations,
|
||||
);
|
||||
// Protoc result as a byte array.
|
||||
let new = &result.first().unwrap().content;
|
||||
// Convert to utf8 to compare with existing.
|
||||
let new = std::str::from_utf8(&new).unwrap();
|
||||
// Save newly generated file if changed.
|
||||
if new != existing {
|
||||
write(dest, &new).unwrap();
|
||||
}
|
||||
|
||||
fn cksum<T: AsRef<[u8]>>(data: T) -> u32 {
|
||||
let data = data.as_ref();
|
||||
|
||||
let mut value = 0u32;
|
||||
for x in data {
|
||||
value = (value << 8) ^ CRC_LOOKUP_ARRAY[(*x as u32 ^ (value >> 24)) as usize];
|
||||
}
|
||||
|
||||
let mut n = data.len();
|
||||
while n != 0 {
|
||||
value = (value << 8) ^ CRC_LOOKUP_ARRAY[((n & 0xFF) as u32 ^ (value >> 24)) as usize];
|
||||
n >>= 8;
|
||||
}
|
||||
|
||||
!value
|
||||
}
|
||||
|
||||
static CRC_LOOKUP_ARRAY: &'static [u32] = &[
|
||||
0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 0x1a864db2, 0x1e475005,
|
||||
0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd,
|
||||
0x4c11db70, 0x48d0c6c7, 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75,
|
||||
0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 0x709f7b7a, 0x745e66cd,
|
||||
0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5,
|
||||
0xbe2b5b58, 0xbaea46ef, 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d,
|
||||
0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 0xceb42022, 0xca753d95,
|
||||
0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d,
|
||||
0x34867077, 0x30476dc0, 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072,
|
||||
0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 0x0808d07d, 0x0cc9cdca,
|
||||
0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02,
|
||||
0x5e9f46bf, 0x5a5e5b08, 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba,
|
||||
0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 0xb6238b25, 0xb2e29692,
|
||||
0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a,
|
||||
0xe0b41de7, 0xe4750050, 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2,
|
||||
0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 0xdc3abded, 0xd8fba05a,
|
||||
0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb,
|
||||
0x4f040d56, 0x4bc510e1, 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53,
|
||||
0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 0x3f9b762c, 0x3b5a6b9b,
|
||||
0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623,
|
||||
0xf12f560e, 0xf5ee4bb9, 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b,
|
||||
0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 0xcda1f604, 0xc960ebb3,
|
||||
0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b,
|
||||
0x9b3660c6, 0x9ff77d71, 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3,
|
||||
0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 0x470cdd2b, 0x43cdc09c,
|
||||
0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24,
|
||||
0x119b4be9, 0x155a565e, 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec,
|
||||
0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 0x2d15ebe3, 0x29d4f654,
|
||||
0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c,
|
||||
0xe3a1cbc1, 0xe760d676, 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4,
|
||||
0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 0x933eb0bb, 0x97ffad0c,
|
||||
0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4,
|
||||
];
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
// Autogenerated by build.rs
|
||||
|
||||
pub const FILES: &'static [(&'static str, u32)] = &[
|
||||
("proto/authentication.proto", 2098196376),
|
||||
("proto/keyexchange.proto", 451735664),
|
||||
("proto/mercury.proto", 709993906),
|
||||
("proto/metadata.proto", 2474472423),
|
||||
("proto/pubsub.proto", 2686584829),
|
||||
("proto/spirc.proto", 1587493382),
|
||||
];
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
// Autogenerated by build.sh
|
||||
|
||||
extern crate protobuf;
|
||||
// This file is parsed by build.rs
|
||||
// Each included module will be compiled from the matching .proto definition.
|
||||
pub mod authentication;
|
||||
pub mod keyexchange;
|
||||
pub mod mercury;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
// This file is generated by rust-protobuf 2.0.5. Do not edit
|
||||
// This file is generated by rust-protobuf 2.8.0. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/Manishearth/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
|
@ -17,17 +17,28 @@
|
|||
#![allow(unsafe_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `mercury.proto`
|
||||
|
||||
use protobuf::Message as Message_imported_for_functions;
|
||||
use protobuf::ProtobufEnum as ProtobufEnum_imported_for_functions;
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_8_0;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct MercuryMultiGetRequest {
|
||||
// message fields
|
||||
request: ::protobuf::RepeatedField<MercuryRequest>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a MercuryMultiGetRequest {
|
||||
fn default() -> &'a MercuryMultiGetRequest {
|
||||
<MercuryMultiGetRequest as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl MercuryMultiGetRequest {
|
||||
|
@ -37,6 +48,10 @@ impl MercuryMultiGetRequest {
|
|||
|
||||
// repeated .MercuryRequest request = 1;
|
||||
|
||||
|
||||
pub fn get_request(&self) -> &[MercuryRequest] {
|
||||
&self.request
|
||||
}
|
||||
pub fn clear_request(&mut self) {
|
||||
self.request.clear();
|
||||
}
|
||||
|
@ -55,10 +70,6 @@ impl MercuryMultiGetRequest {
|
|||
pub fn take_request(&mut self) -> ::protobuf::RepeatedField<MercuryRequest> {
|
||||
::std::mem::replace(&mut self.request, ::protobuf::RepeatedField::new())
|
||||
}
|
||||
|
||||
pub fn get_request(&self) -> &[MercuryRequest] {
|
||||
&self.request
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for MercuryMultiGetRequest {
|
||||
|
@ -121,13 +132,13 @@ impl ::protobuf::Message for MercuryMultiGetRequest {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -174,7 +185,7 @@ impl ::protobuf::Message for MercuryMultiGetRequest {
|
|||
|
||||
impl ::protobuf::Clear for MercuryMultiGetRequest {
|
||||
fn clear(&mut self) {
|
||||
self.clear_request();
|
||||
self.request.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -196,8 +207,14 @@ pub struct MercuryMultiGetReply {
|
|||
// message fields
|
||||
reply: ::protobuf::RepeatedField<MercuryReply>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a MercuryMultiGetReply {
|
||||
fn default() -> &'a MercuryMultiGetReply {
|
||||
<MercuryMultiGetReply as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl MercuryMultiGetReply {
|
||||
|
@ -207,6 +224,10 @@ impl MercuryMultiGetReply {
|
|||
|
||||
// repeated .MercuryReply reply = 1;
|
||||
|
||||
|
||||
pub fn get_reply(&self) -> &[MercuryReply] {
|
||||
&self.reply
|
||||
}
|
||||
pub fn clear_reply(&mut self) {
|
||||
self.reply.clear();
|
||||
}
|
||||
|
@ -225,10 +246,6 @@ impl MercuryMultiGetReply {
|
|||
pub fn take_reply(&mut self) -> ::protobuf::RepeatedField<MercuryReply> {
|
||||
::std::mem::replace(&mut self.reply, ::protobuf::RepeatedField::new())
|
||||
}
|
||||
|
||||
pub fn get_reply(&self) -> &[MercuryReply] {
|
||||
&self.reply
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for MercuryMultiGetReply {
|
||||
|
@ -291,13 +308,13 @@ impl ::protobuf::Message for MercuryMultiGetReply {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -344,7 +361,7 @@ impl ::protobuf::Message for MercuryMultiGetReply {
|
|||
|
||||
impl ::protobuf::Clear for MercuryMultiGetReply {
|
||||
fn clear(&mut self) {
|
||||
self.clear_reply();
|
||||
self.reply.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -369,8 +386,14 @@ pub struct MercuryRequest {
|
|||
body: ::protobuf::SingularField<::std::vec::Vec<u8>>,
|
||||
etag: ::protobuf::SingularField<::std::vec::Vec<u8>>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a MercuryRequest {
|
||||
fn default() -> &'a MercuryRequest {
|
||||
<MercuryRequest as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl MercuryRequest {
|
||||
|
@ -380,6 +403,13 @@ impl MercuryRequest {
|
|||
|
||||
// optional string uri = 1;
|
||||
|
||||
|
||||
pub fn get_uri(&self) -> &str {
|
||||
match self.uri.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn clear_uri(&mut self) {
|
||||
self.uri.clear();
|
||||
}
|
||||
|
@ -407,15 +437,15 @@ impl MercuryRequest {
|
|||
self.uri.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_uri(&self) -> &str {
|
||||
match self.uri.as_ref() {
|
||||
// optional string content_type = 2;
|
||||
|
||||
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self.content_type.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional string content_type = 2;
|
||||
|
||||
pub fn clear_content_type(&mut self) {
|
||||
self.content_type.clear();
|
||||
}
|
||||
|
@ -443,15 +473,15 @@ impl MercuryRequest {
|
|||
self.content_type.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self.content_type.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional bytes body = 3;
|
||||
|
||||
|
||||
pub fn get_body(&self) -> &[u8] {
|
||||
match self.body.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
pub fn clear_body(&mut self) {
|
||||
self.body.clear();
|
||||
}
|
||||
|
@ -479,15 +509,15 @@ impl MercuryRequest {
|
|||
self.body.take().unwrap_or_else(|| ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
pub fn get_body(&self) -> &[u8] {
|
||||
match self.body.as_ref() {
|
||||
// optional bytes etag = 4;
|
||||
|
||||
|
||||
pub fn get_etag(&self) -> &[u8] {
|
||||
match self.etag.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
// optional bytes etag = 4;
|
||||
|
||||
pub fn clear_etag(&mut self) {
|
||||
self.etag.clear();
|
||||
}
|
||||
|
@ -514,13 +544,6 @@ impl MercuryRequest {
|
|||
pub fn take_etag(&mut self) -> ::std::vec::Vec<u8> {
|
||||
self.etag.take().unwrap_or_else(|| ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
pub fn get_etag(&self) -> &[u8] {
|
||||
match self.etag.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for MercuryRequest {
|
||||
|
@ -602,13 +625,13 @@ impl ::protobuf::Message for MercuryRequest {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -670,10 +693,10 @@ impl ::protobuf::Message for MercuryRequest {
|
|||
|
||||
impl ::protobuf::Clear for MercuryRequest {
|
||||
fn clear(&mut self) {
|
||||
self.clear_uri();
|
||||
self.clear_content_type();
|
||||
self.clear_body();
|
||||
self.clear_etag();
|
||||
self.uri.clear();
|
||||
self.content_type.clear();
|
||||
self.body.clear();
|
||||
self.etag.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -701,8 +724,14 @@ pub struct MercuryReply {
|
|||
content_type: ::protobuf::SingularField<::std::string::String>,
|
||||
body: ::protobuf::SingularField<::std::vec::Vec<u8>>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a MercuryReply {
|
||||
fn default() -> &'a MercuryReply {
|
||||
<MercuryReply as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl MercuryReply {
|
||||
|
@ -712,6 +741,10 @@ impl MercuryReply {
|
|||
|
||||
// optional sint32 status_code = 1;
|
||||
|
||||
|
||||
pub fn get_status_code(&self) -> i32 {
|
||||
self.status_code.unwrap_or(0)
|
||||
}
|
||||
pub fn clear_status_code(&mut self) {
|
||||
self.status_code = ::std::option::Option::None;
|
||||
}
|
||||
|
@ -725,12 +758,15 @@ impl MercuryReply {
|
|||
self.status_code = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
pub fn get_status_code(&self) -> i32 {
|
||||
self.status_code.unwrap_or(0)
|
||||
}
|
||||
|
||||
// optional string status_message = 2;
|
||||
|
||||
|
||||
pub fn get_status_message(&self) -> &str {
|
||||
match self.status_message.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn clear_status_message(&mut self) {
|
||||
self.status_message.clear();
|
||||
}
|
||||
|
@ -758,15 +794,12 @@ impl MercuryReply {
|
|||
self.status_message.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_status_message(&self) -> &str {
|
||||
match self.status_message.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional .MercuryReply.CachePolicy cache_policy = 3;
|
||||
|
||||
|
||||
pub fn get_cache_policy(&self) -> MercuryReply_CachePolicy {
|
||||
self.cache_policy.unwrap_or(MercuryReply_CachePolicy::CACHE_NO)
|
||||
}
|
||||
pub fn clear_cache_policy(&mut self) {
|
||||
self.cache_policy = ::std::option::Option::None;
|
||||
}
|
||||
|
@ -780,12 +813,12 @@ impl MercuryReply {
|
|||
self.cache_policy = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
pub fn get_cache_policy(&self) -> MercuryReply_CachePolicy {
|
||||
self.cache_policy.unwrap_or(MercuryReply_CachePolicy::CACHE_NO)
|
||||
}
|
||||
|
||||
// optional sint32 ttl = 4;
|
||||
|
||||
|
||||
pub fn get_ttl(&self) -> i32 {
|
||||
self.ttl.unwrap_or(0)
|
||||
}
|
||||
pub fn clear_ttl(&mut self) {
|
||||
self.ttl = ::std::option::Option::None;
|
||||
}
|
||||
|
@ -799,12 +832,15 @@ impl MercuryReply {
|
|||
self.ttl = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
pub fn get_ttl(&self) -> i32 {
|
||||
self.ttl.unwrap_or(0)
|
||||
}
|
||||
|
||||
// optional bytes etag = 5;
|
||||
|
||||
|
||||
pub fn get_etag(&self) -> &[u8] {
|
||||
match self.etag.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
pub fn clear_etag(&mut self) {
|
||||
self.etag.clear();
|
||||
}
|
||||
|
@ -832,15 +868,15 @@ impl MercuryReply {
|
|||
self.etag.take().unwrap_or_else(|| ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
pub fn get_etag(&self) -> &[u8] {
|
||||
match self.etag.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
|
||||
// optional string content_type = 6;
|
||||
|
||||
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self.content_type.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn clear_content_type(&mut self) {
|
||||
self.content_type.clear();
|
||||
}
|
||||
|
@ -868,15 +904,15 @@ impl MercuryReply {
|
|||
self.content_type.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self.content_type.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional bytes body = 7;
|
||||
|
||||
|
||||
pub fn get_body(&self) -> &[u8] {
|
||||
match self.body.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
pub fn clear_body(&mut self) {
|
||||
self.body.clear();
|
||||
}
|
||||
|
@ -903,13 +939,6 @@ impl MercuryReply {
|
|||
pub fn take_body(&mut self) -> ::std::vec::Vec<u8> {
|
||||
self.body.take().unwrap_or_else(|| ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
pub fn get_body(&self) -> &[u8] {
|
||||
match self.body.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for MercuryReply {
|
||||
|
@ -1026,13 +1055,13 @@ impl ::protobuf::Message for MercuryReply {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1109,13 +1138,13 @@ impl ::protobuf::Message for MercuryReply {
|
|||
|
||||
impl ::protobuf::Clear for MercuryReply {
|
||||
fn clear(&mut self) {
|
||||
self.clear_status_code();
|
||||
self.clear_status_message();
|
||||
self.clear_cache_policy();
|
||||
self.clear_ttl();
|
||||
self.clear_etag();
|
||||
self.clear_content_type();
|
||||
self.clear_body();
|
||||
self.status_code = ::std::option::Option::None;
|
||||
self.status_message.clear();
|
||||
self.cache_policy = ::std::option::Option::None;
|
||||
self.ttl = ::std::option::Option::None;
|
||||
self.etag.clear();
|
||||
self.content_type.clear();
|
||||
self.body.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1178,6 +1207,13 @@ impl ::protobuf::ProtobufEnum for MercuryReply_CachePolicy {
|
|||
impl ::std::marker::Copy for MercuryReply_CachePolicy {
|
||||
}
|
||||
|
||||
// Note, `Default` is implemented although default value is not 0
|
||||
impl ::std::default::Default for MercuryReply_CachePolicy {
|
||||
fn default() -> Self {
|
||||
MercuryReply_CachePolicy::CACHE_NO
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::reflect::ProtobufValue for MercuryReply_CachePolicy {
|
||||
fn as_ref(&self) -> ::protobuf::reflect::ProtobufValueRef {
|
||||
::protobuf::reflect::ProtobufValueRef::Enum(self.descriptor())
|
||||
|
@ -1193,8 +1229,14 @@ pub struct Header {
|
|||
status_code: ::std::option::Option<i32>,
|
||||
user_fields: ::protobuf::RepeatedField<UserField>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a Header {
|
||||
fn default() -> &'a Header {
|
||||
<Header as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl Header {
|
||||
|
@ -1204,6 +1246,13 @@ impl Header {
|
|||
|
||||
// optional string uri = 1;
|
||||
|
||||
|
||||
pub fn get_uri(&self) -> &str {
|
||||
match self.uri.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn clear_uri(&mut self) {
|
||||
self.uri.clear();
|
||||
}
|
||||
|
@ -1231,15 +1280,15 @@ impl Header {
|
|||
self.uri.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_uri(&self) -> &str {
|
||||
match self.uri.as_ref() {
|
||||
// optional string content_type = 2;
|
||||
|
||||
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self.content_type.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional string content_type = 2;
|
||||
|
||||
pub fn clear_content_type(&mut self) {
|
||||
self.content_type.clear();
|
||||
}
|
||||
|
@ -1267,15 +1316,15 @@ impl Header {
|
|||
self.content_type.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_content_type(&self) -> &str {
|
||||
match self.content_type.as_ref() {
|
||||
// optional string method = 3;
|
||||
|
||||
|
||||
pub fn get_method(&self) -> &str {
|
||||
match self.method.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional string method = 3;
|
||||
|
||||
pub fn clear_method(&mut self) {
|
||||
self.method.clear();
|
||||
}
|
||||
|
@ -1303,15 +1352,12 @@ impl Header {
|
|||
self.method.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_method(&self) -> &str {
|
||||
match self.method.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional sint32 status_code = 4;
|
||||
|
||||
|
||||
pub fn get_status_code(&self) -> i32 {
|
||||
self.status_code.unwrap_or(0)
|
||||
}
|
||||
pub fn clear_status_code(&mut self) {
|
||||
self.status_code = ::std::option::Option::None;
|
||||
}
|
||||
|
@ -1325,12 +1371,12 @@ impl Header {
|
|||
self.status_code = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
pub fn get_status_code(&self) -> i32 {
|
||||
self.status_code.unwrap_or(0)
|
||||
}
|
||||
|
||||
// repeated .UserField user_fields = 6;
|
||||
|
||||
|
||||
pub fn get_user_fields(&self) -> &[UserField] {
|
||||
&self.user_fields
|
||||
}
|
||||
pub fn clear_user_fields(&mut self) {
|
||||
self.user_fields.clear();
|
||||
}
|
||||
|
@ -1349,10 +1395,6 @@ impl Header {
|
|||
pub fn take_user_fields(&mut self) -> ::protobuf::RepeatedField<UserField> {
|
||||
::std::mem::replace(&mut self.user_fields, ::protobuf::RepeatedField::new())
|
||||
}
|
||||
|
||||
pub fn get_user_fields(&self) -> &[UserField] {
|
||||
&self.user_fields
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for Header {
|
||||
|
@ -1455,13 +1497,13 @@ impl ::protobuf::Message for Header {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1528,11 +1570,11 @@ impl ::protobuf::Message for Header {
|
|||
|
||||
impl ::protobuf::Clear for Header {
|
||||
fn clear(&mut self) {
|
||||
self.clear_uri();
|
||||
self.clear_content_type();
|
||||
self.clear_method();
|
||||
self.clear_status_code();
|
||||
self.clear_user_fields();
|
||||
self.uri.clear();
|
||||
self.content_type.clear();
|
||||
self.method.clear();
|
||||
self.status_code = ::std::option::Option::None;
|
||||
self.user_fields.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1555,8 +1597,14 @@ pub struct UserField {
|
|||
key: ::protobuf::SingularField<::std::string::String>,
|
||||
value: ::protobuf::SingularField<::std::vec::Vec<u8>>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a UserField {
|
||||
fn default() -> &'a UserField {
|
||||
<UserField as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl UserField {
|
||||
|
@ -1566,6 +1614,13 @@ impl UserField {
|
|||
|
||||
// optional string key = 1;
|
||||
|
||||
|
||||
pub fn get_key(&self) -> &str {
|
||||
match self.key.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn clear_key(&mut self) {
|
||||
self.key.clear();
|
||||
}
|
||||
|
@ -1593,15 +1648,15 @@ impl UserField {
|
|||
self.key.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_key(&self) -> &str {
|
||||
match self.key.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional bytes value = 2;
|
||||
|
||||
|
||||
pub fn get_value(&self) -> &[u8] {
|
||||
match self.value.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
pub fn clear_value(&mut self) {
|
||||
self.value.clear();
|
||||
}
|
||||
|
@ -1628,13 +1683,6 @@ impl UserField {
|
|||
pub fn take_value(&mut self) -> ::std::vec::Vec<u8> {
|
||||
self.value.take().unwrap_or_else(|| ::std::vec::Vec::new())
|
||||
}
|
||||
|
||||
pub fn get_value(&self) -> &[u8] {
|
||||
match self.value.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => &[],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for UserField {
|
||||
|
@ -1698,13 +1746,13 @@ impl ::protobuf::Message for UserField {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -1756,8 +1804,8 @@ impl ::protobuf::Message for UserField {
|
|||
|
||||
impl ::protobuf::Clear for UserField {
|
||||
fn clear(&mut self) {
|
||||
self.clear_key();
|
||||
self.clear_value();
|
||||
self.key.clear();
|
||||
self.value.clear();
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -1775,27 +1823,24 @@ impl ::protobuf::reflect::ProtobufValue for UserField {
|
|||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\rmercury.proto\x12\0\">\n\x16MercuryMultiGetRequest\x12$\n\x07request\
|
||||
\x18\x01\x20\x03(\x0b2\x0f.MercuryRequestB\x02\x18\0\"8\n\x14MercuryMult\
|
||||
iGetReply\x12\x20\n\x05reply\x18\x01\x20\x03(\x0b2\r.MercuryReplyB\x02\
|
||||
\x18\0\"_\n\x0eMercuryRequest\x12\x0f\n\x03uri\x18\x01\x20\x01(\tB\x02\
|
||||
\x18\0\x12\x18\n\x0ccontent_type\x18\x02\x20\x01(\tB\x02\x18\0\x12\x10\n\
|
||||
\x04body\x18\x03\x20\x01(\x0cB\x02\x18\0\x12\x10\n\x04etag\x18\x04\x20\
|
||||
\x01(\x0cB\x02\x18\0\"\x8d\x02\n\x0cMercuryReply\x12\x17\n\x0bstatus_cod\
|
||||
e\x18\x01\x20\x01(\x11B\x02\x18\0\x12\x1a\n\x0estatus_message\x18\x02\
|
||||
\x20\x01(\tB\x02\x18\0\x123\n\x0ccache_policy\x18\x03\x20\x01(\x0e2\x19.\
|
||||
MercuryReply.CachePolicyB\x02\x18\0\x12\x0f\n\x03ttl\x18\x04\x20\x01(\
|
||||
\x11B\x02\x18\0\x12\x10\n\x04etag\x18\x05\x20\x01(\x0cB\x02\x18\0\x12\
|
||||
\x18\n\x0ccontent_type\x18\x06\x20\x01(\tB\x02\x18\0\x12\x10\n\x04body\
|
||||
\x18\x07\x20\x01(\x0cB\x02\x18\0\"D\n\x0bCachePolicy\x12\x0c\n\x08CACHE_\
|
||||
NO\x10\x01\x12\x11\n\rCACHE_PRIVATE\x10\x02\x12\x10\n\x0cCACHE_PUBLIC\
|
||||
\x10\x03\x1a\x02\x10\0\"\x85\x01\n\x06Header\x12\x0f\n\x03uri\x18\x01\
|
||||
\x20\x01(\tB\x02\x18\0\x12\x18\n\x0ccontent_type\x18\x02\x20\x01(\tB\x02\
|
||||
\x18\0\x12\x12\n\x06method\x18\x03\x20\x01(\tB\x02\x18\0\x12\x17\n\x0bst\
|
||||
atus_code\x18\x04\x20\x01(\x11B\x02\x18\0\x12#\n\x0buser_fields\x18\x06\
|
||||
\x20\x03(\x0b2\n.UserFieldB\x02\x18\0\"/\n\tUserField\x12\x0f\n\x03key\
|
||||
\x18\x01\x20\x01(\tB\x02\x18\0\x12\x11\n\x05value\x18\x02\x20\x01(\x0cB\
|
||||
\x02\x18\0B\0b\x06proto2\
|
||||
\n\rmercury.proto\x12\0\">\n\x16MercuryMultiGetRequest\x12\"\n\x07reques\
|
||||
t\x18\x01\x20\x03(\x0b2\x0f.MercuryRequestB\0:\0\"8\n\x14MercuryMultiGet\
|
||||
Reply\x12\x1e\n\x05reply\x18\x01\x20\x03(\x0b2\r.MercuryReplyB\0:\0\"Y\n\
|
||||
\x0eMercuryRequest\x12\r\n\x03uri\x18\x01\x20\x01(\tB\0\x12\x16\n\x0ccon\
|
||||
tent_type\x18\x02\x20\x01(\tB\0\x12\x0e\n\x04body\x18\x03\x20\x01(\x0cB\
|
||||
\0\x12\x0e\n\x04etag\x18\x04\x20\x01(\x0cB\0:\0\"\xff\x01\n\x0cMercuryRe\
|
||||
ply\x12\x15\n\x0bstatus_code\x18\x01\x20\x01(\x11B\0\x12\x18\n\x0estatus\
|
||||
_message\x18\x02\x20\x01(\tB\0\x121\n\x0ccache_policy\x18\x03\x20\x01(\
|
||||
\x0e2\x19.MercuryReply.CachePolicyB\0\x12\r\n\x03ttl\x18\x04\x20\x01(\
|
||||
\x11B\0\x12\x0e\n\x04etag\x18\x05\x20\x01(\x0cB\0\x12\x16\n\x0ccontent_t\
|
||||
ype\x18\x06\x20\x01(\tB\0\x12\x0e\n\x04body\x18\x07\x20\x01(\x0cB\0\"B\n\
|
||||
\x0bCachePolicy\x12\x0c\n\x08CACHE_NO\x10\x01\x12\x11\n\rCACHE_PRIVATE\
|
||||
\x10\x02\x12\x10\n\x0cCACHE_PUBLIC\x10\x03\x1a\0:\0\"}\n\x06Header\x12\r\
|
||||
\n\x03uri\x18\x01\x20\x01(\tB\0\x12\x16\n\x0ccontent_type\x18\x02\x20\
|
||||
\x01(\tB\0\x12\x10\n\x06method\x18\x03\x20\x01(\tB\0\x12\x15\n\x0bstatus\
|
||||
_code\x18\x04\x20\x01(\x11B\0\x12!\n\x0buser_fields\x18\x06\x20\x03(\x0b\
|
||||
2\n.UserFieldB\0:\0\"-\n\tUserField\x12\r\n\x03key\x18\x01\x20\x01(\tB\0\
|
||||
\x12\x0f\n\x05value\x18\x02\x20\x01(\x0cB\0:\0B\0b\x06proto2\
|
||||
";
|
||||
|
||||
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,9 @@
|
|||
// This file is generated by rust-protobuf 2.0.5. Do not edit
|
||||
// This file is generated by rust-protobuf 2.8.0. Do not edit
|
||||
// @generated
|
||||
|
||||
// https://github.com/Manishearth/rust-clippy/issues/702
|
||||
#![allow(unknown_lints)]
|
||||
#![allow(clippy)]
|
||||
#![allow(clippy::all)]
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
|
||||
|
@ -17,10 +17,15 @@
|
|||
#![allow(unsafe_code)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(unused_results)]
|
||||
//! Generated file from `pubsub.proto`
|
||||
|
||||
use protobuf::Message as Message_imported_for_functions;
|
||||
use protobuf::ProtobufEnum as ProtobufEnum_imported_for_functions;
|
||||
|
||||
/// Generated files are compatible only with the same version
|
||||
/// of protobuf runtime.
|
||||
const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_8_0;
|
||||
|
||||
#[derive(PartialEq,Clone,Default)]
|
||||
pub struct Subscription {
|
||||
// message fields
|
||||
|
@ -28,8 +33,14 @@ pub struct Subscription {
|
|||
expiry: ::std::option::Option<i32>,
|
||||
status_code: ::std::option::Option<i32>,
|
||||
// special fields
|
||||
unknown_fields: ::protobuf::UnknownFields,
|
||||
cached_size: ::protobuf::CachedSize,
|
||||
pub unknown_fields: ::protobuf::UnknownFields,
|
||||
pub cached_size: ::protobuf::CachedSize,
|
||||
}
|
||||
|
||||
impl<'a> ::std::default::Default for &'a Subscription {
|
||||
fn default() -> &'a Subscription {
|
||||
<Subscription as ::protobuf::Message>::default_instance()
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
|
@ -39,6 +50,13 @@ impl Subscription {
|
|||
|
||||
// optional string uri = 1;
|
||||
|
||||
|
||||
pub fn get_uri(&self) -> &str {
|
||||
match self.uri.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
pub fn clear_uri(&mut self) {
|
||||
self.uri.clear();
|
||||
}
|
||||
|
@ -66,15 +84,12 @@ impl Subscription {
|
|||
self.uri.take().unwrap_or_else(|| ::std::string::String::new())
|
||||
}
|
||||
|
||||
pub fn get_uri(&self) -> &str {
|
||||
match self.uri.as_ref() {
|
||||
Some(v) => &v,
|
||||
None => "",
|
||||
}
|
||||
}
|
||||
|
||||
// optional int32 expiry = 2;
|
||||
|
||||
|
||||
pub fn get_expiry(&self) -> i32 {
|
||||
self.expiry.unwrap_or(0)
|
||||
}
|
||||
pub fn clear_expiry(&mut self) {
|
||||
self.expiry = ::std::option::Option::None;
|
||||
}
|
||||
|
@ -88,12 +103,12 @@ impl Subscription {
|
|||
self.expiry = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
pub fn get_expiry(&self) -> i32 {
|
||||
self.expiry.unwrap_or(0)
|
||||
}
|
||||
|
||||
// optional int32 status_code = 3;
|
||||
|
||||
|
||||
pub fn get_status_code(&self) -> i32 {
|
||||
self.status_code.unwrap_or(0)
|
||||
}
|
||||
pub fn clear_status_code(&mut self) {
|
||||
self.status_code = ::std::option::Option::None;
|
||||
}
|
||||
|
@ -106,10 +121,6 @@ impl Subscription {
|
|||
pub fn set_status_code(&mut self, v: i32) {
|
||||
self.status_code = ::std::option::Option::Some(v);
|
||||
}
|
||||
|
||||
pub fn get_status_code(&self) -> i32 {
|
||||
self.status_code.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ::protobuf::Message for Subscription {
|
||||
|
@ -190,13 +201,13 @@ impl ::protobuf::Message for Subscription {
|
|||
&mut self.unknown_fields
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &::std::any::Any {
|
||||
self as &::std::any::Any
|
||||
fn as_any(&self) -> &dyn (::std::any::Any) {
|
||||
self as &dyn (::std::any::Any)
|
||||
}
|
||||
fn as_any_mut(&mut self) -> &mut ::std::any::Any {
|
||||
self as &mut ::std::any::Any
|
||||
fn as_any_mut(&mut self) -> &mut dyn (::std::any::Any) {
|
||||
self as &mut dyn (::std::any::Any)
|
||||
}
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<::std::any::Any> {
|
||||
fn into_any(self: Box<Self>) -> ::std::boxed::Box<dyn (::std::any::Any)> {
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -253,9 +264,9 @@ impl ::protobuf::Message for Subscription {
|
|||
|
||||
impl ::protobuf::Clear for Subscription {
|
||||
fn clear(&mut self) {
|
||||
self.clear_uri();
|
||||
self.clear_expiry();
|
||||
self.clear_status_code();
|
||||
self.uri.clear();
|
||||
self.expiry = ::std::option::Option::None;
|
||||
self.status_code = ::std::option::Option::None;
|
||||
self.unknown_fields.clear();
|
||||
}
|
||||
}
|
||||
|
@ -273,10 +284,9 @@ impl ::protobuf::reflect::ProtobufValue for Subscription {
|
|||
}
|
||||
|
||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||
\n\x0cpubsub.proto\x12\0\"L\n\x0cSubscription\x12\x0f\n\x03uri\x18\x01\
|
||||
\x20\x01(\tB\x02\x18\0\x12\x12\n\x06expiry\x18\x02\x20\x01(\x05B\x02\x18\
|
||||
\0\x12\x17\n\x0bstatus_code\x18\x03\x20\x01(\x05B\x02\x18\0B\0b\x06proto\
|
||||
2\
|
||||
\n\x0cpubsub.proto\x12\0\"H\n\x0cSubscription\x12\r\n\x03uri\x18\x01\x20\
|
||||
\x01(\tB\0\x12\x10\n\x06expiry\x18\x02\x20\x01(\x05B\0\x12\x15\n\x0bstat\
|
||||
us_code\x18\x03\x20\x01(\x05B\0:\0B\0b\x06proto2\
|
||||
";
|
||||
|
||||
static mut file_descriptor_proto_lazy: ::protobuf::lazy::Lazy<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::lazy::Lazy {
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -2,7 +2,6 @@
|
|||
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
||||
|
||||
extern crate base64;
|
||||
extern crate crypto;
|
||||
extern crate futures;
|
||||
extern crate hyper;
|
||||
extern crate num_bigint;
|
||||
|
|
78
src/main.rs
78
src/main.rs
|
@ -1,4 +1,3 @@
|
|||
extern crate crypto;
|
||||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate getopts;
|
||||
|
@ -11,10 +10,10 @@ extern crate tokio_io;
|
|||
extern crate tokio_process;
|
||||
extern crate tokio_signal;
|
||||
extern crate url;
|
||||
extern crate sha1;
|
||||
extern crate hex;
|
||||
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha1::Sha1;
|
||||
use env_logger::LogBuilder;
|
||||
use sha1::{Sha1, Digest};
|
||||
use futures::sync::mpsc::UnboundedReceiver;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use std::env;
|
||||
|
@ -37,16 +36,14 @@ use librespot::connect::discovery::{discovery, DiscoveryStream};
|
|||
use librespot::connect::spirc::{Spirc, SpircTask};
|
||||
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
|
||||
use librespot::playback::config::{Bitrate, PlayerConfig};
|
||||
use librespot::playback::mixer::{self, Mixer};
|
||||
use librespot::playback::mixer::{self, Mixer, MixerConfig};
|
||||
use librespot::playback::player::{Player, PlayerEvent};
|
||||
|
||||
mod player_event_handler;
|
||||
use player_event_handler::run_program_on_events;
|
||||
|
||||
fn device_id(name: &str) -> String {
|
||||
let mut h = Sha1::new();
|
||||
h.input_str(name);
|
||||
h.result_str()
|
||||
hex::encode(Sha1::digest(name.as_bytes()))
|
||||
}
|
||||
|
||||
fn usage(program: &str, opts: &getopts::Options) -> String {
|
||||
|
@ -55,11 +52,11 @@ fn usage(program: &str, opts: &getopts::Options) -> String {
|
|||
}
|
||||
|
||||
fn setup_logging(verbose: bool) {
|
||||
let mut builder = LogBuilder::new();
|
||||
let mut builder = env_logger::Builder::new();
|
||||
match env::var("RUST_LOG") {
|
||||
Ok(config) => {
|
||||
builder.parse(&config);
|
||||
builder.init().unwrap();
|
||||
builder.parse_filters(&config);
|
||||
builder.init();
|
||||
|
||||
if verbose {
|
||||
warn!("`--verbose` flag overidden by `RUST_LOG` environment variable");
|
||||
|
@ -67,11 +64,11 @@ fn setup_logging(verbose: bool) {
|
|||
}
|
||||
Err(_) => {
|
||||
if verbose {
|
||||
builder.parse("mdns=info,librespot=trace");
|
||||
builder.parse_filters("mdns=info,librespot=trace");
|
||||
} else {
|
||||
builder.parse("mdns=info,librespot=info");
|
||||
builder.parse_filters("mdns=info,librespot=info");
|
||||
}
|
||||
builder.init().unwrap();
|
||||
builder.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -92,12 +89,13 @@ struct Setup {
|
|||
backend: fn(Option<String>) -> Box<Sink>,
|
||||
device: Option<String>,
|
||||
|
||||
mixer: fn() -> Box<Mixer>,
|
||||
mixer: fn(Option<MixerConfig>) -> Box<Mixer>,
|
||||
|
||||
cache: Option<Cache>,
|
||||
player_config: PlayerConfig,
|
||||
session_config: SessionConfig,
|
||||
connect_config: ConnectConfig,
|
||||
mixer_config: MixerConfig,
|
||||
credentials: Option<Credentials>,
|
||||
enable_discovery: bool,
|
||||
zeroconf_port: u16,
|
||||
|
@ -141,10 +139,28 @@ fn setup(args: &[String]) -> Setup {
|
|||
.optopt(
|
||||
"",
|
||||
"device",
|
||||
"Audio device to use. Use '?' to list options if using portaudio",
|
||||
"Audio device to use. Use '?' to list options if using portaudio or alsa",
|
||||
"DEVICE",
|
||||
)
|
||||
.optopt("", "mixer", "Mixer to use", "MIXER")
|
||||
.optopt("", "mixer", "Mixer to use (alsa or softmixer)", "MIXER")
|
||||
.optopt(
|
||||
"m",
|
||||
"mixer-name",
|
||||
"Alsa mixer name, e.g \"PCM\" or \"Master\". Defaults to 'PCM'",
|
||||
"MIXER_NAME",
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"mixer-card",
|
||||
"Alsa mixer card, e.g \"hw:0\" or similar from `aplay -l`. Defaults to 'default' ",
|
||||
"MIXER_CARD",
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"mixer-index",
|
||||
"Alsa mixer index, Index of the cards mixer. Defaults to 0",
|
||||
"MIXER_INDEX",
|
||||
)
|
||||
.optopt(
|
||||
"",
|
||||
"initial-volume",
|
||||
|
@ -202,10 +218,23 @@ fn setup(args: &[String]) -> Setup {
|
|||
let backend = audio_backend::find(backend_name).expect("Invalid backend");
|
||||
|
||||
let device = matches.opt_str("device");
|
||||
if device == Some("?".into()) {
|
||||
backend(device);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
let mixer_name = matches.opt_str("mixer");
|
||||
let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer");
|
||||
|
||||
let mixer_config = MixerConfig {
|
||||
card: matches.opt_str("mixer-card").unwrap_or(String::from("default")),
|
||||
mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")),
|
||||
index: matches
|
||||
.opt_str("mixer-index")
|
||||
.map(|index| index.parse::<u32>().unwrap())
|
||||
.unwrap_or(0),
|
||||
};
|
||||
|
||||
let use_audio_cache = !matches.opt_present("disable-audio-cache");
|
||||
|
||||
let cache = matches
|
||||
|
@ -220,8 +249,7 @@ fn setup(args: &[String]) -> Setup {
|
|||
panic!("Initial volume must be in the range 0-100");
|
||||
}
|
||||
(volume as i32 * 0xFFFF / 100) as u16
|
||||
})
|
||||
.or_else(|| cache.as_ref().and_then(Cache::volume))
|
||||
}).or_else(|| cache.as_ref().and_then(Cache::volume))
|
||||
.unwrap_or(0x8000);
|
||||
|
||||
let zeroconf_port = matches
|
||||
|
@ -322,6 +350,7 @@ fn setup(args: &[String]) -> Setup {
|
|||
enable_discovery: enable_discovery,
|
||||
zeroconf_port: zeroconf_port,
|
||||
mixer: mixer,
|
||||
mixer_config: mixer_config,
|
||||
player_event_program: matches.opt_str("onevent"),
|
||||
}
|
||||
}
|
||||
|
@ -333,7 +362,8 @@ struct Main {
|
|||
connect_config: ConnectConfig,
|
||||
backend: fn(Option<String>) -> Box<Sink>,
|
||||
device: Option<String>,
|
||||
mixer: fn() -> Box<Mixer>,
|
||||
mixer: fn(Option<MixerConfig>) -> Box<Mixer>,
|
||||
mixer_config: MixerConfig,
|
||||
handle: Handle,
|
||||
|
||||
discovery: Option<DiscoveryStream>,
|
||||
|
@ -360,13 +390,14 @@ impl Main {
|
|||
backend: setup.backend,
|
||||
device: setup.device,
|
||||
mixer: setup.mixer,
|
||||
mixer_config: setup.mixer_config,
|
||||
|
||||
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()),
|
||||
signal: Box::new(tokio_signal::ctrl_c().flatten_stream()),
|
||||
|
||||
player_event_channel: None,
|
||||
player_event_program: setup.player_event_program,
|
||||
|
@ -420,13 +451,14 @@ impl Future for Main {
|
|||
|
||||
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 mixer_config = self.mixer_config.clone();
|
||||
let mixer = (self.mixer)(Some(mixer_config));
|
||||
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 device = self.device.clone();
|
||||
let (player, event_channel) =
|
||||
Player::new(player_config, session.clone(), audio_filter, move || {
|
||||
(backend)(device)
|
||||
|
|
Loading…
Reference in a new issue