fix: make sure quits work properly

This commit is contained in:
talwat 2024-09-24 23:25:58 +02:00
parent 1d5af7dc3e
commit 6e457241d2
3 changed files with 74 additions and 56 deletions

View File

@ -1,3 +1,5 @@
use std::process::exit;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
mod scrape; mod scrape;

View File

@ -1,36 +1,29 @@
use std::{collections::VecDeque, sync::Arc, time::Duration}; use std::{collections::VecDeque, sync::Arc, time::Duration};
use rodio::{Decoder, OutputStream, Sink, Source}; use reqwest::Client;
use rodio::{source::SineWave, Decoder, OutputStream, OutputStreamHandle, Sink, Source};
use tokio::{ use tokio::{
sync::{mpsc, RwLock}, sync::RwLock,
task, task, time::sleep,
time::sleep,
}; };
/// The amount of songs to buffer up. /// The amount of songs to buffer up.
const BUFFER_SIZE: usize = 5; const BUFFER_SIZE: usize = 5;
use crate::tracks::{self}; use crate::tracks::Track;
#[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 })
}
}
/// Main struct responsible for queuing up tracks.
///
/// Internally tracks are stored in an [Arc],
/// so it's fine to clone this struct.
#[derive(Debug, Clone)]
pub struct Queue { pub struct Queue {
tracks: Arc<RwLock<VecDeque<Track>>>, tracks: Arc<RwLock<VecDeque<Track>>>,
} }
unsafe impl Send for Queue {}
unsafe impl Sync for Queue {}
impl Queue { impl Queue {
pub async fn new() -> Self { pub async fn new() -> Self {
Self { Self {
@ -38,14 +31,19 @@ impl Queue {
} }
} }
pub async fn get(&self) -> eyre::Result<Track> { /// This will play the next track, as well as refilling the buffer in the background.
pub async fn next(&self, client: &Client) -> eyre::Result<Track> {
// This refills the queue in the background. // This refills the queue in the background.
task::spawn({
let client = client.clone();
let tracks = self.tracks.clone(); let tracks = self.tracks.clone();
task::spawn(async move {
async move {
while tracks.read().await.len() < BUFFER_SIZE { while tracks.read().await.len() < BUFFER_SIZE {
let track = Track::random().await.unwrap(); let track = Track::random(&client).await.unwrap();
tracks.write().await.push_back(track); tracks.write().await.push_back(track);
} }
}
}); });
let track = self.tracks.write().await.pop_front(); let track = self.tracks.write().await.pop_front();
@ -53,51 +51,52 @@ impl Queue {
Some(x) => x, Some(x) => x,
// If the queue is completely empty, then fallback to simply getting a new track. // If the queue is completely empty, then fallback to simply getting a new track.
// This is relevant particularly at the first song. // This is relevant particularly at the first song.
None => Track::random().await?, None => Track::random(client).await?,
}; };
Ok(track) Ok(track)
} }
pub async fn play(self, sink: Sink) -> eyre::Result<()> {
let client = Client::builder().build()?;
let sink = Arc::new(sink);
loop {
sink.stop();
let track = self.next(&client).await?;
sink.append(Decoder::new(track.data)?);
let sink = sink.clone();
task::spawn_blocking(move || sink.sleep_until_end()).await?;
}
}
} }
pub async fn play() -> eyre::Result<()> { pub async fn play() -> eyre::Result<()> {
let queue = Queue::new().await; let queue = Queue::new().await;
let (stream, handle) = OutputStream::try_default()?;
let sink = Sink::try_new(&handle)?;
let (stream, handle) = OutputStream::try_default().unwrap(); let audio = task::spawn(queue.clone().play(sink));
let sink = Sink::try_new(&handle).unwrap();
crossterm::terminal::enable_raw_mode()?; crossterm::terminal::enable_raw_mode()?;
// TODO: Reintroduce the Player struct and seperate 'a: loop {
// 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()? { match crossterm::event::read()? {
crossterm::event::Event::Key(event) => { crossterm::event::Event::Key(event) => match event.code {
match event.code {
crossterm::event::KeyCode::Char(x) => { crossterm::event::KeyCode::Char(x) => {
if x == 's' { if x == 'q' {
continue; break 'a;
} else if x == 'q' {
break;
} }
} }
_ => () _ => (),
}
}, },
_ => () _ => (),
} }
sleep(Duration::from_secs(2)).await;
} }
audio.abort();
crossterm::terminal::disable_raw_mode()?; crossterm::terminal::disable_raw_mode()?;
sink.stop();
drop(stream);
Ok(()) Ok(())
} }

View File

@ -2,17 +2,19 @@ use std::io::Cursor;
use bytes::Bytes; use bytes::Bytes;
use rand::Rng; use rand::Rng;
use reqwest::Client;
pub type Data = Cursor<Bytes>; pub type Data = Cursor<Bytes>;
pub async fn download(track: &str) -> eyre::Result<Data> { async fn download(track: &str, client: &Client) -> 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 response = client.get(url).send().await?;
let file = Cursor::new(response.bytes().await?);
Ok(file) Ok(file)
} }
pub async fn random() -> eyre::Result<&'static str> { async fn random() -> eyre::Result<&'static str> {
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();
@ -21,3 +23,18 @@ pub async fn random() -> eyre::Result<&'static str> {
Ok(track) Ok(track)
} }
#[derive(Debug, PartialEq)]
pub struct Track {
pub name: &'static str,
pub data: Data,
}
impl Track {
pub async fn random(client: &Client) -> eyre::Result<Self> {
let name = random().await?;
let data = download(&name, client).await?;
Ok(Self { name, data })
}
}