mirror of
https://github.com/talwat/lowfi
synced 2025-07-17 23:52:28 +00:00
chore: restructure and clean up
This commit is contained in:
parent
b6a81c9634
commit
6fadfe6304
@ -119,14 +119,7 @@ pub async fn play(args: Args) -> eyre::Result<()> {
|
|||||||
tx.send(Messages::Init).await?;
|
tx.send(Messages::Init).await?;
|
||||||
|
|
||||||
// Actually starts the player.
|
// Actually starts the player.
|
||||||
Player::play(
|
Player::play(Arc::clone(&player), tx.clone(), rx, args.debug).await?;
|
||||||
Arc::clone(&player),
|
|
||||||
tx.clone(),
|
|
||||||
rx,
|
|
||||||
args.buffer_size,
|
|
||||||
args.debug,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Save the volume.txt file for the next session.
|
// Save the volume.txt file for the next session.
|
||||||
PersistentVolume::save(player.sink.volume()).await?;
|
PersistentVolume::save(player.sink.volume()).await?;
|
||||||
|
131
src/player.rs
131
src/player.rs
@ -22,7 +22,6 @@ use tokio::{
|
|||||||
RwLock,
|
RwLock,
|
||||||
},
|
},
|
||||||
task,
|
task,
|
||||||
time::sleep,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "mpris")]
|
#[cfg(feature = "mpris")]
|
||||||
@ -31,11 +30,14 @@ use mpris_server::{PlaybackStatus, PlayerInterface, Property};
|
|||||||
use crate::{
|
use crate::{
|
||||||
messages::Messages,
|
messages::Messages,
|
||||||
play::{PersistentVolume, SendableOutputStream},
|
play::{PersistentVolume, SendableOutputStream},
|
||||||
tracks::{self, bookmark, list::List},
|
tracks::{self, list::List},
|
||||||
Args,
|
Args,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod audio;
|
||||||
|
pub mod bookmark;
|
||||||
pub mod downloader;
|
pub mod downloader;
|
||||||
|
pub mod queue;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
|
|
||||||
#[cfg(feature = "mpris")]
|
#[cfg(feature = "mpris")]
|
||||||
@ -55,6 +57,9 @@ pub struct Player {
|
|||||||
/// [rodio]'s [`Sink`] which can control playback.
|
/// [rodio]'s [`Sink`] which can control playback.
|
||||||
pub sink: Sink,
|
pub sink: Sink,
|
||||||
|
|
||||||
|
/// The internal buffer size.
|
||||||
|
pub buffer_size: usize,
|
||||||
|
|
||||||
/// Whether the current track has been bookmarked.
|
/// Whether the current track has been bookmarked.
|
||||||
bookmarked: AtomicBool,
|
bookmarked: AtomicBool,
|
||||||
|
|
||||||
@ -85,46 +90,6 @@ pub struct Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
/// This gets the output stream while also shutting up alsa with [libc].
|
|
||||||
/// Uses raw libc calls, and therefore is functional only on Linux.
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
fn silent_get_output_stream() -> eyre::Result<(OutputStream, OutputStreamHandle)> {
|
|
||||||
use libc::freopen;
|
|
||||||
use std::ffi::CString;
|
|
||||||
|
|
||||||
// Get the file descriptor to stderr from libc.
|
|
||||||
extern "C" {
|
|
||||||
static stderr: *mut libc::FILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a bit of an ugly hack that basically just uses `libc` to redirect alsa's
|
|
||||||
// output to `/dev/null` so that it wont be shoved down our throats.
|
|
||||||
|
|
||||||
// The mode which to redirect terminal output with.
|
|
||||||
let mode = CString::new("w")?;
|
|
||||||
|
|
||||||
// First redirect to /dev/null, which basically silences alsa.
|
|
||||||
let null = CString::new("/dev/null")?;
|
|
||||||
|
|
||||||
// SAFETY: Simple enough to be impossible to fail. Hopefully.
|
|
||||||
unsafe {
|
|
||||||
freopen(null.as_ptr(), mode.as_ptr(), stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the OutputStream while stderr is still redirected to /dev/null.
|
|
||||||
let (stream, handle) = OutputStream::try_default()?;
|
|
||||||
|
|
||||||
// Redirect back to the current terminal, so that other output isn't silenced.
|
|
||||||
let tty = CString::new("/dev/tty")?;
|
|
||||||
|
|
||||||
// SAFETY: See the first call to `freopen`.
|
|
||||||
unsafe {
|
|
||||||
freopen(tty.as_ptr(), mode.as_ptr(), stderr);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok((stream, handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Just a shorthand for setting `current`.
|
/// Just a shorthand for setting `current`.
|
||||||
fn set_current(&self, info: tracks::Info) {
|
fn set_current(&self, info: tracks::Info) {
|
||||||
self.current.store(Some(Arc::new(info)));
|
self.current.store(Some(Arc::new(info)));
|
||||||
@ -153,7 +118,7 @@ impl Player {
|
|||||||
// We should only shut up alsa forcefully on Linux if we really have to.
|
// We should only shut up alsa forcefully on Linux if we really have to.
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let (stream, handle) = if !args.alternate && !args.debug {
|
let (stream, handle) = if !args.alternate && !args.debug {
|
||||||
Self::silent_get_output_stream()?
|
audio::silent_get_output_stream()?
|
||||||
} else {
|
} else {
|
||||||
OutputStream::try_default()?
|
OutputStream::try_default()?
|
||||||
};
|
};
|
||||||
@ -178,6 +143,7 @@ impl Player {
|
|||||||
|
|
||||||
let player = Self {
|
let player = Self {
|
||||||
tracks: RwLock::new(VecDeque::with_capacity(args.buffer_size)),
|
tracks: RwLock::new(VecDeque::with_capacity(args.buffer_size)),
|
||||||
|
buffer_size: args.buffer_size,
|
||||||
current: ArcSwapOption::new(None),
|
current: ArcSwapOption::new(None),
|
||||||
client,
|
client,
|
||||||
sink,
|
sink,
|
||||||
@ -190,80 +156,6 @@ impl Player {
|
|||||||
Ok((player, SendableOutputStream(stream)))
|
Ok((player, SendableOutputStream(stream)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This will play the next track, as well as refilling the buffer in the background.
|
|
||||||
///
|
|
||||||
/// This will also set `current` to the newly loaded song.
|
|
||||||
pub async fn next(&self) -> Result<tracks::Decoded, tracks::TrackError> {
|
|
||||||
// TODO: Consider replacing this with `unwrap_or_else` when async closures are stablized.
|
|
||||||
let track = self.tracks.write().await.pop_front();
|
|
||||||
let track = if let Some(track) = track {
|
|
||||||
track
|
|
||||||
} else {
|
|
||||||
// If the queue is completely empty, then fallback to simply getting a new track.
|
|
||||||
// This is relevant particularly at the first song.
|
|
||||||
|
|
||||||
// Serves as an indicator that the queue is "loading".
|
|
||||||
// We're doing it here so that we don't get the "loading" display
|
|
||||||
// for only a frame in the other case that the buffer is not empty.
|
|
||||||
self.current.store(None);
|
|
||||||
self.list.random(&self.client).await?
|
|
||||||
};
|
|
||||||
|
|
||||||
let decoded = track.decode()?;
|
|
||||||
|
|
||||||
// Set the current track.
|
|
||||||
self.set_current(decoded.info.clone());
|
|
||||||
|
|
||||||
Ok(decoded)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This basically just calls [`Player::next`], and then appends the new track to the player.
|
|
||||||
///
|
|
||||||
/// This also notifies the background thread to get to work, and will send `TryAgain`
|
|
||||||
/// if it fails. This functions purpose is to be called in the background, so that
|
|
||||||
/// when the audio server recieves a `Next` signal it will still be able to respond to other
|
|
||||||
/// signals while it's loading.
|
|
||||||
///
|
|
||||||
/// This also sends the `NewSong` signal to `tx` apon successful completion.
|
|
||||||
async fn handle_next(
|
|
||||||
player: Arc<Self>,
|
|
||||||
itx: Sender<()>,
|
|
||||||
tx: Sender<Messages>,
|
|
||||||
debug: bool,
|
|
||||||
) -> eyre::Result<()> {
|
|
||||||
// Stop the sink.
|
|
||||||
player.sink.stop();
|
|
||||||
|
|
||||||
let track = player.next().await;
|
|
||||||
|
|
||||||
match track {
|
|
||||||
Ok(track) => {
|
|
||||||
// Start playing the new track.
|
|
||||||
player.sink.append(track.data);
|
|
||||||
|
|
||||||
// Notify the background downloader that there's an empty spot
|
|
||||||
// in the buffer.
|
|
||||||
Downloader::notify(&itx).await?;
|
|
||||||
|
|
||||||
// Notify the audio server that the next song has actually been downloaded.
|
|
||||||
tx.send(Messages::NewSong).await?;
|
|
||||||
}
|
|
||||||
Err(error) => {
|
|
||||||
if !error.is_timeout() {
|
|
||||||
if debug {
|
|
||||||
panic!("{:?}", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(TIMEOUT).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
tx.send(Messages::TryAgain).await?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is the main "audio server".
|
/// This is the main "audio server".
|
||||||
///
|
///
|
||||||
/// `rx` & `tx` are used to communicate with it, for example when to
|
/// `rx` & `tx` are used to communicate with it, for example when to
|
||||||
@ -275,7 +167,6 @@ impl Player {
|
|||||||
player: Arc<Self>,
|
player: Arc<Self>,
|
||||||
tx: Sender<Messages>,
|
tx: Sender<Messages>,
|
||||||
mut rx: Receiver<Messages>,
|
mut rx: Receiver<Messages>,
|
||||||
buf_size: usize,
|
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<()> {
|
||||||
// Initialize the mpris player.
|
// Initialize the mpris player.
|
||||||
@ -292,7 +183,7 @@ impl Player {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// `itx` is used to notify the `Downloader` when it needs to download new tracks.
|
// `itx` is used to notify the `Downloader` when it needs to download new tracks.
|
||||||
let downloader = Downloader::new(Arc::clone(&player), buf_size);
|
let downloader = Downloader::new(Arc::clone(&player));
|
||||||
let (itx, downloader) = downloader.start(debug);
|
let (itx, downloader) = downloader.start(debug);
|
||||||
|
|
||||||
// Start buffering tracks immediately.
|
// Start buffering tracks immediately.
|
||||||
@ -345,7 +236,7 @@ impl Player {
|
|||||||
|
|
||||||
// Handle the rest of the signal in the background,
|
// Handle the rest of the signal in the background,
|
||||||
// as to not block the main audio server thread.
|
// as to not block the main audio server thread.
|
||||||
task::spawn(Self::handle_next(
|
task::spawn(Self::next(
|
||||||
Arc::clone(&player),
|
Arc::clone(&player),
|
||||||
itx.clone(),
|
itx.clone(),
|
||||||
tx.clone(),
|
tx.clone(),
|
||||||
|
42
src/player/audio.rs
Normal file
42
src/player/audio.rs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use rodio::{OutputStream, OutputStreamHandle};
|
||||||
|
|
||||||
|
/// This gets the output stream while also shutting up alsa with [libc].
|
||||||
|
/// Uses raw libc calls, and therefore is functional only on Linux.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn silent_get_output_stream() -> eyre::Result<(OutputStream, OutputStreamHandle)> {
|
||||||
|
use libc::freopen;
|
||||||
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
// Get the file descriptor to stderr from libc.
|
||||||
|
extern "C" {
|
||||||
|
static stderr: *mut libc::FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a bit of an ugly hack that basically just uses `libc` to redirect alsa's
|
||||||
|
// output to `/dev/null` so that it wont be shoved down our throats.
|
||||||
|
|
||||||
|
// The mode which to redirect terminal output with.
|
||||||
|
let mode = CString::new("w")?;
|
||||||
|
|
||||||
|
// First redirect to /dev/null, which basically silences alsa.
|
||||||
|
let null = CString::new("/dev/null")?;
|
||||||
|
|
||||||
|
// SAFETY: Simple enough to be impossible to fail. Hopefully.
|
||||||
|
unsafe {
|
||||||
|
freopen(null.as_ptr(), mode.as_ptr(), stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the OutputStream while stderr is still redirected to /dev/null.
|
||||||
|
let (stream, handle) = OutputStream::try_default()?;
|
||||||
|
|
||||||
|
// Redirect back to the current terminal, so that other output isn't silenced.
|
||||||
|
let tty = CString::new("/dev/tty")?;
|
||||||
|
|
||||||
|
// SAFETY: See the first call to `freopen`.
|
||||||
|
unsafe {
|
||||||
|
freopen(tty.as_ptr(), mode.as_ptr(), stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((stream, handle))
|
||||||
|
}
|
@ -24,9 +24,6 @@ pub struct Downloader {
|
|||||||
/// A copy of the internal sender, which can be useful for keeping
|
/// A copy of the internal sender, which can be useful for keeping
|
||||||
/// track of it.
|
/// track of it.
|
||||||
tx: Sender<()>,
|
tx: Sender<()>,
|
||||||
|
|
||||||
/// The size of the internal download buffer.
|
|
||||||
buf_size: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Downloader {
|
impl Downloader {
|
||||||
@ -40,40 +37,41 @@ impl Downloader {
|
|||||||
///
|
///
|
||||||
/// This also sends a [`Sender`] which can be used to notify
|
/// This also sends a [`Sender`] which can be used to notify
|
||||||
/// when the downloader needs to begin downloading more tracks.
|
/// when the downloader needs to begin downloading more tracks.
|
||||||
pub fn new(player: Arc<Player>, buf_size: usize) -> Self {
|
pub fn new(player: Arc<Player>) -> Self {
|
||||||
let (tx, rx) = mpsc::channel(8);
|
let (tx, rx) = mpsc::channel(8);
|
||||||
Self {
|
Self { player, rx, tx }
|
||||||
player,
|
}
|
||||||
rx,
|
|
||||||
tx,
|
/// Push a new, random track onto the internal buffer.
|
||||||
buf_size,
|
pub async fn push_buffer(&self, debug: bool) {
|
||||||
|
let data = self.player.list.random(&self.player.client).await;
|
||||||
|
match data {
|
||||||
|
Ok(track) => self.player.tracks.write().await.push_back(track),
|
||||||
|
Err(error) if !error.is_timeout() => {
|
||||||
|
if debug {
|
||||||
|
panic!("{}", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(TIMEOUT).await;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Actually starts & consumes the [Downloader].
|
/// Actually starts & consumes the [Downloader].
|
||||||
pub fn start(mut self, debug: bool) -> (Sender<()>, JoinHandle<()>) {
|
pub fn start(mut self, debug: bool) -> (Sender<()>, JoinHandle<()>) {
|
||||||
(
|
let tx = self.tx.clone();
|
||||||
self.tx,
|
|
||||||
task::spawn(async move {
|
|
||||||
// Loop through each update notification.
|
|
||||||
while self.rx.recv().await == Some(()) {
|
|
||||||
// For each update notification, we'll push tracks until the buffer is completely full.
|
|
||||||
while self.player.tracks.read().await.len() < self.buf_size {
|
|
||||||
let data = self.player.list.random(&self.player.client).await;
|
|
||||||
match data {
|
|
||||||
Ok(track) => self.player.tracks.write().await.push_back(track),
|
|
||||||
Err(error) if !error.is_timeout() => {
|
|
||||||
if debug {
|
|
||||||
panic!("{}", error)
|
|
||||||
}
|
|
||||||
|
|
||||||
sleep(TIMEOUT).await;
|
let handle = task::spawn(async move {
|
||||||
}
|
// Loop through each update notification.
|
||||||
_ => {}
|
while self.rx.recv().await == Some(()) {
|
||||||
}
|
// For each update notification, we'll push tracks until the buffer is completely full.
|
||||||
}
|
while self.player.tracks.read().await.len() < self.player.buffer_size {
|
||||||
|
self.push_buffer(debug).await;
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
)
|
});
|
||||||
|
|
||||||
|
return (tx, handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
81
src/player/queue.rs
Normal file
81
src/player/queue.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
use tokio::{sync::mpsc::Sender, time::sleep};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
messages::Messages,
|
||||||
|
player::{downloader::Downloader, Player, TIMEOUT},
|
||||||
|
tracks,
|
||||||
|
};
|
||||||
|
|
||||||
|
impl Player {
|
||||||
|
/// Fetches the next track from the queue, or a random track if the queue is empty.
|
||||||
|
/// This will also set the current track to the fetched track's info.
|
||||||
|
async fn fetch(&self) -> Result<tracks::Decoded, tracks::TrackError> {
|
||||||
|
// TODO: Consider replacing this with `unwrap_or_else` when async closures are stablized.
|
||||||
|
let track = self.tracks.write().await.pop_front();
|
||||||
|
let track = if let Some(track) = track {
|
||||||
|
track
|
||||||
|
} else {
|
||||||
|
// If the queue is completely empty, then fallback to simply getting a new track.
|
||||||
|
// This is relevant particularly at the first song.
|
||||||
|
|
||||||
|
// Serves as an indicator that the queue is "loading".
|
||||||
|
// We're doing it here so that we don't get the "loading" display
|
||||||
|
// for only a frame in the other case that the buffer is not empty.
|
||||||
|
self.current.store(None);
|
||||||
|
self.list.random(&self.client).await?
|
||||||
|
};
|
||||||
|
|
||||||
|
let decoded = track.decode()?;
|
||||||
|
|
||||||
|
// Set the current track.
|
||||||
|
self.set_current(decoded.info.clone());
|
||||||
|
|
||||||
|
Ok(decoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets, decodes, and plays the next track in the queue while also handling the downloader.
|
||||||
|
///
|
||||||
|
/// This functions purpose is to be called in the background, so that when the audio server recieves a
|
||||||
|
/// `Next` signal it will still be able to respond to other signals while it's loading.
|
||||||
|
///
|
||||||
|
/// This also sends the either a `NewSong` or `TryAgain` signal to `tx`.
|
||||||
|
pub async fn next(
|
||||||
|
player: Arc<Self>,
|
||||||
|
itx: Sender<()>,
|
||||||
|
tx: Sender<Messages>,
|
||||||
|
debug: bool,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
// Stop the sink.
|
||||||
|
player.sink.stop();
|
||||||
|
|
||||||
|
let track = player.fetch().await;
|
||||||
|
|
||||||
|
match track {
|
||||||
|
Ok(track) => {
|
||||||
|
// Start playing the new track.
|
||||||
|
player.sink.append(track.data);
|
||||||
|
|
||||||
|
// Notify the background downloader that there's an empty spot
|
||||||
|
// in the buffer.
|
||||||
|
Downloader::notify(&itx).await?;
|
||||||
|
|
||||||
|
// Notify the audio server that the next song has actually been downloaded.
|
||||||
|
tx.send(Messages::NewSong).await?;
|
||||||
|
}
|
||||||
|
Err(error) => {
|
||||||
|
if !error.is_timeout() {
|
||||||
|
if debug {
|
||||||
|
panic!("{:?}", error)
|
||||||
|
}
|
||||||
|
|
||||||
|
sleep(TIMEOUT).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
tx.send(Messages::TryAgain).await?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
@ -28,6 +28,7 @@ use crossterm::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use thiserror::Error;
|
||||||
use tokio::{sync::mpsc::Sender, task, time::sleep};
|
use tokio::{sync::mpsc::Sender, task, time::sleep};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
@ -36,6 +37,20 @@ use super::{Messages, Player};
|
|||||||
mod components;
|
mod components;
|
||||||
mod input;
|
mod input;
|
||||||
|
|
||||||
|
/// The error type for the UI, which is used to handle errors that occur
|
||||||
|
/// while drawing the UI or handling input.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum UIError {
|
||||||
|
#[error("unable to convert number")]
|
||||||
|
Conversion(#[from] std::num::TryFromIntError),
|
||||||
|
|
||||||
|
#[error("unable to write output")]
|
||||||
|
Write(#[from] std::io::Error),
|
||||||
|
|
||||||
|
#[error("sending message to backend failed")]
|
||||||
|
Communication(#[from] tokio::sync::mpsc::error::SendError<Messages>),
|
||||||
|
}
|
||||||
|
|
||||||
/// How long the audio bar will be visible for when audio is adjusted.
|
/// How long the audio bar will be visible for when audio is adjusted.
|
||||||
/// This is in frames.
|
/// This is in frames.
|
||||||
const AUDIO_BAR_DURATION: usize = 10;
|
const AUDIO_BAR_DURATION: usize = 10;
|
||||||
@ -100,7 +115,7 @@ impl Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Actually draws the window, with each element in `content` being on a new line.
|
/// Actually draws the window, with each element in `content` being on a new line.
|
||||||
pub fn draw(&mut self, content: Vec<String>, space: bool) -> eyre::Result<()> {
|
pub fn draw(&mut self, content: Vec<String>, space: bool) -> eyre::Result<(), UIError> {
|
||||||
let len: u16 = content.len().try_into()?;
|
let len: u16 = content.len().try_into()?;
|
||||||
|
|
||||||
// Note that this will have a trailing newline, which we use later.
|
// Note that this will have a trailing newline, which we use later.
|
||||||
@ -151,7 +166,7 @@ async fn interface(
|
|||||||
borderless: bool,
|
borderless: bool,
|
||||||
fps: u8,
|
fps: u8,
|
||||||
width: usize,
|
width: usize,
|
||||||
) -> eyre::Result<()> {
|
) -> eyre::Result<(), UIError> {
|
||||||
let mut window = Window::new(width, borderless);
|
let mut window = Window::new(width, borderless);
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@ -207,7 +222,7 @@ pub struct Environment {
|
|||||||
impl Environment {
|
impl Environment {
|
||||||
/// This prepares the terminal, returning an [Environment] helpful
|
/// This prepares the terminal, returning an [Environment] helpful
|
||||||
/// for cleaning up afterwards.
|
/// for cleaning up afterwards.
|
||||||
pub fn ready(alternate: bool) -> eyre::Result<Self> {
|
pub fn ready(alternate: bool) -> eyre::Result<Self, UIError> {
|
||||||
let mut lock = stdout().lock();
|
let mut lock = stdout().lock();
|
||||||
|
|
||||||
crossterm::execute!(lock, Hide)?;
|
crossterm::execute!(lock, Hide)?;
|
||||||
@ -234,7 +249,7 @@ impl Environment {
|
|||||||
|
|
||||||
/// Uses the information collected from initialization to safely close down
|
/// Uses the information collected from initialization to safely close down
|
||||||
/// the terminal & restore it to it's previous state.
|
/// the terminal & restore it to it's previous state.
|
||||||
pub fn cleanup(&self) -> eyre::Result<()> {
|
pub fn cleanup(&self) -> eyre::Result<(), UIError> {
|
||||||
let mut lock = stdout().lock();
|
let mut lock = stdout().lock();
|
||||||
|
|
||||||
if self.alternate {
|
if self.alternate {
|
||||||
@ -267,7 +282,11 @@ impl Drop for Environment {
|
|||||||
///
|
///
|
||||||
/// `alternate` controls whether to use [`EnterAlternateScreen`] in order to hide
|
/// `alternate` controls whether to use [`EnterAlternateScreen`] in order to hide
|
||||||
/// previous terminal history.
|
/// previous terminal history.
|
||||||
pub async fn start(player: Arc<Player>, sender: Sender<Messages>, args: Args) -> eyre::Result<()> {
|
pub async fn start(
|
||||||
|
player: Arc<Player>,
|
||||||
|
sender: Sender<Messages>,
|
||||||
|
args: Args,
|
||||||
|
) -> eyre::Result<(), UIError> {
|
||||||
let environment = Environment::ready(args.alternate)?;
|
let environment = Environment::ready(args.alternate)?;
|
||||||
let interface = task::spawn(interface(
|
let interface = task::spawn(interface(
|
||||||
Arc::clone(&player),
|
Arc::clone(&player),
|
||||||
|
@ -5,10 +5,13 @@ use crossterm::event::{self, EventStream, KeyCode, KeyEventKind, KeyModifiers};
|
|||||||
use futures::{FutureExt as _, StreamExt as _};
|
use futures::{FutureExt as _, StreamExt as _};
|
||||||
use tokio::sync::mpsc::Sender;
|
use tokio::sync::mpsc::Sender;
|
||||||
|
|
||||||
use crate::player::{ui, Messages};
|
use crate::player::{
|
||||||
|
ui::{self, UIError},
|
||||||
|
Messages,
|
||||||
|
};
|
||||||
|
|
||||||
/// Starts the listener to recieve input from the terminal for various events.
|
/// Starts the listener to recieve input from the terminal for various events.
|
||||||
pub async fn listen(sender: Sender<Messages>) -> eyre::Result<()> {
|
pub async fn listen(sender: Sender<Messages>) -> eyre::Result<(), UIError> {
|
||||||
let mut reader = EventStream::new();
|
let mut reader = EventStream::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
@ -25,9 +25,10 @@ use tokio::io;
|
|||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
|
|
||||||
pub mod bookmark;
|
|
||||||
pub mod list;
|
pub mod list;
|
||||||
|
|
||||||
|
/// The error type for the track system, which is used to handle errors that occur
|
||||||
|
/// while downloading, decoding, or playing tracks.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum TrackError {
|
pub enum TrackError {
|
||||||
#[error("timeout")]
|
#[error("timeout")]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user