mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
Refactored controller command functions, removed unused imports
This commit is contained in:
parent
3a8826bb5d
commit
195eed367b
11 changed files with 487 additions and 456 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(while_true)]
|
||||||
pub mod music_storage {
|
pub mod music_storage {
|
||||||
pub mod library;
|
pub mod library;
|
||||||
pub mod music_collection;
|
pub mod music_collection;
|
||||||
|
@ -13,6 +14,10 @@ pub mod music_controller {
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
pub mod controller_handle;
|
pub mod controller_handle;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
|
pub mod player_command;
|
||||||
|
pub mod player_monitor;
|
||||||
|
pub mod queue_command;
|
||||||
|
pub mod library_command;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
#![allow(while_true)]
|
|
||||||
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 chrono::TimeDelta;
|
||||||
|
@ -11,7 +10,7 @@ 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, PlaybackInfo};
|
use super::controller::Controller;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) enum ConnectionsNotification {
|
pub(super) enum ConnectionsNotification {
|
||||||
|
@ -58,7 +57,7 @@ impl Controller {
|
||||||
use ConnectionsNotification::*;
|
use ConnectionsNotification::*;
|
||||||
while true {
|
while true {
|
||||||
match notifications_tx.recv().unwrap() {
|
match notifications_tx.recv().unwrap() {
|
||||||
Playback { position, duration } => {}
|
Playback { .. } => {}
|
||||||
StateChange(state) => {
|
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(); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
//! The [Controller] is the input and output for the entire
|
//! The [Controller] is the input and output for the entire
|
||||||
//! player. It manages queues, playback, library access, and
|
//! player. It manages queues, playback, library access, and
|
||||||
//! other functions
|
//! other functions
|
||||||
#![allow(while_true)]
|
|
||||||
|
|
||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use kushi::{Queue, QueueItemType};
|
use kushi::Queue;
|
||||||
use kushi::{QueueError, QueueItem};
|
use kushi::{QueueError, QueueItem};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use prismriver::{Error as PrismError, Prismriver, State as PrismState, Volume};
|
use prismriver::{Error as PrismError, Prismriver};
|
||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::to_string_pretty;
|
use serde_json::to_string_pretty;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
@ -18,13 +16,12 @@ use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::config::ConfigError;
|
use crate::config::ConfigError;
|
||||||
use crate::music_storage::library::Song;
|
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 crate::{config::Config, music_storage::library::MusicLibrary};
|
||||||
|
|
||||||
use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections};
|
use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections};
|
||||||
|
@ -207,13 +204,13 @@ impl ControllerHandle {
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct ControllerState {
|
pub struct ControllerState {
|
||||||
path: PathBuf,
|
pub(super) path: PathBuf,
|
||||||
volume: f32,
|
pub(super) volume: f32,
|
||||||
now_playing: Uuid,
|
pub(super) now_playing: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControllerState {
|
impl ControllerState {
|
||||||
fn new(path: PathBuf) -> Self {
|
pub(super) fn new(path: PathBuf) -> Self {
|
||||||
ControllerState {
|
ControllerState {
|
||||||
path,
|
path,
|
||||||
volume: 0.35,
|
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()
|
OpenOptions::new()
|
||||||
.truncate(true)
|
.truncate(true)
|
||||||
.create(true)
|
.create(true)
|
||||||
|
@ -232,7 +229,7 @@ impl ControllerState {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_file(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
|
pub(super) fn read_file(path: impl AsRef<Path>) -> Result<Self, std::io::Error> {
|
||||||
let state = serde_json::from_str(&std::fs::read_to_string(path)?)?;
|
let state = serde_json::from_str(&std::fs::read_to_string(path)?)?;
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
@ -327,7 +324,6 @@ impl Controller {
|
||||||
player_timing,
|
player_timing,
|
||||||
finished_tx,
|
finished_tx,
|
||||||
player_mail.0,
|
player_mail.0,
|
||||||
queue_mail.0,
|
|
||||||
notify_next_song,
|
notify_next_song,
|
||||||
notifications_rx,
|
notifications_rx,
|
||||||
playback_info,
|
playback_info,
|
||||||
|
@ -352,436 +348,6 @@ impl Controller {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn player_command_loop(
|
|
||||||
mut player: Prismriver,
|
|
||||||
player_mail: async_channel::Receiver<PlayerCommandInput>,
|
|
||||||
queue_mail: async_channel::Sender<QueueCommandInput>,
|
|
||||||
lib_mail: async_channel::Sender<LibraryCommandInput>,
|
|
||||||
notify_connections_: Sender<ConnectionsNotification>,
|
|
||||||
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<std::sync::RwLock<PrismState>>,
|
|
||||||
playback_time_tx: Receiver<(Option<TimeDelta>, Option<TimeDelta>)>,
|
|
||||||
finished_recv: Receiver<()>,
|
|
||||||
player_mail: async_channel::Sender<PlayerCommandInput>,
|
|
||||||
queue_mail: async_channel::Sender<QueueCommandInput>,
|
|
||||||
notify_next_song: Sender<Song>,
|
|
||||||
notify_connections_: Sender<ConnectionsNotification>,
|
|
||||||
playback_info: Arc<AtomicCell<PlaybackInfo>>
|
|
||||||
) -> 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<LibraryCommandInput>,
|
|
||||||
library: &mut MusicLibrary,
|
|
||||||
config: Arc<RwLock<Config>>,
|
|
||||||
) -> 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<QueueSong, QueueAlbum>,
|
|
||||||
queue_mail: async_channel::Receiver<QueueCommandInput>,
|
|
||||||
) {
|
|
||||||
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)]
|
#[derive(Debug, Default, Serialize, Clone)]
|
||||||
|
|
53
dmp-core/src/music_controller/library_command.rs
Normal file
53
dmp-core/src/music_controller/library_command.rs
Normal file
|
@ -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<LibraryCommandInput>,
|
||||||
|
library: &mut MusicLibrary,
|
||||||
|
config: Arc<RwLock<Config>>,
|
||||||
|
) -> 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(())
|
||||||
|
}
|
||||||
|
}
|
273
dmp-core/src/music_controller/player_command.rs
Normal file
273
dmp-core/src/music_controller/player_command.rs
Normal file
|
@ -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<PlayerCommandInput>,
|
||||||
|
queue_mail: async_channel::Sender<QueueCommandInput>,
|
||||||
|
lib_mail: async_channel::Sender<LibraryCommandInput>,
|
||||||
|
notify_connections_: Sender<ConnectionsNotification>,
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
75
dmp-core/src/music_controller/player_monitor.rs
Normal file
75
dmp-core/src/music_controller/player_monitor.rs
Normal file
|
@ -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<std::sync::RwLock<PrismState>>,
|
||||||
|
playback_time_tx: Receiver<(Option<TimeDelta>, Option<TimeDelta>)>,
|
||||||
|
finished_recv: Receiver<()>,
|
||||||
|
player_mail: async_channel::Sender<PlayerCommandInput>,
|
||||||
|
notify_next_song: Sender<Song>,
|
||||||
|
notify_connections_: Sender<ConnectionsNotification>,
|
||||||
|
playback_info: Arc<AtomicCell<PlaybackInfo>>
|
||||||
|
) -> 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(())
|
||||||
|
}
|
||||||
|
}
|
61
dmp-core/src/music_controller/queue_command.rs
Normal file
61
dmp-core/src/music_controller/queue_command.rs
Normal file
|
@ -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<QueueSong, QueueAlbum>,
|
||||||
|
queue_mail: async_channel::Receiver<QueueCommandInput>,
|
||||||
|
) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -403,7 +403,7 @@ impl ExternalPlaylist {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_super {
|
mod test_super {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::config::tests::{new_config_lib, read_config_lib};
|
use crate::config::tests::read_config_lib;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn list_to_m3u() {
|
fn list_to_m3u() {
|
||||||
|
|
|
@ -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 kushi::QueueItem;
|
||||||
use tauri::{AppHandle, Emitter, State, Wry};
|
use tauri::{AppHandle, Emitter, State, Wry};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
#![allow(while_true)]
|
#![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 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 futures::channel::oneshot;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use rfd::FileHandle;
|
use tauri::{http::Response, Emitter, Manager, State, Wry};
|
||||||
use tauri::{http::Response, Emitter, Manager, State, WebviewWindowBuilder, Wry};
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wrappers::{_Song, stop};
|
use wrappers::{_Song, stop};
|
||||||
|
|
||||||
|
@ -192,7 +191,7 @@ pub fn run() {
|
||||||
|
|
||||||
app
|
app
|
||||||
.run(|_app_handle, event| match event {
|
.run(|_app_handle, event| match event {
|
||||||
tauri::RunEvent::ExitRequested { api, .. } => {
|
tauri::RunEvent::ExitRequested { .. } => {
|
||||||
// api.prevent_exit();
|
// api.prevent_exit();
|
||||||
//panic!("does this kill the player?")
|
//panic!("does this kill the player?")
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub async fn set_volume(ctrl_handle: State<'_, ControllerHandle>, volume: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ pub async fn prev(app: AppHandle<Wry>, ctrl_handle: State<'_, ControllerHandle>)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue