mirror of
https://github.com/Dangoware/dango-music-player.git
synced 2025-04-19 10:02:53 -05:00
Finished Initial implementation of Discord RPC
This commit is contained in:
parent
31829ad4f5
commit
5be0df4dc2
3 changed files with 97 additions and 56 deletions
|
@ -1,12 +1,11 @@
|
||||||
#![allow(while_true)]
|
#![allow(while_true)]
|
||||||
use std::time::Duration;
|
use std::{thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}};
|
||||||
|
|
||||||
use chrono::TimeDelta;
|
use chrono::TimeDelta;
|
||||||
use crossbeam::scope;
|
use crossbeam::{scope, select};
|
||||||
use crossbeam_channel::{bounded, Receiver};
|
use crossbeam_channel::{bounded, Receiver};
|
||||||
use discord_presence::models::{Activity, ActivityTimestamps, ActivityType};
|
use discord_presence::Client;
|
||||||
use prismriver::State as PrismState;
|
use prismriver::State as PrismState;
|
||||||
use rayon::spawn;
|
|
||||||
|
|
||||||
use crate::music_storage::library::{Song, Tag};
|
use crate::music_storage::library::{Song, Tag};
|
||||||
|
|
||||||
|
@ -68,67 +67,98 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
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>) {
|
||||||
spawn(move || {
|
// TODO: Handle seeking position change
|
||||||
|
std::thread::spawn(move || {
|
||||||
let mut client = discord_presence::Client::new(client_id);
|
let mut client = discord_presence::Client::new(client_id);
|
||||||
client.start();
|
client.start();
|
||||||
client.block_until_event(discord_presence::Event::Connected).unwrap();
|
while !Client::is_ready() { sleep(Duration::from_millis(100)); }
|
||||||
client.set_activity(|_|
|
|
||||||
Activity::new()
|
|
||||||
).unwrap();
|
|
||||||
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();
|
||||||
|
|
||||||
while true {
|
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 state = &mut state;
|
||||||
let song = &mut song;
|
let song = &mut song;
|
||||||
|
select! {
|
||||||
if let Ok(state_) = state_res {
|
recv(state_tx) -> res => {
|
||||||
|
if let Ok(state_) = res {
|
||||||
*state = match state_ {
|
*state = match state_ {
|
||||||
PrismState::Playing => "Playing",
|
PrismState::Playing => "Playing",
|
||||||
PrismState::Paused => "Paused",
|
PrismState::Paused => "Paused",
|
||||||
PrismState::Stopped => "Stopped",
|
PrismState::Stopped => "Stopped",
|
||||||
_ => "I'm Scared, Boss"
|
_ => "I'm Scared, Boss"
|
||||||
}.to_string()
|
}.to_string();
|
||||||
}
|
}
|
||||||
if let Ok(song_) = song_res {
|
},
|
||||||
|
recv(song_tx) -> res => {
|
||||||
|
if let Ok(song_) = res {
|
||||||
*song = Some(song_);
|
*song = Some(song_);
|
||||||
|
now = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards?").as_secs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default(Duration::from_millis(4500)) => ()
|
||||||
}
|
}
|
||||||
|
|
||||||
client.set_activity(|activity| {
|
client.set_activity(|activity| {
|
||||||
activity.state(
|
let a = activity.state(
|
||||||
state.clone()
|
song.as_ref().map_or(String::new(), |s| format!(
|
||||||
|
"{}{}{}",
|
||||||
|
s.get_tag(&Tag::Artist).map_or(String::new(), |album| album.clone()),
|
||||||
|
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())
|
||||||
|
)
|
||||||
|
)
|
||||||
)._type(discord_presence::models::ActivityType::Listening)
|
)._type(discord_presence::models::ActivityType::Listening)
|
||||||
.details(
|
.details(
|
||||||
if let Some(song) = song {
|
if let Some(song) = song {
|
||||||
format!(
|
song.get_tag(&Tag::Title).map_or(String::from("Unknown Title"), |title| title.clone())
|
||||||
"{} - {}\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 {
|
} else {
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
// if let Some(song) = song {
|
if let Some(s) = song {
|
||||||
// a.timestamps(|timestamp| {
|
if state.as_str() == "Playing" {
|
||||||
// ActivityTimestamps::new()
|
a.timestamps(|timestamps| {
|
||||||
// .start(timestamp.start.unwrap_or_default())
|
timestamps.start(now)
|
||||||
// .end(
|
.end(now + s.duration.as_secs())
|
||||||
// song.duration.as_millis().clamp(u64::MIN as u128, u64::MAX as u128) as u64
|
})
|
||||||
// )
|
} else {
|
||||||
// })
|
a
|
||||||
// } else {
|
}
|
||||||
// a
|
} else {
|
||||||
// }
|
a
|
||||||
|
}.assets(|a| {
|
||||||
|
a.large_text(state.clone())
|
||||||
|
})
|
||||||
}).unwrap();
|
}).unwrap();
|
||||||
println!("Changed Status");
|
println!("Updated Discord Status");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test_super {
|
||||||
|
use std::thread::sleep;
|
||||||
|
|
||||||
|
use crossbeam_channel::unbounded;
|
||||||
|
|
||||||
|
use crate::config::tests::read_config_lib;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discord_test() {
|
||||||
|
let client_id = std::env!("DISCORD_CLIENT_ID").parse::<u64>().unwrap();
|
||||||
|
let (song_rx, song_tx) = unbounded();
|
||||||
|
let (_, state_tx) = unbounded();
|
||||||
|
|
||||||
|
let (_, lib ) = read_config_lib();
|
||||||
|
song_rx.send(lib.library[0].clone()).unwrap();
|
||||||
|
|
||||||
|
Controller::discord_rpc(client_id, song_tx, state_tx);
|
||||||
|
sleep(Duration::from_secs(150));
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,6 @@
|
||||||
//! other functions
|
//! other functions
|
||||||
#![allow(while_true)]
|
#![allow(while_true)]
|
||||||
|
|
||||||
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};
|
||||||
|
@ -14,12 +13,11 @@ 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;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::error::Error;
|
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};
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -278,6 +276,7 @@ impl Controller {
|
||||||
|
|
||||||
let a = scope.spawn({
|
let a = scope.spawn({
|
||||||
let queue_mail = queue_mail.clone();
|
let queue_mail = queue_mail.clone();
|
||||||
|
let _notifications_rx = notifications_rx.clone();
|
||||||
move || {
|
move || {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
moro::async_scope!(|scope| {
|
moro::async_scope!(|scope| {
|
||||||
|
@ -292,6 +291,7 @@ impl Controller {
|
||||||
player_mail.1,
|
player_mail.1,
|
||||||
_queue_mail,
|
_queue_mail,
|
||||||
_lib_mail,
|
_lib_mail,
|
||||||
|
_notifications_rx,
|
||||||
state,
|
state,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
@ -354,6 +354,7 @@ impl Controller {
|
||||||
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>,
|
||||||
|
notify_connections_: Sender<ConnectionsNotification>,
|
||||||
mut state: ControllerState,
|
mut state: ControllerState,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
player.set_volume(Volume::new(state.volume));
|
player.set_volume(Volume::new(state.volume));
|
||||||
|
@ -444,6 +445,7 @@ impl Controller {
|
||||||
|
|
||||||
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();
|
||||||
} QueueResponse::Item(Err(e)) => {
|
} QueueResponse::Item(Err(e)) => {
|
||||||
player_mail.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
player_mail.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -469,6 +471,7 @@ impl Controller {
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
QueueResponse::Item(Err(e)) => {
|
QueueResponse::Item(Err(e)) => {
|
||||||
player_mail.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
player_mail.send(PlayerResponse::NowPlaying(Err(e.into()))).await.unwrap();
|
||||||
|
@ -485,10 +488,14 @@ impl Controller {
|
||||||
match queue_mail.recv().await.unwrap() {
|
match queue_mail.recv().await.unwrap() {
|
||||||
QueueResponse::Item(Ok(item)) => {
|
QueueResponse::Item(Ok(item)) => {
|
||||||
match item.item {
|
match item.item {
|
||||||
QueueItemType::Single(song) => {
|
QueueItemType::Single(np_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(&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.write_file();
|
||||||
|
notify_connections_.send(ConnectionsNotification::SongChange(np_song.song)).unwrap();
|
||||||
}
|
}
|
||||||
_ => unimplemented!(),
|
_ => unimplemented!(),
|
||||||
}
|
}
|
||||||
|
@ -504,7 +511,7 @@ impl Controller {
|
||||||
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.
|
||||||
lib_mail.send(LibraryCommand::Song(uuid)).await.unwrap();
|
lib_mail.send(LibraryCommand::Song(uuid)).await.unwrap();
|
||||||
let LibraryResponse::Song(song, index) = lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::Song(np_song, index) = lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
queue_mail.send(QueueCommand::Clear).await.unwrap();
|
queue_mail.send(QueueCommand::Clear).await.unwrap();
|
||||||
|
@ -516,7 +523,7 @@ impl Controller {
|
||||||
}
|
}
|
||||||
_ => unreachable!()
|
_ => unreachable!()
|
||||||
}
|
}
|
||||||
queue_mail.send(QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: song.clone(), location })), true)).await.unwrap();
|
queue_mail.send(QueueCommand::Append(QueueItem::from_item_type(QueueItemType::Single(QueueSong { song: np_song.clone(), location })), true)).await.unwrap();
|
||||||
match queue_mail.recv().await.unwrap() {
|
match queue_mail.recv().await.unwrap() {
|
||||||
QueueResponse::Empty(Ok(())) => (),
|
QueueResponse::Empty(Ok(())) => (),
|
||||||
QueueResponse::Empty(Err(e)) => {
|
QueueResponse::Empty(Err(e)) => {
|
||||||
|
@ -527,7 +534,7 @@ 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(&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();
|
||||||
|
|
||||||
|
@ -548,7 +555,7 @@ impl Controller {
|
||||||
let LibraryResponse::ExternalPlaylist(list) = lib_mail.recv().await.unwrap() else {
|
let LibraryResponse::ExternalPlaylist(list) = lib_mail.recv().await.unwrap() else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
};
|
};
|
||||||
let index = list.get_index(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")
|
||||||
|
@ -572,7 +579,11 @@ impl Controller {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ^ This be my solution for now ^
|
// ^ This be my solution for now ^
|
||||||
player_mail.send(PlayerResponse::NowPlaying(Ok(song.clone()))).await.unwrap();
|
player_mail.send(PlayerResponse::NowPlaying(Ok(np_song.clone()))).await.unwrap();
|
||||||
|
|
||||||
|
state.now_playing = np_song.uuid;
|
||||||
|
_ = state.write_file();
|
||||||
|
notify_connections_.send(ConnectionsNotification::SongChange(np_song)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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(+duration / 60).toString().padStart(2, "0") }:
|
{ Math.floor(+position / 60).toString().padStart(2, "0") }:
|
||||||
{ (+position % 60).toString().padStart(2, "0") }/
|
{ (+position % 60).toString().padStart(2, "0") }/
|
||||||
{ Math.round(+position / 60).toString().padStart(2, "0") }:
|
{ Math.floor(+duration / 60).toString().padStart(2, "0") }:
|
||||||
{ (+duration % 60).toString().padStart(2, "0") }
|
{ (+duration % 60).toString().padStart(2, "0") }
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue