mirror of
https://github.com/Dangoware/dmp-core.git
synced 2025-04-19 13:22:54 -05:00
Refactored music_player, integrated music_tracker into it. Closes #7
This commit is contained in:
parent
c8f16f6b35
commit
14edbe7c46
6 changed files with 378 additions and 230 deletions
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::music_tracker::music_tracker::{LastFM, LastFMConfig, DiscordRPCConfig};
|
use crate::music_tracker::music_tracker::{LastFM, LastFMConfig, DiscordRPCConfig};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, PartialEq, Eq)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
pub db_path: Box<PathBuf>,
|
pub db_path: Box<PathBuf>,
|
||||||
pub lastfm: Option<LastFMConfig>,
|
pub lastfm: Option<LastFMConfig>,
|
||||||
|
@ -20,13 +20,7 @@ impl Default for Config {
|
||||||
return Config {
|
return Config {
|
||||||
db_path: Box::new(path),
|
db_path: Box::new(path),
|
||||||
|
|
||||||
lastfm: Some (LastFMConfig {
|
lastfm: None,
|
||||||
enabled: true,
|
|
||||||
dango_api_key: String::from("29a071e3113ab8ed36f069a2d3e20593"),
|
|
||||||
auth_token: None,
|
|
||||||
shared_secret: Some(String::from("5400c554430de5c5002d5e4bcc295b3d")),
|
|
||||||
session_key: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
discord: Some(DiscordRPCConfig {
|
discord: Some(DiscordRPCConfig {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::{RwLock, Arc, Mutex};
|
||||||
|
|
||||||
use rusqlite::Result;
|
use rusqlite::Result;
|
||||||
|
|
||||||
use crate::music_controller::config::Config;
|
use crate::music_controller::config::Config;
|
||||||
use crate::music_player::music_player::{MusicPlayer, PlayerStatus, PlayerMessage, DSPMessage};
|
use crate::music_player::music_player::{MusicPlayer, PlayerStatus, DecoderMessage, DSPMessage};
|
||||||
use crate::music_processor::music_processor::MusicProcessor;
|
use crate::music_processor::music_processor::MusicProcessor;
|
||||||
use crate::music_storage::music_db::URI;
|
use crate::music_storage::music_db::{URI, Song};
|
||||||
|
|
||||||
pub struct MusicController {
|
pub struct MusicController {
|
||||||
pub config: Config,
|
pub config: Arc<RwLock<Config>>,
|
||||||
music_player: MusicPlayer,
|
music_player: MusicPlayer,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MusicController {
|
impl MusicController {
|
||||||
/// Creates new MusicController with config at given path
|
/// Creates new MusicController with config at given path
|
||||||
pub fn new(config_path: &PathBuf) -> Result<MusicController, std::io::Error>{
|
pub fn new(config_path: &PathBuf) -> Result<MusicController, std::io::Error>{
|
||||||
let config = Config::new(config_path)?;
|
let config = Arc::new(RwLock::new(Config::new(config_path)?));
|
||||||
let music_player = MusicPlayer::new();
|
let music_player = MusicPlayer::new(config.clone());
|
||||||
|
|
||||||
let controller = MusicController {
|
let controller = MusicController {
|
||||||
config,
|
config,
|
||||||
|
@ -28,8 +29,8 @@ impl MusicController {
|
||||||
|
|
||||||
/// Creates new music controller from a config at given path
|
/// Creates new music controller from a config at given path
|
||||||
pub fn from(config_path: &PathBuf) -> std::result::Result<MusicController, toml::de::Error> {
|
pub fn from(config_path: &PathBuf) -> std::result::Result<MusicController, toml::de::Error> {
|
||||||
let config = Config::from(config_path)?;
|
let config = Arc::new(RwLock::new(Config::from(config_path)?));
|
||||||
let music_player = MusicPlayer::new();
|
let music_player = MusicPlayer::new(config.clone());
|
||||||
|
|
||||||
let controller = MusicController {
|
let controller = MusicController {
|
||||||
config,
|
config,
|
||||||
|
@ -39,13 +40,8 @@ impl MusicController {
|
||||||
return Ok(controller)
|
return Ok(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Opens and plays song at given URI
|
/// Sends given message to control music player
|
||||||
pub fn open_song(&mut self, uri: &URI) {
|
pub fn song_control(&mut self, message: DecoderMessage) {
|
||||||
self.music_player.open_song(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sends given message to music player
|
|
||||||
pub fn song_control(&mut self, message: PlayerMessage) {
|
|
||||||
self.music_player.send_message(message);
|
self.music_player.send_message(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,15 +50,20 @@ impl MusicController {
|
||||||
return self.music_player.get_status();
|
return self.music_player.get_status();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets current song being controlled, if any
|
||||||
|
pub fn get_current_song(&self) -> Option<Song> {
|
||||||
|
return self.music_player.get_current_song();
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets audio playback volume
|
/// Gets audio playback volume
|
||||||
pub fn get_vol(&mut self) -> f32 {
|
pub fn get_vol(&self) -> f32 {
|
||||||
return self.music_player.music_processor.audio_volume;
|
return self.music_player.music_processor.audio_volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets audio playback volume on a scale of 0.0 to 1.0
|
/// Sets audio playback volume on a scale of 0.0 to 1.0
|
||||||
pub fn set_vol(&mut self, volume: f32) {
|
pub fn set_vol(&mut self, volume: f32) {
|
||||||
self.music_player.music_processor.audio_volume = volume;
|
self.music_player.music_processor.audio_volume = volume;
|
||||||
self.song_control(PlayerMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
|
self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone()))));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,50 @@
|
||||||
use std::sync::mpsc::{self, Sender, Receiver};
|
use std::sync::mpsc::{self, Sender, Receiver};
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::io::SeekFrom;
|
use std::io::SeekFrom;
|
||||||
|
|
||||||
use async_std::io::ReadExt;
|
use async_std::io::ReadExt;
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
|
|
||||||
|
use futures::future::join_all;
|
||||||
use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions, Decoder};
|
use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions, Decoder};
|
||||||
use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
|
use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo};
|
||||||
use symphonia::core::io::{MediaSourceStream, MediaSource};
|
use symphonia::core::io::{MediaSourceStream, MediaSource};
|
||||||
use symphonia::core::meta::MetadataOptions;
|
use symphonia::core::meta::MetadataOptions;
|
||||||
use symphonia::core::probe::Hint;
|
use symphonia::core::probe::Hint;
|
||||||
use symphonia::core::errors::Error;
|
use symphonia::core::errors::Error;
|
||||||
use symphonia::core::units::Time;
|
use symphonia::core::units::{Time, TimeBase};
|
||||||
|
|
||||||
use futures::AsyncBufRead;
|
use futures::AsyncBufRead;
|
||||||
|
|
||||||
|
use crate::music_controller::config::Config;
|
||||||
use crate::music_player::music_output::AudioStream;
|
use crate::music_player::music_output::AudioStream;
|
||||||
use crate::music_processor::music_processor::MusicProcessor;
|
use crate::music_processor::music_processor::MusicProcessor;
|
||||||
use crate::music_storage::music_db::URI;
|
use crate::music_storage::music_db::{URI, Song};
|
||||||
|
use crate::music_tracker::music_tracker::{MusicTracker, LastFM, DiscordRPCConfig, DiscordRPC, TrackerError};
|
||||||
|
|
||||||
// Struct that controls playback of music
|
// Struct that controls playback of music
|
||||||
pub struct MusicPlayer {
|
pub struct MusicPlayer {
|
||||||
pub music_processor: MusicProcessor,
|
pub music_processor: MusicProcessor,
|
||||||
player_status: PlayerStatus,
|
player_status: PlayerStatus,
|
||||||
message_sender: Option<Sender<PlayerMessage>>,
|
music_trackers: Vec<Box<dyn MusicTracker + Send>>,
|
||||||
status_receiver: Option<Receiver<PlayerStatus>>,
|
current_song: Arc<RwLock<Option<Song>>>,
|
||||||
|
message_sender: Sender<DecoderMessage>,
|
||||||
|
status_receiver: Receiver<PlayerStatus>,
|
||||||
|
config: Arc<RwLock<Config>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum PlayerStatus {
|
pub enum PlayerStatus {
|
||||||
Playing,
|
Playing(f64),
|
||||||
Paused,
|
Paused,
|
||||||
Stopped,
|
Stopped,
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PlayerMessage {
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DecoderMessage {
|
||||||
|
OpenSong(Song),
|
||||||
Play,
|
Play,
|
||||||
Pause,
|
Pause,
|
||||||
Stop,
|
Stop,
|
||||||
|
@ -43,143 +52,43 @@ pub enum PlayerMessage {
|
||||||
DSP(DSPMessage)
|
DSP(DSPMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum TrackerMessage {
|
||||||
|
Track(Song),
|
||||||
|
TrackNow(Song)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub enum DSPMessage {
|
pub enum DSPMessage {
|
||||||
UpdateProcessor(Box<MusicProcessor>)
|
UpdateProcessor(Box<MusicProcessor>)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MusicPlayer {
|
// Holds a song decoder reader, etc
|
||||||
pub fn new() -> Self {
|
struct SongHandler {
|
||||||
MusicPlayer {
|
pub reader: Box<dyn FormatReader>,
|
||||||
music_processor: MusicProcessor::new(),
|
pub decoder: Box<dyn Decoder>,
|
||||||
player_status: PlayerStatus::Stopped,
|
pub time_base: Option<TimeBase>,
|
||||||
message_sender: None,
|
pub duration: Option<u64>,
|
||||||
status_receiver: None,
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Opens and plays song with given path in separate thread
|
|
||||||
pub fn open_song(&mut self, uri: &URI) {
|
|
||||||
// Creates mpsc channels to communicate with thread
|
|
||||||
let (message_sender, message_receiver) = mpsc::channel();
|
|
||||||
let (status_sender, status_receiver) = mpsc::channel();
|
|
||||||
self.message_sender = Some(message_sender);
|
|
||||||
self.status_receiver = Some(status_receiver);
|
|
||||||
|
|
||||||
let owned_uri = uri.clone();
|
|
||||||
|
|
||||||
// Creates thread that audio is decoded in
|
// TODO: actual error handling here
|
||||||
thread::spawn(move || {
|
impl SongHandler {
|
||||||
let (mut reader, mut decoder) = MusicPlayer::get_reader_and_dec(&owned_uri);
|
pub fn new(uri: &URI) -> Result<Self, ()> {
|
||||||
|
|
||||||
let mut seek_time: Option<u64> = None;
|
|
||||||
|
|
||||||
let mut audio_output: Option<Box<dyn AudioStream>> = None;
|
|
||||||
|
|
||||||
let mut music_processor = MusicProcessor::new();
|
|
||||||
|
|
||||||
'main_decode: loop {
|
|
||||||
// Handles message received from the MusicPlayer if there is one // TODO: Refactor
|
|
||||||
let received_message = message_receiver.try_recv();
|
|
||||||
match received_message {
|
|
||||||
Ok(PlayerMessage::Pause) => {
|
|
||||||
status_sender.send(PlayerStatus::Paused).unwrap();
|
|
||||||
// Loops on a blocking message receiver to wait for a play/stop message
|
|
||||||
'inner_pause: loop {
|
|
||||||
let message = message_receiver.try_recv();
|
|
||||||
match message {
|
|
||||||
Ok(PlayerMessage::Play) => {
|
|
||||||
status_sender.send(PlayerStatus::Playing).unwrap();
|
|
||||||
break 'inner_pause
|
|
||||||
},
|
|
||||||
Ok(PlayerMessage::Stop) => {
|
|
||||||
status_sender.send(PlayerStatus::Stopped).unwrap();
|
|
||||||
break 'main_decode
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// Exits main decode loop and subsequently ends thread (?)
|
|
||||||
Ok(PlayerMessage::Stop) => {
|
|
||||||
status_sender.send(PlayerStatus::Stopped).unwrap();
|
|
||||||
break 'main_decode
|
|
||||||
},
|
|
||||||
Ok(PlayerMessage::SeekTo(time)) => seek_time = Some(time),
|
|
||||||
Ok(PlayerMessage::DSP(dsp_message)) => {
|
|
||||||
match dsp_message {
|
|
||||||
DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
match seek_time {
|
|
||||||
Some(time) => {
|
|
||||||
let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) };
|
|
||||||
reader.seek(SeekMode::Accurate, seek_to).unwrap();
|
|
||||||
seek_time = None;
|
|
||||||
}
|
|
||||||
None => {} //Nothing to do!
|
|
||||||
}
|
|
||||||
|
|
||||||
let packet = match reader.next_packet() {
|
|
||||||
Ok(packet) => packet,
|
|
||||||
Err(Error::ResetRequired) => panic!(), //TODO,
|
|
||||||
Err(err) => {
|
|
||||||
//Unrecoverable?
|
|
||||||
panic!("{}", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
match decoder.decode(&packet) {
|
|
||||||
Ok(decoded) => {
|
|
||||||
// Opens audio stream if there is not one
|
|
||||||
if audio_output.is_none() {
|
|
||||||
let spec = *decoded.spec();
|
|
||||||
let duration = decoded.capacity() as u64;
|
|
||||||
|
|
||||||
audio_output.replace(crate::music_player::music_output::open_stream(spec, duration).unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handles audio normally provided there is an audio stream
|
|
||||||
if let Some(ref mut audio_output) = audio_output {
|
|
||||||
// Changes buffer of the MusicProcessor if the packet has a differing capacity or spec
|
|
||||||
if music_processor.audio_buffer.capacity() != decoded.capacity() ||music_processor.audio_buffer.spec() != decoded.spec() {
|
|
||||||
let spec = *decoded.spec();
|
|
||||||
let duration = decoded.capacity() as u64;
|
|
||||||
|
|
||||||
music_processor.set_buffer(duration, spec);
|
|
||||||
}
|
|
||||||
|
|
||||||
let transformed_audio = music_processor.process(&decoded);
|
|
||||||
|
|
||||||
// Writes transformed packet to audio out
|
|
||||||
audio_output.write(transformed_audio).unwrap()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(Error::IoError(_)) => {
|
|
||||||
// rest in peace packet
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
Err(Error::DecodeError(_)) => {
|
|
||||||
// may you one day be decoded
|
|
||||||
continue;
|
|
||||||
},
|
|
||||||
Err(err) => {
|
|
||||||
// Unrecoverable, though shouldn't panic here
|
|
||||||
panic!("{}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_reader_and_dec(uri: &URI) -> (Box<dyn FormatReader>, Box<dyn Decoder>) {
|
|
||||||
// Opens remote/local source and creates MediaSource for symphonia
|
// Opens remote/local source and creates MediaSource for symphonia
|
||||||
let config = RemoteOptions { media_buffer_len: 10000, forward_buffer_len: 10000};
|
let config = RemoteOptions {media_buffer_len: 10000, forward_buffer_len: 10000};
|
||||||
let src: Box<dyn MediaSource> = match uri {
|
let src: Box<dyn MediaSource> = match uri {
|
||||||
URI::Local(path) => Box::new(std::fs::File::open(path).expect("Failed to open file")),
|
URI::Local(path) => {
|
||||||
URI::Remote(_, location) => Box::new(RemoteSource::new(location.as_ref(), &config).unwrap()),
|
match std::fs::File::open(path) {
|
||||||
|
Ok(file) => Box::new(file),
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
URI::Remote(_, location) => {
|
||||||
|
match RemoteSource::new(location.as_ref(), &config) {
|
||||||
|
Ok(remote_source) => Box::new(remote_source),
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
let mss = MediaSourceStream::new(src, Default::default());
|
let mss = MediaSourceStream::new(src, Default::default());
|
||||||
|
@ -198,42 +107,262 @@ impl MusicPlayer {
|
||||||
.iter()
|
.iter()
|
||||||
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
|
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
|
||||||
.expect("no supported audio tracks");
|
.expect("no supported audio tracks");
|
||||||
|
|
||||||
|
let time_base = track.codec_params.time_base;
|
||||||
|
let duration = track.codec_params.n_frames;
|
||||||
|
|
||||||
let dec_opts: DecoderOptions = Default::default();
|
let dec_opts: DecoderOptions = Default::default();
|
||||||
|
|
||||||
let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
|
let mut decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)
|
||||||
.expect("unsupported codec");
|
.expect("unsupported codec");
|
||||||
|
|
||||||
return (reader, decoder);
|
return Ok(SongHandler {reader, decoder, time_base, duration});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MusicPlayer {
|
||||||
|
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||||
|
// Creates mpsc channels to communicate with music player threads
|
||||||
|
let (message_sender, message_receiver) = mpsc::channel();
|
||||||
|
let (status_sender, status_receiver) = mpsc::channel();
|
||||||
|
let current_song = Arc::new(RwLock::new(None));
|
||||||
|
|
||||||
|
MusicPlayer::start_player(message_receiver, status_sender, config.clone(), current_song.clone());
|
||||||
|
|
||||||
|
MusicPlayer {
|
||||||
|
music_processor: MusicProcessor::new(),
|
||||||
|
music_trackers: Vec::new(),
|
||||||
|
player_status: PlayerStatus::Stopped,
|
||||||
|
current_song,
|
||||||
|
message_sender,
|
||||||
|
status_receiver,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_tracker(status_sender: Sender<Result<(), TrackerError>>, tracker_receiver: Receiver<TrackerMessage>, config: Arc<RwLock<Config>>) {
|
||||||
|
thread::spawn(move || {
|
||||||
|
let global_config = &*config.read().unwrap();
|
||||||
|
// Sets local config for trackers to detect changes
|
||||||
|
let local_config = global_config.clone();
|
||||||
|
let mut trackers: Vec<Box<dyn MusicTracker>> = Vec::new();
|
||||||
|
// Updates local trackers to the music controller config
|
||||||
|
let update_trackers = |trackers: &mut Vec<Box<dyn MusicTracker>>|{
|
||||||
|
if let Some(lastfm_config) = global_config.lastfm.clone() {
|
||||||
|
trackers.push(Box::new(LastFM::new(&lastfm_config)));
|
||||||
|
}
|
||||||
|
if let Some(discord_config) = global_config.discord.clone() {
|
||||||
|
trackers.push(Box::new(DiscordRPC::new(&discord_config)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
update_trackers(&mut trackers);
|
||||||
|
loop {
|
||||||
|
if let message = tracker_receiver.recv() {
|
||||||
|
if local_config != global_config {
|
||||||
|
update_trackers(&mut trackers);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut results = Vec::new();
|
||||||
|
task::block_on(async {
|
||||||
|
let mut futures = Vec::new();
|
||||||
|
for tracker in trackers.iter_mut() {
|
||||||
|
match message.clone() {
|
||||||
|
Ok(TrackerMessage::Track(song)) => futures.push(tracker.track_song(song)),
|
||||||
|
Ok(TrackerMessage::TrackNow(song)) => futures.push(tracker.track_now(song)),
|
||||||
|
Err(_) => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results = join_all(futures).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
for result in results {
|
||||||
|
status_sender.send(result).unwrap_or_default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opens and plays song with given path in separate thread
|
||||||
|
fn start_player(message_receiver: Receiver<DecoderMessage>, status_sender: Sender<PlayerStatus>, config: Arc<RwLock<Config>>, current_song: Arc<RwLock<Option<Song>>>) {
|
||||||
|
// Creates thread that audio is decoded in
|
||||||
|
thread::spawn(move || {
|
||||||
|
let current_song = current_song;
|
||||||
|
|
||||||
|
let mut song_handler = None;
|
||||||
|
|
||||||
|
let mut seek_time: Option<u64> = None;
|
||||||
|
|
||||||
|
let mut audio_output: Option<Box<dyn AudioStream>> = None;
|
||||||
|
|
||||||
|
let mut music_processor = MusicProcessor::new();
|
||||||
|
|
||||||
|
let (tracker_sender, tracker_receiver): (Sender<TrackerMessage>, Receiver<TrackerMessage>) = mpsc::channel();
|
||||||
|
let (tracker_status_sender, tracker_status_receiver): (Sender<Result<(), TrackerError>>, Receiver<Result<(), TrackerError>>) = mpsc::channel();
|
||||||
|
|
||||||
|
MusicPlayer::start_tracker(tracker_status_sender, tracker_receiver, config);
|
||||||
|
|
||||||
|
let mut song_tracked = false;
|
||||||
|
let mut song_time = 0.0;
|
||||||
|
let mut paused = true;
|
||||||
|
'main_decode: loop {
|
||||||
|
'handle_message: loop {
|
||||||
|
let message = if paused {
|
||||||
|
// Pauses playback by blocking on waiting for new player messages
|
||||||
|
match message_receiver.recv() {
|
||||||
|
Ok(message) => Some(message),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Resumes playback by not blocking
|
||||||
|
match message_receiver.try_recv() {
|
||||||
|
Ok(message) => Some(message),
|
||||||
|
Err(_) => break 'handle_message,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Handles message received from MusicPlayer struct
|
||||||
|
match message {
|
||||||
|
Some(DecoderMessage::OpenSong(song)) => {
|
||||||
|
let song_uri = song.path.clone();
|
||||||
|
match SongHandler::new(&song_uri) {
|
||||||
|
Ok(new_handler) => {
|
||||||
|
song_handler = Some(new_handler);
|
||||||
|
*current_song.write().unwrap() = Some(song);
|
||||||
|
paused = false;
|
||||||
|
song_tracked = false;
|
||||||
|
}
|
||||||
|
Err(_) => status_sender.send(PlayerStatus::Error).unwrap(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(DecoderMessage::Play) => {
|
||||||
|
if song_handler.is_some() {
|
||||||
|
paused = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(DecoderMessage::Pause) => {
|
||||||
|
paused = true;
|
||||||
|
status_sender.send(PlayerStatus::Paused).unwrap();
|
||||||
|
}
|
||||||
|
Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time),
|
||||||
|
Some(DecoderMessage::DSP(dsp_message)) => {
|
||||||
|
match dsp_message {
|
||||||
|
DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Exits main decode loop and subsequently ends thread
|
||||||
|
Some(DecoderMessage::Stop) => {
|
||||||
|
status_sender.send(PlayerStatus::Stopped).unwrap();
|
||||||
|
break 'main_decode
|
||||||
|
}
|
||||||
|
None => {},
|
||||||
|
}
|
||||||
|
status_sender.send(PlayerStatus::Error).unwrap();
|
||||||
|
}
|
||||||
|
// In theory this check should not need to occur?
|
||||||
|
if let (Some(song_handler), current_song) = (&mut song_handler, &*current_song.read().unwrap()) {
|
||||||
|
match seek_time {
|
||||||
|
Some(time) => {
|
||||||
|
let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) };
|
||||||
|
song_handler.reader.seek(SeekMode::Accurate, seek_to).unwrap();
|
||||||
|
seek_time = None;
|
||||||
|
}
|
||||||
|
None => {} //Nothing to do!
|
||||||
|
}
|
||||||
|
let packet = match song_handler.reader.next_packet() {
|
||||||
|
Ok(packet) => packet,
|
||||||
|
Err(Error::ResetRequired) => panic!(), //TODO,
|
||||||
|
Err(err) => {
|
||||||
|
// Unrecoverable?
|
||||||
|
panic!("{}", err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let (Some(time_base), Some(song)) = (song_handler.time_base, current_song) {
|
||||||
|
let time_units = time_base.calc_time(packet.ts);
|
||||||
|
song_time = time_units.seconds as f64 + time_units.frac;
|
||||||
|
// Tracks song now if song has just started
|
||||||
|
if song_time == 0.0 {
|
||||||
|
tracker_sender.send(TrackerMessage::TrackNow(song.clone())).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(duration) = song_handler.duration {
|
||||||
|
let song_duration = time_base.calc_time(duration);
|
||||||
|
let song_duration_secs = song_duration.seconds as f64 + song_duration.frac;
|
||||||
|
// Tracks song if current time is past half of total song duration or past 4 minutes
|
||||||
|
if (song_duration_secs / 2.0 < song_time || song_time > 240.0) && !song_tracked {
|
||||||
|
song_tracked = true;
|
||||||
|
tracker_sender.send(TrackerMessage::Track(song.clone())).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
status_sender.send(PlayerStatus::Playing(song_time)).unwrap();
|
||||||
|
|
||||||
|
match song_handler.decoder.decode(&packet) {
|
||||||
|
Ok(decoded) => {
|
||||||
|
// Opens audio stream if there is not one
|
||||||
|
if audio_output.is_none() {
|
||||||
|
let spec = *decoded.spec();
|
||||||
|
let duration = decoded.capacity() as u64;
|
||||||
|
|
||||||
|
audio_output.replace(crate::music_player::music_output::open_stream(spec, duration).unwrap());
|
||||||
|
}
|
||||||
|
// Handles audio normally provided there is an audio stream
|
||||||
|
if let Some(ref mut audio_output) = audio_output {
|
||||||
|
// Changes buffer of the MusicProcessor if the packet has a differing capacity or spec
|
||||||
|
if music_processor.audio_buffer.capacity() != decoded.capacity() ||music_processor.audio_buffer.spec() != decoded.spec() {
|
||||||
|
let spec = *decoded.spec();
|
||||||
|
let duration = decoded.capacity() as u64;
|
||||||
|
|
||||||
|
music_processor.set_buffer(duration, spec);
|
||||||
|
}
|
||||||
|
let transformed_audio = music_processor.process(&decoded);
|
||||||
|
|
||||||
|
// Writes transformed packet to audio out
|
||||||
|
audio_output.write(transformed_audio).unwrap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(Error::IoError(_)) => {
|
||||||
|
// rest in peace packet
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Err(Error::DecodeError(_)) => {
|
||||||
|
// may you one day be decoded
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
// Unrecoverable, though shouldn't panic here
|
||||||
|
panic!("{}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates status by checking on messages from spawned thread
|
// Updates status by checking on messages from spawned thread
|
||||||
fn update_status(&mut self) {
|
fn update_player(&mut self) {
|
||||||
let status = self.status_receiver.as_mut().unwrap().try_recv();
|
for message in self.status_receiver.try_recv() {
|
||||||
if status.is_ok() {
|
self.player_status = message;
|
||||||
self.player_status = status.unwrap();
|
}
|
||||||
match status.unwrap() {
|
}
|
||||||
// Removes receiver and sender since spawned thread no longer exists
|
|
||||||
PlayerStatus::Stopped => {
|
pub fn get_current_song(&self) -> Option<Song>{
|
||||||
self.status_receiver = None;
|
match self.current_song.try_read() {
|
||||||
self.message_sender = None;
|
Ok(song) => return (*song).clone(),
|
||||||
}
|
Err(_) => return None,
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sends message to spawned thread
|
// Sends message to spawned thread
|
||||||
pub fn send_message(&mut self, message: PlayerMessage) {
|
pub fn send_message(&mut self, message: DecoderMessage) {
|
||||||
self.update_status();
|
self.update_player();
|
||||||
// Checks that message sender exists before sending a message off
|
// Checks that message sender exists before sending a message off
|
||||||
if self.message_sender.is_some() {
|
self.message_sender.send(message).unwrap();
|
||||||
self.message_sender.as_mut().unwrap().send(message).unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_status(&mut self) -> PlayerStatus {
|
pub fn get_status(&mut self) -> PlayerStatus {
|
||||||
self.update_status();
|
self.update_player();
|
||||||
return self.player_status;
|
return self.player_status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,4 +490,4 @@ impl MediaSource for RemoteSource {
|
||||||
fn byte_len(&self) -> Option<u64> {
|
fn byte_len(&self) -> Option<u64> {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec};
|
use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -6,6 +8,12 @@ pub struct MusicProcessor {
|
||||||
pub audio_volume: f32,
|
pub audio_volume: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for MusicProcessor {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("MusicProcessor").field("audio_volume", &self.audio_volume).finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MusicProcessor {
|
impl MusicProcessor {
|
||||||
/// Returns new MusicProcessor with blank buffer and 100% volume
|
/// Returns new MusicProcessor with blank buffer and 100% volume
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
|
|
@ -11,28 +11,29 @@ use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::music_controller::config::Config;
|
use crate::music_controller::config::Config;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub path: Box<Path>,
|
pub path: URI,
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
pub album: Option<String>,
|
pub album: Option<String>,
|
||||||
tracknum: Option<usize>,
|
pub tracknum: Option<usize>,
|
||||||
pub artist: Option<String>,
|
pub artist: Option<String>,
|
||||||
date: Option<Date>,
|
pub date: Option<Date>,
|
||||||
genre: Option<String>,
|
pub genre: Option<String>,
|
||||||
plays: Option<usize>,
|
pub plays: Option<usize>,
|
||||||
favorited: Option<bool>,
|
pub favorited: Option<bool>,
|
||||||
format: Option<FileFormat>, // TODO: Make this a proper FileFormat eventually
|
pub format: Option<FileFormat>, // TODO: Make this a proper FileFormat eventually
|
||||||
duration: Option<Duration>,
|
pub duration: Option<Duration>,
|
||||||
pub custom_tags: Option<Vec<Tag>>,
|
pub custom_tags: Option<Vec<Tag>>,
|
||||||
}
|
}
|
||||||
#[derive(Clone)]
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
pub enum URI{
|
pub enum URI{
|
||||||
Local(String),
|
Local(String),
|
||||||
Remote(Service, String),
|
Remote(Service, String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub enum Service {
|
pub enum Service {
|
||||||
InternetRadio,
|
InternetRadio,
|
||||||
Spotify,
|
Spotify,
|
||||||
|
@ -143,7 +144,7 @@ fn parse_cuesheet(
|
||||||
|
|
||||||
let tags = track.get_cdtext();
|
let tags = track.get_cdtext();
|
||||||
let cue_song = Song {
|
let cue_song = Song {
|
||||||
path: track_path.into(),
|
path: URI::Local(String::from("URI")),
|
||||||
title: tags.read(PTI::Title),
|
title: tags.read(PTI::Title),
|
||||||
album: album.clone(),
|
album: album.clone(),
|
||||||
tracknum: Some(index + 1),
|
tracknum: Some(index + 1),
|
||||||
|
@ -311,7 +312,7 @@ pub fn add_file_to_db(target_file: &Path, connection: &Connection) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
pub enum Tag {
|
pub enum Tag {
|
||||||
SongPath,
|
SongPath,
|
||||||
Title,
|
Title,
|
||||||
|
@ -363,7 +364,7 @@ impl MusicObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query the database, returning a list of items
|
/// Query the database, returning a list of items
|
||||||
pub fn query(
|
pub fn query (
|
||||||
config: &Config,
|
config: &Config,
|
||||||
text_input: &String,
|
text_input: &String,
|
||||||
queried_tags: &Vec<&Tag>,
|
queried_tags: &Vec<&Tag>,
|
||||||
|
@ -435,7 +436,7 @@ pub fn query(
|
||||||
|
|
||||||
let new_song = Song {
|
let new_song = Song {
|
||||||
// TODO: Implement proper errors here
|
// TODO: Implement proper errors here
|
||||||
path: Path::new(&row.get::<usize, String>(0).unwrap_or("".to_owned())).into(),
|
path: URI::Local(String::from("URI")),
|
||||||
title: row.get::<usize, String>(1).ok(),
|
title: row.get::<usize, String>(1).ok(),
|
||||||
album: row.get::<usize, String>(2).ok(),
|
album: row.get::<usize, String>(2).ok(),
|
||||||
tracknum: row.get::<usize, usize>(3).ok(),
|
tracknum: row.get::<usize, usize>(3).ok(),
|
||||||
|
|
|
@ -7,19 +7,21 @@ use md5::{Md5, Digest};
|
||||||
use discord_presence::{ Event, DiscordError};
|
use discord_presence::{ Event, DiscordError};
|
||||||
use surf::StatusCode;
|
use surf::StatusCode;
|
||||||
|
|
||||||
|
use crate::music_storage::music_db::Song;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait MusicTracker {
|
pub trait MusicTracker {
|
||||||
/// Adds one listen to a song halfway through playback
|
/// Adds one listen to a song halfway through playback
|
||||||
async fn track_song(&mut self, song: &String) -> Result<(), TrackerError>;
|
async fn track_song(&mut self, song: Song) -> Result<(), TrackerError>;
|
||||||
|
|
||||||
/// Adds a 'listening' status to the music tracker service of choice
|
/// Adds a 'listening' status to the music tracker service of choice
|
||||||
async fn track_now(&mut self, song: &String) -> Result<(), TrackerError>;
|
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError>;
|
||||||
|
|
||||||
/// Reads config files, and attempts authentication with service
|
/// Reads config files, and attempts authentication with service
|
||||||
async fn test_tracker(&mut self) -> Result<(), TrackerError>;
|
async fn test_tracker(&mut self) -> Result<(), TrackerError>;
|
||||||
|
|
||||||
/// Returns plays for a given song according to tracker service
|
/// Returns plays for a given song according to tracker service
|
||||||
async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError>;
|
async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -52,13 +54,12 @@ impl TrackerError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct LastFMConfig {
|
pub struct LastFMConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub dango_api_key: String,
|
pub dango_api_key: String,
|
||||||
pub auth_token: Option<String>,
|
pub shared_secret: String,
|
||||||
pub shared_secret: Option<String>,
|
pub session_key: String,
|
||||||
pub session_key: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LastFM {
|
pub struct LastFM {
|
||||||
|
@ -67,15 +68,21 @@ pub struct LastFM {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MusicTracker for LastFM {
|
impl MusicTracker for LastFM {
|
||||||
async fn track_song(&mut self, song: &String) -> Result<(), TrackerError> {
|
async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
||||||
|
|
||||||
// Sets timestamp of song beginning play time
|
// Sets timestamp of song beginning play time
|
||||||
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
|
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30;
|
||||||
let string_timestamp = timestamp.to_string();
|
let string_timestamp = timestamp.to_string();
|
||||||
|
|
||||||
|
let (artist, track) = match (song.artist, song.title) {
|
||||||
|
(Some(artist), Some(track)) => (artist, track),
|
||||||
|
_ => return Err(TrackerError::InvalidSong)
|
||||||
|
};
|
||||||
|
|
||||||
params.insert("method", "track.scrobble");
|
params.insert("method", "track.scrobble");
|
||||||
params.insert("artist", "Kikuo");
|
params.insert("artist", &artist);
|
||||||
params.insert("track", "A Happy Death - Again");
|
params.insert("track", &track);
|
||||||
params.insert("timestamp", &string_timestamp);
|
params.insert("timestamp", &string_timestamp);
|
||||||
|
|
||||||
return match self.api_request(params).await {
|
return match self.api_request(params).await {
|
||||||
|
@ -84,11 +91,17 @@ impl MusicTracker for LastFM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn track_now(&mut self, song: &String) -> Result<(), TrackerError> {
|
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
let mut params: BTreeMap<&str, &str> = BTreeMap::new();
|
||||||
|
|
||||||
|
let (artist, track) = match (song.artist, song.title) {
|
||||||
|
(Some(artist), Some(track)) => (artist, track),
|
||||||
|
_ => return Err(TrackerError::InvalidSong)
|
||||||
|
};
|
||||||
|
|
||||||
params.insert("method", "track.updateNowPlaying");
|
params.insert("method", "track.updateNowPlaying");
|
||||||
params.insert("artist", "Kikuo");
|
params.insert("artist", &artist);
|
||||||
params.insert("track", "A Happy Death - Again");
|
params.insert("track", &track);
|
||||||
|
|
||||||
return match self.api_request(params).await {
|
return match self.api_request(params).await {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
@ -106,7 +119,7 @@ impl MusicTracker for LastFM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError> {
|
async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
|
||||||
todo!();
|
todo!();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,26 +142,21 @@ struct Session {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LastFM {
|
impl LastFM {
|
||||||
// Returns a url to be accessed by the user
|
/// Returns a url to be approved by the user along with the auth token
|
||||||
pub async fn get_auth_url(&mut self) -> Result<String, surf::Error> {
|
pub async fn get_auth(api_key: &String) -> Result<String, surf::Error> {
|
||||||
let method = String::from("auth.gettoken");
|
let method = String::from("auth.gettoken");
|
||||||
let api_key = self.config.dango_api_key.clone();
|
|
||||||
let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json");
|
let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json");
|
||||||
|
|
||||||
let auth_token: AuthToken = surf::get(api_request_url).await?.body_json().await?;
|
let auth_token: AuthToken = surf::get(api_request_url).await?.body_json().await?;
|
||||||
self.config.auth_token = Some(auth_token.token.clone());
|
|
||||||
|
|
||||||
let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
|
let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token);
|
||||||
|
|
||||||
return Ok(auth_url);
|
return Ok(auth_url);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_session(&mut self) {
|
/// Returns a LastFM session key
|
||||||
|
pub async fn get_session_key(api_key: &String, shared_secret: &String, auth_token: &String) -> Result<String, surf::Error> {
|
||||||
let method = String::from("auth.getSession");
|
let method = String::from("auth.getSession");
|
||||||
let api_key = self.config.dango_api_key.clone();
|
|
||||||
let auth_token = self.config.auth_token.clone().unwrap();
|
|
||||||
let shared_secret = self.config.shared_secret.clone().unwrap();
|
|
||||||
|
|
||||||
// Creates api_sig as defined in last.fm documentation
|
// Creates api_sig as defined in last.fm documentation
|
||||||
let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
|
let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}");
|
||||||
|
|
||||||
|
@ -160,14 +168,14 @@ impl LastFM {
|
||||||
|
|
||||||
let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&token={auth_token}&api_sig={hex_string_hash}&format=json");
|
let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&token={auth_token}&api_sig={hex_string_hash}&format=json");
|
||||||
|
|
||||||
let response = surf::get(api_request_url).recv_string().await.unwrap();
|
let response = surf::get(api_request_url).recv_string().await?;
|
||||||
|
|
||||||
// Sets session key from received response
|
// Sets session key from received response
|
||||||
let session_response: Session = serde_json::from_str(&response).unwrap();
|
let session_response: Session = serde_json::from_str(&response)?;
|
||||||
self.config.session_key = Some(session_response.session.key.clone());
|
return Ok(session_response.session.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new LastFM struct
|
/// Creates a new LastFM struct with a given config
|
||||||
pub fn new(config: &LastFMConfig) -> LastFM {
|
pub fn new(config: &LastFMConfig) -> LastFM {
|
||||||
let last_fm = LastFM {
|
let last_fm = LastFM {
|
||||||
config: config.clone()
|
config: config.clone()
|
||||||
|
@ -178,10 +186,10 @@ impl LastFM {
|
||||||
// Creates an api request with the given parameters
|
// Creates an api request with the given parameters
|
||||||
pub async fn api_request(&self, mut params: BTreeMap<&str, &str>) -> Result<surf::Response, surf::Error> {
|
pub async fn api_request(&self, mut params: BTreeMap<&str, &str>) -> Result<surf::Response, surf::Error> {
|
||||||
params.insert("api_key", &self.config.dango_api_key);
|
params.insert("api_key", &self.config.dango_api_key);
|
||||||
params.insert("sk", &self.config.session_key.as_ref().unwrap());
|
params.insert("sk", &self.config.session_key);
|
||||||
|
|
||||||
// Creates and sets api call signature
|
// Creates and sets api call signature
|
||||||
let api_sig = LastFM::request_sig(¶ms, &self.config.shared_secret.as_ref().unwrap());
|
let api_sig = LastFM::request_sig(¶ms, &self.config.shared_secret);
|
||||||
params.insert("api_sig", &api_sig);
|
params.insert("api_sig", &api_sig);
|
||||||
let mut string_params = String::from("");
|
let mut string_params = String::from("");
|
||||||
|
|
||||||
|
@ -226,7 +234,7 @@ impl LastFM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone)]
|
#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)]
|
||||||
pub struct DiscordRPCConfig {
|
pub struct DiscordRPCConfig {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub dango_client_id: u64,
|
pub dango_client_id: u64,
|
||||||
|
@ -250,7 +258,14 @@ impl DiscordRPC {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MusicTracker for DiscordRPC {
|
impl MusicTracker for DiscordRPC {
|
||||||
async fn track_now(&mut self, song: &String) -> Result<(), TrackerError> {
|
async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
|
// Sets song title
|
||||||
|
let song_name = if let Some(song_name) = song.title {
|
||||||
|
song_name
|
||||||
|
} else {
|
||||||
|
String::from("Unknown")
|
||||||
|
};
|
||||||
|
|
||||||
let _client_thread = self.client.start();
|
let _client_thread = self.client.start();
|
||||||
|
|
||||||
// Blocks thread execution until it has connected to local discord client
|
// Blocks thread execution until it has connected to local discord client
|
||||||
|
@ -263,7 +278,7 @@ impl MusicTracker for DiscordRPC {
|
||||||
// Sets discord account activity to current playing song
|
// Sets discord account activity to current playing song
|
||||||
let send_activity = self.client.set_activity(|activity| {
|
let send_activity = self.client.set_activity(|activity| {
|
||||||
activity
|
activity
|
||||||
.state(song)
|
.state(format!("Listening to: {}", song_name))
|
||||||
.assets(|assets| assets.large_image(&self.config.dango_icon))
|
.assets(|assets| assets.large_image(&self.config.dango_icon))
|
||||||
.timestamps(|time| time.start(start_time))
|
.timestamps(|time| time.start(start_time))
|
||||||
});
|
});
|
||||||
|
@ -274,7 +289,7 @@ impl MusicTracker for DiscordRPC {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn track_song(&mut self, song: &String) -> Result<(), TrackerError> {
|
async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,7 +297,7 @@ impl MusicTracker for DiscordRPC {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_times_tracked(&mut self, song: &String) -> Result<u32, TrackerError> {
|
async fn get_times_tracked(&mut self, song: &Song) -> Result<u32, TrackerError> {
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue