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 playback_rate: None,
62 info_update_time: None,
63 bundle_id: None,
64 bundle_name: None,
65 #[cfg(feature = "artwork")]
66 bundle_icon: None,
67 });
68 drop(info_guard);
69
70 update_state(info.clone());
71 update_app(info.clone());
72 update_info(info.clone());
73}
74
75fn update_state(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
76 let mut info_guard = info.write().unwrap();
77 if info_guard.as_ref().is_none() {
78 drop(info_guard);
79 return update_all(info.clone());
80 }
81
82 let is_playing = get_now_playing_application_is_playing();
83 if let Some(is_playing) = is_playing {
84 info_guard.as_mut().unwrap().is_playing = Some(is_playing);
85 }
86}
87
88fn update_info(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
89 let mut info_guard = info.write().unwrap();
90 if info_guard.as_ref().is_none() {
91 drop(info_guard);
92 return update_all(info.clone());
93 }
94
95 let now_playing_info = get_now_playing_info();
96 if let Some(info) = now_playing_info {
97 macro_rules! update_string_info {
98 ($key:expr, $field:expr) => {
99 if let Some(InfoTypes::String(s)) = info.get($key) {
100 if !s.is_empty() {
101 $field = Some(s.clone());
102 }
103 }
104 };
105 }
106
107 update_string_info!(
108 "kMRMediaRemoteNowPlayingInfoTitle",
109 info_guard.as_mut().unwrap().title
110 );
111 update_string_info!(
112 "kMRMediaRemoteNowPlayingInfoArtist",
113 info_guard.as_mut().unwrap().artist
114 );
115 update_string_info!(
116 "kMRMediaRemoteNowPlayingInfoAlbum",
117 info_guard.as_mut().unwrap().album
118 );
119
120 macro_rules! update_float_info {
121 ($key:expr, $field:expr) => {
122 if let Some(InfoTypes::Number(Number::Floating(f))) = info.get($key) {
123 $field = Some(f.clone());
124 }
125 };
126 }
127
128 update_float_info!(
129 "kMRMediaRemoteNowPlayingInfoDuration",
130 info_guard.as_mut().unwrap().duration
131 );
132 update_float_info!(
133 "kMRMediaRemoteNowPlayingInfoElapsedTime",
134 info_guard.as_mut().unwrap().elapsed_time
135 );
136 update_float_info!(
137 "kMRMediaRemoteNowPlayingInfoPlaybackRate",
138 info_guard.as_mut().unwrap().playback_rate
139 );
140
141 #[cfg(feature = "artwork")]
142 if let Some(InfoTypes::Data(d)) = info.get("kMRMediaRemoteNowPlayingInfoArtworkData") {
143 info_guard.as_mut().unwrap().album_cover = ImageReader::new(Cursor::new(d))
144 .with_guessed_format()
145 .ok()
146 .and_then(|img| img.decode().ok());
147 }
148
149 info_guard.as_mut().unwrap().info_update_time = info
150 .get("kMRMediaRemoteNowPlayingInfoTimestamp")
151 .and_then(|f| match f {
152 InfoTypes::SystemTime(t) => Some(t.clone()),
153 _ => None,
154 })
155 .or(Some(SystemTime::now()));
156 }
157}
158
159fn update_app(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
160 let mut info_guard = info.write().unwrap();
161 if info_guard.as_ref().is_none() {
162 drop(info_guard);
163 return update_all(info.clone());
164 }
165
166 let mut bundle_id = get_now_playing_client_parent_app_bundle_identifier();
167 if bundle_id.is_none() {
168 bundle_id = get_now_playing_client_bundle_identifier();
169 }
170
171 if let Some(id) = bundle_id {
172 let bundle_info = get_bundle_info(id.as_str());
173 if let Some(info) = bundle_info {
174 info_guard.as_mut().unwrap().bundle_id = Some(id);
175 info_guard.as_mut().unwrap().bundle_name = Some(info.name);
176 #[cfg(feature = "artwork")]
177 {
178 info_guard.as_mut().unwrap().bundle_icon = Some(info.icon);
179 }
180 }
181 }
182}
183
184impl NowPlaying {
185 fn register(&mut self) {
186 register_for_now_playing_notifications();
187
188 let info = Arc::clone(&self.info);
190 update_all(info.clone());
191
192 macro_rules! add_observer_macro {
193 ($notification:expr, $update_fn:expr) => {{
194 let info = Arc::clone(&self.info);
195 let listeners = Arc::clone(&self.listeners);
196
197 self.observers.push(add_observer($notification, move || {
198 $update_fn(info.clone());
199 for (_, listener) in listeners.clone().lock().unwrap().iter() {
200 listener(info.read().unwrap());
201 }
202 }));
203 }};
204 }
205
206 add_observer_macro!(Notification::NowPlayingApplicationDidChange, update_app);
207
208 add_observer_macro!(Notification::NowPlayingInfoDidChange, update_info);
209 add_observer_macro!(
217 Notification::NowPlayingApplicationIsPlayingDidChange,
218 update_state
219 );
220 }
221
222 pub fn new() -> Self {
237 let mut new_instance = Self {
238 info: Arc::new(RwLock::new(None)),
239 observers: vec![],
240 listeners: Arc::new(Mutex::new(HashMap::new())),
241 token_counter: Arc::new(AtomicU64::new(0)),
242 };
243
244 new_instance.register();
245
246 new_instance
247 }
248
249 pub fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
274 let mut info_guard = self.info.write().unwrap();
275 let info = info_guard.as_mut();
276
277 if info.is_some() {
278 let info = info.unwrap();
279 if info.is_playing.is_some_and(|x| x)
280 && info.elapsed_time.is_some()
281 && info.info_update_time.is_some()
282 {
283 info.elapsed_time = Some(
284 info.elapsed_time.unwrap()
285 + info.info_update_time.unwrap().elapsed().unwrap().as_secs() as f64,
286 );
287 info.info_update_time = Some(SystemTime::now())
288 }
289 }
290
291 drop(info_guard);
292
293 self.info.read().unwrap()
294 }
295}
296
297impl Drop for NowPlaying {
298 fn drop(&mut self) {
299 while let Some(observer) = self.observers.pop() {
300 remove_observer(observer);
301 }
302
303 unregister_for_now_playing_notifications();
304 }
305}
306
307impl Controller for NowPlaying {
308 fn is_info_some(&self) -> bool {
309 self.info.read().unwrap().as_ref().is_some()
310 }
311}
312
313impl Subscription for NowPlaying {
314 fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
315 self.get_info()
316 }
317
318 fn get_token_counter(&self) -> Arc<AtomicU64> {
319 self.token_counter.clone()
320 }
321
322 fn get_listeners(
323 &self,
324 ) -> Arc<
325 Mutex<
326 HashMap<
327 super::subscription::ListenerToken,
328 Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
329 >,
330 >,
331 > {
332 self.listeners.clone()
333 }
334}