mirror of
https://github.com/ivabus/lonelyradio
synced 2024-11-21 23:55:09 +03:00
0.5.0: FLAC support, new GUI player, volume settings
Signed-off-by: Ivan Bushchik <ivabus@ivabus.dev>
This commit is contained in:
parent
39cc35e16d
commit
ff52e14863
17 changed files with 5101 additions and 408 deletions
3832
Cargo.lock
generated
3832
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -2,15 +2,16 @@
|
||||||
members = [
|
members = [
|
||||||
"lonelyradio_types",
|
"lonelyradio_types",
|
||||||
"monoclient",
|
"monoclient",
|
||||||
|
"monoclient-s",
|
||||||
"monolib",
|
"monolib",
|
||||||
"monoloader",
|
"monoloader",
|
||||||
"platform/gtk",
|
"microserve",
|
||||||
]
|
]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lonelyradio"
|
name = "lonelyradio"
|
||||||
description = "TCP radio for lonely ones"
|
description = "TCP radio for lonely ones"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
|
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
|
||||||
|
@ -42,8 +43,10 @@ async-stream = "0.3.5"
|
||||||
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
tokio-stream = { version = "0.1.15", features = ["sync"] }
|
||||||
futures-util = "0.3.30"
|
futures-util = "0.3.30"
|
||||||
samplerate = "0.2.4"
|
samplerate = "0.2.4"
|
||||||
lonelyradio_types = { path = "./lonelyradio_types" }
|
lonelyradio_types = { version = "0.5.0", path = "./lonelyradio_types" }
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
|
flacenc = { version = "0.4.0", default-features = false }
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
strip = true
|
||||||
|
|
52
README.md
52
README.md
|
@ -1,47 +1,77 @@
|
||||||
# lonelyradio
|
# lonelyradio
|
||||||
|
|
||||||
> TCP radio for singles
|
Broadcast audio over the internet.
|
||||||
|
|
||||||
Radio that uses unencrypted TCP socket for broadcasting tagged audio data.
|
|
||||||
|
|
||||||
Decodes audio streams using [symphonia](https://github.com/pdeljanov/Symphonia).
|
Decodes audio streams using [symphonia](https://github.com/pdeljanov/Symphonia).
|
||||||
|
|
||||||
## Install
|
Optionally transcodes audio into and from FLAC using [flacenc-rs](https://github.com/yotarok/flacenc-rs/) and [claxon](https://github.com/ruuda/claxon).
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Install music server
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo install lonelyradio
|
cargo install --git https://github.com/ivabus/lonelyradio --tag 0.5.0 lonelyradio
|
||||||
```
|
```
|
||||||
|
|
||||||
## Build
|
### Install CLI client
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
cargo build -r
|
cargo install --git https://github.com/ivabus/lonelyradio --tag 0.5.0 monoclient
|
||||||
|
```
|
||||||
|
|
||||||
|
### Install GUI (Slint) client
|
||||||
|
|
||||||
|
```shell
|
||||||
|
cargo install --git https://github.com/ivabus/lonelyradio --tag 0.5.0 monoclient-s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Run
|
## Run
|
||||||
|
|
||||||
```
|
```
|
||||||
lonelyradio <MUSIC_FOLDER> [-a <ADDRESS:PORT>] [-p] [-w] [-m|--max-samplerate M]
|
lonelyradio [-a <ADDRESS:PORT>] [-p|--public-log] [-w|--war] [-m|--max-samplerate M] [--xor-key-file FILE] [--no-resampling] [-f|--flac] <MUSIC_FOLDER>
|
||||||
```
|
```
|
||||||
|
|
||||||
All files (recursively) will be shuffled and played back. Public log will be displayed to stdout, private to stderr.
|
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
|
`-m|--max-samplerate M` will resample tracks which samplerate exceeds M to M
|
||||||
|
|
||||||
|
`--xor-key-file FILE` will XOR all outgoing bytes looping through FILE
|
||||||
|
|
||||||
|
`-f|--flac` will enable (experimental) FLAC compression
|
||||||
|
|
||||||
### Clients
|
### Clients
|
||||||
|
|
||||||
[monoclient](./monoclient) is a recommended CLI client for lonelyradio that uses [monolib](./monolib)
|
[monoclient](./monoclient) is a recommended CLI player for lonelyradio that uses [monolib](./monolib)
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
monoclient <SERVER>:<PORT>
|
monoclient <SERVER>:<PORT>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[monoclient-s](./monoclient-s) is a experimental GUI player for lonelyradio built with [Slint](https://slint.dev)
|
||||||
|
|
||||||
|
```shell
|
||||||
|
monoclient-s
|
||||||
|
```
|
||||||
|
|
||||||
|
Desktop integration will be added later.
|
||||||
|
|
||||||
### Other clients
|
### Other clients
|
||||||
|
|
||||||
SwiftUI client is availible in [platform](./platform) directory.
|
SwiftUI client is availible in [platform](./platform) directory.
|
||||||
|
|
||||||
[monolib](./monolib) provides lonelyradio-compatible C API for creating custom clients.
|
[monoloader](./monoloader) is a tool, that allows you to download individual audio tracks from lonelyradio-compatible servers.
|
||||||
|
|
||||||
|
[monolib](./monolib) provides a C API compatible with lonelyradio for creating custom clients.
|
||||||
|
|
||||||
|
#### monolib API stability
|
||||||
|
|
||||||
|
As lonelyradio has not yet reached its first major release, the API may (and will) break at any point.
|
||||||
|
|
||||||
|
### Microphone server
|
||||||
|
|
||||||
|
Experimental server (lonelyradio-compatible) for streaming audio from your microphone is available in the [microserve](./microserve) crate.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
lonelyradio, monolib and monoclient are licensed under the terms of the [MIT license](./LICENSE).
|
lonelyradio, monolib and monoclient, as well as all other crates in this repository, are licensed under the terms of the [MIT license](./LICENSE).
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lonelyradio_types"
|
name = "lonelyradio_types"
|
||||||
version = "0.4.0"
|
description = "Shared types for lonelyradio"
|
||||||
|
license = "MIT"
|
||||||
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
repository = "https://github.com/ivabus/lonelyradio"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0.197", features = ["derive"] }
|
serde = { version = "1.0.197", features = ["derive"] }
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub struct TrackMetadata {
|
||||||
pub track_length_frac: f32,
|
pub track_length_frac: f32,
|
||||||
pub channels: u16,
|
pub channels: u16,
|
||||||
pub sample_rate: u32,
|
pub sample_rate: u32,
|
||||||
|
pub flac: bool,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub album: String,
|
pub album: String,
|
||||||
pub artist: String,
|
pub artist: String,
|
||||||
|
@ -19,11 +20,6 @@ pub struct TrackMetadata {
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
||||||
pub struct FragmentMetadata {
|
pub struct FragmentMetadata {
|
||||||
// In samples
|
// In samples or bytes (if FLAC)
|
||||||
pub length: u64,
|
pub length: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
|
|
||||||
pub struct SessionSettings {
|
|
||||||
pub gzip: bool,
|
|
||||||
}
|
|
||||||
|
|
886
microserve/Cargo.lock
generated
Normal file
886
microserve/Cargo.lock
generated
Normal file
|
@ -0,0 +1,886 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alsa"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37fe60779335388a88c01ac6c3be40304d1e349de3ada3b15f7808bb90fa9dce"
|
||||||
|
dependencies = [
|
||||||
|
"alsa-sys",
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "alsa-sys"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bindgen"
|
||||||
|
version = "0.69.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"cexpr",
|
||||||
|
"clang-sys",
|
||||||
|
"itertools",
|
||||||
|
"lazy_static",
|
||||||
|
"lazycell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
|
"shlex",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.98"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f"
|
||||||
|
dependencies = [
|
||||||
|
"jobserver",
|
||||||
|
"libc",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cesu8"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cexpr"
|
||||||
|
version = "0.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
|
||||||
|
dependencies = [
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clang-sys"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1"
|
||||||
|
dependencies = [
|
||||||
|
"glob",
|
||||||
|
"libc",
|
||||||
|
"libloading",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "combine"
|
||||||
|
version = "4.6.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coreaudio-rs"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "321077172d79c662f64f5071a03120748d5bb652f5231570141be24cfcd2bace"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"coreaudio-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "coreaudio-sys"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7f01585027057ff5f0a5bf276174ae4c1594a2c5bde93d5f46a016d76270f5a9"
|
||||||
|
dependencies = [
|
||||||
|
"bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpal"
|
||||||
|
version = "0.15.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "873dab07c8f743075e57f524c583985fbaf745602acbe916a01539364369a779"
|
||||||
|
dependencies = [
|
||||||
|
"alsa",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"coreaudio-rs",
|
||||||
|
"dasp_sample",
|
||||||
|
"jni",
|
||||||
|
"js-sys",
|
||||||
|
"libc",
|
||||||
|
"mach2",
|
||||||
|
"ndk",
|
||||||
|
"ndk-context",
|
||||||
|
"oboe",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
"windows",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dasp_sample"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "glob"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
|
||||||
|
dependencies = [
|
||||||
|
"cesu8",
|
||||||
|
"cfg-if",
|
||||||
|
"combine",
|
||||||
|
"jni-sys",
|
||||||
|
"log",
|
||||||
|
"thiserror",
|
||||||
|
"walkdir",
|
||||||
|
"windows-sys 0.45.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jni-sys"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jobserver"
|
||||||
|
version = "0.1.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazy_static"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lazycell"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.155"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libloading"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lonelyradio_types"
|
||||||
|
version = "0.4.0"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mach2"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "microserve"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"cpal",
|
||||||
|
"lonelyradio_types",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "microserve-session"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minimal-lexical"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndk"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.5.0",
|
||||||
|
"jni-sys",
|
||||||
|
"log",
|
||||||
|
"ndk-sys",
|
||||||
|
"num_enum",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndk-context"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ndk-sys"
|
||||||
|
version = "0.5.0+25.2.9519653"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8c196769dd60fd4f363e11d948139556a344e79d451aeb2fa2fd040738ef7691"
|
||||||
|
dependencies = [
|
||||||
|
"jni-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "7.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"minimal-lexical",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-derive"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845"
|
||||||
|
dependencies = [
|
||||||
|
"num_enum_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_enum_derive"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro-crate",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oboe"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e8b61bebd49e5d43f5f8cc7ee2891c16e0f41ec7954d36bcb6c14c5e0de867fb"
|
||||||
|
dependencies = [
|
||||||
|
"jni",
|
||||||
|
"ndk",
|
||||||
|
"ndk-context",
|
||||||
|
"num-derive",
|
||||||
|
"num-traits",
|
||||||
|
"oboe-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "oboe-sys"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c8bb09a4a2b1d668170cfe0a7d5bc103f8999fb316c98099b6a9939c9f2e79d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.19.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro-crate"
|
||||||
|
version = "3.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284"
|
||||||
|
dependencies = [
|
||||||
|
"toml_edit",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.82"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-hash"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.202"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.202"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ad3dee41f36859875573074334c200d1add8e4a87bb37113ebd31d926b7b11f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_datetime"
|
||||||
|
version = "0.6.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "toml_edit"
|
||||||
|
version = "0.21.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"toml_datetime",
|
||||||
|
"winnow",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.42"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.92"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.54.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9252e5725dbed82865af151df558e754e4a3c2c30818359eb17465f1346a1b49"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.54.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12661b9c89351d684a50a8a643ce5f608e20243b9fb84687800163429f161d65"
|
||||||
|
dependencies = [
|
||||||
|
"windows-result",
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "749f0da9cc72d82e600d8d2e44cadd0b9eedb9038f71a1c58556ac1c5791813b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.45.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.52.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.52.5",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm 0.42.2",
|
||||||
|
"windows_aarch64_msvc 0.42.2",
|
||||||
|
"windows_i686_gnu 0.42.2",
|
||||||
|
"windows_i686_msvc 0.42.2",
|
||||||
|
"windows_x86_64_gnu 0.42.2",
|
||||||
|
"windows_x86_64_gnullvm 0.42.2",
|
||||||
|
"windows_x86_64_msvc 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||||
|
dependencies = [
|
||||||
|
"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]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.42.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winnow"
|
||||||
|
version = "0.5.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
24
microserve/Cargo.toml
Normal file
24
microserve/Cargo.toml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
[package]
|
||||||
|
name = "microserve"
|
||||||
|
version = "0.5.0"
|
||||||
|
license = "MIT"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
|
||||||
|
repository = "https://github.com/ivabus/lonelyradio"
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
cpal = "0.15.3"
|
||||||
|
lonelyradio_types = { path = "../lonelyradio_types" }
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
queues = "1.1.0"
|
||||||
|
rmp-serde = "1.3.0"
|
||||||
|
tokio = { version = "1.35.1", features = [
|
||||||
|
"sync",
|
||||||
|
"fs",
|
||||||
|
"io-util",
|
||||||
|
"net",
|
||||||
|
"rt-multi-thread",
|
||||||
|
"rt",
|
||||||
|
"macros",
|
||||||
|
] }
|
95
microserve/src/main.rs
Normal file
95
microserve/src/main.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
|
use lonelyradio_types::{FragmentMetadata, TrackMetadata};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
static QUEUE: Lazy<Arc<RwLock<VecDeque<Vec<i16>>>>> =
|
||||||
|
Lazy::new(|| Arc::new(RwLock::new(VecDeque::new())));
|
||||||
|
|
||||||
|
static START_INDEX: Mutex<usize> = Mutex::new(0);
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
tokio::spawn(listen_mic());
|
||||||
|
println!("Started buffering");
|
||||||
|
let listener = TcpListener::bind("0.0.0.0:5894").await.unwrap();
|
||||||
|
std::thread::sleep(Duration::from_secs(5));
|
||||||
|
tokio::spawn(update_start());
|
||||||
|
println!("Accepting connections");
|
||||||
|
loop {
|
||||||
|
let (socket, _) = listener.accept().await.unwrap();
|
||||||
|
let socket = socket.into_std().unwrap();
|
||||||
|
tokio::spawn(stream(socket));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn update_start() {
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(Duration::from_secs(1));
|
||||||
|
*START_INDEX.lock().unwrap() = QUEUE.read().unwrap().len() - 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stream(mut s: std::net::TcpStream) {
|
||||||
|
println!("Playing for {}", s.peer_addr().unwrap());
|
||||||
|
let md = lonelyradio_types::Message::T(TrackMetadata {
|
||||||
|
flac: false,
|
||||||
|
track_length_secs: 0,
|
||||||
|
track_length_frac: 0.0,
|
||||||
|
channels: 1,
|
||||||
|
sample_rate: 44100,
|
||||||
|
title: "microserve instance".to_string(),
|
||||||
|
album: "".to_string(),
|
||||||
|
artist: "".to_string(),
|
||||||
|
});
|
||||||
|
s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).unwrap();
|
||||||
|
let mut ind = *START_INDEX.lock().unwrap();
|
||||||
|
dbg!(ind);
|
||||||
|
loop {
|
||||||
|
let front = QUEUE.read().unwrap()[ind].clone();
|
||||||
|
ind += 1;
|
||||||
|
let md = lonelyradio_types::Message::F(FragmentMetadata {
|
||||||
|
length: front.len() as u64,
|
||||||
|
});
|
||||||
|
s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).unwrap();
|
||||||
|
|
||||||
|
if s.write_all(unsafe { front.as_slice().align_to::<u8>().1 }).is_err() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
while ind >= QUEUE.read().unwrap().len() - 5 {
|
||||||
|
std::thread::sleep(Duration::from_millis(100))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn listen_mic() {
|
||||||
|
let host = cpal::default_host();
|
||||||
|
|
||||||
|
let device = host.default_input_device().unwrap();
|
||||||
|
let config = device.default_input_config().unwrap();
|
||||||
|
let stream = match config.sample_format() {
|
||||||
|
cpal::SampleFormat::F32 => device.build_input_stream(
|
||||||
|
&config.into(),
|
||||||
|
move |data: &[f32], _: &_| {
|
||||||
|
let samples = data.iter().map(|x| (*x * 32767.0) as i16).collect();
|
||||||
|
QUEUE.write().unwrap().push_back(samples);
|
||||||
|
},
|
||||||
|
|e| eprintln!("Error while reading: {}", e),
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
_ => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.unwrap();
|
||||||
|
loop {
|
||||||
|
stream.play().unwrap();
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
19
monoclient-s/Cargo.toml
Normal file
19
monoclient-s/Cargo.toml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
[package]
|
||||||
|
name = "monoclient-s"
|
||||||
|
description = "Client for lonelyradio built with Slint"
|
||||||
|
version = "0.5.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
slint = { version = "1.6.0", features = ["backend-android-activity-06"] }
|
||||||
|
monolib = { path = "../monolib" }
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Set up cargo-bundle
|
||||||
|
#[package.metadata.bundle]
|
||||||
|
#name = "monoclient-s"
|
||||||
|
#identifier = "dev.ivabus.monoclient-s"
|
||||||
|
#icon = ["lonelyradio.png", "lonelyradio.icns"]
|
||||||
|
#version = "0.5.0"
|
||||||
|
#copyright = "Copyright (c) 2024 Ivan Bushchik."
|
||||||
|
#category = "Music"
|
7
monoclient-s/lib.rs
Normal file
7
monoclient-s/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
mod main;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
fn android_main(app: slint::android::AndroidApp) {
|
||||||
|
slint::android::init(app).unwrap();
|
||||||
|
main::main()
|
||||||
|
}
|
181
monoclient-s/src/main.rs
Normal file
181
monoclient-s/src/main.rs
Normal file
|
@ -0,0 +1,181 @@
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use monolib::State;
|
||||||
|
use slint::Weak;
|
||||||
|
|
||||||
|
slint::slint! {
|
||||||
|
import { AboutSlint, Button, VerticalBox, GroupBox, Slider } from "std-widgets.slint";
|
||||||
|
export component MainWindow inherits Window {
|
||||||
|
max-height: self.preferred-height;
|
||||||
|
callback play;
|
||||||
|
callback stop;
|
||||||
|
callback next;
|
||||||
|
callback change_volume(float);
|
||||||
|
callback text_edited;
|
||||||
|
|
||||||
|
in-out property <string> addr: address.text;
|
||||||
|
in-out property <string> mtitle: "";
|
||||||
|
in-out property <string> malbum: "";
|
||||||
|
in-out property <string> martist: "";
|
||||||
|
in-out property <float> volume: svolume.value;
|
||||||
|
in-out property <bool> start_enabled: false;
|
||||||
|
in-out property <bool> playing: false;
|
||||||
|
in-out property <bool> paused: false;
|
||||||
|
|
||||||
|
title: "monoclient-s";
|
||||||
|
min-width: 192px;
|
||||||
|
max-width: 768px;
|
||||||
|
VerticalBox {
|
||||||
|
alignment: center;
|
||||||
|
GroupBox{
|
||||||
|
max-width: 768px;
|
||||||
|
address := TextInput {
|
||||||
|
text: "";
|
||||||
|
horizontal-alignment: center;
|
||||||
|
height: 1.25rem;
|
||||||
|
|
||||||
|
accepted => {
|
||||||
|
self.clear_focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
edited => {
|
||||||
|
text_edited()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VerticalLayout {
|
||||||
|
max-width: 512px;
|
||||||
|
VerticalLayout {
|
||||||
|
spacing: 4px;
|
||||||
|
Button {
|
||||||
|
max-width: 256px;
|
||||||
|
text: playing ? (paused ? "Play" : "Pause") : "Start";
|
||||||
|
enabled: start_enabled || playing;
|
||||||
|
clicked => {
|
||||||
|
play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HorizontalLayout {
|
||||||
|
spacing: 4px;
|
||||||
|
max-width: 256px;
|
||||||
|
Button {
|
||||||
|
text: "Stop";
|
||||||
|
enabled: playing && !paused;
|
||||||
|
clicked => {
|
||||||
|
stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Button {
|
||||||
|
text: "Next";
|
||||||
|
enabled: playing && !paused;
|
||||||
|
clicked => {
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
svolume := Slider {
|
||||||
|
value: 255;
|
||||||
|
maximum: 255;
|
||||||
|
changed(f) => {
|
||||||
|
change_volume(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tartist := Text {
|
||||||
|
height: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text: martist;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
talbum := Text {
|
||||||
|
height: 1.25rem;
|
||||||
|
text: malbum;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
ttitle := Text {
|
||||||
|
height: 1.25rem;
|
||||||
|
text: mtitle;
|
||||||
|
overflow: elide;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_playback(window_weak: Weak<MainWindow>) {
|
||||||
|
let window = window_weak.upgrade().unwrap();
|
||||||
|
let addr = window.get_addr().to_string();
|
||||||
|
let handle = std::thread::spawn(move || monolib::run(&addr, None));
|
||||||
|
std::thread::sleep(Duration::from_millis(166));
|
||||||
|
if handle.is_finished() {
|
||||||
|
window.set_playing(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.set_playing(true);
|
||||||
|
window.set_paused(false);
|
||||||
|
while monolib::get_metadata().is_none() {}
|
||||||
|
monolib::set_volume(window.get_volume() as u8);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main() {
|
||||||
|
let window = MainWindow::new().unwrap();
|
||||||
|
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
window.on_text_edited(move || {
|
||||||
|
let window = window_weak.upgrade().unwrap();
|
||||||
|
let addr = window.get_addr().to_string();
|
||||||
|
window.set_start_enabled(addr.contains(':'));
|
||||||
|
});
|
||||||
|
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
window.on_play(move || match monolib::get_state() {
|
||||||
|
State::NotStarted => start_playback(window_weak.clone()),
|
||||||
|
State::Paused => {
|
||||||
|
let window = window_weak.upgrade().unwrap();
|
||||||
|
window.set_paused(false);
|
||||||
|
monolib::toggle();
|
||||||
|
}
|
||||||
|
State::Resetting => {}
|
||||||
|
State::Playing => {
|
||||||
|
let window = window_weak.upgrade().unwrap();
|
||||||
|
window.set_paused(true);
|
||||||
|
monolib::toggle()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
window.on_next(move || {
|
||||||
|
monolib::stop();
|
||||||
|
start_playback(window_weak.clone())
|
||||||
|
});
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
window.on_stop(move || {
|
||||||
|
let window = window_weak.upgrade().unwrap();
|
||||||
|
window.set_playing(false);
|
||||||
|
window.set_martist("".into());
|
||||||
|
window.set_malbum("".into());
|
||||||
|
window.set_mtitle("".into());
|
||||||
|
monolib::stop();
|
||||||
|
});
|
||||||
|
window.on_change_volume(move |vol| monolib::set_volume(vol as u8));
|
||||||
|
let window_weak = window.as_weak();
|
||||||
|
std::thread::spawn(move || loop {
|
||||||
|
let window = window_weak.clone();
|
||||||
|
while monolib::get_metadata().is_none() {
|
||||||
|
std::thread::sleep(Duration::from_millis(25))
|
||||||
|
}
|
||||||
|
let md = monolib::get_metadata().unwrap();
|
||||||
|
let _md = md.clone();
|
||||||
|
slint::invoke_from_event_loop(move || {
|
||||||
|
let window = window.unwrap();
|
||||||
|
window.set_martist(md.artist.clone().into());
|
||||||
|
window.set_malbum(md.album.clone().into());
|
||||||
|
window.set_mtitle(md.title.clone().into());
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
while monolib::get_metadata() == Some(_md.clone()) {
|
||||||
|
std::thread::sleep(Duration::from_millis(100))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
window.run().unwrap();
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
[package]
|
[package]
|
||||||
name = "monoclient"
|
name = "monoclient"
|
||||||
version = "0.4.0"
|
|
||||||
edition = "2021"
|
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
version = "0.5.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["Ivan Bushchik <ivabus@ivabus.dev>"]
|
||||||
|
repository = "https://github.com/ivabus/lonelyradio"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
monolib = { path = "../monolib" }
|
monolib = { version = "0.5.0", path = "../monolib" }
|
||||||
clap = { version = "4.4.18", features = ["derive"] }
|
clap = { version = "4.4.18", features = ["derive"] }
|
||||||
crossterm = "0.27.0"
|
crossterm = "0.27.0"
|
||||||
|
|
|
@ -1,27 +1,29 @@
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use crossterm::cursor::MoveToColumn;
|
use crossterm::cursor::MoveToColumn;
|
||||||
|
use crossterm::event::{poll, read, Event};
|
||||||
use crossterm::style::Print;
|
use crossterm::style::Print;
|
||||||
use crossterm::terminal::{Clear, ClearType};
|
use crossterm::terminal::{Clear, ClearType};
|
||||||
use std::io::{stdout, IsTerminal};
|
use std::io::stdout;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::time::{Duration, Instant};
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// Remote address
|
/// Remote address
|
||||||
address: String,
|
address: String,
|
||||||
|
|
||||||
/// Do not use backspace control char
|
|
||||||
#[arg(short)]
|
|
||||||
no_backspace: bool,
|
|
||||||
|
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
xor_key_file: Option<PathBuf>,
|
xor_key_file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HELP: &str = r#"Keybinds:
|
||||||
|
Up - Volume up
|
||||||
|
Down - Volume down
|
||||||
|
Q - Quit monoclient
|
||||||
|
H - Show this help"#;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut args = Args::parse();
|
let args = Args::parse();
|
||||||
args.no_backspace |= !std::io::stdout().is_terminal();
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
monolib::run(
|
monolib::run(
|
||||||
&args.address,
|
&args.address,
|
||||||
|
@ -46,7 +48,58 @@ fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let mut track_length = md.track_length_secs as f64 + md.track_length_frac as f64;
|
let mut track_length = md.track_length_secs as f64 + md.track_length_frac as f64;
|
||||||
let mut next_md = md.clone();
|
let mut next_md = md.clone();
|
||||||
|
crossterm::terminal::enable_raw_mode().unwrap();
|
||||||
loop {
|
loop {
|
||||||
|
if let Ok(true) = poll(std::time::Duration::from_micros(1)) {
|
||||||
|
if let Event::Key(event) = read().unwrap() {
|
||||||
|
match (event.code, event.modifiers) {
|
||||||
|
(crossterm::event::KeyCode::Up, _) => {
|
||||||
|
monolib::set_volume(monolib::get_volume().saturating_add(4));
|
||||||
|
}
|
||||||
|
(crossterm::event::KeyCode::Down, _) => {
|
||||||
|
monolib::set_volume(monolib::get_volume().saturating_sub(4));
|
||||||
|
}
|
||||||
|
(crossterm::event::KeyCode::Char('q' | 'й'), _)
|
||||||
|
| (
|
||||||
|
crossterm::event::KeyCode::Char('c' | 'с'),
|
||||||
|
crossterm::event::KeyModifiers::CONTROL,
|
||||||
|
) => {
|
||||||
|
crossterm::terminal::disable_raw_mode().unwrap();
|
||||||
|
println!();
|
||||||
|
std::process::exit(0)
|
||||||
|
}
|
||||||
|
(crossterm::event::KeyCode::Char('h' | 'р'), _) => {
|
||||||
|
crossterm::terminal::disable_raw_mode().unwrap();
|
||||||
|
crossterm::execute!(
|
||||||
|
stdout(),
|
||||||
|
Clear(ClearType::CurrentLine),
|
||||||
|
MoveToColumn(0)
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", HELP);
|
||||||
|
crossterm::terminal::enable_raw_mode().unwrap();
|
||||||
|
seconds_past = (Instant::now() - track_start).as_secs();
|
||||||
|
crossterm::execute!(
|
||||||
|
stdout(),
|
||||||
|
Print(format!(
|
||||||
|
"Playing: {} - {} - {} ({}:{:02} / {}:{:02}) [{:.2}]",
|
||||||
|
md.artist,
|
||||||
|
md.album,
|
||||||
|
md.title,
|
||||||
|
seconds_past / 60,
|
||||||
|
seconds_past % 60,
|
||||||
|
md.track_length_secs / 60,
|
||||||
|
md.track_length_secs % 60,
|
||||||
|
monolib::get_volume() as f32 / 255.0
|
||||||
|
))
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
(crossterm::event::KeyCode::Char(' '), _) => monolib::toggle(),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if monolib::get_metadata().unwrap() != md
|
if monolib::get_metadata().unwrap() != md
|
||||||
&& track_length <= (Instant::now() - track_start).as_secs_f64()
|
&& track_length <= (Instant::now() - track_start).as_secs_f64()
|
||||||
{
|
{
|
||||||
|
@ -58,7 +111,7 @@ fn main() {
|
||||||
md.album,
|
md.album,
|
||||||
md.title,
|
md.title,
|
||||||
md.track_length_secs / 60,
|
md.track_length_secs / 60,
|
||||||
md.track_length_secs % 60
|
md.track_length_secs % 60,
|
||||||
);
|
);
|
||||||
track_start = Instant::now();
|
track_start = Instant::now();
|
||||||
seconds_past = 0;
|
seconds_past = 0;
|
||||||
|
@ -66,24 +119,25 @@ fn main() {
|
||||||
} else if next_md == md {
|
} else if next_md == md {
|
||||||
next_md = monolib::get_metadata().unwrap();
|
next_md = monolib::get_metadata().unwrap();
|
||||||
}
|
}
|
||||||
if (Instant::now() - track_start).as_secs() > seconds_past && !args.no_backspace {
|
if (Instant::now() - track_start).as_secs() > seconds_past {
|
||||||
seconds_past = (Instant::now() - track_start).as_secs();
|
seconds_past = (Instant::now() - track_start).as_secs();
|
||||||
crossterm::execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap();
|
crossterm::execute!(stdout(), Clear(ClearType::CurrentLine), MoveToColumn(0)).unwrap();
|
||||||
crossterm::execute!(
|
crossterm::execute!(
|
||||||
stdout(),
|
stdout(),
|
||||||
Print(format!(
|
Print(format!(
|
||||||
"Playing: {} - {} - {} ({}:{:02} / {}:{:02})",
|
"Playing: {} - {} - {} ({}:{:02} / {}:{:02}) [{:.2}]",
|
||||||
md.artist,
|
md.artist,
|
||||||
md.album,
|
md.album,
|
||||||
md.title,
|
md.title,
|
||||||
seconds_past / 60,
|
seconds_past / 60,
|
||||||
seconds_past % 60,
|
seconds_past % 60,
|
||||||
md.track_length_secs / 60,
|
md.track_length_secs / 60,
|
||||||
md.track_length_secs % 60
|
md.track_length_secs % 60,
|
||||||
|
monolib::get_volume() as f32 / 255.0
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_secs_f32(0.25))
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.0125))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "monolib"
|
name = "monolib"
|
||||||
version = "0.4.0"
|
version = "0.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "A library implementing the lonely radio audio streaming protocol"
|
description = "A library implementing the lonely radio audio streaming protocol"
|
||||||
|
@ -15,4 +15,5 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
rodio = { version = "0.17.3", default-features = false }
|
rodio = { version = "0.17.3", default-features = false }
|
||||||
byteorder = "1.5.0"
|
byteorder = "1.5.0"
|
||||||
rmp-serde = "1.1.2"
|
rmp-serde = "1.1.2"
|
||||||
lonelyradio_types = { path = "../lonelyradio_types" }
|
lonelyradio_types = { version = "0.5.0", path = "../lonelyradio_types" }
|
||||||
|
claxon = "0.4.3"
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
//! println!("Playing: {} - {} - {} ({}:{:02})", md.artist, md.album, md.title, seconds / 60, seconds % 60);
|
//! println!("Playing: {} - {} - {} ({}:{:02})", md.artist, md.album, md.title, seconds / 60, seconds % 60);
|
||||||
//! sleep(Duration::from_secs(10));
|
//! sleep(Duration::from_secs(10));
|
||||||
//! monolib::stop();
|
//! monolib::stop();
|
||||||
//!```
|
//! ```
|
||||||
|
|
||||||
/// Functions, providing C-like API
|
/// Functions, providing C-like API
|
||||||
pub mod c;
|
pub mod c;
|
||||||
|
@ -22,14 +22,17 @@ use byteorder::{LittleEndian, ReadBytesExt};
|
||||||
use lonelyradio_types::{Message, TrackMetadata};
|
use lonelyradio_types::{Message, TrackMetadata};
|
||||||
use rodio::buffer::SamplesBuffer;
|
use rodio::buffer::SamplesBuffer;
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
use std::io::BufReader;
|
use std::error::Error;
|
||||||
|
use std::io::{BufReader, Read};
|
||||||
use std::net::TcpStream;
|
use std::net::TcpStream;
|
||||||
|
use std::sync::atomic::AtomicU8;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
const CACHE_SIZE: usize = 32;
|
const CACHE_SIZE: usize = 128;
|
||||||
|
|
||||||
static SINK: RwLock<Option<Sink>> = RwLock::new(None);
|
static SINK: RwLock<Option<Sink>> = RwLock::new(None);
|
||||||
|
static VOLUME: AtomicU8 = AtomicU8::new(255);
|
||||||
static MD: RwLock<Option<TrackMetadata>> = RwLock::new(None);
|
static MD: RwLock<Option<TrackMetadata>> = RwLock::new(None);
|
||||||
static STATE: RwLock<State> = RwLock::new(State::NotStarted);
|
static STATE: RwLock<State> = RwLock::new(State::NotStarted);
|
||||||
|
|
||||||
|
@ -77,9 +80,10 @@ pub fn stop() {
|
||||||
}
|
}
|
||||||
drop(sink);
|
drop(sink);
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
// Blocking main thread
|
// Blocking main thread
|
||||||
while *STATE.read().unwrap() == State::Resetting {
|
while *STATE.read().unwrap() == State::Resetting {
|
||||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.01))
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.1))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,10 +98,8 @@ pub fn get_metadata() -> Option<TrackMetadata> {
|
||||||
fn _stop() {
|
fn _stop() {
|
||||||
let sink = SINK.read().unwrap();
|
let sink = SINK.read().unwrap();
|
||||||
if let Some(sink) = sink.as_ref() {
|
if let Some(sink) = sink.as_ref() {
|
||||||
sink.pause();
|
|
||||||
sink.clear();
|
sink.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut md = MD.write().unwrap();
|
let mut md = MD.write().unwrap();
|
||||||
if md.is_some() {
|
if md.is_some() {
|
||||||
*md = None;
|
*md = None;
|
||||||
|
@ -110,7 +112,7 @@ fn _stop() {
|
||||||
fn watching_sleep(dur: f32) -> bool {
|
fn watching_sleep(dur: f32) -> bool {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
while Instant::now() < start + std::time::Duration::from_secs_f32(dur) {
|
while Instant::now() < start + std::time::Duration::from_secs_f32(dur) {
|
||||||
std::thread::sleep(std::time::Duration::from_secs_f32(0.0001));
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.01));
|
||||||
if *STATE.read().unwrap() == State::Resetting {
|
if *STATE.read().unwrap() == State::Resetting {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -118,6 +120,28 @@ fn watching_sleep(dur: f32) -> bool {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn watching_sleep_until_end() -> bool {
|
||||||
|
while SINK.read().unwrap().as_ref().unwrap().len() != 0 {
|
||||||
|
std::thread::sleep(std::time::Duration::from_secs_f32(0.01));
|
||||||
|
if *STATE.read().unwrap() == State::Resetting {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_volume() -> u8 {
|
||||||
|
VOLUME.load(std::sync::atomic::Ordering::Acquire)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_volume(volume: u8) {
|
||||||
|
let sink = SINK.read().unwrap();
|
||||||
|
if let Some(sink) = sink.as_ref() {
|
||||||
|
sink.set_volume(get_volume() as f32 / 255.0)
|
||||||
|
}
|
||||||
|
VOLUME.store(volume, std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
/// Download track as samples
|
/// Download track as samples
|
||||||
pub fn get_track(server: &str, xor_key: Option<Vec<u8>>) -> Option<(TrackMetadata, Vec<i16>)> {
|
pub fn get_track(server: &str, xor_key: Option<Vec<u8>>) -> Option<(TrackMetadata, Vec<i16>)> {
|
||||||
let mut stream = BufReader::new(match xor_key {
|
let mut stream = BufReader::new(match xor_key {
|
||||||
|
@ -137,16 +161,30 @@ pub fn get_track(server: &str, xor_key: Option<Vec<u8>>) -> Option<(TrackMetadat
|
||||||
md = Some(tmd);
|
md = Some(tmd);
|
||||||
}
|
}
|
||||||
Message::F(fmd) => {
|
Message::F(fmd) => {
|
||||||
|
if !md.clone().unwrap().flac {
|
||||||
let mut buf = vec![0; fmd.length as usize];
|
let mut buf = vec![0; fmd.length as usize];
|
||||||
stream.read_i16_into::<LittleEndian>(&mut buf).unwrap();
|
stream.read_i16_into::<LittleEndian>(&mut buf).unwrap();
|
||||||
samples.append(&mut buf);
|
samples.append(&mut buf);
|
||||||
|
} else {
|
||||||
|
let take = stream.by_ref().take(fmd.length);
|
||||||
|
let mut reader = claxon::FlacReader::new(take).unwrap();
|
||||||
|
samples.append(
|
||||||
|
&mut reader.samples().map(|x| x.unwrap_or(0) as i16).collect::<Vec<i16>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
md.map(|md| (md, samples))
|
md.map(|md| (md, samples))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unwrap<T, E: Error>(thing: Result<T, E>) -> T {
|
||||||
|
if thing.is_err() {
|
||||||
|
*STATE.write().unwrap() = State::NotStarted;
|
||||||
|
}
|
||||||
|
thing.unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
/// Starts playing at "server:port"
|
/// Starts playing at "server:port"
|
||||||
pub fn run(server: &str, xor_key: Option<Vec<u8>>) {
|
pub fn run(server: &str, xor_key: Option<Vec<u8>>) {
|
||||||
let mut state = STATE.write().unwrap();
|
let mut state = STATE.write().unwrap();
|
||||||
|
@ -157,12 +195,12 @@ pub fn run(server: &str, xor_key: Option<Vec<u8>>) {
|
||||||
drop(state);
|
drop(state);
|
||||||
|
|
||||||
let mut stream = BufReader::new(match xor_key {
|
let mut stream = BufReader::new(match xor_key {
|
||||||
Some(k) => reader::Reader::XorEncrypted(TcpStream::connect(server).unwrap(), k, 0),
|
Some(k) => reader::Reader::XorEncrypted(unwrap(TcpStream::connect(server)), k, 0),
|
||||||
None => reader::Reader::Unencrypted(TcpStream::connect(server).unwrap()),
|
None => reader::Reader::Unencrypted(unwrap(TcpStream::connect(server))),
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut sink = SINK.write().unwrap();
|
let mut sink = SINK.write().unwrap();
|
||||||
let (_stream, stream_handle) = OutputStream::try_default().unwrap();
|
let (_stream, stream_handle) = unwrap(OutputStream::try_default());
|
||||||
|
|
||||||
// Can't reuse old sink for some reason
|
// Can't reuse old sink for some reason
|
||||||
let audio_sink = Sink::try_new(&stream_handle).unwrap();
|
let audio_sink = Sink::try_new(&stream_handle).unwrap();
|
||||||
|
@ -174,8 +212,14 @@ pub fn run(server: &str, xor_key: Option<Vec<u8>>) {
|
||||||
let recv_md: Message = rmp_serde::from_read(&mut stream).expect("Failed to parse message");
|
let recv_md: Message = rmp_serde::from_read(&mut stream).expect("Failed to parse message");
|
||||||
match recv_md {
|
match recv_md {
|
||||||
Message::T(tmd) => {
|
Message::T(tmd) => {
|
||||||
|
// No metadata shift
|
||||||
|
if watching_sleep_until_end() {
|
||||||
|
_stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
let mut md = MD.write().unwrap();
|
let mut md = MD.write().unwrap();
|
||||||
*md = Some(tmd.clone());
|
*md = Some(tmd.clone());
|
||||||
|
drop(md);
|
||||||
}
|
}
|
||||||
Message::F(fmd) => {
|
Message::F(fmd) => {
|
||||||
while *STATE.read().unwrap() == State::Paused {
|
while *STATE.read().unwrap() == State::Paused {
|
||||||
|
@ -185,6 +229,7 @@ pub fn run(server: &str, xor_key: Option<Vec<u8>>) {
|
||||||
_stop();
|
_stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if !MD.read().unwrap().clone().unwrap().flac {
|
||||||
let mut samples_i16 = vec![0; fmd.length as usize];
|
let mut samples_i16 = vec![0; fmd.length as usize];
|
||||||
if stream.read_i16_into::<LittleEndian>(&mut samples_i16).is_err() {
|
if stream.read_i16_into::<LittleEndian>(&mut samples_i16).is_err() {
|
||||||
return;
|
return;
|
||||||
|
@ -192,14 +237,26 @@ pub fn run(server: &str, xor_key: Option<Vec<u8>>) {
|
||||||
samples.append(
|
samples.append(
|
||||||
&mut samples_i16.iter().map(|sample| *sample as f32 / 32767.0).collect(),
|
&mut samples_i16.iter().map(|sample| *sample as f32 / 32767.0).collect(),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
let take = stream.by_ref().take(fmd.length);
|
||||||
|
let mut reader = claxon::FlacReader::new(take).unwrap();
|
||||||
|
samples.append(
|
||||||
|
&mut reader
|
||||||
|
.samples()
|
||||||
|
.map(|x| x.unwrap_or(0) as f32 / 32767.0)
|
||||||
|
.collect::<Vec<f32>>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Sink's thread is detached from main thread, so we need to synchronize with it
|
// Sink's thread is detached from main thread, so we need to synchronize with it
|
||||||
// Why we should synchronize with it?
|
// Why we should synchronize with it?
|
||||||
// Let's say, that if we don't synchronize with it, we would have
|
// 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
|
// a lot (no upper limit, actualy) of buffered sound, waiting for playing in
|
||||||
|
// sink
|
||||||
let sink = SINK.read().unwrap();
|
let sink = SINK.read().unwrap();
|
||||||
let md = MD.read().unwrap();
|
let _md = MD.read().unwrap();
|
||||||
let md = md.as_ref().unwrap();
|
let md = _md.as_ref().unwrap().clone();
|
||||||
|
drop(_md);
|
||||||
if let Some(sink) = sink.as_ref() {
|
if let Some(sink) = sink.as_ref() {
|
||||||
while sink.len() >= CACHE_SIZE {
|
while sink.len() >= CACHE_SIZE {
|
||||||
// Sleeping exactly one buffer and watching for reset signal
|
// Sleeping exactly one buffer and watching for reset signal
|
||||||
|
|
|
@ -101,7 +101,7 @@ pub fn decode_file_stream(file_path: PathBuf) -> impl Stream<Item = Vec<i16>> {
|
||||||
.expect("no supported audio tracks");
|
.expect("no supported audio tracks");
|
||||||
|
|
||||||
let mut decoder = symphonia::default::get_codecs()
|
let mut decoder = symphonia::default::get_codecs()
|
||||||
.make(&track.codec_params, &Default::default())
|
.make(track.codec_params.clone().with_max_frames_per_packet(65536), &Default::default())
|
||||||
.expect("unsupported codec");
|
.expect("unsupported codec");
|
||||||
let track_id = track.id;
|
let track_id = track.id;
|
||||||
stream! {
|
stream! {
|
||||||
|
@ -122,10 +122,15 @@ pub fn decode_file_stream(file_path: PathBuf) -> impl Stream<Item = Vec<i16>> {
|
||||||
let mut byte_buf =
|
let mut byte_buf =
|
||||||
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
|
SampleBuffer::<f32>::new(decoded.capacity() as u64, *decoded.spec());
|
||||||
byte_buf.copy_interleaved_ref(decoded);
|
byte_buf.copy_interleaved_ref(decoded);
|
||||||
|
let output_rate = get_resampling_rate(&spec.rate, &args.max_samplerate);
|
||||||
|
|
||||||
// About Samplerate struct:
|
// About Samplerate struct:
|
||||||
// We are downsampling, not upsampling, so we should be fine
|
// We are downsampling, not upsampling, so we should be fine
|
||||||
yield (samplerate::convert(
|
yield (
|
||||||
|
if output_rate == spec.rate {
|
||||||
|
byte_buf.samples().iter().map(|x| (*x * 32768.0) as i16).collect()
|
||||||
|
} else {
|
||||||
|
samplerate::convert(
|
||||||
spec.rate,
|
spec.rate,
|
||||||
args.max_samplerate,
|
args.max_samplerate,
|
||||||
spec.channels.count(),
|
spec.channels.count(),
|
||||||
|
@ -135,7 +140,9 @@ pub fn decode_file_stream(file_path: PathBuf) -> impl Stream<Item = Vec<i16>> {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(|x| (*x * 32768.0) as i16)
|
.map(|x| (*x * 32768.0) as i16)
|
||||||
.collect());
|
.collect()
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
let mut byte_buf =
|
let mut byte_buf =
|
||||||
|
@ -153,3 +160,15 @@ pub fn decode_file_stream(file_path: PathBuf) -> impl Stream<Item = Vec<i16>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_resampling_rate(in_rate: &u32, max_samplerate: &u32) -> u32 {
|
||||||
|
if in_rate < max_samplerate {
|
||||||
|
*in_rate
|
||||||
|
} else if in_rate % 44100 == 0 {
|
||||||
|
max_samplerate - (max_samplerate % 44100)
|
||||||
|
} else if in_rate % 48000 == 0 {
|
||||||
|
max_samplerate - (max_samplerate % 48000)
|
||||||
|
} else {
|
||||||
|
*max_samplerate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
129
src/main.rs
129
src/main.rs
|
@ -7,16 +7,19 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::Local;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use flacenc::component::BitRepr;
|
||||||
|
use flacenc::error::Verify;
|
||||||
|
use flacenc::source::MemSource;
|
||||||
use futures_util::pin_mut;
|
use futures_util::pin_mut;
|
||||||
|
use futures_util::StreamExt;
|
||||||
use lofty::Accessor;
|
use lofty::Accessor;
|
||||||
use lofty::TaggedFileExt;
|
use lofty::TaggedFileExt;
|
||||||
use lonelyradio_types::{FragmentMetadata, Message, TrackMetadata};
|
use lonelyradio_types::{FragmentMetadata, Message, TrackMetadata};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
use tokio::net::TcpListener;
|
||||||
use tokio_stream::Stream;
|
use tokio_stream::Stream;
|
||||||
use tokio_stream::StreamExt;
|
|
||||||
use walkdir::DirEntry;
|
use walkdir::DirEntry;
|
||||||
use writer::Writer;
|
use writer::Writer;
|
||||||
|
|
||||||
|
@ -25,19 +28,34 @@ use crate::decode::get_meta;
|
||||||
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
struct Args {
|
struct Args {
|
||||||
|
/// Directory with audio files
|
||||||
dir: PathBuf,
|
dir: PathBuf,
|
||||||
|
|
||||||
|
/// Address:port to bind
|
||||||
#[arg(short, default_value = "0.0.0.0:5894")]
|
#[arg(short, default_value = "0.0.0.0:5894")]
|
||||||
address: String,
|
address: String,
|
||||||
|
|
||||||
|
/// Enable "public" log (without sensitive information)
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
public_log: bool,
|
public_log: bool,
|
||||||
|
|
||||||
|
/// Process all samples to -1 or 1
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
war: bool,
|
war: bool,
|
||||||
|
|
||||||
|
/// Resample all tracks, which samplerate exceeds N
|
||||||
#[arg(short, long, default_value = "96000")]
|
#[arg(short, long, default_value = "96000")]
|
||||||
max_samplerate: u32,
|
max_samplerate: u32,
|
||||||
|
|
||||||
|
/// Disable all audio processing (disable resampling)
|
||||||
|
#[arg(long)]
|
||||||
|
no_resampling: bool,
|
||||||
|
|
||||||
|
/// Use FLAC compression
|
||||||
|
#[arg(short, long)]
|
||||||
|
flac: bool,
|
||||||
|
|
||||||
|
/// Enable XOR "encryption"
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
xor_key_file: Option<PathBuf>,
|
xor_key_file: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -60,23 +78,39 @@ async fn stream_track(
|
||||||
) -> bool {
|
) -> bool {
|
||||||
pin_mut!(samples_stream);
|
pin_mut!(samples_stream);
|
||||||
|
|
||||||
if s.write_all(rmp_serde::to_vec(&Message::T(md)).unwrap().as_slice()).is_err() {
|
let _md = md.clone();
|
||||||
|
|
||||||
|
if s.write_all(rmp_serde::to_vec(&Message::T(_md)).unwrap().as_slice()).is_err() {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(mut _samples) = samples_stream.next().await {
|
// Why chunks?
|
||||||
let md = Message::F(FragmentMetadata {
|
// flacenc is broken on low amount of samples (Symphonia's AIFF decoder returns ~2304
|
||||||
length: _samples.len() as u64,
|
// samples per packet (on bo en's tracks), instead of usual ~8192 on any other lossless decoder)
|
||||||
});
|
while let Some(mut _samples) = samples_stream
|
||||||
if s.write_all(rmp_serde::to_vec(&md).unwrap().as_slice()).is_err() {
|
.as_mut()
|
||||||
return true;
|
.chunks(if md.flac && md.track_length_secs > 1 {
|
||||||
}
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
let mut _samples = _samples.concat();
|
||||||
if war {
|
if war {
|
||||||
_samples.iter_mut().for_each(|sample| {
|
_samples.iter_mut().for_each(|sample| {
|
||||||
*sample = sample.signum() * 32767;
|
*sample = sample.signum() * 32767;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if !md.flac {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Launching lonelyradio on the router moment
|
// Launching lonelyradio on the router moment
|
||||||
if cfg!(target_endian = "big") {
|
if cfg!(target_endian = "big") {
|
||||||
_samples.iter_mut().for_each(|sample| {
|
_samples.iter_mut().for_each(|sample| {
|
||||||
|
@ -90,12 +124,42 @@ async fn stream_track(
|
||||||
if s.write_all(samples).is_err() {
|
if s.write_all(samples).is_err() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let encoded = flacenc::encode_with_fixed_block_size(
|
||||||
|
&flacenc::config::Encoder::default().into_verified().unwrap(),
|
||||||
|
MemSource::from_samples(
|
||||||
|
// I'm crying (It's just a burning memory)
|
||||||
|
&_samples.iter().map(|x| *x as i32).collect::<Vec<i32>>(),
|
||||||
|
md.channels as usize,
|
||||||
|
16,
|
||||||
|
md.sample_rate as usize,
|
||||||
|
),
|
||||||
|
256,
|
||||||
|
);
|
||||||
|
if encoded.is_err() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sink = flacenc::bitsink::ByteSink::new();
|
||||||
|
encoded.unwrap().write(&mut sink).unwrap();
|
||||||
|
|
||||||
|
let _md = Message::F(FragmentMetadata {
|
||||||
|
length: sink.as_slice().len() as u64,
|
||||||
|
});
|
||||||
|
if s.write_all(rmp_serde::to_vec(&_md).unwrap().as_slice()).is_err() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if s.write_all(sink.as_slice()).is_err() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
let args = Args::parse();
|
||||||
let listener = TcpListener::bind(Args::parse().address).await.unwrap();
|
let listener = TcpListener::bind(Args::parse().address).await.unwrap();
|
||||||
let tracklist = Arc::new(
|
let tracklist = Arc::new(
|
||||||
walkdir::WalkDir::new(Args::parse().dir)
|
walkdir::WalkDir::new(Args::parse().dir)
|
||||||
|
@ -108,7 +172,23 @@ async fn main() {
|
||||||
);
|
);
|
||||||
loop {
|
loop {
|
||||||
let (socket, _) = listener.accept().await.unwrap();
|
let (socket, _) = listener.accept().await.unwrap();
|
||||||
tokio::spawn(stream(socket, tracklist.clone()));
|
let s = socket.into_std().unwrap();
|
||||||
|
s.set_nonblocking(false).unwrap();
|
||||||
|
let s = if args.xor_key_file.is_some() {
|
||||||
|
Writer::XorEncrypted(
|
||||||
|
s,
|
||||||
|
match &*KEY {
|
||||||
|
Some(a) => a.clone(),
|
||||||
|
_ => {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Writer::Unencrypted(s)
|
||||||
|
};
|
||||||
|
tokio::spawn(stream(s, tracklist.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn is_not_hidden(entry: &DirEntry) -> bool {
|
fn is_not_hidden(entry: &DirEntry) -> bool {
|
||||||
|
@ -127,24 +207,9 @@ fn track_valid(track: &Path) -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn stream(s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
async fn stream(mut s: Writer, tracklist: Arc<Vec<PathBuf>>) {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
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 {
|
loop {
|
||||||
let track = tracklist.choose(&mut thread_rng()).unwrap().clone();
|
let track = tracklist.choose(&mut thread_rng()).unwrap().clone();
|
||||||
|
|
||||||
|
@ -152,7 +217,10 @@ async fn stream(s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
||||||
let mut artist = String::new();
|
let mut artist = String::new();
|
||||||
let mut album = String::new();
|
let mut album = String::new();
|
||||||
let mut file = std::fs::File::open(&track).unwrap();
|
let mut file = std::fs::File::open(&track).unwrap();
|
||||||
let tagged = lofty::read_from(&mut file).unwrap();
|
let tagged = match lofty::read_from(&mut file) {
|
||||||
|
Ok(f) => f,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
if let Some(id3v2) = tagged.primary_tag() {
|
if let Some(id3v2) = tagged.primary_tag() {
|
||||||
title =
|
title =
|
||||||
id3v2.title().unwrap_or(track.file_stem().unwrap().to_string_lossy()).to_string();
|
id3v2.title().unwrap_or(track.file_stem().unwrap().to_string_lossy()).to_string();
|
||||||
|
@ -194,6 +262,7 @@ async fn stream(s: TcpStream, tracklist: Arc<Vec<PathBuf>>) {
|
||||||
TrackMetadata {
|
TrackMetadata {
|
||||||
track_length_frac: time.frac as f32,
|
track_length_frac: time.frac as f32,
|
||||||
track_length_secs: time.seconds,
|
track_length_secs: time.seconds,
|
||||||
|
flac: args.flac,
|
||||||
album,
|
album,
|
||||||
artist,
|
artist,
|
||||||
title,
|
title,
|
||||||
|
|
Loading…
Reference in a new issue