Added proper functionality to the Queue. still missing the remove from queue function.

This commit is contained in:
MrDulfin 2024-12-16 00:04:48 -05:00
parent 04ecd0e9d5
commit 87965ef6da
7 changed files with 174 additions and 56 deletions

View file

@ -32,7 +32,7 @@ pub enum ControllerError {
} }
// TODO: move this to a different location to be used elsewhere // TODO: move this to a different location to be used elsewhere
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
#[non_exhaustive] #[non_exhaustive]
pub enum PlayerLocation { pub enum PlayerLocation {
Test, Test,
@ -73,6 +73,7 @@ pub enum PlayerCommand {
Play, Play,
Enqueue(usize), Enqueue(usize),
SetVolume(f64), SetVolume(f64),
PlayNow(Uuid, PlayerLocation),
} }
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Clone)]
@ -81,39 +82,45 @@ pub enum PlayerResponse {
NowPlaying(Song) NowPlaying(Song)
} }
#[derive(Debug, PartialEq, PartialOrd, Clone)]
pub enum LibraryCommand { pub enum LibraryCommand {
Song(Uuid), Song(Uuid),
AllSongs, AllSongs,
GetLibrary, GetLibrary,
} }
#[derive(Debug)]
pub enum LibraryResponse { pub enum LibraryResponse {
Song(Song), Song(Song),
AllSongs(Vec<Song>), AllSongs(Vec<Song>),
Library(MusicLibrary), Library(MusicLibrary),
} }
#[derive(Debug, PartialEq, PartialOrd, Clone)]
enum InnerLibraryCommand { enum InnerLibraryCommand {
Song(Uuid), Song(Uuid),
AllSongs, AllSongs,
} }
#[derive(Debug, PartialEq, Clone)]
enum InnerLibraryResponse<'a> { enum InnerLibraryResponse<'a> {
Song(&'a Song), Song(&'a Song, usize),
AllSongs(&'a Vec<Song>), AllSongs(&'a Vec<Song>),
} }
#[derive(Debug, Clone)]
#[derive(Debug, PartialEq, Clone)]
pub enum QueueCommand { pub enum QueueCommand {
Append(QueueItem<QueueSong, QueueAlbum>), Append(QueueItem<QueueSong, QueueAlbum>, bool),
Next, Next,
Prev, Prev,
GetIndex(usize), GetIndex(usize),
NowPlaying, NowPlaying,
Get Get,
Clear
} }
#[derive(Debug, Clone)] #[derive(Debug, PartialEq, Clone)]
pub enum QueueResponse { pub enum QueueResponse {
Ok, Ok,
Item(QueueItem<QueueSong, QueueAlbum>), Item(QueueItem<QueueSong, QueueAlbum>),
@ -208,12 +215,14 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
let player = Arc::new(RwLock::new(P::new().unwrap())); let player = Arc::new(RwLock::new(P::new().unwrap()));
let _player = player.clone(); let _player = player.clone();
let _inner_lib_mail = inner_lib_mail.0.clone();
scope scope
.spawn(async move { .spawn(async move {
Controller::<P>::player_command_loop( Controller::<P>::player_command_loop(
_player, _player,
player_mail.1, player_mail.1,
queue_mail.0, queue_mail.0,
_inner_lib_mail
) )
.await .await
.unwrap(); .unwrap();
@ -256,6 +265,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
player: Arc<RwLock<P>>, player: Arc<RwLock<P>>,
player_mail: MailMan<PlayerResponse, PlayerCommand>, player_mail: MailMan<PlayerResponse, PlayerCommand>,
queue_mail: MailMan<QueueCommand, QueueResponse>, queue_mail: MailMan<QueueCommand, QueueResponse>,
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>
) -> Result<(), ()> { ) -> Result<(), ()> {
let mut first = true; let mut first = true;
while true { while true {
@ -294,6 +304,39 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
}; };
player.write().unwrap().enqueue_next(uri).unwrap(); player.write().unwrap().enqueue_next(uri).unwrap();
let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")}; let QueueItemType::Single(np_song) = item.item else { panic!("This is temporary, handle queueItemTypes at some point")};
// Append next song in library
inner_lib_mail.send(InnerLibraryCommand::AllSongs).await.unwrap();
let InnerLibraryResponse::AllSongs(songs) = inner_lib_mail.recv().await.unwrap() else {
unreachable!()
};
inner_lib_mail.send(InnerLibraryCommand::Song(np_song.song.uuid.clone())).await.unwrap();
let InnerLibraryResponse::Song(_, i) = inner_lib_mail.recv().await.unwrap() else {
unreachable!()
};
if let Some(song) = songs.get(i + 49) {
queue_mail.send(
QueueCommand::Append(
QueueItem::from_item_type(
QueueItemType::Single(
QueueSong {
song: song.clone(),
location: np_song.location
}
)
),
false
)
).await
.unwrap();
let QueueResponse::Ok = queue_mail.recv().await.unwrap() else {
unreachable!()
};
} else {
println!("Library Empty");
}
player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap(); player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();
} }
} }
@ -306,7 +349,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
_ => unimplemented!(), _ => unimplemented!(),
}; };
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")};
player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();; player_mail.send(PlayerResponse::NowPlaying(np_song.song.clone())).await.unwrap();
} }
} }
PlayerCommand::Enqueue(index) => { PlayerCommand::Enqueue(index) => {
@ -328,6 +371,46 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
player_mail.send(PlayerResponse::Empty).await.unwrap(); player_mail.send(PlayerResponse::Empty).await.unwrap();
} }
} }
PlayerCommand::PlayNow(uuid, location) => {
// TODO: This assumes the uuid doesn't point to an album. we've been over this.
inner_lib_mail.send(InnerLibraryCommand::Song(uuid)).await.unwrap();
let InnerLibraryResponse::Song(song, index) = inner_lib_mail.recv().await.unwrap() else {
unreachable!()
};
queue_mail.send(QueueCommand::Clear).await.unwrap();
let QueueResponse::Ok = queue_mail.recv().await.unwrap() else {
unreachable!()
};
queue_mail.send(QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location: location })), true)).await.unwrap();
let QueueResponse::Ok = queue_mail.recv().await.unwrap() else {
unreachable!()
};
player.write().unwrap().enqueue_next(song.primary_uri().unwrap().0).unwrap();
// how grab all the songs in a certain subset of the library, I reckon?
// ...
// let's just pretend I figured that out already
inner_lib_mail.send(InnerLibraryCommand::AllSongs).await.unwrap();
let InnerLibraryResponse::AllSongs(songs) = inner_lib_mail.recv().await.unwrap() else {
unreachable!()
};
for i in index+1..(index+50) {
if let Some(song) = songs.get(i) {
queue_mail.send(QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), false)).await.unwrap();
let QueueResponse::Ok = queue_mail.recv().await.unwrap() else {
unreachable!()
};
} else {
println!("End of Library");
break;
}
}
// ^ This be my solution for now ^
player_mail.send(PlayerResponse::NowPlaying(song.clone())).await.unwrap();
}
} }
} else { } else {
return Err(()); return Err(());
@ -347,7 +430,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
.send(InnerLibraryCommand::Song(uuid)) .send(InnerLibraryCommand::Song(uuid))
.await .await
.unwrap(); .unwrap();
let InnerLibraryResponse::Song(song) = inner_lib_mail.recv().await.unwrap() else { let InnerLibraryResponse::Song(song, i) = inner_lib_mail.recv().await.unwrap() else {
unimplemented!(); unimplemented!();
}; };
lib_mail.send(LibraryResponse::Song(song.clone())).await.unwrap(); lib_mail.send(LibraryResponse::Song(song.clone())).await.unwrap();
@ -377,9 +460,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
while true { while true {
match lib_mail.recv().await.unwrap() { match lib_mail.recv().await.unwrap() {
InnerLibraryCommand::Song(uuid) => { InnerLibraryCommand::Song(uuid) => {
let song: &'c Song = library.query_uuid(&uuid).unwrap().0; let (song, i): (&'c Song, usize) = library.query_uuid(&uuid).unwrap();
lib_mail lib_mail
.send(InnerLibraryResponse::Song(song)) .send(InnerLibraryResponse::Song(song, i))
.await .await
.unwrap(); .unwrap();
} }
@ -408,9 +491,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
) { ) {
while true { while true {
match queue_mail.recv().await.unwrap() { match queue_mail.recv().await.unwrap() {
QueueCommand::Append(item) => { QueueCommand::Append(item, by_human) => {
match item.item { match item.item {
QueueItemType::Single(song) => queue.add_item(song, true), QueueItemType::Single(song) => queue.add_item(song, by_human),
_ => unimplemented!(), _ => unimplemented!(),
} }
queue_mail queue_mail
@ -446,6 +529,10 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
QueueCommand::Get => { QueueCommand::Get => {
queue_mail.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap(); queue_mail.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap();
} }
QueueCommand::Clear => {
queue.clear();
queue_mail.send(QueueResponse::Ok).await.unwrap();
}
} }
} }
} }

View file

@ -133,7 +133,6 @@ impl<
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 { if by_human {
self.items.insert( self.items.insert(
i + if empty { 0 } else { 1 }, i + if empty { 0 } else { 1 },

View file

@ -1,8 +1,10 @@
use dmp_core::music_controller::{controller::{ControllerHandle, LibraryResponse, PlayerLocation, QueueResponse}, queue::QueueSong}; use dmp_core::music_controller::{controller::{ControllerHandle, LibraryResponse, PlayerCommand, PlayerLocation, PlayerResponse, QueueResponse}, queue::QueueSong};
use kushi::QueueItem; use kushi::QueueItem;
use tauri::{AppHandle, Emitter, State, Wry}; use tauri::{AppHandle, Emitter, State, Wry};
use uuid::Uuid; use uuid::Uuid;
use crate::wrappers::_Song;
#[tauri::command] #[tauri::command]
@ -11,10 +13,22 @@ pub async fn add_song_to_queue(app: AppHandle<Wry>, ctrl_handle: State<'_, Contr
let LibraryResponse::Song(song) = ctrl_handle.lib_mail.recv().await.unwrap() else { let LibraryResponse::Song(song) = ctrl_handle.lib_mail.recv().await.unwrap() else {
unreachable!() unreachable!()
}; };
ctrl_handle.queue_mail.send(dmp_core::music_controller::controller::QueueCommand::Append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location })))).await.unwrap(); ctrl_handle.queue_mail.send(dmp_core::music_controller::controller::QueueCommand::Append(QueueItem::from_item_type(kushi::QueueItemType::Single(QueueSong { song, location })), true)).await.unwrap();
let QueueResponse::Ok = ctrl_handle.queue_mail.recv().await.unwrap() else { let QueueResponse::Ok = ctrl_handle.queue_mail.recv().await.unwrap() else {
panic!() panic!()
}; };
app.emit("queue_updated", ()).unwrap(); app.emit("queue_updated", ()).unwrap();
Ok(()) Ok(())
} }
#[tauri::command]
pub async fn play_now(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>, uuid: Uuid, location: PlayerLocation) -> Result<(), String> {
ctrl_handle.player_mail.send(PlayerCommand::PlayNow(uuid, location)).await.unwrap();
let PlayerResponse::NowPlaying(song) = ctrl_handle.player_mail.recv().await.unwrap() else {
unreachable!()
};
app.emit("queue_updated", ()).unwrap();
app.emit("now_playing_change", _Song::from(&song)).unwrap();
app.emit("playing", ()).unwrap();
Ok(())
}

View file

@ -1,9 +1,9 @@
use std::{fs, path::PathBuf, str::FromStr, thread::spawn}; use std::{fs, path::PathBuf, str::FromStr, thread::spawn};
use commands::add_song_to_queue; use commands::{add_song_to_queue, play_now};
use crossbeam::channel::{unbounded, Receiver, Sender}; use crossbeam::channel::{unbounded, Receiver, Sender};
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse}, music_player::gstreamer::GStreamer, music_storage::library::{AlbumArt, MusicLibrary}}; use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse}, music_player::gstreamer::GStreamer, music_storage::library::{AlbumArt, MusicLibrary}};
use tauri::{http::Response, Manager, State, Url, WebviewWindowBuilder, Wry}; use tauri::{http::Response, Emitter, Manager, State, Url, WebviewWindowBuilder, Wry};
use uuid::Uuid; use uuid::Uuid;
use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue}; use crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue};
@ -73,6 +73,7 @@ pub fn run() {
lib_already_created, lib_already_created,
get_queue, get_queue,
add_song_to_queue, add_song_to_queue,
play_now,
]).manage(ConfigRx(rx)) ]).manage(ConfigRx(rx))
.manage(LibRx(lib_rx)) .manage(LibRx(lib_rx))
.manage(HandleTx(handle_tx)) .manage(HandleTx(handle_tx))
@ -96,8 +97,6 @@ pub fn run() {
let LibraryResponse::Song(song) = controller.lib_mail.recv().await.unwrap() else { unreachable!() }; let LibraryResponse::Song(song) = controller.lib_mail.recv().await.unwrap() else { unreachable!() };
song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec()) song.album_art(0).unwrap_or_else(|_| None).unwrap_or(DEFAULT_IMAGE.to_vec())
})}; })};
res.respond( res.respond(
Response::builder() Response::builder()
.header("Origin", "*") .header("Origin", "*")
@ -124,7 +123,6 @@ struct ConfigRx(Sender<Config>);
struct LibRx(Sender<Option<PathBuf>>); struct LibRx(Sender<Option<PathBuf>>);
struct HandleTx(Receiver<ControllerHandle>); struct HandleTx(Receiver<ControllerHandle>);
struct DefaultImage<'a>(&'a [u8]);
#[tauri::command] #[tauri::command]
@ -196,6 +194,7 @@ async fn create_library(
lib_rx.inner().0.send(Some(path)).unwrap(); lib_rx.inner().0.send(Some(path)).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap()); app.manage(handle_tx.inner().0.recv().unwrap());
app.emit("library_loaded", ()).unwrap();
window.close().unwrap(); window.close().unwrap();
Ok(()) Ok(())
@ -206,5 +205,6 @@ async fn create_library(
println!("lib already created"); println!("lib already created");
lib_rx.inner().0.send(None).unwrap(); lib_rx.inner().0.send(None).unwrap();
app.manage(handle_tx.inner().0.recv().unwrap()); app.manage(handle_tx.inner().0.recv().unwrap());
app.emit("library_loaded", ()).unwrap();
Ok(()) Ok(())
} }

