Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

call some internal rockbox api from gRPC and GraphQL

+478 -28
+1
crates/graphql/src/schema/mod.rs
··· 6 6 7 7 pub mod browse; 8 8 pub mod metadata; 9 + pub mod objects; 9 10 pub mod playback; 10 11 pub mod playlist; 11 12 pub mod settings;
+1
crates/graphql/src/schema/objects/mod.rs
··· 1 + pub mod track;
+235
crates/graphql/src/schema/objects/track.rs
··· 1 + use async_graphql::*; 2 + use rockbox_sys::Mp3Entry; 3 + use serde::Serialize; 4 + 5 + #[derive(Default, Clone, Serialize)] 6 + pub struct Track { 7 + pub title: String, 8 + pub artist: String, 9 + pub album: String, 10 + pub genre: String, 11 + pub disc: String, 12 + pub track_string: String, 13 + pub year_string: String, 14 + pub composer: String, 15 + pub comment: String, 16 + pub album_artist: String, 17 + pub grouping: String, 18 + pub discnum: i32, 19 + pub tracknum: i32, 20 + pub layer: i32, 21 + pub year: i32, 22 + pub bitrate: u32, 23 + pub frequency: u64, 24 + pub filesize: u64, 25 + pub length: u64, 26 + pub elapsed: u64, 27 + } 28 + 29 + #[Object] 30 + impl Track { 31 + async fn title(&self) -> &str { 32 + &self.title 33 + } 34 + 35 + async fn artist(&self) -> &str { 36 + &self.artist 37 + } 38 + 39 + async fn album(&self) -> &str { 40 + &self.album 41 + } 42 + 43 + async fn genre(&self) -> &str { 44 + &self.genre 45 + } 46 + 47 + async fn disc(&self) -> &str { 48 + &self.disc 49 + } 50 + 51 + async fn track_string(&self) -> &str { 52 + &self.track_string 53 + } 54 + 55 + async fn year_string(&self) -> &str { 56 + &self.year_string 57 + } 58 + 59 + async fn composer(&self) -> &str { 60 + &self.composer 61 + } 62 + 63 + async fn comment(&self) -> &str { 64 + &self.comment 65 + } 66 + 67 + async fn album_artist(&self) -> &str { 68 + &self.album_artist 69 + } 70 + 71 + async fn grouping(&self) -> &str { 72 + &self.grouping 73 + } 74 + 75 + async fn discnum(&self) -> i32 { 76 + self.discnum 77 + } 78 + 79 + async fn tracknum(&self) -> i32 { 80 + self.tracknum 81 + } 82 + 83 + async fn layer(&self) -> i32 { 84 + self.layer 85 + } 86 + 87 + async fn year(&self) -> i32 { 88 + self.year 89 + } 90 + 91 + async fn bitrate(&self) -> u32 { 92 + self.bitrate 93 + } 94 + 95 + async fn frequency(&self) -> u64 { 96 + self.frequency 97 + } 98 + 99 + async fn filesize(&self) -> u64 { 100 + self.filesize 101 + } 102 + 103 + async fn length(&self) -> u64 { 104 + self.length 105 + } 106 + 107 + async fn elapsed(&self) -> u64 { 108 + self.elapsed 109 + } 110 + } 111 + 112 + impl From<Mp3Entry> for Track { 113 + fn from(mp3entry: Mp3Entry) -> Self { 114 + let title = match mp3entry.title.is_null() { 115 + true => "No title".to_string(), 116 + false => unsafe { 117 + std::ffi::CStr::from_ptr(mp3entry.title) 118 + .to_string_lossy() 119 + .to_string() 120 + }, 121 + }; 122 + let artist = match mp3entry.artist.is_null() { 123 + true => "No artist".to_string(), 124 + false => unsafe { 125 + std::ffi::CStr::from_ptr(mp3entry.artist) 126 + .to_string_lossy() 127 + .to_string() 128 + }, 129 + }; 130 + let album = match mp3entry.album.is_null() { 131 + true => "No album".to_string(), 132 + false => unsafe { 133 + std::ffi::CStr::from_ptr(mp3entry.album) 134 + .to_string_lossy() 135 + .to_string() 136 + }, 137 + }; 138 + let genre = match mp3entry.genre_string.is_null() { 139 + true => "No genre".to_string(), 140 + false => unsafe { 141 + std::ffi::CStr::from_ptr(mp3entry.genre_string) 142 + .to_string_lossy() 143 + .to_string() 144 + }, 145 + }; 146 + let disc = match mp3entry.disc_string.is_null() { 147 + true => "No disc".to_string(), 148 + false => unsafe { 149 + std::ffi::CStr::from_ptr(mp3entry.disc_string) 150 + .to_string_lossy() 151 + .to_string() 152 + }, 153 + }; 154 + let track_string = match mp3entry.track_string.is_null() { 155 + true => "No track_string".to_string(), 156 + false => unsafe { 157 + std::ffi::CStr::from_ptr(mp3entry.track_string) 158 + .to_string_lossy() 159 + .to_string() 160 + }, 161 + }; 162 + let year_string = match mp3entry.year_string.is_null() { 163 + true => "No year_string".to_string(), 164 + false => unsafe { 165 + std::ffi::CStr::from_ptr(mp3entry.year_string) 166 + .to_string_lossy() 167 + .to_string() 168 + }, 169 + }; 170 + let composer = match mp3entry.composer.is_null() { 171 + true => "No composer".to_string(), 172 + false => unsafe { 173 + std::ffi::CStr::from_ptr(mp3entry.composer) 174 + .to_string_lossy() 175 + .to_string() 176 + }, 177 + }; 178 + let comment = match mp3entry.comment.is_null() { 179 + true => "No comment".to_string(), 180 + false => unsafe { 181 + std::ffi::CStr::from_ptr(mp3entry.comment) 182 + .to_string_lossy() 183 + .to_string() 184 + }, 185 + }; 186 + let album_artist = match mp3entry.albumartist.is_null() { 187 + true => "No album_artist".to_string(), 188 + false => unsafe { 189 + std::ffi::CStr::from_ptr(mp3entry.albumartist) 190 + .to_string_lossy() 191 + .to_string() 192 + }, 193 + }; 194 + let grouping = match mp3entry.grouping.is_null() { 195 + true => "No grouping".to_string(), 196 + false => unsafe { 197 + std::ffi::CStr::from_ptr(mp3entry.grouping) 198 + .to_string_lossy() 199 + .to_string() 200 + }, 201 + }; 202 + let discnum = mp3entry.discnum; 203 + let tracknum = mp3entry.tracknum; 204 + let layer = mp3entry.layer; 205 + let year = mp3entry.year; 206 + let bitrate = mp3entry.bitrate; 207 + let frequency = mp3entry.frequency; 208 + let filesize = mp3entry.filesize; 209 + let length = mp3entry.length; 210 + let elapsed = mp3entry.elapsed; 211 + 212 + Track { 213 + title, 214 + artist, 215 + album, 216 + genre, 217 + disc, 218 + track_string, 219 + year_string, 220 + composer, 221 + comment, 222 + album_artist, 223 + grouping, 224 + discnum, 225 + tracknum, 226 + layer, 227 + year, 228 + bitrate, 229 + frequency, 230 + filesize, 231 + length, 232 + elapsed, 233 + } 234 + } 235 + }
+19 -6
crates/graphql/src/schema/playback.rs
··· 1 1 use async_graphql::*; 2 + use rockbox_sys as rb; 3 + 4 + use crate::schema::objects::track::Track; 2 5 3 6 #[derive(Default)] 4 7 pub struct PlaybackQuery; ··· 6 9 #[Object] 7 10 impl PlaybackQuery { 8 11 async fn status(&self) -> String { 12 + rb::playback::status(); 9 13 "status".to_string() 10 14 } 11 15 12 - async fn current_track(&self) -> String { 13 - "current_track".to_string() 16 + async fn current_track(&self) -> Option<Track> { 17 + let mp3entry = rb::playback::current_track(); 18 + Some(mp3entry.into()) 14 19 } 15 20 16 - async fn get_file_position(&self) -> String { 17 - "get_file_position".to_string() 21 + async fn get_file_position(&self) -> i32 { 22 + rb::playback::get_file_pos() 18 23 } 19 24 } 20 25 ··· 23 28 24 29 #[Object] 25 30 impl PlaybackMutation { 26 - async fn play(&self) -> String { 31 + async fn play(&self, _ctx: &Context<'_>, elapsed: i64, offset: i64) -> String { 32 + rb::playback::play(elapsed, offset); 27 33 "play".to_string() 28 34 } 29 35 30 36 async fn pause(&self) -> String { 37 + rb::playback::pause(); 31 38 "pause".to_string() 32 39 } 33 40 34 41 async fn resume(&self) -> String { 42 + rb::playback::resume(); 35 43 "resume".to_string() 36 44 } 37 45 38 46 async fn next(&self) -> String { 47 + rb::playback::next(); 39 48 "next".to_string() 40 49 } 41 50 42 51 async fn previous(&self) -> String { 52 + rb::playback::prev(); 43 53 "previous".to_string() 44 54 } 45 55 46 - async fn fast_forward_rewind(&self) -> String { 56 + async fn fast_forward_rewind(&self, _ctx: &Context<'_>, new_time: i32) -> String { 57 + rb::playback::ff_rewind(new_time); 47 58 "fast_forward_rewind".to_string() 48 59 } 49 60 50 61 async fn flush_and_reload_tracks(&self) -> String { 62 + rb::playback::flush_and_reload_tracks(); 51 63 "flush_and_reload_tracks".to_string() 52 64 } 53 65 54 66 async fn hard_stop(&self) -> String { 67 + rb::playback::hard_stop(); 55 68 "hard_stop".to_string() 56 69 } 57 70 }
+2 -2
crates/graphql/src/server.rs
··· 54 54 let addr = format!("{}:{}", "0.0.0.0", graphql_port); 55 55 56 56 println!( 57 - "{} server running on {}", 57 + "{} server is running on {}", 58 58 "Rockbox GraphQL".bright_purple(), 59 - format!("http://{}", addr).bright_green() 59 + addr.bright_green() 60 60 ); 61 61 62 62 HttpServer::new(move || {
+21
crates/rpc/proto/rockbox/v1alpha1/playback.proto
··· 45 45 } 46 46 47 47 message StatusResponse { 48 + int32 status = 1; 48 49 } 49 50 50 51 message CurrentTrackRequest { 51 52 } 52 53 53 54 message CurrentTrackResponse { 55 + string title = 1; 56 + string artist = 2; 57 + string album = 3; 58 + string genre = 4; 59 + string disc = 5; 60 + string track_string = 6; 61 + string year_string = 7; 62 + string composer = 8; 63 + string comment = 9; 64 + string album_artist = 10; 65 + string grouping = 11; 66 + int32 discnum = 12; 67 + int32 tracknum = 13; 68 + int32 layer = 14; 69 + int32 year = 15; 70 + uint32 bitrate = 16; 71 + uint64 frequency = 17; 72 + uint64 filesize = 18; 73 + uint64 length = 19; 74 + uint64 elapsed = 20; 54 75 } 55 76 56 77 message FlushAndReloadTracksRequest {
+47 -3
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 863 863 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 864 864 pub struct StatusRequest {} 865 865 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 866 - pub struct StatusResponse {} 866 + pub struct StatusResponse { 867 + #[prost(int32, tag = "1")] 868 + pub status: i32, 869 + } 867 870 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 868 871 pub struct CurrentTrackRequest {} 869 - #[derive(Clone, Copy, PartialEq, ::prost::Message)] 870 - pub struct CurrentTrackResponse {} 872 + #[derive(Clone, PartialEq, ::prost::Message)] 873 + pub struct CurrentTrackResponse { 874 + #[prost(string, tag = "1")] 875 + pub title: ::prost::alloc::string::String, 876 + #[prost(string, tag = "2")] 877 + pub artist: ::prost::alloc::string::String, 878 + #[prost(string, tag = "3")] 879 + pub album: ::prost::alloc::string::String, 880 + #[prost(string, tag = "4")] 881 + pub genre: ::prost::alloc::string::String, 882 + #[prost(string, tag = "5")] 883 + pub disc: ::prost::alloc::string::String, 884 + #[prost(string, tag = "6")] 885 + pub track_string: ::prost::alloc::string::String, 886 + #[prost(string, tag = "7")] 887 + pub year_string: ::prost::alloc::string::String, 888 + #[prost(string, tag = "8")] 889 + pub composer: ::prost::alloc::string::String, 890 + #[prost(string, tag = "9")] 891 + pub comment: ::prost::alloc::string::String, 892 + #[prost(string, tag = "10")] 893 + pub album_artist: ::prost::alloc::string::String, 894 + #[prost(string, tag = "11")] 895 + pub grouping: ::prost::alloc::string::String, 896 + #[prost(int32, tag = "12")] 897 + pub discnum: i32, 898 + #[prost(int32, tag = "13")] 899 + pub tracknum: i32, 900 + #[prost(int32, tag = "14")] 901 + pub layer: i32, 902 + #[prost(int32, tag = "15")] 903 + pub year: i32, 904 + #[prost(uint32, tag = "16")] 905 + pub bitrate: u32, 906 + #[prost(uint64, tag = "17")] 907 + pub frequency: u64, 908 + #[prost(uint64, tag = "18")] 909 + pub filesize: u64, 910 + #[prost(uint64, tag = "19")] 911 + pub length: u64, 912 + #[prost(uint64, tag = "20")] 913 + pub elapsed: u64, 914 + } 871 915 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 872 916 pub struct FlushAndReloadTracksRequest {} 873 917 #[derive(Clone, Copy, PartialEq, ::prost::Message)]
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+128
crates/rpc/src/lib.rs
··· 10 10 pub mod api { 11 11 #[path = ""] 12 12 pub mod rockbox { 13 + use rockbox_sys::Mp3Entry; 14 + use v1alpha1::CurrentTrackResponse; 15 + 13 16 #[path = "rockbox.v1alpha1.rs"] 14 17 pub mod v1alpha1; 15 18 16 19 pub(crate) const FILE_DESCRIPTOR_SET: &[u8] = include_bytes!("api/rockbox_descriptor.bin"); 20 + 21 + impl From<Mp3Entry> for CurrentTrackResponse { 22 + fn from(mp3entry: Mp3Entry) -> Self { 23 + let title = match mp3entry.title.is_null() { 24 + true => "No title".to_string(), 25 + false => unsafe { 26 + std::ffi::CStr::from_ptr(mp3entry.title) 27 + .to_string_lossy() 28 + .to_string() 29 + }, 30 + }; 31 + let artist = match mp3entry.artist.is_null() { 32 + true => "No artist".to_string(), 33 + false => unsafe { 34 + std::ffi::CStr::from_ptr(mp3entry.artist) 35 + .to_string_lossy() 36 + .to_string() 37 + }, 38 + }; 39 + let album = match mp3entry.album.is_null() { 40 + true => "No album".to_string(), 41 + false => unsafe { 42 + std::ffi::CStr::from_ptr(mp3entry.album) 43 + .to_string_lossy() 44 + .to_string() 45 + }, 46 + }; 47 + let genre = match mp3entry.genre_string.is_null() { 48 + true => "No genre".to_string(), 49 + false => unsafe { 50 + std::ffi::CStr::from_ptr(mp3entry.genre_string) 51 + .to_string_lossy() 52 + .to_string() 53 + }, 54 + }; 55 + let disc = match mp3entry.disc_string.is_null() { 56 + true => "No disc".to_string(), 57 + false => unsafe { 58 + std::ffi::CStr::from_ptr(mp3entry.disc_string) 59 + .to_string_lossy() 60 + .to_string() 61 + }, 62 + }; 63 + let track_string = match mp3entry.track_string.is_null() { 64 + true => "No track_string".to_string(), 65 + false => unsafe { 66 + std::ffi::CStr::from_ptr(mp3entry.track_string) 67 + .to_string_lossy() 68 + .to_string() 69 + }, 70 + }; 71 + let year_string = match mp3entry.year_string.is_null() { 72 + true => "No year_string".to_string(), 73 + false => unsafe { 74 + std::ffi::CStr::from_ptr(mp3entry.year_string) 75 + .to_string_lossy() 76 + .to_string() 77 + }, 78 + }; 79 + let composer = match mp3entry.composer.is_null() { 80 + true => "No composer".to_string(), 81 + false => unsafe { 82 + std::ffi::CStr::from_ptr(mp3entry.composer) 83 + .to_string_lossy() 84 + .to_string() 85 + }, 86 + }; 87 + let album_artist = match mp3entry.albumartist.is_null() { 88 + true => "No album_artist".to_string(), 89 + false => unsafe { 90 + std::ffi::CStr::from_ptr(mp3entry.albumartist) 91 + .to_string_lossy() 92 + .to_string() 93 + }, 94 + }; 95 + let comment = match mp3entry.comment.is_null() { 96 + true => "No comment".to_string(), 97 + false => unsafe { 98 + std::ffi::CStr::from_ptr(mp3entry.comment) 99 + .to_string_lossy() 100 + .to_string() 101 + }, 102 + }; 103 + let grouping = match mp3entry.grouping.is_null() { 104 + true => "No grouping".to_string(), 105 + false => unsafe { 106 + std::ffi::CStr::from_ptr(mp3entry.grouping) 107 + .to_string_lossy() 108 + .to_string() 109 + }, 110 + }; 111 + let discnum = mp3entry.discnum; 112 + let tracknum = mp3entry.tracknum; 113 + let layer = mp3entry.layer; 114 + let year = mp3entry.year; 115 + let bitrate = mp3entry.bitrate; 116 + let frequency = mp3entry.frequency; 117 + let filesize = mp3entry.filesize; 118 + let length = mp3entry.length; 119 + let elapsed = mp3entry.elapsed; 120 + 121 + CurrentTrackResponse { 122 + title, 123 + artist, 124 + album, 125 + genre, 126 + disc, 127 + track_string, 128 + year_string, 129 + composer, 130 + album_artist, 131 + comment, 132 + grouping, 133 + discnum, 134 + tracknum, 135 + layer, 136 + year, 137 + bitrate, 138 + frequency, 139 + filesize, 140 + length, 141 + elapsed, 142 + } 143 + } 144 + } 17 145 } 18 146 }
+16 -2
crates/rpc/src/playback.rs
··· 1 1 use crate::api::rockbox::v1alpha1::{playback_service_server::PlaybackService, *}; 2 + use rockbox_sys as rb; 2 3 3 4 #[derive(Default)] 4 5 pub struct Playback; ··· 9 10 &self, 10 11 request: tonic::Request<PlayRequest>, 11 12 ) -> Result<tonic::Response<PlayResponse>, tonic::Status> { 13 + let params = request.into_inner(); 14 + rb::playback::play(params.elapsed, params.offset); 12 15 Ok(tonic::Response::new(PlayResponse::default())) 13 16 } 14 17 ··· 16 19 &self, 17 20 request: tonic::Request<PauseRequest>, 18 21 ) -> Result<tonic::Response<PauseResponse>, tonic::Status> { 22 + rb::playback::pause(); 19 23 Ok(tonic::Response::new(PauseResponse::default())) 20 24 } 21 25 ··· 23 27 &self, 24 28 request: tonic::Request<ResumeRequest>, 25 29 ) -> Result<tonic::Response<ResumeResponse>, tonic::Status> { 30 + rb::playback::resume(); 26 31 Ok(tonic::Response::new(ResumeResponse::default())) 27 32 } 28 33 ··· 30 35 &self, 31 36 request: tonic::Request<NextRequest>, 32 37 ) -> Result<tonic::Response<NextResponse>, tonic::Status> { 38 + rb::playback::next(); 33 39 Ok(tonic::Response::new(NextResponse::default())) 34 40 } 35 41 ··· 37 43 &self, 38 44 request: tonic::Request<PreviousRequest>, 39 45 ) -> Result<tonic::Response<PreviousResponse>, tonic::Status> { 46 + rb::playback::prev(); 40 47 Ok(tonic::Response::new(PreviousResponse::default())) 41 48 } 42 49 ··· 44 51 &self, 45 52 request: tonic::Request<FastForwardRewindRequest>, 46 53 ) -> Result<tonic::Response<FastForwardRewindResponse>, tonic::Status> { 54 + let params = request.into_inner(); 55 + rb::playback::ff_rewind(params.new_time); 47 56 Ok(tonic::Response::new(FastForwardRewindResponse::default())) 48 57 } 49 58 ··· 51 60 &self, 52 61 request: tonic::Request<StatusRequest>, 53 62 ) -> Result<tonic::Response<StatusResponse>, tonic::Status> { 63 + let status = rb::playback::status(); 54 64 Ok(tonic::Response::new(StatusResponse::default())) 55 65 } 56 66 ··· 58 68 &self, 59 69 request: tonic::Request<CurrentTrackRequest>, 60 70 ) -> Result<tonic::Response<CurrentTrackResponse>, tonic::Status> { 61 - Ok(tonic::Response::new(CurrentTrackResponse::default())) 71 + let track = rb::playback::current_track(); 72 + Ok(tonic::Response::new(track.into())) 62 73 } 63 74 64 75 async fn flush_and_reload_tracks( 65 76 &self, 66 77 request: tonic::Request<FlushAndReloadTracksRequest>, 67 78 ) -> Result<tonic::Response<FlushAndReloadTracksResponse>, tonic::Status> { 79 + rb::playback::flush_and_reload_tracks(); 68 80 Ok(tonic::Response::new(FlushAndReloadTracksResponse::default())) 69 81 } 70 82 ··· 72 84 &self, 73 85 request: tonic::Request<GetFilePositionRequest>, 74 86 ) -> Result<tonic::Response<GetFilePositionResponse>, tonic::Status> { 75 - Ok(tonic::Response::new(GetFilePositionResponse::default())) 87 + let position = rb::playback::get_file_pos(); 88 + Ok(tonic::Response::new(GetFilePositionResponse { position })) 76 89 } 77 90 78 91 async fn hard_stop( 79 92 &self, 80 93 request: tonic::Request<HardStopRequest>, 81 94 ) -> Result<tonic::Response<HardStopResponse>, tonic::Status> { 95 + rb::playback::hard_stop(); 82 96 Ok(tonic::Response::new(HardStopResponse::default())) 83 97 } 84 98 }
+1 -10
crates/server/src/lib.rs
··· 1 - use std::thread; 2 - 3 1 use owo_colors::OwoColorize; 4 - use rockbox_sys::playback; 2 + use std::thread; 5 3 6 4 #[no_mangle] 7 5 pub extern "C" fn start_server() { ··· 16 14 17 15 // Start the server 18 16 println!("{}", BANNER.yellow()); 19 - let status = playback::status(); 20 - let track = playback::current_track(); 21 - 22 - println!("Current Track: {:?}", track); 23 - 24 - println!("Status: {}", status); 25 - playback::pause(); 26 17 27 18 thread::spawn(|| { 28 19 let runtime = tokio::runtime::Builder::new_multi_thread()
+1 -1
crates/sys/src/lib.rs
··· 20 20 const NB_SCREENS: usize = 2; 21 21 22 22 #[repr(C)] 23 - #[derive(Debug)] 23 + #[derive(Debug, Copy, Clone)] 24 24 pub struct Mp3Entry { 25 25 pub path: [c_char; MAX_PATH], // char path[MAX_PATH] 26 26 pub title: *mut c_char, // char* title
+6 -4
crates/sys/src/playback.rs
··· 24 24 unsafe { crate::audio_ff_rewind(newtime) } 25 25 } 26 26 27 - pub fn next_track() -> *mut Mp3Entry { 28 - unsafe { crate::audio_next_track() } 27 + pub fn next_track() -> Mp3Entry { 28 + let track = unsafe { crate::audio_next_track().as_ref().unwrap() }; 29 + *track 29 30 } 30 31 31 32 pub fn status() -> i32 { 32 33 unsafe { crate::audio_status() } 33 34 } 34 35 35 - pub fn current_track() -> *mut Mp3Entry { 36 - unsafe { crate::audio_current_track() } 36 + pub fn current_track() -> Mp3Entry { 37 + let track = unsafe { crate::audio_current_track().as_ref().unwrap() }; 38 + *track 37 39 } 38 40 39 41 pub fn flush_and_reload_tracks() {