mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-06-22 14:42:56 -05:00
Added the rs-ts
crate and made adjustments for proper TS binding generation
This commit is contained in:
parent
b8280775fa
commit
367d2ceda3
31 changed files with 273 additions and 239 deletions
|
@ -17,4 +17,4 @@ strip = true
|
|||
lto = true
|
||||
opt-level = "z"
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
panic = "abort"
|
||||
|
|
|
@ -44,3 +44,7 @@ rustfm-scrobble = "1.1.1"
|
|||
reqwest = { version = "0.12.12", features = ["json"] }
|
||||
tokio = { version = "1.43.0", features = ["macros"] }
|
||||
opener = "0.7.2"
|
||||
ts-rs = { version = "11.0.1", optional = true, features = ["uuid-impl", "chrono-impl", "serde_json"] }
|
||||
|
||||
[features]
|
||||
ts = ["dep:ts-rs"]
|
||||
|
|
|
@ -7,8 +7,10 @@ use std::{
|
|||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::to_string_pretty;
|
||||
use thiserror::Error;
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ConfigLibrary {
|
||||
pub name: String,
|
||||
|
@ -51,6 +53,7 @@ impl ConfigLibrary {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ConfigLibraries {
|
||||
pub default_library: Uuid,
|
||||
|
@ -92,6 +95,7 @@ impl ConfigLibraries {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
pub struct ConfigConnections {
|
||||
pub discord_rpc_client_id: Option<u64>,
|
||||
|
@ -99,6 +103,7 @@ pub struct ConfigConnections {
|
|||
pub last_fm_session: Option<String>,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
|
|
|
@ -28,6 +28,8 @@ use super::connections::{ConnectionsNotification, ControllerConnections};
|
|||
use super::controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput};
|
||||
use super::queue::{QueueAlbum, QueueSong};
|
||||
|
||||
use ts_rs::TS;
|
||||
|
||||
pub struct Controller();
|
||||
|
||||
type QueueItem_ = QueueItem<QueueSong, QueueAlbum>;
|
||||
|
@ -43,6 +45,7 @@ pub enum ControllerError {
|
|||
}
|
||||
|
||||
// TODO: move this to a different location to be used elsewhere
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum PlayerLocation {
|
||||
|
@ -370,8 +373,11 @@ impl Controller {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Default, Serialize, Clone)]
|
||||
pub struct PlaybackInfo {
|
||||
#[ts(as = "(f64, f64)")]
|
||||
pub position: Option<TimeDelta>,
|
||||
#[ts(as = "(f64, f64)")]
|
||||
pub duration: Option<TimeDelta>,
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use async_channel::{Receiver, Sender};
|
||||
use discord_presence::models::Command;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::music_storage::{
|
||||
|
|
|
@ -6,7 +6,7 @@ use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterato
|
|||
use crate::{
|
||||
config::Config,
|
||||
music_storage::{
|
||||
library::{self, MusicLibrary},
|
||||
library::MusicLibrary,
|
||||
playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use crate::music_storage::{
|
||||
library::Song,
|
||||
queue::{Queue, QueueError, QueueItemType},
|
||||
};
|
||||
use crate::music_storage::queue::{Queue, QueueError, QueueItemType};
|
||||
|
||||
use super::{
|
||||
controller::{Controller, QueueCommand, QueueResponse},
|
||||
|
|
|
@ -213,8 +213,8 @@ fn to_tag(string: String) -> Tag {
|
|||
"album artist" => Tag::AlbumArtist,
|
||||
"genre" => Tag::Genre,
|
||||
"comment" => Tag::Comment,
|
||||
"track number" => Tag::Track,
|
||||
"disc number" => Tag::Disk,
|
||||
"track number" => Tag::TrackNumber,
|
||||
"disc number" => Tag::DiskNumber,
|
||||
_ => Tag::Key(string),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use super::playlist::{Playlist, PlaylistFolder};
|
||||
// Crate things
|
||||
use super::utils::{find_images, normalize, read_file, write_file};
|
||||
|
||||
use crate::music_storage::playlist::PlaylistFolderItem;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
|
@ -19,6 +20,7 @@ use lofty::file::{AudioFile as _, TaggedFileExt as _};
|
|||
use lofty::probe::Probe;
|
||||
use lofty::tag::{ItemKey, ItemValue, TagType};
|
||||
use rcue::parser::parse_from_file;
|
||||
use serde::ser::SerializeMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use uuid::Uuid;
|
||||
|
@ -35,6 +37,10 @@ use serde::{Deserialize, Serialize};
|
|||
use rayon::prelude::*;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
// TS
|
||||
use ts_rs::TS;
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub enum AlbumArt {
|
||||
Embedded(usize),
|
||||
|
@ -52,6 +58,7 @@ impl AlbumArt {
|
|||
|
||||
/// A tag for a song
|
||||
#[non_exhaustive]
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub enum Tag {
|
||||
Title,
|
||||
|
@ -60,23 +67,26 @@ pub enum Tag {
|
|||
AlbumArtist,
|
||||
Genre,
|
||||
Comment,
|
||||
Track,
|
||||
Disk,
|
||||
Key(String),
|
||||
TrackNumber,
|
||||
DiskNumber,
|
||||
#[cfg_attr(feature = "ts", ts(type = "string"))]
|
||||
Field(String),
|
||||
#[serde(untagged)]
|
||||
#[cfg_attr(feature = "ts", ts(type = "string"))]
|
||||
Key(String),
|
||||
}
|
||||
|
||||
impl Display for Tag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let path_str: String = match self {
|
||||
Self::Title => "TrackTitle".into(),
|
||||
Self::Album => "AlbumTitle".into(),
|
||||
Self::Artist => "TrackArtist".into(),
|
||||
Self::Title => "Title".into(),
|
||||
Self::Album => "Album".into(),
|
||||
Self::Artist => "Artist".into(),
|
||||
Self::AlbumArtist => "AlbumArtist".into(),
|
||||
Self::Genre => "Genre".into(),
|
||||
Self::Comment => "Comment".into(),
|
||||
Self::Track => "TrackNumber".into(),
|
||||
Self::Disk => "DiscNumber".into(),
|
||||
Self::TrackNumber => "TrackNumber".into(),
|
||||
Self::DiskNumber => "DiscNumber".into(),
|
||||
Self::Key(key) => key.into(),
|
||||
Self::Field(f) => f.into(),
|
||||
};
|
||||
|
@ -121,6 +131,7 @@ impl Display for Field {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum InternalTag {
|
||||
|
@ -131,6 +142,7 @@ pub enum InternalTag {
|
|||
VolumeAdjustment(i8),
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
#[non_exhaustive]
|
||||
pub enum BannedType {
|
||||
|
@ -138,6 +150,7 @@ pub enum BannedType {
|
|||
All,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum DoNotTrack {
|
||||
|
@ -148,6 +161,7 @@ pub enum DoNotTrack {
|
|||
Discord,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[non_exhaustive]
|
||||
pub enum SongType {
|
||||
|
@ -160,6 +174,7 @@ pub enum SongType {
|
|||
}
|
||||
|
||||
/// Stores information about a single song
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Song {
|
||||
pub location: Vec<URI>,
|
||||
|
@ -171,19 +186,75 @@ pub struct Song {
|
|||
pub rating: Option<u8>,
|
||||
/// MIME type
|
||||
pub format: Option<String>,
|
||||
#[cfg_attr(
|
||||
feature = "ts",
|
||||
ts(type = "number"),
|
||||
serde(serialize_with = "dur_ms", deserialize_with = "ms_dur")
|
||||
)]
|
||||
pub duration: Duration,
|
||||
#[cfg_attr(feature = "ts", ts(type = "number"))]
|
||||
pub play_time: Duration,
|
||||
#[serde(with = "ts_milliseconds_option")]
|
||||
#[cfg_attr(feature = "ts", ts(type = "number"))]
|
||||
pub last_played: Option<DateTime<Utc>>,
|
||||
#[serde(with = "ts_milliseconds_option")]
|
||||
#[cfg_attr(feature = "ts", ts(type = "number"))]
|
||||
pub date_added: Option<DateTime<Utc>>,
|
||||
#[serde(with = "ts_milliseconds_option")]
|
||||
#[cfg_attr(feature = "ts", ts(type = "number"))]
|
||||
pub date_modified: Option<DateTime<Utc>>,
|
||||
pub album_art: Vec<AlbumArt>,
|
||||
#[cfg_attr(
|
||||
feature = "ts",
|
||||
// The combination of "Tag" and "Map" doesn't seem to play well, possibly because of the 'Field' variant,
|
||||
// which will cause type errors on the TS side otherwise
|
||||
ts(type = "any"),
|
||||
serde(serialize_with = "tags_strings")
|
||||
)]
|
||||
pub tags: BTreeMap<Tag, String>,
|
||||
pub internal_tags: Vec<InternalTag>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "ts")]
|
||||
fn tags_strings<S: serde::Serializer>(
|
||||
tags: &BTreeMap<Tag, String>,
|
||||
serializer: S,
|
||||
) -> Result<S::Ok, S::Error> {
|
||||
let mut map = serializer.serialize_map(Some(tags.len()))?;
|
||||
for (k, v) in tags {
|
||||
map.serialize_entry(&k.to_string(), &v)?;
|
||||
}
|
||||
map.end()
|
||||
}
|
||||
|
||||
#[cfg(feature = "ts")]
|
||||
fn dur_ms<S: serde::Serializer>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error> {
|
||||
serializer.serialize_u64(duration.as_secs())
|
||||
}
|
||||
#[cfg(feature = "ts")]
|
||||
fn ms_dur<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<Duration, D::Error> {
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use serde::de::Visitor;
|
||||
|
||||
struct De(PhantomData<fn() -> Duration>);
|
||||
impl<'de> Visitor<'de> for De {
|
||||
type Value = Duration;
|
||||
|
||||
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
formatter.write_str("Duration as seconds in the form of a u64")
|
||||
}
|
||||
|
||||
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
||||
where
|
||||
E: serde::de::Error,
|
||||
{
|
||||
Ok(Duration::from_secs(v))
|
||||
}
|
||||
}
|
||||
deserializer.deserialize_u64(De(PhantomData))
|
||||
}
|
||||
|
||||
impl Song {
|
||||
/// Get a tag's value
|
||||
///
|
||||
|
@ -257,13 +328,13 @@ impl Song {
|
|||
for item in tag.items() {
|
||||
let key = match item.key() {
|
||||
ItemKey::TrackTitle => Tag::Title,
|
||||
ItemKey::TrackNumber => Tag::Track,
|
||||
ItemKey::TrackNumber => Tag::TrackNumber,
|
||||
ItemKey::TrackArtist => Tag::Artist,
|
||||
ItemKey::AlbumArtist => Tag::AlbumArtist,
|
||||
ItemKey::Genre => Tag::Genre,
|
||||
ItemKey::Comment => Tag::Comment,
|
||||
ItemKey::AlbumTitle => Tag::Album,
|
||||
ItemKey::DiscNumber => Tag::Disk,
|
||||
ItemKey::DiscNumber => Tag::DiskNumber,
|
||||
ItemKey::Unknown(unknown)
|
||||
if unknown == "ACOUSTID_FINGERPRINT" || unknown == "Acoustid Fingerprint" =>
|
||||
{
|
||||
|
@ -400,7 +471,10 @@ impl Song {
|
|||
tags.insert(Tag::Artist, artist.clone());
|
||||
}
|
||||
|
||||
tags.insert(Tag::Track, track.no.parse().unwrap_or((i + 1).to_string()));
|
||||
tags.insert(
|
||||
Tag::TrackNumber,
|
||||
track.no.parse().unwrap_or((i + 1).to_string()),
|
||||
);
|
||||
match track.title.clone() {
|
||||
Some(title) => tags.insert(Tag::Title, title),
|
||||
None => match track.isrc.clone() {
|
||||
|
@ -498,13 +572,16 @@ impl Song {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum URI {
|
||||
Local(PathBuf),
|
||||
Cue {
|
||||
location: PathBuf,
|
||||
index: usize,
|
||||
#[cfg_attr(feature = "ts", ts(type = "number"))]
|
||||
start: Duration,
|
||||
#[cfg_attr(feature = "ts", ts(type = "number"))]
|
||||
end: Duration,
|
||||
},
|
||||
Remote(Service, String),
|
||||
|
@ -590,6 +667,7 @@ impl Display for URI {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "ts", derive(TS), ts(export))]
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum Service {
|
||||
InternetRadio,
|
||||
|
@ -1134,7 +1212,7 @@ impl MusicLibrary {
|
|||
//let norm_title = normalize(&album_title);
|
||||
|
||||
let disc_num = song
|
||||
.get_tag(&Tag::Disk)
|
||||
.get_tag(&Tag::DiskNumber)
|
||||
.unwrap_or(&"".to_string())
|
||||
.parse::<u16>()
|
||||
.unwrap_or(1);
|
||||
|
@ -1143,7 +1221,7 @@ impl MusicLibrary {
|
|||
// If the album is in the list, add the track to the appropriate disc within the album
|
||||
Some(album) => match album.discs.get_mut(&disc_num) {
|
||||
Some(disc) => disc.push((
|
||||
song.get_tag(&Tag::Track)
|
||||
song.get_tag(&Tag::TrackNumber)
|
||||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
|
@ -1153,7 +1231,7 @@ impl MusicLibrary {
|
|||
album.discs.insert(
|
||||
disc_num,
|
||||
vec![(
|
||||
song.get_tag(&Tag::Track)
|
||||
song.get_tag(&Tag::TrackNumber)
|
||||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
|
@ -1171,7 +1249,7 @@ impl MusicLibrary {
|
|||
discs: BTreeMap::from([(
|
||||
disc_num,
|
||||
vec![(
|
||||
song.get_tag(&Tag::Track)
|
||||
song.get_tag(&Tag::TrackNumber)
|
||||
.unwrap_or(&String::new())
|
||||
.parse::<u16>()
|
||||
.unwrap_or_default(),
|
||||
|
@ -1308,8 +1386,8 @@ mod test {
|
|||
&vec![
|
||||
Tag::Field("location".to_string()),
|
||||
Tag::Album,
|
||||
Tag::Disk,
|
||||
Tag::Track,
|
||||
Tag::DiskNumber,
|
||||
Tag::TrackNumber,
|
||||
],
|
||||
)
|
||||
.unwrap();
|
||||
|
|
|
@ -11,7 +11,6 @@ use std::time::Duration;
|
|||
|
||||
// use chrono::Duration;
|
||||
use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
|
||||
use chrono::format::Item;
|
||||
use itertools::Itertools;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
|
|
|
@ -18,7 +18,7 @@ crate-type = ["staticlib", "cdylib", "rlib"]
|
|||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
dmp-core = { path = "../dmp-core" }
|
||||
dmp-core = { path = "../dmp-core", features = ["ts"] }
|
||||
tauri = { version = "2", features = [ "protocol-asset", "unstable"] }
|
||||
tauri-plugin-shell = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
@ -11,7 +11,7 @@ use tauri::{AppHandle, Emitter, State, Wry};
|
|||
use tempfile::TempDir;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{LAST_FM_API_KEY, LAST_FM_API_SECRET, wrappers::_Song};
|
||||
use crate::{LAST_FM_API_KEY, LAST_FM_API_SECRET};
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn add_song_to_queue(
|
||||
|
@ -47,7 +47,7 @@ pub async fn play_now(
|
|||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("now_playing_change", &song).unwrap();
|
||||
app.emit("playing", true).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -21,12 +21,12 @@ use dmp_core::{
|
|||
use parking_lot::RwLock;
|
||||
use tauri::{AppHandle, Emitter, Manager, http::Response};
|
||||
use uuid::Uuid;
|
||||
use wrappers::{_Song, stop};
|
||||
use wrappers::stop;
|
||||
|
||||
use crate::wrappers::{
|
||||
add_song_to_playlist, clear_queue, delete_playlist, get_library, get_playlist, get_playlists,
|
||||
get_queue, get_song, import_playlist, next, pause, play, play_next_queue, prev,
|
||||
remove_from_queue, seek, set_volume, queue_move_to
|
||||
get_queue, get_song, import_playlist, next, pause, play, play_next_queue, prev, queue_move_to,
|
||||
remove_from_queue, seek, set_volume,
|
||||
};
|
||||
use commands::{
|
||||
add_song_to_queue, display_album_art, last_fm_init_auth, play_now, remove_from_lib_playlist,
|
||||
|
@ -212,7 +212,7 @@ fn start_controller(app: AppHandle) -> Result<(), String> {
|
|||
let next_song_notification = next_song_notification;
|
||||
while true {
|
||||
let song = next_song_notification.recv().unwrap();
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("now_playing_change", &song).unwrap();
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
app.emit("playing", true).unwrap();
|
||||
_ = now_playing.write().insert(song);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
use std::{collections::BTreeMap, path::PathBuf, thread::spawn};
|
||||
use std::{path::PathBuf, thread::spawn};
|
||||
|
||||
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
|
||||
use crossbeam::channel::Sender;
|
||||
use dmp_core::{
|
||||
music_controller::controller::{ControllerHandle, PlayerLocation},
|
||||
music_storage::{
|
||||
library::{Song, Tag, URI},
|
||||
library::{Song, Tag},
|
||||
queue::QueueItemType,
|
||||
},
|
||||
};
|
||||
|
@ -83,7 +82,7 @@ pub async fn next(
|
|||
Ok(s) => s,
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("now_playing_change", song).unwrap();
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
app.emit("playing", true).unwrap();
|
||||
Ok(())
|
||||
|
@ -99,7 +98,7 @@ pub async fn prev(
|
|||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
println!("prev");
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("now_playing_change", song).unwrap();
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -112,7 +111,7 @@ pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<()
|
|||
#[tauri::command]
|
||||
pub async fn get_queue(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<Vec<(_Song, PlayerLocation)>, String> {
|
||||
) -> Result<Vec<(Song, PlayerLocation)>, String> {
|
||||
Ok(ctrl_handle
|
||||
.queue_get_all()
|
||||
.await
|
||||
|
@ -121,7 +120,7 @@ pub async fn get_queue(
|
|||
let QueueItemType::Single(song) = item.item else {
|
||||
unreachable!("There should be no albums in the queue right now")
|
||||
};
|
||||
(_Song::from(&song.song), song.location)
|
||||
(song.song, song.location)
|
||||
})
|
||||
.collect_vec())
|
||||
}
|
||||
|
@ -141,51 +140,9 @@ pub async fn remove_from_queue(
|
|||
}
|
||||
}
|
||||
|
||||
//Grab Album art from custom protocol
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct _Song {
|
||||
pub location: Vec<URI>,
|
||||
pub uuid: Uuid,
|
||||
pub plays: i32,
|
||||
pub format: Option<String>,
|
||||
pub duration: String,
|
||||
#[serde(with = "ts_milliseconds_option")]
|
||||
pub last_played: Option<DateTime<Utc>>,
|
||||
#[serde(with = "ts_milliseconds_option")]
|
||||
pub date_added: Option<DateTime<Utc>>,
|
||||
#[serde(with = "ts_milliseconds_option")]
|
||||
pub date_modified: Option<DateTime<Utc>>,
|
||||
pub tags: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl From<&Song> for _Song {
|
||||
fn from(value: &Song) -> Self {
|
||||
_Song {
|
||||
location: value.location.clone(),
|
||||
uuid: value.uuid.clone(),
|
||||
plays: value.plays.clone(),
|
||||
duration: value.duration.as_secs().to_string(),
|
||||
format: value.format.clone().map(|format| format.to_string()),
|
||||
last_played: value.last_played,
|
||||
date_added: value.date_added,
|
||||
date_modified: value.date_modified,
|
||||
tags: value
|
||||
.tags
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.clone()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<_Song>, String> {
|
||||
let songs = ctrl_handle
|
||||
.lib_get_all()
|
||||
.await
|
||||
.iter()
|
||||
.map(|song| _Song::from(song))
|
||||
.collect_vec();
|
||||
pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<Song>, String> {
|
||||
let songs = ctrl_handle.lib_get_all().await;
|
||||
Ok(songs)
|
||||
}
|
||||
|
||||
|
@ -193,23 +150,17 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec
|
|||
pub async fn get_playlist(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
uuid: Uuid,
|
||||
) -> Result<Vec<_Song>, String> {
|
||||
) -> Result<Vec<Song>, String> {
|
||||
let playlist = match ctrl_handle.playlist_get(uuid).await {
|
||||
Ok(list) => list,
|
||||
Err(_) => todo!(),
|
||||
};
|
||||
|
||||
let songs = playlist
|
||||
.tracks
|
||||
.iter()
|
||||
.map(|song| _Song::from(song))
|
||||
.collect::<Vec<_>>();
|
||||
println!(
|
||||
"Got Playlist {}, len {}",
|
||||
playlist.title,
|
||||
playlist.tracks.len()
|
||||
);
|
||||
Ok(songs)
|
||||
Ok(playlist.tracks)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -264,13 +215,13 @@ pub struct PlaylistPayload {
|
|||
pub async fn get_song(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
uuid: Uuid,
|
||||
) -> Result<_Song, String> {
|
||||
) -> Result<Song, String> {
|
||||
let song = ctrl_handle.lib_get_song(uuid).await.0;
|
||||
println!(
|
||||
"got song {}",
|
||||
&song.tags.get(&Tag::Title).unwrap_or(&String::new())
|
||||
);
|
||||
Ok(_Song::from(&song))
|
||||
Ok(song)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -334,7 +285,7 @@ pub async fn queue_move_to(
|
|||
match ctrl_handle.enqueue(0).await.map_err(|e| e.to_string()) {
|
||||
Ok(song) => {
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("now_playing_change", song).unwrap();
|
||||
app.emit("playing", true).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
|
145
src/App.tsx
145
src/App.tsx
|
@ -1,19 +1,20 @@
|
|||
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||
import "./App.css";
|
||||
import { Config, playbackInfo } from "./types";
|
||||
// import { EventEmitter } from "@tauri-apps/plugin-shell";
|
||||
// import { listen } from "@tauri-apps/api/event";
|
||||
// import { fetch } from "@tauri-apps/plugin-http";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { PhysicalPosition } from "@tauri-apps/api/window";
|
||||
import { Menu, Submenu, SubmenuOptions } from "@tauri-apps/api/menu";
|
||||
|
||||
import { Config } from "./bindings/Config";
|
||||
import { PlaybackInfo } from "./bindings/PlaybackInfo";
|
||||
import { PlayerLocation } from "./bindings/PlayerLocation";
|
||||
import { URI } from "./bindings/URI";
|
||||
import { Song } from "./bindings/Song";
|
||||
|
||||
const appWindow = getCurrentWebviewWindow();
|
||||
|
||||
type Location = "Library" | { "Playlist": string };
|
||||
|
||||
// This needs to be changed to properly reflect cursor position
|
||||
// this will do for now.
|
||||
async function contextMenuPosition(event: React.MouseEvent) {
|
||||
|
@ -43,16 +44,16 @@ function App() {
|
|||
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any>("now_playing_change", ({ payload, }) => {
|
||||
const unlisten = appWindow.listen<Song>("now_playing_change", ({ payload, }) => {
|
||||
const displayArtwork = () => {
|
||||
invoke('display_album_art', { uuid: payload.uuid }).then(() => {})
|
||||
}
|
||||
// console.log(event);
|
||||
|
||||
setNowPlaying(
|
||||
<NowPlaying
|
||||
title={ payload.tags.TrackTitle }
|
||||
album={ payload.tags.AlbumTitle }
|
||||
artist={ payload.tags.TrackArtist }
|
||||
title={ payload.tags.Title }
|
||||
album={ payload.tags.Album }
|
||||
artist={ payload.tags.Artist }
|
||||
artwork={ <img src={convertFileSrc("abc") + "?" + payload.uuid } id="nowPlayingArtwork" alt="Now Playing Artwork" key={payload.uuid} onDoubleClick={ displayArtwork } /> }
|
||||
/>
|
||||
)
|
||||
|
@ -62,15 +63,15 @@ function App() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any>("queue_updated", (_) => {
|
||||
const unlisten = appWindow.listen<null>("queue_updated", (_) => {
|
||||
// console.log(event);
|
||||
invoke('get_queue').then((_songs) => {
|
||||
let songs = _songs as any[];
|
||||
let songs = _songs as [Song, PlayerLocation][];
|
||||
setQueue(
|
||||
songs.filter((_, i) => i != 0).map((song, i) =>
|
||||
<QueueSong
|
||||
song={ song[0] }
|
||||
location={ song[1] as "Library" | {"Playlist" : string}}
|
||||
location={ song[1] }
|
||||
index={i+1}
|
||||
key={ Math.floor((Math.random() * 100_000_000_000) + 1) + '_' + Date.now() }
|
||||
setSelectedSong={ setSelectedSongQueue }
|
||||
|
@ -83,8 +84,8 @@ function App() {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any>("playing", (isPlaying) => {
|
||||
setPlaying(isPlaying.payload as boolean)
|
||||
const unlisten = appWindow.listen<boolean>("playing", (isPlaying) => {
|
||||
setPlaying(isPlaying.payload)
|
||||
})
|
||||
return () => { unlisten.then((f) => f()) }
|
||||
}, []);
|
||||
|
@ -133,11 +134,11 @@ interface PlaylistHeadProps {
|
|||
function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playlistsInfo, setSelected }: PlaylistHeadProps) {
|
||||
function getPlaylist(playlist: PlaylistInfo) {
|
||||
invoke('get_playlist', { uuid: playlist.uuid }).then((list) => {
|
||||
setLibrary([...(list as any[]).map((song, i) => {
|
||||
setLibrary([...(list as Song[]).map((song, i) => {
|
||||
// console.log(song);
|
||||
const reload = () => getPlaylist(playlist)
|
||||
return (
|
||||
<Song
|
||||
<MainViewSong
|
||||
key={ song.uuid + Math.floor(Math.random() * 100_000_000_000) }
|
||||
location={ song.location }
|
||||
playerLocation={ {"Playlist" : playlist.uuid } }
|
||||
|
@ -157,42 +158,42 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli
|
|||
}
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any[]>("playlists_gotten", (_res) => {
|
||||
// console.log(event);
|
||||
let res = _res.payload as PlaylistInfo[];
|
||||
playlistsInfo.current = [...res];
|
||||
// console.log(playlistsInfo, res);
|
||||
const unlisten = appWindow.listen<PlaylistInfo[]>("playlists_gotten", (_res) => {
|
||||
const res = _res.payload;
|
||||
// console.log(event);
|
||||
playlistsInfo.current = [...res];
|
||||
// console.log(playlistsInfo, res);
|
||||
|
||||
setPlaylists([
|
||||
...res.map( (list) => {
|
||||
const _getPlaylist = () => getPlaylist(list)
|
||||
const deletePlaylist = () => {
|
||||
invoke('delete_playlist', { uuid: list.uuid }).then(() => {});
|
||||
invoke('get_playlists').then(() => {});
|
||||
}
|
||||
async function menuHandler(event: React.MouseEvent) {
|
||||
event.preventDefault();
|
||||
const menu = await Menu.new({
|
||||
items: [
|
||||
{ id: "delete_playlist" + list.uuid, text: "Delete Playlist", action: deletePlaylist }
|
||||
]
|
||||
});
|
||||
menu.popup(await contextMenuPosition(event));
|
||||
}
|
||||
setPlaylists([
|
||||
...res.map( (list) => {
|
||||
const _getPlaylist = () => getPlaylist(list)
|
||||
const deletePlaylist = () => {
|
||||
invoke('delete_playlist', { uuid: list.uuid }).then(() => {});
|
||||
invoke('get_playlists').then(() => {});
|
||||
}
|
||||
async function menuHandler(event: React.MouseEvent) {
|
||||
event.preventDefault();
|
||||
const menu = await Menu.new({
|
||||
items: [
|
||||
{ id: "delete_playlist" + list.uuid, text: "Delete Playlist", action: deletePlaylist }
|
||||
]
|
||||
});
|
||||
menu.popup(await contextMenuPosition(event));
|
||||
}
|
||||
|
||||
return (
|
||||
<button onClick={ _getPlaylist }
|
||||
onContextMenu={ menuHandler }
|
||||
key={ 'playlist_' + list.uuid }>{ list.name }</button>
|
||||
)
|
||||
})
|
||||
])
|
||||
return (
|
||||
<button onClick={ _getPlaylist }
|
||||
onContextMenu={ menuHandler }
|
||||
key={ 'playlist_' + list.uuid }>{ list.name }</button>
|
||||
)
|
||||
})
|
||||
])
|
||||
})
|
||||
return () => { unlisten.then((f) => f()) }
|
||||
}, []);
|
||||
let handle_import = () => {
|
||||
invoke('import_playlist').then((_res) => {
|
||||
let res = _res as any;
|
||||
let res = _res as PlaylistInfo;
|
||||
|
||||
setPlaylists([
|
||||
...playlists,
|
||||
|
@ -207,11 +208,11 @@ function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary, playli
|
|||
setViewName("Library");
|
||||
invoke('get_library').then((lib) => {
|
||||
let i = 0;
|
||||
setLibrary([...(lib as any[]).map((song) => {
|
||||
setLibrary([...(lib as Song[]).map((song) => {
|
||||
// console.log(song);
|
||||
i++;
|
||||
return (
|
||||
<Song
|
||||
<MainViewSong
|
||||
key={ song.uuid + Math.floor(Math.random() * 100_000_000_000) }
|
||||
location={ song.location }
|
||||
playerLocation="Library"
|
||||
|
@ -247,7 +248,7 @@ function MainView({ lib_ref, viewName, playlistsInfo, setSelected, selectedSong
|
|||
|
||||
|
||||
const addToQueue = (_: string) => {
|
||||
invoke('add_song_to_queue', { uuid: selectedSong.current!.uuid, location: selectedSong.current!.playerLocation }).then(() => {});
|
||||
invoke('add_song_to_queue', { uuid: selectedSong.current?.uuid, location: selectedSong.current?.playerLocation }).then(() => {});
|
||||
}
|
||||
const playNow = () => invoke("play_now", { uuid: selectedSong.current!.uuid, location: selectedSong.current!.playerLocation }).then(() => {})
|
||||
const playNext = () => invoke("play_next_queue", { uuid: selectedSong.current!.uuid, location: selectedSong.current!.playerLocation }).then(() => {})
|
||||
|
@ -289,14 +290,13 @@ function MainView({ lib_ref, viewName, playlistsInfo, setSelected, selectedSong
|
|||
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any>("library_loaded", (_) => {
|
||||
const unlisten = appWindow.listen<null>("library_loaded", (_) => {
|
||||
console.log("library_loaded");
|
||||
invoke('get_library').then((lib) => {
|
||||
let i = 0;
|
||||
setLibrary([...(lib as any[]).map((song) => {
|
||||
i++;
|
||||
setLibrary([...(lib as Song[]).map((song, i) => {
|
||||
console.log("duration", song.duration)
|
||||
return (
|
||||
<Song
|
||||
<MainViewSong
|
||||
key={ song.uuid + Math.floor(Math.random() * 100_000_000_000) }
|
||||
location={ song.location }
|
||||
playerLocation="Library"
|
||||
|
@ -305,7 +305,7 @@ function MainView({ lib_ref, viewName, playlistsInfo, setSelected, selectedSong
|
|||
duration={ song.duration }
|
||||
tags={ song.tags }
|
||||
playlists={ playlistsInfo }
|
||||
index={ i - 1 }
|
||||
index={ i }
|
||||
setSelected={ setSelected }
|
||||
/>
|
||||
)
|
||||
|
@ -331,12 +331,12 @@ function MainView({ lib_ref, viewName, playlistsInfo, setSelected, selectedSong
|
|||
|
||||
|
||||
interface SongProps {
|
||||
location: any,
|
||||
playerLocation: string | {"Playlist" : any},
|
||||
location: URI[],
|
||||
playerLocation: PlayerLocation,
|
||||
uuid: string,
|
||||
plays: number,
|
||||
format?: string,
|
||||
duration: string,
|
||||
duration: number,
|
||||
last_played?: string,
|
||||
date_added?: string,
|
||||
date_modified?: string,
|
||||
|
@ -347,7 +347,7 @@ interface SongProps {
|
|||
reload?: () => void
|
||||
}
|
||||
|
||||
function Song(props: SongProps) {
|
||||
function MainViewSong(props: SongProps) {
|
||||
// console.log(props.tags);
|
||||
// useEffect(() => {
|
||||
// const unlistenPromise = listen<string>("add_song_to_queue", (event) => {
|
||||
|
@ -363,16 +363,16 @@ function Song(props: SongProps) {
|
|||
// }, []);
|
||||
const setSelected = () => {
|
||||
props.setSelected(props);
|
||||
console.log(props.tags.TrackTitle);
|
||||
console.log(props.tags.Title);
|
||||
}
|
||||
return(
|
||||
<div
|
||||
onContextMenu={ setSelected }
|
||||
onClick={ setSelected }
|
||||
className="song">
|
||||
<p className="artist unselectable">{ props.tags.TrackArtist }</p>
|
||||
<p className="title unselectable">{ props.tags.TrackTitle }</p>
|
||||
<p className="album unselectable">{ props.tags.AlbumTitle }</p>
|
||||
<p className="artist unselectable">{ props.tags.Artist }</p>
|
||||
<p className="title unselectable">{ props.tags.Title }</p>
|
||||
<p className="album unselectable">{ props.tags.Album }</p>
|
||||
<p className="duration unselectable">
|
||||
{ Math.round(+props.duration / 60) }:
|
||||
{ (+props.duration % 60).toString().padStart(2, "0") }
|
||||
|
@ -397,8 +397,7 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
|||
const [lastFmLoggedIn, setLastFmLoggedIn] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any>("playback_info", ({ payload, }) => {
|
||||
const info = payload as playbackInfo;
|
||||
const unlisten = appWindow.listen<PlaybackInfo>("playback_info", ({ payload: info, }) => {
|
||||
const pos_ = Array.isArray(info.position) ? info.position![0] : 0;
|
||||
const dur_ = Array.isArray(info.duration) ? info.duration![0] : 0;
|
||||
|
||||
|
@ -463,9 +462,9 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
|||
}
|
||||
|
||||
interface NowPlayingProps {
|
||||
title: string,
|
||||
artist: string,
|
||||
album: string,
|
||||
title: string | undefined,
|
||||
artist: string | undefined,
|
||||
album: string | undefined,
|
||||
artwork: JSX.Element
|
||||
}
|
||||
|
||||
|
@ -475,7 +474,7 @@ function NowPlaying({ title, artist, album, artwork }: NowPlayingProps) {
|
|||
<div className="artworkWrapper unselectable">
|
||||
{ artwork }
|
||||
</div>
|
||||
<h3>{ title }</h3>
|
||||
<h3>{ title? title : "Unknown Title" }</h3>
|
||||
<p>{ artist }</p>
|
||||
<p>{ album }</p>
|
||||
</section>
|
||||
|
@ -491,7 +490,7 @@ interface QueueProps {
|
|||
interface selectedQueueSong {
|
||||
uuid: string,
|
||||
index: number
|
||||
location: Location,
|
||||
location: PlayerLocation,
|
||||
}
|
||||
|
||||
function Queue({ songs, selectedSong, }: QueueProps) {
|
||||
|
@ -530,8 +529,8 @@ function Queue({ songs, selectedSong, }: QueueProps) {
|
|||
}
|
||||
|
||||
interface QueueSongProps {
|
||||
song: any,
|
||||
location: Location,
|
||||
song: Song,
|
||||
location: PlayerLocation,
|
||||
index: number,
|
||||
setSelectedSong: (song: selectedQueueSong) => void,
|
||||
}
|
||||
|
@ -545,8 +544,8 @@ function QueueSong({ song, location, index, setSelectedSong }: QueueSongProps) {
|
|||
<div className="queueSong unselectable" onAuxClickCapture={ setSelected } onClick={ setSelected } onContextMenu={ setSelected }>
|
||||
<img className="queueSongCoverArt" src={ convertFileSrc('abc') + '?' + song.uuid } key={ 'coverArt_' + song.uuid }/>
|
||||
<div className="queueSongTags">
|
||||
<p className="queueSongTitle">{ song.tags.TrackTitle }</p>
|
||||
<p className="queueSongArtist">{ song.tags.TrackArtist }</p>
|
||||
<p className="queueSongTitle">{ song.tags.Title }</p>
|
||||
<p className="queueSongArtist">{ song.tags.Artist }</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
4
src/bindings/AlbumArt.ts
Normal file
4
src/bindings/AlbumArt.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { URI } from "./URI";
|
||||
|
||||
export type AlbumArt = { "Embedded": number } | { "External": URI };
|
3
src/bindings/BannedType.ts
Normal file
3
src/bindings/BannedType.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type BannedType = "Shuffle" | "All";
|
5
src/bindings/Config.ts
Normal file
5
src/bindings/Config.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ConfigConnections } from "./ConfigConnections";
|
||||
import type { ConfigLibraries } from "./ConfigLibraries";
|
||||
|
||||
export type Config = { path: string, backup_folder: string | null, libraries: ConfigLibraries, connections: ConfigConnections, state_path: string, };
|
3
src/bindings/ConfigConnections.ts
Normal file
3
src/bindings/ConfigConnections.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ConfigConnections = { discord_rpc_client_id: bigint | null, listenbrainz_token: string | null, last_fm_session: string | null, };
|
4
src/bindings/ConfigLibraries.ts
Normal file
4
src/bindings/ConfigLibraries.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { ConfigLibrary } from "./ConfigLibrary";
|
||||
|
||||
export type ConfigLibraries = { default_library: string, library_folder: string, libraries: Array<ConfigLibrary>, };
|
3
src/bindings/ConfigLibrary.ts
Normal file
3
src/bindings/ConfigLibrary.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ConfigLibrary = { name: string, path: string, uuid: string, scan_folders: Array<string> | null, };
|
3
src/bindings/DoNotTrack.ts
Normal file
3
src/bindings/DoNotTrack.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type DoNotTrack = "LastFM" | "LibreFM" | "MusicBrainz" | "Discord";
|
5
src/bindings/InternalTag.ts
Normal file
5
src/bindings/InternalTag.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { DoNotTrack } from "./DoNotTrack";
|
||||
import type { SongType } from "./SongType";
|
||||
|
||||
export type InternalTag = { "DoNotTrack": DoNotTrack } | { "SongType": SongType } | { "SongLink": [string, SongType] } | { "VolumeAdjustment": number };
|
3
src/bindings/PlaybackInfo.ts
Normal file
3
src/bindings/PlaybackInfo.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PlaybackInfo = { position: [number, number], duration: [number, number], };
|
3
src/bindings/PlayerLocation.ts
Normal file
3
src/bindings/PlayerLocation.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type PlayerLocation = "Test" | "Library" | { "Playlist": string } | "File" | "Custom";
|
3
src/bindings/Service.ts
Normal file
3
src/bindings/Service.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type Service = "InternetRadio" | "Spotify" | "Youtube" | "None";
|
14
src/bindings/Song.ts
Normal file
14
src/bindings/Song.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { AlbumArt } from "./AlbumArt";
|
||||
import type { BannedType } from "./BannedType";
|
||||
import type { InternalTag } from "./InternalTag";
|
||||
import type { URI } from "./URI";
|
||||
|
||||
/**
|
||||
* Stores information about a single song
|
||||
*/
|
||||
export type Song = { location: Array<URI>, uuid: string, plays: number, skips: number, favorited: boolean, banned: BannedType | null, rating: number | null,
|
||||
/**
|
||||
* MIME type
|
||||
*/
|
||||
format: string | null, duration: number, play_time: number, last_played: number, date_added: number, date_modified: number, album_art: Array<AlbumArt>, tags: any, internal_tags: Array<InternalTag>, };
|
3
src/bindings/SongType.ts
Normal file
3
src/bindings/SongType.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type SongType = "Main" | "Instrumental" | "Remix" | { "Custom": string };
|
6
src/bindings/Tag.ts
Normal file
6
src/bindings/Tag.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
/**
|
||||
* A tag for a song
|
||||
*/
|
||||
export type Tag = "Title" | "Album" | "Artist" | "AlbumArtist" | "Genre" | "Comment" | "TrackNumber" | "DiskNumber" | { "Field": string } | string;
|
4
src/bindings/URI.ts
Normal file
4
src/bindings/URI.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Service } from "./Service";
|
||||
|
||||
export type URI = { "Local": string } | { "Cue": { location: string, index: number, start: number, end: number, } } | { "Remote": [Service, string] };
|
70
src/types.ts
70
src/types.ts
|
@ -1,70 +0,0 @@
|
|||
export interface Configlibrary {
|
||||
name: string,
|
||||
path: string,
|
||||
uuid: string,
|
||||
scan_folders?: string[]
|
||||
}
|
||||
|
||||
export interface ConfigLibraries {
|
||||
default_library: string,
|
||||
library_folder: string,
|
||||
libraries: Configlibrary[]
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
path: string,
|
||||
backup_folder?: string,
|
||||
libraries: ConfigLibraries,
|
||||
volume: number,
|
||||
connections: ConfigConnections,
|
||||
}
|
||||
|
||||
export interface ConfigConnections {
|
||||
discord_rpc_client_id?: number,
|
||||
last_fm_session?: string,
|
||||
listenbrainz_token?: string
|
||||
}
|
||||
|
||||
export interface Song {
|
||||
location: URI[],
|
||||
uuid: string,
|
||||
plays: number,
|
||||
skips: number,
|
||||
favorited: boolean,
|
||||
banned?: BannedType,
|
||||
rating?: number,
|
||||
format?: string,
|
||||
duration: number,
|
||||
play_time: number,
|
||||
last_played?: number,
|
||||
date_added?: number,
|
||||
date_modified?: number,
|
||||
album_art: AlbumArt[],
|
||||
tags: Map<Tag, String>,
|
||||
internal_tags: InternalTag[],
|
||||
}
|
||||
|
||||
export enum InternalTag {
|
||||
|
||||
}
|
||||
|
||||
export enum Tag {
|
||||
|
||||
}
|
||||
|
||||
export enum AlbumArt {
|
||||
|
||||
}
|
||||
|
||||
export enum URI {
|
||||
|
||||
}
|
||||
|
||||
export enum BannedType {
|
||||
|
||||
}
|
||||
|
||||
export interface playbackInfo {
|
||||
position?: [number, number],
|
||||
duration?: [number, number],
|
||||
}
|
Loading…
Reference in a new issue