mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge branch 'master' into events-on-prev-next
This commit is contained in:
commit
bde157fad7
16 changed files with 231 additions and 208 deletions
85
Cargo.lock
generated
85
Cargo.lock
generated
|
@ -14,14 +14,6 @@ dependencies = [
|
|||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aster"
|
||||
version = "0.41.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.5.2"
|
||||
|
@ -62,11 +54,6 @@ name = "bitflags"
|
|||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.0.1"
|
||||
|
@ -324,7 +311,6 @@ dependencies = [
|
|||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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)",
|
||||
"rpassword 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -371,7 +357,6 @@ dependencies = [
|
|||
"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 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 0.9.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -428,6 +413,7 @@ name = "librespot-playback"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)",
|
||||
"byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -655,16 +641,6 @@ name = "protobuf"
|
|||
version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "protobuf_macros"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/plietar/rust-protobuf-macros#f186dc5a16c0d79f14c319ac8ce30b06de0cefee"
|
||||
dependencies = [
|
||||
"aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.1"
|
||||
|
@ -833,48 +809,6 @@ dependencies = [
|
|||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntex"
|
||||
version = "0.58.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntex_errors"
|
||||
version = "0.58.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntex_pos"
|
||||
version = "0.58.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syntex_syntax"
|
||||
version = "0.58.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "take"
|
||||
version = "0.1.0"
|
||||
|
@ -892,15 +826,6 @@ dependencies = [
|
|||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "term"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termios"
|
||||
version = "0.2.2"
|
||||
|
@ -1177,14 +1102,12 @@ dependencies = [
|
|||
[metadata]
|
||||
"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4"
|
||||
"checksum alsa 0.0.1 (git+https://github.com/plietar/rust-alsa)" = "<none>"
|
||||
"checksum aster 0.41.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ccfdf7355d9db158df68f976ed030ab0f6578af811f5a7bb6dcf221ec24e0e0"
|
||||
"checksum base64 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "30e93c03064e7590d0466209155251b90c22e37fab1daf2771582598b5827557"
|
||||
"checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4"
|
||||
"checksum bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9bf6104718e80d7b26a68fdbacff3481cfc05df670821affc7e9cbc1884400c"
|
||||
"checksum bit-vec 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "02b4ff8b16e6076c3e14220b39fbc1fabb6737522281a388998046859400895f"
|
||||
"checksum bitflags 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "32866f4d103c4e438b1db1158aa1b1a80ee078e5d77a59a2f906fd62a577389c"
|
||||
"checksum bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d"
|
||||
"checksum bitflags 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1370e9fc2a6ae53aea8b7a5110edbd08836ed87c88736dfabccade1c2b44bff4"
|
||||
"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf"
|
||||
"checksum byteorder 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
|
||||
"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23"
|
||||
|
@ -1241,7 +1164,6 @@ dependencies = [
|
|||
"checksum portaudio-rs 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "029e0ab393b44b2d825efbc755cae51c36be7a99d91356b2052be0ed05836636"
|
||||
"checksum portaudio-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5194a4fa953b4ffd851c320ef6f0484cd7278cb7169ea9d6c433e49b23f7b7f5"
|
||||
"checksum protobuf 1.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bec26e67194b7d991908145fdf21b7cae8b08423d96dcb9e860cd31f854b9506"
|
||||
"checksum protobuf_macros 0.6.0 (git+https://github.com/plietar/rust-protobuf-macros)" = "<none>"
|
||||
"checksum quick-error 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "eda5fe9b71976e62bc81b781206aaa076401769b2143379d3eb2118388babac4"
|
||||
"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||
"checksum rand 0.3.20 (registry+https://github.com/rust-lang/crates.io-index)" = "512870020642bb8c221bf68baa1b2573da814f6ccfe5c9699b1c303047abe9b1"
|
||||
|
@ -1265,13 +1187,8 @@ dependencies = [
|
|||
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
|
||||
"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
|
||||
"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
|
||||
"checksum syntex 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f5e3aaa79319573d19938ea38d068056b826db9883a5d47f86c1cecc688f0e"
|
||||
"checksum syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "867cc5c2d7140ae7eaad2ae9e8bf39cb18a67ca651b7834f88d46ca98faadb9c"
|
||||
"checksum syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13ad4762fe52abc9f4008e85c4fb1b1fe3aa91ccb99ff4826a439c7c598e1047"
|
||||
"checksum syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e4dbae163dd98989464c23dd503161b338790640e11537686f2ef0f25c791"
|
||||
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
||||
"checksum tempfile 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11ce2fe9db64b842314052e2421ac61a73ce41b898dc8e3750398b219c5fc1e0"
|
||||
"checksum term 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1"
|
||||
"checksum termios 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d5d9cf598a6d7ce700a4e6a9199da127e6819a61e64b68609683cc9a01b5683a"
|
||||
"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963"
|
||||
"checksum time 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "a15375f1df02096fb3317256ce2cee6a1f42fc84ea5ad5fc8c421cfe40c73098"
|
||||
|
|
|
@ -55,7 +55,6 @@ url = "1.3"
|
|||
[build-dependencies]
|
||||
rand = "0.3.13"
|
||||
vergen = "0.1.0"
|
||||
protobuf_macros = { git = "https://github.com/plietar/rust-protobuf-macros", features = ["with-syntex"] }
|
||||
|
||||
[replace]
|
||||
"rust-crypto:0.2.36" = { git = "https://github.com/awmath/rust-crypto.git", branch = "avx2" }
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
name = "librespot-connect"
|
||||
version = "0.1.0"
|
||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||
build = "build.rs"
|
||||
|
||||
[dependencies.librespot-core]
|
||||
path = "../core"
|
||||
|
@ -29,9 +28,6 @@ url = "1.3"
|
|||
dns-sd = { version = "0.1.3", 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]
|
||||
default = ["mdns"]
|
||||
with-dns-sd = ["dns-sd"]
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
extern crate protobuf_macros;
|
||||
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() {
|
||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
|
||||
protobuf_macros::expand("src/lib.in.rs", &out.join("lib.rs")).unwrap();
|
||||
|
||||
println!("cargo:rerun-if-changed=src/lib.in.rs");
|
||||
println!("cargo:rerun-if-changed=src/spirc.rs");
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
#[allow(unused_mut)]
|
||||
pub mod spirc;
|
|
@ -24,5 +24,4 @@ extern crate librespot_playback as playback;
|
|||
extern crate librespot_protocol as protocol;
|
||||
|
||||
pub mod discovery;
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/lib.rs"));
|
||||
pub mod spirc;
|
||||
|
|
|
@ -64,71 +64,110 @@ fn now_ms() -> i64 {
|
|||
}
|
||||
|
||||
fn initial_state() -> State {
|
||||
protobuf_init!(protocol::spirc::State::new(), {
|
||||
repeat: false,
|
||||
shuffle: false,
|
||||
status: PlayStatus::kPlayStatusStop,
|
||||
position_ms: 0,
|
||||
position_measured_at: 0,
|
||||
})
|
||||
let mut frame = protocol::spirc::State::new();
|
||||
frame.set_repeat(false);
|
||||
frame.set_shuffle(false);
|
||||
frame.set_status(PlayStatus::kPlayStatusStop);
|
||||
frame.set_position_ms(0);
|
||||
frame.set_position_measured_at(0);
|
||||
frame
|
||||
}
|
||||
|
||||
fn initial_device_state(config: ConnectConfig, volume: u16) -> DeviceState {
|
||||
protobuf_init!(DeviceState::new(), {
|
||||
sw_version: version::version_string(),
|
||||
is_active: false,
|
||||
can_play: true,
|
||||
volume: volume as u32,
|
||||
name: config.name,
|
||||
capabilities => [
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kCanBePlayer,
|
||||
intValue => [1]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kDeviceType,
|
||||
intValue => [config.device_type as i64]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kGaiaEqConnectId,
|
||||
intValue => [1]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kSupportsLogout,
|
||||
intValue => [0]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kIsObservable,
|
||||
intValue => [1]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kVolumeSteps,
|
||||
intValue => [64]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kSupportedContexts,
|
||||
stringValue => [
|
||||
"album",
|
||||
"playlist",
|
||||
"search",
|
||||
"inbox",
|
||||
"toplist",
|
||||
"starred",
|
||||
"publishedstarred",
|
||||
"track",
|
||||
]
|
||||
},
|
||||
@{
|
||||
typ: protocol::spirc::CapabilityType::kSupportedTypes,
|
||||
stringValue => [
|
||||
"audio/local",
|
||||
"audio/track",
|
||||
"local",
|
||||
"track",
|
||||
]
|
||||
}
|
||||
],
|
||||
})
|
||||
{
|
||||
let mut msg = DeviceState::new();
|
||||
msg.set_sw_version(version::version_string());
|
||||
msg.set_is_active(false);
|
||||
msg.set_can_play(true);
|
||||
msg.set_volume(volume as u32);
|
||||
msg.set_name(config.name);
|
||||
{
|
||||
let repeated = msg.mut_capabilities();
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kCanBePlayer);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(1)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kDeviceType);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(config.device_type as i64)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kGaiaEqConnectId);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(1)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kSupportsLogout);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(0)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kIsObservable);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(1)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kVolumeSteps);
|
||||
{
|
||||
let repeated = msg.mut_intValue();
|
||||
repeated.push(64)
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kSupportedContexts);
|
||||
{
|
||||
let repeated = msg.mut_stringValue();
|
||||
repeated.push(::std::convert::Into::into("album"));
|
||||
repeated.push(::std::convert::Into::into("playlist"));
|
||||
repeated.push(::std::convert::Into::into("search"));
|
||||
repeated.push(::std::convert::Into::into("inbox"));
|
||||
repeated.push(::std::convert::Into::into("toplist"));
|
||||
repeated.push(::std::convert::Into::into("starred"));
|
||||
repeated.push(::std::convert::Into::into("publishedstarred"));
|
||||
repeated.push(::std::convert::Into::into("track"))
|
||||
};
|
||||
msg
|
||||
};
|
||||
{
|
||||
let msg = repeated.push_default();
|
||||
msg.set_typ(protocol::spirc::CapabilityType::kSupportedTypes);
|
||||
{
|
||||
let repeated = msg.mut_stringValue();
|
||||
repeated.push(::std::convert::Into::into("audio/local"));
|
||||
repeated.push(::std::convert::Into::into("audio/track"));
|
||||
repeated.push(::std::convert::Into::into("local"));
|
||||
repeated.push(::std::convert::Into::into("track"))
|
||||
};
|
||||
msg
|
||||
};
|
||||
};
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
fn volume_to_mixer(volume: u16) -> u16 {
|
||||
|
@ -628,9 +667,13 @@ impl SpircTask {
|
|||
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();
|
||||
|
||||
self.state.set_playing_track_index(index);
|
||||
self.state.set_track(tracks.into_iter().cloned().collect());
|
||||
self.state.set_context_uri(context_uri);
|
||||
self.state.set_repeat(frame.get_state().get_repeat());
|
||||
self.state.set_shuffle(frame.get_state().get_shuffle());
|
||||
}
|
||||
|
||||
fn load_track(&mut self, play: bool) {
|
||||
|
@ -678,17 +721,14 @@ struct CommandSender<'a> {
|
|||
|
||||
impl<'a> CommandSender<'a> {
|
||||
fn new(spirc: &'a mut SpircTask, cmd: MessageType) -> CommandSender {
|
||||
let frame = protobuf_init!(protocol::spirc::Frame::new(), {
|
||||
version: 1,
|
||||
protocol_version: "2.0.0",
|
||||
ident: spirc.ident.clone(),
|
||||
seq_nr: spirc.sequence.get(),
|
||||
typ: cmd,
|
||||
|
||||
device_state: spirc.device.clone(),
|
||||
state_update_id: now_ms(),
|
||||
});
|
||||
|
||||
let mut frame = protocol::spirc::Frame::new();
|
||||
frame.set_version(1);
|
||||
frame.set_protocol_version(::std::convert::Into::into("2.0.0"));
|
||||
frame.set_ident(spirc.ident.clone());
|
||||
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());
|
||||
CommandSender {
|
||||
spirc: spirc,
|
||||
frame: frame,
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use bytes::Bytes;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha1::Sha1;
|
||||
use futures::{Async, Future, IntoFuture, Poll, Stream};
|
||||
use futures::sync::mpsc;
|
||||
use std::io;
|
||||
|
@ -45,12 +43,6 @@ static SESSION_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
|
|||
#[derive(Clone)]
|
||||
pub struct Session(Arc<SessionInternal>);
|
||||
|
||||
pub fn device_id(name: &str) -> String {
|
||||
let mut h = Sha1::new();
|
||||
h.input_str(name);
|
||||
h.result_str()
|
||||
}
|
||||
|
||||
impl Session {
|
||||
pub fn connect(
|
||||
config: SessionConfig,
|
||||
|
|
|
@ -2,7 +2,7 @@ use std;
|
|||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub struct u128 {
|
||||
pub(crate) struct u128 {
|
||||
high: u64,
|
||||
low: u64,
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use std::ops::{Mul, Rem, Shr};
|
|||
|
||||
mod int128;
|
||||
|
||||
pub use util::int128::u128;
|
||||
pub(crate) use util::int128::u128;
|
||||
|
||||
pub fn rand_vec<G: Rng, R: Rand>(rng: &mut G, size: usize) -> Vec<R> {
|
||||
rng.gen_iter().take(size).collect()
|
||||
|
|
|
@ -5,12 +5,13 @@ use std::env;
|
|||
use tokio_core::reactor::Core;
|
||||
|
||||
use librespot::core::authentication::Credentials;
|
||||
use librespot::playback::config::{PlayerConfig, SessionConfig};
|
||||
use librespot::core::config::SessionConfig;
|
||||
use librespot::playback::config::PlayerConfig;
|
||||
use librespot::core::session::Session;
|
||||
use librespot::core::spotify_id::SpotifyId;
|
||||
|
||||
use librespot::audio_backend;
|
||||
use librespot::player::Player;
|
||||
use librespot::playback::audio_backend;
|
||||
use librespot::playback::player::Player;
|
||||
|
||||
fn main() {
|
||||
let mut core = Core::new().unwrap();
|
||||
|
|
|
@ -13,6 +13,7 @@ path = "../metadata"
|
|||
[dependencies]
|
||||
futures = "0.1.8"
|
||||
log = "0.3.5"
|
||||
byteorder = "1.2.1"
|
||||
|
||||
alsa = { git = "https://github.com/plietar/rust-alsa", optional = true }
|
||||
portaudio-rs = { version = "0.3.0", optional = true }
|
||||
|
|
|
@ -28,12 +28,16 @@ impl Default for Bitrate {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct PlayerConfig {
|
||||
pub bitrate: Bitrate,
|
||||
pub normalisation: bool,
|
||||
pub normalisation_pregain: f32,
|
||||
}
|
||||
|
||||
impl Default for PlayerConfig {
|
||||
fn default() -> PlayerConfig {
|
||||
PlayerConfig {
|
||||
bitrate: Bitrate::default(),
|
||||
normalisation: false,
|
||||
normalisation_pregain: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
#[macro_use] extern crate log;
|
||||
|
||||
extern crate futures;
|
||||
extern crate byteorder;
|
||||
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
extern crate alsa;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use byteorder::{LittleEndian, ReadBytesExt};
|
||||
use futures::sync::oneshot;
|
||||
use futures::{future, Future};
|
||||
use futures;
|
||||
|
@ -61,6 +62,50 @@ pub enum PlayerEvent {
|
|||
}
|
||||
|
||||
type PlayerEventChannel = futures::sync::mpsc::UnboundedReceiver<PlayerEvent>;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct NormalisationData {
|
||||
track_gain_db: f32,
|
||||
track_peak: f32,
|
||||
album_gain_db: f32,
|
||||
album_peak: f32,
|
||||
}
|
||||
|
||||
impl NormalisationData {
|
||||
fn parse_from_file<T: Read + Seek>(mut file: T) -> Result<NormalisationData> {
|
||||
const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144;
|
||||
file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET)).unwrap();
|
||||
|
||||
let track_gain_db = file.read_f32::<LittleEndian>().unwrap();
|
||||
let track_peak = file.read_f32::<LittleEndian>().unwrap();
|
||||
let album_gain_db = file.read_f32::<LittleEndian>().unwrap();
|
||||
let album_peak = file.read_f32::<LittleEndian>().unwrap();
|
||||
|
||||
let r = NormalisationData {
|
||||
track_gain_db: track_gain_db,
|
||||
track_peak: track_peak,
|
||||
album_gain_db: album_gain_db,
|
||||
album_peak: album_peak,
|
||||
};
|
||||
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
|
||||
let mut normalisation_factor = f32::powf(10.0, (data.track_gain_db + config.normalisation_pregain) / 20.0);
|
||||
|
||||
if normalisation_factor * data.track_peak > 1.0 {
|
||||
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
||||
normalisation_factor = 1.0 / data.track_peak;
|
||||
}
|
||||
|
||||
debug!("Normalisation Data: {:?}", data);
|
||||
debug!("Applied normalisation factor: {}", normalisation_factor);
|
||||
|
||||
normalisation_factor
|
||||
}
|
||||
}
|
||||
|
||||
impl Player {
|
||||
pub fn new<F>(config: PlayerConfig, session: Session,
|
||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||
|
@ -142,11 +187,13 @@ enum PlayerState {
|
|||
track_id: SpotifyId,
|
||||
decoder: Decoder,
|
||||
end_of_track: oneshot::Sender<()>,
|
||||
normalisation_factor: f32,
|
||||
},
|
||||
Playing {
|
||||
track_id: SpotifyId,
|
||||
decoder: Decoder,
|
||||
end_of_track: oneshot::Sender<()>,
|
||||
normalisation_factor: f32,
|
||||
},
|
||||
EndOfTrack { track_id: SpotifyId },
|
||||
Invalid,
|
||||
|
@ -176,7 +223,7 @@ impl PlayerState {
|
|||
use self::PlayerState::*;
|
||||
match mem::replace(self, Invalid) {
|
||||
Playing { track_id, end_of_track, ..} => {
|
||||
end_of_track.send(());
|
||||
let _ = end_of_track.send(());
|
||||
*self = EndOfTrack { track_id };
|
||||
},
|
||||
_ => panic!("Called playing_to_end_of_track in non-playing state.")
|
||||
|
@ -186,11 +233,12 @@ impl PlayerState {
|
|||
fn paused_to_playing(&mut self) {
|
||||
use self::PlayerState::*;
|
||||
match ::std::mem::replace(self, Invalid) {
|
||||
Paused { decoder, end_of_track, track_id } => {
|
||||
Paused { track_id, decoder, end_of_track, normalisation_factor } => {
|
||||
*self = Playing {
|
||||
track_id: track_id,
|
||||
decoder: decoder,
|
||||
end_of_track: end_of_track,
|
||||
track_id: track_id,
|
||||
normalisation_factor: normalisation_factor,
|
||||
};
|
||||
}
|
||||
_ => panic!("invalid state"),
|
||||
|
@ -200,11 +248,12 @@ impl PlayerState {
|
|||
fn playing_to_paused(&mut self) {
|
||||
use self::PlayerState::*;
|
||||
match ::std::mem::replace(self, Invalid) {
|
||||
Playing { decoder, end_of_track, track_id } => {
|
||||
Playing { track_id, decoder, end_of_track, normalisation_factor } => {
|
||||
*self = Paused {
|
||||
track_id: track_id,
|
||||
decoder: decoder,
|
||||
end_of_track: end_of_track,
|
||||
track_id: track_id,
|
||||
normalisation_factor: normalisation_factor,
|
||||
};
|
||||
}
|
||||
_ => panic!("invalid state"),
|
||||
|
@ -248,14 +297,17 @@ impl PlayerInternal {
|
|||
}
|
||||
|
||||
if self.sink_running {
|
||||
let packet = if let PlayerState::Playing { ref mut decoder, .. } = self.state {
|
||||
let mut current_normalisation_factor: f32 = 1.0;
|
||||
|
||||
let packet = if let PlayerState::Playing { ref mut decoder, normalisation_factor, .. } = self.state {
|
||||
current_normalisation_factor = normalisation_factor;
|
||||
Some(decoder.next_packet().expect("Vorbis error"))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(packet) = packet {
|
||||
self.handle_packet(packet);
|
||||
self.handle_packet(packet, current_normalisation_factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -279,13 +331,19 @@ impl PlayerInternal {
|
|||
self.sink_running = false;
|
||||
}
|
||||
|
||||
fn handle_packet(&mut self, packet: Option<VorbisPacket>) {
|
||||
fn handle_packet(&mut self, packet: Option<VorbisPacket>, normalisation_factor: f32) {
|
||||
match packet {
|
||||
Some(mut packet) => {
|
||||
if let Some(ref editor) = self.audio_filter {
|
||||
editor.modify_stream(&mut packet.data_mut())
|
||||
};
|
||||
|
||||
if self.config.normalisation && normalisation_factor != 1.0 {
|
||||
for x in packet.data_mut().iter_mut() {
|
||||
*x = (*x as f32 * normalisation_factor) as i16;
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(err) = self.sink.write(&packet.data()) {
|
||||
error!("Could not write audio: {}", err);
|
||||
self.stop_sink();
|
||||
|
@ -308,7 +366,7 @@ impl PlayerInternal {
|
|||
}
|
||||
|
||||
match self.load_track(track_id, position as i64) {
|
||||
Some(decoder) => {
|
||||
Some((decoder, normalisation_factor)) => {
|
||||
if play {
|
||||
match self.state {
|
||||
PlayerState::Playing { track_id: old_track_id, ..}
|
||||
|
@ -326,12 +384,14 @@ impl PlayerInternal {
|
|||
track_id: track_id,
|
||||
decoder: decoder,
|
||||
end_of_track: end_of_track,
|
||||
normalisation_factor: normalisation_factor,
|
||||
};
|
||||
} else {
|
||||
self.state = PlayerState::Paused {
|
||||
track_id: track_id,
|
||||
decoder: decoder,
|
||||
end_of_track: end_of_track,
|
||||
normalisation_factor: normalisation_factor,
|
||||
};
|
||||
match self.state {
|
||||
PlayerState::Playing { track_id: old_track_id, ..}
|
||||
|
@ -422,7 +482,7 @@ impl PlayerInternal {
|
|||
}
|
||||
}
|
||||
|
||||
fn load_track(&self, track_id: SpotifyId, position: i64) -> Option<Decoder> {
|
||||
fn load_track(&self, track_id: SpotifyId, position: i64) -> Option<(Decoder, f32)> {
|
||||
let track = Track::get(&self.session, track_id).wait().unwrap();
|
||||
|
||||
info!("Loading track \"{}\"", track.name);
|
||||
|
@ -452,7 +512,18 @@ impl PlayerInternal {
|
|||
let key = self.session.audio_key().request(track.id, file_id).wait().unwrap();
|
||||
|
||||
let encrypted_file = AudioFile::open(&self.session, file_id).wait().unwrap();
|
||||
let audio_file = Subfile::new(AudioDecrypt::new(key, encrypted_file), 0xa7);
|
||||
|
||||
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
||||
|
||||
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
||||
Ok(normalisation_data) => NormalisationData::get_factor(&self.config, normalisation_data),
|
||||
Err(_) => {
|
||||
warn!("Unable to extract normalisation data, using default value.");
|
||||
1.0 as f32
|
||||
},
|
||||
};
|
||||
|
||||
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
||||
|
||||
let mut decoder = VorbisDecoder::new(audio_file).unwrap();
|
||||
|
||||
|
@ -463,7 +534,7 @@ impl PlayerInternal {
|
|||
|
||||
info!("Track \"{}\" loaded", track.name);
|
||||
|
||||
Some(decoder)
|
||||
Some((decoder, normalisation_factor))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -6,6 +6,7 @@ extern crate librespot;
|
|||
extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_signal;
|
||||
extern crate crypto;
|
||||
|
||||
use env_logger::LogBuilder;
|
||||
use futures::{Future, Async, Poll, Stream};
|
||||
|
@ -18,6 +19,8 @@ use std::str::FromStr;
|
|||
use tokio_core::reactor::{Handle, Core};
|
||||
use tokio_io::IoStream;
|
||||
use std::mem;
|
||||
use crypto::digest::Digest;
|
||||
use crypto::sha1::Sha1;
|
||||
|
||||
use librespot::core::authentication::{get_credentials, Credentials};
|
||||
use librespot::core::cache::Cache;
|
||||
|
@ -35,6 +38,12 @@ use librespot::connect::spirc::{Spirc, SpircTask};
|
|||
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()
|
||||
}
|
||||
|
||||
fn usage(program: &str, opts: &getopts::Options) -> String {
|
||||
let brief = format!("Usage: {} [options]", program);
|
||||
opts.usage(&brief)
|
||||
|
@ -106,7 +115,9 @@ fn setup(args: &[String]) -> Setup {
|
|||
.optopt("", "device", "Audio device to use. Use '?' to list options if using portaudio", "DEVICE")
|
||||
.optopt("", "mixer", "Mixer to use", "MIXER")
|
||||
.optopt("", "initial-volume", "Initial volume in %, once connected (must be from 0 to 100)", "VOLUME")
|
||||
.optopt("", "zeroconf-port", "The port the internal server advertised over zeroconf uses.", "ZEROCONF_PORT");
|
||||
.optopt("", "zeroconf-port", "The port the internal server advertised over zeroconf uses.", "ZEROCONF_PORT")
|
||||
.optflag("", "enable-volume-normalisation", "Play all tracks at the same volume")
|
||||
.optopt("", "normalisation-pregain", "Pregain (dB) applied by volume normalisation", "PREGAIN");
|
||||
|
||||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
|
@ -174,7 +185,7 @@ fn setup(args: &[String]) -> Setup {
|
|||
};
|
||||
|
||||
let session_config = {
|
||||
let device_id = librespot::core::session::device_id(&name);
|
||||
let device_id = device_id(&name);
|
||||
|
||||
SessionConfig {
|
||||
user_agent: version::version_string(),
|
||||
|
@ -187,7 +198,13 @@ fn setup(args: &[String]) -> Setup {
|
|||
.map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate"))
|
||||
.unwrap_or(Bitrate::default());
|
||||
|
||||
PlayerConfig { bitrate: bitrate }
|
||||
PlayerConfig {
|
||||
bitrate: bitrate,
|
||||
normalisation: matches.opt_present("enable-volume-normalisation"),
|
||||
normalisation_pregain: matches.opt_str("normalisation-pregain")
|
||||
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
|
||||
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
||||
}
|
||||
};
|
||||
|
||||
let connect_config = {
|
||||
|
|
Loading…
Reference in a new issue