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