mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
Added Playlist imports, refactored part of the controller 🍄
This commit is contained in:
parent
87965ef6da
commit
417666e21f
11 changed files with 295 additions and 172 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -29,3 +29,5 @@ dist-ssr
|
||||||
target
|
target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
.cargo
|
.cargo
|
||||||
|
|
||||||
|
test-config
|
|
@ -37,7 +37,7 @@ crossbeam = "0.8.2"
|
||||||
quick-xml = "0.31.0"
|
quick-xml = "0.31.0"
|
||||||
leb128 = "0.2.5"
|
leb128 = "0.2.5"
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
m3u8-rs = "5.0.5"
|
m3u8-rs = "6.0.0"
|
||||||
thiserror = "1.0.56"
|
thiserror = "1.0.56"
|
||||||
uuid = { version = "1.6.1", features = ["v4", "serde"] }
|
uuid = { version = "1.6.1", features = ["v4", "serde"] }
|
||||||
serde_json = "1.0.111"
|
serde_json = "1.0.111"
|
||||||
|
@ -54,3 +54,5 @@ text_io = "0.1.12"
|
||||||
tokio = { version = "1.40.0", features = ["macros", "rt"] }
|
tokio = { version = "1.40.0", features = ["macros", "rt"] }
|
||||||
async-channel = "2.3.1"
|
async-channel = "2.3.1"
|
||||||
ciborium = "0.2.2"
|
ciborium = "0.2.2"
|
||||||
|
itertools = "0.13.0"
|
||||||
|
directories = "5.0.1"
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
//! other functions
|
//! other functions
|
||||||
#![allow(while_true)]
|
#![allow(while_true)]
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
use kushi::{Queue, QueueItemType};
|
use kushi::{Queue, QueueItemType};
|
||||||
use kushi::{QueueError, QueueItem};
|
use kushi::{QueueError, QueueItem};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -15,6 +17,7 @@ use uuid::Uuid;
|
||||||
use crate::config::ConfigError;
|
use crate::config::ConfigError;
|
||||||
use crate::music_player::player::{Player, PlayerError};
|
use crate::music_player::player::{Player, PlayerError};
|
||||||
use crate::music_storage::library::Song;
|
use crate::music_storage::library::Song;
|
||||||
|
use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem};
|
||||||
use crate::{config::Config, music_storage::library::MusicLibrary};
|
use crate::{config::Config, music_storage::library::MusicLibrary};
|
||||||
|
|
||||||
use super::queue::{QueueAlbum, QueueSong};
|
use super::queue::{QueueAlbum, QueueSong};
|
||||||
|
@ -87,28 +90,20 @@ pub enum LibraryCommand {
|
||||||
Song(Uuid),
|
Song(Uuid),
|
||||||
AllSongs,
|
AllSongs,
|
||||||
GetLibrary,
|
GetLibrary,
|
||||||
|
Playlist(Uuid),
|
||||||
|
ImportM3UPlayList(PathBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum LibraryResponse {
|
pub enum LibraryResponse {
|
||||||
Song(Song),
|
Ok,
|
||||||
|
Song(Song, usize),
|
||||||
AllSongs(Vec<Song>),
|
AllSongs(Vec<Song>),
|
||||||
Library(MusicLibrary),
|
Library(MusicLibrary),
|
||||||
|
Playlist(ExternalPlaylist),
|
||||||
|
ImportM3UPlayList(Uuid, String)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
|
||||||
enum InnerLibraryCommand {
|
|
||||||
Song(Uuid),
|
|
||||||
AllSongs,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
|
||||||
enum InnerLibraryResponse<'a> {
|
|
||||||
Song(&'a Song, usize),
|
|
||||||
AllSongs(&'a Vec<Song>),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub enum QueueCommand {
|
pub enum QueueCommand {
|
||||||
Append(QueueItem<QueueSong, QueueAlbum>, bool),
|
Append(QueueItem<QueueSong, QueueAlbum>, bool),
|
||||||
|
@ -134,7 +129,10 @@ pub struct ControllerInput {
|
||||||
MailMan<PlayerCommand, PlayerResponse>,
|
MailMan<PlayerCommand, PlayerResponse>,
|
||||||
MailMan<PlayerResponse, PlayerCommand>,
|
MailMan<PlayerResponse, PlayerCommand>,
|
||||||
),
|
),
|
||||||
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
lib_mail: (
|
||||||
|
MailMan<LibraryCommand, LibraryResponse>,
|
||||||
|
MailMan<LibraryResponse, LibraryCommand>
|
||||||
|
),
|
||||||
queue_mail: (
|
queue_mail: (
|
||||||
MailMan<QueueCommand, QueueResponse>,
|
MailMan<QueueCommand, QueueResponse>,
|
||||||
MailMan<QueueResponse, QueueCommand>
|
MailMan<QueueResponse, QueueCommand>
|
||||||
|
@ -157,13 +155,13 @@ impl ControllerHandle {
|
||||||
|
|
||||||
(
|
(
|
||||||
ControllerHandle {
|
ControllerHandle {
|
||||||
lib_mail: lib_mail.0,
|
lib_mail: lib_mail.0.clone(),
|
||||||
player_mail: player_mail.0.clone(),
|
player_mail: player_mail.0.clone(),
|
||||||
queue_mail: queue_mail.0.clone()
|
queue_mail: queue_mail.0.clone()
|
||||||
},
|
},
|
||||||
ControllerInput {
|
ControllerInput {
|
||||||
player_mail,
|
player_mail,
|
||||||
lib_mail: lib_mail.1,
|
lib_mail: lib_mail,
|
||||||
queue_mail: queue_mail,
|
queue_mail: queue_mail,
|
||||||
library,
|
library,
|
||||||
config
|
config
|
||||||
|
@ -203,7 +201,6 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
// true,
|
// true,
|
||||||
// );
|
// );
|
||||||
// }
|
// }
|
||||||
let inner_lib_mail = MailMan::double();
|
|
||||||
let queue = queue;
|
let queue = queue;
|
||||||
|
|
||||||
std::thread::scope(|scope| {
|
std::thread::scope(|scope| {
|
||||||
|
@ -215,14 +212,14 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
let player = Arc::new(RwLock::new(P::new().unwrap()));
|
let player = Arc::new(RwLock::new(P::new().unwrap()));
|
||||||
|
|
||||||
let _player = player.clone();
|
let _player = player.clone();
|
||||||
let _inner_lib_mail = inner_lib_mail.0.clone();
|
let _lib_mail = lib_mail.0.clone();
|
||||||
scope
|
scope
|
||||||
.spawn(async move {
|
.spawn(async move {
|
||||||
Controller::<P>::player_command_loop(
|
Controller::<P>::player_command_loop(
|
||||||
_player,
|
_player,
|
||||||
player_mail.1,
|
player_mail.1,
|
||||||
queue_mail.0,
|
queue_mail.0,
|
||||||
_inner_lib_mail
|
_lib_mail,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -235,12 +232,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
});
|
});
|
||||||
scope
|
scope
|
||||||
.spawn(async {
|
.spawn(async {
|
||||||
Controller::<P>::inner_library_loop(inner_lib_mail.1, &mut library).await
|
Controller::<P>::library_loop(lib_mail.1, &mut library)
|
||||||
.unwrap()
|
|
||||||
});
|
|
||||||
scope
|
|
||||||
.spawn(async {
|
|
||||||
Controller::<P>::outer_library_loop(lib_mail, inner_lib_mail.0)
|
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
@ -265,7 +257,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
player: Arc<RwLock<P>>,
|
player: Arc<RwLock<P>>,
|
||||||
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
||||||
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||||
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>
|
lib_mail: MailMan<LibraryCommand, LibraryResponse>,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let mut first = true;
|
let mut first = true;
|
||||||
while true {
|
while true {
|
||||||
|
@ -306,13 +298,13 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
|
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
|
||||||
|
|
||||||
// Append next song in library
|
// Append next song in library
|
||||||
inner_lib_mail.send(InnerLibraryCommand::AllSongs).await.unwrap();
|
lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
|
||||||
|
|
||||||
let InnerLibraryResponse::AllSongs(songs) = inner_lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::AllSongs(songs) = lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
inner_lib_mail.send(InnerLibraryCommand::Song(np_song.song.uuid.clone())).await.unwrap();
|
lib_mail.send(LibraryCommand::Song(np_song.song.uuid.clone())).await.unwrap();
|
||||||
let InnerLibraryResponse::Song(_, i) = inner_lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::Song(_, i) = lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
if let Some(song) = songs.get(i + 49) {
|
if let Some(song) = songs.get(i + 49) {
|
||||||
|
@ -373,8 +365,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
}
|
}
|
||||||
PlayerCommand::PlayNow(uuid, location) => {
|
PlayerCommand::PlayNow(uuid, location) => {
|
||||||
// TODO: This assumes the uuid doesn't point to an album. we've been over this.
|
// TODO: This assumes the uuid doesn't point to an album. we've been over this.
|
||||||
inner_lib_mail.send(InnerLibraryCommand::Song(uuid)).await.unwrap();
|
lib_mail.send(LibraryCommand::Song(uuid)).await.unwrap();
|
||||||
let InnerLibraryResponse::Song(song, index) = inner_lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::Song(song, index) = lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
queue_mail.send(QueueCommand::Clear).await.unwrap();
|
queue_mail.send(QueueCommand::Clear).await.unwrap();
|
||||||
|
@ -392,8 +384,8 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
// ...
|
// ...
|
||||||
// let's just pretend I figured that out already
|
// let's just pretend I figured that out already
|
||||||
|
|
||||||
inner_lib_mail.send(InnerLibraryCommand::AllSongs).await.unwrap();
|
lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
|
||||||
let InnerLibraryResponse::AllSongs(songs) = inner_lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::AllSongs(songs) = lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -419,59 +411,32 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn outer_library_loop(
|
async fn library_loop(
|
||||||
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
lib_mail: MailMan<LibraryResponse, LibraryCommand>,
|
||||||
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>,
|
|
||||||
) -> Result<(), ()> {
|
|
||||||
while true {
|
|
||||||
match lib_mail.recv().await.unwrap() {
|
|
||||||
LibraryCommand::Song(uuid) => {
|
|
||||||
inner_lib_mail
|
|
||||||
.send(InnerLibraryCommand::Song(uuid))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let InnerLibraryResponse::Song(song, i) = inner_lib_mail.recv().await.unwrap() else {
|
|
||||||
unimplemented!();
|
|
||||||
};
|
|
||||||
lib_mail.send(LibraryResponse::Song(song.clone())).await.unwrap();
|
|
||||||
}
|
|
||||||
LibraryCommand::AllSongs => {
|
|
||||||
inner_lib_mail
|
|
||||||
.send(InnerLibraryCommand::AllSongs)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
let x = inner_lib_mail.recv().await.unwrap();
|
|
||||||
if let InnerLibraryResponse::AllSongs(songs) = x {
|
|
||||||
lib_mail.send(LibraryResponse::AllSongs(songs.clone())).await.unwrap();
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
_ => { todo!() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn inner_library_loop(
|
|
||||||
lib_mail: MailMan<InnerLibraryResponse<'c>, InnerLibraryCommand>,
|
|
||||||
library: &'c mut MusicLibrary,
|
library: &'c mut MusicLibrary,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
while true {
|
while true {
|
||||||
match lib_mail.recv().await.unwrap() {
|
match lib_mail.recv().await.unwrap() {
|
||||||
InnerLibraryCommand::Song(uuid) => {
|
LibraryCommand::Song(uuid) => {
|
||||||
let (song, i): (&'c Song, usize) = library.query_uuid(&uuid).unwrap();
|
let (song, i) = library.query_uuid(&uuid).unwrap();
|
||||||
lib_mail
|
lib_mail.send(LibraryResponse::Song(song.clone(), i)).await.unwrap();
|
||||||
.send(InnerLibraryResponse::Song(song, i))
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
InnerLibraryCommand::AllSongs => {
|
LibraryCommand::AllSongs => {
|
||||||
let songs: &'c Vec<Song> = &library.library;
|
lib_mail.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap();
|
||||||
lib_mail.send(InnerLibraryResponse::AllSongs(songs))
|
},
|
||||||
.await
|
LibraryCommand::Playlist(uuid) => {
|
||||||
.unwrap();
|
let playlist = library.query_playlist_uuid(&uuid).unwrap();
|
||||||
|
lib_mail.send(LibraryResponse::Playlist(ExternalPlaylist::from_playlist(playlist, &library))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
LibraryCommand::ImportM3UPlayList(path) => {
|
||||||
|
let playlist = Playlist::from_m3u(path, library).unwrap();
|
||||||
|
let uuid = playlist.uuid.clone();
|
||||||
|
let name = playlist.title.clone();
|
||||||
|
library.playlists.items.push(PlaylistFolderItem::List(playlist));
|
||||||
|
|
||||||
|
lib_mail.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap();
|
||||||
|
}
|
||||||
|
_ => { todo!() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -2,6 +2,7 @@ use super::playlist::{Playlist, PlaylistFolder};
|
||||||
// Crate things
|
// Crate things
|
||||||
use super::utils::{find_images, normalize, read_file, write_file};
|
use super::utils::{find_images, normalize, read_file, write_file};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
|
use crate::music_storage::playlist::PlaylistFolderItem;
|
||||||
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
// Various std things
|
// Various std things
|
||||||
|
@ -687,7 +688,7 @@ impl AlbumTrack {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||||
pub struct MusicLibrary {
|
pub struct MusicLibrary {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
|
@ -1237,8 +1238,12 @@ impl MusicLibrary {
|
||||||
Ok(albums)
|
Ok(albums)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn query_playlist_uuid(&self, uuid: &Uuid) -> Result<Playlist, Box<dyn Error>> {
|
pub fn query_playlist_uuid(&self, uuid: &Uuid) -> Option<&Playlist> {
|
||||||
todo!()
|
self.playlists.query_uuid(uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_playlist(&mut self, playlist: PlaylistFolderItem) {
|
||||||
|
self.playlists.items.push(playlist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use std::path::Path;
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::Read,
|
io::Read,
|
||||||
|
@ -10,6 +11,7 @@ use std::time::Duration;
|
||||||
|
|
||||||
// use chrono::Duration;
|
// use chrono::Duration;
|
||||||
use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
|
use super::library::{AlbumArt, MusicLibrary, Song, Tag, URI};
|
||||||
|
use itertools::Itertools;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -28,8 +30,8 @@ nest! {
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]*
|
#[derive(Debug, Clone, Deserialize, Serialize)]*
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct PlaylistFolder {
|
pub struct PlaylistFolder {
|
||||||
name: String,
|
pub(crate) name: String,
|
||||||
items: Vec<
|
pub(crate) items: Vec<
|
||||||
pub enum PlaylistFolderItem {
|
pub enum PlaylistFolderItem {
|
||||||
Folder(PlaylistFolder),
|
Folder(PlaylistFolder),
|
||||||
List(Playlist)
|
List(Playlist)
|
||||||
|
@ -38,15 +40,29 @@ nest! {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PlaylistFolder {
|
||||||
|
pub fn query_uuid(&self, uuid: &Uuid) -> Option<&Playlist> {
|
||||||
|
for item in &self.items {
|
||||||
|
match item {
|
||||||
|
PlaylistFolderItem::Folder(folder) => return folder.query_uuid(uuid),
|
||||||
|
PlaylistFolderItem::List(ref playlist) => if &playlist.uuid == uuid {
|
||||||
|
return Some(&playlist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
pub struct Playlist {
|
pub struct Playlist {
|
||||||
uuid: Uuid,
|
pub(crate) uuid: Uuid,
|
||||||
title: String,
|
pub(crate) title: String,
|
||||||
cover: Option<AlbumArt>,
|
pub(crate) cover: Option<AlbumArt>,
|
||||||
tracks: Vec<Uuid>,
|
pub(crate) tracks: Vec<Uuid>,
|
||||||
sort_order: SortOrder,
|
pub(crate) sort_order: SortOrder,
|
||||||
play_count: i32,
|
pub(crate) play_count: i32,
|
||||||
play_time: Duration,
|
pub(crate) play_time: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Playlist {
|
impl Playlist {
|
||||||
|
@ -115,7 +131,7 @@ impl Playlist {
|
||||||
super::utils::read_file(PathBuf::from(path))
|
super::utils::read_file(PathBuf::from(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_m3u8(
|
pub fn to_m3u(
|
||||||
&mut self,
|
&mut self,
|
||||||
lib: Arc<RwLock<MusicLibrary>>,
|
lib: Arc<RwLock<MusicLibrary>>,
|
||||||
location: &str,
|
location: &str,
|
||||||
|
@ -146,9 +162,9 @@ impl Playlist {
|
||||||
})
|
})
|
||||||
.collect::<Vec<MediaSegment>>();
|
.collect::<Vec<MediaSegment>>();
|
||||||
|
|
||||||
let m3u8 = MediaPlaylist {
|
let m3u = MediaPlaylist {
|
||||||
version: Some(6),
|
version: Some(6),
|
||||||
target_duration: 3.0,
|
target_duration: 3,
|
||||||
media_sequence: 338559,
|
media_sequence: 338559,
|
||||||
discontinuity_sequence: 1234,
|
discontinuity_sequence: 1234,
|
||||||
end_list: true,
|
end_list: true,
|
||||||
|
@ -163,18 +179,15 @@ impl Playlist {
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
.open(location)?;
|
.open(location)?;
|
||||||
m3u8.write_to(&mut file)?;
|
m3u.write_to(&mut file)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_m3u8(
|
pub fn from_m3u(
|
||||||
path: &str,
|
m3u_path: impl AsRef<Path>,
|
||||||
lib: Arc<RwLock<MusicLibrary>>,
|
lib: &mut MusicLibrary,
|
||||||
) -> Result<Playlist, Box<dyn Error>> {
|
) -> Result<Playlist, Box<dyn Error>> {
|
||||||
let mut file = match File::open(path) {
|
let mut file = File::open(&m3u_path)?;
|
||||||
Ok(file) => file,
|
|
||||||
Err(e) => return Err(e.into()),
|
|
||||||
};
|
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
file.read_to_end(&mut bytes).unwrap();
|
file.read_to_end(&mut bytes).unwrap();
|
||||||
|
|
||||||
|
@ -182,7 +195,7 @@ impl Playlist {
|
||||||
|
|
||||||
let playlist = match parsed {
|
let playlist = match parsed {
|
||||||
Result::Ok((_, playlist)) => playlist,
|
Result::Ok((_, playlist)) => playlist,
|
||||||
Result::Err(e) => panic!("Parsing error: \n{}", e),
|
Result::Err(e) => panic!("Parsing error\n{e}"),
|
||||||
};
|
};
|
||||||
|
|
||||||
match playlist {
|
match playlist {
|
||||||
|
@ -192,28 +205,39 @@ impl Playlist {
|
||||||
List2::MediaPlaylist(playlist_) => {
|
List2::MediaPlaylist(playlist_) => {
|
||||||
let mut uuids = Vec::new();
|
let mut uuids = Vec::new();
|
||||||
for seg in playlist_.segments {
|
for seg in playlist_.segments {
|
||||||
let path_ = PathBuf::from(seg.uri.to_owned());
|
let seg_path = seg.uri.to_owned();
|
||||||
let mut lib = lib.write().unwrap();
|
|
||||||
|
|
||||||
let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(path_.clone())) {
|
let song_path = if let Ok(path) = PathBuf::from(&seg_path).canonicalize() {
|
||||||
|
path
|
||||||
|
} else {
|
||||||
|
println!("{seg_path}");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) {
|
||||||
song.uuid
|
song.uuid
|
||||||
} else {
|
} else {
|
||||||
let song_ = Song::from_file(&path_)?;
|
let song_: Song = match Song::from_file(&song_path) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => panic!("{e}\npath: {}", song_path.display())
|
||||||
|
};
|
||||||
let uuid = song_.uuid.to_owned();
|
let uuid = song_.uuid.to_owned();
|
||||||
lib.add_song(song_)?;
|
_ = lib.add_song(song_); // TODO: Add proper error handling with Library
|
||||||
uuid
|
uuid
|
||||||
};
|
};
|
||||||
uuids.push(uuid);
|
uuids.push(uuid);
|
||||||
}
|
}
|
||||||
let mut playlist = Playlist::new();
|
let mut playlist = Playlist::new();
|
||||||
|
|
||||||
|
let path: &str = m3u_path.as_ref().to_str().unwrap();
|
||||||
|
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(target_family = "windows")]
|
||||||
{
|
{
|
||||||
playlist.title = path
|
playlist.title = path
|
||||||
.split('\\')
|
.split('\\')
|
||||||
.last()
|
.last()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strip_suffix(".m3u8")
|
.strip_suffix(".m3u")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
|
@ -223,7 +247,7 @@ impl Playlist {
|
||||||
.split("/")
|
.split("/")
|
||||||
.last()
|
.last()
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.strip_suffix(".m3u8")
|
.strip_suffix(".m3u")
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
.to_string();
|
.to_string();
|
||||||
}
|
}
|
||||||
|
@ -313,42 +337,72 @@ impl Default for Playlist {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||||
|
pub struct ExternalPlaylist {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub title: String,
|
||||||
|
pub tracks: Vec<Song>,
|
||||||
|
pub sort_order: SortOrder,
|
||||||
|
pub play_count: i32,
|
||||||
|
pub play_time: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ExternalPlaylist {
|
||||||
|
pub(crate) fn from_playlist(playlist: &Playlist, library: &MusicLibrary) -> Self {
|
||||||
|
let tracks: Vec<Song> = playlist.tracks.iter().filter_map(|uuid| {
|
||||||
|
library.query_uuid(uuid).map(|res| res.0.clone())
|
||||||
|
}).collect_vec();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
uuid: playlist.uuid.clone(),
|
||||||
|
title: playlist.title.clone(),
|
||||||
|
tracks,
|
||||||
|
sort_order: playlist.sort_order.clone(),
|
||||||
|
play_count: playlist.play_count,
|
||||||
|
play_time: playlist.play_time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_super {
|
mod test_super {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::tests::read_config_lib;
|
use crate::config::tests::{new_config_lib, read_config_lib};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_to_m3u8() {
|
fn list_to_m3u() {
|
||||||
let (_, lib) = read_config_lib();
|
let (_, lib) = read_config_lib();
|
||||||
let mut playlist = Playlist::new();
|
let mut playlist = Playlist::new();
|
||||||
let tracks = lib.library.iter().map(|track| track.uuid).collect();
|
let tracks = lib.library.iter().map(|track| track.uuid).collect();
|
||||||
playlist.set_tracks(tracks);
|
playlist.set_tracks(tracks);
|
||||||
|
|
||||||
_ = playlist.to_m3u8(
|
playlist.to_m3u(
|
||||||
Arc::new(RwLock::from(lib)),
|
Arc::new(RwLock::from(lib)),
|
||||||
".\\test-config\\playlists\\playlist.m3u8",
|
".\\test-config\\playlists\\playlist.m3u",
|
||||||
);
|
).unwrap();
|
||||||
}
|
|
||||||
|
|
||||||
fn m3u8_to_list() -> Playlist {
|
|
||||||
let (_, lib) = read_config_lib();
|
|
||||||
let arc = Arc::new(RwLock::from(lib));
|
|
||||||
let playlist =
|
|
||||||
Playlist::from_m3u8(".\\test-config\\playlists\\playlist.m3u8", arc).unwrap();
|
|
||||||
|
|
||||||
_ = playlist.to_file(".\\test-config\\playlists\\playlist");
|
|
||||||
dbg!(playlist)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn out_queue_sort() {
|
fn m3u_to_list() {
|
||||||
let (_, lib) = read_config_lib();
|
let (_, mut lib) = read_config_lib();
|
||||||
let mut list = m3u8_to_list();
|
|
||||||
list.sort_order = SortOrder::Tag(vec![Tag::Album]);
|
|
||||||
|
|
||||||
let songs = &list.out_tracks(Arc::new(RwLock::from(lib)));
|
let playlist =
|
||||||
|
Playlist::from_m3u("F:\\Music\\Mp3\\Music Main\\Playlists\\Nanahira.m3u", &mut lib).unwrap();
|
||||||
|
|
||||||
dbg!(songs);
|
_ = playlist.to_file(".\\test-config\\playlists\\playlist");
|
||||||
|
dbg!(&playlist, playlist.tracks.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn out_queue_sort() {
|
||||||
|
// let (_, lib) = read_config_lib();
|
||||||
|
// let mut list = m3u_to_list();
|
||||||
|
// list.sort_order = SortOrder::Tag(vec![Tag::Album]);
|
||||||
|
|
||||||
|
// let songs = &list.out_tracks(Arc::new(RwLock::from(lib)));
|
||||||
|
|
||||||
|
// dbg!(songs);
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
|
@ -450,7 +450,7 @@ impl<
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, PartialOrd, Clone)]
|
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)]
|
||||||
pub enum QueueError {
|
pub enum QueueError {
|
||||||
#[error("Index out of bounds! Index {index} is over len {len}")]
|
#[error("Index out of bounds! Index {index} is over len {len}")]
|
||||||
OutOfBounds { index: usize, len: usize },
|
OutOfBounds { index: usize, len: usize },
|
||||||
|
|
|
@ -33,6 +33,7 @@ mime = "0.3.17"
|
||||||
file-format = "0.26.0"
|
file-format = "0.26.0"
|
||||||
chrono = { version = "0.4.38", features = ["serde"] }
|
chrono = { version = "0.4.38", features = ["serde"] }
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
|
rfd = "0.15.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "custom-protocol" ]
|
default = [ "custom-protocol" ]
|
||||||
|
|
|
@ -10,7 +10,7 @@ use crate::wrappers::_Song;
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
|
pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
|
||||||
ctrl_handle.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(uuid)).await.unwrap();
|
ctrl_handle.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(uuid)).await.unwrap();
|
||||||
let LibraryResponse::Song(song) = ctrl_handle.lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::Song(song, _) = ctrl_handle.lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
ctrl_handle.queue_mail.send(dmp_core::music_controller::controller::QueueCommand::Append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location })), true)).await.unwrap();
|
ctrl_handle.queue_mail.send(dmp_core::music_controller::controller::QueueCommand::Append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location })), true)).await.unwrap();
|
||||||
|
|
|
@ -6,7 +6,7 @@ use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{C
|
||||||
use tauri::{http::Response, Emitter, Manager, State, Url, WebviewWindowBuilder, Wry};
|
use tauri::{http::Response, Emitter, Manager, State, Url, WebviewWindowBuilder, Wry};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue};
|
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist};
|
||||||
|
|
||||||
pub mod wrappers;
|
pub mod wrappers;
|
||||||
pub mod commands;
|
pub mod commands;
|
||||||
|
@ -74,6 +74,8 @@ pub fn run() {
|
||||||
get_queue,
|
get_queue,
|
||||||
add_song_to_queue,
|
add_song_to_queue,
|
||||||
play_now,
|
play_now,
|
||||||
|
import_playlist,
|
||||||
|
get_playlist,
|
||||||
]).manage(ConfigRx(rx))
|
]).manage(ConfigRx(rx))
|
||||||
.manage(LibRx(lib_rx))
|
.manage(LibRx(lib_rx))
|
||||||
.manage(HandleTx(handle_tx))
|
.manage(HandleTx(handle_tx))
|
||||||
|
@ -94,7 +96,7 @@ pub fn run() {
|
||||||
futures::executor::block_on(async move {
|
futures::executor::block_on(async move {
|
||||||
let controller = ctx.app_handle().state::<ControllerHandle>();
|
let controller = ctx.app_handle().state::<ControllerHandle>();
|
||||||
controller.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(Uuid::parse_str(query.as_str()).unwrap())).await.unwrap();
|
controller.lib_mail.send(dmp_core::music_controller::controller::LibraryCommand::Song(Uuid::parse_str(query.as_str()).unwrap())).await.unwrap();
|
||||||
let LibraryResponse::Song(song) = controller.lib_mail.recv().await.unwrap() else { unreachable!() };
|
let LibraryResponse::Song(song, _) = controller.lib_mail.recv().await.unwrap() else { unreachable!() };
|
||||||
song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())
|
song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())
|
||||||
})};
|
})};
|
||||||
res.respond(
|
res.respond(
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
use std::collections::BTreeMap;
|
use std::{collections::BTreeMap, path::PathBuf};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
|
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
|
||||||
use crossbeam::channel::Sender;
|
use crossbeam::channel::Sender;
|
||||||
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse, QueueCommand, QueueResponse}, music_storage::library::{BannedType, Song, URI}};
|
use dmp_core::{music_controller::controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerResponse, QueueCommand, QueueResponse}, music_storage::library::{BannedType, Song, Tag, URI}};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use kushi::QueueItemType;
|
use kushi::QueueItemType;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tauri::{ipc::Response, AppHandle, Emitter, State, Wry};
|
use tauri::{ipc::Response, AppHandle, Emitter, State, Wry};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::commands;
|
||||||
|
|
||||||
pub struct ArtworkRx(pub Sender<Vec<u8>>);
|
pub struct ArtworkRx(pub Sender<Vec<u8>>);
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
|
@ -130,6 +132,7 @@ impl From<&Song> for _Song {
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<_Song>, String> {
|
pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<_Song>, String> {
|
||||||
ctrl_handle.lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
|
ctrl_handle.lib_mail.send(LibraryCommand::AllSongs).await.unwrap();
|
||||||
|
println!("getting library");
|
||||||
let LibraryResponse::AllSongs(songs) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
|
let LibraryResponse::AllSongs(songs) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
|
||||||
|
|
||||||
let _songs = songs.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
|
let _songs = songs.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
|
||||||
|
@ -138,9 +141,40 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
pub async fn get_playlist(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<Vec<_Song>, String> {
|
||||||
|
ctrl_handle.lib_mail.send(LibraryCommand::Playlist(uuid)).await.unwrap();
|
||||||
|
let LibraryResponse::Playlist(playlist) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
|
||||||
|
|
||||||
|
let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
|
||||||
|
println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len());
|
||||||
|
Ok(songs)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result<PlaylistPayload, String> {
|
||||||
|
let file = rfd::AsyncFileDialog::new()
|
||||||
|
.add_filter("m3u8 Playlist", &["m3u8", "m3u"])
|
||||||
|
.set_title("Import a Playlist")
|
||||||
|
.pick_file()
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
ctrl_handle.lib_mail.send(LibraryCommand::ImportM3UPlayList(PathBuf::from(file.path()))).await.unwrap();
|
||||||
|
let LibraryResponse::ImportM3UPlayList(uuid, name) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
|
||||||
|
println!("Imported Playlist {name}");
|
||||||
|
Ok(PlaylistPayload {uuid, name})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct PlaylistPayload {
|
||||||
|
uuid: Uuid,
|
||||||
|
name: String
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>) -> Result<_Song, String> {
|
||||||
ctrl_handle.lib_mail.send(LibraryCommand::Song(Uuid::default())).await.unwrap();
|
ctrl_handle.lib_mail.send(LibraryCommand::Song(Uuid::default())).await.unwrap();
|
||||||
let LibraryResponse::Song(_) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
|
let LibraryResponse::Song(song, _) = ctrl_handle.lib_mail.recv().await.unwrap() else { unreachable!("It has been reached") };
|
||||||
println!("got songs");
|
println!("got song {}", &song.tags.get(&Tag::Title).unwrap_or(&String::new()));
|
||||||
Ok(())
|
Ok(_Song::from(&song))
|
||||||
}
|
}
|
112
src/App.tsx
112
src/App.tsx
|
@ -13,6 +13,8 @@ function App() {
|
||||||
const library = useState<JSX.Element[]>([]);
|
const library = useState<JSX.Element[]>([]);
|
||||||
const [queue, setQueue] = useState<JSX.Element[]>([]);
|
const [queue, setQueue] = useState<JSX.Element[]>([]);
|
||||||
const [playing, setPlaying] = useState(false);
|
const [playing, setPlaying] = useState(false);
|
||||||
|
const [playlists, setPlaylists] = useState<JSX.Element[]>([]);
|
||||||
|
const [viewName, setViewName] = useState("Library");
|
||||||
|
|
||||||
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
||||||
<NowPlaying
|
<NowPlaying
|
||||||
|
@ -72,13 +74,13 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<main className="container">
|
<main className="container">
|
||||||
<div className="leftSide">
|
<div className="leftSide">
|
||||||
<PlaylistHead />
|
<PlaylistHead playlists={ playlists } setPlaylists={ setPlaylists } setViewName={ setViewName } setLibrary={ library[1] } />
|
||||||
<MainView lib_ref={ library } />
|
<MainView lib_ref={ library } viewName={ viewName } />
|
||||||
<PlayBar playing={ playing } setPlaying={ setPlaying } />
|
<PlayBar playing={ playing } setPlaying={ setPlaying } />
|
||||||
</div>
|
</div>
|
||||||
<div className="rightSide">
|
<div className="rightSide">
|
||||||
{ nowPlaying }
|
{ nowPlaying }
|
||||||
<Queue songs={queue} setSongs={ setQueue } />
|
<Queue songs={ queue } setSongs={ setQueue } />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
@ -87,47 +89,86 @@ function App() {
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|
||||||
function getConfig(): any {
|
interface PlaylistHeadProps {
|
||||||
invoke('get_config').then( (_config) => {
|
playlists: JSX.Element[]
|
||||||
let config = _config as Config;
|
setPlaylists: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
|
||||||
if (config.libraries.libraries.length == 0) {
|
setViewName: React.Dispatch<React.SetStateAction<string>>,
|
||||||
newWindow()
|
setLibrary: React.Dispatch<React.SetStateAction<JSX.Element[]>>,
|
||||||
} else {
|
|
||||||
// console.log("else");
|
|
||||||
invoke('lib_already_created').then(() => {})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function newWindow() {
|
function PlaylistHead({ playlists, setPlaylists, setViewName, setLibrary }: PlaylistHeadProps) {
|
||||||
invoke('new_library_window').then(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
function PlaylistHead() {
|
let handle_import = () => {
|
||||||
|
invoke('import_playlist').then((_res) => {
|
||||||
|
let res = _res as any;
|
||||||
|
|
||||||
|
setPlaylists([
|
||||||
|
...playlists,
|
||||||
|
<button onClick={ () => {
|
||||||
|
invoke('get_playlist', { uuid: res.uuid }).then((list) => {
|
||||||
|
console.log((list as any[]).length);
|
||||||
|
|
||||||
|
setLibrary([...(list as any[]).map((song) => {
|
||||||
|
// console.log(song);
|
||||||
|
return (
|
||||||
|
<Song
|
||||||
|
key={ song.uuid }
|
||||||
|
location={ song.location }
|
||||||
|
uuid={ song.uuid }
|
||||||
|
plays={ song.plays }
|
||||||
|
duration={ song.duration }
|
||||||
|
tags={ song.tags }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})])
|
||||||
|
})
|
||||||
|
setViewName( res.name )
|
||||||
|
} } key={ 'playlist_' + res.uuid }>{ res.name }</button>
|
||||||
|
])
|
||||||
|
console.log(res.name);
|
||||||
|
})
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<section className="playlistHead">
|
<section className="playlistHead">
|
||||||
<button>Library</button>
|
<button onClick={() => {
|
||||||
<button>Playlist 1</button>
|
setViewName("Library");
|
||||||
<button>Playlist 2</button>
|
invoke('get_library').then((lib) => {
|
||||||
<button>Playlist 3</button>
|
setLibrary([...(lib as any[]).map((song) => {
|
||||||
<button>Playlist 4</button>
|
console.log(song);
|
||||||
<button>Playlist 5</button>
|
|
||||||
<button>Playlist 6</button>
|
return (
|
||||||
|
<Song
|
||||||
|
key={ song.uuid }
|
||||||
|
location={ song.location }
|
||||||
|
uuid={ song.uuid }
|
||||||
|
plays={ song.plays }
|
||||||
|
duration={ song.duration }
|
||||||
|
tags={ song.tags }
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})])
|
||||||
|
})
|
||||||
|
} }>Library</button>
|
||||||
|
{ playlists }
|
||||||
|
<button onClick={ handle_import }>Import .m3u8 Playlist</button>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MainViewProps {
|
interface MainViewProps {
|
||||||
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
|
lib_ref: [JSX.Element[], React.Dispatch<React.SetStateAction<JSX.Element[]>>],
|
||||||
|
viewName: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function MainView({ lib_ref }: MainViewProps) {
|
function MainView({ lib_ref, viewName }: MainViewProps) {
|
||||||
const [library, setLibrary] = lib_ref;
|
const [library, setLibrary] = lib_ref;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = appWindow.listen<any>("library_loaded", (_) => {
|
const unlisten = appWindow.listen<any>("library_loaded", (_) => {
|
||||||
|
console.log("library_loaded");
|
||||||
|
|
||||||
invoke('get_library').then((lib) => {
|
invoke('get_library').then((lib) => {
|
||||||
setLibrary([...(lib as any[]).map((song, i) => {
|
setLibrary([...(lib as any[]).map((song) => {
|
||||||
console.log(song);
|
console.log(song);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -146,9 +187,10 @@ function MainView({ lib_ref }: MainViewProps) {
|
||||||
return () => { unlisten.then((f) => f()) }
|
return () => { unlisten.then((f) => f()) }
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mainView">
|
<div className="mainView">
|
||||||
<h1>Library</h1>
|
<h1>{ viewName }</h1>
|
||||||
<div>{ library }</div>
|
<div>{ library }</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -263,3 +305,19 @@ function QueueSong({ song }: QueueSongProps) {
|
||||||
// </button>
|
// </button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConfig(): any {
|
||||||
|
invoke('get_config').then( (_config) => {
|
||||||
|
let config = _config as Config;
|
||||||
|
if (config.libraries.libraries.length == 0) {
|
||||||
|
newWindow()
|
||||||
|
} else {
|
||||||
|
// console.log("else");
|
||||||
|
invoke('lib_already_created').then(() => {})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function newWindow() {
|
||||||
|
invoke('new_library_window').then(() => {})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue