1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Implements the root interface of the MPRIS specification.
//!
//! [org.mpris.MediaPlayer2](https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html)
use futures::executor::block_on;
use mpris_server::{
RootInterface,
zbus::{Error as ZbusError, fdo},
};
use crate::Mpris;
pub const SUPPORTED_MIME_TYPES: [&str; 4] = ["audio/mp3", "audio/wav", "audio/ogg", "audio/flac"];
impl RootInterface for Mpris {
async fn raise(&self) -> fdo::Result<()> {
Ok(())
}
async fn quit(&self) -> fdo::Result<()> {
block_on(self.daemon.clone().daemon_shutdown(()))
.map_err(|e| fdo::Error::Failed(e.to_string()))?;
Ok(())
}
async fn can_quit(&self) -> fdo::Result<bool> {
Ok(true)
}
async fn fullscreen(&self) -> fdo::Result<bool> {
Ok(false)
}
async fn set_fullscreen(&self, _: bool) -> Result<(), ZbusError> {
Err(ZbusError::Unsupported)
}
async fn can_set_fullscreen(&self) -> fdo::Result<bool> {
Ok(false)
}
async fn can_raise(&self) -> fdo::Result<bool> {
// TODO: Maybe in the future we can implement raising the player by starting the GUI.
Ok(false)
}
async fn has_track_list(&self) -> fdo::Result<bool> {
// TODO: when we implement the track list interface, we should return true here.
Ok(false)
}
async fn identity(&self) -> fdo::Result<String> {
Ok("MECOMP Music Player".to_string())
}
async fn desktop_entry(&self) -> fdo::Result<String> {
// TODO: bundle a desktop entry with the application so we can support this
Err(fdo::Error::Failed("Desktop entry not found".to_string()))
}
async fn supported_uri_schemes(&self) -> fdo::Result<Vec<String>> {
Ok(vec!["file".to_string()])
}
async fn supported_mime_types(&self) -> fdo::Result<Vec<String>> {
Ok(SUPPORTED_MIME_TYPES
.iter()
.map(ToString::to_string)
.collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_utils::fixtures;
use pretty_assertions::assert_eq;
#[tokio::test]
/// """
/// The media player may be unable to control how its user interface is displayed, or it may not have a graphical user interface at all.
/// In this case, the [CanRaise] property is false and this method does nothing.
/// """
///
/// Mecomp does not have a graphical user interface, so it does not support raising.
async fn raise() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.can_raise().await;
assert_eq!(result, Ok(false));
let result = mpris.raise().await;
assert_eq!(result, Ok(()));
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
/// """
/// The media player may refuse to allow clients to shut it down. In this case, the [CanQuit] property is false and this method does nothing.
/// Note: Media players which can be D-Bus activated, or for which there is no sensibly easy way to terminate a running instance
/// (via the main interface or a notification area icon for example) should allow clients to use this method. Otherwise, it should not be needed.
/// """
///
/// Mecomp allows clients to shut it down.
async fn quit() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.can_quit().await;
assert_eq!(result, Ok(true));
// this is safe to do since there is no daemon running.
let result = mpris.quit().await;
assert_eq!(result, Ok(()));
}
#[tokio::test]
/// """
/// If false, attempting to set [Fullscreen] will have no effect, and may raise an error.
/// If true, attempting to set [Fullscreen] will not raise an error, and (if it is different from the current value)
/// will cause the media player to attempt to enter or exit fullscreen mode.
///
/// Note that the media player may be unable to fulfil the request. In this case, the value will not change.
/// If the media player knows in advance that it will not be able to fulfil the request, however, this property should be false.
/// """
/// Mecomp does not support fullscreen mode.
async fn fullscreen() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.fullscreen().await;
assert_eq!(result, Ok(false));
let result = mpris.can_set_fullscreen().await;
assert_eq!(result, Ok(false));
let result = mpris.set_fullscreen(true).await;
assert_eq!(result, Err(ZbusError::Unsupported));
}
#[tokio::test]
/// """
/// Indicates whether the /org/mpris/MediaPlayer2 object implements the [TrackList interface].
/// """
///
/// Mecomp currently does not implement the TrackList interface.
async fn has_track_list() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.has_track_list().await;
assert_eq!(result, Ok(false));
}
#[tokio::test]
/// """
/// A friendly name to identify the media player to users (eg: "VLC media player").
/// """
///
/// Mecomp identifies itself as "MECOMP Music Player".
async fn identity() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.identity().await;
assert_eq!(result, Ok("MECOMP Music Player".to_string()));
}
#[tokio::test]
/// """
/// The desktop entry file as described in the [Desktop Entry Specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/).
/// """
///
/// Mecomp currently doesn't have a desktop app, so it does not make sense to return a desktop entry.
/// TODO: Once I've implemented the GUI, it should ship with a desktop entry that can be returned here.
async fn desktop_entry() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.desktop_entry().await;
assert_eq!(
result,
Err(fdo::Error::Failed("Desktop entry not found".to_string()))
);
}
#[tokio::test]
/// """
/// The URI schemes supported by the media player.
/// """
///
/// Mecomp can only play files from the local filesystem, so it supports the "file" URI scheme.
async fn supported_uri_schemes() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.supported_uri_schemes().await;
assert_eq!(result, Ok(vec!["file".to_string()]));
}
#[tokio::test]
/// """
/// The mime-types supported by the media player.
/// """
///
/// Mecomp can play anything that it can decode, so mime-types supported by both the [lofty-rs](https://crates.io/crates/lofty) and [rodio](https://0crates.io/crates/rodio) crates are supported.
/// So, mp3, wav, ogg (vorbis), and flac
async fn supported_mime_types() {
let (mpris, _, _, _) = fixtures().await;
let result = mpris.supported_mime_types().await;
assert_eq!(
result,
Ok(SUPPORTED_MIME_TYPES
.iter()
.map(ToString::to_string)
.collect())
);
}
}