Added cover art, buffering, config, string truncating
This commit is contained in:
parent
0cce7cbcd9
commit
b87a0cf5d9
10 changed files with 320 additions and 64 deletions
|
@ -10,6 +10,9 @@ crate-type = ["cdylib"]
|
|||
[dependencies]
|
||||
mpv-client = "0.4.1"
|
||||
discord-rich-presence="0.2.3"
|
||||
serde = { version = "1.0.152", features = ["derive"] }
|
||||
serde_json = "1.0.92"
|
||||
musicbrainz_rs = { version = "0.5.0", default-features = false, features = ["blocking"] }
|
||||
|
||||
[profile.release-full]
|
||||
inherits = "release"
|
||||
|
|
76
src/config.rs
Normal file
76
src/config.rs
Normal file
|
@ -0,0 +1,76 @@
|
|||
use std::env;
|
||||
use std::fs;
|
||||
use serde::{self, Serialize, Deserialize};
|
||||
use crate::logging::{self, Logger};
|
||||
|
||||
enum ConfigError {
|
||||
CannotLoad,
|
||||
ParseError(serde_json::Error)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Config {
|
||||
#[serde(default = "active_default")]
|
||||
pub active: bool,
|
||||
|
||||
#[serde(default = "cover_art_default")]
|
||||
pub cover_art: bool
|
||||
}
|
||||
|
||||
const fn active_default() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
const fn cover_art_default() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn default() -> Self {
|
||||
Self {
|
||||
active: active_default(),
|
||||
cover_art: cover_art_default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_config_file(logger: &Logger) -> Self {
|
||||
let path = Config::get_config_path();
|
||||
logging::info!(logger, "Config path {path}");
|
||||
|
||||
match Config::parse_config_from_file(&path) {
|
||||
Ok(config) => config,
|
||||
Err(ConfigError::CannotLoad) => {
|
||||
logging::info!(logger, "Cannot load config. Using default options");
|
||||
Config::default()
|
||||
}
|
||||
Err(ConfigError::ParseError(e)) => {
|
||||
logging::error!(logger, "Cannot parse config: {e}");
|
||||
Config::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_config_from_file(path: &str) -> Result<Self, ConfigError> {
|
||||
match fs::read_to_string(path) {
|
||||
Ok(json) => Config::parse_config(&json),
|
||||
Err(_) => Err(ConfigError::CannotLoad)
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_config(json: &str) -> Result<Self, ConfigError>{
|
||||
match serde_json::from_str(json) {
|
||||
Ok(config) => Ok(config),
|
||||
Err(e) => Err(ConfigError::ParseError(e))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config_path() -> String {
|
||||
let mpv_home = env::var("MPV_HOME").or(
|
||||
env::var("HOME").and_then(|home| Ok(home + "/.config/mpv/")).or(
|
||||
env::var("XDG_CONFIG_HOME").and_then(|home| Ok(home + "/.mpv/"))
|
||||
)
|
||||
).unwrap_or("/etc/mpv/".to_string());
|
||||
|
||||
return mpv_home + "rpc.json"
|
||||
}
|
||||
}
|
|
@ -1,21 +1,56 @@
|
|||
use std::rc::Rc;
|
||||
use std::time::SystemTime;
|
||||
use crate::logging::{self, Logger};
|
||||
use crate::mpv_event_queue::events::{MpvEventHandler, MpvEvent, FileInfo};
|
||||
use std::collections::VecDeque;
|
||||
use discord_rich_presence::{DiscordIpcClient, DiscordIpc};
|
||||
use discord_rich_presence::activity::{Activity, Assets, Timestamps};
|
||||
use crate::utils;
|
||||
use crate::logging::{self, Logger};
|
||||
use crate::mpv_event_queue::events::{MpvEventHandler, MpvEvent, FileInfo, MpvRequester, MpvRequest, FileMetadata};
|
||||
|
||||
const MAX_STR_LEN: usize = 128;
|
||||
|
||||
mod music_brainz;
|
||||
|
||||
struct ActivityInfo {
|
||||
details: String,
|
||||
state: String,
|
||||
assets: AssetsInfo,
|
||||
timestamps: Timestamps
|
||||
}
|
||||
|
||||
impl AssetsInfo {
|
||||
pub fn new(large_image: String, large_text: String) -> Self {
|
||||
Self {
|
||||
large_image,
|
||||
large_text
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Self {
|
||||
large_image: String::new(),
|
||||
large_text: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_assets(&self) -> Assets {
|
||||
Assets::new()
|
||||
.large_image(&self.large_image)
|
||||
.large_text(&self.large_text)
|
||||
}
|
||||
}
|
||||
|
||||
struct AssetsInfo {
|
||||
large_image: String,
|
||||
large_text: String
|
||||
}
|
||||
|
||||
impl ActivityInfo {
|
||||
pub fn new(details: String, state: String, timestamps: Timestamps) -> Self {
|
||||
pub fn new(details: String, state: String, assets: AssetsInfo, timestamps: Timestamps) -> Self {
|
||||
Self {
|
||||
details,
|
||||
state,
|
||||
assets,
|
||||
timestamps
|
||||
}
|
||||
}
|
||||
|
@ -24,15 +59,14 @@ impl ActivityInfo {
|
|||
Self {
|
||||
details: String::new(),
|
||||
state: String::new(),
|
||||
assets: AssetsInfo::empty(),
|
||||
timestamps: Timestamps::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_activity(&self) -> Activity {
|
||||
let assets = Assets::new()
|
||||
.large_image("logo")
|
||||
.large_text("mpv");
|
||||
let assets = self.assets.get_assets();
|
||||
|
||||
Activity::new()
|
||||
.assets(assets)
|
||||
|
@ -43,26 +77,36 @@ impl ActivityInfo {
|
|||
}
|
||||
|
||||
|
||||
|
||||
pub struct DiscordClient {
|
||||
logger: Rc<Logger>,
|
||||
discord: DiscordIpcClient,
|
||||
activity_info: ActivityInfo,
|
||||
active: bool
|
||||
active: bool,
|
||||
cover_art: bool,
|
||||
mpv_requests: VecDeque<MpvRequest>,
|
||||
logger: Rc<Logger>
|
||||
}
|
||||
|
||||
impl DiscordClient {
|
||||
pub fn new(client_id: &str, logger: Rc<Logger>) -> Result<Self, &'static str> {
|
||||
pub fn new(client_id: &str, active: bool, cover_art: bool, 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,
|
||||
let mut new_self = Self {
|
||||
discord,
|
||||
activity_info: ActivityInfo::empty(),
|
||||
active: false
|
||||
})
|
||||
active: false,
|
||||
cover_art,
|
||||
mpv_requests: VecDeque::new(),
|
||||
logger
|
||||
};
|
||||
|
||||
if active {
|
||||
new_self.open()?;
|
||||
}
|
||||
Ok(new_self)
|
||||
}
|
||||
|
||||
fn get_state(file_info: &FileInfo) -> String {
|
||||
|
@ -76,6 +120,7 @@ impl DiscordClient {
|
|||
if let Some(album) = &metadata.album {
|
||||
state += &format!(" on {album}");
|
||||
}
|
||||
utils::truncate_string_fmt(&mut state, MAX_STR_LEN);
|
||||
state
|
||||
}
|
||||
|
||||
|
@ -86,12 +131,38 @@ impl DiscordClient {
|
|||
None => return file_info.filename.clone()
|
||||
};
|
||||
|
||||
if let Some(track) = &metadata.track {
|
||||
let mut details = if let Some(track) = &metadata.track {
|
||||
format!("{title} [T{track}] ")
|
||||
}
|
||||
else {
|
||||
title.clone()
|
||||
};
|
||||
|
||||
utils::truncate_string_fmt(&mut details, MAX_STR_LEN);
|
||||
details
|
||||
}
|
||||
|
||||
fn get_assets_info(cover_art: bool, metadata: FileMetadata) -> AssetsInfo {
|
||||
let (large_image, large_text) = DiscordClient::get_large_info(cover_art, metadata);
|
||||
AssetsInfo::new(large_image, large_text)
|
||||
}
|
||||
|
||||
fn get_large_info(cover_art: bool, metadata: FileMetadata) -> (String, String) {
|
||||
if !cover_art {
|
||||
return ("logo".to_string(), "mpv".to_string())
|
||||
}
|
||||
|
||||
let cover_art_url = music_brainz::get_cover_art_url(&metadata.title, &metadata.album, &metadata.artist);
|
||||
let large_image = match cover_art_url {
|
||||
Some(url) => url,
|
||||
None => "logo".to_string()
|
||||
};
|
||||
let large_text = match metadata.title.or(metadata.album) {
|
||||
Some(text) => text,
|
||||
None => "mpv".to_string()
|
||||
};
|
||||
|
||||
(large_image, large_text)
|
||||
}
|
||||
|
||||
fn update_presence(&mut self) -> Result<(), &'static str> {
|
||||
|
@ -105,19 +176,23 @@ impl DiscordClient {
|
|||
Ok(()) => {
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err("cannot set presence")
|
||||
Err(_) => {
|
||||
self.active = false;
|
||||
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());
|
||||
let assets_info = DiscordClient::get_assets_info(self.cover_art, file_info.metadata);
|
||||
|
||||
self.activity_info = ActivityInfo::new(details, state, assets_info, Timestamps::new());
|
||||
self.update_presence()
|
||||
}
|
||||
|
||||
fn update_timestamps(&mut self, remaining_time: i64) -> Result<(), &'static str> {
|
||||
fn set_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,
|
||||
|
@ -130,6 +205,11 @@ impl DiscordClient {
|
|||
self.update_presence()
|
||||
}
|
||||
|
||||
fn clear_timestamps(&mut self) -> Result<(), &'static str> {
|
||||
self.activity_info.timestamps = Timestamps::new();
|
||||
self.update_presence()
|
||||
}
|
||||
|
||||
fn open(&mut self) -> Result<(), &'static str> {
|
||||
if self.active {
|
||||
return Ok(());
|
||||
|
@ -139,6 +219,7 @@ impl DiscordClient {
|
|||
match self.discord.connect() {
|
||||
Ok(()) => {
|
||||
self.active = true;
|
||||
self.request_osd_message("Discord RPC started");
|
||||
self.update_presence()
|
||||
}
|
||||
Err(_) => Err("cannot connect to Discord")
|
||||
|
@ -154,6 +235,7 @@ impl DiscordClient {
|
|||
match self.discord.close() {
|
||||
Ok(()) => {
|
||||
self.active = false;
|
||||
self.request_osd_message("Discord RPC stopped");
|
||||
Ok(())
|
||||
}
|
||||
Err(_) => Err("cannot disconnect from Discord")
|
||||
|
@ -166,16 +248,28 @@ impl DiscordClient {
|
|||
true => self.close()
|
||||
}
|
||||
}
|
||||
|
||||
fn request_osd_message(&mut self, message: &'static str) {
|
||||
self.mpv_requests.push_front(MpvRequest::OSDMessage(message));
|
||||
}
|
||||
}
|
||||
|
||||
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::Seek(remaining_time) => self.set_timestamps(remaining_time),
|
||||
MpvEvent::Play(remaining_time) => self.set_timestamps(remaining_time),
|
||||
MpvEvent::Pause(_) => self.clear_timestamps(),
|
||||
MpvEvent::Buffering => self.clear_timestamps(),
|
||||
MpvEvent::Toggle => self.toggle_activity(),
|
||||
MpvEvent::Exit => self.close(),
|
||||
_ => Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MpvRequester for DiscordClient {
|
||||
fn next_request<'a>(&mut self) -> Option<MpvRequest> {
|
||||
self.mpv_requests.pop_back()
|
||||
}
|
||||
}
|
39
src/discord_client/music_brainz.rs
Normal file
39
src/discord_client/music_brainz.rs
Normal file
|
@ -0,0 +1,39 @@
|
|||
use musicbrainz_rs::entity::release::{Release, ReleaseSearchQuery};
|
||||
use musicbrainz_rs::entity::CoverartResponse;
|
||||
use musicbrainz_rs::{Search, FetchCoverart};
|
||||
|
||||
pub fn get_cover_art_url(title: &Option<String>, album: &Option<String>, artist: &Option<String>) -> Option<String>{
|
||||
let mut builder = ReleaseSearchQuery::query_builder();
|
||||
if let Some(ref title) = title {
|
||||
builder.release(title);
|
||||
}
|
||||
|
||||
if let Some(ref album) = album {
|
||||
builder.or().release(album);
|
||||
}
|
||||
|
||||
if let Some(ref artist) = artist {
|
||||
builder.and().artist(artist);
|
||||
}
|
||||
|
||||
let query = builder.build();
|
||||
|
||||
let result = match Release::search(query).execute() {
|
||||
Ok(res) => res,
|
||||
Err(_) => return None
|
||||
};
|
||||
|
||||
let release = match result.entities.get(0) {
|
||||
Some(group) => group,
|
||||
None => return None
|
||||
};
|
||||
|
||||
let cover_art = match release.get_coverart().front().execute() {
|
||||
Ok(art) => art,
|
||||
Err(_) => return None
|
||||
};
|
||||
match cover_art {
|
||||
CoverartResponse::Url(url) => Some(url),
|
||||
_ => None
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
use mpv_client::mpv_handle;
|
||||
|
||||
mod config;
|
||||
mod logging;
|
||||
mod mpv_event_queue;
|
||||
mod discord_client;
|
||||
mod plugin;
|
||||
mod utils;
|
||||
|
||||
use plugin::RPCPlugin;
|
||||
|
||||
|
@ -13,7 +15,10 @@ const DISCORD_APPID: &str = "1071519995588264016";
|
|||
fn mpv_open_cplugin(handle: *mut mpv_handle) -> std::os::raw::c_int {
|
||||
let plugin = match RPCPlugin::new(handle, DISCORD_APPID) {
|
||||
Ok(plugin) => plugin,
|
||||
Err(_) => return -1
|
||||
Err(e) => {
|
||||
println!("Error creating RPC plugin: {e}");
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
plugin.run();
|
||||
|
|
|
@ -19,12 +19,11 @@ impl From<u32> for LogLevel {
|
|||
1 => LogLevel::Error,
|
||||
2 => LogLevel::Warn,
|
||||
3 => LogLevel::Info,
|
||||
_ => LogLevel::Error
|
||||
_ => LogLevel::Info
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Logger {
|
||||
log_level: LogLevel
|
||||
|
@ -65,5 +64,4 @@ impl Logger {
|
|||
println!("[mpv-rpc (ERROR)] {}", message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
use std::rc::Rc;
|
||||
use std::{rc::Rc, time::Duration};
|
||||
use mpv_client::{Handle, Event, Property, Format, mpv_handle, ClientMessage};
|
||||
use crate::logging::{self, Logger};
|
||||
|
||||
pub mod events;
|
||||
use events::{MpvEvent, FileInfo, FileMetadata};
|
||||
use events::{MpvEvent, MpvRequest, FileInfo, FileMetadata};
|
||||
|
||||
|
||||
const NAME_PAUSE_PROP: &str = "pause";
|
||||
|
@ -11,7 +11,7 @@ const REPL_PAUSE_PROP: u64 = 1;
|
|||
|
||||
pub struct MpvEventQueue {
|
||||
mpv: Handle,
|
||||
logger: Rc<Logger>,
|
||||
logger: Rc<Logger>
|
||||
}
|
||||
|
||||
impl MpvEventQueue {
|
||||
|
@ -40,18 +40,31 @@ impl MpvEventQueue {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn next_event(&self) -> Option<MpvEvent> {
|
||||
pub fn next_event(&mut 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
|
||||
}
|
||||
|
||||
pub fn handle_request(&self, request: MpvRequest) -> Result<(), &'static str> {
|
||||
match request {
|
||||
MpvRequest::OSDMessage(message) => self.display_osd_message(message)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn display_osd_message(&self, message: &str) -> Result<(), &'static str> {
|
||||
match self.mpv.osd_message(message, Duration::from_secs(1)) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err("cannot print OSD message")
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_event(&self, event: Event) -> Option<MpvEvent> {
|
||||
match event {
|
||||
Event::None => (),
|
||||
ref event => logging::info!(self.logger, "Event: {event}")
|
||||
}
|
||||
|
||||
match event {
|
||||
Event::FileLoaded => self.get_file_info_event(),
|
||||
Event::PlaybackRestart => self.get_seek_event(),
|
||||
|
@ -87,27 +100,33 @@ impl MpvEventQueue {
|
|||
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())),
|
||||
1 => self.convert_pause_prop(prop.data().unwrap()),
|
||||
2 => self.convert_buffering_prop(prop.data().unwrap()),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_pause_prop(&self, pause: bool) -> MpvEvent {
|
||||
fn convert_pause_prop(&self, pause: bool) -> Option<MpvEvent> {
|
||||
let time = self.get_remaining_time();
|
||||
match pause {
|
||||
false => MpvEvent::Play,
|
||||
true => MpvEvent::Pause
|
||||
false => Some(MpvEvent::Play(time)),
|
||||
true => Some(MpvEvent::Pause(time))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn convert_buffering_prop(&self, buffering: bool) -> Option<MpvEvent> {
|
||||
match buffering {
|
||||
true => Some(MpvEvent::Buffering),
|
||||
false => None
|
||||
}
|
||||
}
|
||||
|
||||
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(self.get_remaining_time()))
|
||||
}
|
||||
|
||||
Some(MpvEvent::Seek(remaining_time))
|
||||
fn get_remaining_time(&self) -> i64 {
|
||||
self.mpv.get_property("time-remaining").unwrap_or_default()
|
||||
}
|
||||
|
||||
fn get_toggle_event(&self, message: ClientMessage) -> Option<MpvEvent> {
|
||||
|
@ -115,10 +134,11 @@ impl MpvEventQueue {
|
|||
logging::info!(self.logger, "Client message: {command}");
|
||||
|
||||
if command.starts_with("key-binding toggle-rpc d-") {
|
||||
return Some(MpvEvent::Toggle)
|
||||
Some(MpvEvent::Toggle)
|
||||
}
|
||||
else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
pub struct FileInfo {
|
||||
pub filename: String,
|
||||
pub metadata: FileMetadata
|
||||
|
@ -14,28 +12,22 @@ pub struct FileMetadata {
|
|||
|
||||
pub enum MpvEvent {
|
||||
Toggle,
|
||||
Play,
|
||||
Pause,
|
||||
Buffering,
|
||||
Exit,
|
||||
FileLoaded(FileInfo),
|
||||
Play(i64),
|
||||
Pause(i64),
|
||||
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 enum MpvRequest {
|
||||
OSDMessage(&'static str)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub trait MpvEventHandler {
|
||||
fn handle_event(&mut self, event: MpvEvent) -> Result<(), &'static str>;
|
||||
}
|
||||
|
||||
pub trait MpvRequester {
|
||||
fn next_request<'a>(&mut self) -> Option<MpvRequest>;
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
use std::rc::Rc;
|
||||
use mpv_client::mpv_handle;
|
||||
use crate::logging::{self, Logger};
|
||||
use crate::config::Config;
|
||||
use crate::discord_client::DiscordClient;
|
||||
use crate::mpv_event_queue::MpvEventQueue;
|
||||
use crate::mpv_event_queue::events::{MpvEventHandler, MpvEvent};
|
||||
use crate::mpv_event_queue::events::{MpvEventHandler, MpvRequester, MpvEvent, MpvRequest};
|
||||
|
||||
pub struct RPCPlugin {
|
||||
logger: Rc<Logger>,
|
||||
|
@ -14,8 +15,9 @@ pub struct RPCPlugin {
|
|||
impl RPCPlugin {
|
||||
pub fn new(handle: *mut mpv_handle, client_id: &str) -> Result<Self, &'static str> {
|
||||
let logger = Rc::new(Logger::from_env());
|
||||
let config = Config::from_config_file(&logger);
|
||||
let mpv = MpvEventQueue::from_ptr(handle, Rc::clone(&logger))?;
|
||||
let discord = DiscordClient::new(client_id, Rc::clone(&logger))?;
|
||||
let discord = DiscordClient::new(client_id, config.active, config.cover_art, Rc::clone(&logger))?;
|
||||
|
||||
Ok(Self {
|
||||
logger,
|
||||
|
@ -28,7 +30,7 @@ impl RPCPlugin {
|
|||
loop {
|
||||
let event = self.mpv.next_event();
|
||||
match event {
|
||||
None => continue,
|
||||
None => (),
|
||||
Some(event) => {
|
||||
if self.handle_event(event)
|
||||
{
|
||||
|
@ -36,6 +38,12 @@ impl RPCPlugin {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
let request = self.discord.next_request();
|
||||
match request {
|
||||
None => (),
|
||||
Some(request) => self.handle_request(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -51,4 +59,10 @@ impl RPCPlugin {
|
|||
|
||||
exit
|
||||
}
|
||||
|
||||
fn handle_request(&self, request: MpvRequest) {
|
||||
if let Err(e) = self.mpv.handle_request(request) {
|
||||
logging::error!(self.logger, "Failed to handle mpv request: {e}");
|
||||
}
|
||||
}
|
||||
}
|
15
src/utils.rs
Normal file
15
src/utils.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
pub fn truncate_string(current: &mut String, length: usize) {
|
||||
match current.char_indices().nth(length) {
|
||||
None => (),
|
||||
Some((index, _)) => current.truncate(index)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn truncate_string_fmt(current: &mut String, length: usize) {
|
||||
if current.chars().count() <= length {
|
||||
return;
|
||||
}
|
||||
|
||||
truncate_string(current, length - 3);
|
||||
current.push_str("...");
|
||||
}
|
Loading…
Reference in a new issue