From 6e457241d263c464c77b7277130949098e8fd8d3 Mon Sep 17 00:00:00 2001 From: talwat <83217276+talwat@users.noreply.github.com> Date: Tue, 24 Sep 2024 23:25:58 +0200 Subject: [PATCH] fix: make sure quits work properly --- src/main.rs | 2 + src/player.rs | 105 +++++++++++++++++++++++++------------------------- src/tracks.rs | 23 +++++++++-- 3 files changed, 74 insertions(+), 56 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8e257b0..eee1a4e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use std::process::exit; + use clap::{Parser, Subcommand}; mod scrape; diff --git a/src/player.rs b/src/player.rs index f3cda2b..5f895e6 100644 --- a/src/player.rs +++ b/src/player.rs @@ -1,36 +1,29 @@ 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::{ - sync::{mpsc, RwLock}, - task, - time::sleep, + sync::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 { - let name = tracks::random().await?; - let data = tracks::download(&name).await?; - - Ok(Self { name, data }) - } -} +use crate::tracks::Track; +/// 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 { tracks: Arc>>, } +unsafe impl Send for Queue {} +unsafe impl Sync for Queue {} + impl Queue { pub async fn new() -> Self { Self { @@ -38,13 +31,18 @@ impl Queue { } } - pub async fn get(&self) -> eyre::Result { + /// This will play the next track, as well as refilling the buffer in the background. + pub async fn next(&self, client: &Client) -> eyre::Result { // 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); + task::spawn({ + let client = client.clone(); + let tracks = self.tracks.clone(); + + async move { + while tracks.read().await.len() < BUFFER_SIZE { + let track = Track::random(&client).await.unwrap(); + tracks.write().await.push_back(track); + } } }); @@ -53,51 +51,52 @@ impl Queue { 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?, + None => Track::random(client).await?, }; - + 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<()> { 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 sink = Sink::try_new(&handle).unwrap(); + let audio = task::spawn(queue.clone().play(sink)); 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)?); - + 'a: loop { 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; - } + crossterm::event::Event::Key(event) => match event.code { + crossterm::event::KeyCode::Char(x) => { + if x == 'q' { + break 'a; } - _ => () } + _ => (), }, - _ => () + _ => (), } - - sleep(Duration::from_secs(2)).await; } + audio.abort(); crossterm::terminal::disable_raw_mode()?; - sink.stop(); - drop(stream); Ok(()) } diff --git a/src/tracks.rs b/src/tracks.rs index 9803a24..56fea32 100644 --- a/src/tracks.rs +++ b/src/tracks.rs @@ -2,17 +2,19 @@ use std::io::Cursor; use bytes::Bytes; use rand::Rng; +use reqwest::Client; pub type Data = Cursor; -pub async fn download(track: &str) -> eyre::Result { +async fn download(track: &str, client: &Client) -> eyre::Result { 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) } -pub async fn random() -> eyre::Result<&'static str> { +async fn random() -> eyre::Result<&'static str> { let tracks = include_str!("../data/tracks.txt"); let tracks: Vec<&str> = tracks.split_ascii_whitespace().collect(); @@ -20,4 +22,19 @@ pub async fn random() -> eyre::Result<&'static str> { let track = tracks[random]; 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 { + let name = random().await?; + let data = download(&name, client).await?; + + Ok(Self { name, data }) + } } \ No newline at end of file