Merge branch 'dev' into tokio_migration

This commit is contained in:
johannesd3 2021-02-10 21:51:33 +01:00
commit 872fab62d8
23 changed files with 884 additions and 562 deletions

View file

@ -41,7 +41,7 @@ jobs:
matrix:
os: [ubuntu-latest]
toolchain:
- 1.40.0 # MSRV (Minimum supported rust version)
- 1.42.0 # MSRV (Minimum supported rust version)
- stable
- beta
experimental: [false]
@ -59,6 +59,15 @@ jobs:
profile: minimal
toolchain: ${{ matrix.toolchain }}
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
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
@ -94,6 +103,15 @@ jobs:
target: ${{ matrix.target }}
toolchain: ${{ matrix.toolchain }}
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
run: cargo install cross || true
- name: Build

View file

@ -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
View file

@ -73,38 +73,16 @@ dependencies = [
"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]]
name = "alsa"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb213f6b3e4b1480a60931ca2035794aa67b73103d254715b1db7b70dcb3c934"
dependencies = [
"alsa-sys 0.3.1",
"bitflags 1.2.1",
"alsa-sys",
"bitflags",
"libc",
"nix 0.15.0",
]
[[package]]
name = "alsa-sys"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0edcbbf9ef68f15ae1b620f722180b82a98b6f0628d30baa6b8d2a5abc87d58"
dependencies = [
"libc",
"pkg-config",
"nix",
]
[[package]]
@ -129,27 +107,6 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "async-trait"
version = "0.1.42"
@ -220,7 +177,7 @@ version = "0.56.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2da379dbebc0b76ef63ca68d8fc6e71c0f13e59432e0987e508c1820e6ab5239"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"cexpr",
"clang-sys",
"lazy_static",
@ -248,12 +205,6 @@ version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4efd02e230a02e18f92fc2735f44597385ed02ad8f831e7c1c1156ee5e1ab3a5"
[[package]]
name = "bitflags"
version = "1.2.1"
@ -283,9 +234,9 @@ dependencies = [
[[package]]
name = "bumpalo"
version = "3.4.0"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]]
name = "byte-tools"
@ -353,9 +304,9 @@ dependencies = [
[[package]]
name = "chunked_transfer"
version = "1.3.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca"
checksum = "fff857943da45f546682664a79488be82e69e43c1a7a2307679ab9afb3a66d2e"
[[package]]
name = "cipher"
@ -368,9 +319,9 @@ dependencies = [
[[package]]
name = "clang-sys"
version = "1.0.3"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0659001ab56b791be01d4b729c44376edc6718cf389a502e579b77b758f3296c"
checksum = "5cb92721cb37482245ed88428f72253ce422b3b4ee169c70a0642521bb5db4cc"
dependencies = [
"glob",
"libc",
@ -383,7 +334,7 @@ version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags 1.2.1",
"bitflags",
]
[[package]]
@ -422,7 +373,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f"
dependencies = [
"percent-encoding 2.1.0",
"time 0.2.24",
"time 0.2.25",
"version_check",
]
@ -433,12 +384,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3818dfca4b0cb5211a659bbcbb94225b7127407b2b135e650d717bfb78ab10d3"
dependencies = [
"cookie",
"idna 0.2.0",
"idna 0.2.1",
"log",
"publicsuffix",
"serde",
"serde_json",
"time 0.2.24",
"time 0.2.25",
"url 2.2.0",
]
@ -454,7 +405,7 @@ version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f229761965dad3e9b11081668a6ea00f1def7aa46062321b5ec245b834f6e491"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"coreaudio-sys",
]
@ -473,7 +424,7 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05631e2089dfa5d3b6ea1cfbbfd092e2ee5deeb69698911bc976b28b746d3657"
dependencies = [
"alsa 0.4.3",
"alsa",
"core-foundation-sys",
"coreaudio-rs",
"jni 0.17.0",
@ -483,7 +434,7 @@ dependencies = [
"mach",
"ndk",
"ndk-glue",
"nix 0.15.0",
"nix",
"oboe",
"parking_lot",
"stdweb 0.1.3",
@ -557,9 +508,9 @@ dependencies = [
[[package]]
name = "derivative"
version = "2.1.3"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
@ -800,7 +751,7 @@ checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi 0.10.1+wasi-snapshot-preview1",
"wasi 0.10.2+wasi-snapshot-preview1",
]
[[package]]
@ -815,7 +766,7 @@ version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c685013b7515e668f1b57a165b009d4d28cb139a8a989bbd699c10dad29d0c5"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"futures-channel",
"futures-core",
"futures-executor",
@ -877,7 +828,7 @@ version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d50f822055923f1cbede233aa5dfd4ee957cf328fb3076e330886094e11d6cf"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"cfg-if 1.0.0",
"futures-channel",
"futures-core",
@ -901,7 +852,7 @@ version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc80888271338c3ede875d8cafc452eb207476ff5539dcbe0018a8f5b827af0e"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"futures-core",
"futures-sink",
"glib",
@ -934,7 +885,7 @@ version = "0.16.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bafd01c56f59cb10f4b5a10f97bb4bdf8c2b2784ae5b04da7e2d400cf6e6afcf"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"glib",
"glib-sys",
"gobject-sys",
@ -1046,9 +997,9 @@ dependencies = [
[[package]]
name = "httparse"
version = "1.3.4"
version = "1.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
[[package]]
name = "httpdate"
@ -1064,9 +1015,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.2"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe"
checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7"
dependencies = [
"bytes",
"futures-channel",
@ -1078,7 +1029,7 @@ dependencies = [
"httparse",
"httpdate",
"itoa",
"pin-project 1.0.4",
"pin-project 1.0.5",
"socket2",
"tokio",
"tower-service",
@ -1105,9 +1056,9 @@ dependencies = [
[[package]]
name = "idna"
version = "0.2.0"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
checksum = "de910d521f7cc3135c4de8db1cb910e0b5ed1dc6f57c381cd07e8e661ce10094"
dependencies = [
"matches",
"unicode-bidi",
@ -1154,7 +1105,7 @@ version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c1871c91fa65aa328f3bedbaa54a6e5d1de009264684c153eb708ba933aa6f5"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"jack-sys",
"lazy_static",
"libc",
@ -1207,9 +1158,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "js-sys"
version = "0.3.46"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
dependencies = [
"wasm-bindgen",
]
@ -1239,9 +1190,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.82"
version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
[[package]]
name = "libflate"
@ -1273,11 +1224,11 @@ dependencies = [
[[package]]
name = "libpulse-binding"
version = "2.22.0"
version = "2.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce89ab17bd343b08bd4321c674ef1477d34f83be18b1ab2ee47a5e5fbee64a91"
checksum = "b2405f806801527dfb3d2b6d48a282cdebe9a1b41b0652e0d7b5bad81dbc700e"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"libc",
"libpulse-sys",
"num-derive",
@ -1287,9 +1238,9 @@ dependencies = [
[[package]]
name = "libpulse-simple-binding"
version = "2.20.1"
version = "2.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e47f6cda2748fb86f15e5e65cc33be306577140f4b500622b5d98df2ca17240"
checksum = "a574975292db859087c3957b9182f7d53278553f06bddaa2099c90e4ac3a0ee0"
dependencies = [
"libpulse-binding",
"libpulse-simple-sys",
@ -1308,9 +1259,9 @@ dependencies = [
[[package]]
name = "libpulse-sys"
version = "1.17.0"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcfb56118765adba111da47e36278b77d00aebf822e4f014a832fbfa183a13b"
checksum = "cf17e9832643c4f320c42b7d78b2c0510f45aa5e823af094413b94e45076ba82"
dependencies = [
"libc",
"num-derive",
@ -1359,6 +1310,7 @@ dependencies = [
"byteorder",
"bytes",
"env_logger",
"error-chain",
"futures",
"hmac",
"httparse",
@ -1404,7 +1356,7 @@ dependencies = [
name = "librespot-playback"
version = "0.1.3"
dependencies = [
"alsa 0.2.2",
"alsa",
"byteorder",
"cpal",
"futures",
@ -1438,9 +1390,9 @@ dependencies = [
[[package]]
name = "librespot-tremor"
version = "0.1.0"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b155a7dc4e4d272e01c37a1b85c1ee1bee7f04980ad4a7784c1a6e0f2de5929b"
checksum = "97f525bff915d478a76940a7b988e5ea34911ba7280c97bd3a7673f54d68b4fe"
dependencies = [
"cc",
"libc",
@ -1465,11 +1417,11 @@ dependencies = [
[[package]]
name = "log"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 0.1.10",
"cfg-if 1.0.0",
]
[[package]]
@ -1577,25 +1529,13 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "nix"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"cc",
"cfg-if 0.1.10",
"libc",
@ -1836,6 +1776,15 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "pin-project"
version = "0.4.27"
@ -1847,11 +1796,11 @@ dependencies = [
[[package]]
name = "pin-project"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b70b68509f17aa2857863b6fa00bf21fc93674c7a8893de2f469f6aa7ca2f2"
checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63"
dependencies = [
"pin-project-internal 1.0.4",
"pin-project-internal 1.0.5",
]
[[package]]
@ -1867,9 +1816,9 @@ dependencies = [
[[package]]
name = "pin-project-internal"
version = "1.0.4"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa25a6393f22ce819b0f50e0be89287292fda8d425be38ee0ca14c4931d9e71"
checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b"
dependencies = [
"proc-macro2",
"quote",
@ -1900,7 +1849,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb6b5eff96ccc9bf44d34c379ab03ae944426d83d1694345bdf8159d561d562"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"libc",
"portaudio-sys",
]
@ -2013,7 +1962,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3bbaa49075179162b49acac1c6aa45fb4dafb5f13cf6794276d77bc7fd95757b"
dependencies = [
"error-chain",
"idna 0.2.0",
"idna 0.2.1",
"lazy_static",
"regex",
"url 2.2.0",
@ -2065,9 +2014,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18519b42a40024d661e1714153e9ad0c3de27cd495760ceb09710920f1098b1e"
checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
dependencies = [
"libc",
"rand_chacha 0.3.0",
@ -2158,7 +2107,7 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
dependencies = [
"bitflags 1.2.1",
"bitflags",
]
[[package]]
@ -2190,9 +2139,9 @@ dependencies = [
[[package]]
name = "ring"
version = "0.16.19"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "024a1e66fea74c66c66624ee5622a7ff0e4b73a13b4f5c326ddb50c708944226"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
@ -2236,7 +2185,16 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
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]]
@ -2295,7 +2253,7 @@ version = "0.34.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcbb85f4211627a7291c83434d6bbfa723e28dcaa53c7606087e3c61929e4b9c"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"lazy_static",
"libc",
"sdl2-sys",
@ -2318,7 +2276,16 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
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]]
@ -2328,19 +2295,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "1.0.120"
name = "semver-parser"
version = "0.10.2"
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 = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.120"
version = "1.0.123"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775"
checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
dependencies = [
"proc-macro2",
"quote",
@ -2349,9 +2325,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.61"
version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486"
dependencies = [
"itoa",
"ryu",
@ -2440,9 +2416,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "standback"
version = "0.2.14"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66a8cff4fa24853fdf6b51f75c6d7f8206d7c75cab4e467bcd7f25c2b1febe0"
checksum = "a2beb4d1860a61f571530b3f855a1b538d0200f7871c63331ecd6f17b1f014f8"
dependencies = [
"version_check",
]
@ -2460,7 +2436,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5"
dependencies = [
"discard",
"rustc_version",
"rustc_version 0.2.3",
"stdweb-derive",
"stdweb-internal-macros",
"stdweb-internal-runtime",
@ -2534,9 +2510,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
[[package]]
name = "syn"
version = "1.0.58"
version = "1.0.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
dependencies = [
"proc-macro2",
"quote",
@ -2572,13 +2548,12 @@ dependencies = [
[[package]]
name = "tar"
version = "0.4.30"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290"
checksum = "0313546c01d59e29be4f09687bcb4fb6690cec931cc3607b6aec7a0e417f4cc6"
dependencies = [
"filetime",
"libc",
"redox_syscall 0.1.57",
"xattr",
]
@ -2590,7 +2565,7 @@ checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
"libc",
"rand 0.8.2",
"rand 0.8.3",
"redox_syscall 0.2.4",
"remove_dir_all",
"winapi",
@ -2646,9 +2621,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.2.24"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "273d3ed44dca264b0d6b3665e8d48fb515042d42466fad93d2a45b90ec4058f7"
checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7"
dependencies = [
"const_fn",
"libc",
@ -2684,9 +2659,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f"
checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
dependencies = [
"tinyvec_macros",
]
@ -2699,9 +2674,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8efab2086f17abcddb8f756117665c958feee6b2e39974c2f1600592ab3a4195"
checksum = "e8190d04c665ea9e6b6a0dc45523ade572c088d2e6566244c1122671dbf4ae3a"
dependencies = [
"autocfg",
"bytes",
@ -2715,40 +2690,27 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "1.0.0"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494"
checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57"
dependencies = [
"proc-macro2",
"quote",
"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]]
name = "tokio-util"
version = "0.6.2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "feb971a26599ffd28066d387f109746df178eff14d5ea1e235015c5601967a4b"
checksum = "ebb7cb2f00c5ae8df755b252306272cd1790d39728363936e01827e11f0b017b"
dependencies = [
"async-stream",
"bytes",
"futures-core",
"futures-sink",
"log",
"pin-project-lite",
"tokio",
"tokio-stream",
]
[[package]]
@ -2762,15 +2724,15 @@ dependencies = [
[[package]]
name = "tower-service"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
[[package]]
name = "tracing"
version = "0.1.22"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3"
checksum = "f7d40a22fd029e33300d8d89a5cc8ffce18bb7c587662f54629e94c9de5487f3"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@ -2808,6 +2770,12 @@ version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-bidi"
version = "0.3.4"
@ -2819,9 +2787,9 @@ dependencies = [
[[package]]
name = "unicode-normalization"
version = "0.1.16"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606"
checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef"
dependencies = [
"tinyvec",
]
@ -2890,7 +2858,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e"
dependencies = [
"form_urlencoded",
"idna 0.2.0",
"idna 0.2.1",
"matches",
"percent-encoding 2.1.0",
]
@ -2906,12 +2874,13 @@ dependencies = [
[[package]]
name = "vergen"
version = "3.1.0"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ce50d8996df1f85af15f2cd8d33daae6e479575123ef4314a51a70a230739cb"
checksum = "e7141e445af09c8919f1d5f8a20dae0b20c3b57a45dee0d5823c6ed5d237f15a"
dependencies = [
"bitflags 1.2.1",
"bitflags",
"chrono",
"rustc_version 0.3.3",
]
[[package]]
@ -2998,15 +2967,15 @@ checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
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"
checksum = "93c6c3420963c5c64bca373b25e77acb562081b9bb4dd5bb864187742186cea9"
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@ -3014,9 +2983,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
dependencies = [
"bumpalo",
"lazy_static",
@ -3029,9 +2998,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -3039,9 +3008,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
dependencies = [
"proc-macro2",
"quote",
@ -3052,15 +3021,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.69"
version = "0.2.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
[[package]]
name = "web-sys"
version = "0.3.46"
version = "0.3.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
dependencies = [
"js-sys",
"wasm-bindgen",

View file

@ -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://travis-ci.org/librespot-org/librespot.svg?branch=dev)](https://travis-ci.org/librespot-org/librespot)
[![Build Status](https://github.com/librespot-org/librespot/workflows/test/badge.svg)](https://github.com/librespot-org/librespot/actions)
[![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)
@ -21,7 +20,7 @@ As the origin by [plietar](https://github.com/plietar/) is no longer actively ma
# Documentation
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).
[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.
# 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
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)
_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
Come and hang out on gitter if you need help or want to offer some.
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.
- [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.

View file

@ -23,7 +23,7 @@ num-traits = "0.2"
pin-project-lite = "0.2.4"
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 }
[features]

View file

@ -312,8 +312,8 @@ impl AudioFile {
let session_ = session.clone();
session.spawn(complete_rx.map_ok(move |mut file| {
if let Some(cache) = session_.cache() {
cache.save_file(file_id, &mut file);
debug!("File {} complete, saving to cache", file_id);
cache.save_file(file_id, &mut file);
} else {
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 {

View file

@ -73,10 +73,6 @@ impl fmt::Display for VorbisError {
}
impl error::Error for VorbisError {
fn description(&self) -> &str {
error::Error::description(&self.0)
}
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
error::Error::source(&self.0)
}

View file

@ -77,7 +77,7 @@ impl Discovery {
"status": 101,
"statusString": "ERROR-OK",
"spotifyError": 0,
"version": "2.1.0",
"version": "2.7.1",
"deviceID": (self.0.device_id),
"remoteName": (self.0.config.name),
"activeUser": "",
@ -87,6 +87,9 @@ impl Discovery {
"accountReq": "PREMIUM",
"brandDisplayName": "librespot",
"modelDisplayName": "librespot",
"resolverVersion": "0",
"groupStatus": "NONE",
"voiceSupport": "NO",
});
let body = result.to_string();

View file

@ -21,7 +21,6 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError};
use librespot_core::util::url_encode;
use librespot_core::util::SeqGenerator;
use librespot_core::version;
use librespot_core::volume::Volume;
enum SpircPlayStatus {
Stopped,
@ -1297,7 +1296,7 @@ impl SpircTask {
self.mixer
.set_volume(volume_to_mixer(volume, &self.config.volume_ctrl));
if let Some(cache) = self.session.cache() {
cache.save_volume(Volume { volume })
cache.save_volume(volume)
}
self.player.emit_volume_set_event(volume);
}

View file

@ -17,6 +17,7 @@ aes = "0.6"
base64 = "0.13"
byteorder = "1.4"
bytes = "1.0"
error-chain = { version = "0.12", default-features = false }
futures = { version = "0.3", features = ["bilock", "unstable"] }
hmac = "0.7"
httparse = "1.3"

View file

@ -5,12 +5,10 @@ use hmac::Hmac;
use pbkdf2::pbkdf2;
use protobuf::ProtobufEnum;
use sha1::{Digest, Sha1};
use std::fs::File;
use std::io::{self, Read, Write};
use std::ops::FnOnce;
use std::path::Path;
use std::io::{self, Read};
use crate::protocol::authentication::AuthenticationType;
use crate::protocol::keyexchange::{APLoginFailed, ErrorCode};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Credentials {
@ -110,27 +108,6 @@ impl Credentials {
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>
@ -189,3 +166,37 @@ pub fn get_credentials<F: FnOnce(&String) -> String>(
(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(),
}
}
}

View file

@ -1,115 +1,178 @@
use std::fs;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::io::{self, Error, ErrorKind, Read, Write};
use std::path::{Path, PathBuf};
use crate::authentication::Credentials;
use crate::spotify_id::FileId;
use crate::volume::Volume;
/// A cache for volume, credentials and audio files.
#[derive(Clone)]
pub struct Cache {
audio_root: PathBuf,
system_root: PathBuf,
use_audio_cache: bool,
}
fn mkdir_existing(path: &Path) -> io::Result<()> {
fs::create_dir(path).or_else(|err| {
if err.kind() == io::ErrorKind::AlreadyExists {
Ok(())
} else {
Err(err)
}
})
credentials_location: Option<PathBuf>,
volume_location: Option<PathBuf>,
audio_location: Option<PathBuf>,
}
impl Cache {
pub fn new(audio_location: PathBuf, system_location: PathBuf, use_audio_cache: bool) -> Cache {
if use_audio_cache == true {
mkdir_existing(&audio_location).unwrap();
mkdir_existing(&audio_location.join("files")).unwrap();
pub fn new<P: AsRef<Path>>(
system_location: Option<P>,
audio_location: Option<P>,
) -> io::Result<Self> {
if let Some(location) = &system_location {
fs::create_dir_all(location)?;
}
mkdir_existing(&system_location).unwrap();
Cache {
audio_root: audio_location,
system_root: system_location,
use_audio_cache: use_audio_cache,
if let Some(location) = &audio_location {
fs::create_dir_all(location)?;
}
}
}
impl Cache {
fn credentials_path(&self) -> PathBuf {
self.system_root.join("credentials.json")
let audio_location = audio_location.map(|p| p.as_ref().to_owned());
let volume_location = system_location.as_ref().map(|p| p.as_ref().join("volume"));
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> {
let path = self.credentials_path();
Credentials::from_file(path)
let location = self.credentials_location.as_ref()?;
// 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) {
let path = self.credentials_path();
cred.save_to_file(&path);
}
}
if let Some(location) = &self.credentials_location {
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
impl Cache {
fn volume_path(&self) -> PathBuf {
self.system_root.join("volume")
if let Err(e) = result {
warn!("Cannot save credentials to cache: {}", e)
}
}
}
pub fn volume(&self) -> Option<u16> {
let path = self.volume_path();
Volume::from_file(path)
let location = self.volume_location.as_ref()?;
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) {
let path = self.volume_path();
volume.save_to_file(&path);
pub fn save_volume(&self, volume: u16) {
if let Some(ref location) = self.volume_location {
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) -> PathBuf {
let name = file.to_base16();
self.audio_root
.join("files")
.join(&name[0..2])
.join(&name[2..])
fn file_path(&self, file: FileId) -> Option<PathBuf> {
self.audio_location.as_ref().map(|location| {
let name = file.to_base16();
let mut path = location.join(&name[0..2]);
path.push(&name[2..]);
path
})
}
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) {
if self.use_audio_cache {
let path = self.file_path(file);
mkdir_existing(path.parent().unwrap()).unwrap();
pub fn save_file<F: Read>(&self, file: FileId, contents: &mut F) {
let path = if let Some(path) = self.file_path(file) {
path
} else {
return;
};
let parent = path.parent().unwrap();
let mut cache_file = File::create(path).unwrap_or_else(|_e| {
::std::fs::remove_dir_all(&self.audio_root.join("files")).unwrap();
mkdir_existing(&self.audio_root.join("files")).unwrap();
let result = fs::create_dir_all(parent)
.and_then(|_| File::create(&path))
.and_then(|mut file| io::copy(contents, &mut file));
let path = self.file_path(file);
mkdir_existing(path.parent().unwrap()).unwrap();
File::create(path).unwrap()
});
::std::io::copy(contents, &mut cache_file).unwrap_or_else(|_e| {
::std::fs::remove_dir_all(&self.audio_root.join("files")).unwrap();
mkdir_existing(&self.audio_root.join("files")).unwrap();
if let Err(e) = result {
if e.kind() == ErrorKind::Other {
// Perhaps there's no space left in the cache
// TODO: try to narrow down the error (platform-dependently)
info!("An error occured while writing to cache, trying to flush the cache");
let path = self.file_path(file);
mkdir_existing(path.parent().unwrap()).unwrap();
let mut file = File::create(path).unwrap();
::std::io::copy(contents, &mut file).unwrap()
});
if fs::remove_dir_all(self.audio_location.as_ref().unwrap())
.and_then(|_| fs::create_dir_all(parent))
.and_then(|_| File::create(&path))
.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
}
}
}

View file

@ -36,6 +36,16 @@ pub enum DeviceType {
AVR = 6,
STB = 7,
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 {
@ -51,6 +61,14 @@ impl FromStr for DeviceType {
"avr" => Ok(AVR),
"stb" => Ok(STB),
"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(()),
}
}
@ -69,6 +87,16 @@ impl fmt::Display for DeviceType {
AVR => f.write_str("AVR"),
STB => f.write_str("STB"),
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"),
}
}
}

View file

@ -12,7 +12,7 @@ use tokio::net::TcpStream;
use tokio_util::codec::Framed;
use url::Url;
use crate::authentication::Credentials;
use crate::authentication::{AuthenticationError, Credentials};
use crate::version;
use crate::proxytunnel;
@ -64,7 +64,7 @@ pub async fn authenticate(
transport: &mut Transport,
credentials: Credentials,
device_id: &str,
) -> io::Result<Credentials> {
) -> Result<Credentials, AuthenticationError> {
use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os};
use crate::protocol::keyexchange::APLoginFailed;
@ -114,10 +114,7 @@ pub async fn authenticate(
0xad => {
let error_data: APLoginFailed = protobuf::parse_from_bytes(data.as_ref()).unwrap();
panic!(
"Authentication failed with reason: {:?}",
error_data.get_error_code()
)
Err(error_data.into())
}
_ => panic!("Unexpected packet {:?}", cmd),

View file

@ -6,6 +6,8 @@ extern crate log;
extern crate serde_derive;
#[macro_use]
extern crate pin_project_lite;
#[macro_use]
extern crate error_chain;
extern crate aes;
extern crate base64;
extern crate byteorder;
@ -51,4 +53,3 @@ pub mod session;
pub mod spotify_id;
pub mod util;
pub mod version;
pub mod volume;

View file

@ -19,6 +19,8 @@ use crate::config::SessionConfig;
use crate::connection;
use crate::mercury::MercuryManager;
pub use crate::authentication::{AuthenticationError, AuthenticationErrorKind};
struct SessionData {
country: String,
time_delta: i64,
@ -50,7 +52,7 @@ impl Session {
config: SessionConfig,
credentials: Credentials,
cache: Option<Cache>,
) -> io::Result<Session> {
) -> Result<Session, AuthenticationError> {
let ap = apresolve_or_fallback(&config.proxy, &config.ap_port).await;
info!("Connecting to AP \"{}\"", ap);

View file

@ -1,4 +1,4 @@
use std;
use std::convert::TryInto;
use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -8,6 +8,26 @@ pub enum SpotifyAudioType {
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)]
pub struct SpotifyId {
pub id: u128,
@ -17,104 +37,178 @@ pub struct SpotifyId {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct SpotifyIdError;
const BASE62_DIGITS: &'static [u8] =
b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef";
const BASE62_DIGITS: &[u8; 62] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const BASE16_DIGITS: &[u8; 16] = b"0123456789abcdef";
impl SpotifyId {
const SIZE: usize = 16;
const SIZE_BASE16: usize = 32;
const SIZE_BASE62: usize = 22;
fn as_track(n: u128) -> SpotifyId {
SpotifyId {
id: n.to_owned(),
id: n,
audio_type: SpotifyAudioType::Track,
}
}
pub fn from_base16(id: &str) -> Result<SpotifyId, SpotifyIdError> {
let data = id.as_bytes();
/// Parses a base16 (hex) encoded [Spotify ID] into a `SpotifyId`.
///
/// `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 data {
let d = match BASE16_DIGITS.iter().position(|e| e == c) {
None => return Err(SpotifyIdError),
Some(x) => x as u128,
};
n = n * 16;
n = n + d;
for c in src.as_bytes() {
let p = match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'f' => c - b'a' + 10,
_ => return Err(SpotifyIdError),
} as u128;
dst <<= 4;
dst += p;
}
Ok(SpotifyId::as_track(n))
Ok(SpotifyId::as_track(dst))
}
pub fn from_base62(id: &str) -> Result<SpotifyId, SpotifyIdError> {
let data = id.as_bytes();
/// Parses a base62 encoded [Spotify ID] into a `SpotifyId`.
///
/// `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 data {
let d = match BASE62_DIGITS.iter().position(|e| e == c) {
None => return Err(SpotifyIdError),
Some(x) => x as u128,
};
n = n * 62;
n = n + d;
for c in src.as_bytes() {
let p = match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'z' => c - b'a' + 10,
b'A'..=b'Z' => c - b'A' + 36,
_ => return Err(SpotifyIdError),
} as u128;
dst *= 62;
dst += p;
}
Ok(SpotifyId::as_track(n))
Ok(SpotifyId::as_track(dst))
}
pub fn from_raw(data: &[u8]) -> Result<SpotifyId, SpotifyIdError> {
if data.len() != 16 {
/// Creates a `SpotifyId` from a copy of `SpotifyId::SIZE` (16) bytes in big-endian order.
///
/// 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);
};
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 {
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 {
let &SpotifyId { id: mut n, .. } = self;
let mut dst = [0u8; 22];
let mut i = 0;
let n = self.id;
let mut data = [0u8; 22];
for i in 0..22 {
data[21 - i] = BASE62_DIGITS[(n % 62) as usize];
n /= 62;
// The algorithm is based on:
// https://github.com/trezor/trezor-crypto/blob/c316e775a2152db255ace96b6b65ac0f20525ec0/base58.c
//
// 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 {
match self.audio_type {
SpotifyAudioType::Track => format!("spotify:track:{}", self.to_base62()),
SpotifyAudioType::Podcast => format!("spotify:episode:{}", self.to_base62()),
SpotifyAudioType::NonPlayable => format!("spotify:unknown:{}", self.to_base62()),
dst.reverse();
unsafe {
// Safety: We are only dealing with ASCII characters.
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()
}
/// 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)]
@ -122,11 +216,7 @@ pub struct FileId(pub [u8; 20]);
impl FileId {
pub fn to_base16(&self) -> String {
self.0
.iter()
.map(|b| format!("{:02x}", b))
.collect::<Vec<String>>()
.concat()
to_base16(&self.0, &mut [0u8; 40])
}
}
@ -141,3 +231,185 @@ impl fmt::Display for FileId {
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));
}
}
}

View file

@ -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)
}
}

View file

@ -23,7 +23,7 @@ log = "0.4"
byteorder = "1.4"
shell-words = "1.0.0"
alsa = { version = "0.2", optional = true }
alsa = { version = "0.4", optional = true }
portaudio-rs = { version = "0.3", optional = true }
libpulse-binding = { version = "2.13", optional = true, default-features = false }
libpulse-simple-binding = { version = "2.13", optional = true, default-features = false }

View file

@ -67,7 +67,11 @@ impl Sink for PulseAudioSink {
fn write(&mut self, data: &[i16]) -> io::Result<()> {
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) {
Ok(_) => Ok(()),

View file

@ -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)]
pub struct PlayerConfig {
pub bitrate: Bitrate,
pub normalisation: bool,
pub normalisation_type: NormalisationType,
pub normalisation_pregain: f32,
pub gapless: bool,
}
@ -38,6 +62,7 @@ impl Default for PlayerConfig {
PlayerConfig {
bitrate: Bitrate::default(),
normalisation: false,
normalisation_type: NormalisationType::default(),
normalisation_pregain: 0.0,
gapless: true,
}

View file

@ -5,6 +5,7 @@ use crate::audio::{
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
};
use crate::audio_backend::Sink;
use crate::config::NormalisationType;
use crate::config::{Bitrate, PlayerConfig};
use crate::librespot_core::tokio;
use crate::metadata::{AudioItem, FileFormat};
@ -217,17 +218,20 @@ impl NormalisationData {
}
fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 {
let mut normalisation_factor = f32::powf(
10.0,
(data.track_gain_db + config.normalisation_pregain) / 20.0,
);
let [gain_db, gain_peak] = match config.normalisation_type {
NormalisationType::Album => [data.album_gain_db, data.album_peak],
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.");
normalisation_factor = 1.0 / data.track_peak;
normalisation_factor = 1.0 / gain_peak;
}
debug!("Normalisation Data: {:?}", data);
debug!("Normalisation Type: {:?}", config.normalisation_type);
debug!("Applied normalisation factor: {}", normalisation_factor);
normalisation_factor
@ -652,49 +656,27 @@ impl PlayerTrackLoader {
FileFormat::OGG_VORBIS_96,
],
};
let format = formats
.iter()
.find(|format| audio.files.contains_key(format))
.unwrap();
let file_id = match audio.files.get(&format) {
Some(&file_id) => file_id,
let entry = formats.iter().find_map(|format| {
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 => {
warn!("<{}> in not available in format {:?}", audio.name, format);
warn!("<{}> is not available in any supported format", audio.name);
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 key = self.session.audio_key().request(spotify_id, file_id);
let encrypted_file = AudioFile::open(
&self.session,
file_id,
bytes_per_second,
play_from_beginning,
);
let encrypted_file = 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 {
let key = match self.session.audio_key().request(spotify_id, file_id).await {
Ok(key) => key,
Err(_) => {
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) {
Ok(normalisation_data) => {
NormalisationData::get_factor(&self.config, normalisation_data)
let encrypted_file = match encrypted_file.await {
Ok(encrypted_file) => encrypted_file,
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.");
1.0_f32
let mut decrypted_file = AudioDecrypt::new(key, encrypted_file);
let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file)
{
Ok(normalisation_data) => {
NormalisationData::get_factor(&self.config, normalisation_data)
}
Err(_) => {
warn!("Unable to extract normalisation data, using default value.");
1.0_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);
let mut decoder = VorbisDecoder::new(audio_file).unwrap();
if position_ms != 0 {
match decoder.seek(position_ms as i64) {
Ok(_) => (),
Err(err) => error!("Vorbis error: {:?}", err),
}
stream_loader_controller.set_stream_mode();
return Some(PlayerLoadedTrackData {
decoder,
normalisation_factor,
stream_loader_controller,
bytes_per_second,
duration_ms,
stream_position_pcm,
});
}
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,
})
}
}

View file

@ -24,16 +24,16 @@ pub fn run_program_on_events(event: PlayerEvent, onevent: &str) -> Option<io::Re
old_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("TRACK_ID", new_track_id.to_base62());
}
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());
}
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());
}
PlayerEvent::Playing {