mirror of
https://github.com/talwat/lowfi
synced 2025-12-19 13:13:21 +00:00
145 lines
4.3 KiB
Rust
145 lines
4.3 KiB
Rust
//! Has all of the structs for managing the state
|
|
//! of tracks, as well as downloading them & finding new ones.
|
|
//!
|
|
//! There are several structs which represent the different stages
|
|
//! that go on in downloading and playing tracks. When first queued,
|
|
//! the downloader will return a [`Queued`] track.
|
|
//!
|
|
//! Then, when it's time to play the track, it is decoded into
|
|
//! a [`Decoded`] track, which includes all the information
|
|
//! in the form of [`Info`].
|
|
|
|
use std::{fmt::Debug, io::Cursor, time::Duration};
|
|
|
|
use bytes::Bytes;
|
|
use rodio::{Decoder, Source as _};
|
|
use unicode_segmentation::UnicodeSegmentation as _;
|
|
|
|
pub mod list;
|
|
pub use list::List;
|
|
pub mod error;
|
|
pub mod format;
|
|
pub use error::{Error, Result};
|
|
|
|
use crate::tracks::error::WithTrackContext as _;
|
|
|
|
/// Just a shorthand for a decoded [Bytes].
|
|
pub type DecodedData = Decoder<Cursor<Bytes>>;
|
|
|
|
/// Tracks which are still waiting in the queue, and can't be played yet.
|
|
///
|
|
/// This means that only the data & track name are included.
|
|
#[derive(PartialEq, Eq)]
|
|
pub struct Queued {
|
|
/// Display name of the track.
|
|
pub display: String,
|
|
|
|
/// Full downloadable path/url of the track.
|
|
pub path: String,
|
|
|
|
/// The raw data of the track, which is not decoded and
|
|
/// therefore much more memory efficient.
|
|
pub data: Bytes,
|
|
}
|
|
|
|
impl Debug for Queued {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("Queued")
|
|
.field("display", &self.display)
|
|
.field("path", &self.path)
|
|
.field("data", &self.data.len())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl Queued {
|
|
/// This will actually decode and format the track,
|
|
/// returning a [`DecodedTrack`] which can be played
|
|
/// and also has a duration & formatted name.
|
|
pub fn decode(self) -> Result<Decoded> {
|
|
Decoded::new(self)
|
|
}
|
|
|
|
/// Creates a new queued track.
|
|
pub fn new(path: String, data: Bytes, display: Option<String>) -> Result<Self> {
|
|
let display = match display {
|
|
None => self::format::name(&path)?,
|
|
Some(custom) => custom,
|
|
};
|
|
|
|
Ok(Self {
|
|
display,
|
|
path,
|
|
data,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// The [`Info`] struct, which has the name and duration of a track.
|
|
///
|
|
/// This is not included in [Track] as the duration has to be acquired
|
|
/// from the decoded data and not from the raw data.
|
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
|
pub struct Info {
|
|
/// The full downloadable path/url of the track.
|
|
pub path: String,
|
|
|
|
/// This is a formatted name, so it doesn't include the full path.
|
|
pub display: String,
|
|
|
|
/// This is the *actual* terminal width of the track name, used to make
|
|
/// the UI consistent.
|
|
pub width: usize,
|
|
|
|
/// The duration of the track, this is an [Option] because there are
|
|
/// cases where the duration of a track is unknown.
|
|
pub duration: Option<Duration>,
|
|
}
|
|
|
|
impl Info {
|
|
/// Converts the info back into a full track list entry.
|
|
pub fn to_entry(&self) -> String {
|
|
let mut entry = self.path.clone();
|
|
entry.push('!');
|
|
entry.push_str(&self.display);
|
|
|
|
entry
|
|
}
|
|
|
|
/// Creates a new [`Info`] from decoded data & the queued track.
|
|
pub fn new(decoded: &DecodedData, path: String, display: String) -> Result<Self> {
|
|
Ok(Self {
|
|
duration: decoded.total_duration(),
|
|
width: display.graphemes(true).count(),
|
|
path,
|
|
display,
|
|
})
|
|
}
|
|
}
|
|
|
|
/// This struct is separate from [Track] since it is generated lazily from
|
|
/// a track, and not when the track is first downloaded.
|
|
pub struct Decoded {
|
|
/// Has both the formatted name and some information from the decoded data.
|
|
pub info: Info,
|
|
|
|
/// The decoded data, which is able to be played by [rodio].
|
|
pub data: DecodedData,
|
|
}
|
|
|
|
impl Decoded {
|
|
/// Creates a new track.
|
|
/// This is equivalent to [`QueuedTrack::decode`].
|
|
pub fn new(track: Queued) -> Result<Self> {
|
|
let (path, display) = (track.path.clone(), track.display.clone());
|
|
let data = Decoder::builder()
|
|
.with_byte_len(track.data.len().try_into()?)
|
|
.with_data(Cursor::new(track.data))
|
|
.build()
|
|
.track(track.display)?;
|
|
|
|
let info = Info::new(&data, path, display)?;
|
|
Ok(Self { info, data })
|
|
}
|
|
}
|