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
678d1777fd
32 changed files with 674 additions and 360 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -3,4 +3,7 @@ target
|
||||||
spotify_appkey.key
|
spotify_appkey.key
|
||||||
.vagrant/
|
.vagrant/
|
||||||
.project
|
.project
|
||||||
.history
|
.history
|
||||||
|
*.save
|
||||||
|
|
||||||
|
|
||||||
|
|
315
Cargo.lock
generated
315
Cargo.lock
generated
|
@ -27,44 +27,21 @@ version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
|
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-soft 0.6.4",
|
"aes-soft",
|
||||||
"aesni 0.10.0",
|
"aesni",
|
||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aes-ctr"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee"
|
|
||||||
dependencies = [
|
|
||||||
"aes-soft 0.3.3",
|
|
||||||
"aesni 0.6.0",
|
|
||||||
"ctr 0.3.2",
|
|
||||||
"stream-cipher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aes-ctr"
|
name = "aes-ctr"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763"
|
checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-soft 0.6.4",
|
"aes-soft",
|
||||||
"aesni 0.10.0",
|
"aesni",
|
||||||
"cipher",
|
"cipher",
|
||||||
"ctr 0.6.0",
|
"ctr",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aes-soft"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d"
|
|
||||||
dependencies = [
|
|
||||||
"block-cipher-trait",
|
|
||||||
"byteorder",
|
|
||||||
"opaque-debug 0.2.3",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -77,17 +54,6 @@ dependencies = [
|
||||||
"opaque-debug 0.3.0",
|
"opaque-debug 0.3.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aesni"
|
|
||||||
version = "0.6.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100"
|
|
||||||
dependencies = [
|
|
||||||
"block-cipher-trait",
|
|
||||||
"opaque-debug 0.2.3",
|
|
||||||
"stream-cipher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aesni"
|
name = "aesni"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -189,16 +155,6 @@ version = "0.2.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "base64"
|
|
||||||
version = "0.9.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
|
|
||||||
dependencies = [
|
|
||||||
"byteorder",
|
|
||||||
"safemem",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.13.0"
|
version = "0.13.0"
|
||||||
|
@ -251,29 +207,29 @@ version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-padding",
|
"block-padding 0.1.5",
|
||||||
"byte-tools",
|
"byte-tools",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"generic-array 0.12.3",
|
"generic-array 0.12.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-cipher-trait"
|
name = "block-buffer"
|
||||||
version = "0.6.2"
|
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 = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
|
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.12.3",
|
"generic-array 0.14.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-modes"
|
name = "block-modes"
|
||||||
version = "0.3.3"
|
version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529"
|
checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-cipher-trait",
|
"block-padding 0.2.1",
|
||||||
"block-padding",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -285,6 +241,12 @@ dependencies = [
|
||||||
"byte-tools",
|
"byte-tools",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "block-padding"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.6.1"
|
version = "3.6.1"
|
||||||
|
@ -372,22 +334,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clang-sys"
|
name = "clang-sys"
|
||||||
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 = "5cb92721cb37482245ed88428f72253ce422b3b4ee169c70a0642521bb5db4cc"
|
checksum = "f54d78e30b388d4815220c8dd03fea5656b6c6d32adb59e89061552a102f8da1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading",
|
"libloading 0.7.0",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cloudabi"
|
|
||||||
version = "0.0.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
|
||||||
dependencies = [
|
|
||||||
"bitflags",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -496,6 +449,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpuid-bool"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -507,24 +466,14 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crypto-mac"
|
name = "crypto-mac"
|
||||||
version = "0.7.0"
|
version = "0.10.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
|
checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array 0.12.3",
|
"generic-array 0.14.4",
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ctr"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "022cd691704491df67d25d006fe8eca083098253c4d43516c2206479c58c6736"
|
|
||||||
dependencies = [
|
|
||||||
"block-cipher-trait",
|
|
||||||
"stream-cipher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctr"
|
name = "ctr"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -589,6 +538,15 @@ dependencies = [
|
||||||
"generic-array 0.12.3",
|
"generic-array 0.12.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||||
|
dependencies = [
|
||||||
|
"generic-array 0.14.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "discard"
|
name = "discard"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
|
@ -679,17 +637,11 @@ dependencies = [
|
||||||
"percent-encoding 2.1.0",
|
"percent-encoding 2.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fuchsia-cprng"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
|
checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -702,9 +654,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-channel"
|
name = "futures-channel"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
|
checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
@ -712,15 +664,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-core"
|
name = "futures-core"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
|
checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-executor"
|
name = "futures-executor"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9"
|
checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
|
@ -729,15 +681,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
|
checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-macro"
|
name = "futures-macro"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd"
|
checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro-hack",
|
"proc-macro-hack",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
|
@ -747,24 +699,21 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6"
|
checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-task"
|
name = "futures-task"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86"
|
checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80"
|
||||||
dependencies = [
|
|
||||||
"once_cell",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-util"
|
name = "futures-util"
|
||||||
version = "0.3.12"
|
version = "0.3.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
|
checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -1054,12 +1003,12 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.7.1"
|
version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
|
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crypto-mac",
|
"crypto-mac",
|
||||||
"digest",
|
"digest 0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1239,7 +1188,7 @@ checksum = "8e1d6ab7ada402b6a27912a2b86504be62a48c58313c886fe72a059127acb4d7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"libloading",
|
"libloading 0.6.7",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1342,6 +1291,16 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f84d96438c15fcd6c3f244c8fce01d1e2b9c6b5623e9c711dc9286d8fc92d6a"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libmdns"
|
name = "libmdns"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
|
@ -1410,9 +1369,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot"
|
name = "librespot"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"getopts",
|
"getopts",
|
||||||
|
@ -1429,16 +1388,16 @@ dependencies = [
|
||||||
"protobuf",
|
"protobuf",
|
||||||
"rand 0.7.3",
|
"rand 0.7.3",
|
||||||
"rpassword",
|
"rpassword",
|
||||||
"sha-1",
|
"sha-1 0.8.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url 1.7.2",
|
"url 1.7.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-audio"
|
name = "librespot-audio"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-ctr 0.6.0",
|
"aes-ctr",
|
||||||
"bit-set",
|
"bit-set",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
@ -1449,6 +1408,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"ogg",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"vorbis",
|
"vorbis",
|
||||||
|
@ -1456,10 +1416,10 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-connect"
|
name = "librespot-connect"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes-ctr 0.3.0",
|
"aes-ctr",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"block-modes",
|
"block-modes",
|
||||||
"dns-sd",
|
"dns-sd",
|
||||||
"futures",
|
"futures",
|
||||||
|
@ -1476,17 +1436,17 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha-1",
|
"sha-1 0.9.4",
|
||||||
"tokio",
|
"tokio",
|
||||||
"url 1.7.2",
|
"url 1.7.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-core"
|
name = "librespot-core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
@ -1508,7 +1468,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha-1",
|
"sha-1 0.9.4",
|
||||||
"shannon",
|
"shannon",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
|
@ -1520,7 +1480,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-metadata"
|
name = "librespot-metadata"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -1534,7 +1494,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-playback"
|
name = "librespot-playback"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"alsa",
|
"alsa",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
@ -1561,7 +1521,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "librespot-protocol"
|
name = "librespot-protocol"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
|
@ -1644,9 +1604,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.7.8"
|
version = "0.7.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95"
|
checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1892,9 +1852,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.5.2"
|
version = "1.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
|
checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "opaque-debug"
|
name = "opaque-debug"
|
||||||
|
@ -1941,17 +1901,12 @@ checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pbkdf2"
|
name = "pbkdf2"
|
||||||
version = "0.3.0"
|
version = "0.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
|
checksum = "309c95c5f738c85920eb7062a2de29f3840d4f96974453fc9ac1ba078da9c627"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.9.3",
|
|
||||||
"byteorder",
|
|
||||||
"crypto-mac",
|
"crypto-mac",
|
||||||
"hmac",
|
"hmac",
|
||||||
"rand 0.5.6",
|
|
||||||
"sha2",
|
|
||||||
"subtle",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2168,19 +2123,6 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand"
|
|
||||||
version = "0.5.6"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9"
|
|
||||||
dependencies = [
|
|
||||||
"cloudabi",
|
|
||||||
"fuchsia-cprng",
|
|
||||||
"libc",
|
|
||||||
"rand_core 0.3.1",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
|
@ -2226,21 +2168,6 @@ dependencies = [
|
||||||
"rand_core 0.6.2",
|
"rand_core 0.6.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
|
||||||
dependencies = [
|
|
||||||
"rand_core 0.4.2",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rand_core"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand_core"
|
name = "rand_core"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -2389,7 +2316,7 @@ version = "0.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
|
checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"log",
|
"log",
|
||||||
"ring",
|
"ring",
|
||||||
"sct",
|
"sct",
|
||||||
|
@ -2402,12 +2329,6 @@ 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 = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "safemem"
|
|
||||||
version = "0.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "same-file"
|
name = "same-file"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -2526,30 +2447,31 @@ version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"block-buffer 0.7.3",
|
||||||
"digest",
|
"digest 0.8.1",
|
||||||
"fake-simd",
|
"fake-simd",
|
||||||
"opaque-debug 0.2.3",
|
"opaque-debug 0.2.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha-1"
|
||||||
|
version = "0.9.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfebf75d25bd900fd1e7d11501efab59bc846dbc76196839663e6637bba9f25f"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.9.0",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"cpuid-bool",
|
||||||
|
"digest 0.9.0",
|
||||||
|
"opaque-debug 0.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
|
checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sha2"
|
|
||||||
version = "0.8.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
|
|
||||||
dependencies = [
|
|
||||||
"block-buffer",
|
|
||||||
"digest",
|
|
||||||
"fake-simd",
|
|
||||||
"opaque-debug 0.2.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shannon"
|
name = "shannon"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2673,15 +2595,6 @@ version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stream-cipher"
|
|
||||||
version = "0.3.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c"
|
|
||||||
dependencies = [
|
|
||||||
"generic-array 0.12.3",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.9.3"
|
version = "0.9.3"
|
||||||
|
@ -2708,9 +2621,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "1.0.0"
|
version = "2.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
|
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
|
@ -3040,7 +2953,7 @@ version = "1.5.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "294b85ef5dbc3670a72e82a89971608a1fcc4ed5c7c5a2895230d31a95f0569b"
|
checksum = "294b85ef5dbc3670a72e82a89971608a1fcc4ed5c7c5a2895230d31a95f0569b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.13.0",
|
"base64",
|
||||||
"chunked_transfer",
|
"chunked_transfer",
|
||||||
"cookie",
|
"cookie",
|
||||||
"cookie_store",
|
"cookie_store",
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot"
|
name = "librespot"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Librespot Org"]
|
authors = ["Librespot Org"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "An open source client library for Spotify, with support for Spotify Connect"
|
description = "An open source client library for Spotify, with support for Spotify Connect"
|
||||||
|
@ -22,27 +22,27 @@ doc = false
|
||||||
|
|
||||||
[dependencies.librespot-audio]
|
[dependencies.librespot-audio]
|
||||||
path = "audio"
|
path = "audio"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies.librespot-connect]
|
[dependencies.librespot-connect]
|
||||||
path = "connect"
|
path = "connect"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "core"
|
path = "core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies.librespot-metadata]
|
[dependencies.librespot-metadata]
|
||||||
path = "metadata"
|
path = "metadata"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies.librespot-playback]
|
[dependencies.librespot-playback]
|
||||||
path = "playback"
|
path = "playback"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies.librespot-protocol]
|
[dependencies.librespot-protocol]
|
||||||
path = "protocol"
|
path = "protocol"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
|
|
@ -27,7 +27,9 @@ There is some brief documentation on how the protocol works in the [docs](https:
|
||||||
|
|
||||||
If you wish to learn more about how librespot works overall, the best way is to simply read the code, and ask any questions you have in our [Gitter Room](https://gitter.im/librespot-org/spotify-connect-resources).
|
If you wish to learn more about how librespot works overall, the best way is to simply read the code, and ask any questions you have in our [Gitter Room](https://gitter.im/librespot-org/spotify-connect-resources).
|
||||||
|
|
||||||
# Issues
|
# Issues & Discussions
|
||||||
|
**We have recently started using Github discussions for general questions and feature requests, as they are a more natural medium for such cases, and allow for upvoting to prioritize feature development. Check them out [here](https://github.com/librespot-org/librespot/discussions). Bugs and issues with the underlying library should still be reported as issues.**
|
||||||
|
|
||||||
If you run into a bug when using librespot, please search the existing issues before opening a new one. Chances are, we've encountered it before, and have provided a resolution. If not, please open a new one, and where possible, include the backtrace librespot generates on crashing, along with anything we can use to reproduce the issue, eg. the Spotify URI of the song that caused the crash.
|
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
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-audio"
|
name = "librespot-audio"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||||
description="The audio fetching and processing logic for librespot"
|
description="The audio fetching and processing logic for librespot"
|
||||||
license="MIT"
|
license="MIT"
|
||||||
|
@ -8,7 +8,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes-ctr = "0.6"
|
aes-ctr = "0.6"
|
||||||
|
@ -17,6 +17,7 @@ byteorder = "1.4"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
lewton = "0.10"
|
lewton = "0.10"
|
||||||
|
ogg = "0.8"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
num-bigint = "0.3"
|
num-bigint = "0.3"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
|
|
|
@ -2,12 +2,12 @@ extern crate lewton;
|
||||||
|
|
||||||
use self::lewton::inside_ogg::OggStreamReader;
|
use self::lewton::inside_ogg::OggStreamReader;
|
||||||
|
|
||||||
|
use super::{AudioDecoder, AudioError, AudioPacket};
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
|
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
|
||||||
pub struct VorbisPacket(Vec<i16>);
|
|
||||||
pub struct VorbisError(lewton::VorbisError);
|
pub struct VorbisError(lewton::VorbisError);
|
||||||
|
|
||||||
impl<R> VorbisDecoder<R>
|
impl<R> VorbisDecoder<R>
|
||||||
|
@ -17,41 +17,38 @@ where
|
||||||
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
||||||
Ok(VorbisDecoder(OggStreamReader::new(input)?))
|
Ok(VorbisDecoder(OggStreamReader::new(input)?))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn seek(&mut self, ms: i64) -> Result<(), VorbisError> {
|
impl<R> AudioDecoder for VorbisDecoder<R>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
|
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
|
||||||
let absgp = ms * 44100 / 1000;
|
let absgp = ms * 44100 / 1000;
|
||||||
self.0.seek_absgp_pg(absgp as u64)?;
|
match self.0.seek_absgp_pg(absgp as u64) {
|
||||||
Ok(())
|
Ok(_) => return Ok(()),
|
||||||
|
Err(err) => return Err(AudioError::VorbisError(err.into())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_packet(&mut self) -> Result<Option<VorbisPacket>, VorbisError> {
|
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
|
||||||
use self::lewton::audio::AudioReadError::AudioIsHeader;
|
use self::lewton::audio::AudioReadError::AudioIsHeader;
|
||||||
use self::lewton::OggReadError::NoCapturePatternFound;
|
use self::lewton::OggReadError::NoCapturePatternFound;
|
||||||
use self::lewton::VorbisError::BadAudio;
|
use self::lewton::VorbisError::BadAudio;
|
||||||
use self::lewton::VorbisError::OggError;
|
use self::lewton::VorbisError::OggError;
|
||||||
loop {
|
loop {
|
||||||
match self.0.read_dec_packet_itl() {
|
match self.0.read_dec_packet_itl() {
|
||||||
Ok(Some(packet)) => return Ok(Some(VorbisPacket(packet))),
|
Ok(Some(packet)) => return Ok(Some(AudioPacket::Samples(packet))),
|
||||||
Ok(None) => return Ok(None),
|
Ok(None) => return Ok(None),
|
||||||
|
|
||||||
Err(BadAudio(AudioIsHeader)) => (),
|
Err(BadAudio(AudioIsHeader)) => (),
|
||||||
Err(OggError(NoCapturePatternFound)) => (),
|
Err(OggError(NoCapturePatternFound)) => (),
|
||||||
Err(err) => return Err(err.into()),
|
Err(err) => return Err(AudioError::VorbisError(err.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VorbisPacket {
|
|
||||||
pub fn data(&self) -> &[i16] {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data_mut(&mut self) -> &mut [i16] {
|
|
||||||
&mut self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<lewton::VorbisError> for VorbisError {
|
impl From<lewton::VorbisError> for VorbisError {
|
||||||
fn from(err: lewton::VorbisError) -> VorbisError {
|
fn from(err: lewton::VorbisError) -> VorbisError {
|
||||||
VorbisError(err)
|
VorbisError(err)
|
||||||
|
|
|
@ -23,6 +23,7 @@ mod fetch;
|
||||||
mod lewton_decoder;
|
mod lewton_decoder;
|
||||||
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
||||||
mod libvorbis_decoder;
|
mod libvorbis_decoder;
|
||||||
|
mod passthrough_decoder;
|
||||||
|
|
||||||
mod range_set;
|
mod range_set;
|
||||||
|
|
||||||
|
@ -32,8 +33,70 @@ pub use fetch::{
|
||||||
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
|
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
|
||||||
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
||||||
};
|
};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
pub enum AudioPacket {
|
||||||
|
Samples(Vec<i16>),
|
||||||
|
OggData(Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AudioPacket {
|
||||||
|
pub fn samples(&self) -> &[i16] {
|
||||||
|
match self {
|
||||||
|
AudioPacket::Samples(s) => s,
|
||||||
|
AudioPacket::OggData(_) => panic!("can't return OggData on samples"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oggdata(&self) -> &[u8] {
|
||||||
|
match self {
|
||||||
|
AudioPacket::Samples(_) => panic!("can't return samples on OggData"),
|
||||||
|
AudioPacket::OggData(d) => d,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
AudioPacket::Samples(s) => s.is_empty(),
|
||||||
|
AudioPacket::OggData(d) => d.is_empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
#[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))]
|
||||||
pub use crate::lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
pub use crate::lewton_decoder::{VorbisDecoder, VorbisError};
|
||||||
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
|
||||||
pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
|
pub use libvorbis_decoder::{VorbisDecoder, VorbisError};
|
||||||
|
pub use passthrough_decoder::{PassthroughDecoder, PassthroughError};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum AudioError {
|
||||||
|
PassthroughError(PassthroughError),
|
||||||
|
VorbisError(VorbisError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for AudioError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
AudioError::PassthroughError(err) => write!(f, "PassthroughError({})", err),
|
||||||
|
AudioError::VorbisError(err) => write!(f, "VorbisError({})", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VorbisError> for AudioError {
|
||||||
|
fn from(err: VorbisError) -> AudioError {
|
||||||
|
AudioError::VorbisError(VorbisError::from(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PassthroughError> for AudioError {
|
||||||
|
fn from(err: PassthroughError) -> AudioError {
|
||||||
|
AudioError::PassthroughError(PassthroughError::from(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait AudioDecoder {
|
||||||
|
fn seek(&mut self, ms: i64) -> Result<(), AudioError>;
|
||||||
|
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError>;
|
||||||
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ extern crate librespot_tremor as vorbis;
|
||||||
#[cfg(not(feature = "with-tremor"))]
|
#[cfg(not(feature = "with-tremor"))]
|
||||||
extern crate vorbis;
|
extern crate vorbis;
|
||||||
|
|
||||||
|
use super::{AudioDecoder, AudioError, AudioPacket};
|
||||||
use std::error;
|
use std::error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io::{Read, Seek};
|
use std::io::{Read, Seek};
|
||||||
|
|
||||||
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
|
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
|
||||||
pub struct VorbisPacket(vorbis::Packet);
|
|
||||||
pub struct VorbisError(vorbis::VorbisError);
|
pub struct VorbisError(vorbis::VorbisError);
|
||||||
|
|
||||||
impl<R> VorbisDecoder<R>
|
impl<R> VorbisDecoder<R>
|
||||||
|
@ -18,23 +18,28 @@ where
|
||||||
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
|
||||||
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
|
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R> AudioDecoder for VorbisDecoder<R>
|
||||||
|
where
|
||||||
|
R: Read + Seek,
|
||||||
|
{
|
||||||
#[cfg(not(feature = "with-tremor"))]
|
#[cfg(not(feature = "with-tremor"))]
|
||||||
pub fn seek(&mut self, ms: i64) -> Result<(), VorbisError> {
|
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
|
||||||
self.0.time_seek(ms as f64 / 1000f64)?;
|
self.0.time_seek(ms as f64 / 1000f64)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "with-tremor")]
|
#[cfg(feature = "with-tremor")]
|
||||||
pub fn seek(&mut self, ms: i64) -> Result<(), VorbisError> {
|
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
|
||||||
self.0.time_seek(ms)?;
|
self.0.time_seek(ms)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_packet(&mut self) -> Result<Option<VorbisPacket>, VorbisError> {
|
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
|
||||||
loop {
|
loop {
|
||||||
match self.0.packets().next() {
|
match self.0.packets().next() {
|
||||||
Some(Ok(packet)) => return Ok(Some(VorbisPacket(packet))),
|
Some(Ok(packet)) => return Ok(Some(AudioPacket::Samples(packet.data))),
|
||||||
None => return Ok(None),
|
None => return Ok(None),
|
||||||
|
|
||||||
Some(Err(vorbis::VorbisError::Hole)) => (),
|
Some(Err(vorbis::VorbisError::Hole)) => (),
|
||||||
|
@ -44,16 +49,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VorbisPacket {
|
|
||||||
pub fn data(&self) -> &[i16] {
|
|
||||||
&self.0.data
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data_mut(&mut self) -> &mut [i16] {
|
|
||||||
&mut self.0.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<vorbis::VorbisError> for VorbisError {
|
impl From<vorbis::VorbisError> for VorbisError {
|
||||||
fn from(err: vorbis::VorbisError) -> VorbisError {
|
fn from(err: vorbis::VorbisError) -> VorbisError {
|
||||||
VorbisError(err)
|
VorbisError(err)
|
||||||
|
@ -77,3 +72,9 @@ impl error::Error for VorbisError {
|
||||||
error::Error::source(&self.0)
|
error::Error::source(&self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<vorbis::VorbisError> for AudioError {
|
||||||
|
fn from(err: vorbis::VorbisError) -> AudioError {
|
||||||
|
AudioError::VorbisError(VorbisError(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
191
audio/src/passthrough_decoder.rs
Normal file
191
audio/src/passthrough_decoder.rs
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
// Passthrough decoder for librespot
|
||||||
|
use super::{AudioDecoder, AudioError, AudioPacket};
|
||||||
|
use ogg::{OggReadError, Packet, PacketReader, PacketWriteEndInfo, PacketWriter};
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{Read, Seek};
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
|
||||||
|
fn write_headers<T: Read + Seek>(
|
||||||
|
rdr: &mut PacketReader<T>,
|
||||||
|
wtr: &mut PacketWriter<Vec<u8>>,
|
||||||
|
) -> Result<u32, PassthroughError> {
|
||||||
|
let mut stream_serial: u32 = SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_millis() as u32;
|
||||||
|
|
||||||
|
// search for ident, comment, setup
|
||||||
|
get_header(1, rdr, wtr, &mut stream_serial, PacketWriteEndInfo::EndPage)?;
|
||||||
|
get_header(
|
||||||
|
3,
|
||||||
|
rdr,
|
||||||
|
wtr,
|
||||||
|
&mut stream_serial,
|
||||||
|
PacketWriteEndInfo::NormalPacket,
|
||||||
|
)?;
|
||||||
|
get_header(5, rdr, wtr, &mut stream_serial, PacketWriteEndInfo::EndPage)?;
|
||||||
|
|
||||||
|
// remove un-needed packets
|
||||||
|
rdr.delete_unread_packets();
|
||||||
|
return Ok(stream_serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_header<T>(
|
||||||
|
code: u8,
|
||||||
|
rdr: &mut PacketReader<T>,
|
||||||
|
wtr: &mut PacketWriter<Vec<u8>>,
|
||||||
|
stream_serial: &mut u32,
|
||||||
|
info: PacketWriteEndInfo,
|
||||||
|
) -> Result<u32, PassthroughError>
|
||||||
|
where
|
||||||
|
T: Read + Seek,
|
||||||
|
{
|
||||||
|
let pck: Packet = rdr.read_packet_expected()?;
|
||||||
|
|
||||||
|
// set a unique serial number
|
||||||
|
if pck.stream_serial() != 0 {
|
||||||
|
*stream_serial = pck.stream_serial();
|
||||||
|
}
|
||||||
|
|
||||||
|
let pkt_type = pck.data[0];
|
||||||
|
debug!("Vorbis header type{}", &pkt_type);
|
||||||
|
|
||||||
|
// all headers are mandatory
|
||||||
|
if pkt_type != code {
|
||||||
|
return Err(PassthroughError(OggReadError::InvalidData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// headers keep original granule number
|
||||||
|
let absgp_page = pck.absgp_page();
|
||||||
|
wtr.write_packet(
|
||||||
|
pck.data.into_boxed_slice(),
|
||||||
|
*stream_serial,
|
||||||
|
info,
|
||||||
|
absgp_page,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
return Ok(*stream_serial);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PassthroughDecoder<R: Read + Seek> {
|
||||||
|
rdr: PacketReader<R>,
|
||||||
|
wtr: PacketWriter<Vec<u8>>,
|
||||||
|
lastgp_page: Option<u64>,
|
||||||
|
absgp_page: u64,
|
||||||
|
stream_serial: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PassthroughError(ogg::OggReadError);
|
||||||
|
|
||||||
|
impl<R: Read + Seek> PassthroughDecoder<R> {
|
||||||
|
/// Constructs a new Decoder from a given implementation of `Read + Seek`.
|
||||||
|
pub fn new(rdr: R) -> Result<Self, PassthroughError> {
|
||||||
|
let mut rdr = PacketReader::new(rdr);
|
||||||
|
let mut wtr = PacketWriter::new(Vec::new());
|
||||||
|
|
||||||
|
let stream_serial = write_headers(&mut rdr, &mut wtr)?;
|
||||||
|
info!("Starting passthrough track with serial {}", stream_serial);
|
||||||
|
|
||||||
|
return Ok(PassthroughDecoder {
|
||||||
|
rdr,
|
||||||
|
wtr,
|
||||||
|
lastgp_page: Some(0),
|
||||||
|
absgp_page: 0,
|
||||||
|
stream_serial,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: Read + Seek> AudioDecoder for PassthroughDecoder<R> {
|
||||||
|
fn seek(&mut self, ms: i64) -> Result<(), AudioError> {
|
||||||
|
info!("Seeking to {}", ms);
|
||||||
|
self.lastgp_page = match ms {
|
||||||
|
0 => Some(0),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// hard-coded to 44.1 kHz
|
||||||
|
match self.rdr.seek_absgp(None, (ms * 44100 / 1000) as u64) {
|
||||||
|
Ok(_) => return Ok(()),
|
||||||
|
Err(err) => return Err(AudioError::PassthroughError(err.into())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
|
||||||
|
let mut skip = self.lastgp_page.is_none();
|
||||||
|
loop {
|
||||||
|
let pck = match self.rdr.read_packet() {
|
||||||
|
Ok(Some(pck)) => pck,
|
||||||
|
|
||||||
|
Ok(None) | Err(OggReadError::NoCapturePatternFound) => {
|
||||||
|
info!("end of streaming");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(err) => return Err(AudioError::PassthroughError(err.into())),
|
||||||
|
};
|
||||||
|
|
||||||
|
let pckgp_page = pck.absgp_page();
|
||||||
|
let lastgp_page = self.lastgp_page.get_or_insert(pckgp_page);
|
||||||
|
|
||||||
|
// consume packets till next page to get a granule reference
|
||||||
|
if skip {
|
||||||
|
if *lastgp_page == pckgp_page {
|
||||||
|
debug!("skipping packet");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
skip = false;
|
||||||
|
info!("skipped at {}", pckgp_page);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now we can calculate absolute granule
|
||||||
|
self.absgp_page += pckgp_page - *lastgp_page;
|
||||||
|
self.lastgp_page = Some(pckgp_page);
|
||||||
|
|
||||||
|
// set packet type
|
||||||
|
let inf = if pck.last_in_stream() {
|
||||||
|
self.lastgp_page = Some(0);
|
||||||
|
PacketWriteEndInfo::EndStream
|
||||||
|
} else if pck.last_in_page() {
|
||||||
|
PacketWriteEndInfo::EndPage
|
||||||
|
} else {
|
||||||
|
PacketWriteEndInfo::NormalPacket
|
||||||
|
};
|
||||||
|
|
||||||
|
self.wtr
|
||||||
|
.write_packet(
|
||||||
|
pck.data.into_boxed_slice(),
|
||||||
|
self.stream_serial,
|
||||||
|
inf,
|
||||||
|
self.absgp_page,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let data = self.wtr.inner_mut();
|
||||||
|
|
||||||
|
if data.len() > 0 {
|
||||||
|
let result = AudioPacket::OggData(std::mem::take(data));
|
||||||
|
return Ok(Some(result));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for PassthroughError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ogg::OggReadError> for PassthroughError {
|
||||||
|
fn from(err: OggReadError) -> PassthroughError {
|
||||||
|
PassthroughError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for PassthroughError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-connect"
|
name = "librespot-connect"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||||
description = "The discovery and Spotify Connect logic for librespot"
|
description = "The discovery and Spotify Connect logic for librespot"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
@ -9,17 +9,20 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
[dependencies.librespot-playback]
|
[dependencies.librespot-playback]
|
||||||
path = "../playback"
|
path = "../playback"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
[dependencies.librespot-protocol]
|
[dependencies.librespot-protocol]
|
||||||
path = "../protocol"
|
path = "../protocol"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
aes-ctr = "0.6"
|
||||||
base64 = "0.13"
|
base64 = "0.13"
|
||||||
|
block-modes = "0.7"
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
hmac = "0.10"
|
||||||
hyper = { version = "0.14", features = ["server", "http1"] }
|
hyper = { version = "0.14", features = ["server", "http1"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
num-bigint = "0.3"
|
num-bigint = "0.3"
|
||||||
|
@ -28,12 +31,9 @@ rand = "0.7"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
sha-1 = "0.9"
|
||||||
tokio = { version = "1.0", features = ["macros"] }
|
tokio = { version = "1.0", features = ["macros"] }
|
||||||
url = "1.7"
|
url = "1.7"
|
||||||
sha-1 = "0.8"
|
|
||||||
hmac = "0.7"
|
|
||||||
aes-ctr = "0.3"
|
|
||||||
block-modes = "0.3"
|
|
||||||
|
|
||||||
dns-sd = { version = "0.1.3", optional = true }
|
dns-sd = { version = "0.1.3", optional = true }
|
||||||
libmdns = { version = "0.6", optional = true }
|
libmdns = { version = "0.6", optional = true }
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
use aes_ctr::stream_cipher::generic_array::GenericArray;
|
use aes_ctr::cipher::generic_array::GenericArray;
|
||||||
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
|
use aes_ctr::cipher::{NewStreamCipher, SyncStreamCipher};
|
||||||
use aes_ctr::Aes128Ctr;
|
use aes_ctr::Aes128Ctr;
|
||||||
use base64;
|
use base64;
|
||||||
use futures::channel::{mpsc, oneshot};
|
use futures::channel::{mpsc, oneshot};
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac, NewMac};
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
|
@ -118,18 +118,18 @@ impl Discovery {
|
||||||
|
|
||||||
let checksum_key = {
|
let checksum_key = {
|
||||||
let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size");
|
let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size");
|
||||||
h.input(b"checksum");
|
h.update(b"checksum");
|
||||||
h.result().code()
|
h.finalize().into_bytes()
|
||||||
};
|
};
|
||||||
|
|
||||||
let encryption_key = {
|
let encryption_key = {
|
||||||
let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size");
|
let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size");
|
||||||
h.input(b"encryption");
|
h.update(b"encryption");
|
||||||
h.result().code()
|
h.finalize().into_bytes()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut h = HmacSha1::new_varkey(&checksum_key).expect("HMAC can take key of any size");
|
let mut h = HmacSha1::new_varkey(&checksum_key).expect("HMAC can take key of any size");
|
||||||
h.input(encrypted);
|
h.update(encrypted);
|
||||||
if let Err(_) = h.verify(cksum) {
|
if let Err(_) = h.verify(cksum) {
|
||||||
warn!("Login error for user {:?}: MAC mismatch", username);
|
warn!("Login error for user {:?}: MAC mismatch", username);
|
||||||
let result = json!({
|
let result = json!({
|
||||||
|
|
|
@ -790,7 +790,7 @@ impl SpircTask {
|
||||||
self.handle_play()
|
self.handle_play()
|
||||||
}
|
}
|
||||||
SpircPlayStatus::Playing { .. } | SpircPlayStatus::LoadingPlay { .. } => {
|
SpircPlayStatus::Playing { .. } | SpircPlayStatus::LoadingPlay { .. } => {
|
||||||
self.handle_play()
|
self.handle_pause()
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-core"
|
name = "librespot-core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
description = "The core functionality provided by librespot"
|
description = "The core functionality provided by librespot"
|
||||||
|
@ -10,7 +10,7 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies.librespot-protocol]
|
[dependencies.librespot-protocol]
|
||||||
path = "../protocol"
|
path = "../protocol"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = "0.6"
|
aes = "0.6"
|
||||||
|
@ -19,7 +19,7 @@ byteorder = "1.4"
|
||||||
bytes = "1.0"
|
bytes = "1.0"
|
||||||
error-chain = { version = "0.12", default-features = false }
|
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.10"
|
||||||
httparse = "1.3"
|
httparse = "1.3"
|
||||||
hyper = { version = "0.14", features = ["client", "tcp", "http1", "http2"] }
|
hyper = { version = "0.14", features = ["client", "tcp", "http1", "http2"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
@ -27,14 +27,14 @@ num-bigint = "0.3"
|
||||||
num-integer = "0.1"
|
num-integer = "0.1"
|
||||||
num-traits = "0.2"
|
num-traits = "0.2"
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
pbkdf2 = "0.3"
|
pbkdf2 = { version = "0.7", default_features = false, features = ["hmac"] }
|
||||||
pin-project-lite = "0.2.4"
|
pin-project-lite = "0.2.4"
|
||||||
protobuf = "~2.14.0"
|
protobuf = "~2.14.0"
|
||||||
rand = "0.7"
|
rand = "0.7"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
sha-1 = "~0.8"
|
sha-1 = "0.9"
|
||||||
shannon = "0.2.0"
|
shannon = "0.2.0"
|
||||||
tokio = { version = "1.0", features = ["io-util", "rt-multi-thread"] }
|
tokio = { version = "1.0", features = ["io-util", "rt-multi-thread"] }
|
||||||
tokio-util = { version = "0.6", features = ["codec"] }
|
tokio-util = { version = "0.6", features = ["codec"] }
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use aes::Aes192;
|
use aes::Aes192;
|
||||||
use aes::NewBlockCipher;
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use hmac::Hmac;
|
use hmac::Hmac;
|
||||||
use pbkdf2::pbkdf2;
|
use pbkdf2::pbkdf2;
|
||||||
|
@ -74,7 +73,7 @@ impl Credentials {
|
||||||
let blob = {
|
let blob = {
|
||||||
use aes::cipher::generic_array::typenum::Unsigned;
|
use aes::cipher::generic_array::typenum::Unsigned;
|
||||||
use aes::cipher::generic_array::GenericArray;
|
use aes::cipher::generic_array::GenericArray;
|
||||||
use aes::cipher::BlockCipher;
|
use aes::cipher::{BlockCipher, NewBlockCipher};
|
||||||
|
|
||||||
let mut data = base64::decode(encrypted_blob).unwrap();
|
let mut data = base64::decode(encrypted_blob).unwrap();
|
||||||
let cipher = Aes192::new(GenericArray::from_slice(&key));
|
let cipher = Aes192::new(GenericArray::from_slice(&key));
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
|
||||||
use hmac::{Hmac, Mac};
|
use hmac::{Hmac, Mac, NewMac};
|
||||||
use protobuf::{self, Message};
|
use protobuf::{self, Message};
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use sha1::Sha1;
|
use sha1::Sha1;
|
||||||
|
@ -122,16 +122,16 @@ fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec<u8>, Vec<u8>, Vec<
|
||||||
let mut data = Vec::with_capacity(0x64);
|
let mut data = Vec::with_capacity(0x64);
|
||||||
for i in 1..6 {
|
for i in 1..6 {
|
||||||
let mut mac = HmacSha1::new_varkey(&shared_secret).expect("HMAC can take key of any size");
|
let mut mac = HmacSha1::new_varkey(&shared_secret).expect("HMAC can take key of any size");
|
||||||
mac.input(packets);
|
mac.update(packets);
|
||||||
mac.input(&[i]);
|
mac.update(&[i]);
|
||||||
data.extend_from_slice(&mac.result().code());
|
data.extend_from_slice(&mac.finalize().into_bytes());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut mac = HmacSha1::new_varkey(&data[..0x14]).expect("HMAC can take key of any size");
|
let mut mac = HmacSha1::new_varkey(&data[..0x14]).expect("HMAC can take key of any size");
|
||||||
mac.input(packets);
|
mac.update(packets);
|
||||||
|
|
||||||
(
|
(
|
||||||
mac.result().code().to_vec(),
|
mac.finalize().into_bytes().to_vec(),
|
||||||
data[0x14..0x34].to_vec(),
|
data[0x14..0x34].to_vec(),
|
||||||
data[0x34..0x54].to_vec(),
|
data[0x34..0x54].to_vec(),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-metadata"
|
name = "librespot-metadata"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Paul Lietar <paul@lietar.net>"]
|
authors = ["Paul Lietar <paul@lietar.net>"]
|
||||||
description = "The metadata logic for librespot"
|
description = "The metadata logic for librespot"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
@ -17,7 +17,7 @@ log = "0.4"
|
||||||
|
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
[dependencies.librespot-protocol]
|
[dependencies.librespot-protocol]
|
||||||
path = "../protocol"
|
path = "../protocol"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-playback"
|
name = "librespot-playback"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Sasha Hilton <sashahilton00@gmail.com>"]
|
authors = ["Sasha Hilton <sashahilton00@gmail.com>"]
|
||||||
description = "The audio playback logic for librespot"
|
description = "The audio playback logic for librespot"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
@ -9,13 +9,13 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies.librespot-audio]
|
[dependencies.librespot-audio]
|
||||||
path = "../audio"
|
path = "../audio"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
[dependencies.librespot-core]
|
[dependencies.librespot-core]
|
||||||
path = "../core"
|
path = "../core"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
[dependencies.librespot-metadata]
|
[dependencies.librespot-metadata]
|
||||||
path = "../metadata"
|
path = "../metadata"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use alsa::device_name::HintIter;
|
use alsa::device_name::HintIter;
|
||||||
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
|
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
|
||||||
use alsa::{Direction, Error, ValueOr};
|
use alsa::{Direction, Error, ValueOr};
|
||||||
|
@ -124,8 +125,9 @@ impl Sink for AlsaSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
let mut processed_data = 0;
|
let mut processed_data = 0;
|
||||||
|
let data = packet.samples();
|
||||||
while processed_data < data.len() {
|
while processed_data < data.len() {
|
||||||
let data_to_buffer = min(
|
let data_to_buffer = min(
|
||||||
self.buffer.capacity() - self.buffer.len(),
|
self.buffer.capacity() - self.buffer.len(),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use gst::prelude::*;
|
use gst::prelude::*;
|
||||||
use gst::*;
|
use gst::*;
|
||||||
use std::sync::mpsc::{sync_channel, SyncSender};
|
use std::sync::mpsc::{sync_channel, SyncSender};
|
||||||
|
@ -104,9 +105,9 @@ impl Sink for GstreamerSink {
|
||||||
fn stop(&mut self) -> io::Result<()> {
|
fn stop(&mut self) -> io::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
// Copy expensively (in to_vec()) to avoid thread synchronization
|
// Copy expensively (in to_vec()) to avoid thread synchronization
|
||||||
let deighta: &[u8] = data.as_bytes();
|
let deighta: &[u8] = packet.samples().as_bytes();
|
||||||
self.tx
|
self.tx
|
||||||
.send(deighta.to_vec())
|
.send(deighta.to_vec())
|
||||||
.expect("tx send failed in write function");
|
.expect("tx send failed in write function");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use jack::{
|
use jack::{
|
||||||
AsyncClient, AudioOut, Client, ClientOptions, Control, Port, ProcessHandler, ProcessScope,
|
AsyncClient, AudioOut, Client, ClientOptions, Control, Port, ProcessHandler, ProcessScope,
|
||||||
};
|
};
|
||||||
|
@ -73,8 +74,8 @@ impl Sink for JackSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
for s in data.iter() {
|
for s in packet.samples().iter() {
|
||||||
let res = self.send.send(*s);
|
let res = self.send.send(*s);
|
||||||
if res.is_err() {
|
if res.is_err() {
|
||||||
error!("jackaudio: cannot write to channel");
|
error!("jackaudio: cannot write to channel");
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub trait Open {
|
pub trait Open {
|
||||||
|
@ -7,7 +8,7 @@ pub trait Open {
|
||||||
pub trait Sink {
|
pub trait Sink {
|
||||||
fn start(&mut self) -> io::Result<()>;
|
fn start(&mut self) -> io::Result<()>;
|
||||||
fn stop(&mut self) -> io::Result<()>;
|
fn stop(&mut self) -> io::Result<()>;
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()>;
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type SinkBuilder = fn(Option<String>) -> Box<dyn Sink + Send>;
|
pub type SinkBuilder = fn(Option<String>) -> Box<dyn Sink + Send>;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -26,12 +27,15 @@ impl Sink for StdoutSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
let data: &[u8] = unsafe {
|
let data: &[u8] = match packet {
|
||||||
slice::from_raw_parts(
|
AudioPacket::Samples(data) => unsafe {
|
||||||
data.as_ptr() as *const u8,
|
slice::from_raw_parts(
|
||||||
data.len() * mem::size_of::<i16>(),
|
data.as_ptr() as *const u8,
|
||||||
)
|
data.len() * mem::size_of::<i16>(),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
AudioPacket::OggData(data) => data,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.0.write_all(data)?;
|
self.0.write_all(data)?;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use portaudio_rs;
|
use portaudio_rs;
|
||||||
use portaudio_rs::device::{get_default_output_index, DeviceIndex, DeviceInfo};
|
use portaudio_rs::device::{get_default_output_index, DeviceIndex, DeviceInfo};
|
||||||
use portaudio_rs::stream::*;
|
use portaudio_rs::stream::*;
|
||||||
|
@ -95,8 +96,8 @@ impl<'a> Sink for PortAudioSink<'a> {
|
||||||
self.0 = None;
|
self.0 = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
match self.0.as_mut().unwrap().write(data) {
|
match self.0.as_mut().unwrap().write(packet.samples()) {
|
||||||
Ok(_) => (),
|
Ok(_) => (),
|
||||||
Err(portaudio_rs::PaError::OutputUnderflowed) => error!("PortAudio write underflow"),
|
Err(portaudio_rs::PaError::OutputUnderflowed) => error!("PortAudio write underflow"),
|
||||||
Err(e) => panic!("PA Error {}", e),
|
Err(e) => panic!("PA Error {}", e),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use libpulse_binding::{self as pulse, stream::Direction};
|
use libpulse_binding::{self as pulse, stream::Direction};
|
||||||
use libpulse_simple_binding::Simple;
|
use libpulse_simple_binding::Simple;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
@ -65,13 +66,17 @@ impl Sink for PulseAudioSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
if let Some(s) = &self.s {
|
if let Some(s) = &self.s {
|
||||||
// SAFETY: An i16 consists of two bytes, so that the given slice can be interpreted
|
// 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
|
// as a byte array of double length. Each byte pointer is validly aligned, and so
|
||||||
// is the newly created slice.
|
// is the newly created slice.
|
||||||
let d: &[u8] =
|
let d: &[u8] = unsafe {
|
||||||
unsafe { std::slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * 2) };
|
std::slice::from_raw_parts(
|
||||||
|
packet.samples().as_ptr() as *const u8,
|
||||||
|
packet.samples().len() * 2,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
match s.write(d) {
|
match s.write(d) {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
|
|
@ -6,6 +6,7 @@ use cpal::traits::{DeviceTrait, HostTrait};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum RodioError {
|
pub enum RodioError {
|
||||||
|
@ -178,8 +179,8 @@ impl Sink for RodioSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
let source = rodio::buffer::SamplesBuffer::new(2, 44100, data);
|
let source = rodio::buffer::SamplesBuffer::new(2, 44100, packet.samples());
|
||||||
self.rodio_sink.append(source);
|
self.rodio_sink.append(source);
|
||||||
|
|
||||||
// Chunk sizes seem to be about 256 to 3000 ish items long.
|
// Chunk sizes seem to be about 256 to 3000 ish items long.
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use sdl2::audio::{AudioQueue, AudioSpecDesired};
|
use sdl2::audio::{AudioQueue, AudioSpecDesired};
|
||||||
use std::{io, thread, time};
|
use std::{io, thread, time};
|
||||||
|
|
||||||
|
@ -45,12 +46,12 @@ impl Sink for SdlSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
while self.queue.size() > (2 * 2 * 44_100) {
|
while self.queue.size() > (2 * 2 * 44_100) {
|
||||||
// sleep and wait for sdl thread to drain the queue a bit
|
// sleep and wait for sdl thread to drain the queue a bit
|
||||||
thread::sleep(time::Duration::from_millis(10));
|
thread::sleep(time::Duration::from_millis(10));
|
||||||
}
|
}
|
||||||
self.queue.queue(data);
|
self.queue.queue(packet.samples());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use super::{Open, Sink};
|
use super::{Open, Sink};
|
||||||
|
use crate::audio::AudioPacket;
|
||||||
use shell_words::split;
|
use shell_words::split;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
@ -43,11 +44,11 @@ impl Sink for SubprocessSink {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(&mut self, data: &[i16]) -> io::Result<()> {
|
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
|
||||||
let data: &[u8] = unsafe {
|
let data: &[u8] = unsafe {
|
||||||
slice::from_raw_parts(
|
slice::from_raw_parts(
|
||||||
data.as_ptr() as *const u8,
|
packet.samples().as_ptr() as *const u8,
|
||||||
data.len() * mem::size_of::<i16>(),
|
packet.samples().len() * mem::size_of::<i16>(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
if let Some(child) = &mut self.child {
|
if let Some(child) = &mut self.child {
|
||||||
|
|
|
@ -55,6 +55,7 @@ pub struct PlayerConfig {
|
||||||
pub normalisation_type: NormalisationType,
|
pub normalisation_type: NormalisationType,
|
||||||
pub normalisation_pregain: f32,
|
pub normalisation_pregain: f32,
|
||||||
pub gapless: bool,
|
pub gapless: bool,
|
||||||
|
pub passthrough: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlayerConfig {
|
impl Default for PlayerConfig {
|
||||||
|
@ -65,6 +66,7 @@ impl Default for PlayerConfig {
|
||||||
normalisation_type: NormalisationType::default(),
|
normalisation_type: NormalisationType::default(),
|
||||||
normalisation_pregain: 0.0,
|
normalisation_pregain: 0.0,
|
||||||
gapless: true,
|
gapless: true,
|
||||||
|
passthrough: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
use crate::audio::{AudioDecoder, AudioError, AudioPacket, PassthroughDecoder, VorbisDecoder};
|
||||||
use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
|
use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
|
||||||
use crate::audio::{VorbisDecoder, VorbisPacket};
|
|
||||||
use crate::audio::{
|
use crate::audio::{
|
||||||
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
|
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
|
||||||
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
|
||||||
|
@ -377,7 +377,7 @@ enum PlayerPreload {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Decoder = VorbisDecoder<Subfile<AudioDecrypt<AudioFile>>>;
|
type Decoder = Box<dyn AudioDecoder + Send>;
|
||||||
|
|
||||||
enum PlayerState {
|
enum PlayerState {
|
||||||
Stopped,
|
Stopped,
|
||||||
|
@ -730,7 +730,19 @@ impl PlayerTrackLoader {
|
||||||
|
|
||||||
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
let audio_file = Subfile::new(decrypted_file, 0xa7);
|
||||||
|
|
||||||
let mut decoder = match VorbisDecoder::new(audio_file) {
|
let result = if self.config.passthrough {
|
||||||
|
match PassthroughDecoder::new(audio_file) {
|
||||||
|
Ok(result) => Ok(Box::new(result) as Decoder),
|
||||||
|
Err(e) => Err(AudioError::PassthroughError(e)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
match VorbisDecoder::new(audio_file) {
|
||||||
|
Ok(result) => Ok(Box::new(result) as Decoder),
|
||||||
|
Err(e) => Err(AudioError::VorbisError(e)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut decoder = match result {
|
||||||
Ok(decoder) => decoder,
|
Ok(decoder) => decoder,
|
||||||
Err(e) if is_cached => {
|
Err(e) if is_cached => {
|
||||||
warn!(
|
warn!(
|
||||||
|
@ -779,6 +791,7 @@ impl Future for PlayerInternal {
|
||||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
// While this is written as a future, it still contains blocking code.
|
// While this is written as a future, it still contains blocking code.
|
||||||
// It must be run on its own thread.
|
// It must be run on its own thread.
|
||||||
|
let passthrough = self.config.passthrough;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut all_futures_completed_or_not_ready = true;
|
let mut all_futures_completed_or_not_ready = true;
|
||||||
|
@ -880,32 +893,44 @@ impl Future for PlayerInternal {
|
||||||
{
|
{
|
||||||
let packet = decoder.next_packet().expect("Vorbis error");
|
let packet = decoder.next_packet().expect("Vorbis error");
|
||||||
|
|
||||||
if let Some(ref packet) = packet {
|
if !passthrough {
|
||||||
*stream_position_pcm += (packet.data().len() / 2) as u64;
|
if let Some(ref packet) = packet {
|
||||||
let stream_position_millis = Self::position_pcm_to_ms(*stream_position_pcm);
|
*stream_position_pcm =
|
||||||
|
*stream_position_pcm + (packet.samples().len() / 2) as u64;
|
||||||
|
let stream_position_millis =
|
||||||
|
Self::position_pcm_to_ms(*stream_position_pcm);
|
||||||
|
|
||||||
let notify_about_position = match *reported_nominal_start_time {
|
let notify_about_position = match *reported_nominal_start_time {
|
||||||
None => true,
|
None => true,
|
||||||
Some(reported_nominal_start_time) => {
|
Some(reported_nominal_start_time) => {
|
||||||
// only notify if we're behind. If we're ahead it's probably due to a buffer of the backend and we;re actually in time.
|
// only notify if we're behind. If we're ahead it's probably due to a buffer of the backend and we;re actually in time.
|
||||||
let lag = (Instant::now() - reported_nominal_start_time).as_millis()
|
let lag = (Instant::now() - reported_nominal_start_time)
|
||||||
as i64
|
.as_millis()
|
||||||
- stream_position_millis as i64;
|
as i64
|
||||||
lag > 1000
|
- stream_position_millis as i64;
|
||||||
|
if lag > 1000 {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if notify_about_position {
|
||||||
|
*reported_nominal_start_time = Some(
|
||||||
|
Instant::now()
|
||||||
|
- Duration::from_millis(stream_position_millis as u64),
|
||||||
|
);
|
||||||
|
self.send_event(PlayerEvent::Playing {
|
||||||
|
track_id,
|
||||||
|
play_request_id,
|
||||||
|
position_ms: stream_position_millis as u32,
|
||||||
|
duration_ms,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
|
||||||
if notify_about_position {
|
|
||||||
*reported_nominal_start_time = Some(
|
|
||||||
Instant::now()
|
|
||||||
- Duration::from_millis(stream_position_millis as u64),
|
|
||||||
);
|
|
||||||
self.send_event(PlayerEvent::Playing {
|
|
||||||
track_id,
|
|
||||||
play_request_id,
|
|
||||||
position_ms: stream_position_millis as u32,
|
|
||||||
duration_ms,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// position, even if irrelevant, must be set so that seek() is called
|
||||||
|
*stream_position_pcm = duration_ms.into();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.handle_packet(packet, normalisation_factor);
|
self.handle_packet(packet, normalisation_factor);
|
||||||
|
@ -1087,23 +1112,23 @@ impl PlayerInternal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_packet(&mut self, packet: Option<VorbisPacket>, normalisation_factor: f32) {
|
fn handle_packet(&mut self, packet: Option<AudioPacket>, normalisation_factor: f32) {
|
||||||
match packet {
|
match packet {
|
||||||
Some(mut packet) => {
|
Some(mut packet) => {
|
||||||
if !packet.data().is_empty() {
|
if !packet.is_empty() {
|
||||||
if let Some(ref editor) = self.audio_filter {
|
if let AudioPacket::Samples(ref mut data) = packet {
|
||||||
editor.modify_stream(&mut packet.data_mut())
|
if let Some(ref editor) = self.audio_filter {
|
||||||
};
|
editor.modify_stream(data)
|
||||||
|
}
|
||||||
|
|
||||||
if self.config.normalisation
|
if self.config.normalisation && normalisation_factor != 1.0 {
|
||||||
&& (normalisation_factor - 1.0).abs() < f32::EPSILON
|
for x in data.iter_mut() {
|
||||||
{
|
*x = (*x as f32 * normalisation_factor) as i16;
|
||||||
for x in packet.data_mut().iter_mut() {
|
}
|
||||||
*x = (*x as f32 * normalisation_factor) as i16;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(err) = self.sink.write(&packet.data()) {
|
if let Err(err) = self.sink.write(&packet) {
|
||||||
error!("Could not write audio: {}", err);
|
error!("Could not write audio: {}", err);
|
||||||
self.ensure_sink_stopped(false);
|
self.ensure_sink_stopped(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "librespot-protocol"
|
name = "librespot-protocol"
|
||||||
version = "0.1.3"
|
version = "0.1.6"
|
||||||
authors = ["Paul Liétar <paul@lietar.net>"]
|
authors = ["Paul Liétar <paul@lietar.net>"]
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
description = "The protobuf logic for communicating with Spotify servers"
|
description = "The protobuf logic for communicating with Spotify servers"
|
||||||
|
|
121
publish.sh
121
publish.sh
|
@ -1,16 +1,25 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
SKIP_MERGE='false'
|
||||||
|
DRY_RUN='false'
|
||||||
|
|
||||||
WORKINGDIR="$( cd "$(dirname "$0")" ; pwd -P )"
|
WORKINGDIR="$( cd "$(dirname "$0")" ; pwd -P )"
|
||||||
cd $WORKINGDIR
|
cd $WORKINGDIR
|
||||||
|
|
||||||
crates=( "protocol" "core" "audio" "metadata" "playback" "connect" "librespot" )
|
crates=( "protocol" "core" "audio" "metadata" "playback" "connect" "librespot" )
|
||||||
|
|
||||||
function switchBranch {
|
function switchBranch {
|
||||||
# You are expected to have committed/stashed your changes before running this.
|
if [ "$SKIP_MERGE" = 'false' ] ; then
|
||||||
echo "Switching to master branch and merging development."
|
# You are expected to have committed/stashed your changes before running this.
|
||||||
git checkout master
|
echo "Switching to master branch and merging development."
|
||||||
git pull
|
git checkout master
|
||||||
git merge dev
|
git pull
|
||||||
|
if [ "$DRY_RUN" = 'true' ] ; then
|
||||||
|
git merge --no-commit --no-ff dev
|
||||||
|
else
|
||||||
|
git merge dev
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateVersion {
|
function updateVersion {
|
||||||
|
@ -26,15 +35,25 @@ function updateVersion {
|
||||||
echo "Path is $crate_path"
|
echo "Path is $crate_path"
|
||||||
if [ "$CRATE" = "librespot" ]
|
if [ "$CRATE" = "librespot" ]
|
||||||
then
|
then
|
||||||
cargo update
|
if [ "$DRY_RUN" = 'true' ] ; then
|
||||||
git add . && git commit -a -m "Update Cargo.lock"
|
cargo update --dry-run
|
||||||
|
git add . && git commit --dry-run -a -m "Update Cargo.lock"
|
||||||
|
else
|
||||||
|
cargo update
|
||||||
|
git add . && git commit -a -m "Update Cargo.lock"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
function commitAndTag {
|
function commitAndTag {
|
||||||
git commit -a -m "Update version numbers to $1"
|
if [ "$DRY_RUN" = 'true' ] ; then
|
||||||
git tag "v$1" -a -m "Update to version $1"
|
# Skip tagging on dry run.
|
||||||
|
git commit --dry-run -a -m "Update version numbers to $1"
|
||||||
|
else
|
||||||
|
git commit -a -m "Update version numbers to $1"
|
||||||
|
git tag "v$1" -a -m "Update to version $1"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_crate_name {
|
function get_crate_name {
|
||||||
|
@ -72,9 +91,17 @@ function publishCrates {
|
||||||
if [ "$CRATE" == "protocol" ]
|
if [ "$CRATE" == "protocol" ]
|
||||||
then
|
then
|
||||||
# Protocol crate needs --no-verify option due to build.rs modification.
|
# Protocol crate needs --no-verify option due to build.rs modification.
|
||||||
cargo publish --no-verify
|
if [ "$DRY_RUN" = 'true' ] ; then
|
||||||
|
cargo publish --no-verify --dry-run
|
||||||
|
else
|
||||||
|
cargo publish --no-verify
|
||||||
|
fi
|
||||||
else
|
else
|
||||||
cargo publish
|
if [ "$DRY_RUN" = 'true' ] ; then
|
||||||
|
cargo publish --dry-run
|
||||||
|
else
|
||||||
|
cargo publish
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
echo "Successfully published $crate_name to crates.io"
|
echo "Successfully published $crate_name to crates.io"
|
||||||
remoteWait 30 $crate_name
|
remoteWait 30 $crate_name
|
||||||
|
@ -83,10 +110,32 @@ function publishCrates {
|
||||||
|
|
||||||
function updateRepo {
|
function updateRepo {
|
||||||
cd $WORKINGDIR
|
cd $WORKINGDIR
|
||||||
echo "Pushing to master branch of repo."
|
if [ "$DRY_RUN" = 'true' ] ; then
|
||||||
git push origin master
|
echo "Pushing to master branch of repo. [DRY RUN]"
|
||||||
echo "Pushing v$1 tag to master branch of repo."
|
git push --dry-run origin master
|
||||||
git push origin v$1
|
echo "Pushing v$1 tag to master branch of repo. [DRY RUN]"
|
||||||
|
git push --dry-run origin v$1
|
||||||
|
|
||||||
|
# Cancels any merges in progress
|
||||||
|
git merge --abort
|
||||||
|
|
||||||
|
git checkout dev
|
||||||
|
git merge --no-commit --no-ff master
|
||||||
|
|
||||||
|
# Cancels above merge
|
||||||
|
git merge --abort
|
||||||
|
|
||||||
|
git push --dry-run
|
||||||
|
else
|
||||||
|
echo "Pushing to master branch of repo."
|
||||||
|
git push origin master
|
||||||
|
echo "Pushing v$1 tag to master branch of repo."
|
||||||
|
git push origin v$1
|
||||||
|
# Update the dev repo with latest version commit
|
||||||
|
git checkout dev
|
||||||
|
git merge master
|
||||||
|
git push
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
function rebaseDev {
|
function rebaseDev {
|
||||||
|
@ -105,5 +154,47 @@ function run {
|
||||||
echo "Successfully published v$1 to crates.io and uploaded changes to repo."
|
echo "Successfully published v$1 to crates.io and uploaded changes to repo."
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Set Script Name variable
|
||||||
|
SCRIPT=`basename ${BASH_SOURCE[0]}`
|
||||||
|
|
||||||
|
print_usage () {
|
||||||
|
local l_MSG=$1
|
||||||
|
if [ ! -z "${l_MSG}" ]; then
|
||||||
|
echo "Usage Error: $l_MSG"
|
||||||
|
fi
|
||||||
|
echo "Usage: $SCRIPT <args> <version>"
|
||||||
|
echo " where <version> specifies the version number in semver format, eg. 1.0.1"
|
||||||
|
echo "Recognized optional command line arguments"
|
||||||
|
echo "--dry-run -- Test the script before making live changes"
|
||||||
|
echo "--skip-merge -- Skip merging dev into master before publishing"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
### check number of command line arguments
|
||||||
|
NUMARGS=$#
|
||||||
|
if [ $NUMARGS -eq 0 ]; then
|
||||||
|
print_usage 'No command line arguments specified'
|
||||||
|
fi
|
||||||
|
|
||||||
|
while test $# -gt 0; do
|
||||||
|
case "$1" in
|
||||||
|
-h|--help)
|
||||||
|
print_usage
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
--dry-run)
|
||||||
|
DRY_RUN='true'
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
--skip-merge)
|
||||||
|
SKIP_MERGE='true'
|
||||||
|
shift
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
break
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
# First argument is new version number.
|
# First argument is new version number.
|
||||||
run $1
|
run $1
|
||||||
|
|
|
@ -203,6 +203,11 @@ fn setup(args: &[String]) -> Setup {
|
||||||
"",
|
"",
|
||||||
"disable-gapless",
|
"disable-gapless",
|
||||||
"disable gapless playback.",
|
"disable gapless playback.",
|
||||||
|
)
|
||||||
|
.optflag(
|
||||||
|
"",
|
||||||
|
"passthrough",
|
||||||
|
"Pass raw stream to output, only works for \"pipe\"."
|
||||||
);
|
);
|
||||||
|
|
||||||
let matches = match opts.parse(&args[1..]) {
|
let matches = match opts.parse(&args[1..]) {
|
||||||
|
@ -355,6 +360,8 @@ fn setup(args: &[String]) -> Setup {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let passthrough = matches.opt_present("passthrough");
|
||||||
|
|
||||||
let player_config = {
|
let player_config = {
|
||||||
let bitrate = matches
|
let bitrate = matches
|
||||||
.opt_str("b")
|
.opt_str("b")
|
||||||
|
@ -377,6 +384,7 @@ fn setup(args: &[String]) -> Setup {
|
||||||
.opt_str("normalisation-pregain")
|
.opt_str("normalisation-pregain")
|
||||||
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
|
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
|
||||||
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
.unwrap_or(PlayerConfig::default().normalisation_pregain),
|
||||||
|
passthrough,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue