A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1use std::{env, future, sync::Arc};
2
3use anyhow::Error;
4use async_std::stream::StreamExt;
5use mpris_server::{LoopStatus, Metadata, PlaybackStatus, Player, Time, TrackId};
6use rockbox_graphql::{
7 schema::objects::{audio_status::AudioStatus, track::Track},
8 simplebroker::SimpleBroker,
9};
10use rockbox_rpc::api::rockbox::v1alpha1::{
11 playback_service_client::PlaybackServiceClient, settings_service_client::SettingsServiceClient,
12 sound_service_client::SoundServiceClient, AdjustVolumeRequest, GetGlobalSettingsRequest,
13 HardStopRequest, NextRequest, PauseRequest, PlayOrPauseRequest, PlayRequest, PlayTrackRequest,
14 PreviousRequest, ResumeRequest, SaveSettingsRequest,
15};
16use tokio::sync::Mutex;
17
18pub mod macros;
19
20const PLAYER_NAME: &str = "rockbox";
21
22pub struct MprisServer {
23 player: Arc<Player>,
24}
25
26impl MprisServer {
27 pub async fn start() -> Result<Self, Error> {
28 let host = env::var("ROCKBOX_HOST").unwrap_or_else(|_| "localhost".to_string());
29 let port = env::var("ROCKBOX_PORT").unwrap_or_else(|_| "6061".to_string());
30 let url = format!("tcp://{}:{}", host, port);
31
32 let rt = tokio::runtime::Runtime::new()?;
33 let client = Arc::new(Mutex::new(
34 rt.block_on(PlaybackServiceClient::connect(url.clone()))?,
35 ));
36 let settings_service_client = Arc::new(Mutex::new(
37 rt.block_on(SettingsServiceClient::connect(url.clone()))?,
38 ));
39 let sound_service_client = Arc::new(Mutex::new(
40 rt.block_on(SoundServiceClient::connect(url.clone()))?,
41 ));
42
43 let player = Player::builder(PLAYER_NAME)
44 .can_play(true)
45 .can_pause(true)
46 .can_seek(true)
47 .can_go_next(true)
48 .can_go_previous(true)
49 .can_control(true)
50 .build()
51 .await?;
52
53 player.set_identity("Rockbox").await?;
54
55 connect_player_action!(
56 player,
57 client,
58 connect_previous,
59 previous,
60 PreviousRequest {}
61 );
62 connect_player_action!(player, client, connect_next, next, NextRequest {});
63 connect_player_action!(player, client, connect_play, resume, ResumeRequest {});
64 connect_player_action!(player, client, connect_pause, pause, PauseRequest {});
65 connect_player_action!(
66 player,
67 client,
68 connect_play_pause,
69 play_or_pause,
70 PlayOrPauseRequest {}
71 );
72 connect_player_seek_action!(player, client);
73 connect_player_set_position_action!(player, client);
74 connect_player_action!(player, client, connect_stop, hard_stop, HardStopRequest {});
75 connect_player_volume_action!(player, sound_service_client, settings_service_client);
76 connect_player_shuffle_action!(player, settings_service_client);
77 connect_player_loop_status_action!(player, settings_service_client);
78 connect_player_open_uri_action!(player, client);
79
80 let server = MprisServer {
81 player: Arc::new(player),
82 };
83
84 async_std::task::spawn_local(server.player.run());
85
86 let player_mutex = Arc::new(std::sync::Mutex::new(server.player.clone()));
87 let player_mutex_clone = Arc::clone(&player_mutex);
88
89 async_std::task::spawn_local(async move {
90 let mut subscription = SimpleBroker::<AudioStatus>::subscribe();
91 while let Some(response) = subscription.next().await {
92 let player = player_mutex_clone.lock().unwrap();
93 match response.status {
94 1 => match player.set_playback_status(PlaybackStatus::Playing).await {
95 Ok(_) => {}
96 Err(e) => {
97 eprintln!("Error: {}", e);
98 }
99 },
100 3 => match player.set_playback_status(PlaybackStatus::Paused).await {
101 Ok(_) => {}
102 Err(e) => {
103 eprintln!("Error: {}", e);
104 }
105 },
106 _ => match player.set_playback_status(PlaybackStatus::Stopped).await {
107 Ok(_) => {}
108 Err(e) => {
109 eprintln!("Error: {}", e);
110 }
111 },
112 }
113 }
114 });
115
116 async_std::task::spawn_local(async move {
117 let port = std::env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string());
118 let mut subscription = SimpleBroker::<Track>::subscribe();
119 while let Some(track) = subscription.next().await {
120 let player = player_mutex.lock().unwrap();
121 let mut metadata = Metadata::builder()
122 .title(track.title)
123 .artist([track.artist])
124 .album(track.album)
125 .album_artist([track.album_artist])
126 .track_number(track.tracknum)
127 .disc_number(track.discnum)
128 .length(Time::from_millis(track.length as i64));
129
130 if let Some(album_art) = track.album_art {
131 metadata = match album_art.starts_with("http") {
132 true => metadata.art_url(album_art),
133 false => metadata
134 .art_url(format!("http://localhost:{}/covers/{}", port, album_art)),
135 }
136 }
137
138 if let Some(trackid) = track.id {
139 metadata = metadata.trackid(
140 TrackId::try_from(format!("/rockbox/tracks/{}", trackid)).unwrap(),
141 );
142 }
143
144 let metadata = metadata.build();
145
146 match player.set_metadata(metadata).await {
147 Ok(_) => {}
148 Err(e) => {
149 eprintln!("Error: {}", e);
150 }
151 }
152
153 player.set_position(Time::from_millis(track.elapsed as i64));
154 match player.seeked(Time::from_millis(track.elapsed as i64)).await {
155 Ok(_) => {}
156 Err(e) => {
157 eprintln!("Error: {}", e);
158 }
159 }
160 }
161 });
162
163 future::pending::<()>().await;
164
165 Ok(server)
166 }
167}