From 8df2aded07a1556e0d644e1ebcc121c3ffe63e87 Mon Sep 17 00:00:00 2001 From: Mihail Morosan Date: Sun, 5 Jan 2025 17:52:55 +0000 Subject: [PATCH] Some code improvements. Found a bug, will sort later. --- src/homeassistant.rs | 172 ++++++++++++++++++++++++++++--------------- src/main.rs | 1 + src/mpris.rs | 118 +++++++++-------------------- 3 files changed, 146 insertions(+), 145 deletions(-) diff --git a/src/homeassistant.rs b/src/homeassistant.rs index 6e6c3da..a5410e6 100644 --- a/src/homeassistant.rs +++ b/src/homeassistant.rs @@ -7,6 +7,7 @@ use serde::Deserialize; use serde_json::{json, Value}; use tokio::sync::mpsc::{Receiver, Sender}; use tokio_tungstenite::{connect_async, tungstenite::protocol::Message}; +use url::Url; #[derive(Deserialize, Debug, Clone)] pub struct MediaPlayer { @@ -22,15 +23,73 @@ pub struct MediaPlayerState { pub entity_id: String, } +#[derive(Debug)] +pub struct MediaPlayerMetadata { + pub title: String, + pub artist: String, + pub duration: i64, + pub position: i64, + pub volume: f64, + pub art_url: String, + pub playing: bool, +} + #[derive(Debug)] pub enum HAEvent { Play, Pause, - MetadataUpdated((String, String, i64, i64, String)), + MetadataUpdated(MediaPlayerMetadata), Next, Previous, } +pub fn json_to_metadata( + metadata: HashMap, + playing: bool, + base_url: String, +) -> Result { + Ok(MediaPlayerMetadata { + title: metadata + .get("media_title") + .unwrap_or(&Value::String("".to_string())) + .to_string() + .trim_matches(['\"']) + .to_string(), + artist: metadata + .get("media_artist") + .unwrap_or(&Value::String("".to_string())) + .to_string() + .trim_matches(['\"']) + .to_string(), + duration: metadata + .get("media_duration") + .unwrap_or(&json!(0)) + .as_i64() + .ok_or_eyre("Could not convert Number to i64")?, + position: metadata + .get("media_position") + .unwrap_or(&json!(0)) + .as_i64() + .ok_or_eyre("Could not convert Number to i64")?, + art_url: validate_art_url( + metadata + .get("entity_picture") + .unwrap_or(&Value::String("".to_string())) + .to_string() + .trim_matches(['\"']) + .to_string(), + &base_url, + )? + .to_string(), + volume: metadata + .get("volume_level") + .unwrap_or(&json!(1.0)) + .as_f64() + .ok_or_eyre("Could not convert Number to f64")?, + playing, + }) +} + impl MediaPlayerState { pub fn new(entity_id: String, ha_url: String, ha_token: String) -> Result { Ok(Self { @@ -59,17 +118,10 @@ impl MediaPlayerState { } pub async fn update_metadata( - &mut self, + &self, metadata: serde_json::value::Value, state: String, ) -> Result> { - let attribs: HashMap = - if let serde_json::Value::Object(m) = metadata { - m.into_iter().collect() - } else { - return Ok(vec![]); - }; - let mut events = vec![]; if state.eq("\"playing\"") { @@ -77,30 +129,17 @@ impl MediaPlayerState { } else { events.push(HAEvent::Pause); } - events.push(HAEvent::MetadataUpdated(( - attribs - .get("media_title") - .unwrap_or(&Value::String("".to_string())) - .to_string(), - attribs - .get("media_artist") - .unwrap_or(&Value::String("".to_string())) - .to_string(), - attribs - .get("media_duration") - .unwrap_or(&json!(0)) - .as_i64() - .ok_or_eyre("Could not convert Number to i64")?, - attribs - .get("media_position") - .unwrap_or(&json!(0)) - .as_i64() - .ok_or_eyre("Could not convert Number to i64")?, - attribs - .get("entity_picture") - .unwrap_or(&Value::String("".to_string())) - .to_string(), - ))); + let attribs: HashMap = + if let serde_json::Value::Object(m) = metadata { + m.into_iter().collect() + } else { + eyre::bail!("Oh no"); + }; + events.push(HAEvent::MetadataUpdated(json_to_metadata( + attribs, + state.contains("playing"), + self.ha_url.clone(), + )?)); Ok(events) } @@ -186,39 +225,39 @@ pub async fn listen_for_events( loop { tokio::select! { - Some(Ok(message)) = read.next() => { - if let Message::Text(text) = message { - let event: serde_json::Value = serde_json::from_str(&text).expect("Invalid JSON"); - let entity = event.get("event").and_then(|e| e.get("data")).and_then(|d| d.get("entity_id")).and_then(|e| e.as_str()); - if let Some(entity_id) = entity - { - if let Some(media_player) = media_players.get_mut(entity_id) { + Some(Ok(Message::Text(text))) = read.next() => { + let event: serde_json::Value = serde_json::from_str(&text).expect("Invalid JSON"); + let entity = event.get("event").and_then(|e| e.get("data")).and_then(|d| d.get("entity_id")).and_then(|e| e.as_str()); + if let Some(entity_id) = entity + { + if let Some(media_player) = media_players.get_mut(entity_id) { + println!("{:?}", text); if let Some(new_state) = event.get("event").and_then(|e| e.get("data")).and_then(|d| d.get("new_state")) { - let attr = new_state.get("attributes"); let state = new_state.get("state"); if attr.is_some() && state.is_some() { - - let events = media_player - .update_metadata( - attr.unwrap().clone(), - state.unwrap().to_string().clone() - .to_string() - .clone(), - ) - .await?; - - for e in events { - channels - .get(entity_id) - .unwrap() - .send(e) - .await?; - } + match media_player + .update_metadata( + attr.unwrap().clone(), + state.unwrap().to_string().clone() + .to_string() + .clone(), + ) + .await { + Ok(events) => { + for e in events { + channels + .get(entity_id) + .unwrap() + .send(e) + .await?; + } + }, + Err(e) => println!("Died during metadata update event with {e}"), + }; } } - } } } } @@ -228,12 +267,25 @@ pub async fn listen_for_events( match msg { HAEvent::Play=> mp.play().await?, HAEvent::Pause=> mp.pause().await?, - HAEvent::MetadataUpdated(_)=>todo!(), HAEvent::Next => mp.next().await?, HAEvent::Previous => mp.previous().await?, + _ => {}, }; } } } } } + +fn validate_art_url(art_url: String, base_url: &str) -> eyre::Result { + let parsed_url = url::Url::parse(&art_url); + + match parsed_url { + Ok(url) => Ok(url), + Err(url::ParseError::RelativeUrlWithoutBase) => { + let base = url::Url::parse(base_url)?; + base.join(&art_url).map_err(|_e| eyre::eyre!("Oh no")) + } + Err(_e) => Err(eyre::eyre!("Oh no")), + } +} diff --git a/src/main.rs b/src/main.rs index 6ba0f2d..b222f99 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,6 +69,7 @@ async fn main() -> Result<()> { let (ha_tx, ha_rx) = mpsc::channel(100); channels.insert(player.entity_id.clone(), ha_tx); + println!("{:?}", player.clone()); let _mp_task = set.spawn(new_mpris_player( player.entity_id.clone(), player.clone(), diff --git a/src/mpris.rs b/src/mpris.rs index 3128ae8..cc623a4 100644 --- a/src/mpris.rs +++ b/src/mpris.rs @@ -1,26 +1,21 @@ use std::sync::Arc; -use eyre::OptionExt; use mpris_server::{ zbus::fdo, LoopStatus, Metadata, PlaybackRate, PlaybackStatus, PlayerInterface, Property, RootInterface, Server, Time, TrackId, Volume, }; -use serde_json::json; use tokio::sync::{ mpsc::{Receiver, Sender}, Mutex, }; -use url::Url; -use crate::homeassistant::{HAEvent, MediaPlayer}; +use crate::homeassistant::{json_to_metadata, HAEvent, MediaPlayer, MediaPlayerMetadata}; #[derive(Clone)] pub struct MyPlayer { - base_url: String, entity_id: String, ha_sender: tokio::sync::mpsc::Sender<(String, HAEvent)>, - pub start_state: MediaPlayer, - position: Arc>, + metadata: Arc>, } impl RootInterface for MyPlayer { @@ -33,11 +28,11 @@ impl RootInterface for MyPlayer { } async fn can_quit(&self) -> fdo::Result { - Ok(true) + Ok(false) } async fn fullscreen(&self) -> fdo::Result { - Ok(true) + Ok(false) } async fn set_fullscreen(&self, _fullscreen: bool) -> mpris_server::zbus::Result<()> { @@ -53,7 +48,7 @@ impl RootInterface for MyPlayer { } async fn has_track_list(&self) -> fdo::Result { - Ok(true) + Ok(false) } async fn identity(&self) -> fdo::Result { @@ -107,6 +102,7 @@ impl PlayerInterface for MyPlayer { } async fn stop(&self) -> fdo::Result<()> { + // TODO Ok(()) } @@ -131,7 +127,7 @@ impl PlayerInterface for MyPlayer { } async fn playback_status(&self) -> fdo::Result { - if self.start_state.state.contains("playing") { + if self.metadata.lock().await.playing { Ok(PlaybackStatus::Playing) } else { Ok(PlaybackStatus::Paused) @@ -139,7 +135,7 @@ impl PlayerInterface for MyPlayer { } async fn loop_status(&self) -> fdo::Result { - Ok(LoopStatus::Track) + Ok(LoopStatus::None) } async fn set_loop_status(&self, _loop_status: LoopStatus) -> mpris_server::zbus::Result<()> { @@ -155,7 +151,7 @@ impl PlayerInterface for MyPlayer { } async fn shuffle(&self) -> fdo::Result { - Ok(true) + Ok(false) } async fn set_shuffle(&self, _shuffle: bool) -> mpris_server::zbus::Result<()> { @@ -163,55 +159,26 @@ impl PlayerInterface for MyPlayer { } async fn metadata(&self) -> fdo::Result { - let title = self - .start_state - .attributes - .get("media_title") - .unwrap_or(&json!("")) - .to_string(); - let artist = self - .start_state - .attributes - .get("media_artist") - .unwrap_or(&json!("")) - .to_string(); - let duration = self - .start_state - .attributes - .get("media_duration") - .unwrap_or(&json!(0)) - .as_i64() - .unwrap(); - let art = self - .start_state - .attributes - .get("entity_picture") - .unwrap_or(&json!("")) - .to_string(); + let metadata = self.metadata.lock().await; Ok(Metadata::builder() - .title(title.trim_matches(['\"'])) - .artist(vec![artist.trim_matches(['\"'])]) - .length(Time::from_secs(duration)) - .art_url( - validate_art_url(art.trim_matches(['\"']).to_string(), self.base_url.clone()) - .unwrap_or( - Url::parse("http://example.com").expect("Default URL is always valid"), - ) - .to_string(), - ) + .title(metadata.title.clone()) + .artist(vec![metadata.artist.clone()]) + .length(Time::from_secs(metadata.duration)) + .art_url(metadata.art_url.clone()) .build()) } async fn volume(&self) -> fdo::Result { - Ok(Volume::MIN) + Ok(self.metadata.lock().await.volume) } async fn set_volume(&self, _volume: Volume) -> mpris_server::zbus::Result<()> { + // TODO Ok(()) } async fn position(&self) -> fdo::Result