mirror of
https://github.com/librespot-org/librespot.git
synced 2024-12-18 17:11:53 +00:00
Merge branch 'dev' into tokio_migration
This commit is contained in:
commit
872fab62d8
23 changed files with 884 additions and 562 deletions
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
|
@ -41,7 +41,7 @@ jobs:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
toolchain:
|
toolchain:
|
||||||
- 1.40.0 # MSRV (Minimum supported rust version)
|
- 1.42.0 # MSRV (Minimum supported rust version)
|
||||||
- stable
|
- stable
|
||||||
- beta
|
- beta
|
||||||
experimental: [false]
|
experimental: [false]
|
||||||
|
@ -59,6 +59,15 @@ jobs:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
toolchain: ${{ matrix.toolchain }}
|
toolchain: ${{ matrix.toolchain }}
|
||||||
override: true
|
override: true
|
||||||
|
- name: Cache Rust dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/index
|
||||||
|
~/.cargo/registry/cache
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.lock') }}
|
||||||
- name: Install developer package dependencies
|
- name: Install developer package dependencies
|
||||||
run: sudo apt-get update && sudo apt-get install libpulse-dev portaudio19-dev libasound2-dev libsdl2-dev gstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
run: sudo apt-get update && sudo apt-get install libpulse-dev portaudio19-dev libasound2-dev libsdl2-dev gstreamer1.0-dev libgstreamer-plugins-base1.0-dev
|
||||||
- run: cargo build --locked --no-default-features
|
- run: cargo build --locked --no-default-features
|
||||||
|
@ -94,6 +103,15 @@ jobs:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
toolchain: ${{ matrix.toolchain }}
|
toolchain: ${{ matrix.toolchain }}
|
||||||
override: true
|
override: true
|
||||||
|
- name: Cache Rust dependencies
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
~/.cargo/registry/index
|
||||||
|
~/.cargo/registry/cache
|
||||||
|
~/.cargo/git
|
||||||
|
target
|
||||||
|
key: ${{ runner.os }}-build-${{ hashFiles('**/Cargo.lock') }}
|
||||||
- name: Install cross
|
- name: Install cross
|
||||||
run: cargo install cross || true
|
run: cargo install cross || true
|
||||||
- name: Build
|
- name: Build
|
||||||
|
|
73
.travis.yml
73
.travis.yml
|
@ -1,73 +0,0 @@
|
||||||
language: rust
|
|
||||||
rust:
|
|
||||||
- 1.40.0
|
|
||||||
- stable
|
|
||||||
- beta
|
|
||||||
- nightly
|
|
||||||
|
|
||||||
# Need to cache the whole `.cargo` directory to keep .crates.toml for
|
|
||||||
# cargo-update to work
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- /home/travis/.cargo
|
|
||||||
|
|
||||||
# But don't cache the cargo registry
|
|
||||||
before_cache:
|
|
||||||
- rm -rf /home/travis/.cargo/registry
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
# Performance tweak
|
|
||||||
fast_finish: true
|
|
||||||
# Ignore failures in nightly, not ideal, but necessary
|
|
||||||
allow_failures:
|
|
||||||
- rust: nightly
|
|
||||||
|
|
||||||
# Only run the formatting check for stable
|
|
||||||
include:
|
|
||||||
- name: 'Rust: format check'
|
|
||||||
rust: stable
|
|
||||||
install:
|
|
||||||
- rustup component add rustfmt
|
|
||||||
script:
|
|
||||||
- cargo fmt --verbose --all -- --check
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
packages:
|
|
||||||
- gcc-arm-linux-gnueabihf
|
|
||||||
- libc6-dev-armhf-cross
|
|
||||||
- libpulse-dev
|
|
||||||
- portaudio19-dev
|
|
||||||
- libasound2-dev
|
|
||||||
- libsdl2-dev
|
|
||||||
- gstreamer1.0-dev
|
|
||||||
- libgstreamer-plugins-base1.0-dev
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- 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 build --locked --no-default-features
|
|
||||||
- cargo build --locked --examples
|
|
||||||
- cargo build --locked --no-default-features --features "with-tremor"
|
|
||||||
- cargo build --locked --no-default-features --features "with-vorbis"
|
|
||||||
- cargo build --locked --no-default-features --features "alsa-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "portaudio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "pulseaudio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "jackaudio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "rodio-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "sdl-backend"
|
|
||||||
- cargo build --locked --no-default-features --features "gstreamer-backend"
|
|
||||||
- cargo build --locked --no-default-features --target armv7-unknown-linux-gnueabihf
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
email: false
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- https://webhooks.gitter.im/e/780b178b15811059752e
|
|
||||||
on_success: change # options: [always|never|change] default: always
|
|
||||||
on_failure: always # options: [always|never|change] default: always
|
|
||||||
on_start: never # options: [always|never|change] default: always
|
|
347
Cargo.lock
generated
347
Cargo.lock
generated
|
@ -73,38 +73,16 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "alsa"
|
|
||||||
version = "0.2.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b4a0d4ebc8b23041c5de9bc9aee13b4bad844a589479701f31a5934cfe4aeb32"
|
|
||||||
dependencies = [
|
|
||||||
"alsa-sys 0.1.2",
|
|
||||||
"bitflags 0.9.1",
|
|
||||||
"libc",
|
|
||||||
"nix 0.9.0",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alsa"
|
name = "alsa"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eb213f6b3e4b1480a60931ca2035794aa67b73103d254715b1db7b70dcb3c934"
|
checksum = "eb213f6b3e4b1480a60931ca2035794aa67b73103d254715b1db7b70dcb3c934"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa-sys 0.3.1",
|
"alsa-sys",
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
"nix 0.15.0",
|
"nix",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "alsa-sys"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
"pkg-config",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -129,27 +107,6 @@ version = "0.9.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
|
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "async-stream"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c"
|
|
||||||
dependencies = [
|
|
||||||
"async-stream-impl",
|
|
||||||
"futures-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "async-stream-impl"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.42"
|
version = "0.1.42"
|
||||||
|
@ -220,7 +177,7 @@ version = "0.56.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
|
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -248,12 +205,6 @@ version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bitflags"
|
|
||||||
version = "0.9.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -283,9 +234,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.4.0"
|
version = "3.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byte-tools"
|
name = "byte-tools"
|
||||||
|
@ -353,9 +304,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chunked_transfer"
|
name = "chunked_transfer"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca"
|
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cipher"
|
name = "cipher"
|
||||||
|
@ -368,9 +319,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clang-sys"
|
name = "clang-sys"
|
||||||
version = "1.0.3"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c"
|
checksum = "5cb92721cb37482245ed88428f72253ce422b3b4ee169c70a0642521bb5db4cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -383,7 +334,7 @@ version = "0.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -422,7 +373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
|
checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"percent-encoding 2.1.0",
|
"percent-encoding 2.1.0",
|
||||||
"time 0.2.24",
|
"time 0.2.25",
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -433,12 +384,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3"
|
checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cookie",
|
"cookie",
|
||||||
"idna 0.2.0",
|
"idna 0.2.1",
|
||||||
"log",
|
"log",
|
||||||
"publicsuffix",
|
"publicsuffix",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"time 0.2.24",
|
"time 0.2.25",
|
||||||
"url 2.2.0",
|
"url 2.2.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -454,7 +405,7 @@ version = "0.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491"
|
checksum = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"coreaudio-sys",
|
"coreaudio-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -473,7 +424,7 @@ version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05631e2089dfa5d3b6ea1cfbbfd092e2ee5deeb69698911bc976b28b746d3657"
|
checksum = "05631e2089dfa5d3b6ea1cfbbfd092e2ee5deeb69698911bc976b28b746d3657"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa 0.4.3",
|
"alsa",
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"coreaudio-rs",
|
"coreaudio-rs",
|
||||||
"jni 0.17.0",
|
"jni 0.17.0",
|
||||||
|
@ -483,7 +434,7 @@ dependencies = [
|
||||||
"mach",
|
"mach",
|
||||||
"ndk",
|
"ndk",
|
||||||
"ndk-glue",
|
"ndk-glue",
|
||||||
"nix 0.15.0",
|
"nix",
|
||||||
"oboe",
|
"oboe",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"stdweb 0.1.3",
|
"stdweb 0.1.3",
|
||||||
|
@ -557,9 +508,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derivative"
|
name = "derivative"
|
||||||
version = "2.1.3"
|
version = "2.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183"
|
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -800,7 +751,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi 0.10.1+wasi-snapshot-preview1",
|
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -815,7 +766,7 @@ version = "0.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5"
|
checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-executor",
|
"futures-executor",
|
||||||
|
@ -877,7 +828,7 @@ version = "0.16.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d50f822055923f1cbede233aa5dfd4ee957cf328fb3076e330886094e11d6cf"
|
checksum = "5d50f822055923f1cbede233aa5dfd4ee957cf328fb3076e330886094e11d6cf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -901,7 +852,7 @@ version = "0.16.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc80888271338c3ede875d8cafc452eb207476ff5539dcbe0018a8f5b827af0e"
|
checksum = "cc80888271338c3ede875d8cafc452eb207476ff5539dcbe0018a8f5b827af0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"glib",
|
"glib",
|
||||||
|
@ -934,7 +885,7 @@ version = "0.16.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bafd01c56f59cb10f4b5a10f97bb4bdf8c2b2784ae5b04da7e2d400cf6e6afcf"
|
checksum = "bafd01c56f59cb10f4b5a10f97bb4bdf8c2b2784ae5b04da7e2d400cf6e6afcf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"glib",
|
"glib",
|
||||||
"glib-sys",
|
"glib-sys",
|
||||||
"gobject-sys",
|
"gobject-sys",
|
||||||
|
@ -1046,9 +997,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.3.4"
|
version = "1.3.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
|
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
|
@ -1064,9 +1015,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.2"
|
version = "0.14.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe"
|
checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
@ -1078,7 +1029,7 @@ dependencies = [
|
||||||
"httparse",
|
"httparse",
|
||||||
"httpdate",
|
"httpdate",
|
||||||
"itoa",
|
"itoa",
|
||||||
"pin-project 1.0.4",
|
"pin-project 1.0.5",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
|
@ -1105,9 +1056,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "idna"
|
name = "idna"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
|
checksum = "de910d521f7cc3135c4de8db1cb910e0b5ed1dc6f57c381cd07e8e661ce10094"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"matches",
|
"matches",
|
||||||
"unicode-bidi",
|
"unicode-bidi",
|
||||||
|
@ -1154,7 +1105,7 @@ version = "0.6.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c1871c91fa65aa328f3bedbaa54a6e5d1de009264684c153eb708ba933aa6f5"
|
checksum = "7c1871c91fa65aa328f3bedbaa54a6e5d1de009264684c153eb708ba933aa6f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"jack-sys",
|
"jack-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1207,9 +1158,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.46"
|
version = "0.3.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
|
checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
@ -1239,9 +1190,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.82"
|
version = "0.2.86"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
|
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libflate"
|
name = "libflate"
|
||||||
|
@ -1273,11 +1224,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libpulse-binding"
|
name = "libpulse-binding"
|
||||||
version = "2.22.0"
|
version = "2.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce89ab17bd343b08bd4321c674ef1477d34f83be18b1ab2ee47a5e5fbee64a91"
|
checksum = "b2405f806801527dfb3d2b6d48a282cdebe9a1b41b0652e0d7b5bad81dbc700e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
"libpulse-sys",
|
"libpulse-sys",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
|
@ -1287,9 +1238,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libpulse-simple-binding"
|
name = "libpulse-simple-binding"
|
||||||
version = "2.20.1"
|
version = "2.23.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e47f6cda2748fb86f15e5e65cc33be306577140f4b500622b5d98df2ca17240"
|
checksum = "a574975292db859087c3957b9182f7d53278553f06bddaa2099c90e4ac3a0ee0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libpulse-binding",
|
"libpulse-binding",
|
||||||
"libpulse-simple-sys",
|
"libpulse-simple-sys",
|
||||||
|
@ -1308,9 +1259,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libpulse-sys"
|
name = "libpulse-sys"
|
||||||
version = "1.17.0"
|
version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fcfb56118765adba111da47e36278b77d00aebf822e4f014a832fbfa183a13b"
|
checksum = "cf17e9832643c4f320c42b7d78b2c0510f45aa5e823af094413b94e45076ba82"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"num-derive",
|
"num-derive",
|
||||||
|
@ -1359,6 +1310,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
"error-chain",
|
||||||
"futures",
|
"futures",
|
||||||
"hmac",
|
"hmac",
|
||||||
"httparse",
|
"httparse",
|
||||||
|
@ -1404,7 +1356,7 @@ dependencies = [
|
||||||
name = "librespot-playback"
|
name = "librespot-playback"
|
||||||
version = "0.1.3"
|
version = "0.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa 0.2.2",
|
"alsa",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cpal",
|
"cpal",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -1438,9 +1390,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-tremor"
|
name = "librespot-tremor"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b155a7dc4e4d272e01c37a1b85c1ee1bee7f04980ad4a7784c1a6e0f2de5929b"
|
checksum = "97f525bff915d478a76940a7b988e5ea34911ba7280c97bd3a7673f54d68b4fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1465,11 +1417,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
|
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 1.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1577,25 +1529,13 @@ version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
|
checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nix"
|
|
||||||
version = "0.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a2c5afeb0198ec7be8569d666644b574345aad2e95a53baf3a532da3e0f3fb32"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags 0.9.1",
|
|
||||||
"cfg-if 0.1.10",
|
|
||||||
"libc",
|
|
||||||
"void",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
|
checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"cc",
|
"cc",
|
||||||
"cfg-if 0.1.10",
|
"cfg-if 0.1.10",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1836,6 +1776,15 @@ version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pest"
|
||||||
|
version = "2.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||||
|
dependencies = [
|
||||||
|
"ucd-trie",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
|
@ -1847,11 +1796,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2"
|
checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal 1.0.4",
|
"pin-project-internal 1.0.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1867,9 +1816,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.0.4"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71"
|
checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -1900,7 +1849,7 @@ version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb6b5eff96ccc9bf44d34c379ab03ae944426d83d1694345bdf8159d561d562"
|
checksum = "cdb6b5eff96ccc9bf44d34c379ab03ae944426d83d1694345bdf8159d561d562"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
"portaudio-sys",
|
"portaudio-sys",
|
||||||
]
|
]
|
||||||
|
@ -2013,7 +1962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b"
|
checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"error-chain",
|
"error-chain",
|
||||||
"idna 0.2.0",
|
"idna 0.2.1",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"regex",
|
"regex",
|
||||||
"url 2.2.0",
|
"url 2.2.0",
|
||||||
|
@ -2065,9 +2014,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.2"
|
version = "0.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e"
|
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"rand_chacha 0.3.0",
|
"rand_chacha 0.3.0",
|
||||||
|
@ -2158,7 +2107,7 @@ version = "0.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
|
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2190,9 +2139,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ring"
|
name = "ring"
|
||||||
version = "0.16.19"
|
version = "0.16.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
|
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2236,7 +2185,16 @@ version = "0.2.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver",
|
"semver 0.9.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc_version"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
|
||||||
|
dependencies = [
|
||||||
|
"semver 0.11.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2295,7 +2253,7 @@ version = "0.34.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fcbb85f4211627a7291c83434d6bbfa723e28dcaa53c7606087e3c61929e4b9c"
|
checksum = "fcbb85f4211627a7291c83434d6bbfa723e28dcaa53c7606087e3c61929e4b9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"sdl2-sys",
|
"sdl2-sys",
|
||||||
|
@ -2318,7 +2276,16 @@ version = "0.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"semver-parser",
|
"semver-parser 0.7.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "semver"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
|
||||||
|
dependencies = [
|
||||||
|
"semver-parser 0.10.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2328,19 +2295,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "semver-parser"
|
||||||
version = "1.0.120"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab"
|
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
|
||||||
|
dependencies = [
|
||||||
|
"pest",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.123"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.120"
|
version = "1.0.123"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775"
|
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2349,9 +2325,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.61"
|
version = "1.0.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
|
@ -2440,9 +2416,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "standback"
|
name = "standback"
|
||||||
version = "0.2.14"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c66a8cff4fa24853fdf6b51f75c6d7f8206d7c75cab4e467bcd7f25c2b1febe0"
|
checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
@ -2460,7 +2436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"discard",
|
"discard",
|
||||||
"rustc_version",
|
"rustc_version 0.2.3",
|
||||||
"stdweb-derive",
|
"stdweb-derive",
|
||||||
"stdweb-internal-macros",
|
"stdweb-internal-macros",
|
||||||
"stdweb-internal-runtime",
|
"stdweb-internal-runtime",
|
||||||
|
@ -2534,9 +2510,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.58"
|
version = "1.0.60"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
|
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2572,13 +2548,12 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tar"
|
name = "tar"
|
||||||
version = "0.4.30"
|
version = "0.4.32"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290"
|
checksum = "0313546c01d59e29be4f09687bcb4fb6690cec931cc3607b6aec7a0e417f4cc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"filetime",
|
"filetime",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall 0.1.57",
|
|
||||||
"xattr",
|
"xattr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2590,7 +2565,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"libc",
|
"libc",
|
||||||
"rand 0.8.2",
|
"rand 0.8.3",
|
||||||
"redox_syscall 0.2.4",
|
"redox_syscall 0.2.4",
|
||||||
"remove_dir_all",
|
"remove_dir_all",
|
||||||
"winapi",
|
"winapi",
|
||||||
|
@ -2646,9 +2621,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.2.24"
|
version = "0.2.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "273d3ed44dca264b0d6b3665e8d48fb515042d42466fad93d2a45b90ec4058f7"
|
checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const_fn",
|
"const_fn",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -2684,9 +2659,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinyvec"
|
name = "tinyvec"
|
||||||
version = "1.1.0"
|
version = "1.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
|
checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec_macros",
|
"tinyvec_macros",
|
||||||
]
|
]
|
||||||
|
@ -2699,9 +2674,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8efab2086f17abcddb8f756117665c958feee6b2e39974c2f1600592ab3a4195"
|
checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -2715,40 +2690,27 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "1.0.0"
|
version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494"
|
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-stream"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.6.2"
|
version = "0.6.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "feb971a26599ffd28066d387f109746df178eff14d5ea1e235015c5601967a4b"
|
checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-stream",
|
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2762,15 +2724,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-service"
|
name = "tower-service"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
|
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.22"
|
version = "0.1.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
|
checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
@ -2808,6 +2770,12 @@ version = "1.12.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ucd-trie"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
@ -2819,9 +2787,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-normalization"
|
name = "unicode-normalization"
|
||||||
version = "0.1.16"
|
version = "0.1.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
|
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
]
|
]
|
||||||
|
@ -2890,7 +2858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
|
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"idna 0.2.0",
|
"idna 0.2.1",
|
||||||
"matches",
|
"matches",
|
||||||
"percent-encoding 2.1.0",
|
"percent-encoding 2.1.0",
|
||||||
]
|
]
|
||||||
|
@ -2906,12 +2874,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vergen"
|
name = "vergen"
|
||||||
version = "3.1.0"
|
version = "3.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ce50d8996df1f85af15f2cd8d33daae6e479575123ef4314a51a70a230739cb"
|
checksum = "e7141e445af09c8919f1d5f8a20dae0b20c3b57a45dee0d5823c6ed5d237f15a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.2.1",
|
"bitflags",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"rustc_version 0.3.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2998,15 +2967,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasi"
|
name = "wasi"
|
||||||
version = "0.10.1+wasi-snapshot-preview1"
|
version = "0.10.2+wasi-snapshot-preview1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
|
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.69"
|
version = "0.2.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
|
checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 1.0.0",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
|
@ -3014,9 +2983,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.69"
|
version = "0.2.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
|
checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -3029,9 +2998,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.69"
|
version = "0.2.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
|
checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
@ -3039,9 +3008,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.69"
|
version = "0.2.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
|
checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -3052,15 +3021,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.69"
|
version = "0.2.70"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
|
checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.46"
|
version = "0.3.47"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
|
checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
[![Build Status](https://img.shields.io/github/workflow/status/librespot-org/librespot/test/dev)](https://github.com/librespot-org/librespot/actions)
|
[![Build Status](https://github.com/librespot-org/librespot/workflows/test/badge.svg)](https://github.com/librespot-org/librespot/actions)
|
||||||
[![Build Status](https://travis-ci.org/librespot-org/librespot.svg?branch=dev)](https://travis-ci.org/librespot-org/librespot)
|
|
||||||
[![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources)
|
[![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)
|
[![Crates.io](https://img.shields.io/crates/v/librespot.svg)](https://crates.io/crates/librespot)
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ As the origin by [plietar](https://github.com/plietar/) is no longer actively ma
|
||||||
# Documentation
|
# Documentation
|
||||||
Documentation is currently a work in progress, contributions are welcome!
|
Documentation is currently a work in progress, contributions are welcome!
|
||||||
|
|
||||||
There is some brief documentation on how the protocol works in the [docs](https://github.com/librespot-org/librespot/tree/master/docs) folder,
|
There is some brief documentation on how the protocol works in the [docs](https://github.com/librespot-org/librespot/tree/master/docs) folder,
|
||||||
|
|
||||||
[COMPILING.md](https://github.com/librespot-org/librespot/blob/master/COMPILING.md) contains detailed instructions on setting up a development environment, and compiling librespot. More general usage and compilation information is available on the [wiki](https://github.com/librespot-org/librespot/wiki).
|
[COMPILING.md](https://github.com/librespot-org/librespot/blob/master/COMPILING.md) contains detailed instructions on setting up a development environment, and compiling librespot. More general usage and compilation information is available on the [wiki](https://github.com/librespot-org/librespot/wiki).
|
||||||
[CONTRIBUTING.md](https://github.com/librespot-org/librespot/blob/master/CONTRIBUTING.md) also contains our contributing guidelines.
|
[CONTRIBUTING.md](https://github.com/librespot-org/librespot/blob/master/CONTRIBUTING.md) also contains our contributing guidelines.
|
||||||
|
@ -32,7 +31,7 @@ If you wish to learn more about how librespot works overall, the best way is to
|
||||||
If you run into a bug when using librespot, please search the existing issues before opening a new one. Chances are, we've encountered it before, and have provided a resolution. If not, please open a new one, and where possible, include the backtrace librespot generates on crashing, along with anything we can use to reproduce the issue, eg. the Spotify URI of the song that caused the crash.
|
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
|
# Building
|
||||||
A quick walk through of the build process is outlined here, while a detailed compilation guide can be found [here](https://github.com/librespot-org/librespot/blob/master/COMPILING.md).
|
A quick walk through of the build process is outlined here, while a detailed compilation guide can be found [here](https://github.com/librespot-org/librespot/blob/master/COMPILING.md).
|
||||||
|
|
||||||
## Additional Dependencies
|
## Additional Dependencies
|
||||||
We recently switched to using [Rodio](https://github.com/tomaka/rodio) for audio playback by default, hence for macOS and Windows, you should just be able to clone and build librespot (with the command below).
|
We recently switched to using [Rodio](https://github.com/tomaka/rodio) for audio playback by default, hence for macOS and Windows, you should just be able to clone and build librespot (with the command below).
|
||||||
|
@ -86,6 +85,7 @@ The above command will create a receiver named ```Librespot```, with bitrate set
|
||||||
|
|
||||||
A full list of runtime options are available [here](https://github.com/librespot-org/librespot/wiki/Options)
|
A full list of runtime options are available [here](https://github.com/librespot-org/librespot/wiki/Options)
|
||||||
|
|
||||||
|
_Please Note: When using the cache feature, an authentication blob is stored for your account in the cache directory. For security purposes, we recommend that you set directory permissions on the cache directory to `700`._
|
||||||
## Contact
|
## Contact
|
||||||
Come and hang out on gitter if you need help or want to offer some.
|
Come and hang out on gitter if you need help or want to offer some.
|
||||||
https://gitter.im/librespot-org/spotify-connect-resources
|
https://gitter.im/librespot-org/spotify-connect-resources
|
||||||
|
@ -110,4 +110,3 @@ functionality.
|
||||||
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
|
- [librespot-java](https://github.com/devgianlu/librespot-java) - A Java port of librespot.
|
||||||
- [ncspot](https://github.com/hrkfdn/ncspot) - Cross-platform ncurses Spotify client.
|
- [ncspot](https://github.com/hrkfdn/ncspot) - Cross-platform ncurses Spotify client.
|
||||||
- [ansible-role-librespot](https://github.com/xMordax/ansible-role-librespot/tree/master) - Ansible role that will build, install and configure Librespot.
|
- [ansible-role-librespot](https://github.com/xMordax/ansible-role-librespot/tree/master) - Ansible role that will build, install and configure Librespot.
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ num-traits = "0.2"
|
||||||
pin-project-lite = "0.2.4"
|
pin-project-lite = "0.2.4"
|
||||||
tempfile = "3.1"
|
tempfile = "3.1"
|
||||||
|
|
||||||
librespot-tremor = { version = "0.1.0", optional = true }
|
librespot-tremor = { version = "0.2.0", optional = true }
|
||||||
vorbis = { version ="0.0.14", optional = true }
|
vorbis = { version ="0.0.14", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -312,8 +312,8 @@ impl AudioFile {
|
||||||
let session_ = session.clone();
|
let session_ = session.clone();
|
||||||
session.spawn(complete_rx.map_ok(move |mut file| {
|
session.spawn(complete_rx.map_ok(move |mut file| {
|
||||||
if let Some(cache) = session_.cache() {
|
if let Some(cache) = session_.cache() {
|
||||||
cache.save_file(file_id, &mut file);
|
|
||||||
debug!("File {} complete, saving to cache", file_id);
|
debug!("File {} complete, saving to cache", file_id);
|
||||||
|
cache.save_file(file_id, &mut file);
|
||||||
} else {
|
} else {
|
||||||
debug!("File {} complete", file_id);
|
debug!("File {} complete", file_id);
|
||||||
}
|
}
|
||||||
|
@ -336,6 +336,13 @@ impl AudioFile {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_cached(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AudioFile::Cached { .. } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioFileStreaming {
|
impl AudioFileStreaming {
|
||||||
|
|
|
@ -73,10 +73,6 @@ impl fmt::Display for VorbisError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl error::Error for VorbisError {
|
impl error::Error for VorbisError {
|
||||||
fn description(&self) -> &str {
|
|
||||||
error::Error::description(&self.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||||
error::Error::source(&self.0)
|
error::Error::source(&self.0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ impl Discovery {
|
||||||
"status": 101,
|
"status": 101,
|
||||||
"statusString": "ERROR-OK",
|
"statusString": "ERROR-OK",
|
||||||
"spotifyError": 0,
|
"spotifyError": 0,
|
||||||
"version": "2.1.0",
|
"version": "2.7.1",
|
||||||
"deviceID": (self.0.device_id),
|
"deviceID": (self.0.device_id),
|
||||||
"remoteName": (self.0.config.name),
|
"remoteName": (self.0.config.name),
|
||||||
"activeUser": "",
|
"activeUser": "",
|
||||||
|
@ -87,6 +87,9 @@ impl Discovery {
|
||||||
"accountReq": "PREMIUM",
|
"accountReq": "PREMIUM",
|
||||||
"brandDisplayName": "librespot",
|
"brandDisplayName": "librespot",
|
||||||
"modelDisplayName": "librespot",
|
"modelDisplayName": "librespot",
|
||||||
|
"resolverVersion": "0",
|
||||||
|
"groupStatus": "NONE",
|
||||||
|
"voiceSupport": "NO",
|
||||||
});
|
});
|
||||||
|
|
||||||
let body = result.to_string();
|
let body = result.to_string();
|
||||||
|
|
|
@ -21,7 +21,6 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError};
|
||||||
use librespot_core::util::url_encode;
|
use librespot_core::util::url_encode;
|
||||||
use librespot_core::util::SeqGenerator;
|
use librespot_core::util::SeqGenerator;
|
||||||
use librespot_core::version;
|
use librespot_core::version;
|
||||||
use librespot_core::volume::Volume;
|
|
||||||
|
|
||||||
enum SpircPlayStatus {
|
enum SpircPlayStatus {
|
||||||
Stopped,
|
Stopped,
|
||||||
|
@ -1297,7 +1296,7 @@ impl SpircTask {
|
||||||
self.mixer
|
self.mixer
|
||||||
.set_volume(volume_to_mixer(volume, &self.config.volume_ctrl));
|
.set_volume(volume_to_mixer(volume, &self.config.volume_ctrl));
|
||||||
if let Some(cache) = self.session.cache() {
|
if let Some(cache) = self.session.cache() {
|
||||||
cache.save_volume(Volume { volume })
|
cache.save_volume(volume)
|
||||||
}
|
}
|
||||||
self.player.emit_volume_set_event(volume);
|
self.player.emit_volume_set_event(volume);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ aes = "0.6"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
|
error-chain = { version = "0.12", default-features = false }
|
||||||
futures = { version = "0.3", features = ["bilock", "unstable"] }
|
futures = { version = "0.3", features = ["bilock", "unstable"] }
|
||||||
hmac = "0.7"
|
hmac = "0.7"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
|
|
|
@ -5,12 +5,10 @@ use hmac::Hmac;
|
||||||
use pbkdf2::pbkdf2;
|
use pbkdf2::pbkdf2;
|
||||||
use protobuf::ProtobufEnum;
|
use protobuf::ProtobufEnum;
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use std::fs::File;
|
use std::io::{self, Read};
|
||||||
use std::io::{self, Read, Write};
|
|
||||||
use std::ops::FnOnce;
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::protocol::authentication::AuthenticationType;
|
use crate::protocol::authentication::AuthenticationType;
|
||||||
|
use crate::protocol::keyexchange::{APLoginFailed, ErrorCode};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct Credentials {
|
pub struct Credentials {
|
||||||
|
@ -110,27 +108,6 @@ impl Credentials {
|
||||||
auth_data: auth_data,
|
auth_data: auth_data,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_reader<R: Read>(mut reader: R) -> Credentials {
|
|
||||||
let mut contents = String::new();
|
|
||||||
reader.read_to_string(&mut contents).unwrap();
|
|
||||||
|
|
||||||
serde_json::from_str(&contents).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_file<P: AsRef<Path>>(path: P) -> Option<Credentials> {
|
|
||||||
File::open(path).ok().map(Credentials::from_reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_to_writer<W: Write>(&self, writer: &mut W) {
|
|
||||||
let contents = serde_json::to_string(&self.clone()).unwrap();
|
|
||||||
writer.write_all(contents.as_bytes()).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {
|
|
||||||
let mut file = File::create(path).unwrap();
|
|
||||||
self.save_to_writer(&mut file)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
|
fn serialize_protobuf_enum<T, S>(v: &T, ser: S) -> Result<S::Ok, S::Error>
|
||||||
|
@ -189,3 +166,37 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
|
||||||
(None, _, None) => None,
|
(None, _, None) => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
types {
|
||||||
|
AuthenticationError, AuthenticationErrorKind, AuthenticationResultExt, AuthenticationResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_links {
|
||||||
|
Io(::std::io::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
errors {
|
||||||
|
BadCredentials {
|
||||||
|
description("Bad credentials")
|
||||||
|
display("Authentication failed with error: Bad credentials")
|
||||||
|
}
|
||||||
|
PremiumAccountRequired {
|
||||||
|
description("Premium account required")
|
||||||
|
display("Authentication failed with error: Premium account required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<APLoginFailed> for AuthenticationError {
|
||||||
|
fn from(login_failure: APLoginFailed) -> Self {
|
||||||
|
let error_code = login_failure.get_error_code();
|
||||||
|
match error_code {
|
||||||
|
ErrorCode::BadCredentials => Self::from_kind(AuthenticationErrorKind::BadCredentials),
|
||||||
|
ErrorCode::PremiumAccountRequired => {
|
||||||
|
Self::from_kind(AuthenticationErrorKind::PremiumAccountRequired)
|
||||||
|
}
|
||||||
|
_ => format!("Authentication failed with error: {:?}", error_code).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,115 +1,178 @@
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io;
|
use std::io::{self, Error, ErrorKind, Read, Write};
|
||||||
use std::io::Read;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use crate::authentication::Credentials;
|
use crate::authentication::Credentials;
|
||||||
use crate::spotify_id::FileId;
|
use crate::spotify_id::FileId;
|
||||||
use crate::volume::Volume;
|
|
||||||
|
|
||||||
|
/// A cache for volume, credentials and audio files.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
audio_root: PathBuf,
|
credentials_location: Option<PathBuf>,
|
||||||
system_root: PathBuf,
|
volume_location: Option<PathBuf>,
|
||||||
use_audio_cache: bool,
|
audio_location: Option<PathBuf>,
|
||||||
}
|
|
||||||
|
|
||||||
fn mkdir_existing(path: &Path) -> io::Result<()> {
|
|
||||||
fs::create_dir(path).or_else(|err| {
|
|
||||||
if err.kind() == io::ErrorKind::AlreadyExists {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
pub fn new(audio_location: PathBuf, system_location: PathBuf, use_audio_cache: bool) -> Cache {
|
pub fn new<P: AsRef<Path>>(
|
||||||
if use_audio_cache == true {
|
system_location: Option<P>,
|
||||||
mkdir_existing(&audio_location).unwrap();
|
audio_location: Option<P>,
|
||||||
mkdir_existing(&audio_location.join("files")).unwrap();
|
) -> io::Result<Self> {
|
||||||
|
if let Some(location) = &system_location {
|
||||||
|
fs::create_dir_all(location)?;
|
||||||
}
|
}
|
||||||
mkdir_existing(&system_location).unwrap();
|
|
||||||
|
|
||||||
Cache {
|
if let Some(location) = &audio_location {
|
||||||
audio_root: audio_location,
|
fs::create_dir_all(location)?;
|
||||||
system_root: system_location,
|
|
||||||
use_audio_cache: use_audio_cache,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cache {
|
let audio_location = audio_location.map(|p| p.as_ref().to_owned());
|
||||||
fn credentials_path(&self) -> PathBuf {
|
let volume_location = system_location.as_ref().map(|p| p.as_ref().join("volume"));
|
||||||
self.system_root.join("credentials.json")
|
let credentials_location = system_location
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| p.as_ref().join("credentials.json"));
|
||||||
|
|
||||||
|
let cache = Cache {
|
||||||
|
credentials_location,
|
||||||
|
volume_location,
|
||||||
|
audio_location,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn credentials(&self) -> Option<Credentials> {
|
pub fn credentials(&self) -> Option<Credentials> {
|
||||||
let path = self.credentials_path();
|
let location = self.credentials_location.as_ref()?;
|
||||||
Credentials::from_file(path)
|
|
||||||
|
// This closure is just convencience to enable the question mark operator
|
||||||
|
let read = || {
|
||||||
|
let mut file = File::open(location)?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
serde_json::from_str(&contents).map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
||||||
|
};
|
||||||
|
|
||||||
|
match read() {
|
||||||
|
Ok(c) => Some(c),
|
||||||
|
Err(e) => {
|
||||||
|
// If the file did not exist, the file was probably not written
|
||||||
|
// before. Otherwise, log the error.
|
||||||
|
if e.kind() != ErrorKind::NotFound {
|
||||||
|
warn!("Error reading credentials from cache: {}", e);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_credentials(&self, cred: &Credentials) {
|
pub fn save_credentials(&self, cred: &Credentials) {
|
||||||
let path = self.credentials_path();
|
if let Some(location) = &self.credentials_location {
|
||||||
cred.save_to_file(&path);
|
let result = File::create(location).and_then(|mut file| {
|
||||||
}
|
let data = serde_json::to_string(cred)?;
|
||||||
}
|
write!(file, "{}", data)
|
||||||
|
});
|
||||||
|
|
||||||
// cache volume to system_root/volume
|
if let Err(e) = result {
|
||||||
impl Cache {
|
warn!("Cannot save credentials to cache: {}", e)
|
||||||
fn volume_path(&self) -> PathBuf {
|
}
|
||||||
self.system_root.join("volume")
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn volume(&self) -> Option<u16> {
|
pub fn volume(&self) -> Option<u16> {
|
||||||
let path = self.volume_path();
|
let location = self.volume_location.as_ref()?;
|
||||||
Volume::from_file(path)
|
|
||||||
|
let read = || {
|
||||||
|
let mut file = File::open(location)?;
|
||||||
|
let mut contents = String::new();
|
||||||
|
file.read_to_string(&mut contents)?;
|
||||||
|
contents
|
||||||
|
.parse()
|
||||||
|
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
|
||||||
|
};
|
||||||
|
|
||||||
|
match read() {
|
||||||
|
Ok(v) => Some(v),
|
||||||
|
Err(e) => {
|
||||||
|
if e.kind() != ErrorKind::NotFound {
|
||||||
|
warn!("Error reading volume from cache: {}", e);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_volume(&self, volume: Volume) {
|
pub fn save_volume(&self, volume: u16) {
|
||||||
let path = self.volume_path();
|
if let Some(ref location) = self.volume_location {
|
||||||
volume.save_to_file(&path);
|
let result = File::create(location).and_then(|mut file| write!(file, "{}", volume));
|
||||||
|
if let Err(e) = result {
|
||||||
|
warn!("Cannot save volume to cache: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Cache {
|
fn file_path(&self, file: FileId) -> Option<PathBuf> {
|
||||||
fn file_path(&self, file: FileId) -> PathBuf {
|
self.audio_location.as_ref().map(|location| {
|
||||||
let name = file.to_base16();
|
let name = file.to_base16();
|
||||||
self.audio_root
|
let mut path = location.join(&name[0..2]);
|
||||||
.join("files")
|
path.push(&name[2..]);
|
||||||
.join(&name[0..2])
|
path
|
||||||
.join(&name[2..])
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn file(&self, file: FileId) -> Option<File> {
|
pub fn file(&self, file: FileId) -> Option<File> {
|
||||||
File::open(self.file_path(file)).ok()
|
File::open(self.file_path(file)?)
|
||||||
|
.map_err(|e| {
|
||||||
|
if e.kind() != ErrorKind::NotFound {
|
||||||
|
warn!("Error reading file from cache: {}", e)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_file(&self, file: FileId, contents: &mut dyn Read) {
|
pub fn save_file<F: Read>(&self, file: FileId, contents: &mut F) {
|
||||||
if self.use_audio_cache {
|
let path = if let Some(path) = self.file_path(file) {
|
||||||
let path = self.file_path(file);
|
path
|
||||||
mkdir_existing(path.parent().unwrap()).unwrap();
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let parent = path.parent().unwrap();
|
||||||
|
|
||||||
let mut cache_file = File::create(path).unwrap_or_else(|_e| {
|
let result = fs::create_dir_all(parent)
|
||||||
::std::fs::remove_dir_all(&self.audio_root.join("files")).unwrap();
|
.and_then(|_| File::create(&path))
|
||||||
mkdir_existing(&self.audio_root.join("files")).unwrap();
|
.and_then(|mut file| io::copy(contents, &mut file));
|
||||||
|
|
||||||
let path = self.file_path(file);
|
if let Err(e) = result {
|
||||||
mkdir_existing(path.parent().unwrap()).unwrap();
|
if e.kind() == ErrorKind::Other {
|
||||||
File::create(path).unwrap()
|
// Perhaps there's no space left in the cache
|
||||||
});
|
// TODO: try to narrow down the error (platform-dependently)
|
||||||
::std::io::copy(contents, &mut cache_file).unwrap_or_else(|_e| {
|
info!("An error occured while writing to cache, trying to flush the cache");
|
||||||
::std::fs::remove_dir_all(&self.audio_root.join("files")).unwrap();
|
|
||||||
mkdir_existing(&self.audio_root.join("files")).unwrap();
|
|
||||||
|
|
||||||
let path = self.file_path(file);
|
if fs::remove_dir_all(self.audio_location.as_ref().unwrap())
|
||||||
mkdir_existing(path.parent().unwrap()).unwrap();
|
.and_then(|_| fs::create_dir_all(parent))
|
||||||
let mut file = File::create(path).unwrap();
|
.and_then(|_| File::create(&path))
|
||||||
::std::io::copy(contents, &mut file).unwrap()
|
.and_then(|mut file| io::copy(contents, &mut file))
|
||||||
});
|
.is_ok()
|
||||||
|
{
|
||||||
|
// It worked, there's no need to print a warning
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn!("Cannot save file to cache: {}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_file(&self, file: FileId) -> bool {
|
||||||
|
if let Some(path) = self.file_path(file) {
|
||||||
|
if let Err(err) = fs::remove_file(path) {
|
||||||
|
warn!("Unable to remove file from cache: {}", err);
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,16 @@ pub enum DeviceType {
|
||||||
AVR = 6,
|
AVR = 6,
|
||||||
STB = 7,
|
STB = 7,
|
||||||
AudioDongle = 8,
|
AudioDongle = 8,
|
||||||
|
GameConsole = 9,
|
||||||
|
CastAudio = 10,
|
||||||
|
CastVideo = 11,
|
||||||
|
Automobile = 12,
|
||||||
|
Smartwatch = 13,
|
||||||
|
Chromebook = 14,
|
||||||
|
UnknownSpotify = 100,
|
||||||
|
CarThing = 101,
|
||||||
|
Observer = 102,
|
||||||
|
HomeThing = 103,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for DeviceType {
|
impl FromStr for DeviceType {
|
||||||
|
@ -51,6 +61,14 @@ impl FromStr for DeviceType {
|
||||||
"avr" => Ok(AVR),
|
"avr" => Ok(AVR),
|
||||||
"stb" => Ok(STB),
|
"stb" => Ok(STB),
|
||||||
"audiodongle" => Ok(AudioDongle),
|
"audiodongle" => Ok(AudioDongle),
|
||||||
|
"gameconsole" => Ok(GameConsole),
|
||||||
|
"castaudio" => Ok(CastAudio),
|
||||||
|
"castvideo" => Ok(CastVideo),
|
||||||
|
"automobile" => Ok(Automobile),
|
||||||
|
"smartwatch" => Ok(Smartwatch),
|
||||||
|
"chromebook" => Ok(Chromebook),
|
||||||
|
"carthing" => Ok(CarThing),
|
||||||
|
"homething" => Ok(HomeThing),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +87,16 @@ impl fmt::Display for DeviceType {
|
||||||
AVR => f.write_str("AVR"),
|
AVR => f.write_str("AVR"),
|
||||||
STB => f.write_str("STB"),
|
STB => f.write_str("STB"),
|
||||||
AudioDongle => f.write_str("AudioDongle"),
|
AudioDongle => f.write_str("AudioDongle"),
|
||||||
|
GameConsole => f.write_str("GameConsole"),
|
||||||
|
CastAudio => f.write_str("CastAudio"),
|
||||||
|
CastVideo => f.write_str("CastVideo"),
|
||||||
|
Automobile => f.write_str("Automobile"),
|
||||||
|
Smartwatch => f.write_str("Smartwatch"),
|
||||||
|
Chromebook => f.write_str("Chromebook"),
|
||||||
|
UnknownSpotify => f.write_str("UnknownSpotify"),
|
||||||
|
CarThing => f.write_str("CarThing"),
|
||||||
|
Observer => f.write_str("Observer"),
|
||||||
|
HomeThing => f.write_str("HomeThing"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ use tokio::net::TcpStream;
|
||||||
use tokio_util::codec::Framed;
|
use tokio_util::codec::Framed;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::authentication::Credentials;
|
use crate::authentication::{AuthenticationError, Credentials};
|
||||||
use crate::version;
|
use crate::version;
|
||||||
|
|
||||||
use crate::proxytunnel;
|
use crate::proxytunnel;
|
||||||
|
@ -64,7 +64,7 @@ pub async fn authenticate(
|
||||||
transport: &mut Transport,
|
transport: &mut Transport,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
device_id: &str,
|
device_id: &str,
|
||||||
) -> io::Result<Credentials> {
|
) -> Result<Credentials, AuthenticationError> {
|
||||||
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
|
||||||
use crate::protocol::keyexchange::APLoginFailed;
|
use crate::protocol::keyexchange::APLoginFailed;
|
||||||
|
|
||||||
|
@ -114,10 +114,7 @@ pub async fn authenticate(
|
||||||
|
|
||||||
0xad => {
|
0xad => {
|
||||||
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!(
|
Err(error_data.into())
|
||||||
"Authentication failed with reason: {:?}",
|
|
||||||
error_data.get_error_code()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => panic!("Unexpected packet {:?}", cmd),
|
_ => panic!("Unexpected packet {:?}", cmd),
|
||||||
|
|
|
@ -6,6 +6,8 @@ extern crate log;
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pin_project_lite;
|
extern crate pin_project_lite;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate error_chain;
|
||||||
extern crate aes;
|
extern crate aes;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
|
@ -51,4 +53,3 @@ pub mod session;
|
||||||
pub mod spotify_id;
|
pub mod spotify_id;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
pub mod volume;
|
|
||||||
|
|
|
@ -19,6 +19,8 @@ use crate::config::SessionConfig;
|
||||||
use crate::connection;
|
use crate::connection;
|
||||||
use crate::mercury::MercuryManager;
|
use crate::mercury::MercuryManager;
|
||||||
|
|
||||||
|
pub use crate::authentication::{AuthenticationError, AuthenticationErrorKind};
|
||||||
|
|
||||||
struct SessionData {
|
struct SessionData {
|
||||||
country: String,
|
country: String,
|
||||||
time_delta: i64,
|
time_delta: i64,
|
||||||
|
@ -50,7 +52,7 @@ impl Session {
|
||||||
config: SessionConfig,
|
config: SessionConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
cache: Option<Cache>,
|
cache: Option<Cache>,
|
||||||
) -> io::Result<Session> {
|
) -> Result<Session, AuthenticationError> {
|
||||||
let ap = apresolve_or_fallback(&config.proxy, &config.ap_port).await;
|
let ap = apresolve_or_fallback(&config.proxy, &config.ap_port).await;
|
||||||
|
|
||||||
info!("Connecting to AP \"{}\"", ap);
|
info!("Connecting to AP \"{}\"", ap);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std;
|
use std::convert::TryInto;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
|
@ -8,6 +8,26 @@ pub enum SpotifyAudioType {
|
||||||
NonPlayable,
|
NonPlayable,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<&str> for SpotifyAudioType {
|
||||||
|
fn from(v: &str) -> Self {
|
||||||
|
match v {
|
||||||
|
"track" => SpotifyAudioType::Track,
|
||||||
|
"episode" => SpotifyAudioType::Podcast,
|
||||||
|
_ => SpotifyAudioType::NonPlayable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<&str> for SpotifyAudioType {
|
||||||
|
fn into(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
SpotifyAudioType::Track => "track",
|
||||||
|
SpotifyAudioType::Podcast => "episode",
|
||||||
|
SpotifyAudioType::NonPlayable => "unknown",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct SpotifyId {
|
pub struct SpotifyId {
|
||||||
pub id: u128,
|
pub id: u128,
|
||||||
|
@ -17,104 +37,178 @@ pub struct SpotifyId {
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct SpotifyIdError;
|
pub struct SpotifyIdError;
|
||||||
|
|
||||||
const BASE62_DIGITS: &'static [u8] =
|
const BASE62_DIGITS: &[u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||||
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
const BASE16_DIGITS: &[u8; 16] = b"0123456789abcdef";
|
||||||
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
|
|
||||||
|
|
||||||
impl SpotifyId {
|
impl SpotifyId {
|
||||||
|
const SIZE: usize = 16;
|
||||||
|
const SIZE_BASE16: usize = 32;
|
||||||
|
const SIZE_BASE62: usize = 22;
|
||||||
|
|
||||||
fn as_track(n: u128) -> SpotifyId {
|
fn as_track(n: u128) -> SpotifyId {
|
||||||
SpotifyId {
|
SpotifyId {
|
||||||
id: n.to_owned(),
|
id: n,
|
||||||
audio_type: SpotifyAudioType::Track,
|
audio_type: SpotifyAudioType::Track,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_base16(id: &str) -> Result<SpotifyId, SpotifyIdError> {
|
/// Parses a base16 (hex) encoded [Spotify ID] into a `SpotifyId`.
|
||||||
let data = id.as_bytes();
|
///
|
||||||
|
/// `src` is expected to be 32 bytes long and encoded using valid characters.
|
||||||
|
///
|
||||||
|
/// [Spotify ID]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
|
pub fn from_base16(src: &str) -> Result<SpotifyId, SpotifyIdError> {
|
||||||
|
let mut dst: u128 = 0;
|
||||||
|
|
||||||
let mut n = 0u128;
|
for c in src.as_bytes() {
|
||||||
for c in data {
|
let p = match c {
|
||||||
let d = match BASE16_DIGITS.iter().position(|e| e == c) {
|
b'0'..=b'9' => c - b'0',
|
||||||
None => return Err(SpotifyIdError),
|
b'a'..=b'f' => c - b'a' + 10,
|
||||||
Some(x) => x as u128,
|
_ => return Err(SpotifyIdError),
|
||||||
};
|
} as u128;
|
||||||
n = n * 16;
|
|
||||||
n = n + d;
|
dst <<= 4;
|
||||||
|
dst += p;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SpotifyId::as_track(n))
|
Ok(SpotifyId::as_track(dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_base62(id: &str) -> Result<SpotifyId, SpotifyIdError> {
|
/// Parses a base62 encoded [Spotify ID] into a `SpotifyId`.
|
||||||
let data = id.as_bytes();
|
///
|
||||||
|
/// `src` is expected to be 22 bytes long and encoded using valid characters.
|
||||||
|
///
|
||||||
|
/// [Spotify ID]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
|
pub fn from_base62(src: &str) -> Result<SpotifyId, SpotifyIdError> {
|
||||||
|
let mut dst: u128 = 0;
|
||||||
|
|
||||||
let mut n = 0u128;
|
for c in src.as_bytes() {
|
||||||
for c in data {
|
let p = match c {
|
||||||
let d = match BASE62_DIGITS.iter().position(|e| e == c) {
|
b'0'..=b'9' => c - b'0',
|
||||||
None => return Err(SpotifyIdError),
|
b'a'..=b'z' => c - b'a' + 10,
|
||||||
Some(x) => x as u128,
|
b'A'..=b'Z' => c - b'A' + 36,
|
||||||
};
|
_ => return Err(SpotifyIdError),
|
||||||
n = n * 62;
|
} as u128;
|
||||||
n = n + d;
|
|
||||||
|
dst *= 62;
|
||||||
|
dst += p;
|
||||||
}
|
}
|
||||||
Ok(SpotifyId::as_track(n))
|
|
||||||
|
Ok(SpotifyId::as_track(dst))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_raw(data: &[u8]) -> Result<SpotifyId, SpotifyIdError> {
|
/// Creates a `SpotifyId` from a copy of `SpotifyId::SIZE` (16) bytes in big-endian order.
|
||||||
if data.len() != 16 {
|
///
|
||||||
|
/// The resulting `SpotifyId` will default to a `SpotifyAudioType::TRACK`.
|
||||||
|
pub fn from_raw(src: &[u8]) -> Result<SpotifyId, SpotifyIdError> {
|
||||||
|
match src.try_into() {
|
||||||
|
Ok(dst) => Ok(SpotifyId::as_track(u128::from_be_bytes(dst))),
|
||||||
|
Err(_) => Err(SpotifyIdError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a [Spotify URI] into a `SpotifyId`.
|
||||||
|
///
|
||||||
|
/// `uri` is expected to be in the canonical form `spotify:{type}:{id}`, where `{type}`
|
||||||
|
/// can be arbitrary while `{id}` is a 22-character long, base62 encoded Spotify ID.
|
||||||
|
///
|
||||||
|
/// [Spotify URI]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
|
pub fn from_uri(src: &str) -> Result<SpotifyId, SpotifyIdError> {
|
||||||
|
// We expect the ID to be the last colon-delimited item in the URI.
|
||||||
|
let b = src.as_bytes();
|
||||||
|
let id_i = b.len() - SpotifyId::SIZE_BASE62;
|
||||||
|
if b[id_i - 1] != b':' {
|
||||||
return Err(SpotifyIdError);
|
return Err(SpotifyIdError);
|
||||||
};
|
|
||||||
|
|
||||||
let mut arr: [u8; 16] = Default::default();
|
|
||||||
arr.copy_from_slice(&data[0..16]);
|
|
||||||
|
|
||||||
Ok(SpotifyId::as_track(u128::from_be_bytes(arr)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_uri(uri: &str) -> Result<SpotifyId, SpotifyIdError> {
|
|
||||||
let parts = uri.split(":").collect::<Vec<&str>>();
|
|
||||||
let gid = parts.last().unwrap();
|
|
||||||
if uri.contains(":episode:") {
|
|
||||||
let mut spotify_id = SpotifyId::from_base62(gid).unwrap();
|
|
||||||
let _ = std::mem::replace(&mut spotify_id.audio_type, SpotifyAudioType::Podcast);
|
|
||||||
Ok(spotify_id)
|
|
||||||
} else if uri.contains(":track:") {
|
|
||||||
SpotifyId::from_base62(gid)
|
|
||||||
} else {
|
|
||||||
// show/playlist/artist/album/??
|
|
||||||
let mut spotify_id = SpotifyId::from_base62(gid).unwrap();
|
|
||||||
let _ = std::mem::replace(&mut spotify_id.audio_type, SpotifyAudioType::NonPlayable);
|
|
||||||
Ok(spotify_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut id = SpotifyId::from_base62(&src[id_i..])?;
|
||||||
|
|
||||||
|
// Slice offset by 8 as we are skipping the "spotify:" prefix.
|
||||||
|
id.audio_type = src[8..id_i - 1].into();
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `SpotifyId` as a base16 (hex) encoded, `SpotifyId::SIZE_BASE62` (22)
|
||||||
|
/// character long `String`.
|
||||||
pub fn to_base16(&self) -> String {
|
pub fn to_base16(&self) -> String {
|
||||||
format!("{:032x}", self.id)
|
to_base16(&self.to_raw(), &mut [0u8; SpotifyId::SIZE_BASE16])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `SpotifyId` as a [canonically] base62 encoded, `SpotifyId::SIZE_BASE62` (22)
|
||||||
|
/// character long `String`.
|
||||||
|
///
|
||||||
|
/// [canonically]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
pub fn to_base62(&self) -> String {
|
pub fn to_base62(&self) -> String {
|
||||||
let &SpotifyId { id: mut n, .. } = self;
|
let mut dst = [0u8; 22];
|
||||||
|
let mut i = 0;
|
||||||
|
let n = self.id;
|
||||||
|
|
||||||
let mut data = [0u8; 22];
|
// The algorithm is based on:
|
||||||
for i in 0..22 {
|
// https://github.com/trezor/trezor-crypto/blob/c316e775a2152db255ace96b6b65ac0f20525ec0/base58.c
|
||||||
data[21 - i] = BASE62_DIGITS[(n % 62) as usize];
|
//
|
||||||
n /= 62;
|
// We are not using naive division of self.id as it is an u128 and div + mod are software
|
||||||
|
// emulated at runtime (and unoptimized into mul + shift) on non-128bit platforms,
|
||||||
|
// making them very expensive.
|
||||||
|
//
|
||||||
|
// Trezor's algorithm allows us to stick to arithmetic on native registers making this
|
||||||
|
// an order of magnitude faster. Additionally, as our sizes are known, instead of
|
||||||
|
// dealing with the ID on a byte by byte basis, we decompose it into four u32s and
|
||||||
|
// use 64-bit arithmetic on them for an additional speedup.
|
||||||
|
for shift in &[96, 64, 32, 0] {
|
||||||
|
let mut carry = (n >> shift) as u32 as u64;
|
||||||
|
|
||||||
|
for b in &mut dst[..i] {
|
||||||
|
carry += (*b as u64) << 32;
|
||||||
|
*b = (carry % 62) as u8;
|
||||||
|
carry /= 62;
|
||||||
|
}
|
||||||
|
|
||||||
|
while carry > 0 {
|
||||||
|
dst[i] = (carry % 62) as u8;
|
||||||
|
carry /= 62;
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::str::from_utf8(&data).unwrap().to_owned()
|
for b in &mut dst {
|
||||||
}
|
*b = BASE62_DIGITS[*b as usize];
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_uri(&self) -> String {
|
dst.reverse();
|
||||||
match self.audio_type {
|
|
||||||
SpotifyAudioType::Track => format!("spotify:track:{}", self.to_base62()),
|
unsafe {
|
||||||
SpotifyAudioType::Podcast => format!("spotify:episode:{}", self.to_base62()),
|
// Safety: We are only dealing with ASCII characters.
|
||||||
SpotifyAudioType::NonPlayable => format!("spotify:unknown:{}", self.to_base62()),
|
String::from_utf8_unchecked(dst.to_vec())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_raw(&self) -> [u8; 16] {
|
/// Returns a copy of the `SpotifyId` as an array of `SpotifyId::SIZE` (16) bytes in
|
||||||
|
/// big-endian order.
|
||||||
|
pub fn to_raw(&self) -> [u8; SpotifyId::SIZE] {
|
||||||
self.id.to_be_bytes()
|
self.id.to_be_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the `SpotifyId` as a [Spotify URI] in the canonical form `spotify:{type}:{id}`,
|
||||||
|
/// where `{type}` is an arbitrary string and `{id}` is a 22-character long, base62 encoded
|
||||||
|
/// Spotify ID.
|
||||||
|
///
|
||||||
|
/// If the `SpotifyId` has an associated type unrecognized by the library, `{type}` will
|
||||||
|
/// be encoded as `unknown`.
|
||||||
|
///
|
||||||
|
/// [Spotify URI]: https://developer.spotify.com/documentation/web-api/#spotify-uris-and-ids
|
||||||
|
pub fn to_uri(&self) -> String {
|
||||||
|
// 8 chars for the "spotify:" prefix + 1 colon + 22 chars base62 encoded ID = 31
|
||||||
|
// + unknown size audio_type.
|
||||||
|
let audio_type: &str = self.audio_type.into();
|
||||||
|
let mut dst = String::with_capacity(31 + audio_type.len());
|
||||||
|
dst.push_str("spotify:");
|
||||||
|
dst.push_str(audio_type);
|
||||||
|
dst.push(':');
|
||||||
|
dst.push_str(&self.to_base62());
|
||||||
|
|
||||||
|
dst
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
@ -122,11 +216,7 @@ pub struct FileId(pub [u8; 20]);
|
||||||
|
|
||||||
impl FileId {
|
impl FileId {
|
||||||
pub fn to_base16(&self) -> String {
|
pub fn to_base16(&self) -> String {
|
||||||
self.0
|
to_base16(&self.0, &mut [0u8; 40])
|
||||||
.iter()
|
|
||||||
.map(|b| format!("{:02x}", b))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.concat()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,3 +231,185 @@ impl fmt::Display for FileId {
|
||||||
f.write_str(&self.to_base16())
|
f.write_str(&self.to_base16())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn to_base16(src: &[u8], buf: &mut [u8]) -> String {
|
||||||
|
let mut i = 0;
|
||||||
|
for v in src {
|
||||||
|
buf[i] = BASE16_DIGITS[(v >> 4) as usize];
|
||||||
|
buf[i + 1] = BASE16_DIGITS[(v & 0x0f) as usize];
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Safety: We are only dealing with ASCII characters.
|
||||||
|
String::from_utf8_unchecked(buf.to_vec())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct ConversionCase {
|
||||||
|
id: u128,
|
||||||
|
kind: SpotifyAudioType,
|
||||||
|
uri: &'static str,
|
||||||
|
base16: &'static str,
|
||||||
|
base62: &'static str,
|
||||||
|
raw: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONV_VALID: [ConversionCase; 4] = [
|
||||||
|
ConversionCase {
|
||||||
|
id: 238762092608182713602505436543891614649,
|
||||||
|
kind: SpotifyAudioType::Track,
|
||||||
|
uri: "spotify:track:5sWHDYs0csV6RS48xBl0tH",
|
||||||
|
base16: "b39fe8081e1f4c54be38e8d6f9f12bb9",
|
||||||
|
base62: "5sWHDYs0csV6RS48xBl0tH",
|
||||||
|
raw: &[
|
||||||
|
179, 159, 232, 8, 30, 31, 76, 84, 190, 56, 232, 214, 249, 241, 43, 185,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ConversionCase {
|
||||||
|
id: 204841891221366092811751085145916697048,
|
||||||
|
kind: SpotifyAudioType::Track,
|
||||||
|
uri: "spotify:track:4GNcXTGWmnZ3ySrqvol3o4",
|
||||||
|
base16: "9a1b1cfbc6f244569ae0356c77bbe9d8",
|
||||||
|
base62: "4GNcXTGWmnZ3ySrqvol3o4",
|
||||||
|
raw: &[
|
||||||
|
154, 27, 28, 251, 198, 242, 68, 86, 154, 224, 53, 108, 119, 187, 233, 216,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ConversionCase {
|
||||||
|
id: 204841891221366092811751085145916697048,
|
||||||
|
kind: SpotifyAudioType::Podcast,
|
||||||
|
uri: "spotify:episode:4GNcXTGWmnZ3ySrqvol3o4",
|
||||||
|
base16: "9a1b1cfbc6f244569ae0356c77bbe9d8",
|
||||||
|
base62: "4GNcXTGWmnZ3ySrqvol3o4",
|
||||||
|
raw: &[
|
||||||
|
154, 27, 28, 251, 198, 242, 68, 86, 154, 224, 53, 108, 119, 187, 233, 216,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ConversionCase {
|
||||||
|
id: 204841891221366092811751085145916697048,
|
||||||
|
kind: SpotifyAudioType::NonPlayable,
|
||||||
|
uri: "spotify:unknown:4GNcXTGWmnZ3ySrqvol3o4",
|
||||||
|
base16: "9a1b1cfbc6f244569ae0356c77bbe9d8",
|
||||||
|
base62: "4GNcXTGWmnZ3ySrqvol3o4",
|
||||||
|
raw: &[
|
||||||
|
154, 27, 28, 251, 198, 242, 68, 86, 154, 224, 53, 108, 119, 187, 233, 216,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
static CONV_INVALID: [ConversionCase; 2] = [
|
||||||
|
ConversionCase {
|
||||||
|
id: 0,
|
||||||
|
kind: SpotifyAudioType::NonPlayable,
|
||||||
|
// Invalid ID in the URI.
|
||||||
|
uri: "spotify:arbitrarywhatever:5sWHDYs0Bl0tH",
|
||||||
|
base16: "ZZZZZ8081e1f4c54be38e8d6f9f12bb9",
|
||||||
|
base62: "!!!!!Ys0csV6RS48xBl0tH",
|
||||||
|
raw: &[
|
||||||
|
// Invalid length.
|
||||||
|
154, 27, 28, 251, 198, 242, 68, 86, 154, 224, 5, 3, 108, 119, 187, 233, 216, 255,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
ConversionCase {
|
||||||
|
id: 0,
|
||||||
|
kind: SpotifyAudioType::NonPlayable,
|
||||||
|
// Missing colon between ID and type.
|
||||||
|
uri: "spotify:arbitrarywhatever5sWHDYs0csV6RS48xBl0tH",
|
||||||
|
base16: "--------------------",
|
||||||
|
base62: "....................",
|
||||||
|
raw: &[
|
||||||
|
// Invalid length.
|
||||||
|
154, 27, 28, 251,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_base62() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
assert_eq!(SpotifyId::from_base62(c.base62).unwrap().id, c.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in &CONV_INVALID {
|
||||||
|
assert_eq!(SpotifyId::from_base62(c.base62), Err(SpotifyIdError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_base62() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
let id = SpotifyId {
|
||||||
|
id: c.id,
|
||||||
|
audio_type: c.kind,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(id.to_base62(), c.base62);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_base16() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
assert_eq!(SpotifyId::from_base16(c.base16).unwrap().id, c.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in &CONV_INVALID {
|
||||||
|
assert_eq!(SpotifyId::from_base16(c.base16), Err(SpotifyIdError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_base16() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
let id = SpotifyId {
|
||||||
|
id: c.id,
|
||||||
|
audio_type: c.kind,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(id.to_base16(), c.base16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_uri() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
let actual = SpotifyId::from_uri(c.uri).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(actual.id, c.id);
|
||||||
|
assert_eq!(actual.audio_type, c.kind);
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in &CONV_INVALID {
|
||||||
|
assert_eq!(SpotifyId::from_uri(c.uri), Err(SpotifyIdError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_uri() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
let id = SpotifyId {
|
||||||
|
id: c.id,
|
||||||
|
audio_type: c.kind,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(id.to_uri(), c.uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn from_raw() {
|
||||||
|
for c in &CONV_VALID {
|
||||||
|
assert_eq!(SpotifyId::from_raw(c.raw).unwrap().id, c.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
for c in &CONV_INVALID {
|
||||||
|
assert_eq!(SpotifyId::from_raw(c.raw), Err(SpotifyIdError));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub struct Volume {
|
|
||||||
pub volume: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Volume {
|
|
||||||
// read volume from file
|
|
||||||
fn from_reader<R: Read>(mut reader: R) -> u16 {
|
|
||||||
let mut contents = String::new();
|
|
||||||
reader.read_to_string(&mut contents).unwrap();
|
|
||||||
contents.trim().parse::<u16>().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_file<P: AsRef<Path>>(path: P) -> Option<u16> {
|
|
||||||
File::open(path).ok().map(Volume::from_reader)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write volume to file
|
|
||||||
fn save_to_writer<W: Write>(&self, writer: &mut W) {
|
|
||||||
writer
|
|
||||||
.write_all(self.volume.to_string().as_bytes())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn save_to_file<P: AsRef<Path>>(&self, path: P) {
|
|
||||||
let mut file = File::create(path).unwrap();
|
|
||||||
self.save_to_writer(&mut file)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,7 +23,7 @@ log = "0.4"
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
shell-words = "1.0.0"
|
shell-words = "1.0.0"
|
||||||
|
|
||||||
alsa = { version = "0.2", optional = true }
|
alsa = { version = "0.4", optional = true }
|
||||||
portaudio-rs = { version = "0.3", optional = true }
|
portaudio-rs = { version = "0.3", optional = true }
|
||||||
libpulse-binding = { version = "2.13", optional = true, default-features = false }
|
libpulse-binding = { version = "2.13", optional = true, default-features = false }
|
||||||
libpulse-simple-binding = { version = "2.13", optional = true, default-features = false }
|
libpulse-simple-binding = { version = "2.13", optional = true, default-features = false }
|
||||||
|
|
|
@ -67,7 +67,11 @@ impl Sink for PulseAudioSink {
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
||||||
if let Some(s) = &self.s {
|
if let Some(s) = &self.s {
|
||||||
let d: &[u8] = unsafe { std::mem::transmute(data) };
|
// SAFETY: An i16 consists of two bytes, so that the given slice can be interpreted
|
||||||
|
// as a byte array of double length. Each byte pointer is validly aligned, and so
|
||||||
|
// is the newly created slice.
|
||||||
|
let d: &[u8] =
|
||||||
|
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) };
|
||||||
|
|
||||||
match s.write(d) {
|
match s.write(d) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
|
|
@ -25,10 +25,34 @@ impl Default for Bitrate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum NormalisationType {
|
||||||
|
Album,
|
||||||
|
Track,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for NormalisationType {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"album" => Ok(NormalisationType::Album),
|
||||||
|
"track" => Ok(NormalisationType::Track),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NormalisationType {
|
||||||
|
fn default() -> NormalisationType {
|
||||||
|
NormalisationType::Album
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PlayerConfig {
|
pub struct PlayerConfig {
|
||||||
pub bitrate: Bitrate,
|
pub bitrate: Bitrate,
|
||||||
pub normalisation: bool,
|
pub normalisation: bool,
|
||||||
|
pub normalisation_type: NormalisationType,
|
||||||
pub normalisation_pregain: f32,
|
pub normalisation_pregain: f32,
|
||||||
pub gapless: bool,
|
pub gapless: bool,
|
||||||
}
|
}
|
||||||
|
@ -38,6 +62,7 @@ impl Default for PlayerConfig {
|
||||||
PlayerConfig {
|
PlayerConfig {
|
||||||
bitrate: Bitrate::default(),
|
bitrate: Bitrate::default(),
|
||||||
normalisation: false,
|
normalisation: false,
|
||||||
|
normalisation_type: NormalisationType::default(),
|
||||||
normalisation_pregain: 0.0,
|
normalisation_pregain: 0.0,
|
||||||
gapless: true,
|
gapless: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::audio::{
|
||||||
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
||||||
};
|
};
|
||||||
use crate::audio_backend::Sink;
|
use crate::audio_backend::Sink;
|
||||||
|
use crate::config::NormalisationType;
|
||||||
use crate::config::{Bitrate, PlayerConfig};
|
use crate::config::{Bitrate, PlayerConfig};
|
||||||
use crate::librespot_core::tokio;
|
use crate::librespot_core::tokio;
|
||||||
use crate::metadata::{AudioItem, FileFormat};
|
use crate::metadata::{AudioItem, FileFormat};
|
||||||
|
@ -217,17 +218,20 @@ impl NormalisationData {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
|
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
|
||||||
let mut normalisation_factor = f32::powf(
|
let [gain_db, gain_peak] = match config.normalisation_type {
|
||||||
10.0,
|
NormalisationType::Album => [data.album_gain_db, data.album_peak],
|
||||||
(data.track_gain_db + config.normalisation_pregain) / 20.0,
|
NormalisationType::Track => [data.track_gain_db, data.track_peak],
|
||||||
);
|
};
|
||||||
|
let mut normalisation_factor =
|
||||||
|
f32::powf(10.0, (gain_db + config.normalisation_pregain) / 20.0);
|
||||||
|
|
||||||
if normalisation_factor * data.track_peak > 1.0 {
|
if normalisation_factor * gain_peak > 1.0 {
|
||||||
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid.");
|
||||||
normalisation_factor = 1.0 / data.track_peak;
|
normalisation_factor = 1.0 / gain_peak;
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Normalisation Data: {:?}", data);
|
debug!("Normalisation Data: {:?}", data);
|
||||||
|
debug!("Normalisation Type: {:?}", config.normalisation_type);
|
||||||
debug!("Applied normalisation factor: {}", normalisation_factor);
|
debug!("Applied normalisation factor: {}", normalisation_factor);
|
||||||
|
|
||||||
normalisation_factor
|
normalisation_factor
|
||||||
|
@ -652,49 +656,27 @@ impl PlayerTrackLoader {
|
||||||
FileFormat::OGG_VORBIS_96,
|
FileFormat::OGG_VORBIS_96,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
let format = formats
|
|
||||||
.iter()
|
|
||||||
.find(|format| audio.files.contains_key(format))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let file_id = match audio.files.get(&format) {
|
let entry = formats.iter().find_map(|format| {
|
||||||
Some(&file_id) => file_id,
|
if let Some(&file_id) = audio.files.get(format) {
|
||||||
|
Some((*format, file_id))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let (format, file_id) = match entry {
|
||||||
|
Some(t) => t,
|
||||||
None => {
|
None => {
|
||||||
warn!("<{}> in not available in format {:?}", audio.name, format);
|
warn!("<{}> is not available in any supported format", audio.name);
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes_per_second = self.stream_data_rate(*format);
|
let bytes_per_second = self.stream_data_rate(format);
|
||||||
let play_from_beginning = position_ms == 0;
|
let play_from_beginning = position_ms == 0;
|
||||||
|
|
||||||
let key = self.session.audio_key().request(spotify_id, file_id);
|
let key = match self.session.audio_key().request(spotify_id, file_id).await {
|
||||||
let encrypted_file = AudioFile::open(
|
|
||||||
&self.session,
|
|
||||||
file_id,
|
|
||||||
bytes_per_second,
|
|
||||||
play_from_beginning,
|
|
||||||
);
|
|
||||||
|
|
||||||
let encrypted_file = match encrypted_file.await {
|
|
||||||
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();
|
|
||||||
|
|
||||||
if play_from_beginning {
|
|
||||||
// No need to seek -> we stream from the beginning
|
|
||||||
stream_loader_controller.set_stream_mode();
|
|
||||||
} else {
|
|
||||||
// we need to seek -> we set stream mode after the initial seek.
|
|
||||||
stream_loader_controller.set_random_access_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
let key = match key.await {
|
|
||||||
Ok(key) => key,
|
Ok(key) => key,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
error!("Unable to load decryption key");
|
error!("Unable to load decryption key");
|
||||||
|
@ -702,39 +684,90 @@ impl PlayerTrackLoader {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
// This is only a loop to be able to reload the file if an error occured
|
||||||
|
// while opening a cached file.
|
||||||
|
loop {
|
||||||
|
let encrypted_file = AudioFile::open(
|
||||||
|
&self.session,
|
||||||
|
file_id,
|
||||||
|
bytes_per_second,
|
||||||
|
play_from_beginning,
|
||||||
|
);
|
||||||
|
|
||||||
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) {
|
let encrypted_file = match encrypted_file.await {
|
||||||
Ok(normalisation_data) => {
|
Ok(encrypted_file) => encrypted_file,
|
||||||
NormalisationData::get_factor(&self.config, normalisation_data)
|
Err(_) => {
|
||||||
|
error!("Unable to load encrypted file.");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let is_cached = encrypted_file.is_cached();
|
||||||
|
|
||||||
|
let mut stream_loader_controller = encrypted_file.get_stream_loader_controller();
|
||||||
|
|
||||||
|
if play_from_beginning {
|
||||||
|
// No need to seek -> we stream from the beginning
|
||||||
|
stream_loader_controller.set_stream_mode();
|
||||||
|
} else {
|
||||||
|
// we need to seek -> we set stream mode after the initial seek.
|
||||||
|
stream_loader_controller.set_random_access_mode();
|
||||||
}
|
}
|
||||||
Err(_) => {
|
|
||||||
warn!("Unable to extract normalisation data, using default value.");
|
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
|
||||||
1.0_f32
|
|
||||||
|
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file)
|
||||||
|
{
|
||||||
|
Ok(normalisation_data) => {
|
||||||
|
NormalisationData::get_factor(&self.config, normalisation_data)
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
warn!("Unable to extract normalisation data, using default value.");
|
||||||
|
1.0_f32
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
||||||
|
|
||||||
|
let mut decoder = match VorbisDecoder::new(audio_file) {
|
||||||
|
Ok(decoder) => decoder,
|
||||||
|
Err(e) if is_cached => {
|
||||||
|
warn!(
|
||||||
|
"Unable to read cached audio file: {}. Trying to download it.",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
|
||||||
|
// unwrap safety: The file is cached, so session must have a cache
|
||||||
|
if !self.session.cache().unwrap().remove_file(file_id) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just try it again
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Unable to read audio file: {}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if position_ms != 0 {
|
||||||
|
if let Err(err) = decoder.seek(position_ms as i64) {
|
||||||
|
error!("Vorbis error: {}", err);
|
||||||
|
}
|
||||||
|
stream_loader_controller.set_stream_mode();
|
||||||
}
|
}
|
||||||
};
|
let stream_position_pcm = PlayerInternal::position_ms_to_pcm(position_ms);
|
||||||
|
info!("<{}> ({} ms) loaded", audio.name, audio.duration);
|
||||||
|
|
||||||
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
return Some(PlayerLoadedTrackData {
|
||||||
|
decoder,
|
||||||
let mut decoder = VorbisDecoder::new(audio_file).unwrap();
|
normalisation_factor,
|
||||||
|
stream_loader_controller,
|
||||||
if position_ms != 0 {
|
bytes_per_second,
|
||||||
match decoder.seek(position_ms as i64) {
|
duration_ms,
|
||||||
Ok(_) => (),
|
stream_position_pcm,
|
||||||
Err(err) => error!("Vorbis error: {:?}", err),
|
});
|
||||||
}
|
|
||||||
stream_loader_controller.set_stream_mode();
|
|
||||||
}
|
}
|
||||||
let stream_position_pcm = PlayerInternal::position_ms_to_pcm(position_ms);
|
|
||||||
info!("<{}> ({} ms) loaded", audio.name, audio.duration);
|
|
||||||
Some(PlayerLoadedTrackData {
|
|
||||||
decoder,
|
|
||||||
normalisation_factor,
|
|
||||||
stream_loader_controller,
|
|
||||||
bytes_per_second,
|
|
||||||
duration_ms,
|
|
||||||
stream_position_pcm,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,16 @@ pub fn run_program_on_events(event: PlayerEvent, onevent: &str) -> Option<io::Re
|
||||||
old_track_id,
|
old_track_id,
|
||||||
new_track_id,
|
new_track_id,
|
||||||
} => {
|
} => {
|
||||||
env_vars.insert("PLAYER_EVENT", "change".to_string());
|
env_vars.insert("PLAYER_EVENT", "changed".to_string());
|
||||||
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base62());
|
env_vars.insert("OLD_TRACK_ID", old_track_id.to_base62());
|
||||||
env_vars.insert("TRACK_ID", new_track_id.to_base62());
|
env_vars.insert("TRACK_ID", new_track_id.to_base62());
|
||||||
}
|
}
|
||||||
PlayerEvent::Started { track_id, .. } => {
|
PlayerEvent::Started { track_id, .. } => {
|
||||||
env_vars.insert("PLAYER_EVENT", "start".to_string());
|
env_vars.insert("PLAYER_EVENT", "started".to_string());
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base62());
|
env_vars.insert("TRACK_ID", track_id.to_base62());
|
||||||
}
|
}
|
||||||
PlayerEvent::Stopped { track_id, .. } => {
|
PlayerEvent::Stopped { track_id, .. } => {
|
||||||
env_vars.insert("PLAYER_EVENT", "stop".to_string());
|
env_vars.insert("PLAYER_EVENT", "stopped".to_string());
|
||||||
env_vars.insert("TRACK_ID", track_id.to_base62());
|
env_vars.insert("TRACK_ID", track_id.to_base62());
|
||||||
}
|
}
|
||||||
PlayerEvent::Playing {
|
PlayerEvent::Playing {
|
||||||
|
|
Loading…
Reference in a new issue