mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
more progress on RPC Integration
This commit is contained in:
parent
2130af1e4a
commit
31829ad4f5
9 changed files with 234 additions and 70 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -31,3 +31,4 @@ Cargo.lock
|
||||||
.cargo
|
.cargo
|
||||||
|
|
||||||
test-config
|
test-config
|
||||||
|
.txt
|
|
@ -39,3 +39,4 @@ ciborium = "0.2.2"
|
||||||
itertools = "0.13.0"
|
itertools = "0.13.0"
|
||||||
prismriver = { git = "https://github.com/Dangoware/prismriver.git"}
|
prismriver = { git = "https://github.com/Dangoware/prismriver.git"}
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
discord-presence = { version = "1.4.1", features = ["activity_type"] }
|
||||||
|
|
|
@ -9,6 +9,7 @@ pub mod music_storage {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod music_controller {
|
pub mod music_controller {
|
||||||
|
pub mod connections;
|
||||||
pub mod controller;
|
pub mod controller;
|
||||||
pub mod controller_handle;
|
pub mod controller_handle;
|
||||||
pub mod queue;
|
pub mod queue;
|
||||||
|
|
134
dmp-core/src/music_controller/connections.rs
Normal file
134
dmp-core/src/music_controller/connections.rs
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
#![allow(while_true)]
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use chrono::TimeDelta;
|
||||||
|
use crossbeam::scope;
|
||||||
|
use crossbeam_channel::{bounded, Receiver};
|
||||||
|
use discord_presence::models::{Activity, ActivityTimestamps, ActivityType};
|
||||||
|
use prismriver::State as PrismState;
|
||||||
|
use rayon::spawn;
|
||||||
|
|
||||||
|
use crate::music_storage::library::{Song, Tag};
|
||||||
|
|
||||||
|
use super::controller::Controller;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(super) enum ConnectionsNotification {
|
||||||
|
Playback {
|
||||||
|
position: Option<TimeDelta>,
|
||||||
|
duration: Option<TimeDelta>
|
||||||
|
},
|
||||||
|
StateChange(PrismState),
|
||||||
|
SongChange(Song),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConnectionsInput {
|
||||||
|
pub discord_rpc_client_id: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct ControllerConnections {
|
||||||
|
pub notifications_tx: Receiver<ConnectionsNotification>,
|
||||||
|
pub inner: ConnectionsInput
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Controller {
|
||||||
|
pub(super) fn handle_connections(ControllerConnections {
|
||||||
|
notifications_tx,
|
||||||
|
inner: ConnectionsInput {
|
||||||
|
discord_rpc_client_id
|
||||||
|
},
|
||||||
|
}: ControllerConnections
|
||||||
|
) {
|
||||||
|
let (dc_state_rx, dc_state_tx) = bounded::<PrismState>(1);
|
||||||
|
let (dc_song_rx, dc_song_tx) = bounded::<Song>(1);
|
||||||
|
scope(|s| {
|
||||||
|
s.builder().name("Notifications Sorter".to_string()).spawn(|_| {
|
||||||
|
use ConnectionsNotification::*;
|
||||||
|
while true {
|
||||||
|
match notifications_tx.recv().unwrap() {
|
||||||
|
Playback { position, duration } => { continue; }
|
||||||
|
StateChange(state) => {
|
||||||
|
dc_state_rx.send(state.clone()).unwrap();
|
||||||
|
}
|
||||||
|
SongChange(song) => {
|
||||||
|
dc_song_rx.send(song).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).unwrap();
|
||||||
|
|
||||||
|
if let Some(client_id) = discord_rpc_client_id {
|
||||||
|
println!("Discord thingy detected");
|
||||||
|
s.builder().name("Discord RPC Handler".to_string()).spawn(move |_| {
|
||||||
|
Controller::discord_rpc(client_id, dc_song_tx, dc_state_tx);
|
||||||
|
}).unwrap();
|
||||||
|
};
|
||||||
|
}).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discord_rpc(client_id: u64, song_tx: Receiver<Song>, state_tx: Receiver<PrismState>) {
|
||||||
|
spawn(move || {
|
||||||
|
let mut client = discord_presence::Client::new(client_id);
|
||||||
|
client.start();
|
||||||
|
client.block_until_event(discord_presence::Event::Connected).unwrap();
|
||||||
|
client.set_activity(|_|
|
||||||
|
Activity::new()
|
||||||
|
).unwrap();
|
||||||
|
println!("discord connected");
|
||||||
|
|
||||||
|
let mut state = "Started".to_string();
|
||||||
|
let mut song: Option<Song> = None;
|
||||||
|
|
||||||
|
while true {
|
||||||
|
let state_res = state_tx.recv_timeout(Duration::from_secs(5));
|
||||||
|
let song_res = song_tx.recv_timeout(Duration::from_millis(100));
|
||||||
|
|
||||||
|
let state = &mut state;
|
||||||
|
let song = &mut song;
|
||||||
|
|
||||||
|
if let Ok(state_) = state_res {
|
||||||
|
*state = match state_ {
|
||||||
|
PrismState::Playing => "Playing",
|
||||||
|
PrismState::Paused => "Paused",
|
||||||
|
PrismState::Stopped => "Stopped",
|
||||||
|
_ => "I'm Scared, Boss"
|
||||||
|
}.to_string()
|
||||||
|
}
|
||||||
|
if let Ok(song_) = song_res {
|
||||||
|
*song = Some(song_);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.set_activity(|activity| {
|
||||||
|
activity.state(
|
||||||
|
state.clone()
|
||||||
|
)._type(discord_presence::models::ActivityType::Listening)
|
||||||
|
.details(
|
||||||
|
if let Some(song) = song {
|
||||||
|
format!(
|
||||||
|
"{} - {}\n{}",
|
||||||
|
song.get_tag(&Tag::Title).map_or(String::from("No Title"), |title| title.clone()),
|
||||||
|
song.get_tag(&Tag::Artist).map_or(String::from("No Artist"), |artist| artist.clone()),
|
||||||
|
song.get_tag(&Tag::Album).map_or(String::from("No Album"), |album| album.clone())
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// if let Some(song) = song {
|
||||||
|
// a.timestamps(|timestamp| {
|
||||||
|
// ActivityTimestamps::new()
|
||||||
|
// .start(timestamp.start.unwrap_or_default())
|
||||||
|
// .end(
|
||||||
|
// song.duration.as_millis().clamp(u64::MIN as u128, u64::MAX as u128) as u64
|
||||||
|
// )
|
||||||
|
// })
|
||||||
|
// } else {
|
||||||
|
// a
|
||||||
|
// }
|
||||||
|
}).unwrap();
|
||||||
|
println!("Changed Status");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,13 +3,14 @@
|
||||||
//! other functions
|
//! other functions
|
||||||
#![allow(while_true)]
|
#![allow(while_true)]
|
||||||
|
|
||||||
use async_channel::unbounded;
|
use async_channel::{bounded, unbounded};
|
||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
use crossbeam_channel::{Receiver, Sender};
|
||||||
use kushi::{Queue, QueueItemType};
|
use kushi::{Queue, QueueItemType};
|
||||||
use kushi::{QueueError, QueueItem};
|
use kushi::{QueueError, QueueItem};
|
||||||
use prismriver::{Prismriver, Volume, Error as PrismError};
|
use parking_lot::RwLock;
|
||||||
|
use prismriver::{Error as PrismError, Prismriver, State as PrismState, Volume};
|
||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::to_string_pretty;
|
use serde_json::to_string_pretty;
|
||||||
|
@ -18,7 +19,7 @@ use std::error::Error;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -28,6 +29,7 @@ use crate::music_storage::library::Song;
|
||||||
use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem};
|
use crate::music_storage::playlist::{ExternalPlaylist, Playlist, PlaylistFolderItem};
|
||||||
use crate::{config::Config, music_storage::library::MusicLibrary};
|
use crate::{config::Config, music_storage::library::MusicLibrary};
|
||||||
|
|
||||||
|
use super::connections::{ConnectionsInput, ConnectionsNotification, ControllerConnections};
|
||||||
use super::queue::{QueueAlbum, QueueSong};
|
use super::queue::{QueueAlbum, QueueSong};
|
||||||
|
|
||||||
pub struct Controller();
|
pub struct Controller();
|
||||||
|
@ -166,6 +168,7 @@ pub struct ControllerInput {
|
||||||
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>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ControllerHandle {
|
pub struct ControllerHandle {
|
||||||
|
@ -175,7 +178,7 @@ pub struct ControllerHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControllerHandle {
|
impl ControllerHandle {
|
||||||
pub fn new(library: MusicLibrary, config: Arc<RwLock<Config>>) -> (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 = MailMan::double();
|
let lib_mail = MailMan::double();
|
||||||
let player_mail = MailMan::double();
|
let player_mail = MailMan::double();
|
||||||
let queue_mail = MailMan::double();
|
let queue_mail = MailMan::double();
|
||||||
|
@ -195,6 +198,7 @@ impl ControllerHandle {
|
||||||
config,
|
config,
|
||||||
playback_info: Arc::clone(&playback_info),
|
playback_info: Arc::clone(&playback_info),
|
||||||
notify_next_song: notify_next_song.0,
|
notify_next_song: notify_next_song.0,
|
||||||
|
connections,
|
||||||
},
|
},
|
||||||
playback_info,
|
playback_info,
|
||||||
notify_next_song.1
|
notify_next_song.1
|
||||||
|
@ -246,6 +250,7 @@ impl Controller {
|
||||||
config,
|
config,
|
||||||
playback_info,
|
playback_info,
|
||||||
notify_next_song,
|
notify_next_song,
|
||||||
|
connections,
|
||||||
}: ControllerInput
|
}: ControllerInput
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let queue: Queue<QueueSong, QueueAlbum> = Queue {
|
let queue: Queue<QueueSong, QueueAlbum> = Queue {
|
||||||
|
@ -256,7 +261,7 @@ impl Controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
let state = {
|
let state = {
|
||||||
let path = &config.read().unwrap().state_path;
|
let path = &config.read().state_path;
|
||||||
if let Ok(state) = ControllerState::read_file(path) {
|
if let Ok(state) = ControllerState::read_file(path) {
|
||||||
state
|
state
|
||||||
} else {
|
} else {
|
||||||
|
@ -264,24 +269,26 @@ impl Controller {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let player = Arc::new(RwLock::new(Prismriver::new()));
|
|
||||||
|
|
||||||
std::thread::scope(|scope| {
|
std::thread::scope(|scope| {
|
||||||
|
let player = Prismriver::new();
|
||||||
|
let player_state = player.state.clone();
|
||||||
|
let player_timing = player.get_timing_recv();
|
||||||
|
let finished_tx = player.get_finished_recv();
|
||||||
|
let (notifications_rx, notifications_tx) = crossbeam_channel::unbounded::<ConnectionsNotification>();
|
||||||
|
|
||||||
let a = scope.spawn({
|
let a = scope.spawn({
|
||||||
let player = Arc::clone(&player);
|
|
||||||
let queue_mail = queue_mail.clone();
|
let queue_mail = queue_mail.clone();
|
||||||
move || {
|
move || {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
moro::async_scope!(|scope| {
|
moro::async_scope!(|scope| {
|
||||||
println!("async scope created");
|
println!("async scope created");
|
||||||
|
|
||||||
let _player = player.clone();
|
|
||||||
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,
|
||||||
|
@ -314,14 +321,26 @@ impl Controller {
|
||||||
|
|
||||||
let c = scope.spawn(|| {
|
let c = scope.spawn(|| {
|
||||||
Controller::player_monitor_loop(
|
Controller::player_monitor_loop(
|
||||||
player,
|
player_state,
|
||||||
|
player_timing,
|
||||||
|
finished_tx,
|
||||||
player_mail.0,
|
player_mail.0,
|
||||||
queue_mail.0,
|
queue_mail.0,
|
||||||
playback_info,
|
|
||||||
notify_next_song,
|
notify_next_song,
|
||||||
|
notifications_rx,
|
||||||
|
playback_info,
|
||||||
).unwrap();
|
).unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if let Some(inner) = connections {
|
||||||
|
dbg!(&inner);
|
||||||
|
let d = scope.spawn(|| {
|
||||||
|
Controller::handle_connections( ControllerConnections {
|
||||||
|
notifications_tx,
|
||||||
|
inner,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
a.join().unwrap();
|
a.join().unwrap();
|
||||||
b.join().unwrap();
|
b.join().unwrap();
|
||||||
c.join().unwrap();
|
c.join().unwrap();
|
||||||
|
@ -331,41 +350,42 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn player_command_loop(
|
async fn player_command_loop(
|
||||||
player: Arc<RwLock<Prismriver>>,
|
mut player: Prismriver,
|
||||||
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
player_mail: MailMan<PlayerResponse, PlayerCommand>,
|
||||||
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||||
lib_mail: MailMan<LibraryCommand, LibraryResponse>,
|
lib_mail: MailMan<LibraryCommand, LibraryResponse>,
|
||||||
mut state: ControllerState,
|
mut state: ControllerState,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
player.write().unwrap().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(mail) = _mail {
|
if let Ok(mail) = _mail {
|
||||||
match mail {
|
match mail {
|
||||||
PlayerCommand::Play => {
|
PlayerCommand::Play => {
|
||||||
player.write().unwrap().play();
|
player.play();
|
||||||
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerCommand::Pause => {
|
PlayerCommand::Pause => {
|
||||||
player.write().unwrap().pause();
|
player.pause();
|
||||||
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerCommand::Stop => {
|
PlayerCommand::Stop => {
|
||||||
player.write().unwrap().stop();
|
player.stop();
|
||||||
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerCommand::Seek(time) => {
|
PlayerCommand::Seek(time) => {
|
||||||
let res = player.write().unwrap().seek_to(TimeDelta::milliseconds(time));
|
let res = player.seek_to(TimeDelta::milliseconds(time));
|
||||||
player_mail.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap();
|
player_mail.send(PlayerResponse::Empty(res.map_err(|e| e.into()))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerCommand::SetVolume(volume) => {
|
PlayerCommand::SetVolume(volume) => {
|
||||||
player.write().unwrap().set_volume(Volume::new(volume));
|
player.set_volume(Volume::new(volume));
|
||||||
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
player_mail.send(PlayerResponse::Empty(Ok(()))).await.unwrap();
|
||||||
|
|
||||||
|
// make this async or something
|
||||||
state.volume = volume;
|
state.volume = volume;
|
||||||
_ = state.write_file()
|
_ = state.write_file()
|
||||||
}
|
}
|
||||||
|
@ -384,8 +404,8 @@ impl Controller {
|
||||||
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.write().unwrap().load_new(&prism_uri).unwrap();
|
player.load_new(&prism_uri).unwrap();
|
||||||
player.write().unwrap().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")};
|
||||||
|
|
||||||
|
@ -441,8 +461,8 @@ impl Controller {
|
||||||
};
|
};
|
||||||
|
|
||||||
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.write().unwrap().load_new(&prism_uri).unwrap();
|
player.load_new(&prism_uri).unwrap();
|
||||||
player.write().unwrap().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")};
|
||||||
player_mail.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap();
|
player_mail.send(PlayerResponse::NowPlaying(Ok(np_song.song.clone()))).await.unwrap();
|
||||||
|
@ -467,8 +487,8 @@ impl Controller {
|
||||||
match item.item {
|
match item.item {
|
||||||
QueueItemType::Single(song) => {
|
QueueItemType::Single(song) => {
|
||||||
let prism_uri = prismriver::utils::path_to_uri(&song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
|
let prism_uri = prismriver::utils::path_to_uri(&song.song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
|
||||||
player.write().unwrap().load_new(&prism_uri).unwrap();
|
player.load_new(&prism_uri).unwrap();
|
||||||
player.write().unwrap().play();
|
player.play();
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -508,8 +528,8 @@ impl Controller {
|
||||||
|
|
||||||
// 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(&song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
|
let prism_uri = prismriver::utils::path_to_uri(&song.primary_uri().unwrap().0.as_path().unwrap()).unwrap();
|
||||||
player.write().unwrap().load_new(&prism_uri).unwrap();
|
player.load_new(&prism_uri).unwrap();
|
||||||
player.write().unwrap().play();
|
player.play();
|
||||||
|
|
||||||
// how grab all the songs in a certain subset of the library, I reckon?
|
// how grab all the songs in a certain subset of the library, I reckon?
|
||||||
// ...
|
// ...
|
||||||
|
@ -563,36 +583,33 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn player_monitor_loop(
|
fn player_monitor_loop(
|
||||||
player: Arc<RwLock<Prismriver>>,
|
playback_state: Arc<std::sync::RwLock<PrismState>>,
|
||||||
|
playback_time_tx: Receiver<(Option<TimeDelta>, Option<TimeDelta>)>,
|
||||||
|
finished_recv: Receiver<()>,
|
||||||
player_mail: MailMan<PlayerCommand, PlayerResponse>,
|
player_mail: MailMan<PlayerCommand, PlayerResponse>,
|
||||||
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
queue_mail: MailMan<QueueCommand, QueueResponse>,
|
||||||
player_info: Arc<AtomicCell<PlaybackInfo>>,
|
|
||||||
notify_next_song: Sender<Song>,
|
notify_next_song: Sender<Song>,
|
||||||
|
notify_connections_: Sender<ConnectionsNotification>,
|
||||||
|
playback_info: Arc<AtomicCell<PlaybackInfo>>
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
|
|
||||||
let finished_recv = player.read().unwrap().get_finished_recv();
|
|
||||||
|
|
||||||
std::thread::scope(|s| {
|
std::thread::scope(|s| {
|
||||||
// Thread for timing and metadata
|
// Thread for timing and metadata
|
||||||
|
let notify_connections = notify_connections_.clone();
|
||||||
s.spawn({
|
s.spawn({
|
||||||
// let player = Arc::clone(&player);
|
|
||||||
move || {
|
move || {
|
||||||
|
println!("playback monitor started");
|
||||||
while true {
|
while true {
|
||||||
let player = player.read().unwrap();
|
let (position, duration) = playback_time_tx.recv().unwrap();
|
||||||
player_info.store(PlaybackInfo {
|
notify_connections.send(ConnectionsNotification::Playback { position: position.clone(), duration: duration.clone() }).unwrap();
|
||||||
duration: player.duration(),
|
playback_info.store(PlaybackInfo { position, duration });
|
||||||
position: player.position(),
|
|
||||||
metadata: player.metadata(),
|
|
||||||
});
|
|
||||||
drop(player);
|
|
||||||
|
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Thread for End of Track
|
// Thread for End of Track
|
||||||
|
let notify_connections = notify_connections_.clone();
|
||||||
s.spawn(move || { futures::executor::block_on(async {
|
s.spawn(move || { futures::executor::block_on(async {
|
||||||
|
println!("EOS monitor started");
|
||||||
while true {
|
while true {
|
||||||
let _ = finished_recv.recv();
|
let _ = finished_recv.recv();
|
||||||
println!("End of song");
|
println!("End of song");
|
||||||
|
@ -602,16 +619,31 @@ impl Controller {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
if let Ok(song) = res {
|
if let Ok(song) = res {
|
||||||
notify_next_song.send(song).unwrap();
|
notify_next_song.send(song.clone()).unwrap();
|
||||||
|
notify_connections.send(ConnectionsNotification::SongChange(song)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(100));
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
});});
|
});});
|
||||||
|
|
||||||
|
let notify_connections = notify_connections_.clone();
|
||||||
|
s.spawn(move || {
|
||||||
|
let mut state = PrismState::Stopped;
|
||||||
|
while true {
|
||||||
|
let _state = playback_state.read().unwrap().to_owned();
|
||||||
|
if _state != state {
|
||||||
|
state = _state;
|
||||||
|
println!("State Changed to {state:?}");
|
||||||
|
notify_connections.send(ConnectionsNotification::StateChange(state.clone())).unwrap();
|
||||||
|
}
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for duration and spit it out
|
|
||||||
// Check for end of song to get the next
|
|
||||||
|
|
||||||
|
|
||||||
|
println!("Monitor Loops Ended");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -643,7 +675,7 @@ impl Controller {
|
||||||
lib_mail.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap();
|
lib_mail.send(LibraryResponse::ImportM3UPlayList(uuid, name)).await.unwrap();
|
||||||
}
|
}
|
||||||
LibraryCommand::Save => {
|
LibraryCommand::Save => {
|
||||||
library.save(config.read().unwrap().libraries.get_library(&library.uuid).unwrap().path.clone()).unwrap();
|
library.save(config.read().libraries.get_library(&library.uuid).unwrap().path.clone()).unwrap();
|
||||||
lib_mail.send(LibraryResponse::Ok).await.unwrap();
|
lib_mail.send(LibraryResponse::Ok).await.unwrap();
|
||||||
}
|
}
|
||||||
LibraryCommand::Playlists => {
|
LibraryCommand::Playlists => {
|
||||||
|
@ -716,7 +748,6 @@ impl Controller {
|
||||||
|
|
||||||
#[derive(Debug, Default, Serialize, Clone)]
|
#[derive(Debug, Default, Serialize, Clone)]
|
||||||
pub struct PlaybackInfo {
|
pub struct PlaybackInfo {
|
||||||
pub duration: Option<TimeDelta>,
|
|
||||||
pub position: Option<TimeDelta>,
|
pub position: Option<TimeDelta>,
|
||||||
pub metadata: HashMap<String, String>,
|
pub duration: Option<TimeDelta>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ rfd = "0.15.1"
|
||||||
colog = "1.3.0"
|
colog = "1.3.0"
|
||||||
tempfile = "3.14.0"
|
tempfile = "3.14.0"
|
||||||
opener = "0.7.2"
|
opener = "0.7.2"
|
||||||
discord-presence = { version = "1.4.1", features = ["activity_type"] }
|
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
use std::{borrow::BorrowMut, fs, ops::Deref, path::PathBuf, sync::{atomic::Ordering, Arc}, thread::{scope, spawn}, time::Duration};
|
use std::{borrow::BorrowMut, fs, ops::Deref, path::PathBuf, sync::{atomic::Ordering, Arc}, thread::{scope, spawn}, time::Duration};
|
||||||
|
|
||||||
use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
|
use crossbeam::channel::{bounded, unbounded, Receiver, Sender};
|
||||||
use discord_presence::{models::{Activity, ActivityButton, ActivityTimestamps, ActivityType}, Event};
|
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::{connections::ConnectionsInput, controller::{Controller, ControllerHandle, LibraryResponse, PlaybackInfo}}, music_storage::library::{MusicLibrary, Song, Tag}};
|
||||||
use dmp_core::{config::{Config, ConfigLibrary}, music_controller::controller::{Controller, ControllerHandle, LibraryResponse, PlaybackInfo}, music_storage::library::{MusicLibrary, Song}};
|
|
||||||
use futures::channel::oneshot;
|
use futures::channel::oneshot;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use rfd::FileHandle;
|
use rfd::FileHandle;
|
||||||
|
@ -77,7 +76,10 @@ pub fn run() {
|
||||||
next_song_notification,
|
next_song_notification,
|
||||||
) = ControllerHandle::new(
|
) = ControllerHandle::new(
|
||||||
library,
|
library,
|
||||||
std::sync::Arc::new(std::sync::RwLock::new(config))
|
std::sync::Arc::new(RwLock::new(config)),
|
||||||
|
Some(ConnectionsInput {
|
||||||
|
discord_rpc_client_id: std::option_env!("DISCORD_CLIENT_ID").map(|id| id.parse::<u64>().unwrap()),
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
handle_rx.send(handle).unwrap();
|
handle_rx.send(handle).unwrap();
|
||||||
|
@ -123,8 +125,8 @@ pub fn run() {
|
||||||
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::new(RwLock::new(PlaybackInfo::default()));
|
let mut _info: Arc<RwLock<PlaybackInfo>> = Arc::new(RwLock::new(PlaybackInfo::default()));
|
||||||
let mut _now_playing = Arc::new(RwLock::new(None));
|
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();
|
||||||
|
@ -148,13 +150,9 @@ pub fn run() {
|
||||||
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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let info = _info.clone();
|
|
||||||
let now_playing = _now_playing.clone();
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
|
|
||||||
|
|
16
src/App.tsx
16
src/App.tsx
|
@ -299,12 +299,12 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = appWindow.listen<any>("playback_info", ({ payload, }) => {
|
const unlisten = appWindow.listen<any>("playback_info", ({ payload, }) => {
|
||||||
const info = payload as playbackInfo;
|
const info = payload as playbackInfo;
|
||||||
const _pos = Array.isArray(info.position) ? info.position![0] : 0;
|
const pos_ = Array.isArray(info.position) ? info.position![0] : 0;
|
||||||
const _dur = Array.isArray(info.duration) ? info.duration![0] : 0;
|
const dur_ = Array.isArray(info.duration) ? info.duration![0] : 0;
|
||||||
|
|
||||||
setPosition(_pos);
|
setPosition(pos_);
|
||||||
setDuration(_dur);
|
setDuration(dur_);
|
||||||
let progress = ((_pos/_dur) * 100);
|
let progress = ((dur_/pos_) * 100);
|
||||||
setSeekBarSize(progress)
|
setSeekBarSize(progress)
|
||||||
})
|
})
|
||||||
return () => { unlisten.then((f) => f()) }
|
return () => { unlisten.then((f) => f()) }
|
||||||
|
@ -313,7 +313,7 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
||||||
const seek = (event: React.MouseEvent<HTMLDivElement>) => {
|
const seek = (event: React.MouseEvent<HTMLDivElement>) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
let rect = seekBarRef.current!.getBoundingClientRect();
|
let rect = seekBarRef.current!.getBoundingClientRect();
|
||||||
let val = ((event.clientX-rect.left) / (rect.width))*duration;
|
let val = ((event.clientX-rect.left) / (rect.width))*position;
|
||||||
|
|
||||||
invoke('seek', { time: Math.round(val * 1000) }).then()
|
invoke('seek', { time: Math.round(val * 1000) }).then()
|
||||||
};
|
};
|
||||||
|
@ -340,9 +340,9 @@ function PlayBar({ playing, setPlaying }: PlayBarProps) {
|
||||||
invoke('set_volume', { volume: volume.target.value }).then(() => {})
|
invoke('set_volume', { volume: volume.target.value }).then(() => {})
|
||||||
}} />
|
}} />
|
||||||
<p id="timeDisplay">
|
<p id="timeDisplay">
|
||||||
{ Math.round(+position / 60).toString().padStart(2, "0") }:
|
|
||||||
{ (+position % 60).toString().padStart(2, "0") }/
|
|
||||||
{ Math.round(+duration / 60).toString().padStart(2, "0") }:
|
{ Math.round(+duration / 60).toString().padStart(2, "0") }:
|
||||||
|
{ (+position % 60).toString().padStart(2, "0") }/
|
||||||
|
{ Math.round(+position / 60).toString().padStart(2, "0") }:
|
||||||
{ (+duration % 60).toString().padStart(2, "0") }
|
{ (+duration % 60).toString().padStart(2, "0") }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -63,7 +63,6 @@ export enum BannedType {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface playbackInfo {
|
export interface playbackInfo {
|
||||||
duration?: number[],
|
|
||||||
position?: [number, number],
|
position?: [number, number],
|
||||||
metadata: Map<string, string>
|
duration?: [number, number],
|
||||||
}
|
}
|
Loading…
Reference in a new issue