0.4.0: XOR encryption + memory optimization

- Optional XOR encryption is now available in lonelyradio and monolib
- lonelyradio was optimized to use less TCP calls and less memory copies
- New program: monoloader - music downloader for lonelyradio
- New monolib function - get_track() for downloading track (as samples),
  not playing
- lonelyradio and monolib are using extensible Writer and Reader enums
  to provide ability to use lonelyradio with different transport
  protocols or encryption
- Add lonelyradio_types crate to share types

Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
Ivan Bushchik 2024-05-16 08:28:18 +03:00
parent 05b65e25ed
commit e0b7f60b69
No known key found for this signature in database
GPG key ID: 2F16FBF3262E090C
16 changed files with 617 additions and 334 deletions

353
Cargo.lock generated
View file

@ -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"

View file

@ -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 <ivabus@ivabus.dev>"]
@ -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

View file

@ -21,11 +21,13 @@ cargo build -r
## Run
```
lonelyradio [-a <ADDRESS:PORT>] <MUSIC_FOLDER> [-p] [-w]
lonelyradio <MUSIC_FOLDER> [-a <ADDRESS:PORT>] [-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)

View file

@ -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"] }

View file

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

View file

@ -1,6 +1,6 @@
[package]
name = "monoclient"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
license = "MIT"

View file

@ -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<PathBuf>,
}
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();

View file

@ -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 <ivabus@ivabus.dev>"]
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
[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" }

View file

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

View file

@ -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<Option<Sink>> = RwLock::new(None);
static MD: RwLock<Option<Metadata>> = RwLock::new(None);
static MD: RwLock<Option<TrackMetadata>> = RwLock::new(None);
static STATE: RwLock<State> = 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<Metadata> {
pub fn get_metadata() -> Option<TrackMetadata> {
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<Vec<u8>>) -> Option<(TrackMetadata, Vec<i16>)> {
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<TrackMetadata> = 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::<LittleEndian>(&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<Vec<u8>>) {
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::<LittleEndian>(&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();
}
}
}

25
monolib/src/reader.rs Normal file
View file

@ -0,0 +1,25 @@
use std::{io, net::TcpStream};
pub(crate) enum Reader {
Unencrypted(TcpStream),
XorEncrypted(TcpStream, Vec<u8>, u64),
}
impl io::Read for Reader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
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
}
}
}
}

11
monoloader/Cargo.toml Normal file
View file

@ -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"

38
monoloader/src/main.rs Normal file
View file

@ -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<PathBuf>,
}
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();
}

View file

@ -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<Vec<i16>>) {
pub fn decode_file_stream(file_path: PathBuf) -> impl Stream<Item = Vec<i16>> {
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<Vec<i
.make(&track.codec_params, &Default::default())
.expect("unsupported codec");
let track_id = track.id;
loop {
let packet = match format.next_packet() {
Ok(packet) => 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::<f32>::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::<i16>::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::<f32>::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::<i16>::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;
}
}
}
}

View file

@ -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<PathBuf>,
}
#[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<Option<Arc<Vec<u8>>>> = 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<Item = Vec<i16>>,
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::<u8>() };
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<Vec<PathBuf>>) {
async fn stream(s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
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<Vec<PathBuf>>) {
);
}
let (channels, sample_rate, time) = get_meta(track.as_path()).await;
let (tx, mut rx) = mpsc::channel::<Vec<i16>>(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,
},

59
src/writer.rs Normal file
View file

@ -0,0 +1,59 @@
use std::{
borrow::BorrowMut,
io,
net::{SocketAddr, TcpStream},
sync::Arc,
};
pub(crate) enum Writer {
Unencrypted(TcpStream),
XorEncrypted(TcpStream, Arc<Vec<u8>>, u64),
}
impl io::Write for Writer {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
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::<Vec<u8>>(),
),
}
}
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<SocketAddr> {
match self {
Self::XorEncrypted(s, _, _) | Self::Unencrypted(s) => s.peer_addr(),
}
}
}