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}