mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 01:52:53 -05:00
ran cargo fmt
This commit is contained in:
parent
195eed367b
commit
2ac3acdf5a
17 changed files with 1427 additions and 1061 deletions
|
@ -29,7 +29,12 @@ impl Default for ConfigLibrary {
|
|||
}
|
||||
|
||||
impl ConfigLibrary {
|
||||
pub fn new(path: PathBuf, name: String, scan_folders: Option<Vec<PathBuf>>, uuid: Option<Uuid>) -> Self {
|
||||
pub fn new(
|
||||
path: PathBuf,
|
||||
name: String,
|
||||
scan_folders: Option<Vec<PathBuf>>,
|
||||
uuid: Option<Uuid>,
|
||||
) -> Self {
|
||||
ConfigLibrary {
|
||||
name,
|
||||
path,
|
||||
|
@ -197,7 +202,7 @@ pub mod tests {
|
|||
PathBuf::from("test-config/library"),
|
||||
String::from("library"),
|
||||
None,
|
||||
None
|
||||
None,
|
||||
);
|
||||
let mut config = Config {
|
||||
path: PathBuf::from("test-config/config_test.json"),
|
||||
|
|
|
@ -13,11 +13,11 @@ pub mod music_controller {
|
|||
pub mod connections;
|
||||
pub mod controller;
|
||||
pub mod controller_handle;
|
||||
pub mod queue;
|
||||
pub mod library_command;
|
||||
pub mod player_command;
|
||||
pub mod player_monitor;
|
||||
pub mod queue;
|
||||
pub mod queue_command;
|
||||
pub mod library_command;
|
||||
}
|
||||
|
||||
pub mod config;
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
use std::{sync::{atomic::{AtomicBool, Ordering}, Arc}, thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}};
|
||||
use std::{
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread::sleep,
|
||||
time::{Duration, SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use chrono::TimeDelta;
|
||||
use crossbeam::{scope, select};
|
||||
|
@ -8,7 +15,10 @@ use listenbrainz::ListenBrainz;
|
|||
use parking_lot::RwLock;
|
||||
use prismriver::State as PrismState;
|
||||
|
||||
use crate::{config::Config, music_storage::library::{Song, Tag}};
|
||||
use crate::{
|
||||
config::Config,
|
||||
music_storage::library::{Song, Tag},
|
||||
};
|
||||
|
||||
use super::controller::Controller;
|
||||
|
||||
|
@ -16,7 +26,7 @@ use super::controller::Controller;
|
|||
pub(super) enum ConnectionsNotification {
|
||||
Playback {
|
||||
position: Option<TimeDelta>,
|
||||
duration: Option<TimeDelta>
|
||||
duration: Option<TimeDelta>,
|
||||
},
|
||||
StateChange(PrismState),
|
||||
SongChange(Song),
|
||||
|
@ -30,10 +40,9 @@ pub struct ConnectionsInput {
|
|||
|
||||
pub(super) struct ControllerConnections {
|
||||
pub notifications_tx: Receiver<ConnectionsNotification>,
|
||||
pub inner: ConnectionsInput
|
||||
pub inner: ConnectionsInput,
|
||||
}
|
||||
|
||||
|
||||
static DC_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
static LB_ACTIVE: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
|
@ -43,9 +52,9 @@ impl Controller {
|
|||
ControllerConnections {
|
||||
notifications_tx,
|
||||
inner: ConnectionsInput {
|
||||
discord_rpc_client_id
|
||||
discord_rpc_client_id,
|
||||
},
|
||||
}: ControllerConnections
|
||||
}: ControllerConnections,
|
||||
) {
|
||||
let (dc_state_rx, dc_state_tx) = bounded::<PrismState>(1);
|
||||
let (dc_song_rx, dc_song_tx) = bounded::<Song>(1);
|
||||
|
@ -53,49 +62,73 @@ impl Controller {
|
|||
let (lb_eos_rx, lb_eos_tx) = bounded::<()>(1);
|
||||
|
||||
scope(|s| {
|
||||
s.builder().name("Notifications Sorter".to_string()).spawn(|_| {
|
||||
s.builder()
|
||||
.name("Notifications Sorter".to_string())
|
||||
.spawn(|_| {
|
||||
use ConnectionsNotification::*;
|
||||
while true {
|
||||
match notifications_tx.recv().unwrap() {
|
||||
Playback { .. } => {}
|
||||
StateChange(state) => {
|
||||
if DC_ACTIVE.load(Ordering::Relaxed) { dc_state_rx.send(state.clone()).unwrap(); }
|
||||
if DC_ACTIVE.load(Ordering::Relaxed) {
|
||||
dc_state_rx.send(state.clone()).unwrap();
|
||||
}
|
||||
}
|
||||
SongChange(song) => {
|
||||
if DC_ACTIVE.load(Ordering::Relaxed) { dc_song_rx.send(song.clone()).unwrap(); }
|
||||
if LB_ACTIVE.load(Ordering::Relaxed) { lb_song_rx.send(song).unwrap(); }
|
||||
if DC_ACTIVE.load(Ordering::Relaxed) {
|
||||
dc_song_rx.send(song.clone()).unwrap();
|
||||
}
|
||||
if LB_ACTIVE.load(Ordering::Relaxed) {
|
||||
lb_song_rx.send(song).unwrap();
|
||||
}
|
||||
}
|
||||
EOS => {
|
||||
if LB_ACTIVE.load(Ordering::Relaxed) { lb_eos_rx.send(()).unwrap(); }
|
||||
if LB_ACTIVE.load(Ordering::Relaxed) {
|
||||
lb_eos_rx.send(()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}).unwrap();
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
if let Some(client_id) = discord_rpc_client_id {
|
||||
s.builder().name("Discord RPC Handler".to_string()).spawn(move |_| {
|
||||
s.builder()
|
||||
.name("Discord RPC Handler".to_string())
|
||||
.spawn(move |_| {
|
||||
Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx);
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
};
|
||||
|
||||
if let Some(token) = config.read().connections.listenbrainz_token.clone() {
|
||||
s.builder().name("ListenBrainz Handler".to_string()).spawn(move |_| {
|
||||
s.builder()
|
||||
.name("ListenBrainz Handler".to_string())
|
||||
.spawn(move |_| {
|
||||
Controller::listenbrainz_scrobble(&token, lb_song_tx, lb_eos_tx);
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<PrismState>) {
|
||||
// TODO: Handle seeking position change and pause
|
||||
let mut client = discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None);
|
||||
let mut client =
|
||||
discord_presence::Client::with_error_config(client_id, Duration::from_secs(5), None);
|
||||
client.start();
|
||||
while !Client::is_ready() { sleep(Duration::from_millis(100)); }
|
||||
while !Client::is_ready() {
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
println!("discord connected");
|
||||
|
||||
let mut state = "Started".to_string();
|
||||
let mut song: Option<Song> = None;
|
||||
let mut now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?").as_secs();
|
||||
let mut now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("Time went backwards?")
|
||||
.as_secs();
|
||||
DC_ACTIVE.store(true, Ordering::Relaxed);
|
||||
|
||||
while true {
|
||||
|
@ -121,38 +154,47 @@ impl Controller {
|
|||
default(Duration::from_millis(99)) => ()
|
||||
}
|
||||
|
||||
client.set_activity(|activity| {
|
||||
let a = activity.state(
|
||||
song.as_ref().map_or(String::new(), |s| format!(
|
||||
client
|
||||
.set_activity(|activity| {
|
||||
let a = activity
|
||||
.state(song.as_ref().map_or(String::new(), |s| {
|
||||
format!(
|
||||
"{}{}{}",
|
||||
s.get_tag(&Tag::Artist).map_or(String::new(), |album| album.clone()),
|
||||
if s.get_tag(&Tag::Album).is_some() && s.get_tag(&Tag::Artist).is_some() { " - " } else { "" },
|
||||
s.get_tag(&Tag::Album).map_or(String::new(), |album| album.clone())
|
||||
s.get_tag(&Tag::Artist)
|
||||
.map_or(String::new(), |album| album.clone()),
|
||||
if s.get_tag(&Tag::Album).is_some()
|
||||
&& s.get_tag(&Tag::Artist).is_some()
|
||||
{
|
||||
" - "
|
||||
} else {
|
||||
""
|
||||
},
|
||||
s.get_tag(&Tag::Album)
|
||||
.map_or(String::new(), |album| album.clone())
|
||||
)
|
||||
)
|
||||
)._type(discord_presence::models::ActivityType::Listening)
|
||||
.details(
|
||||
if let Some(song) = song {
|
||||
song.get_tag(&Tag::Title).map_or(String::from("Unknown Title"), |title| title.clone())
|
||||
}))
|
||||
._type(discord_presence::models::ActivityType::Listening)
|
||||
.details(if let Some(song) = song {
|
||||
song.get_tag(&Tag::Title)
|
||||
.map_or(String::from("Unknown Title"), |title| title.clone())
|
||||
} else {
|
||||
String::new()
|
||||
}
|
||||
);
|
||||
});
|
||||
if let Some(s) = song {
|
||||
if state.as_str() == "Playing" {
|
||||
a.timestamps(|timestamps| {
|
||||
timestamps.start(now)
|
||||
.end(now + s.duration.as_secs())
|
||||
timestamps.start(now).end(now + s.duration.as_secs())
|
||||
})
|
||||
} else {
|
||||
a
|
||||
}
|
||||
} else {
|
||||
a
|
||||
}.assets(|a| {
|
||||
a.large_text(state.clone())
|
||||
}).instance(true)
|
||||
}).unwrap();
|
||||
}
|
||||
.assets(|a| a.large_text(state.clone()))
|
||||
.instance(true)
|
||||
})
|
||||
.unwrap();
|
||||
}
|
||||
DC_ACTIVE.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
@ -225,10 +267,14 @@ mod test_super {
|
|||
let (song_rx, song_tx) = unbounded();
|
||||
let (eos_rx, eos_tx) = unbounded();
|
||||
|
||||
let (config, lib ) = read_config_lib();
|
||||
let (config, lib) = read_config_lib();
|
||||
song_rx.send(lib.library[0].clone()).unwrap();
|
||||
spawn(|| {
|
||||
Controller::listenbrainz_scrobble(config.connections.listenbrainz_token.unwrap().as_str(), song_tx, eos_tx);
|
||||
Controller::listenbrainz_scrobble(
|
||||
config.connections.listenbrainz_token.unwrap().as_str(),
|
||||
song_tx,
|
||||
eos_tx,
|
||||
);
|
||||
});
|
||||
sleep(Duration::from_secs(10));
|
||||
eos_rx.send(()).unwrap();
|
||||
|
|
|
@ -92,7 +92,7 @@ pub enum PlayerCommand {
|
|||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PlayerResponse {
|
||||
Empty(Result<(), PlayerError>),
|
||||
NowPlaying(Result<Song, QueueError>)
|
||||
NowPlaying(Result<Song, QueueError>),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Clone)]
|
||||
|
@ -146,25 +146,24 @@ pub enum QueueResponse {
|
|||
GetAll(Vec<QueueItem_>),
|
||||
}
|
||||
|
||||
|
||||
pub struct ControllerInput {
|
||||
player_mail: (
|
||||
async_channel::Sender<PlayerCommandInput>,
|
||||
async_channel::Receiver<PlayerCommandInput>
|
||||
async_channel::Receiver<PlayerCommandInput>,
|
||||
),
|
||||
lib_mail: (
|
||||
async_channel::Sender<LibraryCommandInput>,
|
||||
async_channel::Receiver<LibraryCommandInput>
|
||||
async_channel::Receiver<LibraryCommandInput>,
|
||||
),
|
||||
queue_mail: (
|
||||
async_channel::Sender<QueueCommandInput>,
|
||||
async_channel::Receiver<QueueCommandInput>
|
||||
async_channel::Receiver<QueueCommandInput>,
|
||||
),
|
||||
library: MusicLibrary,
|
||||
config: Arc<RwLock<Config>>,
|
||||
playback_info: Arc<AtomicCell<PlaybackInfo>>,
|
||||
notify_next_song: Sender<Song>,
|
||||
connections: Option<ConnectionsInput>
|
||||
connections: Option<ConnectionsInput>,
|
||||
}
|
||||
|
||||
pub struct ControllerHandle {
|
||||
|
@ -174,17 +173,26 @@ pub struct ControllerHandle {
|
|||
}
|
||||
|
||||
impl ControllerHandle {
|
||||
pub fn new(library: MusicLibrary, config: Arc<RwLock<Config>>, connections: Option<ConnectionsInput>) -> (Self, ControllerInput, Arc<AtomicCell<PlaybackInfo>>, Receiver<Song>) {
|
||||
pub fn new(
|
||||
library: MusicLibrary,
|
||||
config: Arc<RwLock<Config>>,
|
||||
connections: Option<ConnectionsInput>,
|
||||
) -> (
|
||||
Self,
|
||||
ControllerInput,
|
||||
Arc<AtomicCell<PlaybackInfo>>,
|
||||
Receiver<Song>,
|
||||
) {
|
||||
let (lib_mail_rx, lib_mail_tx) = async_channel::unbounded();
|
||||
let (player_mail_rx, player_mail_tx) = async_channel::unbounded();
|
||||
let (queue_mail_rx, queue_mail_tx)= async_channel::unbounded();
|
||||
let (queue_mail_rx, queue_mail_tx) = async_channel::unbounded();
|
||||
let playback_info = Arc::new(AtomicCell::new(PlaybackInfo::default()));
|
||||
let notify_next_song = crossbeam::channel::unbounded::<Song>();
|
||||
(
|
||||
ControllerHandle {
|
||||
lib_mail_rx: lib_mail_rx.clone(),
|
||||
player_mail_rx: player_mail_rx.clone(),
|
||||
queue_mail_rx: queue_mail_rx.clone()
|
||||
queue_mail_rx: queue_mail_rx.clone(),
|
||||
},
|
||||
ControllerInput {
|
||||
player_mail: (player_mail_rx, player_mail_tx),
|
||||
|
@ -197,7 +205,7 @@ impl ControllerHandle {
|
|||
connections,
|
||||
},
|
||||
playback_info,
|
||||
notify_next_song.1
|
||||
notify_next_song.1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +255,7 @@ impl Controller {
|
|||
playback_info,
|
||||
notify_next_song,
|
||||
connections,
|
||||
}: ControllerInput
|
||||
}: ControllerInput,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let queue: Queue<QueueSong, QueueAlbum> = Queue {
|
||||
items: Vec::new(),
|
||||
|
@ -270,7 +278,8 @@ impl Controller {
|
|||
let player_state = player.state.clone();
|
||||
let player_timing = player.get_timing_recv();
|
||||
let finished_tx = player.get_finished_recv();
|
||||
let (notifications_rx, notifications_tx) = crossbeam_channel::unbounded::<ConnectionsNotification>();
|
||||
let (notifications_rx, notifications_tx) =
|
||||
crossbeam_channel::unbounded::<ConnectionsNotification>();
|
||||
|
||||
let a = scope.spawn({
|
||||
let queue_mail = queue_mail.clone();
|
||||
|
@ -283,8 +292,7 @@ impl Controller {
|
|||
|
||||
let _lib_mail = lib_mail.0.clone();
|
||||
let _queue_mail = queue_mail.0.clone();
|
||||
scope
|
||||
.spawn(async move {
|
||||
scope.spawn(async move {
|
||||
Controller::player_command_loop(
|
||||
player,
|
||||
player_mail.1,
|
||||
|
@ -296,13 +304,8 @@ impl Controller {
|
|||
.await
|
||||
.unwrap();
|
||||
});
|
||||
scope
|
||||
.spawn(async {
|
||||
Controller::library_loop(
|
||||
lib_mail.1,
|
||||
&mut library,
|
||||
_config,
|
||||
)
|
||||
scope.spawn(async {
|
||||
Controller::library_loop(lib_mail.1, &mut library, _config)
|
||||
.await
|
||||
.unwrap();
|
||||
});
|
||||
|
@ -327,7 +330,8 @@ impl Controller {
|
|||
notify_next_song,
|
||||
notifications_rx,
|
||||
playback_info,
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
if let Some(inner) = connections {
|
||||
|
@ -338,7 +342,8 @@ impl Controller {
|
|||
ControllerConnections {
|
||||
notifications_tx,
|
||||
inner,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
a.join().unwrap();
|
||||
|
|
|
@ -6,7 +6,13 @@ use uuid::Uuid;
|
|||
|
||||
use crate::music_storage::{library::Song, playlist::ExternalPlaylist};
|
||||
|
||||
use super::{controller::{ControllerHandle, LibraryCommand, LibraryResponse, PlayerCommand, PlayerError, PlayerLocation, PlayerResponse, QueueCommand, QueueResponse}, queue::{QueueAlbum, QueueSong}};
|
||||
use super::{
|
||||
controller::{
|
||||
ControllerHandle, LibraryCommand, LibraryResponse, PlayerCommand, PlayerError,
|
||||
PlayerLocation, PlayerResponse, QueueCommand, QueueResponse,
|
||||
},
|
||||
queue::{QueueAlbum, QueueSong},
|
||||
};
|
||||
|
||||
impl ControllerHandle {
|
||||
// The Library Section
|
||||
|
@ -59,12 +65,17 @@ impl ControllerHandle {
|
|||
pub async fn playlist_import_m3u(&self, path: PathBuf) -> Result<(Uuid, String), ()> {
|
||||
let (command, tx) = LibraryCommandInput::command(LibraryCommand::ImportM3UPlayList(path));
|
||||
self.lib_mail_rx.send(command).await.unwrap();
|
||||
let LibraryResponse::ImportM3UPlayList(uuid, name) = tx.recv().await.unwrap() else { unreachable!("It has been reached") };
|
||||
let LibraryResponse::ImportM3UPlayList(uuid, name) = tx.recv().await.unwrap() else {
|
||||
unreachable!("It has been reached")
|
||||
};
|
||||
Ok((uuid, name))
|
||||
}
|
||||
|
||||
// The Queue Section
|
||||
pub async fn queue_append(&self, item: QueueItem<QueueSong, QueueAlbum>) -> Result<(), QueueError> {
|
||||
pub async fn queue_append(
|
||||
&self,
|
||||
item: QueueItem<QueueSong, QueueAlbum>,
|
||||
) -> Result<(), QueueError> {
|
||||
let (command, tx) = QueueCommandInput::command(QueueCommand::Append(item, true));
|
||||
self.queue_mail_rx.send(command).await.unwrap();
|
||||
let QueueResponse::Empty(res) = tx.recv().await.unwrap() else {
|
||||
|
@ -73,7 +84,10 @@ impl ControllerHandle {
|
|||
res
|
||||
}
|
||||
|
||||
pub async fn queue_remove(&self, index: usize) -> Result<QueueItem<QueueSong, QueueAlbum>, QueueError> {
|
||||
pub async fn queue_remove(
|
||||
&self,
|
||||
index: usize,
|
||||
) -> Result<QueueItem<QueueSong, QueueAlbum>, QueueError> {
|
||||
let (command, tx) = QueueCommandInput::command(QueueCommand::Remove(index));
|
||||
self.queue_mail_rx.send(command).await.unwrap();
|
||||
let QueueResponse::Item(res) = tx.recv().await.unwrap() else {
|
||||
|
@ -166,7 +180,7 @@ impl ControllerHandle {
|
|||
|
||||
pub(super) struct LibraryCommandInput {
|
||||
pub res_rx: Sender<LibraryResponse>,
|
||||
pub command: LibraryCommand
|
||||
pub command: LibraryCommand,
|
||||
}
|
||||
|
||||
impl LibraryCommandInput {
|
||||
|
@ -175,16 +189,16 @@ impl LibraryCommandInput {
|
|||
(
|
||||
Self {
|
||||
res_rx: rx,
|
||||
command
|
||||
command,
|
||||
},
|
||||
tx
|
||||
tx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct QueueCommandInput {
|
||||
pub res_rx: Sender<QueueResponse>,
|
||||
pub command: QueueCommand
|
||||
pub command: QueueCommand,
|
||||
}
|
||||
|
||||
impl QueueCommandInput {
|
||||
|
@ -193,16 +207,16 @@ impl QueueCommandInput {
|
|||
(
|
||||
Self {
|
||||
res_rx: rx,
|
||||
command
|
||||
command,
|
||||
},
|
||||
tx
|
||||
tx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct PlayerCommandInput {
|
||||
pub res_rx: Sender<PlayerResponse>,
|
||||
pub command: PlayerCommand
|
||||
pub command: PlayerCommand,
|
||||
}
|
||||
|
||||
impl PlayerCommandInput {
|
||||
|
@ -211,9 +225,9 @@ impl PlayerCommandInput {
|
|||
(
|
||||
Self {
|
||||
res_rx: rx,
|
||||
command
|
||||
command,
|
||||
},
|
||||
tx
|
||||
tx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,18 @@ use std::sync::Arc;
|
|||
use parking_lot::RwLock;
|
||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||
|
||||
use crate::{config::Config, music_storage::{library::MusicLibrary, playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}}};
|
||||
use crate::{
|
||||
config::Config,
|
||||
music_storage::{
|
||||
library::MusicLibrary,
|
||||
playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem},
|
||||
},
|
||||
};
|
||||
|
||||
use super::{controller::{Controller, LibraryCommand, LibraryResponse}, controller_handle::LibraryCommandInput};
|
||||
use super::{
|
||||
controller::{Controller, LibraryCommand, LibraryResponse},
|
||||
controller_handle::LibraryCommandInput,
|
||||
};
|
||||
|
||||
impl Controller {
|
||||
pub(super) async fn library_loop(
|
||||
|
@ -18,34 +27,71 @@ impl Controller {
|
|||
match command {
|
||||
LibraryCommand::Song(uuid) => {
|
||||
let (song, i) = library.query_uuid(&uuid).unwrap();
|
||||
res_rx.send(LibraryResponse::Song(song.clone(), i)).await.unwrap();
|
||||
res_rx
|
||||
.send(LibraryResponse::Song(song.clone(), i))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
LibraryCommand::AllSongs => {
|
||||
res_rx.send(LibraryResponse::AllSongs(library.library.clone())).await.unwrap();
|
||||
},
|
||||
res_rx
|
||||
.send(LibraryResponse::AllSongs(library.library.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
LibraryCommand::ExternalPlaylist(uuid) => {
|
||||
let playlist = library.query_playlist_uuid(&uuid).unwrap();
|
||||
res_rx.send(LibraryResponse::ExternalPlaylist(ExternalPlaylist::from_playlist(playlist, library))).await.unwrap();
|
||||
res_rx
|
||||
.send(LibraryResponse::ExternalPlaylist(
|
||||
ExternalPlaylist::from_playlist(playlist, library),
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
LibraryCommand::ImportM3UPlayList(path) => {
|
||||
let playlist = Playlist::from_m3u(path, library).unwrap();
|
||||
let uuid = playlist.uuid;
|
||||
let name = playlist.title.clone();
|
||||
library.playlists.items.push(PlaylistFolderItem::List(playlist));
|
||||
library
|
||||
.playlists
|
||||
.items
|
||||
.push(PlaylistFolderItem::List(playlist));
|
||||
|
||||
res_rx.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap();
|
||||
res_rx
|
||||
.send(LibraryResponse::ImportM3UPlayList(uuid, name))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
LibraryCommand::Save => {
|
||||
library.save(config.read().libraries.get_library(&library.uuid).unwrap().path.clone()).unwrap();
|
||||
library
|
||||
.save(
|
||||
config
|
||||
.read()
|
||||
.libraries
|
||||
.get_library(&library.uuid)
|
||||
.unwrap()
|
||||
.path
|
||||
.clone(),
|
||||
)
|
||||
.unwrap();
|
||||
res_rx.send(LibraryResponse::Ok).await.unwrap();
|
||||
}
|
||||
LibraryCommand::Playlists => {
|
||||
let mut lists = vec![];
|
||||
library.playlists.lists_recursive().into_par_iter().map(|list| (list.uuid, list.title.clone())).collect_into_vec(&mut lists);
|
||||
library
|
||||
.playlists
|
||||
.lists_recursive()
|
||||
.into_par_iter()
|
||||
.map(|list| (list.uuid, list.title.clone()))
|
||||
.collect_into_vec(&mut lists);
|
||||
|
||||
res_rx.send(LibraryResponse::Playlists(lists)).await.unwrap();
|
||||
res_rx
|
||||
.send(LibraryResponse::Playlists(lists))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
_ => {
|
||||
todo!()
|
||||
}
|
||||
_ => { todo!() }
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -3,9 +3,19 @@ use crossbeam_channel::Sender;
|
|||
use kushi::{QueueItem, QueueItemType};
|
||||
use prismriver::{Prismriver, Volume};
|
||||
|
||||
use crate::music_controller::{controller::{LibraryCommand, LibraryResponse}, queue::QueueSong};
|
||||
use crate::music_controller::{
|
||||
controller::{LibraryCommand, LibraryResponse},
|
||||
queue::QueueSong,
|
||||
};
|
||||
|
||||
use super::{connections::ConnectionsNotification, controller::{Controller, ControllerState, PlayerCommand, PlayerLocation, PlayerResponse, QueueCommand, QueueResponse}, controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput}};
|
||||
use super::{
|
||||
connections::ConnectionsNotification,
|
||||
controller::{
|
||||
Controller, ControllerState, PlayerCommand, PlayerLocation, PlayerResponse, QueueCommand,
|
||||
QueueResponse,
|
||||
},
|
||||
controller_handle::{LibraryCommandInput, PlayerCommandInput, QueueCommandInput},
|
||||
};
|
||||
|
||||
impl Controller {
|
||||
pub(super) async fn player_command_loop(
|
||||
|
@ -19,7 +29,7 @@ impl Controller {
|
|||
player.set_volume(Volume::new(state.volume));
|
||||
'outer: while true {
|
||||
let _mail = player_mail.recv().await;
|
||||
if let Ok(PlayerCommandInput {res_rx, command}) = _mail {
|
||||
if let Ok(PlayerCommandInput { res_rx, command }) = _mail {
|
||||
match command {
|
||||
PlayerCommand::Play => {
|
||||
player.play();
|
||||
|
@ -38,7 +48,10 @@ impl Controller {
|
|||
|
||||
PlayerCommand::Seek(time) => {
|
||||
let res = player.seek_to(TimeDelta::milliseconds(time));
|
||||
res_rx.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::Empty(res.map_err(|e| e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
PlayerCommand::SetVolume(volume) => {
|
||||
|
@ -57,63 +70,79 @@ impl Controller {
|
|||
match tx.recv().await.unwrap() {
|
||||
QueueResponse::Item(Ok(item)) => {
|
||||
let uri = match &item.item {
|
||||
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
|
||||
QueueItemType::Single(song) => {
|
||||
song.song.primary_uri().unwrap().0
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap();
|
||||
let prism_uri =
|
||||
prismriver::utils::path_to_uri(&uri.as_path().unwrap())
|
||||
.unwrap();
|
||||
println!("Playing song at path: {:?}", prism_uri);
|
||||
|
||||
// handle error here for unknown formats
|
||||
player.load_new(&prism_uri).unwrap();
|
||||
player.play();
|
||||
|
||||
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")
|
||||
};
|
||||
|
||||
let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs);
|
||||
let (command, tx) =
|
||||
LibraryCommandInput::command(LibraryCommand::AllSongs);
|
||||
// Append next song in library
|
||||
lib_mail.send(command).await.unwrap();
|
||||
let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else {
|
||||
let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap()
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(np_song.song.uuid));
|
||||
let (command, tx) = LibraryCommandInput::command(
|
||||
LibraryCommand::Song(np_song.song.uuid),
|
||||
);
|
||||
lib_mail.send(command).await.unwrap();
|
||||
let LibraryResponse::Song(_, i) = tx.recv().await.unwrap() else {
|
||||
unreachable!()
|
||||
};
|
||||
if let Some(song) = songs.get(i + 49) {
|
||||
let (command, tx) = QueueCommandInput::command(
|
||||
QueueCommand::Append(
|
||||
QueueItem::from_item_type(
|
||||
QueueItemType::Single(
|
||||
let (command, tx) =
|
||||
QueueCommandInput::command(QueueCommand::Append(
|
||||
QueueItem::from_item_type(QueueItemType::Single(
|
||||
QueueSong {
|
||||
song: song.clone(),
|
||||
location: np_song.location
|
||||
}
|
||||
)
|
||||
),
|
||||
false
|
||||
)
|
||||
);
|
||||
queue_mail.send(command).await
|
||||
.unwrap();
|
||||
let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap() else {
|
||||
location: np_song.location,
|
||||
},
|
||||
)),
|
||||
false,
|
||||
));
|
||||
queue_mail.send(command).await.unwrap();
|
||||
let QueueResponse::Empty(Ok(())) = tx.recv().await.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
} else {
|
||||
println!("Library Empty");
|
||||
}
|
||||
|
||||
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone())))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state.now_playing = np_song.song.uuid;
|
||||
_ = state.write_file();
|
||||
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
|
||||
} QueueResponse::Item(Err(e)) => {
|
||||
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||
notify_connections_
|
||||
.send(ConnectionsNotification::SongChange(np_song.song))
|
||||
.unwrap();
|
||||
}
|
||||
_ => continue
|
||||
QueueResponse::Item(Err(e)) => {
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Err(e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,60 +152,87 @@ impl Controller {
|
|||
match tx.recv().await.unwrap() {
|
||||
QueueResponse::Item(Ok(item)) => {
|
||||
let uri = match &item.item {
|
||||
QueueItemType::Single(song) => song.song.primary_uri().unwrap().0,
|
||||
QueueItemType::Single(song) => {
|
||||
song.song.primary_uri().unwrap().0
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
};
|
||||
|
||||
let prism_uri = prismriver::utils::path_to_uri(&uri.as_path().unwrap()).unwrap();
|
||||
let prism_uri =
|
||||
prismriver::utils::path_to_uri(&uri.as_path().unwrap())
|
||||
.unwrap();
|
||||
player.load_new(&prism_uri).unwrap();
|
||||
player.play();
|
||||
|
||||
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
|
||||
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap();
|
||||
let QueueItemType::Single(np_song) = item.item else {
|
||||
panic!("This is temporary, handle queueItemTypes at some point")
|
||||
};
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone())))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state.now_playing = np_song.song.uuid;
|
||||
_ = state.write_file();
|
||||
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
|
||||
notify_connections_
|
||||
.send(ConnectionsNotification::SongChange(np_song.song))
|
||||
.unwrap();
|
||||
}
|
||||
QueueResponse::Item(Err(e)) => {
|
||||
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Err(e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
_ => continue
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
PlayerCommand::Enqueue(index) => {
|
||||
let (command, tx) = QueueCommandInput::command(QueueCommand::GetIndex(index));
|
||||
queue_mail
|
||||
.send(command)
|
||||
.await
|
||||
.unwrap();
|
||||
let (command, tx) =
|
||||
QueueCommandInput::command(QueueCommand::GetIndex(index));
|
||||
queue_mail.send(command).await.unwrap();
|
||||
match tx.recv().await.unwrap() {
|
||||
QueueResponse::Item(Ok(item)) => {
|
||||
match item.item {
|
||||
QueueItemType::Single(np_song) => {
|
||||
let prism_uri = prismriver::utils::path_to_uri(&np_song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
|
||||
let prism_uri = prismriver::utils::path_to_uri(
|
||||
&np_song
|
||||
.song
|
||||
.primary_uri()
|
||||
.unwrap()
|
||||
.0
|
||||
.as_path()
|
||||
.unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
player.load_new(&prism_uri).unwrap();
|
||||
player.play();
|
||||
|
||||
state.now_playing = np_song.song.uuid;
|
||||
_ = state.write_file();
|
||||
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
|
||||
notify_connections_
|
||||
.send(ConnectionsNotification::SongChange(np_song.song))
|
||||
.unwrap();
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
||||
}
|
||||
QueueResponse::Item(Err(e)) => {
|
||||
res_rx.send(PlayerResponse::Empty(Err(e.into()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::Empty(Err(e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
_ => continue
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
PlayerCommand::PlayNow(uuid, location) => {
|
||||
// TODO: This assumes the uuid doesn't point to an album. we've been over this.
|
||||
let (command, tx) = LibraryCommandInput::command(LibraryCommand::Song(uuid));
|
||||
let (command, tx) =
|
||||
LibraryCommandInput::command(LibraryCommand::Song(uuid));
|
||||
lib_mail.send(command).await.unwrap();
|
||||
let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else {
|
||||
unreachable!()
|
||||
|
@ -187,27 +243,40 @@ impl Controller {
|
|||
match tx.recv().await.unwrap() {
|
||||
QueueResponse::Empty(Ok(())) => (),
|
||||
QueueResponse::Empty(Err(e)) => {
|
||||
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Err(e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
continue;
|
||||
}
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
let (command, tx) = QueueCommandInput::command(
|
||||
QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: np_song.clone(), location })), true)
|
||||
);
|
||||
let (command, tx) = QueueCommandInput::command(QueueCommand::Append(
|
||||
QueueItem::from_item_type(QueueItemType::Single(QueueSong {
|
||||
song: np_song.clone(),
|
||||
location,
|
||||
})),
|
||||
true,
|
||||
));
|
||||
queue_mail.send(command).await.unwrap();
|
||||
match tx.recv().await.unwrap() {
|
||||
QueueResponse::Empty(Ok(())) => (),
|
||||
QueueResponse::Empty(Err(e)) => {
|
||||
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Err(e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
continue;
|
||||
}
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
// TODO: Handle non Local URIs here, and whenever `load_new()` or `load_gapless()` is called
|
||||
let prism_uri = prismriver::utils::path_to_uri(&np_song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
|
||||
let prism_uri = prismriver::utils::path_to_uri(
|
||||
&np_song.primary_uri().unwrap().0.as_path().unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
player.load_new(&prism_uri).unwrap();
|
||||
player.play();
|
||||
|
||||
|
@ -217,39 +286,54 @@ impl Controller {
|
|||
|
||||
let (songs, index) = match location {
|
||||
PlayerLocation::Library => {
|
||||
let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs);
|
||||
let (command, tx) =
|
||||
LibraryCommandInput::command(LibraryCommand::AllSongs);
|
||||
lib_mail.send(command).await.unwrap();
|
||||
let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else {
|
||||
let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
(songs, index)
|
||||
}
|
||||
PlayerLocation::Playlist(uuid) => {
|
||||
let (command, tx) = LibraryCommandInput::command(LibraryCommand::ExternalPlaylist(uuid));
|
||||
let (command, tx) = LibraryCommandInput::command(
|
||||
LibraryCommand::ExternalPlaylist(uuid),
|
||||
);
|
||||
lib_mail.send(command).await.unwrap();
|
||||
let LibraryResponse::ExternalPlaylist(list) = tx.recv().await.unwrap() else {
|
||||
let LibraryResponse::ExternalPlaylist(list) =
|
||||
tx.recv().await.unwrap()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
let index = list.get_index(np_song.uuid).unwrap();
|
||||
(list.tracks, index)
|
||||
}
|
||||
_ => todo!("Got Location other than Library or Playlist")
|
||||
_ => todo!("Got Location other than Library or Playlist"),
|
||||
};
|
||||
|
||||
|
||||
for i in index+1..(index+50) {
|
||||
for i in index + 1..(index + 50) {
|
||||
if let Some(song) = songs.get(i) {
|
||||
let (command, tx) = QueueCommandInput::command(
|
||||
QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), false)
|
||||
);
|
||||
let (command, tx) =
|
||||
QueueCommandInput::command(QueueCommand::Append(
|
||||
QueueItem::from_item_type(QueueItemType::Single(
|
||||
QueueSong {
|
||||
song: song.clone(),
|
||||
location,
|
||||
},
|
||||
)),
|
||||
false,
|
||||
));
|
||||
queue_mail.send(command).await.unwrap();
|
||||
match tx.recv().await.unwrap() {
|
||||
QueueResponse::Empty(Ok(())) => (),
|
||||
QueueResponse::Empty(Err(e)) => {
|
||||
res_rx.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Err(e.into())))
|
||||
.await
|
||||
.unwrap();
|
||||
continue 'outer;
|
||||
}
|
||||
_ => unreachable!()
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
println!("End of Library / Playlist");
|
||||
|
@ -257,11 +341,16 @@ impl Controller {
|
|||
}
|
||||
}
|
||||
// ^ This be my solution for now ^
|
||||
res_rx.send(PlayerResponse::NowPlaying(Ok(np_song.clone()))).await.unwrap();
|
||||
res_rx
|
||||
.send(PlayerResponse::NowPlaying(Ok(np_song.clone())))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
state.now_playing = np_song.uuid;
|
||||
_ = state.write_file();
|
||||
notify_connections_.send(ConnectionsNotification::SongChange(np_song)).unwrap();
|
||||
notify_connections_
|
||||
.send(ConnectionsNotification::SongChange(np_song))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -5,9 +5,16 @@ use crossbeam::atomic::AtomicCell;
|
|||
use crossbeam_channel::{Receiver, Sender};
|
||||
use prismriver::State as PrismState;
|
||||
|
||||
use crate::{music_controller::controller::{PlayerCommand, PlayerResponse}, music_storage::library::Song};
|
||||
use crate::{
|
||||
music_controller::controller::{PlayerCommand, PlayerResponse},
|
||||
music_storage::library::Song,
|
||||
};
|
||||
|
||||
use super::{connections::ConnectionsNotification, controller::{Controller, PlaybackInfo}, controller_handle::PlayerCommandInput};
|
||||
use super::{
|
||||
connections::ConnectionsNotification,
|
||||
controller::{Controller, PlaybackInfo},
|
||||
controller_handle::PlayerCommandInput,
|
||||
};
|
||||
|
||||
impl Controller {
|
||||
pub(super) fn player_monitor_loop(
|
||||
|
@ -17,7 +24,7 @@ impl Controller {
|
|||
player_mail: async_channel::Sender<PlayerCommandInput>,
|
||||
notify_next_song: Sender<Song>,
|
||||
notify_connections_: Sender<ConnectionsNotification>,
|
||||
playback_info: Arc<AtomicCell<PlaybackInfo>>
|
||||
playback_info: Arc<AtomicCell<PlaybackInfo>>,
|
||||
) -> Result<(), ()> {
|
||||
std::thread::scope(|s| {
|
||||
// Thread for timing and metadata
|
||||
|
@ -27,7 +34,12 @@ impl Controller {
|
|||
println!("playback monitor started");
|
||||
while true {
|
||||
let (position, duration) = playback_time_tx.recv().unwrap();
|
||||
notify_connections.send(ConnectionsNotification::Playback { position: position.clone(), duration: duration.clone() }).unwrap();
|
||||
notify_connections
|
||||
.send(ConnectionsNotification::Playback {
|
||||
position: position.clone(),
|
||||
duration: duration.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
playback_info.store(PlaybackInfo { position, duration });
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +47,8 @@ impl Controller {
|
|||
|
||||
// Thread for End of Track
|
||||
let notify_connections = notify_connections_.clone();
|
||||
s.spawn(move || { futures::executor::block_on(async {
|
||||
s.spawn(move || {
|
||||
futures::executor::block_on(async {
|
||||
println!("EOS monitor started");
|
||||
while true {
|
||||
let _ = finished_recv.recv();
|
||||
|
@ -48,12 +61,17 @@ impl Controller {
|
|||
};
|
||||
if let Ok(song) = res {
|
||||
notify_next_song.send(song.clone()).unwrap();
|
||||
notify_connections.send(ConnectionsNotification::SongChange(song)).unwrap();
|
||||
notify_connections.send(ConnectionsNotification::EOS).unwrap();
|
||||
notify_connections
|
||||
.send(ConnectionsNotification::SongChange(song))
|
||||
.unwrap();
|
||||
notify_connections
|
||||
.send(ConnectionsNotification::EOS)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
});});
|
||||
});
|
||||
});
|
||||
|
||||
let notify_connections = notify_connections_.clone();
|
||||
s.spawn(move || {
|
||||
|
@ -63,7 +81,9 @@ impl Controller {
|
|||
if _state != state {
|
||||
state = _state;
|
||||
println!("State Changed to {state:?}");
|
||||
notify_connections.send(ConnectionsNotification::StateChange(state.clone())).unwrap();
|
||||
notify_connections
|
||||
.send(ConnectionsNotification::StateChange(state.clone()))
|
||||
.unwrap();
|
||||
}
|
||||
std::thread::sleep(Duration::from_millis(100));
|
||||
}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
use kushi::{Queue, QueueError, QueueItemType};
|
||||
|
||||
use super::{controller::{Controller, QueueCommand, QueueResponse}, controller_handle::QueueCommandInput, queue::{QueueAlbum, QueueSong}};
|
||||
use super::{
|
||||
controller::{Controller, QueueCommand, QueueResponse},
|
||||
controller_handle::QueueCommandInput,
|
||||
queue::{QueueAlbum, QueueSong},
|
||||
};
|
||||
|
||||
impl Controller {
|
||||
pub(super) async fn queue_loop(
|
||||
|
@ -15,45 +19,55 @@ impl Controller {
|
|||
QueueItemType::Single(song) => queue.add_item(song, by_human),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
res_rx
|
||||
.send(QueueResponse::Empty(Ok(())))
|
||||
.await
|
||||
.unwrap();
|
||||
},
|
||||
res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap();
|
||||
}
|
||||
QueueCommand::Next => {
|
||||
let next = queue.next().map_or( Err(QueueError::NoNext), |s| Ok(s.clone()));
|
||||
let next = queue
|
||||
.next()
|
||||
.map_or(Err(QueueError::NoNext), |s| Ok(s.clone()));
|
||||
res_rx
|
||||
.send(QueueResponse::Item(next.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
QueueCommand::Prev => {
|
||||
let prev = queue.prev().map_or( Err(QueueError::EmptyPlayed), |s| Ok(s.clone()));
|
||||
let prev = queue
|
||||
.prev()
|
||||
.map_or(Err(QueueError::EmptyPlayed), |s| Ok(s.clone()));
|
||||
res_rx
|
||||
.send(QueueResponse::Item(prev.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
QueueCommand::GetIndex(index) => {
|
||||
let item = queue.items.get(index).map_or( Err(QueueError::OutOfBounds { index, len: queue.items.len() }), |s| Ok(s.clone()));
|
||||
let item = queue.items.get(index).map_or(
|
||||
Err(QueueError::OutOfBounds {
|
||||
index,
|
||||
len: queue.items.len(),
|
||||
}),
|
||||
|s| Ok(s.clone()),
|
||||
);
|
||||
res_rx.send(QueueResponse::Item(item)).await.unwrap();
|
||||
}
|
||||
QueueCommand::NowPlaying => {
|
||||
let item = queue.current().map(|t| t.clone());
|
||||
res_rx
|
||||
.send(QueueResponse::Item(item))
|
||||
.await
|
||||
.unwrap();
|
||||
res_rx.send(QueueResponse::Item(item)).await.unwrap();
|
||||
}
|
||||
QueueCommand::Get => {
|
||||
res_rx.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap();
|
||||
res_rx
|
||||
.send(QueueResponse::GetAll(queue.items.clone()))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
QueueCommand::Clear => {
|
||||
queue.clear();
|
||||
res_rx.send(QueueResponse::Empty(Ok(()))).await.unwrap();
|
||||
}
|
||||
QueueCommand::Remove(index) => {
|
||||
res_rx.send(QueueResponse::Item(queue.remove_item(index))).await.unwrap();
|
||||
res_rx
|
||||
.send(QueueResponse::Item(queue.remove_item(index)))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,9 +70,7 @@ impl ExternalLibrary for ITunesLibrary {
|
|||
if song_tags.contains_key("Location") {
|
||||
count3 += 1;
|
||||
//check for skipped IDs
|
||||
if &count3.to_string()
|
||||
!= song_tags.get_key_value("Track ID").unwrap().1
|
||||
{
|
||||
if &count3.to_string() != song_tags.get_key_value("Track ID").unwrap().1 {
|
||||
count3 += 1;
|
||||
count4 += 1;
|
||||
}
|
||||
|
@ -232,7 +230,8 @@ fn get_art(file: &Path) -> Result<Vec<AlbumArt>, lofty::error::LoftyError> {
|
|||
let mut album_art: Vec<AlbumArt> = Vec::new();
|
||||
|
||||
let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2);
|
||||
let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
|
||||
let normal_options =
|
||||
lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
|
||||
let tagged_file: lofty::file::TaggedFile;
|
||||
|
||||
let tag = match Probe::open(file)?.options(normal_options).read() {
|
||||
|
@ -284,7 +283,9 @@ impl ITunesSong {
|
|||
Default::default()
|
||||
}
|
||||
|
||||
fn from_hashmap(map: &mut HashMap<String, String>) -> Result<ITunesSong, lofty::error::LoftyError> {
|
||||
fn from_hashmap(
|
||||
map: &mut HashMap<String, String>,
|
||||
) -> Result<ITunesSong, lofty::error::LoftyError> {
|
||||
let mut song = ITunesSong::new();
|
||||
//get the path with the first bit chopped off
|
||||
let path_: String = map.get_key_value("Location").unwrap().1.clone();
|
||||
|
@ -351,7 +352,7 @@ mod tests {
|
|||
PathBuf::from("test-config/library2"),
|
||||
String::from("library2"),
|
||||
None,
|
||||
None
|
||||
None,
|
||||
);
|
||||
config.libraries.libraries.push(config_lib.clone());
|
||||
|
||||
|
|
|
@ -66,7 +66,6 @@ pub enum Tag {
|
|||
Field(String),
|
||||
}
|
||||
|
||||
|
||||
impl Display for Tag {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let path_str: String = match self {
|
||||
|
@ -228,7 +227,8 @@ impl Song {
|
|||
|
||||
/// Creates a [`Song`] from a music file
|
||||
pub fn from_file<P: ?Sized + AsRef<Path>>(target_file: &P) -> Result<Self, Box<dyn Error>> {
|
||||
let normal_options = lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
|
||||
let normal_options =
|
||||
lofty::config::ParseOptions::new().parsing_mode(lofty::config::ParsingMode::Relaxed);
|
||||
|
||||
let blank_tag = &lofty::tag::Tag::new(TagType::Id3v2);
|
||||
let tagged_file: lofty::file::TaggedFile;
|
||||
|
@ -480,8 +480,12 @@ impl Song {
|
|||
match art {
|
||||
AlbumArt::Embedded(j) => {
|
||||
let file = lofty::read_from_path(self.primary_uri()?.0.path())?;
|
||||
Ok(Some(file.tag(file.primary_tag_type()).unwrap().pictures()[*j].data().to_vec()))
|
||||
},
|
||||
Ok(Some(
|
||||
file.tag(file.primary_tag_type()).unwrap().pictures()[*j]
|
||||
.data()
|
||||
.to_vec(),
|
||||
))
|
||||
}
|
||||
AlbumArt::External(ref path) => {
|
||||
let mut buf = vec![];
|
||||
std::fs::File::open(path.path())?.read_to_end(&mut buf)?;
|
||||
|
@ -491,7 +495,6 @@ impl Song {
|
|||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1236,15 +1239,12 @@ impl MusicLibrary {
|
|||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
use crate::music_storage::library::Tag;
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
config::Config,
|
||||
music_storage::library::MusicLibrary,
|
||||
};
|
||||
use crate::{config::Config, music_storage::library::MusicLibrary};
|
||||
|
||||
#[test]
|
||||
fn library_init() {
|
||||
|
@ -1263,10 +1263,12 @@ mod test {
|
|||
let lib = MusicLibrary::init(
|
||||
PathBuf::from("/media/g2/Storage4/Media-Files/Music/Albums/library.dlib"),
|
||||
Uuid::new_v4(),
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let timer = Instant::now();
|
||||
let result = lib.query_tracks(
|
||||
let result = lib
|
||||
.query_tracks(
|
||||
&String::from(""),
|
||||
&vec![],
|
||||
&vec![
|
||||
|
@ -1275,8 +1277,13 @@ mod test {
|
|||
Tag::Disk,
|
||||
Tag::Track,
|
||||
],
|
||||
).unwrap();
|
||||
println!("{} songs in {}ms", result.len(), timer.elapsed().as_millis());
|
||||
)
|
||||
.unwrap();
|
||||
println!(
|
||||
"{} songs in {}ms",
|
||||
result.len(),
|
||||
timer.elapsed().as_millis()
|
||||
);
|
||||
|
||||
/*
|
||||
for song in result {
|
||||
|
|
|
@ -45,11 +45,13 @@ impl PlaylistFolder {
|
|||
for item in &self.items {
|
||||
match item {
|
||||
PlaylistFolderItem::Folder(folder) => return folder.query_uuid(uuid),
|
||||
PlaylistFolderItem::List(ref playlist) => if &playlist.uuid == uuid {
|
||||
PlaylistFolderItem::List(ref playlist) => {
|
||||
if &playlist.uuid == uuid {
|
||||
return Some(playlist);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
|
@ -225,12 +227,13 @@ impl Playlist {
|
|||
continue;
|
||||
};
|
||||
|
||||
let uuid = if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) {
|
||||
let uuid =
|
||||
if let Some((song, _)) = lib.query_uri(&URI::Local(song_path.clone())) {
|
||||
song.uuid
|
||||
} else {
|
||||
let song_: Song = match Song::from_file(&song_path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => panic!("{e}\npath: {}", song_path.display())
|
||||
Err(e) => panic!("{e}\npath: {}", song_path.display()),
|
||||
};
|
||||
let uuid = song_.uuid.to_owned();
|
||||
_ = lib.add_song(song_); // TODO: Add proper error handling with Library
|
||||
|
@ -348,7 +351,6 @@ impl Default for Playlist {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize)]
|
||||
pub struct ExternalPlaylist {
|
||||
pub uuid: Uuid,
|
||||
|
@ -361,11 +363,11 @@ pub struct ExternalPlaylist {
|
|||
|
||||
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();
|
||||
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,
|
||||
|
@ -373,7 +375,7 @@ impl ExternalPlaylist {
|
|||
tracks,
|
||||
sort_order: playlist.sort_order.clone(),
|
||||
play_count: playlist.play_count,
|
||||
play_time: playlist.play_time
|
||||
play_time: playlist.play_time,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -399,7 +401,6 @@ impl ExternalPlaylist {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_super {
|
||||
use super::*;
|
||||
|
@ -412,18 +413,19 @@ mod test_super {
|
|||
let tracks = lib.library.iter().map(|track| track.uuid).collect();
|
||||
playlist.set_tracks(tracks);
|
||||
|
||||
playlist.to_m3u(
|
||||
playlist
|
||||
.to_m3u(
|
||||
Arc::new(RwLock::from(lib)),
|
||||
".\\test-config\\playlists\\playlist.m3u",
|
||||
).unwrap();
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn m3u_to_list() {
|
||||
let (_, mut lib) = read_config_lib();
|
||||
|
||||
let playlist =
|
||||
Playlist::from_m3u(".\\test-config\\playlists\\playlist", &mut lib).unwrap();
|
||||
let playlist = Playlist::from_m3u(".\\test-config\\playlists\\playlist", &mut lib).unwrap();
|
||||
|
||||
_ = playlist.to_file(".\\test-config\\playlists\\playlist");
|
||||
dbg!(&playlist, playlist.tracks.len());
|
||||
|
|
|
@ -26,13 +26,14 @@ pub enum QueueItemType<
|
|||
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
|
||||
> {
|
||||
Single(T),
|
||||
Multi(U)
|
||||
Multi(U),
|
||||
}
|
||||
|
||||
impl<
|
||||
T: Debug + Clone + PartialEq, // T: The Singular Item Type
|
||||
U: Debug + PartialEq + Clone + IntoIterator, // U: The Multi-Item Type. Needs to be tracked as multiple items
|
||||
> QueueItemType<T, U> {
|
||||
> QueueItemType<T, U>
|
||||
{
|
||||
pub fn from_single(item: T) -> Self {
|
||||
QueueItemType::Single(item)
|
||||
}
|
||||
|
@ -42,12 +43,7 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
impl<
|
||||
T: Debug + Clone + PartialEq,
|
||||
U: Debug + PartialEq + Clone + IntoIterator,
|
||||
>
|
||||
QueueItem<T, U> {
|
||||
impl<T: Debug + Clone + PartialEq, U: Debug + PartialEq + Clone + IntoIterator> QueueItem<T, U> {
|
||||
pub fn from_item_type(item: QueueItemType<T, U>) -> Self {
|
||||
QueueItem {
|
||||
item,
|
||||
|
@ -69,10 +65,7 @@ pub struct Queue<
|
|||
}
|
||||
|
||||
// TODO: HAndle the First QueueState[looping] and shuffle
|
||||
impl<
|
||||
T: Debug + Clone + PartialEq,
|
||||
U: Debug + PartialEq + Clone + IntoIterator,
|
||||
> Queue<T, U> {
|
||||
impl<T: Debug + Clone + PartialEq, U: Debug + PartialEq + Clone + IntoIterator> Queue<T, U> {
|
||||
fn has_addhere(&self) -> bool {
|
||||
for item in &self.items {
|
||||
if item.state == QueueState::AddHere {
|
||||
|
@ -98,7 +91,7 @@ impl<
|
|||
items: Vec::new(),
|
||||
played: Vec::new(),
|
||||
loop_,
|
||||
shuffle
|
||||
shuffle,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -130,7 +123,10 @@ impl<
|
|||
let empty = self.items.is_empty();
|
||||
|
||||
if !empty {
|
||||
self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState;
|
||||
self.items
|
||||
.get_mut(i)
|
||||
.expect("There should be an item at index {i}")
|
||||
.state = QueueState::NoState;
|
||||
}
|
||||
|
||||
if by_human {
|
||||
|
@ -143,13 +139,11 @@ impl<
|
|||
},
|
||||
);
|
||||
} else {
|
||||
self.items.push(
|
||||
QueueItem {
|
||||
self.items.push(QueueItem {
|
||||
item,
|
||||
state: QueueState::NoState,
|
||||
by_human,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +190,10 @@ impl<
|
|||
|
||||
let empty = self.items.is_empty();
|
||||
if !empty {
|
||||
self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState;
|
||||
self.items
|
||||
.get_mut(i)
|
||||
.expect("There should be an item at index {i}")
|
||||
.state = QueueState::NoState;
|
||||
}
|
||||
|
||||
let len = items.len();
|
||||
|
@ -211,13 +208,11 @@ impl<
|
|||
},
|
||||
);
|
||||
} else {
|
||||
self.items.push(
|
||||
QueueItem {
|
||||
self.items.push(QueueItem {
|
||||
item,
|
||||
state: QueueState::NoState,
|
||||
by_human, // false
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
self.items[i + len - if empty { 1 } else { 0 }].state = QueueState::AddHere;
|
||||
|
@ -418,7 +413,8 @@ impl<
|
|||
|
||||
if let QueueItemType::Multi(_) = self.items[0].item {
|
||||
unimplemented!(); // TODO: Handle Multi items here?
|
||||
} if let QueueItemType::Multi(_) = item.item {
|
||||
}
|
||||
if let QueueItemType::Multi(_) = item.item {
|
||||
unimplemented!(); // TODO: Handle Multi items here?
|
||||
}
|
||||
|
||||
|
@ -447,7 +443,6 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug, PartialEq, Eq, PartialOrd, Clone)]
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use std::{fs::OpenOptions, io::Write};
|
||||
|
||||
use dmp_core::music_controller::{controller::{ControllerHandle, PlayerLocation}, queue::QueueSong};
|
||||
use dmp_core::music_controller::{
|
||||
controller::{ControllerHandle, PlayerLocation},
|
||||
queue::QueueSong,
|
||||
};
|
||||
use kushi::QueueItem;
|
||||
use tauri::{AppHandle, Emitter, State, Wry};
|
||||
use tempfile::TempDir;
|
||||
|
@ -8,25 +11,38 @@ use uuid::Uuid;
|
|||
|
||||
use crate::wrappers::_Song;
|
||||
|
||||
|
||||
|
||||
#[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> {
|
||||
dbg!(&location);
|
||||
let (song, _) = ctrl_handle.lib_get_song(uuid).await;
|
||||
match ctrl_handle.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location }))).await {
|
||||
match ctrl_handle
|
||||
.queue_append(QueueItem::from_item_type(kushi::QueueItemType::Single(
|
||||
QueueSong { song, location },
|
||||
)))
|
||||
.await
|
||||
{
|
||||
Ok(()) => (),
|
||||
Err(e) => return Err(e.to_string())
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
|
||||
pub async fn play_now(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
uuid: Uuid,
|
||||
location: PlayerLocation,
|
||||
) -> Result<(), String> {
|
||||
let song = match ctrl_handle.play_now(uuid, location).await {
|
||||
Ok(song) => song,
|
||||
Err(e) => return Err(e.to_string())
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
|
@ -35,11 +51,18 @@ pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHand
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_dir: State<'_, TempDir>, uuid: Uuid) -> Result<(), String> {
|
||||
pub async fn display_album_art(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
temp_dir: State<'_, TempDir>,
|
||||
uuid: Uuid,
|
||||
) -> Result<(), String> {
|
||||
match ctrl_handle.lib_get_song(uuid.clone()).await.0.album_art(0) {
|
||||
Ok(art) => {
|
||||
let mut art = art.unwrap();
|
||||
let path = temp_dir.path().join(format!("CoverArt_{uuid}.{}", file_format::FileFormat::from_bytes(&art).extension()));
|
||||
let path = temp_dir.path().join(format!(
|
||||
"CoverArt_{uuid}.{}",
|
||||
file_format::FileFormat::from_bytes(&art).extension()
|
||||
));
|
||||
if !path.exists() {
|
||||
// TODO: This can be optimised later
|
||||
let mut file = OpenOptions::new()
|
||||
|
@ -53,7 +76,7 @@ pub async fn display_album_art(ctrl_handle: State<'_, ControllerHandle>, temp_di
|
|||
}
|
||||
opener::open(path).unwrap();
|
||||
}
|
||||
Err(e) => return Err(e.to_string())
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
Ok(())
|
||||
}
|
|
@ -1,21 +1,36 @@
|
|||
#![allow(while_true)]
|
||||
|
||||
use std::{fs, path::PathBuf, sync::Arc, thread::{scope, spawn}, time::Duration};
|
||||
use std::{
|
||||
fs,
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
thread::{scope, spawn},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
|
||||
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::{connections::ConnectionsInput, controller::{Controller, ControllerHandle, PlaybackInfo}}, music_storage::library::{MusicLibrary, Song}};
|
||||
use dmp_core::{
|
||||
config::{Config, ConfigLibrary},
|
||||
music_controller::{
|
||||
connections::ConnectionsInput,
|
||||
controller::{Controller, ControllerHandle, PlaybackInfo},
|
||||
},
|
||||
music_storage::library::{MusicLibrary, Song},
|
||||
};
|
||||
use futures::channel::oneshot;
|
||||
use parking_lot::RwLock;
|
||||
use tauri::{http::Response, Emitter, Manager, State, Wry};
|
||||
use uuid::Uuid;
|
||||
use wrappers::{_Song, stop};
|
||||
|
||||
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue, import_playlist, get_playlist, get_playlists, remove_from_queue, seek};
|
||||
use commands::{add_song_to_queue, play_now, display_album_art};
|
||||
use crate::wrappers::{
|
||||
get_library, get_playlist, get_playlists, get_queue, get_song, import_playlist, next, pause,
|
||||
play, prev, remove_from_queue, seek, set_volume,
|
||||
};
|
||||
use commands::{add_song_to_queue, display_album_art, play_now};
|
||||
|
||||
|
||||
pub mod wrappers;
|
||||
pub mod commands;
|
||||
pub mod wrappers;
|
||||
|
||||
const DEFAULT_IMAGE: &[u8] = include_bytes!("../icons/icon.png");
|
||||
|
||||
|
@ -28,7 +43,7 @@ pub fn run() {
|
|||
let (next_rx, next_tx) = bounded(1);
|
||||
|
||||
let _controller_thread = spawn(move || {
|
||||
let mut config = { tx.recv().unwrap() } ;
|
||||
let mut config = { tx.recv().unwrap() };
|
||||
let scan_path = { lib_tx.recv().unwrap() };
|
||||
let _temp_config = ConfigLibrary::default();
|
||||
let _lib = config.libraries.get_default().unwrap_or(&_temp_config);
|
||||
|
@ -46,18 +61,32 @@ pub fn run() {
|
|||
} else {
|
||||
_lib.path.clone()
|
||||
};
|
||||
println!("save_path: {}\nscan_path:{scan_path:?}", save_path.display());
|
||||
println!(
|
||||
"save_path: {}\nscan_path:{scan_path:?}",
|
||||
save_path.display()
|
||||
);
|
||||
|
||||
let mut library = MusicLibrary::init(
|
||||
save_path.clone(),
|
||||
_lib.uuid
|
||||
).unwrap();
|
||||
let mut library = MusicLibrary::init(save_path.clone(), _lib.uuid).unwrap();
|
||||
|
||||
let scan_path = scan_path.unwrap_or_else(|| config.libraries.get_default().unwrap().scan_folders.as_ref().unwrap()[0].clone());
|
||||
let scan_path = scan_path.unwrap_or_else(|| {
|
||||
config
|
||||
.libraries
|
||||
.get_default()
|
||||
.unwrap()
|
||||
.scan_folders
|
||||
.as_ref()
|
||||
.unwrap()[0]
|
||||
.clone()
|
||||
});
|
||||
|
||||
if config.libraries.get_default().is_err() {
|
||||
library.scan_folder(&scan_path).unwrap();
|
||||
config.push_library( ConfigLibrary::new(save_path.clone(), String::from("Library"), Some(vec![scan_path.clone()]), Some(library.uuid)));
|
||||
config.push_library(ConfigLibrary::new(
|
||||
save_path.clone(),
|
||||
String::from("Library"),
|
||||
Some(vec![scan_path.clone()]),
|
||||
Some(library.uuid),
|
||||
));
|
||||
}
|
||||
if library.library.is_empty() {
|
||||
println!("library is empty");
|
||||
|
@ -68,16 +97,12 @@ pub fn run() {
|
|||
|
||||
library.save(save_path).unwrap();
|
||||
|
||||
let (
|
||||
handle,
|
||||
input,
|
||||
playback_info,
|
||||
next_song_notification,
|
||||
) = ControllerHandle::new(
|
||||
let (handle, input, playback_info, next_song_notification) = ControllerHandle::new(
|
||||
library,
|
||||
std::sync::Arc::new(RwLock::new(config)),
|
||||
Some(ConnectionsInput {
|
||||
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID").map(|id| id.parse::<u64>().unwrap()),
|
||||
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID")
|
||||
.map(|id| id.parse::<u64>().unwrap()),
|
||||
}),
|
||||
);
|
||||
|
||||
|
@ -86,9 +111,6 @@ pub fn run() {
|
|||
next_rx.send(next_song_notification).unwrap();
|
||||
|
||||
let _controller = futures::executor::block_on(Controller::start(input)).unwrap();
|
||||
|
||||
|
||||
|
||||
});
|
||||
let app = tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
|
@ -113,7 +135,8 @@ pub fn run() {
|
|||
remove_from_queue,
|
||||
display_album_art,
|
||||
seek,
|
||||
]).manage(ConfigRx(rx))
|
||||
])
|
||||
.manage(ConfigRx(rx))
|
||||
.manage(LibRx(lib_rx))
|
||||
.manage(HandleTx(handle_tx))
|
||||
.manage(tempfile::TempDir::new().unwrap())
|
||||
|
@ -124,7 +147,8 @@ pub fn run() {
|
|||
std::thread::Builder::new()
|
||||
.name("PlaybackInfo handler".to_string())
|
||||
.spawn(move || {
|
||||
let mut _info: Arc<RwLock<PlaybackInfo>> = Arc::new(RwLock::new(PlaybackInfo::default()));
|
||||
let mut _info: Arc<RwLock<PlaybackInfo>> =
|
||||
Arc::new(RwLock::new(PlaybackInfo::default()));
|
||||
let mut _now_playing: Arc<RwLock<Option<Song>>> = Arc::new(RwLock::new(None));
|
||||
|
||||
scope(|s| {
|
||||
|
@ -153,7 +177,8 @@ pub fn run() {
|
|||
}
|
||||
});
|
||||
});
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
@ -171,11 +196,20 @@ pub fn run() {
|
|||
|
||||
let bytes = if query.as_str() == "default" {
|
||||
Some(DEFAULT_IMAGE.to_vec())
|
||||
} else {futures::executor::block_on(async move {
|
||||
} else {
|
||||
futures::executor::block_on(async move {
|
||||
let controller = ctx.app_handle().state::<ControllerHandle>();
|
||||
let song = controller.lib_get_song(Uuid::parse_str(query.as_str()).unwrap()).await.0;
|
||||
Some(song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec()))
|
||||
})};
|
||||
let song = controller
|
||||
.lib_get_song(Uuid::parse_str(query.as_str()).unwrap())
|
||||
.await
|
||||
.0;
|
||||
Some(
|
||||
song.album_art(0)
|
||||
.unwrap_or_else(|_| None)
|
||||
.unwrap_or(DEFAULT_IMAGE.to_vec()),
|
||||
)
|
||||
})
|
||||
};
|
||||
|
||||
res.respond(
|
||||
Response::builder()
|
||||
|
@ -183,14 +217,13 @@ pub fn run() {
|
|||
.header("Content-Length", bytes.as_ref().unwrap_or(&vec![]).len())
|
||||
.status(200)
|
||||
.body(bytes.unwrap_or_default())
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
);
|
||||
})
|
||||
.build(tauri::generate_context!())
|
||||
.expect("error while building tauri application");
|
||||
|
||||
app
|
||||
.run(|_app_handle, event| match event {
|
||||
app.run(|_app_handle, event| match event {
|
||||
tauri::RunEvent::ExitRequested { .. } => {
|
||||
// api.prevent_exit();
|
||||
//panic!("does this kill the player?")
|
||||
|
@ -204,18 +237,19 @@ struct ConfigRx(Sender<Config>);
|
|||
struct LibRx(Sender<Option<PathBuf>>);
|
||||
struct HandleTx(Receiver<ControllerHandle>);
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_config(state: State<'_, ConfigRx>) -> Result<Config, String> {
|
||||
if let Some(dir) = directories::ProjectDirs::from("", "Dangoware", "dmp") {
|
||||
let path = dir.config_dir();
|
||||
fs::create_dir_all(path).or_else(|err| {
|
||||
fs::create_dir_all(path)
|
||||
.or_else(|err| {
|
||||
if err.kind() == std::io::ErrorKind::AlreadyExists {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(err)
|
||||
}
|
||||
}).unwrap();
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let config = if let Ok(mut c) = Config::read_file(PathBuf::from(path).join("config")) {
|
||||
if c.state_path == PathBuf::default() {
|
||||
|
@ -268,7 +302,11 @@ async fn create_new_library(
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn lib_already_created(app: tauri::AppHandle<Wry>, lib_rx: State<'_, LibRx>, handle_tx: State<'_, HandleTx>) -> Result<(), String> {
|
||||
async fn lib_already_created(
|
||||
app: tauri::AppHandle<Wry>,
|
||||
lib_rx: State<'_, LibRx>,
|
||||
handle_tx: State<'_, HandleTx>,
|
||||
) -> Result<(), String> {
|
||||
println!("lib already created");
|
||||
lib_rx.inner().0.send(None).unwrap();
|
||||
app.manage(handle_tx.inner().0.recv().unwrap());
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
|
||||
fn main() {
|
||||
colog::init();
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
use std::{collections::BTreeMap, path::PathBuf};
|
||||
|
||||
use chrono::{DateTime, Utc, serde::ts_milliseconds_option};
|
||||
use chrono::{serde::ts_milliseconds_option, DateTime, Utc};
|
||||
use crossbeam::channel::Sender;
|
||||
use dmp_core::{music_controller::controller::{ControllerHandle, PlayerLocation}, music_storage::library::{Song, Tag, URI}};
|
||||
use dmp_core::{
|
||||
music_controller::controller::{ControllerHandle, PlayerLocation},
|
||||
music_storage::library::{Song, Tag, URI},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use kushi::QueueItemType;
|
||||
use serde::Serialize;
|
||||
|
@ -12,40 +15,52 @@ use uuid::Uuid;
|
|||
pub struct ArtworkRx(pub Sender<Vec<u8>>);
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn play(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
pub async fn play(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<(), String> {
|
||||
match ctrl_handle.play().await {
|
||||
Ok(()) => {
|
||||
app.emit("playing", ()).unwrap();
|
||||
Ok(())
|
||||
},
|
||||
Err(e) => Err(e.to_string())
|
||||
}
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn pause(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
pub async fn pause(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<(), String> {
|
||||
match ctrl_handle.pause().await {
|
||||
Ok(()) => {
|
||||
app.emit("paused", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.to_string())
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn stop(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
pub async fn stop(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<(), String> {
|
||||
match ctrl_handle.stop().await {
|
||||
Ok(()) => {
|
||||
app.emit("stop", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.to_string())
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String) -> Result<(), String> {
|
||||
pub async fn set_volume(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
volume: String,
|
||||
) -> Result<(), String> {
|
||||
let volume = volume.parse::<f32>().unwrap() / 100.0;
|
||||
ctrl_handle.set_volume(volume).await;
|
||||
Ok(())
|
||||
|
@ -57,10 +72,13 @@ pub async fn get_volume(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(),
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
pub async fn next(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<(), String> {
|
||||
let song = match ctrl_handle.next().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(e.to_string())
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
|
@ -69,10 +87,13 @@ pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn prev(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
pub async fn prev(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<(), String> {
|
||||
let song = match ctrl_handle.prev().await {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Err(e.to_string())
|
||||
Err(e) => return Err(e.to_string()),
|
||||
};
|
||||
println!("prev");
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
|
@ -82,33 +103,38 @@ pub async fn prev(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
|
|||
|
||||
#[tauri::command]
|
||||
pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_queue(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec<(_Song, PlayerLocation)>, String> {
|
||||
Ok(
|
||||
ctrl_handle
|
||||
pub async fn get_queue(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<Vec<(_Song, PlayerLocation)>, String> {
|
||||
Ok(ctrl_handle
|
||||
.queue_get_all()
|
||||
.await
|
||||
.into_iter()
|
||||
.map(|item| {
|
||||
let QueueItemType::Single(song) = item.item else { unreachable!("There should be no albums in the queue right now") };
|
||||
let QueueItemType::Single(song) = item.item else {
|
||||
unreachable!("There should be no albums in the queue right now")
|
||||
};
|
||||
(_Song::from(&song.song), song.location)
|
||||
}
|
||||
).collect_vec()
|
||||
)
|
||||
})
|
||||
.collect_vec())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn remove_from_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, index: usize) -> Result<(), String> {
|
||||
pub async fn remove_from_queue(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
index: usize,
|
||||
) -> Result<(), String> {
|
||||
match ctrl_handle.queue_remove(index).await {
|
||||
Ok(_) => {
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.to_string())
|
||||
Err(e) => Err(e.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -140,7 +166,11 @@ impl From<&Song> for _Song {
|
|||
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()
|
||||
tags: value
|
||||
.tags
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.clone()))
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -157,26 +187,49 @@ pub async fn get_library(ctrl_handle: State<'_, ControllerHandle>) -> Result<Vec
|
|||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_playlist(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<Vec<_Song>, String> {
|
||||
pub async fn get_playlist(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
uuid: Uuid,
|
||||
) -> Result<Vec<_Song>, String> {
|
||||
let playlist = match ctrl_handle.playlist_get(uuid).await {
|
||||
Ok(list) => list,
|
||||
Err(_) => todo!()
|
||||
Err(_) => todo!(),
|
||||
};
|
||||
|
||||
let songs = playlist.tracks.iter().map(|song| _Song::from(song)).collect::<Vec<_>>();
|
||||
println!("Got Playlist {}, len {}", playlist.title, playlist.tracks.len());
|
||||
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 get_playlists(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
|
||||
pub async fn get_playlists(
|
||||
app: AppHandle<Wry>,
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
) -> Result<(), String> {
|
||||
let lists = ctrl_handle.playlist_get_all().await;
|
||||
app.emit("playlists_gotten", lists.into_iter().map(|(uuid, name)| PlaylistPayload { uuid, name }).collect_vec()).unwrap();
|
||||
app.emit(
|
||||
"playlists_gotten",
|
||||
lists
|
||||
.into_iter()
|
||||
.map(|(uuid, name)| PlaylistPayload { uuid, name })
|
||||
.collect_vec(),
|
||||
)
|
||||
.unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result<PlaylistPayload, String> {
|
||||
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")
|
||||
|
@ -184,23 +237,32 @@ pub async fn import_playlist(ctrl_handle: State<'_, ControllerHandle>) -> Result
|
|||
.await
|
||||
.unwrap();
|
||||
|
||||
let (uuid, name) = ctrl_handle.playlist_import_m3u(PathBuf::from(file.path())).await.unwrap();
|
||||
let (uuid, name) = ctrl_handle
|
||||
.playlist_import_m3u(PathBuf::from(file.path()))
|
||||
.await
|
||||
.unwrap();
|
||||
ctrl_handle.lib_save().await;
|
||||
|
||||
println!("Imported Playlist {name}");
|
||||
Ok(PlaylistPayload {uuid, name})
|
||||
Ok(PlaylistPayload { uuid, name })
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
pub struct PlaylistPayload {
|
||||
uuid: Uuid,
|
||||
name: String
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_song(ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid) -> Result<_Song, String> {
|
||||
pub async fn get_song(
|
||||
ctrl_handle: State<'_, ControllerHandle>,
|
||||
uuid: Uuid,
|
||||
) -> 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()));
|
||||
println!(
|
||||
"got song {}",
|
||||
&song.tags.get(&Tag::Title).unwrap_or(&String::new())
|
||||
);
|
||||
Ok(_Song::from(&song))
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue