Event handling refactor and Rich Presence implementation
This commit is contained in:
parent
b04ce61aba
commit
d349e2ac16
9 changed files with 513 additions and 149 deletions
|
@ -9,6 +9,7 @@ crate-type = ["cdylib"]
|
|||
|
||||
[dependencies]
|
||||
mpv-client = "0.4.1"
|
||||
discord-rich-presence="0.2.3"
|
||||
|
||||
[profile.release-full]
|
||||
inherits = "release"
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
use std::rc::Rc;
|
||||
use crate::mpv_event_handler::events::{MpvEvent, Listener, FileInfo};
|
||||
use crate::logging::{self, Logger};
|
||||
|
||||
pub struct MpvListener {
|
||||
logger: Rc<Logger>
|
||||
}
|
||||
|
||||
impl MpvListener {
|
||||
pub fn new(logger: Rc<Logger>) -> Self {
|
||||
Self {
|
||||
logger
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn print_file_info(&self, file_info: FileInfo) -> Result<(), &'static str> {
|
||||
let FileInfo {filename, metadata} = file_info;
|
||||
|
||||
logging::info!(self.logger, "FILENAME {}", filename);
|
||||
|
||||
if let Some(artist) = metadata.artist {
|
||||
logging::info!(self.logger, "ARTIST: {artist}");
|
||||
}
|
||||
|
||||
if let Some(album) = metadata.album {
|
||||
logging::info!(self.logger, "ALBUM: {album}");
|
||||
}
|
||||
|
||||
if let Some(title) = metadata.title {
|
||||
logging::info!(self.logger, "TITLE: {title}");
|
||||
}
|
||||
|
||||
if let Some(track) = metadata.track {
|
||||
logging::info!(self.logger, "TRACK: {track}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_seek_time(&self, time: i64) -> Result<(), &'static str>{
|
||||
logging::info!(self.logger, "SEEKING: {time}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_play(&self) -> Result<(), &'static str> {
|
||||
logging::info!(self.logger, "PLAY");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_pause(&self) -> Result<(), &'static str> {
|
||||
logging::info!(self.logger, "PAUSE");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Listener for MpvListener {
|
||||
fn handle_event(&self, event: MpvEvent) -> Result<(), &'static str>{
|
||||
match event {
|
||||
MpvEvent::FileLoaded(file_info) => self.print_file_info(file_info),
|
||||
MpvEvent::Seek(time) => self.print_seek_time(time),
|
||||
MpvEvent::Pause => self.print_pause(),
|
||||
MpvEvent::Play => self.print_play(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,181 @@
|
|||
use std::rc::Rc;
|
||||
use std::time::SystemTime;
|
||||
use crate::logging::{self, Logger};
|
||||
use crate::mpv_event_queue::events::{MpvEventHandler, MpvEvent, FileInfo};
|
||||
use discord_rich_presence::{DiscordIpcClient, DiscordIpc};
|
||||
use discord_rich_presence::activity::{Activity, Assets, Timestamps};
|
||||
|
||||
struct ActivityInfo {
|
||||
details: String,
|
||||
state: String,
|
||||
timestamps: Timestamps
|
||||
}
|
||||
|
||||
impl ActivityInfo {
|
||||
pub fn new(details: String, state: String, timestamps: Timestamps) -> Self {
|
||||
Self {
|
||||
details,
|
||||
state,
|
||||
timestamps
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
details: String::new(),
|
||||
state: String::new(),
|
||||
timestamps: Timestamps::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_activity(&self) -> Activity {
|
||||
let assets = Assets::new()
|
||||
.large_image("logo")
|
||||
.large_text("mpv");
|
||||
|
||||
Activity::new()
|
||||
.assets(assets)
|
||||
.details(&self.details)
|
||||
.state(&self.state)
|
||||
.timestamps(self.timestamps.clone())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct DiscordClient {
|
||||
logger: Rc<Logger>,
|
||||
discord: DiscordIpcClient,
|
||||
activity_info: ActivityInfo,
|
||||
active: bool
|
||||
}
|
||||
|
||||
impl DiscordClient {
|
||||
pub fn new(client_id: &str, logger: Rc<Logger>) -> Result<Self, &'static str> {
|
||||
let discord = match DiscordIpcClient::new(client_id) {
|
||||
Ok(discord) => discord,
|
||||
Err(_) => return Err("cannot init discord client")
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
logger,
|
||||
discord,
|
||||
activity_info: ActivityInfo::empty(),
|
||||
active: false
|
||||
})
|
||||
}
|
||||
|
||||
fn get_state(file_info: &FileInfo) -> String {
|
||||
let metadata = &file_info.metadata;
|
||||
let mut state = String::new();
|
||||
|
||||
if let Some(artist) = &metadata.artist {
|
||||
state += &format!("by {artist}");
|
||||
}
|
||||
|
||||
if let Some(album) = &metadata.album {
|
||||
state += &format!(" on {album}");
|
||||
}
|
||||
state
|
||||
}
|
||||
|
||||
fn get_details(file_info: &FileInfo) -> String {
|
||||
let metadata = &file_info.metadata;
|
||||
let title = match &metadata.title {
|
||||
Some(title) => title,
|
||||
None => return file_info.filename.clone()
|
||||
};
|
||||
|
||||
if let Some(track) = &metadata.track {
|
||||
format!("{title} [T{track}] ")
|
||||
}
|
||||
else {
|
||||
title.clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn update_presence(&mut self) -> Result<(), &'static str> {
|
||||
if !self.active {
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
logging::info!(self.logger, "Updating rich presence");
|
||||
|
||||
match self.discord.set_activity(self.activity_info.get_activity()) {
|
||||
Ok(()) => {
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err("cannot set presence")
|
||||
}
|
||||
}
|
||||
|
||||
fn set_presence(&mut self, file_info: FileInfo) -> Result<(), &'static str> {
|
||||
let details = DiscordClient::get_details(&file_info);
|
||||
let state = DiscordClient::get_state(&file_info);
|
||||
self.activity_info = ActivityInfo::new(details, state, Timestamps::new());
|
||||
|
||||
self.update_presence()
|
||||
}
|
||||
|
||||
fn update_timestamps(&mut self, remaining_time: i64) -> Result<(), &'static str> {
|
||||
let current_time = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH);
|
||||
let current_time = match current_time {
|
||||
Ok(time) => time.as_secs() as i64,
|
||||
Err(_) => return Err("cannot get current system time")
|
||||
};
|
||||
|
||||
let predicted_time = current_time + remaining_time;
|
||||
|
||||
self.activity_info.timestamps = Timestamps::new().end(predicted_time);
|
||||
self.update_presence()
|
||||
}
|
||||
|
||||
fn open(&mut self) -> Result<(), &'static str> {
|
||||
if self.active {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
logging::info!(self.logger, "Opening discord client");
|
||||
match self.discord.connect() {
|
||||
Ok(()) => {
|
||||
self.active = true;
|
||||
self.update_presence()
|
||||
}
|
||||
Err(_) => Err("cannot connect to Discord")
|
||||
}
|
||||
}
|
||||
|
||||
fn close(&mut self) -> Result<(), &'static str> {
|
||||
if !self.active {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
logging::info!(self.logger, "Closing discord client");
|
||||
match self.discord.close() {
|
||||
Ok(()) => {
|
||||
self.active = false;
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err("cannot disconnect from Discord")
|
||||
}
|
||||
}
|
||||
|
||||
fn toggle_activity(&mut self) -> Result<(), &'static str> {
|
||||
match self.active {
|
||||
false => self.open(),
|
||||
true => self.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MpvEventHandler for DiscordClient {
|
||||
fn handle_event(&mut self, event: MpvEvent) -> Result<(), &'static str> {
|
||||
match event {
|
||||
MpvEvent::FileLoaded(file_info) => self.set_presence(file_info),
|
||||
MpvEvent::Seek(remaining_time) => self.update_timestamps(remaining_time),
|
||||
MpvEvent::Toggle => self.toggle_activity(),
|
||||
MpvEvent::Exit => self.close(),
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
}
|
26
src/lib.rs
26
src/lib.rs
|
@ -1,25 +1,21 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use mpv_client::mpv_handle;
|
||||
|
||||
mod mpv_event_handler;
|
||||
mod logging;
|
||||
mod basic_listener; // For testing purposes
|
||||
mod mpv_event_queue;
|
||||
mod discord_client;
|
||||
mod plugin;
|
||||
|
||||
use plugin::RPCPlugin;
|
||||
|
||||
const DISCORD_APPID: &str = "1071519995588264016";
|
||||
|
||||
#[no_mangle]
|
||||
fn mpv_open_cplugin(handle: *mut mpv_handle) -> std::os::raw::c_int {
|
||||
let logger = Rc::new(logging::Logger::from_env());
|
||||
|
||||
let listener = Box::new(basic_listener::MpvListener::new(Rc::clone(&logger)));
|
||||
let result = mpv_event_handler::MpvEventHandler::from_ptr(handle, listener, Rc::clone(&logger));
|
||||
let client = match result {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
logging::error!(logger, "Error initializing event_handling: {e}");
|
||||
return -1;
|
||||
}
|
||||
let plugin = match RPCPlugin::new(handle, DISCORD_APPID) {
|
||||
Ok(plugin) => plugin,
|
||||
Err(_) => return -1
|
||||
};
|
||||
|
||||
client.run();
|
||||
plugin.run();
|
||||
return 0;
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
use std::rc::Rc;
|
||||
|
||||
use mpv_client::{Handle, Event, Property, Format, mpv_handle};
|
||||
use crate::logging::{self, Logger};
|
||||
use std::{rc::Rc, cell::{RefCell, Cell}, borrow::Borrow};
|
||||
use crate::{logging::{self, Logger}, info};
|
||||
use mpv_client::{Handle, Event, Property, Format, mpv_handle, ClientMessage};
|
||||
|
||||
pub mod events;
|
||||
use events::{MpvEvent, Listener, FileInfo, FileMetadata};
|
||||
|
@ -10,6 +9,7 @@ const NAME_PAUSE_PROP: &str = "pause";
|
|||
const REPL_PAUSE_PROP: u64 = 1;
|
||||
|
||||
pub struct MpvEventHandler {
|
||||
listening: bool,
|
||||
mpv: Handle,
|
||||
listener: Box<dyn Listener>,
|
||||
logger: Rc<Logger>
|
||||
|
@ -17,7 +17,11 @@ pub struct MpvEventHandler {
|
|||
|
||||
impl MpvEventHandler {
|
||||
pub fn new(mpv: Handle, listener: Box<dyn Listener>, logger: Rc<Logger>) -> Result<Self, String> {
|
||||
let mpv = Cell::new(mpv);
|
||||
let listener = Cell::new(listener);
|
||||
|
||||
let new_self = Self {
|
||||
listening: false,
|
||||
mpv,
|
||||
listener,
|
||||
logger
|
||||
|
@ -31,8 +35,21 @@ impl MpvEventHandler {
|
|||
MpvEventHandler::new(Handle::from_ptr(handle), listener, logger)
|
||||
}
|
||||
|
||||
pub fn run(self) {
|
||||
logging::info!(self.logger, "Starting mpv-rpc");
|
||||
logging::info!(self.logger, "Client name: {}", self.mpv.borrow().as_ptr().cl);
|
||||
|
||||
while self.poll_events() {
|
||||
// Wait for shutdown
|
||||
}
|
||||
|
||||
if self.listening {
|
||||
self.listener.get_mut().close();
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(&self) -> Result<(), String> {
|
||||
self.observe_property(REPL_PAUSE_PROP, NAME_PAUSE_PROP, bool::MPV_FORMAT)
|
||||
self.observe_property(REPL_PAUSE_PROP, NAME_PAUSE_PROP, bool::MPV_FORMAT)
|
||||
}
|
||||
|
||||
fn observe_property(&self, id: u64, name: &str, format: i32) -> Result<(), String>{
|
||||
|
@ -42,21 +59,57 @@ impl MpvEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_event(&self, event: Event) -> Result<(), &'static str>{
|
||||
fn get_event(&self) -> Event {
|
||||
self.mpv.wait_event(0.0)
|
||||
}
|
||||
|
||||
fn poll_events(&self) -> bool {
|
||||
let event = self.mpv.wait_event(0.0);
|
||||
let result = match event {
|
||||
Event::None => Ok(()),
|
||||
Event::Shutdown => return false,
|
||||
event => self.handle_event(event)
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
logging::error!(self.logger, "Error handling event: {e}");
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn handle_event(&self, event: Event) -> Result<(), &'static str> {
|
||||
logging::info!(self.logger, "Event: {event}");
|
||||
|
||||
if let Event::ClientMessage(message) = event {
|
||||
return self.handle_client_message(message);
|
||||
}
|
||||
|
||||
if self.listening {
|
||||
match event {
|
||||
Event::PropertyChange(prop_id, prop) => self.send_property_to_listener(prop_id, prop),
|
||||
e => self.send_event_to_listener(e)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn send_event_to_listener(&self, event: Event) -> Result<(), &'static str>{
|
||||
match event {
|
||||
Event::FileLoaded => self.handle_file_loaded(),
|
||||
Event::PlaybackRestart => self.handle_playback_restart(),
|
||||
Event::FileLoaded => self.send_file_info(),
|
||||
Event::PlaybackRestart => self.send_seek(),
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn on_property_change(&self, prop_id: u64, prop: Property) -> Result<(), &'static str> {
|
||||
fn send_property_to_listener(&self, prop_id: u64, prop: Property) -> Result<(), &'static str> {
|
||||
logging::info!(self.logger, "Property changed: {prop_id}");
|
||||
match prop_id {
|
||||
REPL_PAUSE_PROP => {
|
||||
match prop.data() {
|
||||
Some(pause) => self.handle_pause_change(pause),
|
||||
Some(pause) => self.send_pause(pause),
|
||||
None => Err("property pause doesn't exist")
|
||||
}
|
||||
}
|
||||
|
@ -64,12 +117,12 @@ impl MpvEventHandler {
|
|||
}
|
||||
}
|
||||
|
||||
fn handle_file_loaded(&self) -> Result<(), &'static str> {
|
||||
fn send_file_info(&self) -> Result<(), &'static str> {
|
||||
let filename_res = self.mpv.get_property("filename");
|
||||
let artist = self.mpv.get_property("metadata/by-key/artist").ok();
|
||||
let album = self.mpv.get_property("metadata/by-key/album").ok();
|
||||
let title = self.mpv.get_property("metadata/by-key/title").ok();
|
||||
let track = self.mpv.get_property("metadata/by-key/Artist").ok();
|
||||
let track = self.mpv.get_property("metadata/by-key/track").ok();
|
||||
|
||||
let filename = match filename_res {
|
||||
Ok(name) => name,
|
||||
|
@ -89,53 +142,55 @@ impl MpvEventHandler {
|
|||
};
|
||||
|
||||
let event = MpvEvent::FileLoaded(file_info);
|
||||
self.listener.handle_event(event)
|
||||
self.listener.get_mut().handle_event(event)
|
||||
}
|
||||
|
||||
fn handle_playback_restart(&self) -> Result<(), &'static str>{
|
||||
let res = self.mpv.get_property("playback-time").ok();
|
||||
match res {
|
||||
Some(time) => {
|
||||
let event = MpvEvent::Seek(time);
|
||||
self.listener.handle_event(event)
|
||||
}
|
||||
None => {
|
||||
logging::warning!(self.logger, "Failed retrieving playback-time.");
|
||||
logging::warning!(self.logger, "This usually happens seeking into file end. Possibly mpv bug?");
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
|
||||
fn send_seek(&self) -> Result<(), &'static str>{
|
||||
let remaining_time = self.mpv.get_property("time-remaining").unwrap_or_else(|_| {
|
||||
logging::warning!(self.logger, "Failed retrieving remaing-time.");
|
||||
logging::warning!(self.logger, "This usually happens seeking into file end. Possibly mpv bug?");
|
||||
logging::warning!(self.logger, "Defaulting to 0.");
|
||||
0
|
||||
});
|
||||
|
||||
let event = MpvEvent::Seek(remaining_time);
|
||||
self.listener.get_mut().handle_event(event)
|
||||
}
|
||||
|
||||
fn handle_pause_change(&self, pause: bool) -> Result<(), &'static str> {
|
||||
fn send_pause(&self, pause: bool) -> Result<(), &'static str> {
|
||||
let event = match pause {
|
||||
false => MpvEvent::Play,
|
||||
true => MpvEvent::Pause
|
||||
};
|
||||
self.listener.handle_event(event)
|
||||
self.listener.get_mut().handle_event(event)
|
||||
}
|
||||
|
||||
fn toggle_listening(&self) -> Result<(), &'static str> {
|
||||
let new_state = !self.listening;
|
||||
let mut listener_ref = self.listener.get_mut();
|
||||
|
||||
pub fn run(&self) {
|
||||
while self.poll_events() {
|
||||
// Wait for shutdown
|
||||
}
|
||||
}
|
||||
|
||||
// Refactor to accept warnings as well
|
||||
pub fn poll_events(&self) -> bool {
|
||||
let event = self.mpv.wait_event(0.0);
|
||||
let event_name = event.to_string();
|
||||
let result = match event {
|
||||
Event::None => Ok(()),
|
||||
Event::Shutdown => return false,
|
||||
Event::PropertyChange(prop_id, prop) => self.on_property_change(prop_id, prop),
|
||||
e => self.handle_event(e)
|
||||
let res = match new_state {
|
||||
true => listener_ref.open(),
|
||||
false => listener_ref.close()
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
logging::error!(self.logger, "Error handling event {event_name}: {e}");
|
||||
match res {
|
||||
Ok(()) => self.listening = true,
|
||||
Err(e) => return Err(e)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_client_message(&self, message: ClientMessage) -> Result<(), &'static str> {
|
||||
let command = message.args().join(" ");
|
||||
logging::info!(self.logger, "Client message: {command}");
|
||||
|
||||
if command.starts_with("key-binding toggle-rpc d-") {
|
||||
self.toggle_listening()
|
||||
}
|
||||
else {
|
||||
Ok(())
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
pub struct FileInfo {
|
||||
pub filename: String,
|
||||
pub metadata: FileMetadata
|
||||
}
|
||||
|
||||
pub struct FileMetadata {
|
||||
pub artist: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub track: Option<String>
|
||||
}
|
||||
|
||||
pub enum MpvEvent {
|
||||
FileLoaded(FileInfo),
|
||||
Seek(i64),
|
||||
Pause,
|
||||
Play
|
||||
}
|
||||
|
||||
pub trait Listener {
|
||||
fn handle_event(&self, event: MpvEvent) -> Result<(), &'static str>;
|
||||
}
|
124
src/mpv_event_queue.rs
Normal file
124
src/mpv_event_queue.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use std::rc::Rc;
|
||||
use mpv_client::{Handle, Event, Property, Format, mpv_handle, ClientMessage};
|
||||
use crate::logging::{self, Logger};
|
||||
|
||||
pub mod events;
|
||||
use events::{MpvEvent, FileInfo, FileMetadata};
|
||||
|
||||
|
||||
const NAME_PAUSE_PROP: &str = "pause";
|
||||
const REPL_PAUSE_PROP: u64 = 1;
|
||||
|
||||
pub struct MpvEventQueue {
|
||||
mpv: Handle,
|
||||
logger: Rc<Logger>,
|
||||
}
|
||||
|
||||
impl MpvEventQueue {
|
||||
pub fn new(mpv: Handle,logger: Rc<Logger>) -> Result<Self, &'static str> {
|
||||
let new_self = Self {
|
||||
mpv,
|
||||
logger,
|
||||
};
|
||||
|
||||
new_self.initialize()?;
|
||||
Ok(new_self)
|
||||
}
|
||||
|
||||
pub fn from_ptr<'a>(handle: *mut mpv_handle, logger: Rc<Logger>) -> Result<Self, &'static str> {
|
||||
MpvEventQueue::new(Handle::from_ptr(handle), logger)
|
||||
}
|
||||
|
||||
fn initialize(&self) -> Result<(), &'static str> {
|
||||
self.observe_property(REPL_PAUSE_PROP, NAME_PAUSE_PROP, bool::MPV_FORMAT)
|
||||
}
|
||||
|
||||
fn observe_property(&self, id: u64, name: &str, format: i32) -> Result<(), &'static str> {
|
||||
match self.mpv.observe_property(id, name, format) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_) => Err("cannot observe property")
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next_event(&self) -> Option<MpvEvent> {
|
||||
let event = self.mpv.wait_event(0.0);
|
||||
let mpv_event = self.convert_event(event);
|
||||
|
||||
if let Some(ref event) = mpv_event {
|
||||
logging::info!(self.logger, "Event: {event}");
|
||||
}
|
||||
|
||||
mpv_event
|
||||
}
|
||||
|
||||
fn convert_event(&self, event: Event) -> Option<MpvEvent> {
|
||||
match event {
|
||||
Event::FileLoaded => self.get_file_info_event(),
|
||||
Event::PlaybackRestart => self.get_seek_event(),
|
||||
Event::ClientMessage(message) => self.get_toggle_event(message),
|
||||
Event::PropertyChange(prop_id, prop) => self.get_property_event(prop_id, prop),
|
||||
Event::Shutdown => Some(MpvEvent::Exit),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn get_file_info_event(&self) -> Option<MpvEvent> {
|
||||
let filename = self.mpv.get_property("filename").unwrap();
|
||||
let artist = self.mpv.get_property("metadata/by-key/artist").ok();
|
||||
let album = self.mpv.get_property("metadata/by-key/album").ok();
|
||||
let title = self.mpv.get_property("metadata/by-key/title").ok();
|
||||
let track = self.mpv.get_property("metadata/by-key/track").ok();
|
||||
|
||||
let metadata = FileMetadata {
|
||||
artist,
|
||||
album,
|
||||
title,
|
||||
track
|
||||
};
|
||||
|
||||
let file_info = FileInfo {
|
||||
filename,
|
||||
metadata
|
||||
};
|
||||
|
||||
Some(MpvEvent::FileLoaded(file_info))
|
||||
}
|
||||
|
||||
fn get_property_event(&self, prop_id: u64, prop: Property) -> Option<MpvEvent> {
|
||||
logging::info!(self.logger, "Property changed: {prop_id}");
|
||||
match prop_id {
|
||||
1 => Some(self.convert_pause_prop(prop.data().unwrap())),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_pause_prop(&self, pause: bool) -> MpvEvent {
|
||||
match pause {
|
||||
false => MpvEvent::Play,
|
||||
true => MpvEvent::Pause
|
||||
}
|
||||
}
|
||||
|
||||
fn get_seek_event(&self) -> Option<MpvEvent> {
|
||||
let remaining_time = self.mpv.get_property("time-remaining").unwrap_or_else(|_| {
|
||||
logging::warning!(self.logger, "Failed retrieving remaing-time.");
|
||||
logging::warning!(self.logger, "This usually happens seeking into file end. Possibly mpv bug?");
|
||||
logging::warning!(self.logger, "Defaulting to 0.");
|
||||
0
|
||||
});
|
||||
|
||||
Some(MpvEvent::Seek(remaining_time))
|
||||
}
|
||||
|
||||
fn get_toggle_event(&self, message: ClientMessage) -> Option<MpvEvent> {
|
||||
let command = message.args().join(" ");
|
||||
logging::info!(self.logger, "Client message: {command}");
|
||||
|
||||
if command.starts_with("key-binding toggle-rpc d-") {
|
||||
return Some(MpvEvent::Toggle)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
41
src/mpv_event_queue/events.rs
Normal file
41
src/mpv_event_queue/events.rs
Normal file
|
@ -0,0 +1,41 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
pub struct FileInfo {
|
||||
pub filename: String,
|
||||
pub metadata: FileMetadata
|
||||
}
|
||||
|
||||
pub struct FileMetadata {
|
||||
pub artist: Option<String>,
|
||||
pub album: Option<String>,
|
||||
pub title: Option<String>,
|
||||
pub track: Option<String>
|
||||
}
|
||||
|
||||
pub enum MpvEvent {
|
||||
Toggle,
|
||||
Play,
|
||||
Pause,
|
||||
Exit,
|
||||
FileLoaded(FileInfo),
|
||||
Seek(i64)
|
||||
}
|
||||
|
||||
impl Display for MpvEvent {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let event_name = match self {
|
||||
MpvEvent::Toggle => "Toggle",
|
||||
MpvEvent::Play => "Play",
|
||||
MpvEvent::Pause => "Pause",
|
||||
MpvEvent::Exit => "Exit",
|
||||
MpvEvent::FileLoaded(_) => "FileLoaded",
|
||||
MpvEvent::Seek(_) => "Seek"
|
||||
};
|
||||
write!(f, "{}", event_name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait MpvEventHandler {
|
||||
fn handle_event(&mut self, event: MpvEvent) -> Result<(), &'static str>;
|
||||
}
|
54
src/plugin.rs
Normal file
54
src/plugin.rs
Normal file
|
@ -0,0 +1,54 @@
|
|||
use std::rc::Rc;
|
||||
use mpv_client::mpv_handle;
|
||||
use crate::logging::{self, Logger};
|
||||
use crate::discord_client::DiscordClient;
|
||||
use crate::mpv_event_queue::MpvEventQueue;
|
||||
use crate::mpv_event_queue::events::{MpvEventHandler, MpvEvent};
|
||||
|
||||
pub struct RPCPlugin {
|
||||
logger: Rc<Logger>,
|
||||
mpv: MpvEventQueue,
|
||||
discord: DiscordClient,
|
||||
}
|
||||
|
||||
impl RPCPlugin {
|
||||
pub fn new(handle: *mut mpv_handle, client_id: &str) -> Result<Self, &'static str> {
|
||||
let logger = Rc::new(Logger::from_env());
|
||||
let mpv = MpvEventQueue::from_ptr(handle, Rc::clone(&logger))?;
|
||||
let discord = DiscordClient::new(client_id, Rc::clone(&logger))?;
|
||||
|
||||
Ok(Self {
|
||||
logger,
|
||||
mpv,
|
||||
discord
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(mut self) {
|
||||
loop {
|
||||
let event = self.mpv.next_event();
|
||||
match event {
|
||||
None => continue,
|
||||
Some(event) => {
|
||||
if self.handle_event(event)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: MpvEvent) -> bool {
|
||||
let exit = match event {
|
||||
MpvEvent::Exit => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
if let Err(e) = self.discord.handle_event(event) {
|
||||
logging::error!(self.logger, "Failed to handle event: {e}");
|
||||
}
|
||||
|
||||
exit
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue