Merge branch 'dev' into tokio_migration

This commit is contained in:
johannesd3 2021-02-23 14:45:01 +01:00 committed by Johannesd3
commit 678d1777fd
32 changed files with 674 additions and 360 deletions

3
.gitignore vendored
View file

@ -4,3 +4,6 @@ spotify_appkey.key
.vagrant/
.project
.history
*.save

315
Cargo.lock generated
View file

@ -27,44 +27,21 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561"
dependencies = [
"aes-soft 0.6.4",
"aesni 0.10.0",
"aes-soft",
"aesni",
"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]]
name = "aes-ctr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763"
dependencies = [
"aes-soft 0.6.4",
"aesni 0.10.0",
"aes-soft",
"aesni",
"cipher",
"ctr 0.6.0",
]
[[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",
"ctr",
]
[[package]]
@ -77,17 +54,6 @@ dependencies = [
"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]]
name = "aesni"
version = "0.10.0"
@ -189,16 +155,6 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "base64"
version = "0.13.0"
@ -251,29 +207,29 @@ version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
"block-padding",
"block-padding 0.1.5",
"byte-tools",
"byteorder",
"generic-array 0.12.3",
]
[[package]]
name = "block-cipher-trait"
version = "0.6.2"
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.12.3",
"generic-array 0.14.4",
]
[[package]]
name = "block-modes"
version = "0.3.3"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529"
checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0"
dependencies = [
"block-cipher-trait",
"block-padding",
"block-padding 0.2.1",
"cipher",
]
[[package]]
@ -285,6 +241,12 @@ dependencies = [
"byte-tools",
]
[[package]]
name = "block-padding"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
[[package]]
name = "bumpalo"
version = "3.6.1"
@ -372,22 +334,13 @@ dependencies = [
[[package]]
name = "clang-sys"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cb92721cb37482245ed88428f72253ce422b3b4ee169c70a0642521bb5db4cc"
checksum = "f54d78e30b388d4815220c8dd03fea5656b6c6d32adb59e89061552a102f8da1"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
"libloading 0.7.0",
]
[[package]]
@ -496,6 +449,12 @@ dependencies = [
"winapi",
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8aebca1129a03dc6dc2b127edd729435bbc4a37e1d5f4d7513165089ceb02634"
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -507,24 +466,14 @@ dependencies = [
[[package]]
name = "crypto-mac"
version = "0.7.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5"
checksum = "4857fd85a0c34b3c3297875b747c1e02e06b6a0ea32dd892d8192b9ce0813ea6"
dependencies = [
"generic-array 0.12.3",
"generic-array 0.14.4",
"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]]
name = "ctr"
version = "0.6.0"
@ -589,6 +538,15 @@ dependencies = [
"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]]
name = "discard"
version = "1.0.4"
@ -679,17 +637,11 @@ dependencies = [
"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]]
name = "futures"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150"
checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1"
dependencies = [
"futures-channel",
"futures-core",
@ -702,9 +654,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846"
checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939"
dependencies = [
"futures-core",
"futures-sink",
@ -712,15 +664,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65"
checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94"
[[package]]
name = "futures-executor"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9"
checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1"
dependencies = [
"futures-core",
"futures-task",
@ -729,15 +681,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500"
checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59"
[[package]]
name = "futures-macro"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd"
checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7"
dependencies = [
"proc-macro-hack",
"proc-macro2",
@ -747,24 +699,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6"
checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3"
[[package]]
name = "futures-task"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86"
dependencies = [
"once_cell",
]
checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80"
[[package]]
name = "futures-util"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b"
checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1"
dependencies = [
"futures-channel",
"futures-core",
@ -1054,12 +1003,12 @@ checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]]
name = "hmac"
version = "0.7.1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695"
checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15"
dependencies = [
"crypto-mac",
"digest",
"digest 0.9.0",
]
[[package]]
@ -1239,7 +1188,7 @@ checksum = "8e1d6ab7ada402b6a27912a2b86504be62a48c58313c886fe72a059127acb4d7"
dependencies = [
"lazy_static",
"libc",
"libloading",
"libloading 0.6.7",
]
[[package]]
@ -1342,6 +1291,16 @@ dependencies = [
"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]]
name = "libmdns"
version = "0.6.0"
@ -1410,9 +1369,9 @@ dependencies = [
[[package]]
name = "librespot"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"base64 0.13.0",
"base64",
"env_logger",
"futures",
"getopts",
@ -1429,16 +1388,16 @@ dependencies = [
"protobuf",
"rand 0.7.3",
"rpassword",
"sha-1",
"sha-1 0.8.2",
"tokio",
"url 1.7.2",
]
[[package]]
name = "librespot-audio"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"aes-ctr 0.6.0",
"aes-ctr",
"bit-set",
"byteorder",
"bytes",
@ -1449,6 +1408,7 @@ dependencies = [
"log",
"num-bigint",
"num-traits",
"ogg",
"pin-project-lite",
"tempfile",
"vorbis",
@ -1456,10 +1416,10 @@ dependencies = [
[[package]]
name = "librespot-connect"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"aes-ctr 0.3.0",
"base64 0.13.0",
"aes-ctr",
"base64",
"block-modes",
"dns-sd",
"futures",
@ -1476,17 +1436,17 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"sha-1",
"sha-1 0.9.4",
"tokio",
"url 1.7.2",
]
[[package]]
name = "librespot-core"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"aes",
"base64 0.13.0",
"base64",
"byteorder",
"bytes",
"env_logger",
@ -1508,7 +1468,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"sha-1",
"sha-1 0.9.4",
"shannon",
"tokio",
"tokio-util",
@ -1520,7 +1480,7 @@ dependencies = [
[[package]]
name = "librespot-metadata"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"async-trait",
"byteorder",
@ -1534,7 +1494,7 @@ dependencies = [
[[package]]
name = "librespot-playback"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"alsa",
"byteorder",
@ -1561,7 +1521,7 @@ dependencies = [
[[package]]
name = "librespot-protocol"
version = "0.1.3"
version = "0.1.6"
dependencies = [
"glob",
"protobuf",
@ -1644,9 +1604,9 @@ dependencies = [
[[package]]
name = "mio"
version = "0.7.8"
version = "0.7.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc250d6848c90d719ea2ce34546fb5df7af1d3fd189d10bf7bad80bfcebecd95"
checksum = "a5dede4e2065b3842b8b0af444119f3aa331cc7cc2dd20388bfb0f5d5a38823a"
dependencies = [
"libc",
"log",
@ -1892,9 +1852,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.5.2"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
checksum = "4ad167a2f54e832b82dbe003a046280dceffe5227b5f79e08e363a29638cfddd"
[[package]]
name = "opaque-debug"
@ -1941,17 +1901,12 @@ checksum = "c5d65c4d95931acda4498f675e332fcbdc9a06705cd07086c510e9b6009cd1c1"
[[package]]
name = "pbkdf2"
version = "0.3.0"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9"
checksum = "309c95c5f738c85920eb7062a2de29f3840d4f96974453fc9ac1ba078da9c627"
dependencies = [
"base64 0.9.3",
"byteorder",
"crypto-mac",
"hmac",
"rand 0.5.6",
"sha2",
"subtle",
]
[[package]]
@ -2168,19 +2123,6 @@ dependencies = [
"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]]
name = "rand"
version = "0.7.3"
@ -2226,21 +2168,6 @@ dependencies = [
"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]]
name = "rand_core"
version = "0.5.1"
@ -2389,7 +2316,7 @@ version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "064fd21ff87c6e87ed4506e68beb42459caa4a0e2eb144932e6776768556980b"
dependencies = [
"base64 0.13.0",
"base64",
"log",
"ring",
"sct",
@ -2402,12 +2329,6 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "same-file"
version = "1.0.6"
@ -2526,30 +2447,31 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"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]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "shannon"
version = "0.2.0"
@ -2673,15 +2595,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "strsim"
version = "0.9.3"
@ -2708,9 +2621,9 @@ dependencies = [
[[package]]
name = "subtle"
version = "1.0.0"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2"
[[package]]
name = "syn"
@ -3040,7 +2953,7 @@ version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "294b85ef5dbc3670a72e82a89971608a1fcc4ed5c7c5a2895230d31a95f0569b"
dependencies = [
"base64 0.13.0",
"base64",
"chunked_transfer",
"cookie",
"cookie_store",

View file

@ -1,6 +1,6 @@
[package]
name = "librespot"
version = "0.1.3"
version = "0.1.6"
authors = ["Librespot Org"]
license = "MIT"
description = "An open source client library for Spotify, with support for Spotify Connect"
@ -22,27 +22,27 @@ doc = false
[dependencies.librespot-audio]
path = "audio"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-connect]
path = "connect"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-core]
path = "core"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-metadata]
path = "metadata"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-playback]
path = "playback"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-protocol]
path = "protocol"
version = "0.1.3"
version = "0.1.6"
[dependencies]
base64 = "0.13"

View file

@ -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).
# 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.
# Building

View file

@ -1,6 +1,6 @@
[package]
name = "librespot-audio"
version = "0.1.3"
version = "0.1.6"
authors = ["Paul Lietar <paul@lietar.net>"]
description="The audio fetching and processing logic for librespot"
license="MIT"
@ -8,7 +8,7 @@ edition = "2018"
[dependencies.librespot-core]
path = "../core"
version = "0.1.3"
version = "0.1.6"
[dependencies]
aes-ctr = "0.6"
@ -17,6 +17,7 @@ byteorder = "1.4"
bytes = "1.0"
futures = "0.3"
lewton = "0.10"
ogg = "0.8"
log = "0.4"
num-bigint = "0.3"
num-traits = "0.2"

View file

@ -2,12 +2,12 @@ extern crate lewton;
use self::lewton::inside_ogg::OggStreamReader;
use super::{AudioDecoder, AudioError, AudioPacket};
use std::error;
use std::fmt;
use std::io::{Read, Seek};
pub struct VorbisDecoder<R: Read + Seek>(OggStreamReader<R>);
pub struct VorbisPacket(Vec<i16>);
pub struct VorbisError(lewton::VorbisError);
impl<R> VorbisDecoder<R>
@ -17,41 +17,38 @@ where
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
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;
self.0.seek_absgp_pg(absgp as u64)?;
Ok(())
match self.0.seek_absgp_pg(absgp as u64) {
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::OggReadError::NoCapturePatternFound;
use self::lewton::VorbisError::BadAudio;
use self::lewton::VorbisError::OggError;
loop {
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),
Err(BadAudio(AudioIsHeader)) => (),
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 {
fn from(err: lewton::VorbisError) -> VorbisError {
VorbisError(err)

View file

@ -23,6 +23,7 @@ mod fetch;
mod lewton_decoder;
#[cfg(any(feature = "with-tremor", feature = "with-vorbis"))]
mod libvorbis_decoder;
mod passthrough_decoder;
mod range_set;
@ -32,8 +33,70 @@ pub use fetch::{
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_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")))]
pub use crate::lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket};
pub use crate::lewton_decoder::{VorbisDecoder, VorbisError};
#[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>;
}

View file

@ -3,12 +3,12 @@ extern crate librespot_tremor as vorbis;
#[cfg(not(feature = "with-tremor"))]
extern crate vorbis;
use super::{AudioDecoder, AudioError, AudioPacket};
use std::error;
use std::fmt;
use std::io::{Read, Seek};
pub struct VorbisDecoder<R: Read + Seek>(vorbis::Decoder<R>);
pub struct VorbisPacket(vorbis::Packet);
pub struct VorbisError(vorbis::VorbisError);
impl<R> VorbisDecoder<R>
@ -18,23 +18,28 @@ where
pub fn new(input: R) -> Result<VorbisDecoder<R>, VorbisError> {
Ok(VorbisDecoder(vorbis::Decoder::new(input)?))
}
}
impl<R> AudioDecoder for VorbisDecoder<R>
where
R: Read + Seek,
{
#[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)?;
Ok(())
}
#[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)?;
Ok(())
}
pub fn next_packet(&mut self) -> Result<Option<VorbisPacket>, VorbisError> {
fn next_packet(&mut self) -> Result<Option<AudioPacket>, AudioError> {
loop {
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),
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 {
fn from(err: vorbis::VorbisError) -> VorbisError {
VorbisError(err)
@ -77,3 +72,9 @@ impl error::Error for VorbisError {
error::Error::source(&self.0)
}
}
impl From<vorbis::VorbisError> for AudioError {
fn from(err: vorbis::VorbisError) -> AudioError {
AudioError::VorbisError(VorbisError(err))
}
}

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

View file

@ -1,6 +1,6 @@
[package]
name = "librespot-connect"
version = "0.1.3"
version = "0.1.6"
authors = ["Paul Lietar <paul@lietar.net>"]
description = "The discovery and Spotify Connect logic for librespot"
license = "MIT"
@ -9,17 +9,20 @@ edition = "2018"
[dependencies.librespot-core]
path = "../core"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-playback]
path = "../playback"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-protocol]
path = "../protocol"
version = "0.1.3"
version = "0.1.6"
[dependencies]
aes-ctr = "0.6"
base64 = "0.13"
block-modes = "0.7"
futures = "0.3"
hmac = "0.10"
hyper = { version = "0.14", features = ["server", "http1"] }
log = "0.4"
num-bigint = "0.3"
@ -28,12 +31,9 @@ rand = "0.7"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
sha-1 = "0.9"
tokio = { version = "1.0", features = ["macros"] }
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 }
libmdns = { version = "0.6", optional = true }

View file

@ -1,10 +1,10 @@
use aes_ctr::stream_cipher::generic_array::GenericArray;
use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher};
use aes_ctr::cipher::generic_array::GenericArray;
use aes_ctr::cipher::{NewStreamCipher, SyncStreamCipher};
use aes_ctr::Aes128Ctr;
use base64;
use futures::channel::{mpsc, oneshot};
use futures::{Stream, StreamExt};
use hmac::{Hmac, Mac};
use hmac::{Hmac, Mac, NewMac};
use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, Request, Response, StatusCode};
use sha1::{Digest, Sha1};
@ -118,18 +118,18 @@ impl Discovery {
let checksum_key = {
let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size");
h.input(b"checksum");
h.result().code()
h.update(b"checksum");
h.finalize().into_bytes()
};
let encryption_key = {
let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size");
h.input(b"encryption");
h.result().code()
h.update(b"encryption");
h.finalize().into_bytes()
};
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) {
warn!("Login error for user {:?}: MAC mismatch", username);
let result = json!({

View file

@ -790,7 +790,7 @@ impl SpircTask {
self.handle_play()
}
SpircPlayStatus::Playing { .. } | SpircPlayStatus::LoadingPlay { .. } => {
self.handle_play()
self.handle_pause()
}
_ => (),
}

View file

@ -1,6 +1,6 @@
[package]
name = "librespot-core"
version = "0.1.3"
version = "0.1.6"
authors = ["Paul Lietar <paul@lietar.net>"]
build = "build.rs"
description = "The core functionality provided by librespot"
@ -10,7 +10,7 @@ edition = "2018"
[dependencies.librespot-protocol]
path = "../protocol"
version = "0.1.3"
version = "0.1.6"
[dependencies]
aes = "0.6"
@ -19,7 +19,7 @@ byteorder = "1.4"
bytes = "1.0"
error-chain = { version = "0.12", default-features = false }
futures = { version = "0.3", features = ["bilock", "unstable"] }
hmac = "0.7"
hmac = "0.10"
httparse = "1.3"
hyper = { version = "0.14", features = ["client", "tcp", "http1", "http2"] }
log = "0.4"
@ -27,14 +27,14 @@ num-bigint = "0.3"
num-integer = "0.1"
num-traits = "0.2"
once_cell = "1.5.2"
pbkdf2 = "0.3"
pbkdf2 = { version = "0.7", default_features = false, features = ["hmac"] }
pin-project-lite = "0.2.4"
protobuf = "~2.14.0"
rand = "0.7"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
sha-1 = "~0.8"
sha-1 = "0.9"
shannon = "0.2.0"
tokio = { version = "1.0", features = ["io-util", "rt-multi-thread"] }
tokio-util = { version = "0.6", features = ["codec"] }

View file

@ -1,5 +1,4 @@
use aes::Aes192;
use aes::NewBlockCipher;
use byteorder::{BigEndian, ByteOrder};
use hmac::Hmac;
use pbkdf2::pbkdf2;
@ -74,7 +73,7 @@ impl Credentials {
let blob = {
use aes::cipher::generic_array::typenum::Unsigned;
use aes::cipher::generic_array::GenericArray;
use aes::cipher::BlockCipher;
use aes::cipher::{BlockCipher, NewBlockCipher};
let mut data = base64::decode(encrypted_blob).unwrap();
let cipher = Aes192::new(GenericArray::from_slice(&key));

View file

@ -1,5 +1,5 @@
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use hmac::{Hmac, Mac};
use hmac::{Hmac, Mac, NewMac};
use protobuf::{self, Message};
use rand::thread_rng;
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);
for i in 1..6 {
let mut mac = HmacSha1::new_varkey(&shared_secret).expect("HMAC can take key of any size");
mac.input(packets);
mac.input(&[i]);
data.extend_from_slice(&mac.result().code());
mac.update(packets);
mac.update(&[i]);
data.extend_from_slice(&mac.finalize().into_bytes());
}
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[0x34..0x54].to_vec(),
)

View file

@ -1,6 +1,6 @@
[package]
name = "librespot-metadata"
version = "0.1.3"
version = "0.1.6"
authors = ["Paul Lietar <paul@lietar.net>"]
description = "The metadata logic for librespot"
license = "MIT"
@ -17,7 +17,7 @@ log = "0.4"
[dependencies.librespot-core]
path = "../core"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-protocol]
path = "../protocol"
version = "0.1.3"
version = "0.1.6"

View file

@ -1,6 +1,6 @@
[package]
name = "librespot-playback"
version = "0.1.3"
version = "0.1.6"
authors = ["Sasha Hilton <sashahilton00@gmail.com>"]
description = "The audio playback logic for librespot"
license = "MIT"
@ -9,13 +9,13 @@ edition = "2018"
[dependencies.librespot-audio]
path = "../audio"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-core]
path = "../core"
version = "0.1.3"
version = "0.1.6"
[dependencies.librespot-metadata]
path = "../metadata"
version = "0.1.3"
version = "0.1.6"
[dependencies]
futures = "0.3"

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use alsa::device_name::HintIter;
use alsa::pcm::{Access, Format, Frames, HwParams, PCM};
use alsa::{Direction, Error, ValueOr};
@ -124,8 +125,9 @@ impl Sink for AlsaSink {
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
let mut processed_data = 0;
let data = packet.samples();
while processed_data < data.len() {
let data_to_buffer = min(
self.buffer.capacity() - self.buffer.len(),

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use gst::prelude::*;
use gst::*;
use std::sync::mpsc::{sync_channel, SyncSender};
@ -104,9 +105,9 @@ impl Sink for GstreamerSink {
fn stop(&mut self) -> io::Result<()> {
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
let deighta: &[u8] = data.as_bytes();
let deighta: &[u8] = packet.samples().as_bytes();
self.tx
.send(deighta.to_vec())
.expect("tx send failed in write function");

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use jack::{
AsyncClient, AudioOut, Client, ClientOptions, Control, Port, ProcessHandler, ProcessScope,
};
@ -73,8 +74,8 @@ impl Sink for JackSink {
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
for s in data.iter() {
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
for s in packet.samples().iter() {
let res = self.send.send(*s);
if res.is_err() {
error!("jackaudio: cannot write to channel");

View file

@ -1,3 +1,4 @@
use crate::audio::AudioPacket;
use std::io;
pub trait Open {
@ -7,7 +8,7 @@ pub trait Open {
pub trait Sink {
fn start(&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>;

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use std::fs::OpenOptions;
use std::io::{self, Write};
use std::mem;
@ -26,12 +27,15 @@ impl Sink for StdoutSink {
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
let data: &[u8] = unsafe {
slice::from_raw_parts(
data.as_ptr() as *const u8,
data.len() * mem::size_of::<i16>(),
)
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
let data: &[u8] = match packet {
AudioPacket::Samples(data) => unsafe {
slice::from_raw_parts(
data.as_ptr() as *const u8,
data.len() * mem::size_of::<i16>(),
)
},
AudioPacket::OggData(data) => data,
};
self.0.write_all(data)?;

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use portaudio_rs;
use portaudio_rs::device::{get_default_output_index, DeviceIndex, DeviceInfo};
use portaudio_rs::stream::*;
@ -95,8 +96,8 @@ impl<'a> Sink for PortAudioSink<'a> {
self.0 = None;
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
match self.0.as_mut().unwrap().write(data) {
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
match self.0.as_mut().unwrap().write(packet.samples()) {
Ok(_) => (),
Err(portaudio_rs::PaError::OutputUnderflowed) => error!("PortAudio write underflow"),
Err(e) => panic!("PA Error {}", e),

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use libpulse_binding::{self as pulse, stream::Direction};
use libpulse_simple_binding::Simple;
use std::io;
@ -65,13 +66,17 @@ impl Sink for PulseAudioSink {
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
if let Some(s) = &self.s {
// 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) };
let d: &[u8] = unsafe {
std::slice::from_raw_parts(
packet.samples().as_ptr() as *const u8,
packet.samples().len() * 2,
)
};
match s.write(d) {
Ok(_) => Ok(()),

View file

@ -6,6 +6,7 @@ use cpal::traits::{DeviceTrait, HostTrait};
use thiserror::Error;
use super::{Open, Sink};
use crate::audio::AudioPacket;
#[derive(Debug, Error)]
pub enum RodioError {
@ -178,8 +179,8 @@ impl Sink for RodioSink {
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
let source = rodio::buffer::SamplesBuffer::new(2, 44100, data);
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
let source = rodio::buffer::SamplesBuffer::new(2, 44100, packet.samples());
self.rodio_sink.append(source);
// Chunk sizes seem to be about 256 to 3000 ish items long.

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use sdl2::audio::{AudioQueue, AudioSpecDesired};
use std::{io, thread, time};
@ -45,12 +46,12 @@ impl Sink for SdlSink {
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) {
// sleep and wait for sdl thread to drain the queue a bit
thread::sleep(time::Duration::from_millis(10));
}
self.queue.queue(data);
self.queue.queue(packet.samples());
Ok(())
}
}

View file

@ -1,4 +1,5 @@
use super::{Open, Sink};
use crate::audio::AudioPacket;
use shell_words::split;
use std::io::{self, Write};
use std::mem;
@ -43,11 +44,11 @@ impl Sink for SubprocessSink {
Ok(())
}
fn write(&mut self, data: &[i16]) -> io::Result<()> {
fn write(&mut self, packet: &AudioPacket) -> io::Result<()> {
let data: &[u8] = unsafe {
slice::from_raw_parts(
data.as_ptr() as *const u8,
data.len() * mem::size_of::<i16>(),
packet.samples().as_ptr() as *const u8,
packet.samples().len() * mem::size_of::<i16>(),
)
};
if let Some(child) = &mut self.child {

View file

@ -55,6 +55,7 @@ pub struct PlayerConfig {
pub normalisation_type: NormalisationType,
pub normalisation_pregain: f32,
pub gapless: bool,
pub passthrough: bool,
}
impl Default for PlayerConfig {
@ -65,6 +66,7 @@ impl Default for PlayerConfig {
normalisation_type: NormalisationType::default(),
normalisation_pregain: 0.0,
gapless: true,
passthrough: false,
}
}
}

View file

@ -1,5 +1,5 @@
use crate::audio::{AudioDecoder, AudioError, AudioPacket, PassthroughDecoder, VorbisDecoder};
use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController};
use crate::audio::{VorbisDecoder, VorbisPacket};
use crate::audio::{
READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS,
READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS,
@ -377,7 +377,7 @@ enum PlayerPreload {
},
}
type Decoder = VorbisDecoder<Subfile<AudioDecrypt<AudioFile>>>;
type Decoder = Box<dyn AudioDecoder + Send>;
enum PlayerState {
Stopped,
@ -730,7 +730,19 @@ impl PlayerTrackLoader {
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,
Err(e) if is_cached => {
warn!(
@ -779,6 +791,7 @@ impl Future for PlayerInternal {
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// While this is written as a future, it still contains blocking code.
// It must be run on its own thread.
let passthrough = self.config.passthrough;
loop {
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");
if let Some(ref packet) = packet {
*stream_position_pcm += (packet.data().len() / 2) as u64;
let stream_position_millis = Self::position_pcm_to_ms(*stream_position_pcm);
if !passthrough {
if let Some(ref packet) = packet {
*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 {
None => true,
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.
let lag = (Instant::now() - reported_nominal_start_time).as_millis()
as i64
- stream_position_millis as i64;
lag > 1000
let notify_about_position = match *reported_nominal_start_time {
None => true,
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.
let lag = (Instant::now() - reported_nominal_start_time)
.as_millis()
as i64
- 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);
@ -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 {
Some(mut packet) => {
if !packet.data().is_empty() {
if let Some(ref editor) = self.audio_filter {
editor.modify_stream(&mut packet.data_mut())
};
if !packet.is_empty() {
if let AudioPacket::Samples(ref mut data) = packet {
if let Some(ref editor) = self.audio_filter {
editor.modify_stream(data)
}
if self.config.normalisation
&& (normalisation_factor - 1.0).abs() < f32::EPSILON
{
for x in packet.data_mut().iter_mut() {
*x = (*x as f32 * normalisation_factor) as i16;
if self.config.normalisation && normalisation_factor != 1.0 {
for x in data.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);
self.ensure_sink_stopped(false);
}

View file

@ -1,6 +1,6 @@
[package]
name = "librespot-protocol"
version = "0.1.3"
version = "0.1.6"
authors = ["Paul Liétar <paul@lietar.net>"]
build = "build.rs"
description = "The protobuf logic for communicating with Spotify servers"

View file

@ -1,16 +1,25 @@
#!/bin/bash
SKIP_MERGE='false'
DRY_RUN='false'
WORKINGDIR="$( cd "$(dirname "$0")" ; pwd -P )"
cd $WORKINGDIR
crates=( "protocol" "core" "audio" "metadata" "playback" "connect" "librespot" )
function switchBranch {
# You are expected to have committed/stashed your changes before running this.
echo "Switching to master branch and merging development."
git checkout master
git pull
git merge dev
if [ "$SKIP_MERGE" = 'false' ] ; then
# You are expected to have committed/stashed your changes before running this.
echo "Switching to master branch and merging development."
git checkout master
git pull
if [ "$DRY_RUN" = 'true' ] ; then
git merge --no-commit --no-ff dev
else
git merge dev
fi
fi
}
function updateVersion {
@ -26,15 +35,25 @@ function updateVersion {
echo "Path is $crate_path"
if [ "$CRATE" = "librespot" ]
then
cargo update
git add . && git commit -a -m "Update Cargo.lock"
if [ "$DRY_RUN" = 'true' ] ; then
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
done
}
function commitAndTag {
git commit -a -m "Update version numbers to $1"
git tag "v$1" -a -m "Update to version $1"
if [ "$DRY_RUN" = 'true' ] ; then
# 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 {
@ -72,9 +91,17 @@ function publishCrates {
if [ "$CRATE" == "protocol" ]
then
# 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
cargo publish
if [ "$DRY_RUN" = 'true' ] ; then
cargo publish --dry-run
else
cargo publish
fi
fi
echo "Successfully published $crate_name to crates.io"
remoteWait 30 $crate_name
@ -83,10 +110,32 @@ function publishCrates {
function updateRepo {
cd $WORKINGDIR
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
if [ "$DRY_RUN" = 'true' ] ; then
echo "Pushing to master branch of repo. [DRY RUN]"
git push --dry-run origin master
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 {
@ -105,5 +154,47 @@ function run {
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.
run $1

View file

@ -203,6 +203,11 @@ fn setup(args: &[String]) -> Setup {
"",
"disable-gapless",
"disable gapless playback.",
)
.optflag(
"",
"passthrough",
"Pass raw stream to output, only works for \"pipe\"."
);
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 bitrate = matches
.opt_str("b")
@ -377,6 +384,7 @@ fn setup(args: &[String]) -> Setup {
.opt_str("normalisation-pregain")
.map(|pregain| pregain.parse::<f32>().expect("Invalid pregain float value"))
.unwrap_or(PlayerConfig::default().normalisation_pregain),
passthrough,
}
};