View file

@ -21,15 +21,17 @@ pub async fn play(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
} else { } else {
unreachable!() unreachable!()
}; };
app.emit("playing", ()).unwrap();
Ok(()) Ok(())
} }
#[tauri::command] #[tauri::command]
pub async fn pause(ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> { pub async fn pause(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>) -> Result<(), String> {
ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::Pause).await.unwrap(); ctrl_handle.player_mail.send(dmp_core::music_controller::controller::PlayerCommand::Pause).await.unwrap();
let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else { let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else {
unreachable!() unreachable!()
}; };
app.emit("paused", ()).unwrap();
Ok(()) Ok(())
} }
@ -58,6 +60,7 @@ pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
println!("next"); println!("next");
app.emit("now_playing_change", _Song::from(&song)).unwrap(); app.emit("now_playing_change", _Song::from(&song)).unwrap();
app.emit("queue_updated", ()).unwrap(); app.emit("queue_updated", ()).unwrap();
app.emit("playing", ()).unwrap();
Ok(()) Ok(())
} }

View file

@ -12,6 +12,7 @@ const appWindow = getCurrentWebviewWindow();
function App() { function App() {
const library = useState<JSX.Element[]>([]); const library = useState<JSX.Element[]>([]);
const [queue, setQueue] = useState<JSX.Element[]>([]); const [queue, setQueue] = useState<JSX.Element[]>([]);
const [playing, setPlaying] = useState(false);
const [nowPlaying, setNowPlaying] = useState<JSX.Element>( const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
<NowPlaying <NowPlaying
@ -51,6 +52,19 @@ function App() {
return () => { unlisten.then((f) => f()) } return () => { unlisten.then((f) => f()) }
}, []); }, []);
useEffect(() => {
const unlisten = appWindow.listen<any>("playing", (_) => {
setPlaying(true)
})
return () => { unlisten.then((f) => f()) }
}, []);
useEffect(() => {
const unlisten = appWindow.listen<any>("paused", (_) => {
setPlaying(false)
})
return () => { unlisten.then((f) => f()) }
}, []);
useEffect(() => { useEffect(() => {
getConfig(); getConfig();
}, []) }, [])
@ -60,7 +74,7 @@ function App() {
<div className="leftSide"> <div className="leftSide">
<PlaylistHead /> <PlaylistHead />
<MainView lib_ref={ library } /> <MainView lib_ref={ library } />
<PlayBar /> <PlayBar playing={ playing } setPlaying={ setPlaying } />
</div> </div>
<div className="rightSide"> <div className="rightSide">
{ nowPlaying } { nowPlaying }
@ -110,14 +124,10 @@ interface MainViewProps {
function MainView({ lib_ref }: MainViewProps) { function MainView({ lib_ref }: MainViewProps) {
const [library, setLibrary] = lib_ref; const [library, setLibrary] = lib_ref;
return ( useEffect(() => {
<div className="mainView"> const unlisten = appWindow.listen<any>("library_loaded", (_) => {
main view
<button onClick={ (e) => {
e.preventDefault();
invoke('get_library').then((lib) => { invoke('get_library').then((lib) => {
setLibrary([...(lib as any[]).map((song) => { setLibrary([...(lib as any[]).map((song, i) => {
console.log(song); console.log(song);
return ( return (
@ -131,7 +141,14 @@ function MainView({ lib_ref }: MainViewProps) {
/> />
) )
})]) })])
})} }>get library</button> })
})
return () => { unlisten.then((f) => f()) }
}, []);
return (
<div className="mainView">
<h1>Library</h1>
<div>{ library }</div> <div>{ library }</div>
</div> </div>
) )
@ -162,16 +179,19 @@ function Song(props: SongProps) {
invoke('add_song_to_queue', { uuid: props.uuid, location: 'Library' }).then(() => {} ) invoke('add_song_to_queue', { uuid: props.uuid, location: 'Library' }).then(() => {} )
}} }}
>Add to Queue</button> >Add to Queue</button>
<button onClick={() => {
invoke("play_now", { uuid: props.uuid, location: 'Library' }).then(() => {})
}}>Play Now</button>
</div> </div>
) )
} }
function PlayBar() { interface PlayBarProps {
let [playing, setPlaying] = useState('play'); playing: boolean,
setPlaying: React.Dispatch<React.SetStateAction<boolean>>
}
function PlayBar({ playing, setPlaying }: PlayBarProps) {
return ( return (
<section id="playBar" className="playBar"> <section id="playBar" className="playBar">
<div className="topHalf"> <div className="topHalf">
@ -181,14 +201,9 @@ function PlayBar() {
</div> </div>
<button onClick={ () => invoke('prev').then(() => {}) }>prev</button> <button onClick={ () => invoke('prev').then(() => {}) }>prev</button>
<button onClick={ (_) => { <button onClick={ (_) => {
if (playing == 'play') { setPlaying( playing ? false : true );
setPlaying('pause') invoke( playing ? 'pause' : 'play' ).then(() => {})
invoke('play').then(() => {}) }}>{ playing ? 'pause' : 'play' }</button>
} else {
setPlaying('play')
invoke('pause').then(() => {})
}
}}>{ playing }</button>
<button onClick={ () => invoke('next').then(() => {}) }>next</button> <button onClick={ () => invoke('next').then(() => {}) }>next</button>
<input type="range" name="volume" id="volumeSlider" onChange={ (volume) => { <input type="range" name="volume" id="volumeSlider" onChange={ (volume) => {
invoke('set_volume', { volume: volume.target.value }).then(() => {}) invoke('set_volume', { volume: volume.target.value }).then(() => {})