media_remote/high_level/
now_playing.rs1use std::{
2 collections::HashMap,
3 sync::{atomic::AtomicU64, Arc, Mutex, RwLock, RwLockReadGuard},
4 time::SystemTime,
5};
6
7#[cfg(feature = "artwork")]
8use std::io::Cursor;
9
10#[cfg(feature = "artwork")]
11use image::ImageReader;
12
13use crate::{
14 add_observer, get_bundle_info, get_now_playing_application_is_playing,
15 get_now_playing_client_bundle_identifier, get_now_playing_client_parent_app_bundle_identifier,
16 get_now_playing_info, register_for_now_playing_notifications, remove_observer,
17 unregister_for_now_playing_notifications, InfoTypes, Notification, NowPlayingInfo, Number,
18 Observer, Subscription,
19};
20
21use crate::{Controller, ListenerToken};
22
23pub struct NowPlaying {
37 info: Arc<RwLock<Option<NowPlayingInfo>>>,
38 observers: Vec<Observer>,
39 listeners: Arc<
40 Mutex<
41 HashMap<
42 ListenerToken,
43 Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
44 >,
45 >,
46 >,
47 token_counter: Arc<AtomicU64>,
48}
49
50fn update_all(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
51 let mut info_guard = info.write().unwrap();
52 *info_guard = Some(NowPlayingInfo {
53 is_playing: None,
54 title: None,
55 artist: None,
56 album: None,
57 #[cfg(feature = "artwork")]
58 album_cover: None,
59 elapsed_time: None,
60 duration: None,
61 info_update_time: None,
62 bundle_id: None,
63 bundle_name: None,
64 #[cfg(feature = "artwork")]
65 bundle_icon: None,
66 });
67 drop(info_guard);
68
69 update_state(info.clone());
70 update_app(info.clone());
71 update_info(info.clone());
72}
73
74fn update_state(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
75 let mut info_guard = info.write().unwrap();
76 if info_guard.as_ref().is_none() {
77 drop(info_guard);
78 return update_all(info.clone());
79 }
80
81 let is_playing = get_now_playing_application_is_playing();
82 if let Some(is_playing) = is_playing {
83 info_guard.as_mut().unwrap().is_playing = Some(is_playing);
84 }
85}
86
87fn update_info(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
88 let mut info_guard = info.write().unwrap();
89 if info_guard.as_ref().is_none() {
90 drop(info_guard);
91 return update_all(info.clone());
92 }
93
94 let now_playing_info = get_now_playing_info();
95 if let Some(info) = now_playing_info {
96 macro_rules! update_string_info {
97 ($key:expr, $field:expr) => {
98 if let Some(InfoTypes::String(s)) = info.get($key) {
99 if !s.is_empty() {
100 $field = Some(s.clone());
101 }
102 }
103 };
104 }
105
106 update_string_info!(
107 "kMRMediaRemoteNowPlayingInfoTitle",
108 info_guard.as_mut().unwrap().title
109 );
110 update_string_info!(
111 "kMRMediaRemoteNowPlayingInfoArtist",
112 info_guard.as_mut().unwrap().artist
113 );
114 update_string_info!(
115 "kMRMediaRemoteNowPlayingInfoAlbum",
116 info_guard.as_mut().unwrap().album
117 );
118
119 macro_rules! update_float_info {
120 ($key:expr, $field:expr) => {
121 if let Some(InfoTypes::Number(Number::Floating(f))) = info.get($key) {
122 $field = Some(f.clone());
123 }
124 };
125 }
126
127 update_float_info!(
128 "kMRMediaRemoteNowPlayingInfoDuration",
129 info_guard.as_mut().unwrap().duration
130 );
131 update_float_info!(
132 "kMRMediaRemoteNowPlayingInfoElapsedTime",
133 info_guard.as_mut().unwrap().elapsed_time
134 );
135
136 #[cfg(feature = "artwork")]
137 if let Some(InfoTypes::Data(d)) = info.get("kMRMediaRemoteNowPlayingInfoArtworkData") {
138 info_guard.as_mut().unwrap().album_cover = ImageReader::new(Cursor::new(d))
139 .with_guessed_format()
140 .ok()
141 .and_then(|img| img.decode().ok());
142 }
143
144 info_guard.as_mut().unwrap().info_update_time = info
145 .get("kMRMediaRemoteNowPlayingInfoTimestamp")
146 .and_then(|f| match f {
147 InfoTypes::SystemTime(t) => Some(t.clone()),
148 _ => None,
149 })
150 .or(Some(SystemTime::now()));
151 }
152}
153
154fn update_app(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
155 let mut info_guard = info.write().unwrap();
156 if info_guard.as_ref().is_none() {
157 drop(info_guard);
158 return update_all(info.clone());
159 }
160
161 let mut bundle_id = get_now_playing_client_parent_app_bundle_identifier();
162 if bundle_id.is_none() {
163 bundle_id = get_now_playing_client_bundle_identifier();
164 }
165
166 if let Some(id) = bundle_id {
167 let bundle_info = get_bundle_info(id.as_str());
168 if let Some(info) = bundle_info {
169 info_guard.as_mut().unwrap().bundle_id = Some(id);
170 info_guard.as_mut().unwrap().bundle_name = Some(info.name);
171 #[cfg(feature = "artwork")]
172 {
173 info_guard.as_mut().unwrap().bundle_icon = Some(info.icon);
174 }
175 }
176 }
177}
178
179impl NowPlaying {
180 fn register(&mut self) {
181 register_for_now_playing_notifications();
182
183 let info = Arc::clone(&self.info);
185 update_all(info.clone());
186
187 macro_rules! add_observer_macro {
188 ($notification:expr, $update_fn:expr) => {{
189 let info = Arc::clone(&self.info);
190 let listeners = Arc::clone(&self.listeners);
191
192 self.observers.push(add_observer($notification, move || {
193 $update_fn(info.clone());
194 for (_, listener) in listeners.clone().lock().unwrap().iter() {
195 listener(info.read().unwrap());
196 }
197 }));
198 }};
199 }
200
201 add_observer_macro!(Notification::NowPlayingApplicationDidChange, update_app);
202
203 add_observer_macro!(Notification::NowPlayingInfoDidChange, update_info);
204 add_observer_macro!(
212 Notification::NowPlayingApplicationIsPlayingDidChange,
213 update_state
214 );
215 }
216
217 pub fn new() -> Self {
232 let mut new_instance = Self {
233 info: Arc::new(RwLock::new(None)),
234 observers: vec![],
235 listeners: Arc::new(Mutex::new(HashMap::new())),
236 token_counter: Arc::new(AtomicU64::new(0)),
237 };
238
239 new_instance.register();
240
241 new_instance
242 }
243
244 pub fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
269 let mut info_guard = self.info.write().unwrap();
270 let info = info_guard.as_mut();
271
272 if info.is_some() {
273 let info = info.unwrap();
274 if info.is_playing.is_some_and(|x| x)
275 && info.elapsed_time.is_some()
276 && info.info_update_time.is_some()
277 {
278 info.elapsed_time = Some(
279 info.elapsed_time.unwrap()
280 + info.info_update_time.unwrap().elapsed().unwrap().as_secs() as f64,
281 );
282 info.info_update_time = Some(SystemTime::now())
283 }
284 }
285
286 drop(info_guard);
287
288 self.info.read().unwrap()
289 }
290}
291
292impl Drop for NowPlaying {
293 fn drop(&mut self) {
294 while let Some(observer) = self.observers.pop() {
295 remove_observer(observer);
296 }
297
298 unregister_for_now_playing_notifications();
299 }
300}
301
302impl Controller for NowPlaying {
303 fn is_info_some(&self) -> bool {
304 self.info.read().unwrap().as_ref().is_some()
305 }
306}
307
308impl Subscription for NowPlaying {
309 fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
310 self.get_info()
311 }
312
313 fn get_token_counter(&self) -> Arc<AtomicU64> {
314 self.token_counter.clone()
315 }
316
317 fn get_listeners(
318 &self,
319 ) -> Arc<
320 Mutex<
321 HashMap<
322 super::subscription::ListenerToken,
323 Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
324 >,
325 >,
326 > {
327 self.listeners.clone()
328 }
329}