A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 167 lines 6.5 kB view raw
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}