mirror of
https://github.com/talwat/lowfi
synced 2025-08-14 13:32:37 +00:00
chore: migrate to rodio v0.21.1 feat: add a currently nonfunctional source argument to the scrape command well, this is an important commit for anyone who has some interest in the workings of lowfi, so strap in. rodio finally updated cpal, so that ugly unsafe bit of code to implement send is no longer necessary since cpal did it themselves (a year ago) they also removed output stream handles and some other things which nobody really used anyway. next, the other file types is literally not even code, just adding a feature flag which enables the default rodio feature flags. i do feel a bit silly for thinking it'd be super difficult, so thanks rodio. lastly, the source flag is for a possible switch to the chillhop tracklist. anything embedded into lowfi should be generated with an open source script, hence if lowfi does switch to chillhop, it'll need to be selectable.
118 lines
3.5 KiB
Rust
118 lines
3.5 KiB
Rust
//! Responsible for the basic initialization & shutdown of the audio server & frontend.
|
|
|
|
use std::env;
|
|
use std::io::{stdout, IsTerminal};
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
|
|
use eyre::eyre;
|
|
use tokio::fs;
|
|
use tokio::{sync::mpsc, task};
|
|
|
|
use crate::messages::Messages;
|
|
use crate::player::ui;
|
|
use crate::player::Player;
|
|
use crate::Args;
|
|
|
|
/// This is the representation of the persistent volume,
|
|
/// which is loaded at startup and saved on shutdown.
|
|
#[derive(Clone, Copy)]
|
|
pub struct PersistentVolume {
|
|
/// The volume, as a percentage.
|
|
inner: u16,
|
|
}
|
|
|
|
impl PersistentVolume {
|
|
/// Retrieves the config directory.
|
|
async fn config() -> eyre::Result<PathBuf> {
|
|
let config = dirs::config_dir()
|
|
.ok_or_else(|| eyre!("Couldn't find config directory"))?
|
|
.join(PathBuf::from("lowfi"));
|
|
|
|
if !config.exists() {
|
|
fs::create_dir_all(&config).await?;
|
|
}
|
|
|
|
Ok(config)
|
|
}
|
|
|
|
/// Returns the volume as a float from 0 to 1.
|
|
pub fn float(self) -> f32 {
|
|
f32::from(self.inner) / 100.0
|
|
}
|
|
|
|
/// Loads the [`PersistentVolume`] from [`dirs::config_dir()`].
|
|
pub async fn load() -> eyre::Result<Self> {
|
|
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 trimmed = contents.trim();
|
|
let stripped = trimmed.strip_suffix("%").unwrap_or(trimmed);
|
|
stripped
|
|
.parse()
|
|
.map_err(|_error| eyre!("volume.txt file is invalid"))?
|
|
} else {
|
|
fs::write(&volume, "100").await?;
|
|
100u16
|
|
};
|
|
|
|
Ok(Self { inner: volume })
|
|
}
|
|
|
|
/// Saves `volume` to `volume.txt`.
|
|
pub async fn save(volume: f32) -> eyre::Result<()> {
|
|
let config = Self::config().await?;
|
|
let path = config.join(PathBuf::from("volume.txt"));
|
|
|
|
// Already rounded & absolute, therefore this should be safe.
|
|
#[expect(
|
|
clippy::as_conversions,
|
|
clippy::cast_sign_loss,
|
|
clippy::cast_possible_truncation
|
|
)]
|
|
let percentage = (volume * 100.0).abs().round() as u16;
|
|
|
|
fs::write(path, percentage.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<()> {
|
|
// Actually initializes the player.
|
|
// Stream kept here in the master thread to keep it alive.
|
|
let (player, stream) = Player::new(&args).await?;
|
|
let player = Arc::new(player);
|
|
|
|
// Initialize the UI, as well as the internal communication channel.
|
|
let (tx, rx) = mpsc::channel(8);
|
|
let ui = if stdout().is_terminal() && !(env::var("LOWFI_DISABLE_UI") == Ok("1".to_owned())) {
|
|
Some(task::spawn(ui::start(
|
|
Arc::clone(&player),
|
|
tx.clone(),
|
|
args.clone(),
|
|
)))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
// Sends the player an "init" signal telling it to start playing a song straight away.
|
|
tx.send(Messages::Init).await?;
|
|
|
|
// Actually starts the player.
|
|
Player::play(Arc::clone(&player), tx.clone(), rx, args.debug).await?;
|
|
|
|
// Save the volume.txt file for the next session.
|
|
PersistentVolume::save(player.sink.volume()).await?;
|
|
drop(stream);
|
|
player.sink.stop();
|
|
ui.and_then(|x| Some(x.abort()));
|
|
|
|
Ok(())
|
|
}
|