mpris_async/
fake_progress.rs

1//! Used to make [`crate::progress::ProgressStream`] possible. The code is identical to mpris's
2//! Progress.
3use std::time::{Instant, Duration};
4
5use mpris::{LoopStatus, PlaybackStatus, Metadata, Progress};
6
7/// Used Because Cloning Progress is impossible, making [`crate::progress::ProgressStream`]
8/// impossible for me to implement
9#[derive(Clone)]
10pub struct ProgressClone {
11    pub(crate) metadata: Metadata,
12    pub(crate) playback_status: PlaybackStatus,
13    pub(crate) shuffle: bool,
14    pub(crate) loop_status: LoopStatus,
15
16    /// When this Progress was constructed, in order to calculate how old it is.
17    pub(crate) instant: Instant,
18
19    pub(crate) position: Duration,
20    pub(crate) rate: f64,
21    pub(crate) current_volume: f64,
22}
23
24impl ProgressClone {
25    pub(crate) fn from(progress: &Progress) -> Self {
26        ProgressClone {
27            metadata: progress.metadata().clone(),
28            playback_status: progress.playback_status(),
29            shuffle: progress.shuffle(),
30            loop_status: progress.loop_status(),
31            instant: *progress.created_at(),
32            position: progress.position(),
33            rate: progress.playback_rate(),
34            current_volume: progress.current_volume(),
35        }
36    }
37    /// The track metadata at the point in time that this Progress was constructed.
38    pub fn metadata(&self) -> &Metadata {
39        &self.metadata
40    }
41
42    /// The playback status at the point in time that this Progress was constructed.
43    pub fn playback_status(&self) -> PlaybackStatus {
44        self.playback_status
45    }
46
47    /// The shuffle status at the point in time that this Progress was constructed.
48    pub fn shuffle(&self) -> bool {
49        self.shuffle
50    }
51
52    /// The loop status at the point in time that this Progress was constructed.
53    pub fn loop_status(&self) -> LoopStatus {
54        self.loop_status
55    }
56
57    /// The playback rate at the point in time that this Progress was constructed.
58    pub fn playback_rate(&self) -> f64 {
59        self.rate
60    }
61
62    /// Returns the length of the current track as a [`Duration`].
63    pub fn length(&self) -> Option<Duration> {
64        self.metadata.length()
65    }
66
67    /// Returns the current position of the current track as a [`Duration`].
68    ///
69    /// This method will calculate the expected position of the track at the instant of the
70    /// invocation using the [`initial_position`](Self::initial_position) and knowledge of how long ago that position was
71    /// determined.
72    ///
73    /// **Note:** Some players might not support this and will return a bad position. Spotify is
74    /// one such example. There is no reliable way of detecting problematic players, so it will be
75    /// up to your client to check for this.
76    ///
77    /// One way of doing this is to query the [`initial_position`](Self::initial_position) for two measures with the
78    /// [`PlaybackStatus::Playing`] and if both are `0`, then it is likely that this client does not
79    /// support positions.
80    pub fn position(&self) -> Duration {
81        self.position + self.elapsed()
82    }
83
84    /// Returns the position that the current track was at when the [`Progress`] was created.
85    ///
86    /// This is the number that was returned for the [`Position`][position] property in the MPRIS2 interface.
87    ///
88    /// [position]: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html#Property:Position
89    pub fn initial_position(&self) -> Duration {
90        self.position
91    }
92
93    /// The instant where this [`Progress`] was recorded.
94    ///
95    /// See: [`age`](Self::age).
96    pub fn created_at(&self) -> &Instant {
97        &self.instant
98    }
99
100    /// Returns the age of the data as a [`Duration`].
101    ///
102    /// If the [`Progress`] has a high age it is more likely to be out of date.
103    pub fn age(&self) -> Duration {
104        self.instant.elapsed()
105    }
106
107    /// Returns the player's volume as it was at the time of refresh.
108    ///
109    /// See: [`Player::get_volume`](mpris::Player::get_volume).
110    pub fn current_volume(&self) -> f64 {
111        self.current_volume
112    }
113
114    fn elapsed(&self) -> Duration {
115        let elapsed_ms = match self.playback_status {
116            PlaybackStatus::Playing => {
117                DurationExtensions::as_millis(&self.age()) as f64 * self.rate
118            }
119            _ => 0.0,
120        };
121        Duration::from_millis(elapsed_ms as u64)
122    }
123}
124
125
126pub(crate) trait DurationExtensions {
127    // Rust beta has a from_micros function that is unstable.
128    fn from_micros_ext(_: u64) -> Duration;
129    fn as_millis(&self) -> u64;
130    fn as_micros(&self) -> u64;
131}
132
133impl DurationExtensions for Duration {
134    fn from_micros_ext(micros: u64) -> Duration {
135        let whole_seconds = micros / 1_000_000;
136        let rest = (micros - (whole_seconds * 1_000_000)) as u32;
137        Duration::new(whole_seconds, rest * 1000)
138    }
139
140    fn as_millis(&self) -> u64 {
141        self.as_secs() * 1000 + u64::from(self.subsec_millis())
142    }
143
144    fn as_micros(&self) -> u64 {
145        self.as_secs() * 1000 * 1000 + u64::from(self.subsec_micros())
146    }
147}