mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 01:52:53 -05:00
Added proper functionality to the Queue. still missing the remove from queue function.
This commit is contained in:
parent
04ecd0e9d5
commit
87965ef6da
7 changed files with 174 additions and 56 deletions
|
@ -32,7 +32,7 @@ pub enum ControllerError {
|
|||
}
|
||||
|
||||
// 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]
|
||||
pub enum PlayerLocation {
|
||||
Test,
|
||||
|
@ -73,6 +73,7 @@ pub enum PlayerCommand {
|
|||
Play,
|
||||
Enqueue(usize),
|
||||
SetVolume(f64),
|
||||
PlayNow(Uuid, PlayerLocation),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
|
@ -81,39 +82,45 @@ pub enum PlayerResponse {
|
|||
NowPlaying(Song)
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
pub enum LibraryCommand {
|
||||
Song(Uuid),
|
||||
AllSongs,
|
||||
GetLibrary,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum LibraryResponse {
|
||||
Song(Song),
|
||||
AllSongs(Vec<Song>),
|
||||
Library(MusicLibrary),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, PartialOrd, Clone)]
|
||||
enum InnerLibraryCommand {
|
||||
Song(Uuid),
|
||||
AllSongs,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
enum InnerLibraryResponse<'a> {
|
||||
Song(&'a Song),
|
||||
Song(&'a Song, usize),
|
||||
AllSongs(&'a Vec<Song>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum QueueCommand {
|
||||
Append(QueueItem<QueueSong, QueueAlbum>),
|
||||
Append(QueueItem<QueueSong, QueueAlbum>, bool),
|
||||
Next,
|
||||
Prev,
|
||||
GetIndex(usize),
|
||||
NowPlaying,
|
||||
Get
|
||||
Get,
|
||||
Clear
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum QueueResponse {
|
||||
Ok,
|
||||
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 = player.clone();
|
||||
let _inner_lib_mail = inner_lib_mail.0.clone();
|
||||
scope
|
||||
.spawn(async move {
|
||||
Controller::<P>::player_command_loop(
|
||||
_player,
|
||||
player_mail.1,
|
||||
queue_mail.0,
|
||||
_inner_lib_mail
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -256,6 +265,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
player: Arc<RwLock<P>>,
|
||||
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
||||
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||
inner_lib_mail: MailMan<InnerLibraryCommand, InnerLibraryResponse<'c>>
|
||||
) -> Result<(), ()> {
|
||||
let mut first = true;
|
||||
while true {
|
||||
|
@ -294,6 +304,39 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
};
|
||||
player.write().unwrap().enqueue_next(uri).unwrap();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
@ -306,7 +349,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
_ => unimplemented!(),
|
||||
};
|
||||
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) => {
|
||||
|
@ -328,6 +371,46 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
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 {
|
||||
return Err(());
|
||||
|
@ -347,7 +430,7 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
.send(InnerLibraryCommand::Song(uuid))
|
||||
.await
|
||||
.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!();
|
||||
};
|
||||
lib_mail.send(LibraryResponse::Song(song.clone())).await.unwrap();
|
||||
|
@ -377,9 +460,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
while true {
|
||||
match lib_mail.recv().await.unwrap() {
|
||||
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
|
||||
.send(InnerLibraryResponse::Song(song))
|
||||
.send(InnerLibraryResponse::Song(song, i))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -408,9 +491,9 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
) {
|
||||
while true {
|
||||
match queue_mail.recv().await.unwrap() {
|
||||
QueueCommand::Append(item) => {
|
||||
QueueCommand::Append(item, by_human) => {
|
||||
match item.item {
|
||||
QueueItemType::Single(song) => queue.add_item(song, true),
|
||||
QueueItemType::Single(song) => queue.add_item(song, by_human),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
queue_mail
|
||||
|
@ -446,6 +529,10 @@ impl<'c, P: Player + Send + Sync> Controller<'c, P> {
|
|||
QueueCommand::Get => {
|
||||
queue_mail.send(QueueResponse::GetAll(queue.items.clone())).await.unwrap();
|
||||
}
|
||||
QueueCommand::Clear => {
|
||||
queue.clear();
|
||||
queue_mail.send(QueueResponse::Ok).await.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -133,7 +133,6 @@ impl<
|
|||
self.items.get_mut(i).expect("There should be an item at index {i}").state = QueueState::NoState;
|
||||
}
|
||||
|
||||
|
||||
if by_human {
|
||||
self.items.insert(
|
||||
i + if empty { 0 } else { 1 },
|
||||
|
|
2
src-tauri/.gitignore
vendored
2
src-tauri/.gitignore
vendored
|
@ -4,4 +4,4 @@
|
|||
|
||||
# Generated by Tauri
|
||||
# will have schema files for capabilities auto-completion
|
||||
/gen/schemas
|
||||
/gen/schemas
|
|
@ -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 tauri::{AppHandle, Emitter, State, Wry};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::wrappers::_Song;
|
||||
|
||||
|
||||
|
||||
#[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 {
|
||||
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 {
|
||||
panic!()
|
||||
};
|
||||
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> {
|
||||
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(())
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
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 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 crate::wrappers::{get_library, play, pause, prev, set_volume, get_song, next, get_queue};
|
||||
|
@ -73,6 +73,7 @@ pub fn run() {
|
|||
lib_already_created,
|
||||
get_queue,
|
||||
add_song_to_queue,
|
||||
play_now,
|
||||
]).manage(ConfigRx(rx))
|
||||
.manage(LibRx(lib_rx))
|
||||
.manage(HandleTx(handle_tx))
|
||||
|
@ -96,8 +97,6 @@ pub fn run() {
|
|||
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())
|
||||
})};
|
||||
|
||||
|
||||
res.respond(
|
||||
Response::builder()
|
||||
.header("Origin", "*")
|
||||
|
@ -124,7 +123,6 @@ struct ConfigRx(Sender<Config>);
|
|||
|
||||
struct LibRx(Sender<Option<PathBuf>>);
|
||||
struct HandleTx(Receiver<ControllerHandle>);
|
||||
struct DefaultImage<'a>(&'a [u8]);
|
||||
|
||||
|
||||
#[tauri::command]
|
||||
|
@ -196,6 +194,7 @@ async fn create_library(
|
|||
|
||||
lib_rx.inner().0.send(Some(path)).unwrap();
|
||||
app.manage(handle_tx.inner().0.recv().unwrap());
|
||||
app.emit("library_loaded", ()).unwrap();
|
||||
|
||||
window.close().unwrap();
|
||||
Ok(())
|
||||
|
@ -206,5 +205,6 @@ async fn create_library(
|
|||
println!("lib already created");
|
||||
lib_rx.inner().0.send(None).unwrap();
|
||||
app.manage(handle_tx.inner().0.recv().unwrap());
|
||||
app.emit("library_loaded", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -21,15 +21,17 @@ pub async fn play(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
|
|||
} else {
|
||||
unreachable!()
|
||||
};
|
||||
app.emit("playing", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[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();
|
||||
let PlayerResponse::Empty = ctrl_handle.player_mail.recv().await.unwrap() else {
|
||||
unreachable!()
|
||||
};
|
||||
app.emit("paused", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -58,6 +60,7 @@ pub async fn next(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
|
|||
println!("next");
|
||||
app.emit("now_playing_change", _Song::from(&song)).unwrap();
|
||||
app.emit("queue_updated", ()).unwrap();
|
||||
app.emit("playing", ()).unwrap();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
83
src/App.tsx
83
src/App.tsx
|
@ -12,6 +12,7 @@ const appWindow = getCurrentWebviewWindow();
|
|||
function App() {
|
||||
const library = useState<JSX.Element[]>([]);
|
||||
const [queue, setQueue] = useState<JSX.Element[]>([]);
|
||||
const [playing, setPlaying] = useState(false);
|
||||
|
||||
const [nowPlaying, setNowPlaying] = useState<JSX.Element>(
|
||||
<NowPlaying
|
||||
|
@ -51,6 +52,19 @@ function App() {
|
|||
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(() => {
|
||||
getConfig();
|
||||
}, [])
|
||||
|
@ -60,7 +74,7 @@ function App() {
|
|||
<div className="leftSide">
|
||||
<PlaylistHead />
|
||||
<MainView lib_ref={ library } />
|
||||
<PlayBar />
|
||||
<PlayBar playing={ playing } setPlaying={ setPlaying } />
|
||||
</div>
|
||||
<div className="rightSide">
|
||||
{ nowPlaying }
|
||||
|
@ -110,28 +124,31 @@ interface MainViewProps {
|
|||
function MainView({ lib_ref }: MainViewProps) {
|
||||
const [library, setLibrary] = lib_ref;
|
||||
|
||||
useEffect(() => {
|
||||
const unlisten = appWindow.listen<any>("library_loaded", (_) => {
|
||||
invoke('get_library').then((lib) => {
|
||||
setLibrary([...(lib as any[]).map((song, i) => {
|
||||
console.log(song);
|
||||
|
||||
return (
|
||||
<Song
|
||||
key={ song.uuid }
|
||||
location={ song.location }
|
||||
uuid={ song.uuid }
|
||||
plays={ song.plays }
|
||||
duration={ song.duration }
|
||||
tags={ song.tags }
|
||||
/>
|
||||
)
|
||||
})])
|
||||
})
|
||||
})
|
||||
return () => { unlisten.then((f) => f()) }
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="mainView">
|
||||
main view
|
||||
<button onClick={ (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
invoke('get_library').then((lib) => {
|
||||
setLibrary([...(lib as any[]).map((song) => {
|
||||
console.log(song);
|
||||
|
||||
return (
|
||||
<Song
|
||||
key={ song.uuid }
|
||||
location={ song.location }
|
||||
uuid={ song.uuid }
|
||||
plays={ song.plays }
|
||||
duration={ song.duration }
|
||||
tags={ song.tags }
|
||||
/>
|
||||
)
|
||||
})])
|
||||
})} }>get library</button>
|
||||
<h1>Library</h1>
|
||||
<div>{ library }</div>
|
||||
</div>
|
||||
)
|
||||
|
@ -162,16 +179,19 @@ function Song(props: SongProps) {
|
|||
invoke('add_song_to_queue', { uuid: props.uuid, location: 'Library' }).then(() => {} )
|
||||
}}
|
||||
>Add to Queue</button>
|
||||
<button onClick={() => {
|
||||
invoke("play_now", { uuid: props.uuid, location: 'Library' }).then(() => {})
|
||||
}}>Play Now</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
function PlayBar() {
|
||||
let [playing, setPlaying] = useState('play');
|
||||
|
||||
|
||||
|
||||
interface PlayBarProps {
|
||||
playing: boolean,
|
||||
setPlaying: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
||||
return (
|
||||
<section id="playBar" className="playBar">
|
||||
<div className="topHalf">
|
||||
|
@ -181,14 +201,9 @@ function PlayBar() {
|
|||
</div>
|
||||
<button onClick={ () => invoke('prev').then(() => {}) }>prev</button>
|
||||
<button onClick={ (_) => {
|
||||
if (playing == 'play') {
|
||||
setPlaying('pause')
|
||||
invoke('play').then(() => {})
|
||||
} else {
|
||||
setPlaying('play')
|
||||
invoke('pause').then(() => {})
|
||||
}
|
||||
}}>{ playing }</button>
|
||||
setPlaying( playing ? false : true );
|
||||
invoke( playing ? 'pause' : 'play' ).then(() => {})
|
||||
}}>{ playing ? 'pause' : 'play' }</button>
|
||||
<button onClick={ () => invoke('next').then(() => {}) }>next</button>
|
||||
<input type="range" name="volume" id="volumeSlider" onChange={ (volume) => {
|
||||
invoke('set_volume', { volume: volume.target.value }).then(() => {})
|
||||
|
|
Loading…
Reference in a new issue