diff --git a/Cargo.lock b/Cargo.lock index 75f3a55..f276052 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -33,7 +33,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce" dependencies = [ "alsa-sys", - "bitflags 2.4.2", + "bitflags 2.5.0", "libc", ] @@ -64,47 +64,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -140,15 +141,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" dependencies = [ "addr2line", "cc", @@ -165,7 +166,7 @@ version = "0.69.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "cexpr", "clang-sys", "itertools", @@ -187,21 +188,21 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.2" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "bumpalo" -version = "3.15.4" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "byteorder" @@ -211,18 +212,19 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.90" +version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" +checksum = "099a5357d84c4c61eb35fc8eafa9a79a902c2f76911e5747ced4e032edd8d9b4" dependencies = [ "jobserver", "libc", + "once_cell", ] [[package]] @@ -248,16 +250,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.35" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -273,9 +275,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.2" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -295,9 +297,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.0" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ "heck", "proc-macro2", @@ -322,15 +324,15 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -400,7 +402,7 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "crossterm_winapi", "libc", "mio", @@ -427,21 +429,21 @@ checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "either" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" [[package]] name = "encoding_rs" -version = "0.8.33" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] @@ -460,9 +462,9 @@ checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -513,9 +515,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.12" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -536,15 +538,15 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" @@ -552,6 +554,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "hound" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f" + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -577,14 +585,20 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.5" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.12.1" @@ -618,9 +632,9 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.28" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab46a6e9526ddef3ae7f787c06f0f2600639ba80ea3eade3d8e670a2230f51d6" +checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] @@ -648,9 +662,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.154" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" [[package]] name = "libloading" @@ -659,7 +673,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -673,9 +687,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -715,13 +729,15 @@ checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "lonelyradio" -version = "0.3.0" +version = "0.4.0" dependencies = [ "async-stream", "chrono", "clap", "futures-util", "lofty", + "lonelyradio_types", + "once_cell", "rand", "rmp-serde", "samplerate", @@ -732,6 +748,13 @@ dependencies = [ "walkdir", ] +[[package]] +name = "lonelyradio_types" +version = "0.4.0" +dependencies = [ + "serde", +] + [[package]] name = "mach2" version = "0.4.2" @@ -743,9 +766,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "minimal-lexical" @@ -776,7 +799,7 @@ dependencies = [ [[package]] name = "monoclient" -version = "0.3.0" +version = "0.4.0" dependencies = [ "clap", "crossterm", @@ -785,12 +808,21 @@ dependencies = [ [[package]] name = "monolib" -version = "0.3.0" +version = "0.4.0" dependencies = [ "byteorder", + "lonelyradio_types", "rmp-serde", "rodio", - "serde", +] + +[[package]] +name = "monoloader" +version = "0.4.0" +dependencies = [ + "clap", + "hound", + "monolib", ] [[package]] @@ -799,7 +831,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.4.2", + "bitflags 2.5.0", "jni-sys", "log", "ndk-sys", @@ -834,9 +866,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] @@ -863,9 +895,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -935,9 +967,9 @@ dependencies = [ [[package]] name = "ogg_pager" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c949d63b387b25c332f6e39d1762dd4b405008289dd7681f02c258b1294653ca" +checksum = "87b0bef808533c5890ab77279538212efdbbbd9aa4ef1ccdfcfbf77a42f7e6fa" dependencies = [ "byteorder", ] @@ -950,9 +982,9 @@ checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "7e4af0ca4f6caed20e900d564c242b8e5d4903fdacf31d3daf527b66fe6f42fb" dependencies = [ "lock_api", "parking_lot_core", @@ -960,28 +992,28 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1021,18 +1053,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.79" +version = "1.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1069,18 +1101,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "regex" -version = "1.10.3" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", @@ -1101,15 +1133,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rmp" -version = "0.8.12" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9860a6cc38ed1da53456442089b4dfa35e7cedaa326df63017af88385e6b20" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" dependencies = [ "byteorder", "num-traits", @@ -1118,9 +1150,9 @@ dependencies = [ [[package]] name = "rmp-serde" -version = "1.1.2" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bffea85eea980d8a74453e5d02a8d93028f3c34725de143085a844ebe953258a" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" dependencies = [ "byteorder", "rmp", @@ -1138,9 +1170,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1189,18 +1221,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.202" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838" dependencies = [ "proc-macro2", "quote", @@ -1236,9 +1268,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1260,9 +1292,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1276,9 +1308,9 @@ checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" [[package]] name = "strsim" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "symphonia" @@ -1478,9 +1510,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.52" +version = "2.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" dependencies = [ "proc-macro2", "quote", @@ -1489,18 +1521,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "579e9083ca58dd9dcf91a9923bb9054071b9ebbd800b342194c9feb0ee89fc18" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "e2470041c06ec3ac1ab38d0356a6119054dedaea53e12fbefc0de730a1c08524" dependencies = [ "proc-macro2", "quote", @@ -1509,9 +1541,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.36.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -1549,9 +1581,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", @@ -1562,9 +1594,9 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" [[package]] name = "toml_edit" @@ -1715,11 +1747,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] [[package]] @@ -1735,7 +1767,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49" dependencies = [ "windows-core 0.54.0", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1744,7 +1776,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1754,16 +1786,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65" dependencies = [ "windows-result", - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] name = "windows-result" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd19df78e5168dfb0aedc343d1d1b8d422ab2db6756d2dc3fef75035402a3f64" +checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1790,7 +1822,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.4", + "windows-targets 0.52.5", ] [[package]] @@ -1825,17 +1857,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ - "windows_aarch64_gnullvm 0.52.4", - "windows_aarch64_msvc 0.52.4", - "windows_i686_gnu 0.52.4", - "windows_i686_msvc 0.52.4", - "windows_x86_64_gnu 0.52.4", - "windows_x86_64_gnullvm 0.52.4", - "windows_x86_64_msvc 0.52.4", + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", ] [[package]] @@ -1852,9 +1885,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" @@ -1870,9 +1903,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[package]] name = "windows_i686_gnu" @@ -1888,9 +1921,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" @@ -1906,9 +1945,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[package]] name = "windows_x86_64_gnu" @@ -1924,9 +1963,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[package]] name = "windows_x86_64_gnullvm" @@ -1942,9 +1981,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" @@ -1960,9 +1999,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.4" +version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "winnow" diff --git a/Cargo.toml b/Cargo.toml index cf39a14..ed7039c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,15 @@ [workspace] -members = ["monoclient", "monolib"] +members = [ + "lonelyradio_types", + "monoclient", + "monolib", + "monoloader", +] [package] name = "lonelyradio" description = "TCP radio for lonely ones" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "MIT" authors = ["Ivan Bushchik "] @@ -36,6 +41,8 @@ async-stream = "0.3.5" tokio-stream = { version = "0.1.15", features = ["sync"] } futures-util = "0.3.30" samplerate = "0.2.4" +lonelyradio_types = { path = "./lonelyradio_types" } +once_cell = "1.19.0" [profile.release] opt-level = 3 diff --git a/README.md b/README.md index fe4e278..41bfa3c 100644 --- a/README.md +++ b/README.md @@ -21,11 +21,13 @@ cargo build -r ## Run ``` -lonelyradio [-a ] [-p] [-w] +lonelyradio [-a ] [-p] [-w] [-m|--max-samplerate M] ``` All files (recursively) will be shuffled and played back. Public log will be displayed to stdout, private to stderr. +`-m|--max-samplerate M` will resample tracks which samplerate exceeds M to M + ### Clients [monoclient](./monoclient) is a recommended CLI client for lonelyradio that uses [monolib](./monolib) diff --git a/lonelyradio_types/Cargo.toml b/lonelyradio_types/Cargo.toml new file mode 100644 index 0000000..cc918d0 --- /dev/null +++ b/lonelyradio_types/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "lonelyradio_types" +version = "0.4.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +serde = { version = "1.0.197", features = ["derive"] } diff --git a/lonelyradio_types/src/lib.rs b/lonelyradio_types/src/lib.rs new file mode 100644 index 0000000..1b6586b --- /dev/null +++ b/lonelyradio_types/src/lib.rs @@ -0,0 +1,29 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub enum Message { + T(TrackMetadata), + F(FragmentMetadata), +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct TrackMetadata { + pub track_length_secs: u64, + pub track_length_frac: f32, + pub channels: u16, + pub sample_rate: u32, + pub title: String, + pub album: String, + pub artist: String, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct FragmentMetadata { + // In samples + pub length: u64, +} + +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)] +pub struct SessionSettings { + pub gzip: bool, +} diff --git a/monoclient/Cargo.toml b/monoclient/Cargo.toml index 651efe8..4616735 100644 --- a/monoclient/Cargo.toml +++ b/monoclient/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "monoclient" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "MIT" diff --git a/monoclient/src/main.rs b/monoclient/src/main.rs index 8121f73..21c6d06 100644 --- a/monoclient/src/main.rs +++ b/monoclient/src/main.rs @@ -3,6 +3,7 @@ use crossterm::cursor::MoveToColumn; use crossterm::style::Print; use crossterm::terminal::{Clear, ClearType}; use std::io::{stdout, IsTerminal}; +use std::path::PathBuf; use std::time::{Duration, Instant}; #[derive(Parser)] @@ -13,12 +14,20 @@ struct Args { /// Do not use backspace control char #[arg(short)] no_backspace: bool, + + #[arg(long)] + xor_key_file: Option, } fn main() { let mut args = Args::parse(); args.no_backspace |= !std::io::stdout().is_terminal(); - std::thread::spawn(move || monolib::run(&args.address)); + std::thread::spawn(move || { + monolib::run( + &args.address, + args.xor_key_file.map(|key| std::fs::read(key).expect("Failed to read preshared key")), + ) + }); while monolib::get_metadata().is_none() {} let mut md = monolib::get_metadata().unwrap(); let mut track_start = Instant::now(); @@ -35,9 +44,13 @@ fn main() { )) ) .unwrap(); + let mut track_length = md.track_length_secs as f64 + md.track_length_frac as f64; + let mut next_md = md.clone(); loop { - if monolib::get_metadata().unwrap() != md { - md = monolib::get_metadata().unwrap(); + if monolib::get_metadata().unwrap() != md + && track_length <= (Instant::now() - track_start).as_secs_f64() + { + md = next_md.clone(); crossterm::execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap(); print!( "Playing: {} - {} - {} (0:00 / {}:{:02})", @@ -49,6 +62,9 @@ fn main() { ); track_start = Instant::now(); seconds_past = 0; + track_length = md.track_length_secs as f64 + md.track_length_frac as f64 + } else if next_md == md { + next_md = monolib::get_metadata().unwrap(); } if (Instant::now() - track_start).as_secs() > seconds_past && !args.no_backspace { seconds_past = (Instant::now() - track_start).as_secs(); diff --git a/monolib/Cargo.toml b/monolib/Cargo.toml index 447d75c..a35fcca 100644 --- a/monolib/Cargo.toml +++ b/monolib/Cargo.toml @@ -1,11 +1,11 @@ [package] name = "monolib" -version = "0.3.0" +version = "0.4.0" edition = "2021" license = "MIT" description = "A library implementing the lonely radio audio streaming protocol" repository = "https://github.com/ivabus/lonelyradio" -authors = [ "Ivan Bushchik "] +authors = ["Ivan Bushchik "] [lib] name = "monolib" @@ -15,4 +15,4 @@ crate-type = ["staticlib", "cdylib", "rlib"] rodio = { version = "0.17.3", default-features = false } byteorder = "1.5.0" rmp-serde = "1.1.2" -serde = { version = "1.0.197", features = ["derive"] } +lonelyradio_types = { path = "../lonelyradio_types" } diff --git a/monolib/src/c.rs b/monolib/src/c.rs index 5aee7f1..e4791b8 100644 --- a/monolib/src/c.rs +++ b/monolib/src/c.rs @@ -7,10 +7,13 @@ use std::ffi::{CStr, CString}; #[allow(clippy::not_unsafe_ptr_arg_deref)] pub extern "C" fn c_start(server: *const c_char) { let serv = unsafe { CStr::from_ptr(server) }; - run(match serv.to_str() { - Ok(s) => s, - _ => "", - }) + run( + match serv.to_str() { + Ok(s) => s, + _ => "", + }, + None, + ) } #[no_mangle] @@ -69,7 +72,7 @@ pub extern "C" fn c_get_metadata_title() -> *mut c_char { pub extern "C" fn c_get_metadata_length() -> *mut c_float { let md = MD.read().unwrap(); match md.as_ref() { - Some(md) => &mut (md.length as c_float / md.sample_rate as c_float), + Some(md) => &mut (md.track_length_secs as c_float + md.track_length_frac as c_float), None => &mut 0.0, } } diff --git a/monolib/src/lib.rs b/monolib/src/lib.rs index 540a205..a99cda7 100644 --- a/monolib/src/lib.rs +++ b/monolib/src/lib.rs @@ -16,20 +16,21 @@ /// Functions, providing C-like API pub mod c; +mod reader; -use byteorder::ByteOrder; +use byteorder::{LittleEndian, ReadBytesExt}; +use lonelyradio_types::{Message, TrackMetadata}; use rodio::buffer::SamplesBuffer; use rodio::{OutputStream, Sink}; -use serde::Deserialize; -use std::io::Read; +use std::io::BufReader; use std::net::TcpStream; use std::sync::RwLock; use std::time::Instant; -const CACHE_SIZE: usize = 500; +const CACHE_SIZE: usize = 32; static SINK: RwLock> = RwLock::new(None); -static MD: RwLock> = RwLock::new(None); +static MD: RwLock> = RwLock::new(None); static STATE: RwLock = RwLock::new(State::NotStarted); /// Player state @@ -42,22 +43,6 @@ pub enum State { Paused = 3, } -/// Track metadata -#[derive(Deserialize, Clone, Debug, PartialEq)] -pub struct Metadata { - /// Fragment length - pub length: u64, - /// Total track length - pub track_length_secs: u64, - pub track_length_frac: f32, - pub channels: u16, - // Yep, no more interpolation - pub sample_rate: u32, - pub title: String, - pub album: String, - pub artist: String, -} - /// Play/pauses playback pub fn toggle() { let mut state = crate::STATE.write().unwrap(); @@ -81,6 +66,9 @@ pub fn toggle() { /// Stops playback pub fn stop() { let mut state = STATE.write().unwrap(); + if *state == State::NotStarted { + return; + } *state = State::Resetting; let sink = SINK.read().unwrap(); @@ -99,7 +87,7 @@ pub fn get_state() -> State { *STATE.read().unwrap() } -pub fn get_metadata() -> Option { +pub fn get_metadata() -> Option { MD.read().unwrap().clone() } @@ -130,8 +118,37 @@ fn watching_sleep(dur: f32) -> bool { false } +/// Download track as samples +pub fn get_track(server: &str, xor_key: Option>) -> Option<(TrackMetadata, Vec)> { + let mut stream = BufReader::new(match xor_key { + Some(k) => reader::Reader::XorEncrypted(TcpStream::connect(server).unwrap(), k, 0), + None => reader::Reader::Unencrypted(TcpStream::connect(server).unwrap()), + }); + + let mut samples = vec![]; + let mut md: Option = None; + loop { + let recv_md: Message = rmp_serde::from_read(&mut stream).expect("Failed to parse message"); + match recv_md { + Message::T(tmd) => { + if md.is_some() { + break; + } + md = Some(tmd); + } + Message::F(fmd) => { + let mut buf = vec![0; fmd.length as usize]; + stream.read_i16_into::(&mut buf).unwrap(); + samples.append(&mut buf); + } + } + } + + md.map(|md| (md, samples)) +} + /// Starts playing at "server:port" -pub fn run(server: &str) { +pub fn run(server: &str, xor_key: Option>) { let mut state = STATE.write().unwrap(); if *state == State::Playing || *state == State::Paused { return; @@ -139,7 +156,11 @@ pub fn run(server: &str) { *state = State::Playing; drop(state); - let mut stream = TcpStream::connect(server).unwrap(); + let mut stream = BufReader::new(match xor_key { + Some(k) => reader::Reader::XorEncrypted(TcpStream::connect(server).unwrap(), k, 0), + None => reader::Reader::Unencrypted(TcpStream::connect(server).unwrap()), + }); + let mut sink = SINK.write().unwrap(); let (_stream, stream_handle) = OutputStream::try_default().unwrap(); @@ -148,56 +169,60 @@ pub fn run(server: &str) { *sink = Some(audio_sink); drop(sink); - let mut buffer = [0u8; 2]; let mut samples = Vec::with_capacity(8192); loop { - let recv_md: Metadata = - rmp_serde::from_read(&stream).expect("Failed to parse track metadata"); - - let mut md = MD.write().unwrap(); - *md = Some(recv_md.clone()); - drop(md); - for _ in 0..recv_md.length { - while *STATE.read().unwrap() == State::Paused { - std::thread::sleep(std::time::Duration::from_secs_f32(0.25)) + let recv_md: Message = rmp_serde::from_read(&mut stream).expect("Failed to parse message"); + match recv_md { + Message::T(tmd) => { + let mut md = MD.write().unwrap(); + *md = Some(tmd.clone()); } - if *STATE.read().unwrap() == State::Resetting { - _stop(); - return; - } - - if stream.read_exact(&mut buffer).is_err() { - return; - }; - samples.push(byteorder::LittleEndian::read_i16(&buffer[..2]) as f32 / 32768.0); - } - // Sink's thread is detached from main thread, so we need to synchronize with it - // Why we should synchronize with it? - // Let's say, that if we don't synchronize with it, we would have - // a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink - let sink = SINK.read().unwrap(); - if let Some(sink) = sink.as_ref() { - while sink.len() >= CACHE_SIZE { - // Sleeping exactly one buffer and watching for reset signal - if watching_sleep( - if sink.len() > 2 { - sink.len() as f32 - 2.0 - } else { - 0.25 - } * recv_md.length as f32 - / recv_md.sample_rate as f32 - / 2.0, - ) { + Message::F(fmd) => { + while *STATE.read().unwrap() == State::Paused { + std::thread::sleep(std::time::Duration::from_secs_f32(0.25)) + } + if *STATE.read().unwrap() == State::Resetting { _stop(); return; } + let mut samples_i16 = vec![0; fmd.length as usize]; + if stream.read_i16_into::(&mut samples_i16).is_err() { + return; + }; + samples.append( + &mut samples_i16.iter().map(|sample| *sample as f32 / 32767.0).collect(), + ); + + // Sink's thread is detached from main thread, so we need to synchronize with it + // Why we should synchronize with it? + // Let's say, that if we don't synchronize with it, we would have + // a lot (no upper limit, actualy) of buffered sound, waiting for playing in sink + let sink = SINK.read().unwrap(); + let md = MD.read().unwrap(); + let md = md.as_ref().unwrap(); + if let Some(sink) = sink.as_ref() { + while sink.len() >= CACHE_SIZE { + // Sleeping exactly one buffer and watching for reset signal + if watching_sleep( + if sink.len() > 2 { + sink.len() as f32 - 2.0 + } else { + 0.25 + } * fmd.length as f32 / md.sample_rate as f32 + / 4.0, + ) { + _stop(); + return; + } + } + sink.append(SamplesBuffer::new( + md.channels, + md.sample_rate, + samples.as_slice(), + )); + samples.clear(); + } } - sink.append(SamplesBuffer::new( - recv_md.channels, - recv_md.sample_rate, - samples.as_slice(), - )); - samples.clear(); } } } diff --git a/monolib/src/reader.rs b/monolib/src/reader.rs new file mode 100644 index 0000000..9102935 --- /dev/null +++ b/monolib/src/reader.rs @@ -0,0 +1,25 @@ +use std::{io, net::TcpStream}; + +pub(crate) enum Reader { + Unencrypted(TcpStream), + XorEncrypted(TcpStream, Vec, u64), +} + +impl io::Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + Self::Unencrypted(s) => s.read(buf), + Self::XorEncrypted(s, key, n) => { + let out = s.read(buf); + if let Ok(i) = &out { + for k in buf.iter_mut().take(*i) { + *k ^= key[*n as usize]; + *n += 1; + *n %= key.len() as u64; + } + } + out + } + } + } +} diff --git a/monoloader/Cargo.toml b/monoloader/Cargo.toml new file mode 100644 index 0000000..8b9a223 --- /dev/null +++ b/monoloader/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "monoloader" +version = "0.4.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +monolib = { path = "../monolib" } +clap = { version = "4.4.18", features = ["derive"] } +hound = "3.5.1" diff --git a/monoloader/src/main.rs b/monoloader/src/main.rs new file mode 100644 index 0000000..0bc17cd --- /dev/null +++ b/monoloader/src/main.rs @@ -0,0 +1,38 @@ +use clap::Parser; +use std::path::PathBuf; + +#[derive(Parser)] +struct Args { + /// Remote address + address: String, + + #[arg(long)] + xor_key_file: Option, +} + +fn main() { + let args = Args::parse(); + let (md, samples) = monolib::get_track( + &args.address, + args.xor_key_file.map(|key| std::fs::read(key).expect("Failed to read preshared key")), + ) + .unwrap(); + println!( + "Downloaded: {} - {} - {} ({} MB)", + md.artist, + md.album, + md.title, + samples.len() as f32 * 2.0 / 1024.0 / 1024.0 + ); + let spec = hound::WavSpec { + channels: md.channels, + sample_rate: md.sample_rate, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + let mut writer = + hound::WavWriter::create(format!("{} - {}.wav", md.artist, md.title), spec).unwrap(); + let mut writer_i16 = writer.get_i16_writer(samples.len() as u32); + samples.iter().for_each(|s| writer_i16.write_sample(*s)); + writer_i16.flush().unwrap(); +} diff --git a/src/decode.rs b/src/decode.rs index 3a4e814..52eb244 100644 --- a/src/decode.rs +++ b/src/decode.rs @@ -1,6 +1,8 @@ use std::path::{Path, PathBuf}; +use async_stream::stream; use clap::Parser; +use futures_util::Stream; use symphonia::core::audio::SampleBuffer; use symphonia::core::codecs::CODEC_TYPE_NULL; use symphonia::core::io::MediaSourceStream; @@ -75,7 +77,7 @@ pub async fn get_meta(file_path: &Path) -> (u16, u32, Time) { } /// Getting samples -pub async fn decode_file(file_path: PathBuf, tx: tokio::sync::mpsc::Sender>) { +pub fn decode_file_stream(file_path: PathBuf) -> impl Stream> { let args = Args::parse(); let file = Box::new(std::fs::File::open(&file_path).unwrap()); let mut hint = Hint::new(); @@ -102,50 +104,51 @@ pub async fn decode_file(file_path: PathBuf, tx: tokio::sync::mpsc::Sender packet, - _ => break, - }; + stream! { + loop { + let packet = match format.next_packet() { + Ok(packet) => packet, + _ => break, + }; - if packet.track_id() != track_id { - continue; - } - - match decoder.decode(&packet) { - Ok(decoded) => { - if decoded.spec().rate > args.max_samplerate { - let spec = *decoded.spec(); - let mut byte_buf = - SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); - byte_buf.copy_interleaved_ref(decoded); - - tx.send( - samplerate::convert( - spec.rate, - args.max_samplerate, - spec.channels.count(), - samplerate::ConverterType::Linear, - byte_buf.samples(), - ) - .unwrap() - .iter() - .map(|x| (*x * 32767.0) as i16) - .collect(), - ) - .await - .unwrap(); - } else { - let mut byte_buf = - SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); - byte_buf.copy_interleaved_ref(decoded); - tx.send(byte_buf.samples().to_vec()).await.unwrap(); - } + if packet.track_id() != track_id { continue; } - _ => { - // Handling any error as track skip - continue; + + match decoder.decode(&packet) { + Ok(decoded) => { + if decoded.spec().rate > args.max_samplerate { + let spec = *decoded.spec(); + let mut byte_buf = + SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); + byte_buf.copy_interleaved_ref(decoded); + + // About Samplerate struct: + // We are downsampling, not upsampling, so we should be fine + yield (samplerate::convert( + spec.rate, + args.max_samplerate, + spec.channels.count(), + samplerate::ConverterType::Linear, + byte_buf.samples(), + ) + .unwrap() + .iter() + .map(|x| (*x * 32768.0) as i16) + .collect()); + + } else { + let mut byte_buf = + SampleBuffer::::new(decoded.capacity() as u64, *decoded.spec()); + byte_buf.copy_interleaved_ref(decoded); + yield (byte_buf.samples().to_vec()); + } + continue; + } + _ => { + // Handling any error as track skip + continue; + } } } } diff --git a/src/main.rs b/src/main.rs index eab06a1..52aa742 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ mod decode; +mod writer; use std::path::Path; use std::path::PathBuf; @@ -9,16 +10,17 @@ use clap::Parser; use futures_util::pin_mut; use lofty::Accessor; use lofty::TaggedFileExt; +use lonelyradio_types::{FragmentMetadata, Message, TrackMetadata}; +use once_cell::sync::Lazy; use rand::prelude::*; -use serde::Serialize; -use tokio::io::AsyncWriteExt; +use std::io::Write; use tokio::net::{TcpListener, TcpStream}; -use tokio::sync::mpsc; use tokio_stream::Stream; use tokio_stream::StreamExt; use walkdir::DirEntry; +use writer::Writer; -use crate::decode::decode_file; +use crate::decode::decode_file_stream; use crate::decode::get_meta; #[derive(Parser)] @@ -35,50 +37,58 @@ struct Args { #[arg(short, long, default_value = "96000")] max_samplerate: u32, + + #[arg(long)] + xor_key_file: Option, } -#[derive(Serialize, Clone)] -struct SentMetadata { - /// Fragment length - length: u64, - /// Total track length - track_length_secs: u64, - track_length_frac: f32, - channels: u16, - sample_rate: u32, - title: String, - album: String, - artist: String, -} +static KEY: Lazy>>> = Lazy::new(|| { + let args = Args::parse(); + if let Some(path) = args.xor_key_file { + let key = std::fs::read(path).expect("Failed to read preshared key"); + Some(Arc::new(key)) + } else { + None + } +}); -async fn stream_samples( +async fn stream_track( samples_stream: impl Stream>, war: bool, - md: SentMetadata, - s: &mut TcpStream, + md: TrackMetadata, + s: &mut Writer, ) -> bool { pin_mut!(samples_stream); - while let Some(samples) = samples_stream.next().await { - let mut md = md.clone(); - md.length = samples.len() as u64; - if s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).await.is_err() { + if s.write_all(rmp_serde::to_vec(&Message::T(md)).unwrap().as_slice()).is_err() { + return true; + }; + + while let Some(mut _samples) = samples_stream.next().await { + let md = Message::F(FragmentMetadata { + length: _samples.len() as u64, + }); + if s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).is_err() { return true; } - for sample in samples { - if s.write_all( - &(if war { - sample.signum() * 32767 - } else { - sample - } - .to_le_bytes()), - ) - .await - .is_err() - { - return true; - }; + + if war { + _samples.iter_mut().for_each(|sample| { + *sample = sample.signum() * 32767; + }); + } + // Launching lonelyradio on the router moment + if cfg!(target_endian = "big") { + _samples.iter_mut().for_each(|sample| { + *sample = sample.to_le(); + }); + } + + // Sowwy about that + let (_, samples, _) = unsafe { _samples.align_to::() }; + + if s.write_all(samples).is_err() { + return true; } } false @@ -117,10 +127,24 @@ fn track_valid(track: &Path) -> bool { true } -async fn stream(mut s: TcpStream, tracklist: Arc>) { +async fn stream(s: TcpStream, tracklist: Arc>) { let args = Args::parse(); - s.set_nodelay(true).unwrap(); - + let s = s.into_std().unwrap(); + s.set_nonblocking(false).unwrap(); + let mut s = if args.xor_key_file.is_some() { + Writer::XorEncrypted( + s, + match &*KEY { + Some(a) => a.clone(), + _ => { + unreachable!() + } + }, + 0, + ) + } else { + Writer::Unencrypted(s) + }; loop { let track = tracklist.choose(&mut thread_rng()).unwrap().clone(); @@ -163,23 +187,16 @@ async fn stream(mut s: TcpStream, tracklist: Arc>) { ); } let (channels, sample_rate, time) = get_meta(track.as_path()).await; - let (tx, mut rx) = mpsc::channel::>(8192); - tokio::spawn(decode_file(track, tx)); - let stream = async_stream::stream! { - while let Some(item) = rx.recv().await { - yield item; - } - }; - if stream_samples( + let stream = decode_file_stream(track); + if stream_track( stream, args.war, - SentMetadata { + TrackMetadata { + track_length_frac: time.frac as f32, + track_length_secs: time.seconds, album, artist, title, - length: 0, - track_length_frac: time.frac as f32, - track_length_secs: time.seconds, sample_rate, channels, }, diff --git a/src/writer.rs b/src/writer.rs new file mode 100644 index 0000000..e1cd17a --- /dev/null +++ b/src/writer.rs @@ -0,0 +1,59 @@ +use std::{ + borrow::BorrowMut, + io, + net::{SocketAddr, TcpStream}, + sync::Arc, +}; + +pub(crate) enum Writer { + Unencrypted(TcpStream), + XorEncrypted(TcpStream, Arc>, u64), +} + +impl io::Write for Writer { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + Self::Unencrypted(s) => s.write(buf), + Self::XorEncrypted(s, key, n) => { + for mut k in buf.iter().copied() { + k ^= key[*n as usize]; + *n += 1; + *n %= key.len() as u64; + s.write_all(&[k])?; + } + Ok(buf.len()) + } + } + } + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + match self { + Self::Unencrypted(s) => s.write_all(buf), + Self::XorEncrypted(s, key, n) => s.write_all( + &buf.iter() + .borrow_mut() + .copied() + .map(|mut k| { + k ^= key[*n as usize]; + *n += 1; + *n %= key.len() as u64; + k + }) + // I don't like it + .collect::>(), + ), + } + } + fn flush(&mut self) -> io::Result<()> { + match self { + Self::XorEncrypted(s, _, _) | Self::Unencrypted(s) => s.flush(), + } + } +} + +impl Writer { + pub fn peer_addr(&self) -> io::Result { + match self { + Self::XorEncrypted(s, _, _) | Self::Unencrypted(s) => s.peer_addr(), + } + } +}