media_remote/high_level/
now_playing_jxa.rs1use std::{
2 collections::HashMap,
3 sync::{
4 atomic::{AtomicBool, AtomicU64, Ordering},
5 Arc, Mutex, RwLock, RwLockReadGuard,
6 },
7 thread::{self, JoinHandle},
8 time::{Duration, SystemTime},
9};
10
11use crate::{get_bundle_info, get_raw_info, ListenerToken, NowPlayingInfo, Subscription};
12
13use super::controller::Controller;
14
15pub fn get_info() -> Option<NowPlayingInfo> {
16 let raw = get_raw_info()?;
17
18 let mut bundle_id = raw["client"]["parentApplicationBundleIdentifier"].as_str();
19 if bundle_id.is_none() {
20 bundle_id = raw["client"]["bundleIdentifier"].as_str();
21 }
22 let bundle_id = bundle_id;
23
24 let bundle_info = bundle_id.and_then(|bid| get_bundle_info(bid));
25
26 Some(NowPlayingInfo {
27 is_playing: raw["isPlaying"].as_bool(),
28 title: raw["info"]["kMRMediaRemoteNowPlayingInfoTitle"]
29 .as_str()
30 .map(|s| s.to_string()),
31 artist: raw["info"]["kMRMediaRemoteNowPlayingInfoArtist"]
32 .as_str()
33 .map(|s| s.to_string()),
34 album: raw["info"]["kMRMediaRemoteNowPlayingInfoAlbum"]
35 .as_str()
36 .map(|s| s.to_string()),
37 album_cover: None,
38 elapsed_time: raw["info"]["kMRMediaRemoteNowPlayingInfoElapsedTime"].as_f64(),
39 duration: raw["info"]["kMRMediaRemoteNowPlayingInfoDuration"].as_f64(),
40 info_update_time: raw["info"]["kMRMediaRemoteNowPlayingInfoTimestamp"]
41 .as_u64()
42 .and_then(|t| Some(SystemTime::UNIX_EPOCH + Duration::from_millis(t)))
43 .or(Some(SystemTime::now())),
44 bundle_id: bundle_id.map(|b| b.to_string()),
45 bundle_name: bundle_info.as_ref().map(|b| b.name.clone()),
46 bundle_icon: bundle_info.map(|b| b.icon),
47 })
48}
49
50pub struct NowPlayingJXA {
51 info: Arc<RwLock<Option<NowPlayingInfo>>>,
52 listeners: Arc<
53 Mutex<
54 HashMap<
55 ListenerToken,
56 Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
57 >,
58 >,
59 >,
60 token_counter: Arc<AtomicU64>,
61 stop_flag: Arc<AtomicBool>,
62 handle: Option<JoinHandle<()>>,
63}
64
65impl NowPlayingJXA {
66 fn update(&mut self, update_interval: Duration) {
67 let info_clone = Arc::clone(&self.info);
68 let stop_clone = Arc::clone(&self.stop_flag);
69 let listeners = Arc::clone(&self.listeners);
70
71 self.handle = Some(thread::spawn(move || {
72 while !stop_clone.load(Ordering::Relaxed) {
73 thread::sleep(update_interval);
74 if let Some(new_info) = get_info() {
75 let mut current = info_clone.write().unwrap();
76 if current.as_ref() != Some(&new_info) {
77 *current = Some(new_info);
78 drop(current);
79
80 for (_, listener) in listeners.clone().lock().unwrap().iter() {
81 listener(info_clone.read().unwrap());
82 }
83 }
84 }
85 }
86 }));
87 }
88
89 pub fn new(update_interval: Duration) -> Self {
105 let mut new_instance = NowPlayingJXA {
106 info: Arc::new(RwLock::new(get_info())),
107 listeners: Arc::new(Mutex::new(HashMap::new())),
108 token_counter: Arc::new(AtomicU64::new(0)),
109 stop_flag: Arc::new(AtomicBool::new(false)),
110 handle: None,
111 };
112
113 new_instance.update(update_interval);
114
115 new_instance
116 }
117
118 pub fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
144 let mut info_guard = self.info.write().unwrap();
145 let info = info_guard.as_mut();
146
147 if info.is_some() {
148 let info = info.unwrap();
149 if info.is_playing.is_some_and(|x| x)
150 && info.elapsed_time.is_some()
151 && info.info_update_time.is_some()
152 {
153 info.elapsed_time = Some(
154 info.elapsed_time.unwrap()
155 + info.info_update_time.unwrap().elapsed().unwrap().as_secs() as f64,
156 );
157 info.info_update_time = Some(SystemTime::now())
158 }
159 }
160
161 drop(info_guard);
162
163 self.info.read().unwrap()
164 }
165}
166
167impl Drop for NowPlayingJXA {
168 fn drop(&mut self) {
169 self.stop_flag.store(true, Ordering::Relaxed);
170 if let Some(handle) = self.handle.take() {
171 let _ = handle.join();
172 }
173 }
174}
175
176impl Controller for NowPlayingJXA {
177 fn is_info_some(&self) -> bool {
178 self.info.read().unwrap().as_ref().is_some()
179 }
180}
181
182impl Subscription for NowPlayingJXA {
183 fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
184 self.get_info()
185 }
186
187 fn get_token_counter(&self) -> Arc<AtomicU64> {
188 self.token_counter.clone()
189 }
190
191 fn get_listeners(
192 &self,
193 ) -> Arc<
194 Mutex<
195 HashMap<
196 super::subscription::ListenerToken,
197 Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
198 >,
199 >,
200 > {
201 self.listeners.clone()
202 }
203}