diff --git a/Cargo.lock b/Cargo.lock index abf4753..d7031ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,28 @@ dependencies = [ "memchr", ] +[[package]] +name = "alsa" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8512c9117059663fb5606788fbca3619e2a91dac0e3fe516242eab1fa6be5e44" +dependencies = [ + "alsa-sys", + "bitflags", + "libc", + "nix 0.24.3", +] + +[[package]] +name = "alsa-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -26,6 +48,12 @@ version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-broadcast" version = "0.5.1" @@ -142,6 +170,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79d6dc922a2792b006573f60b2648076355daeae5ce9cb59507e5908c9625d31" +[[package]] +name = "audio-player" +version = "0.1.0" +dependencies = [ + "anyhow", + "rodio", + "stream-download", + "symphonia", + "url", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -199,6 +238,26 @@ version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f1e31e207a6b8fb791a38ea3105e6cb541f55e4d029902d3039a4ad07cc4105" +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -241,6 +300,12 @@ version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + [[package]] name = "byteorder" version = "1.4.3" @@ -278,6 +343,24 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-expr" @@ -310,6 +393,27 @@ dependencies = [ "winapi", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.2.0" @@ -344,12 +448,73 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys 0.8.4", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + [[package]] name = "core-foundation-sys" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +[[package]] +name = "coreaudio-rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" +dependencies = [ + "bitflags", + "core-foundation-sys 0.6.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f034b2258e6c4ade2f73bf87b21047567fb913ee9550837c2316d139b0262b24" +dependencies = [ + "bindgen", +] + +[[package]] +name = "cpal" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.4", + "coreaudio-rs", + "dasp_sample", + "jni 0.19.0", + "js-sys", + "libc", + "mach2", + "ndk", + "ndk-context", + "oboe", + "once_cell", + "parking_lot", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.46.0", +] + [[package]] name = "cpufeatures" version = "0.2.7" @@ -450,6 +615,12 @@ dependencies = [ "typenum", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "data-encoding" version = "2.4.0" @@ -629,10 +800,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "form_urlencoded" -version = "1.1.0" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] @@ -825,6 +1011,12 @@ dependencies = [ "system-deps", ] +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + [[package]] name = "gobject-sys" version = "0.17.4" @@ -1116,6 +1308,19 @@ dependencies = [ "tokio-io-timeout", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "iana-time-zone" version = "0.1.56" @@ -1123,7 +1328,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", - "core-foundation-sys", + "core-foundation-sys 0.8.4", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", @@ -1152,9 +1357,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -1223,6 +1428,49 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.63" @@ -1249,12 +1497,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -1305,6 +1569,15 @@ dependencies = [ "time 0.3.21", ] +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1353,6 +1626,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "mio" version = "0.8.6" @@ -1386,6 +1665,64 @@ dependencies = [ "getrandom", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "nix" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" +dependencies = [ + "bitflags", + "cfg-if", + "libc", +] + [[package]] name = "nix" version = "0.26.2" @@ -1399,6 +1736,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "notify-rust" version = "4.8.0" @@ -1422,6 +1769,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1462,6 +1820,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "objc" version = "0.2.7" @@ -1491,12 +1870,79 @@ dependencies = [ "objc", ] +[[package]] +name = "oboe" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0" +dependencies = [ + "jni 0.20.0", + "ndk", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +[[package]] +name = "openssl" +version = "0.10.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b3f656a17a6cbc115b5c7a40c616947d213ba182135b014d6051b73ab6f019" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.16", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2ce0f250f34a308dcfdbb351f511359857d4ed2134ba715a4eadd46e1ffd617" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -1564,10 +2010,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "peeking_take_while" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" @@ -1850,6 +2302,12 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rangemap" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9283c6b06096b47afc7109834fdedab891175bb5241ee5d4f7d2546549f263" + [[package]] name = "ratatui" version = "0.20.1" @@ -1863,6 +2321,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "raw-window-handle" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + [[package]] name = "redox_syscall" version = "0.2.16" @@ -1925,10 +2389,12 @@ dependencies = [ "http-body", "hyper", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -1938,12 +2404,15 @@ dependencies = [ "serde_json", "serde_urlencoded", "tokio", + "tokio-native-tls", "tokio-rustls", + "tokio-util", "tower-service", "trust-dns-resolver", "url", "wasm-bindgen", "wasm-bindgen-futures", + "wasm-streams", "web-sys", "webpki-roots", "winreg", @@ -1974,6 +2443,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "rodio" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdf1d4dea18dff2e9eb6dca123724f8b60ef44ad74a9ad283cdfe025df7e73fa" +dependencies = [ + "cpal", + "symphonia", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustix" version = "0.37.19" @@ -2031,6 +2516,24 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -2057,6 +2560,29 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys 0.8.4", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" +dependencies = [ + "core-foundation-sys 0.8.4", + "libc", +] + [[package]] name = "serde" version = "1.0.163" @@ -2164,6 +2690,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" + [[package]] name = "signal-hook" version = "0.3.15" @@ -2240,6 +2772,23 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stream-download" +version = "0.1.0" +source = "git+https://github.com/aschey/stream-download-rs.git#f453f227d81ebee2c19001c32e8ee505dd354131" +dependencies = [ + "async-trait", + "bytes", + "futures", + "futures-util", + "parking_lot", + "rangemap", + "reqwest", + "tempfile", + "tokio", + "tracing", +] + [[package]] name = "strum" version = "0.22.0" @@ -2261,6 +2810,189 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "symphonia" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-wav", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f23b0482a7cb18fcdf9981ab0b78df800ef0080187d294650023c462439058d" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a" +dependencies = [ + "bitflags", + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bdd75b25ce4b84b12a4bd20bfea2460c2dbd7fc1d227ef5533504d3168109d" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870e7dc1865d818c7b6318879d060553a73a3b2a3b8443dff90910f10ac41150" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a27e8763d1c9eff666faf903e73a99d4de2f7a93fca4e3c214c1d68432903b9" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f1fbd220a06a641c8ce2ddad10f5ef6ee5cc0c54d9044d25d43b0d3119deaa" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3953397e3506aa01350c4205817e4f95b58d476877a42f0458d07b665749e203" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" +dependencies = [ + "arrayvec", + "bitflags", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf14bae5cf352032416bc64151e5d6242d29d33cbf3238513b44d4427a1efb" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c61dfc851ad25d4043d8c231d8617e8f7cd02a6cc0edad21ade21848d58895" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf1a00ccd11452d44048a0368828040f778ae650418dbd9d8765b7ee2574c8d" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-wav" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da76614728fa27c003bdcdfbac51396bd8fcbf94c95fe8e62f1d2bac58ef03a4" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c3e1937e31d0e068bbe829f66b2f2bfaa28d056365279e0ef897172c3320c0" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a450ca645b80d69aff8b35576cbfdc7f20940b29998202aab910045714c951f8" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" @@ -2475,6 +3207,16 @@ dependencies = [ "syn 2.0.16", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.0" @@ -2816,12 +3558,12 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna 0.4.0", "percent-encoding", ] @@ -2831,6 +3573,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.1.1" @@ -2849,6 +3597,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -2937,6 +3695,19 @@ version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" +[[package]] +name = "wasm-streams" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.63" @@ -2999,6 +3770,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3018,6 +3798,15 @@ dependencies = [ "windows_x86_64_msvc 0.39.0", ] +[[package]] +name = "windows" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows" version = "0.48.0" @@ -3027,6 +3816,21 @@ dependencies = [ "windows-targets 0.48.0", ] +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -3213,7 +4017,7 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2769203cd13a0c6015d515be729c526d041e9cf2c0cc478d57faee85f40c6dcd" dependencies = [ - "nix", + "nix 0.26.2", "winapi", ] @@ -3239,7 +4043,7 @@ dependencies = [ "futures-sink", "futures-util", "hex", - "nix", + "nix 0.26.2", "once_cell", "ordered-stream", "rand", diff --git a/Cargo.toml b/Cargo.toml index 8541e75..c299094 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,8 @@ [workspace] -members = ["cbd-tui", "crabidy-core", "crabidy-server", "tidaldy"] +members = [ + "audio-player", + "cbd-tui", + "crabidy-core", + "crabidy-server", + "tidaldy", +] diff --git a/audio-player/Cargo.toml b/audio-player/Cargo.toml new file mode 100644 index 0000000..107b265 --- /dev/null +++ b/audio-player/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "audio-player" +version = "0.1.0" +edition = "2021" + +[dependencies] +rodio = { version = "0.17.1", default-features = false, features = ["symphonia-all"] } +symphonia = { version = "0.5.3", features = ["all"] } +stream-download = { git = "https://github.com/aschey/stream-download-rs.git" } +anyhow = "1.0.71" +url = "2.4.0" diff --git a/audio-player/src/decoder.rs b/audio-player/src/decoder.rs new file mode 100644 index 0000000..3249a46 --- /dev/null +++ b/audio-player/src/decoder.rs @@ -0,0 +1,302 @@ +use std::error::Error; +use std::fmt; +use std::time::Duration; +use symphonia::{ + core::{ + audio::{AudioBufferRef, SampleBuffer, SignalSpec}, + codecs::{Decoder, DecoderOptions}, + errors::Error as SymphoniaError, + formats::{FormatOptions, FormatReader, SeekMode, SeekTo, Track}, + io::MediaSourceStream, + meta::{MetadataOptions, MetadataRevision}, + probe::Hint, + units::{self, Time, TimeBase}, + }, + default::get_probe, +}; + +use rodio::Source; + +// Decoder errors are not considered fatal. +// The correct action is to just get a new packet and try again. +// But a decode error in more than 3 consecutive packets is fatal. +const MAX_DECODE_ERRORS: usize = 3; + +pub struct MediaInfo { + pub duration: Option, + pub metadata: Option, + pub track: Track, +} + +pub struct SymphoniaDecoder { + decoder: Box, + current_frame_offset: usize, + format: Box, + buffer: SampleBuffer, + spec: SignalSpec, + time_base: Option, + duration: Option, + elapsed: u64, + metadata: Option, + track: Track, +} + +impl SymphoniaDecoder { + pub fn new(mss: MediaSourceStream, hint: Hint) -> Result { + match SymphoniaDecoder::init(mss, hint) { + Err(e) => match e { + SymphoniaError::IoError(e) => Err(DecoderError::IoError(e.to_string())), + SymphoniaError::DecodeError(e) => Err(DecoderError::DecodeError(e)), + SymphoniaError::SeekError(_) => { + unreachable!("Seek errors should not occur during initialization") + } + SymphoniaError::Unsupported(_) => Err(DecoderError::UnrecognizedFormat), + SymphoniaError::LimitError(e) => Err(DecoderError::LimitError(e)), + SymphoniaError::ResetRequired => Err(DecoderError::ResetRequired), + }, + Ok(Some(decoder)) => Ok(decoder), + Ok(None) => Err(DecoderError::NoStreams), + } + } + + pub fn into_inner(self) -> MediaSourceStream { + self.format.into_inner() + } + + fn init( + mss: MediaSourceStream, + hint: Hint, + ) -> symphonia::core::errors::Result> { + let format_opts: FormatOptions = FormatOptions { + enable_gapless: true, + ..Default::default() + }; + let metadata_opts: MetadataOptions = Default::default(); + let mut probed = get_probe().format(&hint, mss, &format_opts, &metadata_opts)?; + + let track = match probed.format.default_track() { + Some(stream) => stream, + None => return Ok(None), + } + .clone(); + + let time_base = track.codec_params.time_base; + + let dur = track + .codec_params + .n_frames + .map(|frames| track.codec_params.start_ts + frames) + .unwrap_or_default(); + + let duration = match time_base { + Some(tb) => { + let time = tb.calc_time(dur); + Some(Duration::from_secs_f64(time.seconds as f64 + time.frac)) + } + None => None, + }; + + let mut elapsed = 0; + + let mut decoder = symphonia::default::get_codecs() + .make(&track.codec_params, &DecoderOptions { verify: true })?; + + let mut decode_errors: usize = 0; + let decoded = loop { + let current_frame = probed.format.next_packet()?; + elapsed = current_frame.ts(); + match decoder.decode(¤t_frame) { + Ok(decoded) => break decoded, + Err(e) => match e { + SymphoniaError::DecodeError(_) => { + decode_errors += 1; + if decode_errors > MAX_DECODE_ERRORS { + return Err(e); + } else { + continue; + } + } + _ => return Err(e), + }, + } + }; + let spec = decoded.spec().to_owned(); + let buffer = SymphoniaDecoder::get_buffer(decoded, &spec); + + // Prefer metadata that's provided in the container format, over other tags found during the + // probe operation. + let metadata = probed.format.metadata().current().cloned().or_else(|| { + probed + .metadata + .get() + .as_ref() + .and_then(|m| m.current().cloned()) + }); + + Ok(Some(SymphoniaDecoder { + decoder, + current_frame_offset: 0, + format: probed.format, + buffer, + spec, + time_base, + duration, + elapsed, + metadata, + track, + })) + } + + #[inline] + pub fn media_info(&self) -> MediaInfo { + MediaInfo { + duration: self.duration, + metadata: self.metadata.clone(), + track: self.track.clone(), + } + } + + #[inline] + pub fn elapsed(&self) -> Duration { + if let Some(tb) = self.time_base { + let time = tb.calc_time(self.elapsed); + return Duration::from_secs_f64(time.seconds as f64 + time.frac); + }; + Duration::default() + } + + #[inline] + pub fn seek(&mut self, time: Duration) -> Option { + let nanos_per_sec = 1_000_000_000.0; + match self.format.seek( + SeekMode::Coarse, + SeekTo::Time { + time: Time::new( + time.as_secs(), + f64::from(time.subsec_nanos()) / nanos_per_sec, + ), + track_id: None, + }, + ) { + Ok(seeked_to) => { + let base = TimeBase::new(1, self.sample_rate()); + let time = base.calc_time(seeked_to.actual_ts); + + Some(Duration::from_millis( + time.seconds * 1000 + ((time.frac * 60. * 1000.).round() as u64), + )) + } + Err(_) => None, + } + } + + #[inline] + fn get_buffer(decoded: AudioBufferRef, spec: &SignalSpec) -> SampleBuffer { + let duration = decoded.capacity() as u64; + let mut buffer = SampleBuffer::::new(duration, *spec); + buffer.copy_interleaved_ref(decoded); + buffer + } +} + +impl Source for SymphoniaDecoder { + #[inline] + fn current_frame_len(&self) -> Option { + Some(self.buffer.samples().len()) + } + + #[inline] + fn channels(&self) -> u16 { + self.spec.channels.count() as u16 + } + + #[inline] + fn sample_rate(&self) -> u32 { + self.spec.rate + } + + #[inline] + fn total_duration(&self) -> Option { + self.duration + } +} + +impl Iterator for SymphoniaDecoder { + type Item = i16; + + #[inline] + fn next(&mut self) -> Option { + if self.current_frame_offset == self.buffer.len() { + let mut decode_errors: usize = 0; + let decoded = loop { + match self.format.next_packet() { + Ok(packet) => { + self.elapsed = packet.ts(); + match self.decoder.decode(&packet) { + Ok(decoded) => break decoded, + Err(e) => match e { + SymphoniaError::DecodeError(_) => { + decode_errors += 1; + if decode_errors > MAX_DECODE_ERRORS { + return None; + } else { + continue; + } + } + _ => return None, + }, + } + } + Err(_) => return None, + } + }; + self.spec = decoded.spec().to_owned(); + self.buffer = SymphoniaDecoder::get_buffer(decoded, &self.spec); + self.current_frame_offset = 0; + } + + let sample = *self.buffer.samples().get(self.current_frame_offset)?; + self.current_frame_offset += 1; + + Some(sample) + } +} + +/// Error that can happen when creating a decoder. +#[derive(Debug, Clone)] +pub enum DecoderError { + /// The format of the data has not been recognized. + UnrecognizedFormat, + + /// An IO error occurred while reading, writing, or seeking the stream. + IoError(String), + + /// The stream contained malformed data and could not be decoded or demuxed. + DecodeError(&'static str), + + /// A default or user-defined limit was reached while decoding or demuxing the stream. Limits + /// are used to prevent denial-of-service attacks from malicious streams. + LimitError(&'static str), + + /// The demuxer or decoder needs to be reset before continuing. + ResetRequired, + + /// No streams were found by the decoder + NoStreams, +} + +impl fmt::Display for DecoderError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let text = match self { + DecoderError::UnrecognizedFormat => "Unrecognized format", + DecoderError::IoError(msg) => &msg[..], + DecoderError::DecodeError(msg) => msg, + DecoderError::LimitError(msg) => msg, + DecoderError::ResetRequired => "Reset required", + DecoderError::NoStreams => "No streams", + }; + write!(f, "{}", text) + } +} + +impl Error for DecoderError {} diff --git a/audio-player/src/main.rs b/audio-player/src/main.rs new file mode 100644 index 0000000..0dbd36a --- /dev/null +++ b/audio-player/src/main.rs @@ -0,0 +1,141 @@ +mod decoder; + +use std::fs::File; +use std::io::BufReader; +use std::path::Path; +use std::thread; +use std::time::Duration; +use symphonia::core::probe::Hint; +use url::Url; + +use anyhow::{anyhow, Result}; +use decoder::SymphoniaDecoder; +use rodio::source::{PeriodicAccess, SineWave}; +use rodio::{OutputStream, OutputStreamHandle, Sink, Source}; +use stream_download::StreamDownload; +use symphonia::core::io::{ + MediaSource, MediaSourceStream, MediaSourceStreamOptions, ReadOnlySource, +}; + +struct Player { + sink: Option, + stream: Option, +} + +// TODO: +// * Emit Metadata +// * Emit duration +// * Emit track data +// * Emit EOS +// * Emit buffering + +impl Player { + pub fn default() -> Self { + Self { + sink: None, + stream: None, + } + } + + pub fn play(&mut self, source_str: &str) -> Result<()> { + let (stream, handle) = OutputStream::try_default()?; + let mut sink = Sink::try_new(&handle)?; + let (source, hint) = self.get_source(source_str)?; + let mss = MediaSourceStream::new(source, MediaSourceStreamOptions::default()); + + let decoder = SymphoniaDecoder::new(mss, hint)?; + + let media_info = decoder.media_info(); + + let decoder = decoder.periodic_access(Duration::from_millis(500), |src| { + println!("ELAPSED: {:?}", src.elapsed()); + + if src.elapsed().as_secs() > 10 { + src.seek(Duration::from_secs(2)); + } + }); + + sink.append(decoder); + + // We need to keep the stream around, otherwise it gets dropped outside of this scope + self.stream = Some(stream); + // The sink is used to control the stream + self.sink = Some(sink); + + Ok(()) + } + + pub fn pause(&mut self) { + if let Some(sink) = &self.sink { + sink.pause(); + } + } + + pub fn unpause(&mut self) { + if let Some(sink) = &self.sink { + sink.play(); + } + } + + pub fn stop(&mut self) { + if let Some(sink) = &self.sink { + sink.stop(); + self.sink.take(); + self.stream.take(); + } + } + + pub fn is_playing(&self) -> bool { + self.sink.as_ref().map(|s| !s.is_paused()).unwrap_or(false) + } + + pub fn is_paused(&self) -> bool { + self.sink.as_ref().map(|s| s.is_paused()).unwrap_or(false) + } + + pub fn is_stopped(&self) -> bool { + self.sink.is_none() + } + + fn get_source(&self, source_str: &str) -> Result<(Box, Hint)> { + match Url::parse(source_str) { + Ok(url) => { + if let "http" | "https" = url.scheme() { + let reader = StreamDownload::new_http(source_str.parse().unwrap()); + let path = Path::new(url.path()); + let hint = self.get_hint(path); + + Ok((Box::new(ReadOnlySource::new(reader)), hint)) + } else { + Err(anyhow!("Not a valid URL scheme: {}", url.scheme())) + } + } + Err(_) => { + let path = Path::new(source_str); + let hint = self.get_hint(path); + Ok((Box::new(File::open(path)?), hint)) + } + } + } + + fn get_hint(&self, path: &Path) -> Hint { + // Create a hint to help the format registry guess what format reader is appropriate. + let mut hint = Hint::new(); + // Provide the file extension as a hint. + if let Some(extension) = path.extension() { + if let Some(extension_str) = extension.to_str() { + hint.with_extension(extension_str); + } + } + hint + } +} + +fn main() { + let mut player = Player::default(); + player.play("./Slip.m4a"); + + thread::sleep(Duration::from_millis(5000)); + + player.stop(); +}