mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge https://github.com/librespot-org/librespot into gst1.0-2020
This commit is contained in:
commit
ac0df7e11f
59 changed files with 760 additions and 744 deletions
|
@ -15,7 +15,8 @@ before_cache:
|
|||
- rm -rfv target/debug/deps/liblibrespot-*
|
||||
- rm -rfv target/debug/deps/librespot-*
|
||||
- rm -rfv target/debug/{librespot,liblibrespot}.d
|
||||
- cargo clean -p librespot librespot-core librespot-connect librespot-audio librespot-metadata librespot-playback
|
||||
- rm -rfv target/debug/incremental/{build_script_build,librespot,librespot_core,librespot_connect,librespot_audio,librespot_metadata,librespot_playback,librespot_player,librespot_protocol}-*
|
||||
- cargo clean -p librespot -p librespot-core -p librespot-connect -p librespot-audio -p librespot-metadata -p librespot-playback
|
||||
|
||||
addons:
|
||||
apt:
|
||||
|
@ -28,12 +29,14 @@ addons:
|
|||
- libsdl2-dev
|
||||
|
||||
before_script:
|
||||
- rustup component add rustfmt
|
||||
- mkdir -p ~/.cargo
|
||||
- echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config
|
||||
- echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config
|
||||
- rustup target add armv7-unknown-linux-gnueabihf
|
||||
|
||||
script:
|
||||
- cargo fmt --all -- --check
|
||||
- cargo build --locked --no-default-features
|
||||
- cargo build --locked --examples
|
||||
- cargo build --locked --no-default-features --features "with-tremor"
|
||||
|
|
364
Cargo.lock
generated
364
Cargo.lock
generated
|
@ -532,19 +532,6 @@ name = "futures"
|
|||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures-cpupool"
|
||||
version = "0.1.8"
|
||||
|
@ -554,46 +541,6 @@ dependencies = [
|
|||
"num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gcc"
|
||||
version = "0.3.55"
|
||||
|
@ -624,132 +571,11 @@ dependencies = [
|
|||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glib-sys"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glob"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "gobject-sys"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"muldiv 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer-app"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-app-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-base 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer-app-sys"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-base-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer-base"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-base-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer-base-sys"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gstreamer-sys"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.3.2"
|
||||
|
@ -942,7 +768,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librespot"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -950,12 +776,12 @@ dependencies = [
|
|||
"getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"librespot-audio 0.1.0",
|
||||
"librespot-connect 0.1.0",
|
||||
"librespot-core 0.1.0",
|
||||
"librespot-metadata 0.1.0",
|
||||
"librespot-playback 0.1.0",
|
||||
"librespot-protocol 0.1.0",
|
||||
"librespot-audio 0.1.1",
|
||||
"librespot-connect 0.1.1",
|
||||
"librespot-core 0.1.1",
|
||||
"librespot-metadata 0.1.1",
|
||||
"librespot-playback 0.1.1",
|
||||
"librespot-protocol 0.1.1",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -971,7 +797,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librespot-audio"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -979,7 +805,7 @@ dependencies = [
|
|||
"bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"librespot-core 0.1.0",
|
||||
"librespot-core 0.1.1",
|
||||
"librespot-tremor 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -990,7 +816,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librespot-connect"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1000,9 +826,9 @@ dependencies = [
|
|||
"hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libmdns 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"librespot-core 0.1.0",
|
||||
"librespot-playback 0.1.0",
|
||||
"librespot-protocol 0.1.0",
|
||||
"librespot-core 0.1.1",
|
||||
"librespot-playback 0.1.1",
|
||||
"librespot-protocol 0.1.1",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1017,7 +843,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librespot-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1030,7 +856,7 @@ dependencies = [
|
|||
"hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"hyper-proxy 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"librespot-protocol 0.1.0",
|
||||
"librespot-protocol 0.1.1",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1053,12 +879,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librespot-metadata"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"librespot-core 0.1.0",
|
||||
"librespot-protocol 0.1.0",
|
||||
"librespot-core 0.1.1",
|
||||
"librespot-protocol 0.1.1",
|
||||
"linear-map 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1066,31 +892,28 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "librespot-playback"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"alsa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"cpal 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"gstreamer-app 0.15.0 (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.65 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"libpulse-sys 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"librespot-audio 0.1.0",
|
||||
"librespot-core 0.1.0",
|
||||
"librespot-metadata 0.1.0",
|
||||
"librespot-audio 0.1.1",
|
||||
"librespot-core 0.1.1",
|
||||
"librespot-metadata 0.1.1",
|
||||
"log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"portaudio-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rodio 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zerocopy 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "librespot-protocol"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
dependencies = [
|
||||
"protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -1238,11 +1061,6 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "muldiv"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.4.0"
|
||||
|
@ -1360,16 +1178,6 @@ dependencies = [
|
|||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.8"
|
||||
|
@ -1438,26 +1246,6 @@ dependencies = [
|
|||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste-impl"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.3.0"
|
||||
|
@ -1482,11 +1270,6 @@ name = "percent-encoding"
|
|||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "pin-utils"
|
||||
version = "0.1.0-alpha.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.15"
|
||||
|
@ -1516,21 +1299,6 @@ name = "ppv-lite86"
|
|||
version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-nested"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "0.2.3"
|
||||
|
@ -1547,14 +1315,6 @@ dependencies = [
|
|||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "protobuf"
|
||||
version = "2.8.1"
|
||||
|
@ -1598,14 +1358,6 @@ dependencies = [
|
|||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.3.23"
|
||||
|
@ -1974,6 +1726,11 @@ dependencies = [
|
|||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shell-words"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.1.10"
|
||||
|
@ -2078,16 +1835,6 @@ dependencies = [
|
|||
"unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.10.2"
|
||||
|
@ -2446,11 +2193,6 @@ name = "unicode-xid"
|
|||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "1.7.2"
|
||||
|
@ -2620,25 +2362,6 @@ dependencies = [
|
|||
"winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"zerocopy-derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9"
|
||||
"checksum aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee"
|
||||
|
@ -2703,27 +2426,12 @@ dependencies = [
|
|||
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
|
||||
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
|
||||
"checksum futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869"
|
||||
"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86"
|
||||
"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866"
|
||||
"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4"
|
||||
"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231"
|
||||
"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764"
|
||||
"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9"
|
||||
"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76"
|
||||
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
|
||||
"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
|
||||
"checksum getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "72327b15c228bfe31f1390f93dd5e9279587f0463836393c9df719ce62a3e450"
|
||||
"checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55"
|
||||
"checksum glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "121c502fc6895e62d2ce084e677d3289ccbdd7f56edd4ac9a5ab8bd95d4a8670"
|
||||
"checksum glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2"
|
||||
"checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb"
|
||||
"checksum gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9"
|
||||
"checksum gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d9ea04f6e746e90d979eaf5b55a9125fd159e58959f203a2f3fbc4b2a93b77"
|
||||
"checksum gstreamer-app 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe16616846b453c5d976cee9e3617ec4b098bd160d95883a455c43b4e31a3b56"
|
||||
"checksum gstreamer-app-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bf869ce152c23bca5d761ab62146b47f750d0b28d4d499731857532897d48167"
|
||||
"checksum gstreamer-base 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1eaec2ff20b049560762f83545d95b33fd1533512792f37a9a3f6800e45da42b"
|
||||
"checksum gstreamer-base-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ba384f52174b3c586593fca32642680a9e67961fea9f4cd8419f678965023bed"
|
||||
"checksum gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db"
|
||||
"checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
||||
"checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
|
||||
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
||||
|
@ -2760,7 +2468,6 @@ dependencies = [
|
|||
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
|
||||
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
|
||||
"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
|
||||
"checksum muldiv 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "451a9a05d2a32c566c897835e0ea95cf79ed2fdfe957924045a1721a36c9980f"
|
||||
"checksum multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb04b9f127583ed176e163fb9ec6f3e793b87e21deedd5734a69386a18a0151"
|
||||
"checksum nalgebra 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e12856109b5cb8e2934b5e45e4624839416e1c6c1f7d286711a7a66b79db29d"
|
||||
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
|
||||
|
@ -2773,7 +2480,6 @@ dependencies = [
|
|||
"checksum num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fcb0cf31fb3ff77e6d2a6ebd6800df7fdcd106f2ad89113c9130bcd07f93dffc"
|
||||
"checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09"
|
||||
"checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e"
|
||||
"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454"
|
||||
"checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32"
|
||||
"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273"
|
||||
"checksum ogg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d79f1db9148be9d0e174bb3ac890f6030fcb1ed947267c5a91ee4c91b5a91e15"
|
||||
|
@ -2782,28 +2488,21 @@ dependencies = [
|
|||
"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13"
|
||||
"checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337"
|
||||
"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9"
|
||||
"checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49"
|
||||
"checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5"
|
||||
"checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
|
||||
"checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
|
||||
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
|
||||
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
|
||||
"checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af"
|
||||
"checksum portaudio-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fc0e6b38f00fae9dde9a9832a2b54405988c6dcaf2870e6f9551546b447bbd7f"
|
||||
"checksum portaudio-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5194a4fa953b4ffd851c320ef6f0484cd7278cb7169ea9d6c433e49b23f7b7f5"
|
||||
"checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b"
|
||||
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
|
||||
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
|
||||
"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0"
|
||||
"checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759"
|
||||
"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27"
|
||||
"checksum protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20"
|
||||
"checksum protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a"
|
||||
"checksum protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c1646acda5319f5b28b0bff4a484324df43ddae2c0f5a3f3e63c0b26095cd600"
|
||||
"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
|
||||
"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408"
|
||||
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
|
||||
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
|
||||
"checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c"
|
||||
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
"checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
|
||||
|
@ -2847,6 +2546,7 @@ dependencies = [
|
|||
"checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68"
|
||||
"checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d"
|
||||
"checksum shannon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ea5b41c9427b56caa7b808cb548a04fb50bb5b9e98590b53f28064ff4174561"
|
||||
"checksum shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a"
|
||||
"checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68"
|
||||
"checksum signal-hook-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "913661ac8848a61e39684a3c3e7a7a14a4deec7f54b4976d0641e70dda3939b1"
|
||||
"checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23"
|
||||
|
@ -2862,7 +2562,6 @@ dependencies = [
|
|||
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
"checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||
"checksum syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)" = "eadc09306ca51a40555dd6fc2b415538e9e18bc9f870e47b1a524a79fe2dcf5e"
|
||||
"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
|
||||
"checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f"
|
||||
"checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5"
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
|
@ -2896,7 +2595,6 @@ dependencies = [
|
|||
"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
|
||||
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
|
||||
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
|
||||
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
|
||||
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
|
||||
"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde"
|
||||
"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
|
||||
|
@ -2918,5 +2616,3 @@ dependencies = [
|
|||
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
|
||||
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
|
||||
"checksum zerocopy 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "992b9b31f80fd4a167f903f879b8ca43d6716cc368ea01df90538baa2dd34056"
|
||||
"checksum zerocopy-derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b090467ecd0624026e8a6405d343ac7382592530d54881330b3fc8e400280fa5"
|
||||
|
|
15
Cargo.toml
15
Cargo.toml
|
@ -1,12 +1,13 @@
|
|||
[package]
|
||||
name = "librespot"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Librespot Org"]
|
||||
license = "MIT"
|
||||
description = "An open source client library for Spotify, with support for Spotify Connect"
|
||||
keywords = ["spotify"]
|
||||
repository = "https://github.com/librespot-org/librespot"
|
||||
readme = "README.md"
|
||||
edition = "2018"
|
||||
|
||||
[workspace]
|
||||
|
||||
|
@ -21,22 +22,22 @@ doc = false
|
|||
|
||||
[dependencies.librespot-audio]
|
||||
path = "audio"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-connect]
|
||||
path = "connect"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-core]
|
||||
path = "core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-metadata]
|
||||
path = "metadata"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-playback]
|
||||
path = "playback"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-protocol]
|
||||
path = "protocol"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.10"
|
||||
|
|
|
@ -2,13 +2,10 @@
|
|||
[![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources)
|
||||
[![Crates.io](https://img.shields.io/crates/v/librespot.svg)](https://crates.io/crates/librespot)
|
||||
|
||||
Current maintainer is @awiouy folks.
|
||||
Current maintainer is [@awiouy](https://github.com/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.
|
||||
*librespot* is an open source client library for Spotify. It enables applications to use Spotify's service to control and play music via various backends, and to act as a Spotify Connect receiver. It is an alternative to the official and [now deprecated](https://pyspotify.mopidy.com/en/latest/#libspotify-s-deprecation) closed-source `libspotify`. Additionally, it will provide extra features which are not available in the official library.
|
||||
|
||||
_Note: librespot only works with Spotify Premium. This will remain the case for the foreseeable 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._
|
||||
|
||||
|
@ -31,7 +28,6 @@ There is some brief documentation on how the protocol works in the [docs](https:
|
|||
If you wish to learn more about how librespot works overall, the best way is to simply read the code, and ask any questions you have in our [Gitter Room](https://gitter.im/librespot-org/spotify-connect-resources).
|
||||
|
||||
# Issues
|
||||
|
||||
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
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
[package]
|
||||
name = "librespot-audio"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||
description="The audio fetching and processing logic for librespot"
|
||||
license="MIT"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies.librespot-core]
|
||||
path = "../core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
bit-set = "0.5"
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
use std::io;
|
||||
|
||||
use aes_ctr::Aes128Ctr;
|
||||
use aes_ctr::stream_cipher::{
|
||||
NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek
|
||||
};
|
||||
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
||||
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek};
|
||||
use aes_ctr::Aes128Ctr;
|
||||
|
||||
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,
|
||||
0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93,
|
||||
];
|
||||
|
||||
pub struct AudioDecrypt<T: io::Read> {
|
||||
|
@ -30,7 +27,7 @@ impl<T: io::Read> AudioDecrypt<T> {
|
|||
|
||||
impl<T: io::Read> io::Read for AudioDecrypt<T> {
|
||||
fn read(&mut self, output: &mut [u8]) -> io::Result<usize> {
|
||||
let len = try!(self.reader.read(output));
|
||||
let len = self.reader.read(output)?;
|
||||
|
||||
self.cipher.apply_keystream(&mut output[..len]);
|
||||
|
||||
|
@ -40,7 +37,7 @@ 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 newpos = self.reader.seek(pos)?;
|
||||
|
||||
self.cipher.seek(newpos);
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use crate::range_set::{Range, RangeSet};
|
||||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||
use bytes::Bytes;
|
||||
use futures::sync::{mpsc, oneshot};
|
||||
use futures::Stream;
|
||||
use futures::{Async, Future, Poll};
|
||||
use range_set::{Range, RangeSet};
|
||||
use std::cmp::{max, min};
|
||||
use std::fs;
|
||||
use std::io::{self, Read, Seek, SeekFrom, Write};
|
||||
|
@ -446,7 +446,7 @@ impl AudioFile {
|
|||
channel_tx: None,
|
||||
stream_shared: None,
|
||||
file_size: file.metadata().unwrap().len() as usize,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -514,7 +514,10 @@ impl AudioFileFetchDataReceiver {
|
|||
request_length: usize,
|
||||
request_sent_time: Instant,
|
||||
) -> AudioFileFetchDataReceiver {
|
||||
let measure_ping_time = shared.number_of_open_requests.load(atomic::Ordering::SeqCst) == 0;
|
||||
let measure_ping_time = shared
|
||||
.number_of_open_requests
|
||||
.load(atomic::Ordering::SeqCst)
|
||||
== 0;
|
||||
|
||||
shared
|
||||
.number_of_open_requests
|
||||
|
@ -562,7 +565,8 @@ impl Future for AudioFileFetchDataReceiver {
|
|||
if let Some(request_sent_time) = self.request_sent_time {
|
||||
let duration = Instant::now() - request_sent_time;
|
||||
let duration_ms: u64;
|
||||
if 0.001 * (duration.as_millis() as f64) > MAXIMUM_ASSUMED_PING_TIME_SECONDS
|
||||
if 0.001 * (duration.as_millis() as f64)
|
||||
> MAXIMUM_ASSUMED_PING_TIME_SECONDS
|
||||
{
|
||||
duration_ms = (MAXIMUM_ASSUMED_PING_TIME_SECONDS * 1000.0) as u64;
|
||||
} else {
|
||||
|
@ -714,8 +718,13 @@ impl AudioFileFetch {
|
|||
ranges_to_request.subtract_range_set(&download_status.requested);
|
||||
|
||||
for range in ranges_to_request.iter() {
|
||||
let (_headers, data) =
|
||||
request_range(&self.session, self.shared.file_id, range.start, range.length).split();
|
||||
let (_headers, data) = request_range(
|
||||
&self.session,
|
||||
self.shared.file_id,
|
||||
range.start,
|
||||
range.length,
|
||||
)
|
||||
.split();
|
||||
|
||||
download_status.requested.add_range(range);
|
||||
|
||||
|
@ -749,7 +758,10 @@ impl AudioFileFetch {
|
|||
// download data from after the current read position first
|
||||
let mut tail_end = RangeSet::new();
|
||||
let read_position = self.shared.read_position.load(atomic::Ordering::Relaxed);
|
||||
tail_end.add_range(&Range::new(read_position, self.shared.file_size - read_position));
|
||||
tail_end.add_range(&Range::new(
|
||||
read_position,
|
||||
self.shared.file_size - read_position,
|
||||
));
|
||||
let tail_end = tail_end.intersection(&missing_data);
|
||||
|
||||
if !tail_end.is_empty() {
|
||||
|
@ -794,8 +806,9 @@ impl AudioFileFetch {
|
|||
let ping_time_ms: usize = match self.network_response_times_ms.len() {
|
||||
1 => self.network_response_times_ms[0] as usize,
|
||||
2 => {
|
||||
((self.network_response_times_ms[0] + self.network_response_times_ms[1]) / 2)
|
||||
as usize
|
||||
((self.network_response_times_ms[0]
|
||||
+ self.network_response_times_ms[1])
|
||||
/ 2) as usize
|
||||
}
|
||||
3 => {
|
||||
let mut times = self.network_response_times_ms.clone();
|
||||
|
@ -863,10 +876,12 @@ impl AudioFileFetch {
|
|||
self.download_range(request.start, request.length);
|
||||
}
|
||||
Ok(Async::Ready(Some(StreamLoaderCommand::RandomAccessMode()))) => {
|
||||
*(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::RandomAccess();
|
||||
*(self.shared.download_strategy.lock().unwrap()) =
|
||||
DownloadStrategy::RandomAccess();
|
||||
}
|
||||
Ok(Async::Ready(Some(StreamLoaderCommand::StreamMode()))) => {
|
||||
*(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::Streaming();
|
||||
*(self.shared.download_strategy.lock().unwrap()) =
|
||||
DownloadStrategy::Streaming();
|
||||
}
|
||||
Ok(Async::Ready(Some(StreamLoaderCommand::Close()))) => {
|
||||
return Ok(Async::Ready(()));
|
||||
|
@ -908,15 +923,20 @@ impl Future for AudioFileFetch {
|
|||
}
|
||||
|
||||
if let DownloadStrategy::Streaming() = self.get_download_strategy() {
|
||||
let number_of_open_requests =
|
||||
self.shared.number_of_open_requests.load(atomic::Ordering::SeqCst);
|
||||
let number_of_open_requests = self
|
||||
.shared
|
||||
.number_of_open_requests
|
||||
.load(atomic::Ordering::SeqCst);
|
||||
let max_requests_to_send =
|
||||
MAX_PREFETCH_REQUESTS - min(MAX_PREFETCH_REQUESTS, number_of_open_requests);
|
||||
|
||||
if max_requests_to_send > 0 {
|
||||
let bytes_pending: usize = {
|
||||
let download_status = self.shared.download_status.lock().unwrap();
|
||||
download_status.requested.minus(&download_status.downloaded).len()
|
||||
download_status
|
||||
.requested
|
||||
.minus(&download_status.downloaded)
|
||||
.len()
|
||||
};
|
||||
|
||||
let ping_time_seconds =
|
||||
|
@ -924,9 +944,11 @@ impl Future for AudioFileFetch {
|
|||
let download_rate = self.session.channel().get_download_rate_estimate();
|
||||
|
||||
let desired_pending_bytes = max(
|
||||
(PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * self.shared.stream_data_rate as f64)
|
||||
(PREFETCH_THRESHOLD_FACTOR
|
||||
* ping_time_seconds
|
||||
* self.shared.stream_data_rate as f64) as usize,
|
||||
(FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64)
|
||||
as usize,
|
||||
(FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64) as usize,
|
||||
);
|
||||
|
||||
if bytes_pending < desired_pending_bytes {
|
||||
|
@ -1003,13 +1025,15 @@ impl Read for AudioFileStreaming {
|
|||
.unwrap()
|
||||
.0;
|
||||
}
|
||||
let available_length = download_status.downloaded.contained_length_from_value(offset);
|
||||
let available_length = download_status
|
||||
.downloaded
|
||||
.contained_length_from_value(offset);
|
||||
assert!(available_length > 0);
|
||||
drop(download_status);
|
||||
|
||||
self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap();
|
||||
let read_len = min(length, available_length);
|
||||
let read_len = try!(self.read_file.read(&mut output[..read_len]));
|
||||
let read_len = self.read_file.read(&mut output[..read_len])?;
|
||||
|
||||
if download_message_printed {
|
||||
debug!(
|
||||
|
@ -1031,7 +1055,7 @@ impl Read for AudioFileStreaming {
|
|||
|
||||
impl Seek for AudioFileStreaming {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
self.position = try!(self.read_file.seek(pos));
|
||||
self.position = self.read_file.seek(pos)?;
|
||||
// Do not seek past EOF
|
||||
self.shared
|
||||
.read_position
|
||||
|
|
|
@ -31,6 +31,6 @@ pub use fetch::{
|
|||
};
|
||||
|
||||
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
||||
pub use lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||
pub use crate::lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
||||
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
||||
|
|
|
@ -77,7 +77,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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,7 +113,8 @@ impl RangeSet {
|
|||
// the new range starts after anything we already passed and ends before the next range starts (they don't touch) -> insert it.
|
||||
self.ranges.insert(index, range.clone());
|
||||
return;
|
||||
} else if range.start <= self.ranges[index].end() && self.ranges[index].start <= range.end()
|
||||
} else if range.start <= self.ranges[index].end()
|
||||
&& self.ranges[index].start <= range.end()
|
||||
{
|
||||
// the new range overlaps (or touches) the first range. They are to be merged.
|
||||
// In addition we might have to merge further ranges in as well.
|
||||
|
@ -161,7 +162,9 @@ impl RangeSet {
|
|||
if range.end() <= self.ranges[index].start {
|
||||
// the remaining ranges are past the one to subtract. -> we're done.
|
||||
return;
|
||||
} else if range.start <= self.ranges[index].start && self.ranges[index].start < range.end() {
|
||||
} else if range.start <= self.ranges[index].start
|
||||
&& self.ranges[index].start < range.end()
|
||||
{
|
||||
// the range to subtract started before the current range and reaches into the current range
|
||||
// -> we have to remove the beginning of the range or the entire range and do the same for following ranges.
|
||||
|
||||
|
@ -223,8 +226,14 @@ impl RangeSet {
|
|||
other_index += 1;
|
||||
} else {
|
||||
// the two intervals overlap. Add the union and advance the index of the one that ends first.
|
||||
let new_start = max(self.ranges[self_index].start, other.ranges[other_index].start);
|
||||
let new_end = min(self.ranges[self_index].end(), other.ranges[other_index].end());
|
||||
let new_start = max(
|
||||
self.ranges[self_index].start,
|
||||
other.ranges[other_index].start,
|
||||
);
|
||||
let new_end = min(
|
||||
self.ranges[self_index].end(),
|
||||
other.ranges[other_index].end(),
|
||||
);
|
||||
assert!(new_start <= new_end);
|
||||
result.add_range(&Range::new(new_start, new_end - new_start));
|
||||
if self.ranges[self_index].end() <= other.ranges[other_index].end() {
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
[package]
|
||||
name = "librespot-connect"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||
description="The discovery and Spotify Connect logic for librespot"
|
||||
license="MIT"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies.librespot-core]
|
||||
path = "../core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-playback]
|
||||
path = "../playback"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-protocol]
|
||||
path = "../protocol"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.10"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::protocol::spirc::TrackRef;
|
||||
use librespot_core::spotify_id::SpotifyId;
|
||||
use protocol::spirc::TrackRef;
|
||||
|
||||
use serde;
|
||||
|
||||
|
@ -69,7 +69,7 @@ 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 v: Vec<TrackContext> = serde::Deserialize::deserialize(de)?;
|
||||
let track_vec = v
|
||||
.iter()
|
||||
.map(|v| {
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use base64;
|
||||
use sha1::{Sha1, Digest};
|
||||
use hmac::{Hmac, Mac};
|
||||
use aes_ctr::Aes128Ctr;
|
||||
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
|
||||
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
||||
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
|
||||
use aes_ctr::Aes128Ctr;
|
||||
use base64;
|
||||
use futures::sync::mpsc;
|
||||
use futures::{Future, Poll, Stream};
|
||||
use hmac::{Hmac, Mac};
|
||||
use hyper::server::{Http, Request, Response, Service};
|
||||
use hyper::{self, Get, Post, StatusCode};
|
||||
use sha1::{Digest, Sha1};
|
||||
|
||||
#[cfg(feature = "with-dns-sd")]
|
||||
use dns_sd::DNSService;
|
||||
|
@ -114,21 +114,18 @@ impl Discovery {
|
|||
let base_key = &base_key[..16];
|
||||
|
||||
let checksum_key = {
|
||||
let mut h = HmacSha1::new_varkey(base_key)
|
||||
.expect("HMAC can take key of any size");
|
||||
let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size");
|
||||
h.input(b"checksum");
|
||||
h.result().code()
|
||||
};
|
||||
|
||||
let encryption_key = {
|
||||
let mut h = HmacSha1::new_varkey(&base_key)
|
||||
.expect("HMAC can take key of any size");
|
||||
let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size");
|
||||
h.input(b"encryption");
|
||||
h.result().code()
|
||||
};
|
||||
|
||||
let mut h = HmacSha1::new_varkey(&checksum_key)
|
||||
.expect("HMAC can take key of any size");
|
||||
let mut h = HmacSha1::new_varkey(&checksum_key).expect("HMAC can take key of any size");
|
||||
h.input(encrypted);
|
||||
if let Err(_) = h.verify(cksum) {
|
||||
warn!("Login error for user {:?}: MAC mismatch", username);
|
||||
|
@ -139,7 +136,7 @@ impl Discovery {
|
|||
});
|
||||
|
||||
let body = result.to_string();
|
||||
return ::futures::finished(Response::new().with_body(body))
|
||||
return ::futures::finished(Response::new().with_body(body));
|
||||
}
|
||||
|
||||
let decrypted = {
|
||||
|
@ -152,7 +149,8 @@ impl Discovery {
|
|||
String::from_utf8(data).unwrap()
|
||||
};
|
||||
|
||||
let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
|
||||
let credentials =
|
||||
Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id);
|
||||
|
||||
self.0.tx.unbounded_send(credentials).unwrap();
|
||||
|
||||
|
@ -175,7 +173,7 @@ impl Service for Discovery {
|
|||
type Request = Request;
|
||||
type Response = Response;
|
||||
type Error = hyper::Error;
|
||||
type Future = Box<Future<Item = Response, Error = hyper::Error>>;
|
||||
type Future = Box<dyn Future<Item = Response, Error = hyper::Error>>;
|
||||
|
||||
fn call(&self, request: Request) -> Self::Future {
|
||||
let mut params = BTreeMap::new();
|
||||
|
@ -194,17 +192,18 @@ impl Service for Discovery {
|
|||
body.fold(Vec::new(), |mut acc, chunk| {
|
||||
acc.extend_from_slice(chunk.as_ref());
|
||||
Ok::<_, hyper::Error>(acc)
|
||||
}).map(move |body| {
|
||||
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
||||
params
|
||||
})
|
||||
.and_then(
|
||||
move |params| match (method, params.get("action").map(AsRef::as_ref)) {
|
||||
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
||||
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
||||
_ => this.not_found(),
|
||||
},
|
||||
),
|
||||
})
|
||||
.map(move |body| {
|
||||
params.extend(url::form_urlencoded::parse(&body).into_owned());
|
||||
params
|
||||
})
|
||||
.and_then(move |params| {
|
||||
match (method, params.get("action").map(AsRef::as_ref)) {
|
||||
(Get, Some("getInfo")) => this.handle_get_info(¶ms),
|
||||
(Post, Some("addUser")) => this.handle_add_user(¶ms),
|
||||
_ => this.not_found(),
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -235,7 +234,8 @@ pub fn discovery(
|
|||
&format!("0.0.0.0:{}", port).parse().unwrap(),
|
||||
&handle,
|
||||
move || Ok(discovery.clone()),
|
||||
).unwrap()
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
|
||||
let s_port = serve.incoming_ref().local_addr().port();
|
||||
|
@ -260,7 +260,8 @@ pub fn discovery(
|
|||
None,
|
||||
s_port,
|
||||
&["VERSION=1.0", "CPath=/"],
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
#[cfg(not(feature = "with-dns-sd"))]
|
||||
let responder = libmdns::Responder::spawn(&handle)?;
|
||||
|
|
|
@ -15,10 +15,10 @@ extern crate rand;
|
|||
extern crate tokio_core;
|
||||
extern crate url;
|
||||
|
||||
extern crate sha1;
|
||||
extern crate hmac;
|
||||
extern crate aes_ctr;
|
||||
extern crate block_modes;
|
||||
extern crate hmac;
|
||||
extern crate sha1;
|
||||
|
||||
#[cfg(feature = "with-dns-sd")]
|
||||
extern crate dns_sd;
|
||||
|
|
|
@ -9,7 +9,11 @@ use rand;
|
|||
use rand::seq::SliceRandom;
|
||||
use serde_json;
|
||||
|
||||
use context::StationContext;
|
||||
use crate::context::StationContext;
|
||||
use crate::playback::mixer::Mixer;
|
||||
use crate::playback::player::Player;
|
||||
use crate::protocol;
|
||||
use crate::protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
|
||||
use librespot_core::config::ConnectConfig;
|
||||
use librespot_core::mercury::MercuryError;
|
||||
use librespot_core::session::Session;
|
||||
|
@ -17,14 +21,10 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError};
|
|||
use librespot_core::util::SeqGenerator;
|
||||
use librespot_core::version;
|
||||
use librespot_core::volume::Volume;
|
||||
use playback::mixer::Mixer;
|
||||
use playback::player::Player;
|
||||
use protocol;
|
||||
use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef};
|
||||
|
||||
pub struct SpircTask {
|
||||
player: Player,
|
||||
mixer: Box<Mixer>,
|
||||
mixer: Box<dyn Mixer>,
|
||||
config: SpircTaskConfig,
|
||||
|
||||
sequence: SeqGenerator<u32>,
|
||||
|
@ -33,15 +33,15 @@ pub struct SpircTask {
|
|||
device: DeviceState,
|
||||
state: State,
|
||||
|
||||
subscription: Box<Stream<Item = Frame, Error = MercuryError>>,
|
||||
sender: Box<Sink<SinkItem = Frame, SinkError = MercuryError>>,
|
||||
subscription: Box<dyn Stream<Item = Frame, Error = MercuryError>>,
|
||||
sender: Box<dyn Sink<SinkItem = Frame, SinkError = MercuryError>>,
|
||||
commands: mpsc::UnboundedReceiver<SpircCommand>,
|
||||
end_of_track: Box<Future<Item = (), Error = oneshot::Canceled>>,
|
||||
end_of_track: Box<dyn Future<Item = (), Error = oneshot::Canceled>>,
|
||||
|
||||
shutdown: bool,
|
||||
session: Session,
|
||||
context_fut: Box<Future<Item = serde_json::Value, Error = MercuryError>>,
|
||||
autoplay_fut: Box<Future<Item = String, Error = MercuryError>>,
|
||||
context_fut: Box<dyn Future<Item = serde_json::Value, Error = MercuryError>>,
|
||||
autoplay_fut: Box<dyn Future<Item = String, Error = MercuryError>>,
|
||||
context: Option<StationContext>,
|
||||
}
|
||||
|
||||
|
@ -221,7 +221,7 @@ impl Spirc {
|
|||
config: ConnectConfig,
|
||||
session: Session,
|
||||
player: Player,
|
||||
mixer: Box<Mixer>,
|
||||
mixer: Box<dyn Mixer>,
|
||||
) -> (Spirc, SpircTask) {
|
||||
debug!("new Spirc[{}]", session.session_id());
|
||||
|
||||
|
@ -333,7 +333,11 @@ impl Future for SpircTask {
|
|||
progress = true;
|
||||
self.handle_frame(frame);
|
||||
}
|
||||
Async::Ready(None) => panic!("subscription terminated"),
|
||||
Async::Ready(None) => {
|
||||
error!("subscription terminated");
|
||||
self.shutdown = true;
|
||||
self.commands.close();
|
||||
}
|
||||
Async::NotReady => (),
|
||||
}
|
||||
|
||||
|
@ -526,7 +530,8 @@ impl SpircTask {
|
|||
|
||||
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_ms(frame.get_state().get_position_ms());
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
|
||||
let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay;
|
||||
|
@ -534,6 +539,8 @@ impl SpircTask {
|
|||
} else {
|
||||
info!("No more tracks left in queue");
|
||||
self.state.set_status(PlayStatus::kPlayStatusStop);
|
||||
self.player.stop();
|
||||
self.mixer.stop();
|
||||
}
|
||||
|
||||
self.notify(None);
|
||||
|
@ -670,11 +677,14 @@ impl SpircTask {
|
|||
// Removes current track if it is queued
|
||||
// Returns the index of the next track
|
||||
let current_index = self.state.get_playing_track_index() as usize;
|
||||
if self.state.get_track()[current_index].get_queued() {
|
||||
if (current_index < self.state.get_track().len())
|
||||
&& self.state.get_track()[current_index].get_queued()
|
||||
{
|
||||
self.state.mut_track().remove(current_index);
|
||||
return current_index;
|
||||
current_index
|
||||
} else {
|
||||
current_index + 1
|
||||
}
|
||||
current_index + 1
|
||||
}
|
||||
|
||||
fn handle_next(&mut self) {
|
||||
|
@ -689,7 +699,8 @@ impl SpircTask {
|
|||
tracks_len - 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:"))
|
||||
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);
|
||||
|
@ -706,12 +717,21 @@ impl SpircTask {
|
|||
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);
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
|
||||
self.load_track(continue_playing);
|
||||
if tracks_len > 0 {
|
||||
self.state.set_playing_track_index(new_index);
|
||||
self.state.set_position_ms(0);
|
||||
let now = self.now_ms();
|
||||
self.state.set_position_measured_at(now as u64);
|
||||
|
||||
self.load_track(continue_playing);
|
||||
} else {
|
||||
info!("Not playing next track because there are no more tracks left in queue.");
|
||||
self.state.set_playing_track_index(0);
|
||||
self.state.set_status(PlayStatus::kPlayStatusStop);
|
||||
self.player.stop();
|
||||
self.mixer.stop();
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_prev(&mut self) {
|
||||
|
@ -785,28 +805,48 @@ impl SpircTask {
|
|||
self.state.get_position_ms() + diff as u32
|
||||
}
|
||||
|
||||
fn resolve_station(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||
fn resolve_station(
|
||||
&self,
|
||||
uri: &str,
|
||||
) -> Box<dyn Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||
let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri);
|
||||
|
||||
self.resolve_uri(&radio_uri)
|
||||
}
|
||||
|
||||
fn resolve_autoplay_uri(&self, uri: &str) -> Box<Future<Item = String, Error = MercuryError>> {
|
||||
fn resolve_autoplay_uri(
|
||||
&self,
|
||||
uri: &str,
|
||||
) -> Box<dyn Future<Item = String, Error = MercuryError>> {
|
||||
let query_uri = format!("hm://autoplay-enabled/query?uri={}", uri);
|
||||
let request = self.session.mercury().get(query_uri);
|
||||
Box::new(request.and_then(move |response| {
|
||||
let data = response.payload.first().expect("Empty autoplay uri").to_vec();
|
||||
let autoplay_uri = String::from_utf8(data).unwrap();
|
||||
|
||||
Ok(autoplay_uri)
|
||||
if response.status_code == 200 {
|
||||
let data = response
|
||||
.payload
|
||||
.first()
|
||||
.expect("Empty autoplay uri")
|
||||
.to_vec();
|
||||
let autoplay_uri = String::from_utf8(data).unwrap();
|
||||
Ok(autoplay_uri)
|
||||
} else {
|
||||
warn!("No autoplay_uri found");
|
||||
Err(MercuryError)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
fn resolve_uri(&self, uri: &str) -> Box<Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||
fn resolve_uri(
|
||||
&self,
|
||||
uri: &str,
|
||||
) -> Box<dyn Future<Item = serde_json::Value, Error = MercuryError>> {
|
||||
let request = self.session.mercury().get(uri);
|
||||
|
||||
Box::new(request.and_then(move |response| {
|
||||
let data = response.payload.first().expect("Empty payload on context uri");
|
||||
let data = response
|
||||
.payload
|
||||
.first()
|
||||
.expect("Empty payload on context uri");
|
||||
let response: serde_json::Value = serde_json::from_slice(&data).unwrap();
|
||||
|
||||
Ok(response)
|
||||
|
@ -824,7 +864,8 @@ impl SpircTask {
|
|||
track_vec.drain(0..head);
|
||||
}
|
||||
track_vec.extend_from_slice(&new_tracks);
|
||||
self.state.set_track(protobuf::RepeatedField::from_vec(track_vec));
|
||||
self.state
|
||||
.set_track(protobuf::RepeatedField::from_vec(track_vec));
|
||||
|
||||
// Update playing index
|
||||
if let Some(new_index) = self
|
||||
|
@ -845,7 +886,9 @@ impl SpircTask {
|
|||
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:") {
|
||||
if context_uri.starts_with("spotify:station:")
|
||||
|| context_uri.starts_with("spotify:dailymix:")
|
||||
{
|
||||
self.context_fut = self.resolve_station(&context_uri);
|
||||
} else if self.config.autoplay {
|
||||
info!("Fetching autoplay context uri");
|
||||
|
@ -856,8 +899,16 @@ impl SpircTask {
|
|||
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());
|
||||
// has_shuffle/repeat seem to always be true in these replace msgs,
|
||||
// but to replicate the behaviour of the Android client we have to
|
||||
// ignore false values.
|
||||
let state = frame.get_state();
|
||||
if state.get_repeat() {
|
||||
self.state.set_repeat(true);
|
||||
}
|
||||
if state.get_shuffle() {
|
||||
self.state.set_shuffle(true);
|
||||
}
|
||||
}
|
||||
|
||||
// should this be a method of SpotifyId directly?
|
||||
|
@ -885,7 +936,8 @@ impl SpircTask {
|
|||
let track = {
|
||||
let mut track_ref = self.state.get_track()[index as usize].clone();
|
||||
let mut track_id = self.get_spotify_id_for_track(&track_ref);
|
||||
while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable {
|
||||
while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable
|
||||
{
|
||||
warn!(
|
||||
"Skipping track <{:?}> at position [{}] of {}",
|
||||
track_ref.get_uri(),
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
[package]
|
||||
name = "librespot-core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||
build = "build.rs"
|
||||
description="The core functionality provided by librespot"
|
||||
license="MIT"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies.librespot-protocol]
|
||||
path = "../protocol"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
base64 = "0.10"
|
||||
|
|
|
@ -10,7 +10,7 @@ use std::str::FromStr;
|
|||
use tokio_core::reactor::Handle;
|
||||
use url::Url;
|
||||
|
||||
error_chain!{}
|
||||
error_chain! {}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct APResolveData {
|
||||
|
@ -21,7 +21,7 @@ fn apresolve(
|
|||
handle: &Handle,
|
||||
proxy: &Option<Url>,
|
||||
ap_port: &Option<u16>,
|
||||
) -> Box<Future<Item = String, Error = Error>> {
|
||||
) -> Box<dyn Future<Item = String, Error = Error>> {
|
||||
let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL");
|
||||
let use_proxy = proxy.is_some();
|
||||
|
||||
|
@ -52,19 +52,20 @@ fn apresolve(
|
|||
})
|
||||
});
|
||||
let body = body.then(|result| result.chain_err(|| "HTTP error"));
|
||||
let body = body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response"));
|
||||
let body =
|
||||
body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response"));
|
||||
|
||||
let data =
|
||||
body.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
||||
let data = body
|
||||
.and_then(|body| serde_json::from_str::<APResolveData>(&body).chain_err(|| "invalid JSON"));
|
||||
|
||||
let p = ap_port.clone();
|
||||
|
||||
let ap = data.and_then(move |data| {
|
||||
let mut aps = data.ap_list.iter().filter(|ap| {
|
||||
if p.is_some() {
|
||||
Uri::from_str(ap)
|
||||
.ok()
|
||||
.map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap()))
|
||||
Uri::from_str(ap).ok().map_or(false, |uri| {
|
||||
uri.port().map_or(false, |port| port == p.unwrap())
|
||||
})
|
||||
} else if use_proxy {
|
||||
// It is unlikely that the proxy will accept CONNECT on anything other than 443.
|
||||
Uri::from_str(ap)
|
||||
|
@ -86,7 +87,7 @@ pub(crate) fn apresolve_or_fallback<E>(
|
|||
handle: &Handle,
|
||||
proxy: &Option<Url>,
|
||||
ap_port: &Option<u16>,
|
||||
) -> Box<Future<Item = String, Error = E>>
|
||||
) -> Box<dyn Future<Item = String, Error = E>>
|
||||
where
|
||||
E: 'static,
|
||||
{
|
||||
|
|
|
@ -5,8 +5,8 @@ use futures::{Async, Future, Poll};
|
|||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
|
||||
use spotify_id::{FileId, SpotifyId};
|
||||
use util::SeqGenerator;
|
||||
use crate::spotify_id::{FileId, SpotifyId};
|
||||
use crate::util::SeqGenerator;
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)]
|
||||
pub struct AudioKey(pub [u8; 16]);
|
||||
|
@ -35,7 +35,11 @@ impl AudioKeyManager {
|
|||
let _ = sender.send(Ok(AudioKey(key)));
|
||||
}
|
||||
0xe => {
|
||||
warn!("error audio key {:x} {:x}", data.as_ref()[0], data.as_ref()[1]);
|
||||
warn!(
|
||||
"error audio key {:x} {:x}",
|
||||
data.as_ref()[0],
|
||||
data.as_ref()[1]
|
||||
);
|
||||
let _ = sender.send(Err(AudioKeyError));
|
||||
}
|
||||
_ => (),
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
use aes::Aes192;
|
||||
use base64;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use aes::Aes192;
|
||||
use hmac::Hmac;
|
||||
use sha1::{Sha1, Digest};
|
||||
use pbkdf2::pbkdf2;
|
||||
use protobuf::ProtobufEnum;
|
||||
use serde;
|
||||
use serde_json;
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::FnOnce;
|
||||
use std::path::Path;
|
||||
|
||||
use protocol::authentication::AuthenticationType;
|
||||
use crate::protocol::authentication::AuthenticationType;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Credentials {
|
||||
|
@ -40,24 +40,24 @@ impl Credentials {
|
|||
pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials {
|
||||
fn read_u8<R: Read>(stream: &mut R) -> io::Result<u8> {
|
||||
let mut data = [0u8];
|
||||
try!(stream.read_exact(&mut data));
|
||||
stream.read_exact(&mut data)?;
|
||||
Ok(data[0])
|
||||
}
|
||||
|
||||
fn read_int<R: Read>(stream: &mut R) -> io::Result<u32> {
|
||||
let lo = try!(read_u8(stream)) as u32;
|
||||
let lo = read_u8(stream)? as u32;
|
||||
if lo & 0x80 == 0 {
|
||||
return Ok(lo);
|
||||
}
|
||||
|
||||
let hi = try!(read_u8(stream)) as u32;
|
||||
let hi = read_u8(stream)? as u32;
|
||||
Ok(lo & 0x7f | hi << 7)
|
||||
}
|
||||
|
||||
fn read_bytes<R: Read>(stream: &mut R) -> io::Result<Vec<u8>> {
|
||||
let length = try!(read_int(stream));
|
||||
let length = read_int(stream)?;
|
||||
let mut data = vec![0u8; length as usize];
|
||||
try!(stream.read_exact(&mut data));
|
||||
stream.read_exact(&mut data)?;
|
||||
|
||||
Ok(data)
|
||||
}
|
||||
|
@ -76,9 +76,9 @@ impl Credentials {
|
|||
|
||||
// decrypt data using ECB mode without padding
|
||||
let blob = {
|
||||
use aes::block_cipher_trait::BlockCipher;
|
||||
use aes::block_cipher_trait::generic_array::GenericArray;
|
||||
use aes::block_cipher_trait::generic_array::typenum::Unsigned;
|
||||
use aes::block_cipher_trait::generic_array::GenericArray;
|
||||
use aes::block_cipher_trait::BlockCipher;
|
||||
|
||||
let mut data = base64::decode(encrypted_blob).unwrap();
|
||||
let cipher = Aes192::new(GenericArray::from_slice(&key));
|
||||
|
@ -148,7 +148,7 @@ where
|
|||
T: ProtobufEnum,
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v: i32 = try!(serde::Deserialize::deserialize(de));
|
||||
let v: i32 = serde::Deserialize::deserialize(de)?;
|
||||
T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value"))
|
||||
}
|
||||
|
||||
|
@ -164,7 +164,7 @@ fn deserialize_base64<'de, D>(de: D) -> Result<Vec<u8>, D::Error>
|
|||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
let v: String = try!(serde::Deserialize::deserialize(de));
|
||||
let v: String = serde::Deserialize::deserialize(de)?;
|
||||
base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string()))
|
||||
}
|
||||
|
||||
|
@ -181,9 +181,10 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
|
|||
Some(credentials.clone())
|
||||
}
|
||||
|
||||
(Some(username), None, _) => {
|
||||
Some(Credentials::with_password(username.clone(), prompt(&username)))
|
||||
}
|
||||
(Some(username), None, _) => Some(Credentials::with_password(
|
||||
username.clone(),
|
||||
prompt(&username),
|
||||
)),
|
||||
|
||||
(None, _, Some(credentials)) => Some(credentials),
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ use std::io::Read;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use authentication::Credentials;
|
||||
use spotify_id::FileId;
|
||||
use volume::Volume;
|
||||
use crate::authentication::Credentials;
|
||||
use crate::spotify_id::FileId;
|
||||
use crate::volume::Volume;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cache {
|
||||
|
@ -80,7 +80,7 @@ impl Cache {
|
|||
File::open(self.file_path(file)).ok()
|
||||
}
|
||||
|
||||
pub fn save_file(&self, file: FileId, contents: &mut Read) {
|
||||
pub fn save_file(&self, file: FileId, contents: &mut dyn Read) {
|
||||
if self.use_audio_cache {
|
||||
let path = self.file_path(file);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ use futures::{Async, Poll, Stream};
|
|||
use std::collections::HashMap;
|
||||
use std::time::Instant;
|
||||
|
||||
use util::SeqGenerator;
|
||||
use crate::util::SeqGenerator;
|
||||
|
||||
component! {
|
||||
ChannelManager : ChannelManagerInner {
|
||||
|
@ -14,6 +14,7 @@ component! {
|
|||
download_rate_estimate: usize = 0,
|
||||
download_measurement_start: Option<Instant> = None,
|
||||
download_measurement_bytes: usize = 0,
|
||||
invalid: bool = false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,7 +47,9 @@ impl ChannelManager {
|
|||
|
||||
let seq = self.lock(|inner| {
|
||||
let seq = inner.sequence.get();
|
||||
inner.channels.insert(seq, tx);
|
||||
if !inner.invalid {
|
||||
inner.channels.insert(seq, tx);
|
||||
}
|
||||
seq
|
||||
});
|
||||
|
||||
|
@ -87,12 +90,21 @@ impl ChannelManager {
|
|||
pub fn get_download_rate_estimate(&self) -> usize {
|
||||
return self.lock(|inner| inner.download_rate_estimate);
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&self) {
|
||||
self.lock(|inner| {
|
||||
inner.invalid = true;
|
||||
// destroy the sending halves of the channels to signal everyone who is waiting for something.
|
||||
inner.channels.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Channel {
|
||||
fn recv_packet(&mut self) -> Poll<Bytes, ChannelError> {
|
||||
let (cmd, packet) = match self.receiver.poll() {
|
||||
Ok(Async::Ready(t)) => t.expect("channel closed"),
|
||||
Ok(Async::Ready(Some(t))) => t,
|
||||
Ok(Async::Ready(None)) => return Err(ChannelError), // The channel has been closed.
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(()) => unreachable!(),
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||
use url::Url;
|
||||
use uuid::Uuid;
|
||||
|
||||
use version;
|
||||
use crate::version;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionConfig {
|
||||
|
|
|
@ -88,7 +88,8 @@ impl Decoder for APCodec {
|
|||
|
||||
let mut payload = buf.split_to(size + MAC_SIZE);
|
||||
|
||||
self.decode_cipher.decrypt(&mut payload.get_mut(..size).unwrap());
|
||||
self.decode_cipher
|
||||
.decrypt(&mut payload.get_mut(..size).unwrap());
|
||||
let mac = payload.split_off(size);
|
||||
self.decode_cipher.check_mac(mac.as_ref())?;
|
||||
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha1::Sha1;
|
||||
use futures::{Async, Future, Poll};
|
||||
use hmac::{Hmac, Mac};
|
||||
use protobuf::{self, Message};
|
||||
use rand::thread_rng;
|
||||
use sha1::Sha1;
|
||||
use std::io::{self, Read};
|
||||
use std::marker::PhantomData;
|
||||
use tokio_codec::{Decoder, Framed};
|
||||
|
@ -11,10 +11,10 @@ use tokio_io::io::{read_exact, write_all, ReadExact, Window, WriteAll};
|
|||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::codec::APCodec;
|
||||
use diffie_hellman::DHLocalKeys;
|
||||
use protocol;
|
||||
use protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
|
||||
use util;
|
||||
use crate::diffie_hellman::DHLocalKeys;
|
||||
use crate::protocol;
|
||||
use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext};
|
||||
use crate::util;
|
||||
|
||||
pub struct Handshake<T> {
|
||||
keys: DHLocalKeys,
|
||||
|
@ -62,7 +62,8 @@ impl<T: AsyncRead + AsyncWrite> Future for Handshake<T> {
|
|||
.to_owned();
|
||||
|
||||
let shared_secret = self.keys.shared_secret(&remote_key);
|
||||
let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator);
|
||||
let (challenge, send_key, recv_key) =
|
||||
compute_keys(&shared_secret, &accumulator);
|
||||
let codec = APCodec::new(&send_key, &recv_key);
|
||||
|
||||
let write = client_response(connection, challenge);
|
||||
|
@ -92,7 +93,10 @@ fn client_hello<T: AsyncWrite>(connection: T, gc: Vec<u8>) -> WriteAll<T, Vec<u8
|
|||
packet
|
||||
.mut_cryptosuites_supported()
|
||||
.push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON);
|
||||
packet.mut_login_crypto_hello().mut_diffie_hellman().set_gc(gc);
|
||||
packet
|
||||
.mut_login_crypto_hello()
|
||||
.mut_diffie_hellman()
|
||||
.set_gc(gc);
|
||||
packet
|
||||
.mut_login_crypto_hello()
|
||||
.mut_diffie_hellman()
|
||||
|
@ -190,15 +194,13 @@ fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<
|
|||
|
||||
let mut data = Vec::with_capacity(0x64);
|
||||
for i in 1..6 {
|
||||
let mut mac = HmacSha1::new_varkey(&shared_secret)
|
||||
.expect("HMAC can take key of any size");
|
||||
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());
|
||||
}
|
||||
|
||||
let mut mac = HmacSha1::new_varkey(&data[..0x14])
|
||||
.expect("HMAC can take key of any size");;
|
||||
let mut mac = HmacSha1::new_varkey(&data[..0x14]).expect("HMAC can take key of any size");
|
||||
mac.input(packets);
|
||||
|
||||
(
|
||||
|
|
|
@ -8,15 +8,15 @@ use futures::{Future, Sink, Stream};
|
|||
use protobuf::{self, Message};
|
||||
use std::io;
|
||||
use std::net::ToSocketAddrs;
|
||||
use tokio_codec::Framed;
|
||||
use tokio_core::net::TcpStream;
|
||||
use tokio_core::reactor::Handle;
|
||||
use tokio_codec::Framed;
|
||||
use url::Url;
|
||||
|
||||
use authentication::Credentials;
|
||||
use version;
|
||||
use crate::authentication::Credentials;
|
||||
use crate::version;
|
||||
|
||||
use proxytunnel;
|
||||
use crate::proxytunnel;
|
||||
|
||||
pub type Transport = Framed<TcpStream, APCodec>;
|
||||
|
||||
|
@ -24,13 +24,31 @@ pub fn connect(
|
|||
addr: String,
|
||||
handle: &Handle,
|
||||
proxy: &Option<Url>,
|
||||
) -> Box<Future<Item = Transport, Error = io::Error>> {
|
||||
) -> Box<dyn Future<Item = Transport, Error = io::Error>> {
|
||||
let (addr, connect_url) = match *proxy {
|
||||
Some(ref url) => {
|
||||
info!("Using proxy \"{}\"", url);
|
||||
(url.to_socket_addrs().unwrap().next().unwrap(), Some(addr))
|
||||
match url.to_socket_addrs().and_then(|mut iter| {
|
||||
iter.next().ok_or(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Can't resolve proxy server address",
|
||||
))
|
||||
}) {
|
||||
Ok(socket_addr) => (socket_addr, Some(addr)),
|
||||
Err(error) => return Box::new(futures::future::err(error)),
|
||||
}
|
||||
}
|
||||
None => {
|
||||
match addr.to_socket_addrs().and_then(|mut iter| {
|
||||
iter.next().ok_or(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"Can't resolve server address",
|
||||
))
|
||||
}) {
|
||||
Ok(socket_addr) => (socket_addr, None),
|
||||
Err(error) => return Box::new(futures::future::err(error)),
|
||||
}
|
||||
}
|
||||
None => (addr.to_socket_addrs().unwrap().next().unwrap(), None),
|
||||
};
|
||||
|
||||
let socket = TcpStream::connect(&addr, handle);
|
||||
|
@ -48,23 +66,31 @@ pub fn authenticate(
|
|||
transport: Transport,
|
||||
credentials: Credentials,
|
||||
device_id: String,
|
||||
) -> Box<Future<Item = (Transport, Credentials), Error = io::Error>> {
|
||||
use protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
||||
use protocol::keyexchange::APLoginFailed;
|
||||
) -> Box<dyn Future<Item = (Transport, Credentials), Error = io::Error>> {
|
||||
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
||||
use crate::protocol::keyexchange::APLoginFailed;
|
||||
|
||||
let mut packet = ClientResponseEncrypted::new();
|
||||
packet.mut_login_credentials().set_username(credentials.username);
|
||||
packet.mut_login_credentials().set_typ(credentials.auth_type);
|
||||
packet
|
||||
.mut_login_credentials()
|
||||
.set_username(credentials.username);
|
||||
packet
|
||||
.mut_login_credentials()
|
||||
.set_typ(credentials.auth_type);
|
||||
packet
|
||||
.mut_login_credentials()
|
||||
.set_auth_data(credentials.auth_data);
|
||||
packet.mut_system_info().set_cpu_family(CpuFamily::CPU_UNKNOWN);
|
||||
packet
|
||||
.mut_system_info()
|
||||
.set_cpu_family(CpuFamily::CPU_UNKNOWN);
|
||||
packet.mut_system_info().set_os(Os::OS_UNKNOWN);
|
||||
packet.mut_system_info().set_system_information_string(format!(
|
||||
"librespot_{}_{}",
|
||||
version::short_sha(),
|
||||
version::build_id()
|
||||
));
|
||||
packet
|
||||
.mut_system_info()
|
||||
.set_system_information_string(format!(
|
||||
"librespot_{}_{}",
|
||||
version::short_sha(),
|
||||
version::build_id()
|
||||
));
|
||||
packet.mut_system_info().set_device_id(device_id);
|
||||
packet.set_version_string(version::version_string());
|
||||
|
||||
|
@ -77,7 +103,8 @@ pub fn authenticate(
|
|||
.and_then(|transport| transport.into_future().map_err(|(err, _stream)| err))
|
||||
.and_then(|(packet, transport)| match packet {
|
||||
Some((0xac, data)) => {
|
||||
let welcome_data: APWelcome = protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||
let welcome_data: APWelcome =
|
||||
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||
|
||||
let reusable_credentials = Credentials {
|
||||
username: welcome_data.get_canonical_username().to_owned(),
|
||||
|
@ -89,7 +116,8 @@ pub fn authenticate(
|
|||
}
|
||||
|
||||
Some((0xad, data)) => {
|
||||
let error_data: APLoginFailed = protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||
let error_data: APLoginFailed =
|
||||
protobuf::parse_from_bytes(data.as_ref()).unwrap();
|
||||
panic!(
|
||||
"Authentication failed with reason: {:?}",
|
||||
error_data.get_error_code()
|
||||
|
|
|
@ -2,17 +2,18 @@ use num_bigint::BigUint;
|
|||
use num_traits::FromPrimitive;
|
||||
use rand::Rng;
|
||||
|
||||
use util;
|
||||
use crate::util;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref DH_GENERATOR: BigUint = BigUint::from_u64(0x2).unwrap();
|
||||
pub static ref DH_PRIME: BigUint = BigUint::from_bytes_be(&[
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34,
|
||||
0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74,
|
||||
0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd,
|
||||
0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37,
|
||||
0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6,
|
||||
0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2,
|
||||
0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67,
|
||||
0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e,
|
||||
0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
|
||||
0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5,
|
||||
0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff,
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -39,7 +40,11 @@ impl DHLocalKeys {
|
|||
}
|
||||
|
||||
pub fn shared_secret(&self, remote_key: &[u8]) -> Vec<u8> {
|
||||
let shared_key = util::powm(&BigUint::from_bytes_be(remote_key), &self.private_key, &DH_PRIME);
|
||||
let shared_key = util::powm(
|
||||
&BigUint::from_bytes_be(remote_key),
|
||||
&self.private_key,
|
||||
&DH_PRIME,
|
||||
);
|
||||
shared_key.to_bytes_be()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use futures::Future;
|
||||
use serde_json;
|
||||
|
||||
use mercury::MercuryError;
|
||||
use session::Session;
|
||||
use crate::mercury::MercuryError;
|
||||
use crate::session::Session;
|
||||
|
||||
#[derive(Deserialize, Debug, Clone)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
|
@ -17,7 +17,7 @@ pub fn get_token(
|
|||
session: &Session,
|
||||
client_id: &str,
|
||||
scopes: &str,
|
||||
) -> Box<Future<Item = Token, Error = MercuryError>> {
|
||||
) -> Box<dyn Future<Item = Token, Error = MercuryError>> {
|
||||
let url = format!(
|
||||
"hm://keymaster/token/authenticated?client_id={}&scope={}",
|
||||
client_id, scopes
|
||||
|
|
|
@ -11,29 +11,29 @@ extern crate log;
|
|||
#[macro_use]
|
||||
extern crate serde_derive;
|
||||
|
||||
extern crate aes;
|
||||
extern crate base64;
|
||||
extern crate byteorder;
|
||||
extern crate bytes;
|
||||
extern crate hmac;
|
||||
extern crate httparse;
|
||||
extern crate hyper;
|
||||
extern crate hyper_proxy;
|
||||
extern crate num_bigint;
|
||||
extern crate num_integer;
|
||||
extern crate num_traits;
|
||||
extern crate pbkdf2;
|
||||
extern crate protobuf;
|
||||
extern crate rand;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate sha1;
|
||||
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,13 +1,13 @@
|
|||
use crate::protocol;
|
||||
use byteorder::{BigEndian, ByteOrder};
|
||||
use bytes::Bytes;
|
||||
use futures::sync::{mpsc, oneshot};
|
||||
use futures::{Async, Future, Poll};
|
||||
use protobuf;
|
||||
use protocol;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
|
||||
use util::SeqGenerator;
|
||||
use crate::util::SeqGenerator;
|
||||
|
||||
mod types;
|
||||
pub use self::types::*;
|
||||
|
@ -20,6 +20,7 @@ component! {
|
|||
sequence: SeqGenerator<u64> = SeqGenerator::new(0),
|
||||
pending: HashMap<Vec<u8>, MercuryPending> = HashMap::new(),
|
||||
subscriptions: Vec<(String, mpsc::UnboundedSender<MercuryResponse>)> = Vec::new(),
|
||||
invalid: bool = false,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -61,7 +62,11 @@ impl MercuryManager {
|
|||
};
|
||||
|
||||
let seq = self.next_seq();
|
||||
self.lock(|inner| inner.pending.insert(seq.clone(), pending));
|
||||
self.lock(|inner| {
|
||||
if !inner.invalid {
|
||||
inner.pending.insert(seq.clone(), pending);
|
||||
}
|
||||
});
|
||||
|
||||
let cmd = req.method.command();
|
||||
let data = req.encode(&seq);
|
||||
|
@ -95,7 +100,8 @@ impl MercuryManager {
|
|||
pub fn subscribe<T: Into<String>>(
|
||||
&self,
|
||||
uri: T,
|
||||
) -> Box<Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>> {
|
||||
) -> Box<dyn Future<Item = mpsc::UnboundedReceiver<MercuryResponse>, Error = MercuryError>>
|
||||
{
|
||||
let uri = uri.into();
|
||||
let request = self.request(MercuryRequest {
|
||||
method: MercuryMethod::SUB,
|
||||
|
@ -109,21 +115,23 @@ impl MercuryManager {
|
|||
let (tx, rx) = mpsc::unbounded();
|
||||
|
||||
manager.lock(move |inner| {
|
||||
debug!("subscribed uri={} count={}", uri, response.payload.len());
|
||||
if response.payload.len() > 0 {
|
||||
// Old subscription protocol, watch the provided list of URIs
|
||||
for sub in response.payload {
|
||||
let mut sub: protocol::pubsub::Subscription =
|
||||
protobuf::parse_from_bytes(&sub).unwrap();
|
||||
let sub_uri = sub.take_uri();
|
||||
if !inner.invalid {
|
||||
debug!("subscribed uri={} count={}", uri, response.payload.len());
|
||||
if response.payload.len() > 0 {
|
||||
// Old subscription protocol, watch the provided list of URIs
|
||||
for sub in response.payload {
|
||||
let mut sub: protocol::pubsub::Subscription =
|
||||
protobuf::parse_from_bytes(&sub).unwrap();
|
||||
let sub_uri = sub.take_uri();
|
||||
|
||||
debug!("subscribed sub_uri={}", sub_uri);
|
||||
debug!("subscribed sub_uri={}", sub_uri);
|
||||
|
||||
inner.subscriptions.push((sub_uri, tx.clone()));
|
||||
inner.subscriptions.push((sub_uri, tx.clone()));
|
||||
}
|
||||
} else {
|
||||
// New subscription protocol, watch the requested URI
|
||||
inner.subscriptions.push((uri, tx));
|
||||
}
|
||||
} else {
|
||||
// New subscription protocol, watch the requested URI
|
||||
inner.subscriptions.push((uri, tx));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -222,4 +230,13 @@ impl MercuryManager {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(&self) {
|
||||
self.lock(|inner| {
|
||||
inner.invalid = true;
|
||||
// destroy the sending halves of the channels to signal everyone who is waiting for something.
|
||||
inner.pending.clear();
|
||||
inner.subscriptions.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt};
|
|||
use protobuf::Message;
|
||||
use std::io::Write;
|
||||
|
||||
use protocol;
|
||||
use crate::protocol;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum MercuryMethod {
|
||||
|
@ -37,7 +37,8 @@ impl ToString for MercuryMethod {
|
|||
MercuryMethod::SUB => "SUB",
|
||||
MercuryMethod::UNSUB => "UNSUB",
|
||||
MercuryMethod::SEND => "SEND",
|
||||
}.to_owned()
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,9 @@ impl<T: AsyncRead + AsyncWrite> Future for ProxyTunnel<T> {
|
|||
let mut response = httparse::Response::new(&mut headers);
|
||||
let status = match response.parse(&buf) {
|
||||
Ok(status) => status,
|
||||
Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err.description())),
|
||||
Err(err) => {
|
||||
return Err(io::Error::new(io::ErrorKind::Other, err.description()));
|
||||
}
|
||||
};
|
||||
|
||||
if status.is_complete() {
|
||||
|
@ -102,7 +104,8 @@ fn proxy_connect<T: AsyncWrite>(connection: T, connect_url: &str) -> WriteAll<T,
|
|||
\r\n",
|
||||
uri.host().expect(&format!("No host in {}", uri)),
|
||||
uri.port().expect(&format!("No port in {}", uri))
|
||||
).into_bytes();
|
||||
)
|
||||
.into_bytes();
|
||||
|
||||
write_all(connection, buffer)
|
||||
}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
use std::io;
|
||||
use std::sync::{Arc, RwLock, Weak};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::{Arc, RwLock, Weak};
|
||||
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 futures::{Async, Future, IntoFuture, Poll, Stream};
|
||||
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 mercury::MercuryManager;
|
||||
use crate::apresolve::apresolve_or_fallback;
|
||||
use crate::audio_key::AudioKeyManager;
|
||||
use crate::authentication::Credentials;
|
||||
use crate::cache::Cache;
|
||||
use crate::channel::ChannelManager;
|
||||
use crate::component::Lazy;
|
||||
use crate::config::SessionConfig;
|
||||
use crate::connection;
|
||||
use crate::mercury::MercuryManager;
|
||||
|
||||
struct SessionData {
|
||||
country: String,
|
||||
|
@ -53,8 +53,9 @@ impl Session {
|
|||
credentials: Credentials,
|
||||
cache: Option<Cache>,
|
||||
handle: Handle,
|
||||
) -> Box<Future<Item = Session, Error = io::Error>> {
|
||||
let access_point = apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
|
||||
) -> Box<dyn Future<Item = Session, Error = io::Error>> {
|
||||
let access_point =
|
||||
apresolve_or_fallback::<io::Error>(&handle, &config.proxy, &config.ap_port);
|
||||
|
||||
let handle_ = handle.clone();
|
||||
let proxy = config.proxy.clone();
|
||||
|
@ -64,8 +65,9 @@ impl Session {
|
|||
});
|
||||
|
||||
let device_id = config.device_id.clone();
|
||||
let authentication = connection
|
||||
.and_then(move |connection| connection::authenticate(connection, credentials, device_id));
|
||||
let authentication = connection.and_then(move |connection| {
|
||||
connection::authenticate(connection, credentials, device_id)
|
||||
});
|
||||
|
||||
let result = authentication.map(move |(transport, reusable_credentials)| {
|
||||
info!("Authenticated as \"{}\" !", reusable_credentials.username);
|
||||
|
@ -97,7 +99,7 @@ impl Session {
|
|||
config: SessionConfig,
|
||||
cache: Option<Cache>,
|
||||
username: String,
|
||||
) -> (Session, Box<Future<Item = (), Error = io::Error>>) {
|
||||
) -> (Session, Box<dyn Future<Item = (), Error = io::Error>>) {
|
||||
let (sink, stream) = transport.split();
|
||||
|
||||
let (sender_tx, sender_rx) = mpsc::unbounded();
|
||||
|
@ -133,7 +135,11 @@ impl Session {
|
|||
.map(|_| ());
|
||||
let receiver_task = DispatchTask(stream, session.weak());
|
||||
|
||||
let task = Box::new((receiver_task, sender_task).into_future().map(|((), ())| ()));
|
||||
let task = Box::new(
|
||||
(receiver_task, sender_task)
|
||||
.into_future()
|
||||
.map(|((), ())| ()),
|
||||
);
|
||||
|
||||
(session, task)
|
||||
}
|
||||
|
@ -197,7 +203,7 @@ impl Session {
|
|||
|
||||
0x9 | 0xa => self.channel().dispatch(cmd, data),
|
||||
0xd | 0xe => self.audio_key().dispatch(cmd, data),
|
||||
0xb2...0xb6 => self.mercury().dispatch(cmd, data),
|
||||
0xb2..=0xb6 => self.mercury().dispatch(cmd, data),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
@ -237,6 +243,8 @@ impl Session {
|
|||
pub fn shutdown(&self) {
|
||||
debug!("Invalidating session[{}]", self.0.session_id);
|
||||
self.0.data.write().unwrap().invalid = true;
|
||||
self.mercury().shutdown();
|
||||
self.channel().shutdown();
|
||||
}
|
||||
|
||||
pub fn is_invalid(&self) -> bool {
|
||||
|
@ -283,13 +291,18 @@ where
|
|||
|
||||
loop {
|
||||
let (cmd, data) = match self.0.poll() {
|
||||
Ok(Async::Ready(t)) => t,
|
||||
Ok(Async::Ready(Some(t))) => t,
|
||||
Ok(Async::Ready(None)) => {
|
||||
warn!("Connection to server closed.");
|
||||
session.shutdown();
|
||||
return Ok(Async::Ready(()));
|
||||
}
|
||||
Ok(Async::NotReady) => return Ok(Async::NotReady),
|
||||
Err(e) => {
|
||||
session.shutdown();
|
||||
return Err(From::from(e));
|
||||
}
|
||||
}.expect("connection closed");
|
||||
};
|
||||
|
||||
session.dispatch(cmd, data);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ pub struct SpotifyId {
|
|||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct SpotifyIdError;
|
||||
|
||||
const BASE62_DIGITS: &'static [u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const BASE62_DIGITS: &'static [u8] =
|
||||
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
|
||||
|
||||
impl SpotifyId {
|
||||
|
|
|
@ -6,7 +6,10 @@ use std::mem;
|
|||
use std::ops::{Mul, Rem, Shr};
|
||||
|
||||
pub fn rand_vec<G: Rng>(rng: &mut G, size: usize) -> Vec<u8> {
|
||||
::std::iter::repeat(()).map(|()| rng.gen()).take(size).collect()
|
||||
::std::iter::repeat(())
|
||||
.map(|()| rng.gen())
|
||||
.take(size)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint {
|
||||
|
|
|
@ -21,7 +21,9 @@ impl Volume {
|
|||
|
||||
// write volume to file
|
||||
fn save_to_writer<W: Write>(&self, writer: &mut W) {
|
||||
writer.write_all(self.volume.to_string().as_bytes()).unwrap();
|
||||
writer
|
||||
.write_all(self.volume.to_string().as_bytes())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
extern crate librespot;
|
||||
extern crate tokio_core;
|
||||
|
||||
use std::env;
|
||||
use tokio_core::reactor::Core;
|
||||
|
||||
|
@ -37,7 +34,9 @@ fn main() {
|
|||
.run(Session::connect(session_config, credentials, None, handle))
|
||||
.unwrap();
|
||||
|
||||
let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None));
|
||||
let (player, _) = Player::new(player_config, session.clone(), None, move || {
|
||||
(backend)(None)
|
||||
});
|
||||
|
||||
println!("Playing...");
|
||||
core.run(player.load(track, true, 0)).unwrap();
|
||||
|
|
|
@ -1,11 +1,4 @@
|
|||
extern crate log;
|
||||
extern crate env_logger;
|
||||
|
||||
extern crate librespot;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate futures;
|
||||
|
||||
use env_logger;
|
||||
use std::env;
|
||||
use tokio_core::reactor::Core;
|
||||
|
||||
|
@ -13,7 +6,7 @@ use librespot::core::authentication::Credentials;
|
|||
use librespot::core::config::SessionConfig;
|
||||
use librespot::core::session::Session;
|
||||
use librespot::core::spotify_id::SpotifyId;
|
||||
use librespot::metadata::{Metadata, Track, Playlist};
|
||||
use librespot::metadata::{Metadata, Playlist, Track};
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
|
@ -32,7 +25,7 @@ fn main() {
|
|||
|
||||
let uri_split = args[3].split(":");
|
||||
let uri_parts: Vec<&str> = uri_split.collect();
|
||||
println!("{}, {}, {}",uri_parts[0], uri_parts[1], uri_parts[2]);
|
||||
println!("{}, {}, {}", uri_parts[0], uri_parts[1], uri_parts[2]);
|
||||
|
||||
let plist_uri = SpotifyId::from_base62(uri_parts[2]).unwrap();
|
||||
|
||||
|
@ -41,7 +34,7 @@ fn main() {
|
|||
.unwrap();
|
||||
|
||||
let plist = core.run(Playlist::get(&session, plist_uri)).unwrap();
|
||||
println!("{:?}",plist);
|
||||
println!("{:?}", plist);
|
||||
for track_id in plist.tracks {
|
||||
let plist_track = core.run(Track::get(&session, track_id)).unwrap();
|
||||
println!("track: {} ", plist_track.name);
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
[package]
|
||||
name = "librespot-metadata"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||
description="The metadata logic for librespot"
|
||||
license="MIT"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.3"
|
||||
|
@ -14,7 +15,7 @@ log = "0.4"
|
|||
|
||||
[dependencies.librespot-core]
|
||||
path = "../core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-protocol]
|
||||
path = "../protocol"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
|
|
@ -19,7 +19,7 @@ use librespot_core::mercury::MercuryError;
|
|||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId};
|
||||
|
||||
pub use protocol::metadata::AudioFile_Format as FileFormat;
|
||||
pub use crate::protocol::metadata::AudioFile_Format as FileFormat;
|
||||
|
||||
fn countrylist_contains(list: &str, country: &str) -> bool {
|
||||
list.chunks(2).any(|cc| cc == country)
|
||||
|
@ -301,7 +301,6 @@ impl Metadata for Playlist {
|
|||
}
|
||||
|
||||
fn parse(msg: &Self::Message, _: &Session) -> Self {
|
||||
|
||||
let tracks = msg
|
||||
.get_contents()
|
||||
.get_items()
|
||||
|
@ -314,7 +313,11 @@ impl Metadata for Playlist {
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
if tracks.len() != msg.get_length() as usize {
|
||||
warn!("Got {} tracks, but the playlist should contain {} tracks.", tracks.len(), msg.get_length());
|
||||
warn!(
|
||||
"Got {} tracks, but the playlist should contain {} tracks.",
|
||||
tracks.len(),
|
||||
msg.get_length()
|
||||
);
|
||||
}
|
||||
|
||||
Playlist {
|
||||
|
|
|
@ -1,24 +1,26 @@
|
|||
[package]
|
||||
name = "librespot-playback"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Sasha Hilton <sashahilton00@gmail.com>"]
|
||||
description="The audio playback logic for librespot"
|
||||
license="MIT"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies.librespot-audio]
|
||||
path = "../audio"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-core]
|
||||
path = "../core"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
[dependencies.librespot-metadata]
|
||||
path = "../metadata"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies]
|
||||
futures = "0.1"
|
||||
log = "0.4"
|
||||
byteorder = "1.3"
|
||||
shell-words = "0.1.0"
|
||||
|
||||
alsa = { version = "0.2.1", optional = true }
|
||||
portaudio-rs = { version = "0.3.0", optional = true }
|
||||
|
|
|
@ -64,7 +64,8 @@ impl Open for AlsaSink {
|
|||
}
|
||||
Some(device) => device,
|
||||
None => "default",
|
||||
}.to_string();
|
||||
}
|
||||
.to_string();
|
||||
|
||||
AlsaSink(None, name)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use super::{Open, Sink};
|
||||
use jack::prelude::{
|
||||
client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, ProcessHandler,
|
||||
ProcessScope,
|
||||
client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port,
|
||||
ProcessHandler, ProcessScope,
|
||||
};
|
||||
use std::io;
|
||||
use std::sync::mpsc::{sync_channel, Receiver, SyncSender};
|
||||
|
@ -45,9 +45,14 @@ impl Open for JackSink {
|
|||
info!("Using jack sink!");
|
||||
|
||||
let client_name = client_name.unwrap_or("librespot".to_string());
|
||||
let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
|
||||
let ch_r = client.register_port("out_0", AudioOutSpec::default()).unwrap();
|
||||
let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap();
|
||||
let (client, _status) =
|
||||
Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap();
|
||||
let ch_r = client
|
||||
.register_port("out_0", AudioOutSpec::default())
|
||||
.unwrap();
|
||||
let ch_l = client
|
||||
.register_port("out_1", AudioOutSpec::default())
|
||||
.unwrap();
|
||||
// buffer for samples from librespot (~10ms)
|
||||
let (tx, rx) = sync_channel(2 * 1024 * 4);
|
||||
let jack_data = JackData {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::io;
|
||||
|
||||
pub trait Open {
|
||||
fn open(Option<String>) -> Self;
|
||||
fn open(_: Option<String>) -> Self;
|
||||
}
|
||||
|
||||
pub trait Sink {
|
||||
|
@ -10,7 +10,7 @@ pub trait Sink {
|
|||
fn write(&mut self, data: &[i16]) -> io::Result<()>;
|
||||
}
|
||||
|
||||
fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<Sink> {
|
||||
fn mk_sink<S: Sink + Open + 'static>(device: Option<String>) -> Box<dyn Sink> {
|
||||
Box::new(S::open(device))
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,10 @@ use self::sdl::SdlSink;
|
|||
mod pipe;
|
||||
use self::pipe::StdoutSink;
|
||||
|
||||
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] = &[
|
||||
mod subprocess;
|
||||
use self::subprocess::SubprocessSink;
|
||||
|
||||
pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<dyn Sink>)] = &[
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
("alsa", mk_sink::<AlsaSink>),
|
||||
#[cfg(feature = "portaudio-backend")]
|
||||
|
@ -67,9 +70,10 @@ pub const BACKENDS: &'static [(&'static str, fn(Option<String>) -> Box<Sink>)] =
|
|||
#[cfg(feature = "sdl-backend")]
|
||||
("sdl", mk_sink::<SdlSink>),
|
||||
("pipe", mk_sink::<StdoutSink>),
|
||||
("subprocess", mk_sink::<SubprocessSink>),
|
||||
];
|
||||
|
||||
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<Sink>> {
|
||||
pub fn find(name: Option<String>) -> Option<fn(Option<String>) -> Box<dyn Sink>> {
|
||||
if let Some(name) = name {
|
||||
BACKENDS
|
||||
.iter()
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::io::{self, Write};
|
|||
use std::mem;
|
||||
use std::slice;
|
||||
|
||||
pub struct StdoutSink(Box<Write>);
|
||||
pub struct StdoutSink(Box<dyn Write>);
|
||||
|
||||
impl Open for StdoutSink {
|
||||
fn open(path: Option<String>) -> StdoutSink {
|
||||
|
@ -28,7 +28,10 @@ impl Sink for StdoutSink {
|
|||
|
||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||
let data: &[u8] = unsafe {
|
||||
slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::<i16>())
|
||||
slice::from_raw_parts(
|
||||
data.as_ptr() as *const u8,
|
||||
data.len() * mem::size_of::<i16>(),
|
||||
)
|
||||
};
|
||||
|
||||
self.0.write_all(data)?;
|
||||
|
|
|
@ -11,7 +11,7 @@ pub struct PortAudioSink<'a>(
|
|||
StreamParameters<i16>,
|
||||
);
|
||||
|
||||
fn output_devices() -> Box<Iterator<Item = (DeviceIndex, DeviceInfo)>> {
|
||||
fn output_devices() -> Box<dyn Iterator<Item = (DeviceIndex, DeviceInfo)>> {
|
||||
let count = portaudio_rs::device::get_count().unwrap();
|
||||
let devices = (0..count)
|
||||
.filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info)))
|
||||
|
@ -51,7 +51,8 @@ impl<'a> Open for PortAudioSink<'a> {
|
|||
}
|
||||
Some(device) => find_output(device),
|
||||
None => get_default_output_index(),
|
||||
}.expect("Could not find device");
|
||||
}
|
||||
.expect("Could not find device");
|
||||
|
||||
let info = portaudio_rs::device::get_info(device_idx);
|
||||
let latency = match info {
|
||||
|
@ -81,8 +82,9 @@ impl<'a> Sink for PortAudioSink<'a> {
|
|||
FRAMES_PER_BUFFER_UNSPECIFIED,
|
||||
StreamFlags::empty(),
|
||||
None,
|
||||
).unwrap(),
|
||||
);;
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
self.0.as_mut().unwrap().start().unwrap();
|
||||
|
|
|
@ -14,7 +14,11 @@ pub struct PulseAudioSink {
|
|||
desc: CString,
|
||||
}
|
||||
|
||||
fn call_pulseaudio<T, F, FailCheck>(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result<T>
|
||||
fn call_pulseaudio<T, F, FailCheck>(
|
||||
f: F,
|
||||
fail_check: FailCheck,
|
||||
kind: io::ErrorKind,
|
||||
) -> io::Result<T>
|
||||
where
|
||||
T: Copy,
|
||||
F: Fn(*mut libc::c_int) -> T,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use super::{Open, Sink};
|
||||
extern crate rodio;
|
||||
extern crate cpal;
|
||||
use std::{io, thread, time};
|
||||
extern crate rodio;
|
||||
use std::process::exit;
|
||||
use std::{io, thread, time};
|
||||
|
||||
pub struct RodioSink {
|
||||
rodio_sink: rodio::Sink,
|
||||
|
@ -14,7 +14,7 @@ fn list_formats(ref device: &rodio::Device) {
|
|||
Err(e) => {
|
||||
warn!("Error getting default rodio::Sink format: {:?}", e);
|
||||
return;
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
let mut output_formats = match device.supported_output_formats() {
|
||||
|
@ -22,13 +22,16 @@ fn list_formats(ref device: &rodio::Device) {
|
|||
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);
|
||||
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 {
|
||||
|
@ -79,9 +82,7 @@ impl Open for RodioSink {
|
|||
}
|
||||
let sink = rodio::Sink::new(&rodio_device);
|
||||
|
||||
RodioSink {
|
||||
rodio_sink: sink,
|
||||
}
|
||||
RodioSink { rodio_sink: sink }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
59
playback/src/audio_backend/subprocess.rs
Normal file
59
playback/src/audio_backend/subprocess.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
use super::{Open, Sink};
|
||||
use shell_words::split;
|
||||
use std::io::{self, Write};
|
||||
use std::mem;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::slice;
|
||||
|
||||
pub struct SubprocessSink {
|
||||
shell_command: String,
|
||||
child: Option<Child>,
|
||||
}
|
||||
|
||||
impl Open for SubprocessSink {
|
||||
fn open(shell_command: Option<String>) -> SubprocessSink {
|
||||
if let Some(shell_command) = shell_command {
|
||||
SubprocessSink {
|
||||
shell_command: shell_command,
|
||||
child: None,
|
||||
}
|
||||
} else {
|
||||
panic!("subprocess sink requires specifying a shell command");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sink for SubprocessSink {
|
||||
fn start(&mut self) -> io::Result<()> {
|
||||
let args = split(&self.shell_command).unwrap();
|
||||
self.child = Some(
|
||||
Command::new(&args[0])
|
||||
.args(&args[1..])
|
||||
.stdin(Stdio::piped())
|
||||
.spawn()?,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stop(&mut self) -> io::Result<()> {
|
||||
if let Some(child) = &mut self.child.take() {
|
||||
child.kill()?;
|
||||
child.wait()?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||
let data: &[u8] = unsafe {
|
||||
slice::from_raw_parts(
|
||||
data.as_ptr() as *const u8,
|
||||
data.len() * mem::size_of::<i16>(),
|
||||
)
|
||||
};
|
||||
if let Some(child) = &mut self.child {
|
||||
let child_stdin = child.stdin.as_mut().unwrap();
|
||||
child_stdin.write_all(data)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ extern crate log;
|
|||
|
||||
extern crate byteorder;
|
||||
extern crate futures;
|
||||
extern crate shell_words;
|
||||
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
extern crate alsa;
|
||||
|
|
|
@ -10,13 +10,17 @@ pub struct AlsaMixer {
|
|||
}
|
||||
|
||||
impl AlsaMixer {
|
||||
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<Error>> {
|
||||
fn map_volume(&self, set_volume: Option<u16>) -> Result<(u16), Box<dyn Error>> {
|
||||
let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?;
|
||||
let 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 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;
|
||||
|
||||
|
@ -72,7 +76,7 @@ impl Mixer for AlsaMixer {
|
|||
}
|
||||
}
|
||||
|
||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
||||
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
pub trait Mixer: Send {
|
||||
fn open(Option<MixerConfig>) -> Self
|
||||
fn open(_: Option<MixerConfig>) -> Self
|
||||
where
|
||||
Self: Sized;
|
||||
fn start(&self);
|
||||
fn stop(&self);
|
||||
fn set_volume(&self, volume: u16);
|
||||
fn volume(&self) -> u16;
|
||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
||||
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -28,10 +28,11 @@ pub struct MixerConfig {
|
|||
}
|
||||
|
||||
impl Default for MixerConfig {
|
||||
fn default() -> MixerConfig { MixerConfig {
|
||||
card: String::from("default"),
|
||||
mixer: String::from("PCM"),
|
||||
index: 0,
|
||||
fn default() -> MixerConfig {
|
||||
MixerConfig {
|
||||
card: String::from("default"),
|
||||
mixer: String::from("PCM"),
|
||||
index: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -39,11 +40,11 @@ impl Default for MixerConfig {
|
|||
pub mod softmixer;
|
||||
use self::softmixer::SoftMixer;
|
||||
|
||||
fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<Mixer> {
|
||||
fn mk_sink<M: Mixer + 'static>(device: Option<MixerConfig>) -> Box<dyn Mixer> {
|
||||
Box::new(M::open(device))
|
||||
}
|
||||
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<Mixer>> {
|
||||
pub fn find<T: AsRef<str>>(name: Option<T>) -> Option<fn(Option<MixerConfig>) -> Box<dyn Mixer>> {
|
||||
match name.as_ref().map(AsRef::as_ref) {
|
||||
None | Some("softvol") => Some(mk_sink::<SoftMixer>),
|
||||
#[cfg(feature = "alsa-backend")]
|
||||
|
|
|
@ -23,7 +23,7 @@ impl Mixer for SoftMixer {
|
|||
fn set_volume(&self, volume: u16) {
|
||||
self.volume.store(volume as usize, Ordering::Relaxed);
|
||||
}
|
||||
fn get_audio_filter(&self) -> Option<Box<AudioFilter + Send>> {
|
||||
fn get_audio_filter(&self) -> Option<Box<dyn AudioFilter + Send>> {
|
||||
Some(Box::new(SoftVolumeApplier {
|
||||
volume: self.volume.clone(),
|
||||
}))
|
||||
|
|
|
@ -11,19 +11,19 @@ use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError};
|
|||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use config::{Bitrate, PlayerConfig};
|
||||
use crate::config::{Bitrate, PlayerConfig};
|
||||
use librespot_core::session::Session;
|
||||
use librespot_core::spotify_id::SpotifyId;
|
||||
|
||||
use audio::{AudioDecrypt, AudioFile, StreamLoaderController};
|
||||
use audio::{VorbisDecoder, VorbisPacket};
|
||||
use audio::{
|
||||
use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
|
||||
use crate::audio::{VorbisDecoder, VorbisPacket};
|
||||
use crate::audio::{
|
||||
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
|
||||
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
||||
};
|
||||
use audio_backend::Sink;
|
||||
use metadata::{AudioItem, FileFormat};
|
||||
use mixer::AudioFilter;
|
||||
use crate::audio_backend::Sink;
|
||||
use crate::metadata::{AudioItem, FileFormat};
|
||||
use crate::mixer::AudioFilter;
|
||||
|
||||
pub struct Player {
|
||||
commands: Option<std::sync::mpsc::Sender<PlayerCommand>>,
|
||||
|
@ -36,9 +36,9 @@ struct PlayerInternal {
|
|||
commands: std::sync::mpsc::Receiver<PlayerCommand>,
|
||||
|
||||
state: PlayerState,
|
||||
sink: Box<Sink>,
|
||||
sink: Box<dyn Sink>,
|
||||
sink_running: bool,
|
||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||
audio_filter: Option<Box<dyn AudioFilter + Send>>,
|
||||
event_sender: futures::sync::mpsc::UnboundedSender<PlayerEvent>,
|
||||
}
|
||||
|
||||
|
@ -98,8 +98,10 @@ impl NormalisationData {
|
|||
}
|
||||
|
||||
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);
|
||||
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.");
|
||||
|
@ -117,11 +119,11 @@ impl Player {
|
|||
pub fn new<F>(
|
||||
config: PlayerConfig,
|
||||
session: Session,
|
||||
audio_filter: Option<Box<AudioFilter + Send>>,
|
||||
audio_filter: Option<Box<dyn AudioFilter + Send>>,
|
||||
sink_builder: F,
|
||||
) -> (Player, PlayerEventChannel)
|
||||
where
|
||||
F: FnOnce() -> Box<Sink> + Send + 'static,
|
||||
F: FnOnce() -> Box<dyn Sink> + Send + 'static,
|
||||
{
|
||||
let (cmd_tx, cmd_rx) = std::sync::mpsc::channel();
|
||||
let (event_sender, event_receiver) = futures::sync::mpsc::unbounded();
|
||||
|
@ -238,7 +240,12 @@ impl PlayerState {
|
|||
use self::PlayerState::*;
|
||||
match *self {
|
||||
Stopped | EndOfTrack { .. } => None,
|
||||
Paused { ref mut decoder, .. } | Playing { ref mut decoder, .. } => Some(decoder),
|
||||
Paused {
|
||||
ref mut decoder, ..
|
||||
}
|
||||
| Playing {
|
||||
ref mut decoder, ..
|
||||
} => Some(decoder),
|
||||
Invalid => panic!("invalid state"),
|
||||
}
|
||||
}
|
||||
|
@ -518,7 +525,10 @@ impl PlayerInternal {
|
|||
if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
|
||||
stream_loader_controller.set_stream_mode();
|
||||
}
|
||||
if let PlayerState::Playing { bytes_per_second, .. } = self.state {
|
||||
if let PlayerState::Playing {
|
||||
bytes_per_second, ..
|
||||
} = self.state
|
||||
{
|
||||
if let Some(stream_loader_controller) = self.state.stream_loader_controller() {
|
||||
// Request our read ahead range
|
||||
let request_data_length = max(
|
||||
|
@ -592,7 +602,10 @@ impl PlayerInternal {
|
|||
.iter()
|
||||
.map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id));
|
||||
let alternatives = future::join_all(alternatives).wait().unwrap();
|
||||
alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned)
|
||||
alternatives
|
||||
.into_iter()
|
||||
.find(|alt| alt.available)
|
||||
.map(Cow::Owned)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
@ -623,9 +636,14 @@ impl PlayerInternal {
|
|||
spotify_id: SpotifyId,
|
||||
position: i64,
|
||||
) -> Option<(Decoder, f32, StreamLoaderController, usize)> {
|
||||
let audio = AudioItem::get_audio_item(&self.session, spotify_id)
|
||||
.wait()
|
||||
.unwrap();
|
||||
let audio = match AudioItem::get_audio_item(&self.session, spotify_id).wait() {
|
||||
Ok(audio) => audio,
|
||||
Err(_) => {
|
||||
error!("Unable to load audio item.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
info!("Loading <{}> with Spotify URI <{}>", audio.name, audio.uri);
|
||||
|
||||
let audio = match self.find_available_alternative(&audio) {
|
||||
|
@ -670,10 +688,20 @@ impl PlayerInternal {
|
|||
let play_from_beginning = position == 0;
|
||||
|
||||
let key = self.session.audio_key().request(spotify_id, file_id);
|
||||
let encrypted_file =
|
||||
AudioFile::open(&self.session, file_id, bytes_per_second, play_from_beginning);
|
||||
let encrypted_file = AudioFile::open(
|
||||
&self.session,
|
||||
file_id,
|
||||
bytes_per_second,
|
||||
play_from_beginning,
|
||||
);
|
||||
|
||||
let encrypted_file = encrypted_file.wait().unwrap();
|
||||
let encrypted_file = match encrypted_file.wait() {
|
||||
Ok(encrypted_file) => encrypted_file,
|
||||
Err(_) => {
|
||||
error!("Unable to load encrypted file.");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let mut stream_loader_controller = encrypted_file.get_stream_loader_controller();
|
||||
|
||||
|
@ -685,11 +713,20 @@ impl PlayerInternal {
|
|||
stream_loader_controller.set_random_access_mode();
|
||||
}
|
||||
|
||||
let key = key.wait().unwrap();
|
||||
let key = match key.wait() {
|
||||
Ok(key) => key,
|
||||
Err(_) => {
|
||||
error!("Unable to load decryption key");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
Ok(normalisation_data) => {
|
||||
NormalisationData::get_factor(&self.config, normalisation_data)
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Unable to extract normalisation data, using default value.");
|
||||
1.0 as f32
|
||||
|
@ -768,7 +805,7 @@ impl<T: Read + Seek> Seek for Subfile<T> {
|
|||
x => x,
|
||||
};
|
||||
|
||||
let newpos = try!(self.stream.seek(pos));
|
||||
let newpos = self.stream.seek(pos)?;
|
||||
if newpos > self.offset {
|
||||
Ok(newpos - self.offset)
|
||||
} else {
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
[package]
|
||||
name = "librespot-protocol"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
authors = ["Paul Liétar <paul@lietar.net>"]
|
||||
build = "build.rs"
|
||||
description="The protobuf logic for communicating with Spotify servers"
|
||||
license="MIT"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
protobuf = "2.8.1"
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
extern crate protobuf_codegen; // Does the business
|
||||
extern crate protobuf_codegen_pure; // Helper function
|
||||
|
||||
use std::path::Path;
|
||||
use std::fs::{read_to_string, write};
|
||||
use std::path::Path;
|
||||
|
||||
use protobuf_codegen_pure::Customize;
|
||||
use protobuf_codegen_pure::parse_and_typecheck;
|
||||
use protobuf_codegen_pure::Customize;
|
||||
|
||||
fn main() {
|
||||
let customizations = Customize { ..Default::default() };
|
||||
let customizations = Customize {
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let lib_str = read_to_string("src/lib.rs").unwrap();
|
||||
|
||||
|
@ -21,10 +23,9 @@ fn main() {
|
|||
|
||||
let name;
|
||||
if line.starts_with("pub mod ") {
|
||||
name = &line[8..len-1]; // Remove keywords and semi-colon
|
||||
}
|
||||
else {
|
||||
name = &line[4..len-1]; // Remove keywords and semi-colon
|
||||
name = &line[8..len - 1]; // Remove keywords and semi-colon
|
||||
} else {
|
||||
name = &line[4..len - 1]; // Remove keywords and semi-colon
|
||||
}
|
||||
|
||||
// Build the paths to relevant files.
|
||||
|
@ -44,11 +45,7 @@ fn main() {
|
|||
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,
|
||||
);
|
||||
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.
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
max_width = 105
|
||||
# max_width = 105
|
||||
reorder_imports = true
|
||||
reorder_imports_in_group = true
|
||||
reorder_modules = true
|
||||
|
|
|
@ -1,15 +1,6 @@
|
|||
#![crate_name = "librespot"]
|
||||
#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))]
|
||||
|
||||
extern crate base64;
|
||||
extern crate futures;
|
||||
extern crate hyper;
|
||||
extern crate num_bigint;
|
||||
extern crate protobuf;
|
||||
extern crate rand;
|
||||
extern crate tokio_core;
|
||||
extern crate url;
|
||||
|
||||
pub extern crate librespot_audio as audio;
|
||||
pub extern crate librespot_connect as connect;
|
||||
pub extern crate librespot_core as core;
|
||||
|
|
119
src/main.rs
119
src/main.rs
|
@ -1,20 +1,6 @@
|
|||
extern crate env_logger;
|
||||
extern crate futures;
|
||||
extern crate getopts;
|
||||
extern crate librespot;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
extern crate hex;
|
||||
extern crate rpassword;
|
||||
extern crate sha1;
|
||||
extern crate tokio_core;
|
||||
extern crate tokio_io;
|
||||
extern crate tokio_process;
|
||||
extern crate tokio_signal;
|
||||
extern crate url;
|
||||
|
||||
use futures::sync::mpsc::UnboundedReceiver;
|
||||
use futures::{Async, Future, Poll, Stream};
|
||||
use log::{error, info, trace, warn};
|
||||
use sha1::{Digest, Sha1};
|
||||
use std::env;
|
||||
use std::io::{self, stderr, Write};
|
||||
|
@ -22,6 +8,7 @@ use std::mem;
|
|||
use std::path::PathBuf;
|
||||
use std::process::exit;
|
||||
use std::str::FromStr;
|
||||
use std::time::Instant;
|
||||
use tokio_core::reactor::{Core, Handle};
|
||||
use tokio_io::IoStream;
|
||||
use url::Url;
|
||||
|
@ -40,7 +27,7 @@ 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;
|
||||
use crate::player_event_handler::run_program_on_events;
|
||||
|
||||
fn device_id(name: &str) -> String {
|
||||
hex::encode(Sha1::digest(name.as_bytes()))
|
||||
|
@ -86,10 +73,10 @@ fn list_backends() {
|
|||
|
||||
#[derive(Clone)]
|
||||
struct Setup {
|
||||
backend: fn(Option<String>) -> Box<Sink>,
|
||||
backend: fn(Option<String>) -> Box<dyn Sink>,
|
||||
device: Option<String>,
|
||||
|
||||
mixer: fn(Option<MixerConfig>) -> Box<Mixer>,
|
||||
mixer: fn(Option<MixerConfig>) -> Box<dyn Mixer>,
|
||||
|
||||
cache: Option<Cache>,
|
||||
player_config: PlayerConfig,
|
||||
|
@ -198,7 +185,13 @@ fn setup(args: &[String]) -> Setup {
|
|||
let matches = match opts.parse(&args[1..]) {
|
||||
Ok(m) => m,
|
||||
Err(f) => {
|
||||
writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap();
|
||||
writeln!(
|
||||
stderr(),
|
||||
"error: {}\n{}",
|
||||
f.to_string(),
|
||||
usage(&args[0], &opts)
|
||||
)
|
||||
.unwrap();
|
||||
exit(1);
|
||||
}
|
||||
};
|
||||
|
@ -232,7 +225,9 @@ fn setup(args: &[String]) -> Setup {
|
|||
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")),
|
||||
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")
|
||||
|
@ -367,9 +362,9 @@ struct Main {
|
|||
player_config: PlayerConfig,
|
||||
session_config: SessionConfig,
|
||||
connect_config: ConnectConfig,
|
||||
backend: fn(Option<String>) -> Box<Sink>,
|
||||
backend: fn(Option<String>) -> Box<dyn Sink>,
|
||||
device: Option<String>,
|
||||
mixer: fn(Option<MixerConfig>) -> Box<Mixer>,
|
||||
mixer: fn(Option<MixerConfig>) -> Box<dyn Mixer>,
|
||||
mixer_config: MixerConfig,
|
||||
handle: Handle,
|
||||
|
||||
|
@ -378,9 +373,11 @@ struct Main {
|
|||
|
||||
spirc: Option<Spirc>,
|
||||
spirc_task: Option<SpircTask>,
|
||||
connect: Box<Future<Item = Session, Error = io::Error>>,
|
||||
connect: Box<dyn Future<Item = Session, Error = io::Error>>,
|
||||
|
||||
shutdown: bool,
|
||||
last_credentials: Option<Credentials>,
|
||||
auto_connect_times: Vec<Instant>,
|
||||
|
||||
player_event_channel: Option<UnboundedReceiver<PlayerEvent>>,
|
||||
player_event_program: Option<String>,
|
||||
|
@ -404,6 +401,8 @@ impl Main {
|
|||
spirc: None,
|
||||
spirc_task: None,
|
||||
shutdown: false,
|
||||
last_credentials: None,
|
||||
auto_connect_times: Vec::new(),
|
||||
signal: Box::new(tokio_signal::ctrl_c().flatten_stream()),
|
||||
|
||||
player_event_channel: None,
|
||||
|
@ -414,7 +413,8 @@ impl Main {
|
|||
let config = task.connect_config.clone();
|
||||
let device_id = task.session_config.device_id.clone();
|
||||
|
||||
task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
|
||||
task.discovery =
|
||||
Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap());
|
||||
}
|
||||
|
||||
if let Some(credentials) = setup.credentials {
|
||||
|
@ -425,6 +425,7 @@ impl Main {
|
|||
}
|
||||
|
||||
fn credentials(&mut self, credentials: Credentials) {
|
||||
self.last_credentials = Some(credentials.clone());
|
||||
let config = self.session_config.clone();
|
||||
let handle = self.handle.clone();
|
||||
|
||||
|
@ -447,36 +448,46 @@ impl Future for Main {
|
|||
loop {
|
||||
let mut progress = false;
|
||||
|
||||
if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) {
|
||||
if let Some(Async::Ready(Some(creds))) =
|
||||
self.discovery.as_mut().map(|d| d.poll().unwrap())
|
||||
{
|
||||
if let Some(ref spirc) = self.spirc {
|
||||
spirc.shutdown();
|
||||
}
|
||||
self.auto_connect_times.clear();
|
||||
self.credentials(creds);
|
||||
|
||||
progress = true;
|
||||
}
|
||||
|
||||
if let Async::Ready(session) = self.connect.poll().unwrap() {
|
||||
self.connect = Box::new(futures::future::empty());
|
||||
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();
|
||||
match self.connect.poll() {
|
||||
Ok(Async::Ready(session)) => {
|
||||
self.connect = Box::new(futures::future::empty());
|
||||
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)
|
||||
});
|
||||
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)
|
||||
});
|
||||
|
||||
let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
|
||||
self.spirc = Some(spirc);
|
||||
self.spirc_task = Some(spirc_task);
|
||||
self.player_event_channel = Some(event_channel);
|
||||
let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer);
|
||||
self.spirc = Some(spirc);
|
||||
self.spirc_task = Some(spirc_task);
|
||||
self.player_event_channel = Some(event_channel);
|
||||
|
||||
progress = true;
|
||||
progress = true;
|
||||
}
|
||||
Ok(Async::NotReady) => (),
|
||||
Err(error) => {
|
||||
error!("Could not connect to server: {}", error);
|
||||
self.connect = Box::new(futures::future::empty());
|
||||
}
|
||||
}
|
||||
|
||||
if let Async::Ready(Some(())) = self.signal.poll().unwrap() {
|
||||
|
@ -495,12 +506,32 @@ impl Future for Main {
|
|||
progress = true;
|
||||
}
|
||||
|
||||
let mut drop_spirc_and_try_to_reconnect = false;
|
||||
if let Some(ref mut spirc_task) = self.spirc_task {
|
||||
if let Async::Ready(()) = spirc_task.poll().unwrap() {
|
||||
if self.shutdown {
|
||||
return Ok(Async::Ready(()));
|
||||
} else {
|
||||
panic!("Spirc shut down unexpectedly");
|
||||
warn!("Spirc shut down unexpectedly");
|
||||
drop_spirc_and_try_to_reconnect = true;
|
||||
}
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
if drop_spirc_and_try_to_reconnect {
|
||||
self.spirc_task = None;
|
||||
while (!self.auto_connect_times.is_empty())
|
||||
&& ((Instant::now() - self.auto_connect_times[0]).as_secs() > 600)
|
||||
{
|
||||
let _ = self.auto_connect_times.remove(0);
|
||||
}
|
||||
|
||||
if let Some(credentials) = self.last_credentials.clone() {
|
||||
if self.auto_connect_times.len() >= 5 {
|
||||
warn!("Spirc shut down too often. Not reconnecting automatically.");
|
||||
} else {
|
||||
self.auto_connect_times.push(Instant::now());
|
||||
self.credentials(credentials);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
use librespot::playback::player::PlayerEvent;
|
||||
use tokio_process::{Child, CommandExt};
|
||||
use log::info;
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use std::process::Command;
|
||||
use tokio_process::{Child, CommandExt};
|
||||
|
||||
fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result<Child> {
|
||||
let mut v: Vec<&str> = program.split_whitespace().collect();
|
||||
|
|
Loading…
Reference in a new issue