mirror of
https://github.com/talwat/lowfi
synced 2025-01-13 20:01:27 +00:00
feat: improve buffering so that filling the queue happens in the background
This commit is contained in:
parent
f0e56ea2aa
commit
1d5af7dc3e
48
Cargo.lock
generated
48
Cargo.lock
generated
@ -349,6 +349,31 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm"
|
||||||
|
version = "0.28.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.6.0",
|
||||||
|
"crossterm_winapi",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"rustix",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossterm_winapi"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cssparser"
|
name = "cssparser"
|
||||||
version = "0.31.2"
|
version = "0.31.2"
|
||||||
@ -928,6 +953,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
|
"crossterm",
|
||||||
"eyre",
|
"eyre",
|
||||||
"futures",
|
"futures",
|
||||||
"rand",
|
"rand",
|
||||||
@ -1021,6 +1047,7 @@ checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
"hermit-abi",
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
@ -1759,6 +1786,27 @@ version = "1.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
|
@ -13,3 +13,4 @@ eyre = "0.6.12"
|
|||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
bytes = "1.7.2"
|
bytes = "1.7.2"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
crossterm = "0.28.1"
|
||||||
|
@ -2,6 +2,7 @@ use clap::{Parser, Subcommand};
|
|||||||
|
|
||||||
mod scrape;
|
mod scrape;
|
||||||
mod tracks;
|
mod tracks;
|
||||||
|
mod player;
|
||||||
|
|
||||||
/// An extremely simple lofi player.
|
/// An extremely simple lofi player.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
@ -15,7 +16,7 @@ struct Args {
|
|||||||
enum Commands {
|
enum Commands {
|
||||||
/// Scrapes the lofi girl website file server for mp3 files.
|
/// Scrapes the lofi girl website file server for mp3 files.
|
||||||
Scrape,
|
Scrape,
|
||||||
/// Plays a single, random, track.
|
/// Starts the player.
|
||||||
Play
|
Play
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,6 +26,6 @@ async fn main() -> eyre::Result<()> {
|
|||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Commands::Scrape => scrape::scrape().await,
|
Commands::Scrape => scrape::scrape().await,
|
||||||
Commands::Play => tracks::random().await
|
Commands::Play => player::play().await
|
||||||
}
|
}
|
||||||
}
|
}
|
34
src/play.rs
34
src/play.rs
@ -1,34 +0,0 @@
|
|||||||
use std::{io::Cursor, time::Duration};
|
|
||||||
|
|
||||||
use rodio::{Decoder, OutputStream, Sink, Source};
|
|
||||||
use tokio::time::sleep;
|
|
||||||
|
|
||||||
pub async fn download() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn play(track: &str) -> eyre::Result<()> {
|
|
||||||
eprintln!("downloading {}...", track);
|
|
||||||
let url = format!("https://lofigirl.com/wp-content/uploads/{}", track);
|
|
||||||
let file = Cursor::new(reqwest::get(url).await?.bytes().await?);
|
|
||||||
|
|
||||||
let source = Decoder::new(file).unwrap();
|
|
||||||
|
|
||||||
let (stream, stream_handle) = OutputStream::try_default().unwrap();
|
|
||||||
let sink = Sink::try_new(&stream_handle).unwrap();
|
|
||||||
sink.append(source);
|
|
||||||
|
|
||||||
eprintln!("playing {}...", track);
|
|
||||||
sink.sleep_until_end();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn random() -> eyre::Result<()> {
|
|
||||||
let tracks = include_str!("../data/tracks.txt");
|
|
||||||
let tracks: Vec<&str> = tracks.split_ascii_whitespace().collect();
|
|
||||||
|
|
||||||
play(tracks[0]).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
103
src/player.rs
Normal file
103
src/player.rs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
use std::{collections::VecDeque, sync::Arc, time::Duration};
|
||||||
|
|
||||||
|
use rodio::{Decoder, OutputStream, Sink, Source};
|
||||||
|
use tokio::{
|
||||||
|
sync::{mpsc, RwLock},
|
||||||
|
task,
|
||||||
|
time::sleep,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// The amount of songs to buffer up.
|
||||||
|
const BUFFER_SIZE: usize = 5;
|
||||||
|
|
||||||
|
use crate::tracks::{self};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub struct Track {
|
||||||
|
pub name: &'static str,
|
||||||
|
pub data: tracks::Data,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Track {
|
||||||
|
pub async fn random() -> eyre::Result<Self> {
|
||||||
|
let name = tracks::random().await?;
|
||||||
|
let data = tracks::download(&name).await?;
|
||||||
|
|
||||||
|
Ok(Self { name, data })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Queue {
|
||||||
|
tracks: Arc<RwLock<VecDeque<Track>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Queue {
|
||||||
|
pub async fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tracks: Arc::new(RwLock::new(VecDeque::with_capacity(5))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get(&self) -> eyre::Result<Track> {
|
||||||
|
// This refills the queue in the background.
|
||||||
|
let tracks = self.tracks.clone();
|
||||||
|
task::spawn(async move {
|
||||||
|
while tracks.read().await.len() < BUFFER_SIZE {
|
||||||
|
let track = Track::random().await.unwrap();
|
||||||
|
tracks.write().await.push_back(track);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let track = self.tracks.write().await.pop_front();
|
||||||
|
let track = match track {
|
||||||
|
Some(x) => x,
|
||||||
|
// If the queue is completely empty, then fallback to simply getting a new track.
|
||||||
|
// This is relevant particularly at the first song.
|
||||||
|
None => Track::random().await?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(track)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn play() -> eyre::Result<()> {
|
||||||
|
let queue = Queue::new().await;
|
||||||
|
|
||||||
|
let (stream, handle) = OutputStream::try_default().unwrap();
|
||||||
|
let sink = Sink::try_new(&handle).unwrap();
|
||||||
|
|
||||||
|
crossterm::terminal::enable_raw_mode()?;
|
||||||
|
|
||||||
|
// TODO: Reintroduce the Player struct and seperate
|
||||||
|
// input/display from song playing so that quits & skips
|
||||||
|
// are instant.
|
||||||
|
loop {
|
||||||
|
sink.stop();
|
||||||
|
|
||||||
|
let track = queue.get().await?;
|
||||||
|
sink.append(Decoder::new(track.data)?);
|
||||||
|
|
||||||
|
match crossterm::event::read()? {
|
||||||
|
crossterm::event::Event::Key(event) => {
|
||||||
|
match event.code {
|
||||||
|
crossterm::event::KeyCode::Char(x) => {
|
||||||
|
if x == 's' {
|
||||||
|
continue;
|
||||||
|
} else if x == 'q' {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(Duration::from_secs(2)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
crossterm::terminal::disable_raw_mode()?;
|
||||||
|
sink.stop();
|
||||||
|
drop(stream);
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -2,38 +2,22 @@ use std::io::Cursor;
|
|||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rodio::{Decoder, OutputStream, Sink};
|
|
||||||
|
|
||||||
pub async fn download(track: &str) -> eyre::Result<Decoder<Cursor<Bytes>>> {
|
pub type Data = Cursor<Bytes>;
|
||||||
|
|
||||||
|
pub async fn download(track: &str) -> eyre::Result<Data> {
|
||||||
let url = format!("https://lofigirl.com/wp-content/uploads/{}", track);
|
let url = format!("https://lofigirl.com/wp-content/uploads/{}", track);
|
||||||
let file = Cursor::new(reqwest::get(url).await?.bytes().await?);
|
let file = Cursor::new(reqwest::get(url).await?.bytes().await?);
|
||||||
let source = Decoder::new(file).unwrap();
|
|
||||||
|
|
||||||
Ok(source)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn play(source: Decoder<Cursor<Bytes>>) -> eyre::Result<()> {
|
pub async fn random() -> eyre::Result<&'static str> {
|
||||||
let (stream, stream_handle) = OutputStream::try_default()?;
|
|
||||||
let sink = Sink::try_new(&stream_handle)?;
|
|
||||||
sink.append(source);
|
|
||||||
|
|
||||||
sink.sleep_until_end();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn random() -> eyre::Result<()> {
|
|
||||||
let tracks = include_str!("../data/tracks.txt");
|
let tracks = include_str!("../data/tracks.txt");
|
||||||
let tracks: Vec<&str> = tracks.split_ascii_whitespace().collect();
|
let tracks: Vec<&str> = tracks.split_ascii_whitespace().collect();
|
||||||
|
|
||||||
let random = rand::thread_rng().gen_range(0..tracks.len());
|
let random = rand::thread_rng().gen_range(0..tracks.len());
|
||||||
let track = tracks[random];
|
let track = tracks[random];
|
||||||
|
|
||||||
eprintln!("downloading {}...", track);
|
Ok(track)
|
||||||
let source = download(track).await?;
|
|
||||||
|
|
||||||
eprintln!("playing {}...", track);
|
|
||||||
play(source).await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user