media_remote/high_level/
now_playing.rs

1use std::{
2    collections::HashMap,
3    io::Cursor,
4    sync::{atomic::AtomicU64, Arc, Mutex, RwLock, RwLockReadGuard},
5    time::SystemTime,
6};
7
8use image::ImageReader;
9
10use crate::{
11    add_observer, get_bundle_info, get_now_playing_application_is_playing,
12    get_now_playing_client_bundle_identifier, get_now_playing_client_parent_app_bundle_identifier,
13    get_now_playing_info, register_for_now_playing_notifications, remove_observer,
14    unregister_for_now_playing_notifications, InfoTypes, Notification, NowPlayingInfo, Number,
15    Observer, Subscription,
16};
17
18use crate::{Controller, ListenerToken};
19
20/// A struct for managing and interacting with the "Now Playing" media session.
21///
22/// The `NowPlaying` struct allows access to the currently playing media information,
23/// and provides functionality to control playback (e.g., play, pause, skip).
24///
25///
26/// # Example
27/// ```rust
28/// use media_remote::{Controller, NowPlaying};
29///
30/// let now_playing = NowPlaying::new();
31/// now_playing.play();
32/// ```
33pub struct NowPlaying {
34    info: Arc<RwLock<Option<NowPlayingInfo>>>,
35    observers: Vec<Observer>,
36    listeners: Arc<
37        Mutex<
38            HashMap<
39                ListenerToken,
40                Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
41            >,
42        >,
43    >,
44    token_counter: Arc<AtomicU64>,
45}
46
47fn update_all(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
48    let mut info_guard = info.write().unwrap();
49    *info_guard = Some(NowPlayingInfo {
50        is_playing: None,
51        title: None,
52        artist: None,
53        album: None,
54        album_cover: None,
55        elapsed_time: None,
56        duration: None,
57        info_update_time: None,
58        bundle_id: None,
59        bundle_name: None,
60        bundle_icon: None,
61    });
62    drop(info_guard);
63
64    update_state(info.clone());
65    update_app(info.clone());
66    update_info(info.clone());
67}
68
69fn update_state(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
70    let mut info_guard = info.write().unwrap();
71    if info_guard.as_ref().is_none() {
72        drop(info_guard);
73        return update_all(info.clone());
74    }
75
76    let is_playing = get_now_playing_application_is_playing();
77    if let Some(is_playing) = is_playing {
78        info_guard.as_mut().unwrap().is_playing = Some(is_playing);
79    }
80}
81
82fn update_info(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
83    let mut info_guard = info.write().unwrap();
84    if info_guard.as_ref().is_none() {
85        drop(info_guard);
86        return update_all(info.clone());
87    }
88
89    let now_playing_info = get_now_playing_info();
90    if let Some(info) = now_playing_info {
91        macro_rules! update_string_info {
92            ($key:expr, $field:expr) => {
93                if let Some(InfoTypes::String(s)) = info.get($key) {
94                    if !s.is_empty() {
95                        $field = Some(s.clone());
96                    }
97                }
98            };
99        }
100
101        update_string_info!(
102            "kMRMediaRemoteNowPlayingInfoTitle",
103            info_guard.as_mut().unwrap().title
104        );
105        update_string_info!(
106            "kMRMediaRemoteNowPlayingInfoArtist",
107            info_guard.as_mut().unwrap().artist
108        );
109        update_string_info!(
110            "kMRMediaRemoteNowPlayingInfoAlbum",
111            info_guard.as_mut().unwrap().album
112        );
113
114        macro_rules! update_float_info {
115            ($key:expr, $field:expr) => {
116                if let Some(InfoTypes::Number(Number::Floating(f))) = info.get($key) {
117                    $field = Some(f.clone());
118                }
119            };
120        }
121
122        update_float_info!(
123            "kMRMediaRemoteNowPlayingInfoDuration",
124            info_guard.as_mut().unwrap().duration
125        );
126        update_float_info!(
127            "kMRMediaRemoteNowPlayingInfoElapsedTime",
128            info_guard.as_mut().unwrap().elapsed_time
129        );
130
131        if let Some(InfoTypes::Data(d)) = info.get("kMRMediaRemoteNowPlayingInfoArtworkData") {
132            info_guard.as_mut().unwrap().album_cover = ImageReader::new(Cursor::new(d))
133                .with_guessed_format()
134                .ok()
135                .and_then(|img| img.decode().ok());
136        }
137
138        info_guard.as_mut().unwrap().info_update_time = info
139            .get("kMRMediaRemoteNowPlayingInfoTimestamp")
140            .and_then(|f| match f {
141                InfoTypes::SystemTime(t) => Some(t.clone()),
142                _ => None,
143            })
144            .or(Some(SystemTime::now()));
145    }
146}
147
148fn update_app(info: Arc<RwLock<Option<NowPlayingInfo>>>) {
149    let mut info_guard = info.write().unwrap();
150    if info_guard.as_ref().is_none() {
151        drop(info_guard);
152        return update_all(info.clone());
153    }
154
155    let mut bundle_id = get_now_playing_client_parent_app_bundle_identifier();
156    if bundle_id.is_none() {
157        bundle_id = get_now_playing_client_bundle_identifier();
158    }
159
160    if let Some(id) = bundle_id {
161        let bundle_info = get_bundle_info(id.as_str());
162        if let Some(info) = bundle_info {
163            info_guard.as_mut().unwrap().bundle_id = Some(id);
164            info_guard.as_mut().unwrap().bundle_name = Some(info.name);
165            info_guard.as_mut().unwrap().bundle_icon = Some(info.icon);
166        }
167    }
168}
169
170impl NowPlaying {
171    fn register(&mut self) {
172        register_for_now_playing_notifications();
173
174        // initialize with current state
175        let info = Arc::clone(&self.info);
176        update_all(info.clone());
177
178        macro_rules! add_observer_macro {
179            ($notification:expr, $update_fn:expr) => {{
180                let info = Arc::clone(&self.info);
181                let listeners = Arc::clone(&self.listeners);
182
183                self.observers.push(add_observer($notification, move || {
184                    $update_fn(info.clone());
185                    for (_, listener) in listeners.clone().lock().unwrap().iter() {
186                        listener(info.read().unwrap());
187                    }
188                }));
189            }};
190        }
191
192        add_observer_macro!(Notification::NowPlayingApplicationDidChange, update_app);
193
194        add_observer_macro!(Notification::NowPlayingInfoDidChange, update_info);
195        // add_observer_macro!(
196        //     Notification::NowPlayingApplicationClientStateDidChange,
197        //     update_info
198        // );
199        // add_observer_macro!(Notification::PlaybackQueueContentItemsChanged, update_info);
200        // add_observer_macro!(Notification::NowPlayingPlaybackQueueChanged, update_info);
201
202        add_observer_macro!(
203            Notification::NowPlayingApplicationIsPlayingDidChange,
204            update_state
205        );
206    }
207
208    /// Creates a new instance of `NowPlaying` and registers for playback notifications.
209    ///
210    /// This function initializes a new `NowPlaying` object, sets up necessary observers,
211    /// and ensures that media metadata is updated upon creation.
212    ///
213    /// # Returns
214    /// - `NowPlaying`: A new instance of the `NowPlaying` struct.
215    ///
216    /// # Example
217    /// ```rust
218    /// use media_remote::NowPlaying;
219    ///
220    /// let now_playing = NowPlaying::new();
221    /// ```
222    pub fn new() -> Self {
223        let mut new_instance = Self {
224            info: Arc::new(RwLock::new(None)),
225            observers: vec![],
226            listeners: Arc::new(Mutex::new(HashMap::new())),
227            token_counter: Arc::new(AtomicU64::new(0)),
228        };
229
230        new_instance.register();
231
232        new_instance
233    }
234
235    /// Retrieves the latest now playing information.
236    ///
237    /// This function provides a read-locked view of the current playing media metadata.
238    ///
239    /// # Note
240    /// - The lock should be released as soon as possible to minimize blocking time.
241    ///
242    /// # Returns
243    /// - `RwLockReadGuard<'_, Option<NowPlayingInfo>>`: A guard to the now playing metadata.
244    ///
245    /// # Example
246    /// ```rust
247    /// use media_remote::NowPlaying;
248    ///
249    /// let now_playing = NowPlaying::new();
250    /// let guard = now_playing.get_info();
251    /// let info = guard.as_ref();
252    ///
253    /// if let Some(info) = info {
254    ///     println!("Currently playing: {:?}", info.title);
255    /// }
256    ///
257    /// drop(guard);
258    /// ```
259    pub fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
260        let mut info_guard = self.info.write().unwrap();
261        let info = info_guard.as_mut();
262
263        if info.is_some() {
264            let info = info.unwrap();
265            if info.is_playing.is_some_and(|x| x)
266                && info.elapsed_time.is_some()
267                && info.info_update_time.is_some()
268            {
269                info.elapsed_time = Some(
270                    info.elapsed_time.unwrap()
271                        + info.info_update_time.unwrap().elapsed().unwrap().as_secs() as f64,
272                );
273                info.info_update_time = Some(SystemTime::now())
274            }
275        }
276
277        drop(info_guard);
278
279        self.info.read().unwrap()
280    }
281}
282
283impl Drop for NowPlaying {
284    fn drop(&mut self) {
285        while let Some(observer) = self.observers.pop() {
286            remove_observer(observer);
287        }
288
289        unregister_for_now_playing_notifications();
290    }
291}
292
293impl Controller for NowPlaying {
294    fn is_info_some(&self) -> bool {
295        self.info.read().unwrap().as_ref().is_some()
296    }
297}
298
299impl Subscription for NowPlaying {
300    fn get_info(&self) -> RwLockReadGuard<'_, Option<NowPlayingInfo>> {
301        self.get_info()
302    }
303
304    fn get_token_counter(&self) -> Arc<AtomicU64> {
305        self.token_counter.clone()
306    }
307
308    fn get_listeners(
309        &self,
310    ) -> Arc<
311        Mutex<
312            HashMap<
313                super::subscription::ListenerToken,
314                Box<dyn Fn(RwLockReadGuard<'_, Option<NowPlayingInfo>>) + Send + Sync>,
315            >,
316        >,
317    > {
318        self.listeners.clone()
319    }
320}