mirror of
https://github.com/librespot-org/librespot.git
synced 2025-01-27 17:44:04 +00:00
Fix conflicts from PR #143
This commit is contained in:
commit
90ae9d713d
15 changed files with 198 additions and 165 deletions
49
Cargo.lock
generated
49
Cargo.lock
generated
|
@ -316,8 +316,8 @@ dependencies = [
|
||||||
"getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
"getopts 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hyper 0.11.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper 0.11.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"librespot-audio 0.1.0",
|
"librespot-audio 0.1.0",
|
||||||
|
"librespot-connect 0.1.0",
|
||||||
"librespot-core 0.1.0",
|
"librespot-core 0.1.0",
|
||||||
"librespot-discovery 0.1.0",
|
|
||||||
"librespot-metadata 0.1.0",
|
"librespot-metadata 0.1.0",
|
||||||
"librespot-playback 0.1.0",
|
"librespot-playback 0.1.0",
|
||||||
"librespot-protocol 0.1.0",
|
"librespot-protocol 0.1.0",
|
||||||
|
@ -356,6 +356,31 @@ dependencies = [
|
||||||
"vorbis 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vorbis 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "librespot-connect"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"dns-sd 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"hyper 0.11.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"librespot-core 0.1.0",
|
||||||
|
"librespot-playback 0.1.0",
|
||||||
|
"librespot-protocol 0.1.0",
|
||||||
|
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"mdns 0.2.0 (git+https://github.com/plietar/rust-mdns)",
|
||||||
|
"num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"protobuf_macros 0.6.0 (git+https://github.com/plietar/rust-protobuf-macros)",
|
||||||
|
"rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"rust-crypto 0.2.36 (git+https://github.com/awmath/rust-crypto.git?branch=avx2)",
|
||||||
|
"serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-core"
|
name = "librespot-core"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -387,28 +412,6 @@ dependencies = [
|
||||||
"vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"vergen 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "librespot-discovery"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"dns-sd 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"hyper 0.11.14 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"librespot-core 0.1.0",
|
|
||||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"mdns 0.2.0 (git+https://github.com/plietar/rust-mdns)",
|
|
||||||
"num-bigint 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"rust-crypto 0.2.36 (git+https://github.com/awmath/rust-crypto.git?branch=avx2)",
|
|
||||||
"serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_derive 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_json 0.9.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"tokio-core 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"url 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-metadata"
|
name = "librespot-metadata"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
name = "librespot"
|
name = "librespot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Paul Liétar <paul@lietar.net>"]
|
authors = ["Paul Liétar <paul@lietar.net>"]
|
||||||
build = "build.rs"
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Open Source Spotify client library"
|
description = "Open Source Spotify client library"
|
||||||
keywords = ["spotify"]
|
keywords = ["spotify"]
|
||||||
|
@ -22,10 +21,10 @@ doc = false
|
||||||
|
|
||||||
[dependencies.librespot-audio]
|
[dependencies.librespot-audio]
|
||||||
path = "audio"
|
path = "audio"
|
||||||
|
[dependencies.librespot-connect]
|
||||||
|
path = "connect"
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "core"
|
path = "core"
|
||||||
[dependencies.librespot-discovery]
|
|
||||||
path = "discovery"
|
|
||||||
[dependencies.librespot-metadata]
|
[dependencies.librespot-metadata]
|
||||||
path = "metadata"
|
path = "metadata"
|
||||||
[dependencies.librespot-playback]
|
[dependencies.librespot-playback]
|
||||||
|
@ -67,7 +66,7 @@ jackaudio-backend = ["librespot-playback/jackaudio-backend"]
|
||||||
with-tremor = ["librespot-audio/with-tremor"]
|
with-tremor = ["librespot-audio/with-tremor"]
|
||||||
with-vorbis = ["librespot-audio/with-vorbis"]
|
with-vorbis = ["librespot-audio/with-vorbis"]
|
||||||
|
|
||||||
with-dns-sd = ["librespot-discovery/with-dns-sd"]
|
with-dns-sd = ["librespot-connect/with-dns-sd"]
|
||||||
|
|
||||||
default = ["librespot-playback/portaudio-backend"]
|
default = ["librespot-playback/portaudio-backend"]
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-discovery"
|
name = "librespot-connect"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||||
|
build = "build.rs"
|
||||||
|
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
|
[dependencies.librespot-playback]
|
||||||
|
path = "../playback"
|
||||||
|
[dependencies.librespot-protocol]
|
||||||
|
path = "../protocol"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.5.0"
|
base64 = "0.5.0"
|
||||||
|
@ -24,6 +29,9 @@ url = "1.3"
|
||||||
dns-sd = { version = "0.1.3", optional = true }
|
dns-sd = { version = "0.1.3", optional = true }
|
||||||
mdns = { git = "https://github.com/plietar/rust-mdns", optional = true }
|
mdns = { git = "https://github.com/plietar/rust-mdns", optional = true }
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
protobuf_macros = { git = "https://github.com/plietar/rust-protobuf-macros", features = ["with-syntex"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mdns"]
|
default = ["mdns"]
|
||||||
with-dns-sd = ["dns-sd"]
|
with-dns-sd = ["dns-sd"]
|
|
@ -10,5 +10,4 @@ fn main() {
|
||||||
|
|
||||||
println!("cargo:rerun-if-changed=src/lib.in.rs");
|
println!("cargo:rerun-if-changed=src/lib.in.rs");
|
||||||
println!("cargo:rerun-if-changed=src/spirc.rs");
|
println!("cargo:rerun-if-changed=src/spirc.rs");
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
use base64;
|
use base64;
|
||||||
|
use crypto;
|
||||||
use crypto::digest::Digest;
|
use crypto::digest::Digest;
|
||||||
use crypto::mac::Mac;
|
use crypto::mac::Mac;
|
||||||
use crypto;
|
use futures::{Future, Poll, Stream};
|
||||||
use futures::sync::mpsc;
|
use futures::sync::mpsc;
|
||||||
use futures::{Future, Stream, Poll};
|
|
||||||
use hyper::server::{Service, Request, Response, Http};
|
|
||||||
use hyper::{self, Get, Post, StatusCode};
|
use hyper::{self, Get, Post, StatusCode};
|
||||||
|
use hyper::server::{Http, Request, Response, Service};
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
#[cfg(feature = "with-dns-sd")]
|
||||||
use dns_sd::DNSService;
|
use dns_sd::DNSService;
|
||||||
|
@ -21,10 +21,10 @@ use std::sync::Arc;
|
||||||
use tokio_core::reactor::Handle;
|
use tokio_core::reactor::Handle;
|
||||||
use url;
|
use url;
|
||||||
|
|
||||||
use core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
|
||||||
use core::authentication::Credentials;
|
use core::authentication::Credentials;
|
||||||
use core::util;
|
|
||||||
use core::config::ConnectConfig;
|
use core::config::ConnectConfig;
|
||||||
|
use core::diffie_hellman::{DH_GENERATOR, DH_PRIME};
|
||||||
|
use core::util;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct Discovery(Arc<DiscoveryInner>);
|
struct Discovery(Arc<DiscoveryInner>);
|
||||||
|
@ -37,9 +37,10 @@ struct DiscoveryInner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Discovery {
|
impl Discovery {
|
||||||
fn new(config: ConnectConfig, device_id: String)
|
fn new(
|
||||||
-> (Discovery, mpsc::UnboundedReceiver<Credentials>)
|
config: ConnectConfig,
|
||||||
{
|
device_id: String,
|
||||||
|
) -> (Discovery, mpsc::UnboundedReceiver<Credentials>) {
|
||||||
let (tx, rx) = mpsc::unbounded();
|
let (tx, rx) = mpsc::unbounded();
|
||||||
|
|
||||||
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
let key_data = util::rand_vec(&mut rand::thread_rng(), 95);
|
||||||
|
@ -59,9 +60,10 @@ impl Discovery {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Discovery {
|
impl Discovery {
|
||||||
fn handle_get_info(&self, _params: &BTreeMap<String, String>)
|
fn handle_get_info(
|
||||||
-> ::futures::Finished<Response, hyper::Error>
|
&self,
|
||||||
{
|
_params: &BTreeMap<String, String>,
|
||||||
|
) -> ::futures::Finished<Response, hyper::Error> {
|
||||||
let public_key = self.0.public_key.to_bytes_be();
|
let public_key = self.0.public_key.to_bytes_be();
|
||||||
let public_key = base64::encode(&public_key);
|
let public_key = base64::encode(&public_key);
|
||||||
|
|
||||||
|
@ -85,9 +87,10 @@ impl Discovery {
|
||||||
::futures::finished(Response::new().with_body(body))
|
::futures::finished(Response::new().with_body(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_add_user(&self, params: &BTreeMap<String, String>)
|
fn handle_add_user(
|
||||||
-> ::futures::Finished<Response, hyper::Error>
|
&self,
|
||||||
{
|
params: &BTreeMap<String, String>,
|
||||||
|
) -> ::futures::Finished<Response, hyper::Error> {
|
||||||
let username = params.get("userName").unwrap();
|
let username = params.get("userName").unwrap();
|
||||||
let encrypted_blob = params.get("blob").unwrap();
|
let encrypted_blob = params.get("blob").unwrap();
|
||||||
let client_key = params.get("clientKey").unwrap();
|
let client_key = params.get("clientKey").unwrap();
|
||||||
|
@ -133,8 +136,8 @@ impl Discovery {
|
||||||
|
|
||||||
let decrypted = {
|
let decrypted = {
|
||||||
let mut data = vec![0u8; encrypted.len()];
|
let mut data = vec![0u8; encrypted.len()];
|
||||||
let mut cipher = crypto::aes::ctr(crypto::aes::KeySize::KeySize128,
|
let mut cipher =
|
||||||
&encryption_key[0..16], iv);
|
crypto::aes::ctr(crypto::aes::KeySize::KeySize128, &encryption_key[0..16], iv);
|
||||||
cipher.process(encrypted, &mut data);
|
cipher.process(encrypted, &mut data);
|
||||||
String::from_utf8(data).unwrap()
|
String::from_utf8(data).unwrap()
|
||||||
};
|
};
|
||||||
|
@ -153,9 +156,7 @@ impl Discovery {
|
||||||
::futures::finished(Response::new().with_body(body))
|
::futures::finished(Response::new().with_body(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn not_found(&self)
|
fn not_found(&self) -> ::futures::Finished<Response, hyper::Error> {
|
||||||
-> ::futures::Finished<Response, hyper::Error>
|
|
||||||
{
|
|
||||||
::futures::finished(Response::new().with_status(StatusCode::NotFound))
|
::futures::finished(Response::new().with_status(StatusCode::NotFound))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -179,19 +180,22 @@ impl Service for Discovery {
|
||||||
}
|
}
|
||||||
|
|
||||||
let this = self.clone();
|
let this = self.clone();
|
||||||
Box::new(body.fold(Vec::new(), |mut acc, chunk| {
|
Box::new(
|
||||||
acc.extend_from_slice(chunk.as_ref());
|
body.fold(Vec::new(), |mut acc, chunk| {
|
||||||
Ok::<_, hyper::Error>(acc)
|
acc.extend_from_slice(chunk.as_ref());
|
||||||
}).map(move |body| {
|
Ok::<_, hyper::Error>(acc)
|
||||||
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
}).map(move |body| {
|
||||||
params
|
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
||||||
}).and_then(move |params| {
|
params
|
||||||
match (method, params.get("action").map(AsRef::as_ref)) {
|
})
|
||||||
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
.and_then(
|
||||||
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
move |params| match (method, params.get("action").map(AsRef::as_ref)) {
|
||||||
_ => this.not_found(),
|
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
||||||
}
|
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
||||||
}))
|
_ => this.not_found(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,22 +211,30 @@ pub struct DiscoveryStream {
|
||||||
_svc: mdns::Service,
|
_svc: mdns::Service,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn discovery(handle: &Handle, config: ConnectConfig, device_id: String, port: u16)
|
pub fn discovery(
|
||||||
-> io::Result<DiscoveryStream>
|
handle: &Handle,
|
||||||
{
|
config: ConnectConfig,
|
||||||
|
device_id: String,
|
||||||
|
port: u16,
|
||||||
|
) -> io::Result<DiscoveryStream> {
|
||||||
let (discovery, creds_rx) = Discovery::new(config.clone(), device_id);
|
let (discovery, creds_rx) = Discovery::new(config.clone(), device_id);
|
||||||
|
|
||||||
let serve = {
|
let serve = {
|
||||||
let http = Http::new();
|
let http = Http::new();
|
||||||
debug!("Zeroconf server listening on 0.0.0.0:{}", port);
|
debug!("Zeroconf server listening on 0.0.0.0:{}", port);
|
||||||
http.serve_addr_handle(&format!("0.0.0.0:{}", port).parse().unwrap(), &handle, move || Ok(discovery.clone())).expect("Unable to bind Zeroconf to port")
|
http.serve_addr_handle(
|
||||||
|
&format!("0.0.0.0:{}", port).parse().unwrap(),
|
||||||
|
&handle,
|
||||||
|
move || Ok(discovery.clone()),
|
||||||
|
).unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let s_port = serve.incoming_ref().local_addr().port();
|
let s_port = serve.incoming_ref().local_addr().port();
|
||||||
|
|
||||||
let server_future = {
|
let server_future = {
|
||||||
let handle = handle.clone();
|
let handle = handle.clone();
|
||||||
serve.for_each(move |connection| {
|
serve
|
||||||
|
.for_each(move |connection| {
|
||||||
handle.spawn(connection.then(|_| Ok(())));
|
handle.spawn(connection.then(|_| Ok(())));
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
|
@ -231,22 +243,25 @@ pub fn discovery(handle: &Handle, config: ConnectConfig, device_id: String, port
|
||||||
handle.spawn(server_future);
|
handle.spawn(server_future);
|
||||||
|
|
||||||
#[cfg(feature = "with-dns-sd")]
|
#[cfg(feature = "with-dns-sd")]
|
||||||
let svc = DNSService::register(Some(&*config.name),
|
let svc = DNSService::register(
|
||||||
"_spotify-connect._tcp",
|
Some(&*config.name),
|
||||||
None,
|
"_spotify-connect._tcp",
|
||||||
None,
|
None,
|
||||||
s_port,
|
None,
|
||||||
&["VERSION=1.0", "CPath=/"]).unwrap();
|
s_port,
|
||||||
|
&["VERSION=1.0", "CPath=/"],
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
#[cfg(not(feature = "with-dns-sd"))]
|
#[cfg(not(feature = "with-dns-sd"))]
|
||||||
let responder = mdns::Responder::spawn(&handle)?;
|
let responder = mdns::Responder::spawn(&handle)?;
|
||||||
|
|
||||||
#[cfg(not(feature = "with-dns-sd"))]
|
#[cfg(not(feature = "with-dns-sd"))]
|
||||||
let svc = responder.register(
|
let svc = responder.register(
|
||||||
"_spotify-connect._tcp".to_owned(),
|
"_spotify-connect._tcp".to_owned(),
|
||||||
config.name,
|
config.name,
|
||||||
s_port,
|
s_port,
|
||||||
&["VERSION=1.0", "CPath=/"]);
|
&["VERSION=1.0", "CPath=/"],
|
||||||
|
);
|
||||||
|
|
||||||
Ok(DiscoveryStream {
|
Ok(DiscoveryStream {
|
||||||
credentials: creds_rx,
|
credentials: creds_rx,
|
|
@ -1,5 +1,7 @@
|
||||||
#[macro_use] extern crate log;
|
#[macro_use]
|
||||||
#[macro_use] extern crate serde_json;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate crypto;
|
extern crate crypto;
|
||||||
|
@ -18,5 +20,9 @@ extern crate dns_sd;
|
||||||
extern crate mdns;
|
extern crate mdns;
|
||||||
|
|
||||||
extern crate librespot_core as core;
|
extern crate librespot_core as core;
|
||||||
|
extern crate librespot_playback as playback;
|
||||||
|
extern crate librespot_protocol as protocol;
|
||||||
|
|
||||||
pub mod discovery;
|
pub mod discovery;
|
||||||
|
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
|
@ -1,24 +1,24 @@
|
||||||
|
use futures::{Async, Future, Poll, Sink, Stream};
|
||||||
use futures::future;
|
use futures::future;
|
||||||
use futures::sync::{oneshot, mpsc};
|
use futures::sync::{mpsc, oneshot};
|
||||||
use futures::{Future, Stream, Sink, Async, Poll};
|
|
||||||
use protobuf::{self, Message};
|
use protobuf::{self, Message};
|
||||||
|
|
||||||
use core::config::ConnectConfig;
|
use core::config::ConnectConfig;
|
||||||
use core::mercury::MercuryError;
|
use core::mercury::MercuryError;
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::{SpotifyId, SeqGenerator};
|
use core::util::{SeqGenerator, SpotifyId};
|
||||||
use core::version;
|
use core::version;
|
||||||
|
|
||||||
use protocol;
|
use protocol;
|
||||||
use protocol::spirc::{PlayStatus, State, MessageType, Frame, DeviceState};
|
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State};
|
||||||
|
|
||||||
use playback::mixer::Mixer;
|
use playback::mixer::Mixer;
|
||||||
use playback::player::Player;
|
use playback::player::Player;
|
||||||
|
|
||||||
use std;
|
|
||||||
use rand;
|
use rand;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::time::{UNIX_EPOCH, SystemTime};
|
use std;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
pub struct SpircTask {
|
pub struct SpircTask {
|
||||||
player: Player,
|
player: Player,
|
||||||
|
@ -47,7 +47,7 @@ pub enum SpircCommand {
|
||||||
Next,
|
Next,
|
||||||
VolumeUp,
|
VolumeUp,
|
||||||
VolumeDown,
|
VolumeDown,
|
||||||
Shutdown
|
Shutdown,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Spirc {
|
pub struct Spirc {
|
||||||
|
@ -152,11 +152,13 @@ fn volume_to_mixer(volume: u16) -> u16 {
|
||||||
val
|
val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Spirc {
|
impl Spirc {
|
||||||
pub fn new(config: ConnectConfig, session: Session, player: Player, mixer: Box<Mixer>)
|
pub fn new(
|
||||||
-> (Spirc, SpircTask)
|
config: ConnectConfig,
|
||||||
{
|
session: Session,
|
||||||
|
player: Player,
|
||||||
|
mixer: Box<Mixer>,
|
||||||
|
) -> (Spirc, SpircTask) {
|
||||||
debug!("new Spirc[{}]", session.session_id());
|
debug!("new Spirc[{}]", session.session_id());
|
||||||
|
|
||||||
let ident = session.device_id().to_owned();
|
let ident = session.device_id().to_owned();
|
||||||
|
@ -164,15 +166,20 @@ impl Spirc {
|
||||||
let uri = format!("hm://remote/3/user/{}/", session.username());
|
let uri = format!("hm://remote/3/user/{}/", session.username());
|
||||||
|
|
||||||
let subscription = session.mercury().subscribe(&uri as &str);
|
let subscription = session.mercury().subscribe(&uri as &str);
|
||||||
let subscription = subscription.map(|stream| stream.map_err(|_| MercuryError)).flatten_stream();
|
let subscription = subscription
|
||||||
|
.map(|stream| stream.map_err(|_| MercuryError))
|
||||||
|
.flatten_stream();
|
||||||
let subscription = Box::new(subscription.map(|response| -> Frame {
|
let subscription = Box::new(subscription.map(|response| -> Frame {
|
||||||
let data = response.payload.first().unwrap();
|
let data = response.payload.first().unwrap();
|
||||||
protobuf::parse_from_bytes(data).unwrap()
|
protobuf::parse_from_bytes(data).unwrap()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let sender = Box::new(session.mercury().sender(uri).with(|frame: Frame| {
|
let sender = Box::new(
|
||||||
Ok(frame.write_to_bytes().unwrap())
|
session
|
||||||
}));
|
.mercury()
|
||||||
|
.sender(uri)
|
||||||
|
.with(|frame: Frame| Ok(frame.write_to_bytes().unwrap())),
|
||||||
|
);
|
||||||
|
|
||||||
let (cmd_tx, cmd_rx) = mpsc::unbounded();
|
let (cmd_tx, cmd_rx) = mpsc::unbounded();
|
||||||
|
|
||||||
|
@ -200,9 +207,7 @@ impl Spirc {
|
||||||
session: session.clone(),
|
session: session.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let spirc = Spirc {
|
let spirc = Spirc { commands: cmd_tx };
|
||||||
commands: cmd_tx,
|
|
||||||
};
|
|
||||||
|
|
||||||
task.hello();
|
task.hello();
|
||||||
|
|
||||||
|
@ -268,9 +273,7 @@ impl Future for SpircTask {
|
||||||
self.handle_end_of_track();
|
self.handle_end_of_track();
|
||||||
}
|
}
|
||||||
Ok(Async::NotReady) => (),
|
Ok(Async::NotReady) => (),
|
||||||
Err(oneshot::Canceled) => {
|
Err(oneshot::Canceled) => self.end_of_track = Box::new(future::empty()),
|
||||||
self.end_of_track = Box::new(future::empty())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,15 +360,18 @@ impl SpircTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_frame(&mut self, frame: Frame) {
|
fn handle_frame(&mut self, frame: Frame) {
|
||||||
debug!("{:?} {:?} {} {} {}",
|
debug!(
|
||||||
frame.get_typ(),
|
"{:?} {:?} {} {} {}",
|
||||||
frame.get_device_state().get_name(),
|
frame.get_typ(),
|
||||||
frame.get_ident(),
|
frame.get_device_state().get_name(),
|
||||||
frame.get_seq_nr(),
|
frame.get_ident(),
|
||||||
frame.get_state_update_id());
|
frame.get_seq_nr(),
|
||||||
|
frame.get_state_update_id()
|
||||||
|
);
|
||||||
|
|
||||||
if frame.get_ident() == self.ident ||
|
if frame.get_ident() == self.ident
|
||||||
(frame.get_recipient().len() > 0 && !frame.get_recipient().contains(&self.ident)) {
|
|| (frame.get_recipient().len() > 0 && !frame.get_recipient().contains(&self.ident))
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -383,7 +389,8 @@ impl SpircTask {
|
||||||
self.update_tracks(&frame);
|
self.update_tracks(&frame);
|
||||||
|
|
||||||
if self.state.get_track().len() > 0 {
|
if self.state.get_track().len() > 0 {
|
||||||
self.state.set_position_ms(frame.get_state().get_position_ms());
|
self.state
|
||||||
|
.set_position_ms(frame.get_state().get_position_ms());
|
||||||
self.state.set_position_measured_at(now_ms() as u64);
|
self.state.set_position_measured_at(now_ms() as u64);
|
||||||
|
|
||||||
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
||||||
|
@ -437,8 +444,7 @@ impl SpircTask {
|
||||||
|
|
||||||
MessageType::kMessageTypeShuffle => {
|
MessageType::kMessageTypeShuffle => {
|
||||||
self.state.set_shuffle(frame.get_state().get_shuffle());
|
self.state.set_shuffle(frame.get_state().get_shuffle());
|
||||||
if self.state.get_shuffle()
|
if self.state.get_shuffle() {
|
||||||
{
|
|
||||||
let current_index = self.state.get_playing_track_index();
|
let current_index = self.state.get_playing_track_index();
|
||||||
{
|
{
|
||||||
let tracks = self.state.mut_track();
|
let tracks = self.state.mut_track();
|
||||||
|
@ -471,14 +477,13 @@ impl SpircTask {
|
||||||
|
|
||||||
MessageType::kMessageTypeVolume => {
|
MessageType::kMessageTypeVolume => {
|
||||||
self.device.set_volume(frame.get_volume());
|
self.device.set_volume(frame.get_volume());
|
||||||
self.mixer.set_volume(volume_to_mixer(frame.get_volume() as u16));
|
self.mixer
|
||||||
|
.set_volume(volume_to_mixer(frame.get_volume() as u16));
|
||||||
self.notify(None);
|
self.notify(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageType::kMessageTypeNotify => {
|
MessageType::kMessageTypeNotify => {
|
||||||
if self.device.get_is_active() &&
|
if self.device.get_is_active() && frame.get_device_state().get_is_active() {
|
||||||
frame.get_device_state().get_is_active()
|
|
||||||
{
|
|
||||||
self.device.set_is_active(false);
|
self.device.set_is_active(false);
|
||||||
self.state.set_status(PlayStatus::kPlayStatusStop);
|
self.state.set_status(PlayStatus::kPlayStatusStop);
|
||||||
self.player.stop();
|
self.player.stop();
|
|
@ -20,31 +20,6 @@ impl Default for SessionConfig {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
|
||||||
pub enum Bitrate {
|
|
||||||
Bitrate96,
|
|
||||||
Bitrate160,
|
|
||||||
Bitrate320,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for Bitrate {
|
|
||||||
type Err = ();
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"96" => Ok(Bitrate::Bitrate96),
|
|
||||||
"160" => Ok(Bitrate::Bitrate160),
|
|
||||||
"320" => Ok(Bitrate::Bitrate320),
|
|
||||||
_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Bitrate {
|
|
||||||
fn default() -> Bitrate {
|
|
||||||
Bitrate::Bitrate160
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
pub enum DeviceType {
|
pub enum DeviceType {
|
||||||
Unknown = 0,
|
Unknown = 0,
|
||||||
|
@ -99,23 +74,6 @@ impl Default for DeviceType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct PlayerConfig {
|
|
||||||
pub bitrate: Bitrate,
|
|
||||||
pub onstart: Option<String>,
|
|
||||||
pub onstop: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for PlayerConfig {
|
|
||||||
fn default() -> PlayerConfig {
|
|
||||||
PlayerConfig {
|
|
||||||
bitrate: Bitrate::default(),
|
|
||||||
onstart: None,
|
|
||||||
onstop: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ConnectConfig {
|
pub struct ConnectConfig {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::env;
|
||||||
use tokio_core::reactor::Core;
|
use tokio_core::reactor::Core;
|
||||||
|
|
||||||
use librespot::core::authentication::Credentials;
|
use librespot::core::authentication::Credentials;
|
||||||
use librespot::core::config::{PlayerConfig, SessionConfig};
|
use librespot::playback::config::{PlayerConfig, SessionConfig};
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::Session;
|
||||||
use librespot::core::util::SpotifyId;
|
use librespot::core::util::SpotifyId;
|
||||||
|
|
||||||
|
|
43
playback/src/config.rs
Normal file
43
playback/src/config.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||||
|
pub enum Bitrate {
|
||||||
|
Bitrate96,
|
||||||
|
Bitrate160,
|
||||||
|
Bitrate320,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Bitrate {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"96" => Ok(Bitrate::Bitrate96),
|
||||||
|
"160" => Ok(Bitrate::Bitrate160),
|
||||||
|
"320" => Ok(Bitrate::Bitrate320),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Bitrate {
|
||||||
|
fn default() -> Bitrate {
|
||||||
|
Bitrate::Bitrate160
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct PlayerConfig {
|
||||||
|
pub bitrate: Bitrate,
|
||||||
|
pub onstart: Option<String>,
|
||||||
|
pub onstop: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PlayerConfig {
|
||||||
|
fn default() -> PlayerConfig {
|
||||||
|
PlayerConfig {
|
||||||
|
bitrate: Bitrate::default(),
|
||||||
|
onstart: None,
|
||||||
|
onstop: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,5 +22,6 @@ extern crate librespot_core as core;
|
||||||
extern crate librespot_metadata as metadata;
|
extern crate librespot_metadata as metadata;
|
||||||
|
|
||||||
pub mod audio_backend;
|
pub mod audio_backend;
|
||||||
|
pub mod config;
|
||||||
pub mod mixer;
|
pub mod mixer;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
|
|
|
@ -9,7 +9,7 @@ use std::sync::mpsc::{RecvError, TryRecvError, RecvTimeoutError};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use core::config::{Bitrate, PlayerConfig};
|
use config::{Bitrate, PlayerConfig};
|
||||||
use core::session::Session;
|
use core::session::Session;
|
||||||
use core::util::SpotifyId;
|
use core::util::SpotifyId;
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
||||||
|
|
||||||
#[macro_use] extern crate log;
|
|
||||||
|
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate crypto;
|
extern crate crypto;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
|
@ -15,11 +13,8 @@ extern crate tokio_core;
|
||||||
extern crate url;
|
extern crate url;
|
||||||
|
|
||||||
pub extern crate librespot_audio as audio;
|
pub extern crate librespot_audio as audio;
|
||||||
|
pub extern crate librespot_connect as connect;
|
||||||
pub extern crate librespot_core as core;
|
pub extern crate librespot_core as core;
|
||||||
pub extern crate librespot_discovery as discovery;
|
|
||||||
pub extern crate librespot_playback as playback;
|
pub extern crate librespot_playback as playback;
|
||||||
pub extern crate librespot_protocol as protocol;
|
pub extern crate librespot_protocol as protocol;
|
||||||
pub extern crate librespot_metadata as metadata;
|
pub extern crate librespot_metadata as metadata;
|
||||||
|
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
|
||||||
|
|
|
@ -20,15 +20,16 @@ use std::mem;
|
||||||
|
|
||||||
use librespot::core::authentication::{get_credentials, Credentials};
|
use librespot::core::authentication::{get_credentials, Credentials};
|
||||||
use librespot::core::cache::Cache;
|
use librespot::core::cache::Cache;
|
||||||
use librespot::core::config::{Bitrate, DeviceType, PlayerConfig, SessionConfig, ConnectConfig};
|
use librespot::core::config::{DeviceType, SessionConfig, ConnectConfig};
|
||||||
use librespot::core::session::Session;
|
use librespot::core::session::Session;
|
||||||
use librespot::core::version;
|
use librespot::core::version;
|
||||||
|
|
||||||
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
|
use librespot::playback::audio_backend::{self, Sink, BACKENDS};
|
||||||
use librespot::discovery::discovery::{discovery, DiscoveryStream};
|
use librespot::playback::config::{Bitrate, PlayerConfig};
|
||||||
|
use librespot::connect::discovery::{discovery, DiscoveryStream};
|
||||||
use librespot::playback::mixer::{self, Mixer};
|
use librespot::playback::mixer::{self, Mixer};
|
||||||
use librespot::playback::player::Player;
|
use librespot::playback::player::Player;
|
||||||
use librespot::spirc::{Spirc, SpircTask};
|
use librespot::connect::spirc::{Spirc, SpircTask};
|
||||||
|
|
||||||
fn usage(program: &str, opts: &getopts::Options) -> String {
|
fn usage(program: &str, opts: &getopts::Options) -> String {
|
||||||
let brief = format!("Usage: {} [options]", program);
|
let brief = format!("Usage: {} [options]", program);
|
||||||
|
|
Loading…
Reference in a new issue