diff --git a/Cargo.lock b/Cargo.lock index 5096b3b..7594b7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,6 +658,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + [[package]] name = "dtoa" version = "1.0.9" @@ -1257,6 +1278,16 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + [[package]] name = "linux-raw-sys" version = "0.4.14" @@ -1288,6 +1319,7 @@ dependencies = [ "bytes", "clap", "crossterm", + "dirs", "eyre", "futures", "lazy_static", @@ -1589,6 +1621,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "ordered-stream" version = "0.2.0" @@ -1849,6 +1887,17 @@ dependencies = [ "bitflags 2.6.0", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.11.0" @@ -2864,6 +2913,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2897,6 +2955,21 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2919,6 +2992,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -2931,6 +3010,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -2943,6 +3028,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2961,6 +3052,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -2973,6 +3070,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -2985,6 +3088,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -2997,6 +3106,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index 74d1584..9681806 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ rand = "0.8.5" tokio = { version = "1.40.0", features = [ "macros", "rt-multi-thread", + "fs" ], default-features = false } futures = "0.3.30" arc-swap = "1.7.1" @@ -37,11 +38,14 @@ arc-swap = "1.7.1" reqwest = "0.12.7" bytes = "1.7.2" +# I/O +crossterm = { version = "0.28.1", features = ["event-stream"] } +rodio = { version = "0.19.0", features = ["symphonia-mp3"], default-features = false } +mpris-server = { version = "0.8.1", optional = true } +dirs = "5.0.1" + # Misc scraper = "0.20.0" -rodio = { version = "0.19.0", features = ["symphonia-mp3"], default-features = false } -crossterm = { version = "0.28.1", features = ["event-stream"] } Inflector = "0.11.4" lazy_static = "1.5.0" libc = "0.2.159" -mpris-server = { version = "0.8.1", optional = true } diff --git a/src/play.rs b/src/play.rs index 447580a..c1f5d5c 100644 --- a/src/play.rs +++ b/src/play.rs @@ -1,23 +1,88 @@ //! Responsible for the basic initialization & shutdown of the audio server & frontend. +use std::path::PathBuf; use std::sync::Arc; +use eyre::eyre; +use tokio::fs; use tokio::{sync::mpsc, task}; use crate::player::Player; use crate::player::{ui, Messages}; use crate::Args; +/// The attributes that are applied at startup. +/// This includes the volume, but also the config file. +/// +/// The volume is seperated from the config since it specifically +/// will be written by lowfi, whereas the config will not. +pub struct InitialProperties { + /// The volume, as a percentage. + pub volume: u16, +} + +impl InitialProperties { + /// Retrieves the config directory. + async fn config() -> eyre::Result { + let config = dirs::config_dir() + .ok_or(eyre!("Couldn't find config directory"))? + .join(PathBuf::from("lowfi")); + + if !config.exists() { + fs::create_dir_all(&config).await?; + } + + Ok(config) + } + + /// Loads the [InitialProperties], including the config and volume file. + pub async fn load() -> eyre::Result { + let config = Self::config().await?; + + let volume = config.join(PathBuf::from("volume.txt")); + + // Basically just read from the volume file if it exists, otherwise return 100. + let volume = if volume.exists() { + let contents = fs::read_to_string(volume).await?; + let stripped = contents.strip_suffix("%").unwrap_or(&contents); + stripped + .parse() + .map_err(|_| eyre!("volume.txt file is invalid"))? + } else { + fs::write(&volume, "100").await?; + 100u16 + }; + + Ok(InitialProperties { volume }) + } + + /// Saves `volume.txt`, and uses the home directory which was previously acquired. + pub async fn save_volume(volume: f32) -> eyre::Result<()> { + let config = Self::config().await?; + let path = config.join(PathBuf::from("volume.txt")); + + fs::write(path, ((volume * 100.0).abs().round() as u16).to_string()).await?; + + Ok(()) + } +} + /// Initializes the audio server, and then safely stops /// it when the frontend quits. pub async fn play(args: Args) -> eyre::Result<()> { + // Load the initial properties (volume & config). + let properties = InitialProperties::load().await?; + let (tx, rx) = mpsc::channel(8); let player = Arc::new(Player::new(!args.alternate).await?); let ui = task::spawn(ui::start(Arc::clone(&player), tx.clone(), args)); tx.send(Messages::Init).await?; - Player::play(Arc::clone(&player), tx.clone(), rx).await?; + Player::play(Arc::clone(&player), properties, tx.clone(), rx).await?; + + // Save the volume.txt file for the next session. + InitialProperties::save_volume(player.sink.volume()).await?; player.sink.stop(); ui.abort(); diff --git a/src/player.rs b/src/player.rs index 9fac515..00fff24 100644 --- a/src/player.rs +++ b/src/player.rs @@ -18,7 +18,10 @@ use tokio::{ task, }; -use crate::tracks::{DecodedTrack, Track, TrackInfo}; +use crate::{ + play::InitialProperties, + tracks::{DecodedTrack, Track, TrackInfo}, +}; pub mod downloader; pub mod ui; @@ -236,6 +239,7 @@ impl Player { /// skip tracks or pause. pub async fn play( player: Arc, + properties: InitialProperties, tx: Sender, mut rx: Receiver, ) -> eyre::Result<()> { @@ -246,6 +250,9 @@ impl Player { // Start buffering tracks immediately. itx.send(()).await?; + // Set the initial sink volume to the one specified. + player.sink.set_volume(properties.volume as f32 / 100.0); + loop { let clone = Arc::clone(&player);