diff --git a/dmp-core/src/lib.rs b/dmp-core/src/lib.rs index 5f86f12..0ddd71a 100644 --- a/dmp-core/src/lib.rs +++ b/dmp-core/src/lib.rs @@ -1,3 +1,4 @@ +#![allow(while_true)] pub mod music_storage { pub mod library; pub mod music_collection; @@ -13,6 +14,10 @@ pub mod music_controller { pub mod controller; pub mod controller_handle; pub mod queue; + pub mod player_command; + pub mod player_monitor; + pub mod queue_command; + pub mod library_command; } pub mod config; diff --git a/dmp-core/src/music_controller/connections.rs b/dmp-core/src/music_controller/connections.rs index ed09ff0..26bc120 100644 --- a/dmp-core/src/music_controller/connections.rs +++ b/dmp-core/src/music_controller/connections.rs @@ -1,4 +1,3 @@ -#![allow(while_true)] use std::{sync::{atomic::{AtomicBool, Ordering}, Arc}, thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}}; use chrono::TimeDelta; @@ -11,7 +10,7 @@ use prismriver::State as PrismState; use crate::{config::Config, music_storage::library::{Song, Tag}}; -use super::controller::{Controller, PlaybackInfo}; +use super::controller::Controller; #[derive(Debug, Clone)] pub(super) enum ConnectionsNotification { @@ -58,7 +57,7 @@ impl Controller { use ConnectionsNotification::*; while true { match notifications_tx.recv().unwrap() { - Playback { position, duration } => {} + Playback { .. } => {} StateChange(state) => { if DC_ACTIVE.load(Ordering::Relaxed) { dc_state_rx.send(state.clone()).unwrap(); } } diff --git a/dmp-core/src/music_controller/controller.rs b/dmp-core/src/music_controller/controller.rs index 9fa2ac7..bdc9151 100644 --- a/dmp-core/src/music_controller/controller.rs +++ b/dmp-core/src/music_controller/controller.rs @@ -1,16 +1,14 @@ //! The [Controller] is the input and output for the entire //! player. It manages queues, playback, library access, and //! other functions -#![allow(while_true)] use chrono::TimeDelta; use crossbeam::atomic::AtomicCell; use crossbeam_channel::{Receiver, Sender}; -use kushi::{Queue, QueueItemType}; +use kushi::Queue; use kushi::{QueueError, QueueItem}; use parking_lot::RwLock; -use prismriver::{Error as PrismError, Prismriver, State as PrismState, Volume}; -use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator}; +use prismriver::{Error as PrismError, Prismriver}; use serde::{Deserialize, Serialize}; use serde_json::to_string_pretty; use std::error::Error; @@ -18,13 +16,12 @@ use std::fs::OpenOptions; use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::Arc; -use std::time::Duration; use thiserror::Error; use uuid::Uuid; use crate::config::ConfigError; use crate::music_storage::library::Song; -use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem}; +use crate::music_storage::playlist::{ExternalPlaylist, Playlist}; use crate::{config::Config, music_storage::library::MusicLibrary}; use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections}; @@ -207,13 +204,13 @@ impl ControllerHandle { #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct ControllerState { - path: PathBuf, - volume: f32, - now_playing: Uuid, + pub(super) path: PathBuf, + pub(super) volume: f32, + pub(super) now_playing: Uuid, } impl ControllerState { - fn new(path: PathBuf) -> Self { + pub(super) fn new(path: PathBuf) -> Self { ControllerState { path, volume: 0.35, @@ -221,7 +218,7 @@ impl ControllerState { } } - fn write_file(&self) -> Result<(), std::io::Error> { + pub(super) fn write_file(&self) -> Result<(), std::io::Error> { OpenOptions::new() .truncate(true) .create(true) @@ -232,7 +229,7 @@ impl ControllerState { Ok(()) } - fn read_file(path: impl AsRef) -> Result { + pub(super) fn read_file(path: impl AsRef) -> Result { let state = serde_json::from_str(&std::fs::read_to_string(path)?)?; Ok(state) } @@ -327,7 +324,6 @@ impl Controller { player_timing, finished_tx, player_mail.0, - queue_mail.0, notify_next_song, notifications_rx, playback_info, @@ -352,436 +348,6 @@ impl Controller { Ok(()) } - - async fn player_command_loop( - mut player: Prismriver, - player_mail: async_channel::Receiver, - queue_mail: async_channel::Sender, - lib_mail: async_channel::Sender, - notify_connections_: Sender, - mut state: ControllerState, - ) -> Result<(), ()> { - player.set_volume(Volume::new(state.volume)); - 'outer: while true { - let _mail = player_mail.recv().await; - if let Ok(PlayerCommandInput {res_rx, command}) = _mail { - match command { - PlayerCommand::Play => { - player.play(); - res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); - } - - PlayerCommand::Pause => { - player.pause(); - res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); - } - - PlayerCommand::Stop => { - player.stop(); - res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); - } - - PlayerCommand::Seek(time) => { - let res = player.seek_to(TimeDelta::milliseconds(time)); - res_rx.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap(); - } - - PlayerCommand::SetVolume(volume) => { - player.set_volume(Volume::new(volume)); - res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); - - // make this async or something - state.volume = volume; - _ = state.write_file() - } - - PlayerCommand::NextSong => { - let (command, tx) = QueueCommandInput::command(QueueCommand::Next); - queue_mail.send(command).await.unwrap(); - - match tx.recv().await.unwrap() { - QueueResponse::Item(Ok(item)) => { - let uri = match &item.item { - QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, - _ => unimplemented!(), - }; - - 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 (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 { - continue; - }; - - 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( - QueueSong { - song: song.clone(), - 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(); - - 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(); - } - _ => continue - } - } - - PlayerCommand::PrevSong => { - let (command, tx) = QueueCommandInput::command(QueueCommand::Prev); - queue_mail.send(command).await.unwrap(); - match tx.recv().await.unwrap() { - QueueResponse::Item(Ok(item)) => { - let uri = match &item.item { - QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, - _ => unimplemented!(), - }; - - 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(); - - 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(); - } - _ => continue - } - } - - PlayerCommand::Enqueue(index) => { - 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(); - 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(); - } - _ => unimplemented!(), - } - res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); - } - QueueResponse::Item(Err(e)) => { - res_rx.send(PlayerResponse::Empty(Err(e.into()))).await.unwrap(); - } - _ => 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)); - lib_mail.send(command).await.unwrap(); - let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else { - unreachable!() - }; - - let (command, tx) = QueueCommandInput::command(QueueCommand::Clear); - 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(); - continue; - } - _ => unreachable!() - } - - 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(); - continue; - } - _ => 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(); - player.load_new(&prism_uri).unwrap(); - player.play(); - - // how grab all the songs in a certain subset of the library, I reckon? - // ... - // let's just pretend I figured that out already - - let (songs, index) = match location { - PlayerLocation::Library => { - let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs); - lib_mail.send(command).await.unwrap(); - let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else { - unreachable!() - }; - (songs, index) - } - PlayerLocation::Playlist(uuid) => { - let (command, tx) = LibraryCommandInput::command(LibraryCommand::ExternalPlaylist(uuid)); - lib_mail.send(command).await.unwrap(); - 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") - }; - - - 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) - ); - 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(); - continue 'outer; - } - _ => unreachable!() - } - } else { - println!("End of Library / Playlist"); - break; - } - } - // ^ This be my solution for now ^ - 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(); - } - } - } else { - return Err(()); - } - } - Ok(()) - } - - fn player_monitor_loop( - playback_state: Arc>, - playback_time_tx: Receiver<(Option, Option)>, - finished_recv: Receiver<()>, - player_mail: async_channel::Sender, - queue_mail: async_channel::Sender, - notify_next_song: Sender, - notify_connections_: Sender, - playback_info: Arc> - ) -> Result<(), ()> { - std::thread::scope(|s| { - // Thread for timing and metadata - let notify_connections = notify_connections_.clone(); - s.spawn({ - move || { - 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(); - playback_info.store(PlaybackInfo { position, duration }); - } - } - }); - - // Thread for End of Track - let notify_connections = notify_connections_.clone(); - s.spawn(move || { futures::executor::block_on(async { - println!("EOS monitor started"); - while true { - let _ = finished_recv.recv(); - println!("End of song"); - - let (command, tx) = PlayerCommandInput::command(PlayerCommand::NextSong); - player_mail.send(command).await.unwrap(); - let PlayerResponse::NowPlaying(res) = tx.recv().await.unwrap() else { - unreachable!() - }; - 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(); - } - } - std::thread::sleep(Duration::from_millis(100)); - });}); - - let notify_connections = notify_connections_.clone(); - s.spawn(move || { - let mut state = PrismState::Stopped; - while true { - let _state = playback_state.read().unwrap().to_owned(); - if _state != state { - state = _state; - println!("State Changed to {state:?}"); - notify_connections.send(ConnectionsNotification::StateChange(state.clone())).unwrap(); - } - std::thread::sleep(Duration::from_millis(100)); - } - }); - }); - - - - println!("Monitor Loops Ended"); - Ok(()) - } - - - async fn library_loop( - lib_mail: async_channel::Receiver, - library: &mut MusicLibrary, - config: Arc>, - ) -> Result<(), ()> { - while true { - let LibraryCommandInput { res_rx, command } = lib_mail.recv().await.unwrap(); - match command { - LibraryCommand::Song(uuid) => { - let (song, i) = library.query_uuid(&uuid).unwrap(); - res_rx.send(LibraryResponse::Song(song.clone(), i)).await.unwrap(); - } - LibraryCommand::AllSongs => { - 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(); - } - 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)); - - res_rx.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap(); - } - LibraryCommand::Save => { - 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); - - res_rx.send(LibraryResponse::Playlists(lists)).await.unwrap(); - } - _ => { todo!() } - } - } - Ok(()) - } - - async fn queue_loop( - mut queue: Queue, - queue_mail: async_channel::Receiver, - ) { - while true { - let QueueCommandInput { res_rx, command } = queue_mail.recv().await.unwrap(); - match command { - QueueCommand::Append(item, by_human) => { - match item.item { - QueueItemType::Single(song) => queue.add_item(song, by_human), - _ => unimplemented!(), - } - res_rx - .send(QueueResponse::Empty(Ok(()))) - .await - .unwrap(); - }, - QueueCommand::Next => { - 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())); - 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())); - 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(); - } - QueueCommand::Get => { - 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(); - } - } - } - } } #[derive(Debug, Default, Serialize, Clone)] diff --git a/dmp-core/src/music_controller/library_command.rs b/dmp-core/src/music_controller/library_command.rs new file mode 100644 index 0000000..8b30ea8 --- /dev/null +++ b/dmp-core/src/music_controller/library_command.rs @@ -0,0 +1,53 @@ +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 super::{controller::{Controller, LibraryCommand, LibraryResponse}, controller_handle::LibraryCommandInput}; + +impl Controller { + pub(super) async fn library_loop( + lib_mail: async_channel::Receiver, + library: &mut MusicLibrary, + config: Arc>, + ) -> Result<(), ()> { + while true { + let LibraryCommandInput { res_rx, command } = lib_mail.recv().await.unwrap(); + match command { + LibraryCommand::Song(uuid) => { + let (song, i) = library.query_uuid(&uuid).unwrap(); + res_rx.send(LibraryResponse::Song(song.clone(), i)).await.unwrap(); + } + LibraryCommand::AllSongs => { + 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(); + } + 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)); + + res_rx.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap(); + } + LibraryCommand::Save => { + 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); + + res_rx.send(LibraryResponse::Playlists(lists)).await.unwrap(); + } + _ => { todo!() } + } + } + Ok(()) + } +} \ No newline at end of file diff --git a/dmp-core/src/music_controller/player_command.rs b/dmp-core/src/music_controller/player_command.rs new file mode 100644 index 0000000..3ab465a --- /dev/null +++ b/dmp-core/src/music_controller/player_command.rs @@ -0,0 +1,273 @@ +use chrono::TimeDelta; +use crossbeam_channel::Sender; +use kushi::{QueueItem, QueueItemType}; +use prismriver::{Prismriver, Volume}; + +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}}; + +impl Controller { + pub(super) async fn player_command_loop( + mut player: Prismriver, + player_mail: async_channel::Receiver, + queue_mail: async_channel::Sender, + lib_mail: async_channel::Sender, + notify_connections_: Sender, + mut state: ControllerState, + ) -> Result<(), ()> { + player.set_volume(Volume::new(state.volume)); + 'outer: while true { + let _mail = player_mail.recv().await; + if let Ok(PlayerCommandInput {res_rx, command}) = _mail { + match command { + PlayerCommand::Play => { + player.play(); + res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); + } + + PlayerCommand::Pause => { + player.pause(); + res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); + } + + PlayerCommand::Stop => { + player.stop(); + res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); + } + + PlayerCommand::Seek(time) => { + let res = player.seek_to(TimeDelta::milliseconds(time)); + res_rx.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap(); + } + + PlayerCommand::SetVolume(volume) => { + player.set_volume(Volume::new(volume)); + res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); + + // make this async or something + state.volume = volume; + _ = state.write_file() + } + + PlayerCommand::NextSong => { + let (command, tx) = QueueCommandInput::command(QueueCommand::Next); + queue_mail.send(command).await.unwrap(); + + match tx.recv().await.unwrap() { + QueueResponse::Item(Ok(item)) => { + let uri = match &item.item { + QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, + _ => unimplemented!(), + }; + + 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 (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 { + continue; + }; + + 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( + QueueSong { + song: song.clone(), + 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(); + + 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(); + } + _ => continue + } + } + + PlayerCommand::PrevSong => { + let (command, tx) = QueueCommandInput::command(QueueCommand::Prev); + queue_mail.send(command).await.unwrap(); + match tx.recv().await.unwrap() { + QueueResponse::Item(Ok(item)) => { + let uri = match &item.item { + QueueItemType::Single(song) => song.song.primary_uri().unwrap().0, + _ => unimplemented!(), + }; + + 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(); + + 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(); + } + _ => continue + } + } + + PlayerCommand::Enqueue(index) => { + 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(); + 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(); + } + _ => unimplemented!(), + } + res_rx.send(PlayerResponse::Empty(Ok(()))).await.unwrap(); + } + QueueResponse::Item(Err(e)) => { + res_rx.send(PlayerResponse::Empty(Err(e.into()))).await.unwrap(); + } + _ => 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)); + lib_mail.send(command).await.unwrap(); + let LibraryResponse::Song(np_song, index) = tx.recv().await.unwrap() else { + unreachable!() + }; + + let (command, tx) = QueueCommandInput::command(QueueCommand::Clear); + 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(); + continue; + } + _ => unreachable!() + } + + 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(); + continue; + } + _ => 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(); + player.load_new(&prism_uri).unwrap(); + player.play(); + + // how grab all the songs in a certain subset of the library, I reckon? + // ... + // let's just pretend I figured that out already + + let (songs, index) = match location { + PlayerLocation::Library => { + let (command, tx) = LibraryCommandInput::command(LibraryCommand::AllSongs); + lib_mail.send(command).await.unwrap(); + let LibraryResponse::AllSongs(songs) = tx.recv().await.unwrap() else { + unreachable!() + }; + (songs, index) + } + PlayerLocation::Playlist(uuid) => { + let (command, tx) = LibraryCommandInput::command(LibraryCommand::ExternalPlaylist(uuid)); + lib_mail.send(command).await.unwrap(); + 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") + }; + + + 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) + ); + 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(); + continue 'outer; + } + _ => unreachable!() + } + } else { + println!("End of Library / Playlist"); + break; + } + } + // ^ This be my solution for now ^ + 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(); + } + } + } else { + return Err(()); + } + } + Ok(()) + } +} \ No newline at end of file diff --git a/dmp-core/src/music_controller/player_monitor.rs b/dmp-core/src/music_controller/player_monitor.rs new file mode 100644 index 0000000..5923f08 --- /dev/null +++ b/dmp-core/src/music_controller/player_monitor.rs @@ -0,0 +1,75 @@ +use std::{sync::Arc, time::Duration}; + +use chrono::TimeDelta; +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 super::{connections::ConnectionsNotification, controller::{Controller, PlaybackInfo}, controller_handle::PlayerCommandInput}; + +impl Controller { + pub(super) fn player_monitor_loop( + playback_state: Arc>, + playback_time_tx: Receiver<(Option, Option)>, + finished_recv: Receiver<()>, + player_mail: async_channel::Sender, + notify_next_song: Sender, + notify_connections_: Sender, + playback_info: Arc> + ) -> Result<(), ()> { + std::thread::scope(|s| { + // Thread for timing and metadata + let notify_connections = notify_connections_.clone(); + s.spawn({ + move || { + 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(); + playback_info.store(PlaybackInfo { position, duration }); + } + } + }); + + // Thread for End of Track + let notify_connections = notify_connections_.clone(); + s.spawn(move || { futures::executor::block_on(async { + println!("EOS monitor started"); + while true { + let _ = finished_recv.recv(); + println!("End of song"); + + let (command, tx) = PlayerCommandInput::command(PlayerCommand::NextSong); + player_mail.send(command).await.unwrap(); + let PlayerResponse::NowPlaying(res) = tx.recv().await.unwrap() else { + unreachable!() + }; + 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(); + } + } + std::thread::sleep(Duration::from_millis(100)); + });}); + + let notify_connections = notify_connections_.clone(); + s.spawn(move || { + let mut state = PrismState::Stopped; + while true { + let _state = playback_state.read().unwrap().to_owned(); + if _state != state { + state = _state; + println!("State Changed to {state:?}"); + notify_connections.send(ConnectionsNotification::StateChange(state.clone())).unwrap(); + } + std::thread::sleep(Duration::from_millis(100)); + } + }); + }); + println!("Monitor Loops Ended"); + Ok(()) + } +} \ No newline at end of file diff --git a/dmp-core/src/music_controller/queue_command.rs b/dmp-core/src/music_controller/queue_command.rs new file mode 100644 index 0000000..fcd6849 --- /dev/null +++ b/dmp-core/src/music_controller/queue_command.rs @@ -0,0 +1,61 @@ +use kushi::{Queue, QueueError, QueueItemType}; + +use super::{controller::{Controller, QueueCommand, QueueResponse}, controller_handle::QueueCommandInput, queue::{QueueAlbum, QueueSong}}; + +impl Controller { + pub(super) async fn queue_loop( + mut queue: Queue, + queue_mail: async_channel::Receiver, + ) { + while true { + let QueueCommandInput { res_rx, command } = queue_mail.recv().await.unwrap(); + match command { + QueueCommand::Append(item, by_human) => { + match item.item { + QueueItemType::Single(song) => queue.add_item(song, by_human), + _ => unimplemented!(), + } + res_rx + .send(QueueResponse::Empty(Ok(()))) + .await + .unwrap(); + }, + QueueCommand::Next => { + 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())); + 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())); + 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(); + } + QueueCommand::Get => { + 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(); + } + } + } + } +} \ No newline at end of file diff --git a/dmp-core/src/music_storage/playlist.rs b/dmp-core/src/music_storage/playlist.rs index 5131649..a33d08b 100644 --- a/dmp-core/src/music_storage/playlist.rs +++ b/dmp-core/src/music_storage/playlist.rs @@ -403,7 +403,7 @@ impl ExternalPlaylist { #[cfg(test)] mod test_super { use super::*; - use crate::config::tests::{new_config_lib, read_config_lib}; + use crate::config::tests::read_config_lib; #[test] fn list_to_m3u() { diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 9d679f8..e31df82 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,6 +1,6 @@ -use std::{fs::OpenOptions, io::{Read, Write}}; +use std::{fs::OpenOptions, io::Write}; -use dmp_core::music_controller::{controller::{ControllerHandle, LibraryResponse, PlayerCommand, PlayerLocation, PlayerResponse, QueueResponse}, queue::QueueSong}; +use dmp_core::music_controller::{controller::{ControllerHandle, PlayerLocation}, queue::QueueSong}; use kushi::QueueItem; use tauri::{AppHandle, Emitter, State, Wry}; use tempfile::TempDir; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index f9a4db0..f5bdd9d 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,13 +1,12 @@ #![allow(while_true)] -use std::{borrow::BorrowMut, fs, ops::Deref, path::PathBuf, sync::{atomic::Ordering, 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, LibraryResponse, PlaybackInfo}}, music_storage::library::{MusicLibrary, Song, Tag}}; +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 rfd::FileHandle; -use tauri::{http::Response, Emitter, Manager, State, WebviewWindowBuilder, Wry}; +use tauri::{http::Response, Emitter, Manager, State, Wry}; use uuid::Uuid; use wrappers::{_Song, stop}; @@ -192,7 +191,7 @@ pub fn run() { app .run(|_app_handle, event| match event { - tauri::RunEvent::ExitRequested { api, .. } => { + tauri::RunEvent::ExitRequested { .. } => { // api.prevent_exit(); //panic!("does this kill the player?") } diff --git a/src-tauri/src/wrappers.rs b/src-tauri/src/wrappers.rs index ad648d6..ffaa750 100644 --- a/src-tauri/src/wrappers.rs +++ b/src-tauri/src/wrappers.rs @@ -52,7 +52,7 @@ pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String } #[tauri::command] -pub async fn get_volume(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn get_volume(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { Ok(()) } @@ -81,7 +81,7 @@ pub async fn prev(app: AppHandle, ctrl_handle: State<'_, ControllerHandle>) } #[tauri::command] -pub async fn now_playing(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { +pub async fn now_playing(_ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { Ok(()) }