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.

Merge pull request #10 from tsirysndr/current-playlist

Current playlist

authored by

Tsiry Sandratraina and committed by
GitHub
c4c41c92 7ca0a989

+460 -111
+1 -6
build.zig
··· 40 40 const optimize = b.standardOptimizeOption(.{}); 41 41 42 42 const lib = b.addStaticLibrary(.{ 43 - .name = "rockbox", 43 + .name = "rb", 44 44 // In this case the main source file is merely a path, however, in more 45 45 // complicated build scripts, this could be a generated file. 46 46 .root_source_file = b.path("src/root.zig"), ··· 139 139 140 140 exe.defineCMacro("ZIG_APP", null); 141 141 exe.defineCMacro("ROCKBOX_SERVER", null); 142 - 143 - lib.addCSourceFiles(.{ 144 - .files = &[_][]const u8{}, 145 - .flags = &cflags, 146 - }); 147 142 148 143 const libfirmware = b.addStaticLibrary(.{ 149 144 .name = "firmware",
+2
crates/ext/example.ts
··· 4 4 console.log(await rb.settings.getGlobalSettings()); 5 5 console.log(await rb.playlist.playlistResume()); 6 6 console.log(await rb.playlist.resumeTrack()); 7 + console.log(await rb.playlist.getCurrent()); 8 + console.log(await rb.playlist.amount());
+1 -1
crates/ext/src/playback/mod.rs
··· 1 1 use std::path::PathBuf; 2 2 3 - use deno_ast::swc::codegen::Result; 4 3 use deno_core::{error::AnyError, extension, op2}; 5 4 use rockbox_sys::types::{ 6 5 audio_status::AudioStatus, file_position::FilePosition, mp3_entry::Mp3Entry, ··· 117 116 let client = reqwest::Client::new(); 118 117 let url = format!("{}/flush_and_reload_tracks", rockbox_url()); 119 118 client.get(&url).send().await?; 119 + Ok(()) 120 120 } 121 121 122 122 #[op2(async)]
+38 -24
crates/ext/src/playlist/mod.rs
··· 1 1 use std::path::PathBuf; 2 2 3 3 use deno_core::{error::AnyError, extension, op2}; 4 + use rockbox_sys::types::{playlist_amount::PlaylistAmount, playlist_info::PlaylistInfo}; 4 5 5 6 use crate::rockbox_url; 6 7 7 8 extension!( 8 9 rb_playlist, 9 10 ops = [ 10 - op_get_current, 11 - op_get_resume_info, 12 - op_get_track_info, 13 - op_get_first_index, 14 - op_get_display_index, 15 - op_amount, 11 + op_playlist_get_current, 12 + op_playlist_get_resume_info, 13 + op_playlist_get_track_info, 14 + op_playlist_get_first_index, 15 + op_playlist_get_display_index, 16 + op_playlist_amount, 16 17 op_playlist_resume, 17 18 op_playlist_resume_track, 18 - op_set_modified, 19 - op_start, 20 - op_sync, 21 - op_remove_all_tracks, 19 + op_playlist_set_modified, 20 + op_playlist_start, 21 + op_playlist_sync, 22 + op_playlist_remove_all_tracks, 22 23 op_create_playlist, 23 - op_insert_track, 24 - op_insert_directory, 24 + op_playlist_insert_track, 25 + op_playlist_insert_directory, 25 26 op_insert_playlist, 26 27 op_shuffle_playlist, 27 28 op_warn_on_playlist_erase, ··· 34 35 } 35 36 36 37 #[op2(async)] 37 - pub async fn op_get_current() {} 38 + #[serde] 39 + pub async fn op_playlist_get_current() -> Result<PlaylistInfo, AnyError> { 40 + let client = reqwest::Client::new(); 41 + let url = format!("{}/current_playlist", rockbox_url()); 42 + let res = client.get(&url).send().await?; 43 + let info = res.json::<PlaylistInfo>().await?; 44 + Ok(info) 45 + } 38 46 39 47 #[op2(async)] 40 - pub async fn op_get_resume_info() {} 48 + pub async fn op_playlist_get_resume_info() {} 41 49 42 50 #[op2(async)] 43 - pub async fn op_get_track_info() {} 51 + pub async fn op_playlist_get_track_info() {} 44 52 45 53 #[op2(async)] 46 - pub async fn op_get_first_index() {} 54 + pub async fn op_playlist_get_first_index() {} 47 55 48 56 #[op2(async)] 49 - pub async fn op_get_display_index() {} 57 + pub async fn op_playlist_get_display_index() {} 50 58 51 59 #[op2(async)] 52 - pub async fn op_amount() {} 60 + pub async fn op_playlist_amount() -> Result<i32, AnyError> { 61 + let client = reqwest::Client::new(); 62 + let url = format!("{}/playlist_amount", rockbox_url()); 63 + let res = client.get(&url).send().await?; 64 + let data = res.json::<PlaylistAmount>().await?; 65 + Ok(data.amount) 66 + } 53 67 54 68 #[op2(async)] 55 69 pub async fn op_playlist_resume() -> Result<(), AnyError> { ··· 68 82 } 69 83 70 84 #[op2(async)] 71 - pub async fn op_set_modified() {} 85 + pub async fn op_playlist_set_modified() {} 72 86 73 87 #[op2(async)] 74 - pub async fn op_start() {} 88 + pub async fn op_playlist_start() {} 75 89 76 90 #[op2(async)] 77 - pub async fn op_sync() {} 91 + pub async fn op_playlist_sync() {} 78 92 79 93 #[op2(async)] 80 - pub async fn op_remove_all_tracks() {} 94 + pub async fn op_playlist_remove_all_tracks() {} 81 95 82 96 #[op2(async)] 83 97 pub async fn op_create_playlist() {} 84 98 85 99 #[op2(async)] 86 - pub async fn op_insert_track() {} 100 + pub async fn op_playlist_insert_track() {} 87 101 88 102 #[op2(async)] 89 - pub async fn op_insert_directory() {} 103 + pub async fn op_playlist_insert_directory() {} 90 104 91 105 #[op2(async)] 92 106 pub async fn op_insert_playlist() {}
+4 -4
crates/ext/src/playlist/playlist.js
··· 13 13 start: () => ops.op_playlist_start(), 14 14 sync: () => ops.op_playlist_sync(), 15 15 removeAllTracks: () => ops.op_playlist_remove_all_tracks(), 16 - createPlaylist: () => ops.op_playlist_create_playlist(), 16 + createPlaylist: () => ops.op_create_playlist(), 17 17 insertTrack: () => ops.op_playlist_insert_track(), 18 18 insertDirectory: () => ops.op_playlist_insert_directory(), 19 - insertPlaylist: () => ops.op_playlist_insert_playlist(), 20 - shufflePlaylist: () => ops.op_playlist_shuffle_playlist(), 21 - warnOnPlaylistErase: () => ops.op_playlist_warn_on_playlist_erase(), 19 + insertPlaylist: () => ops.op_insert_playlist(), 20 + shufflePlaylist: () => ops.op_shuffle_playlist(), 21 + warnOnPlaylistErase: () => ops.op_warn_on_playlist_erase(), 22 22 }; 23 23 24 24 globalThis.rb = { ...globalThis.rb, playlist };
+1
crates/graphql/src/schema/objects/mod.rs
··· 1 1 pub mod compressor_settings; 2 2 pub mod eq_band_setting; 3 + pub mod playlist; 3 4 pub mod replaygain_settings; 4 5 pub mod settings_list; 5 6 pub mod system_status;
+15
crates/graphql/src/schema/objects/playlist.rs
··· 1 + use async_graphql::*; 2 + use crate::schema::objects::track::Track; 3 + use serde::Serialize; 4 + 5 + #[derive(Default, Clone, Serialize)] 6 + pub struct Playlist { 7 + pub tracks: Vec<Track>, 8 + } 9 + 10 + #[Object] 11 + impl Playlist { 12 + async fn tracks(&self) -> &Vec<Track> { 13 + &self.tracks 14 + } 15 + }
+20 -5
crates/graphql/src/schema/playlist.rs
··· 1 1 use std::sync::{mpsc::Sender, Arc, Mutex}; 2 2 3 3 use async_graphql::*; 4 - use rockbox_sys::events::RockboxCommand; 4 + use rockbox_sys::{ 5 + events::RockboxCommand, 6 + types::{playlist_amount::PlaylistAmount, playlist_info::PlaylistInfo}, 7 + }; 8 + 9 + use crate::{rockbox_url, schema::objects::playlist::Playlist}; 5 10 6 11 #[derive(Default)] 7 12 pub struct PlaylistQuery; 8 13 9 14 #[Object] 10 15 impl PlaylistQuery { 11 - async fn playlist_get_current(&self) -> String { 12 - "playlist get current".to_string() 16 + async fn playlist_get_current(&self, _ctx: &Context<'_>) -> Result<Playlist, Error> { 17 + let client = reqwest::Client::new(); 18 + let url = format!("{}/current_playlist", rockbox_url()); 19 + let response = client.get(&url).send().await?; 20 + let response = response.json::<PlaylistInfo>().await?; 21 + Ok(Playlist { 22 + tracks: response.entries.into_iter().map(|t| t.into()).collect(), 23 + }) 13 24 } 14 25 15 26 async fn get_resume_info(&self) -> String { ··· 28 39 "get display index".to_string() 29 40 } 30 41 31 - async fn playlist_amount(&self) -> String { 32 - "playlist amount".to_string() 42 + async fn playlist_amount(&self, _ctx: &Context<'_>) -> Result<i32, Error> { 43 + let client = reqwest::Client::new(); 44 + let url = format!("{}/playlist_amount", rockbox_url()); 45 + let response = client.get(&url).send().await?; 46 + let response = response.json::<PlaylistAmount>().await?; 47 + Ok(response.amount) 33 48 } 34 49 } 35 50
+4 -1
crates/rpc/proto/buf.yaml
··· 1 1 # For details on buf.yaml configuration, visit https://buf.build/docs/configuration/v2/buf-yaml 2 2 version: v2 3 + modules: 4 + - path: . 5 + name: buf.build/tsiry/rockboxapis 3 6 lint: 4 7 use: 5 - - DEFAULT 8 + - STANDARD 6 9 breaking: 7 10 use: 8 11 - FILE
+4
crates/rpc/proto/rockbox/v1alpha1/playlist.proto
··· 2 2 3 3 package rockbox.v1alpha1; 4 4 5 + import "rockbox/v1alpha1/playback.proto"; 6 + 5 7 message GetCurrentRequest { 6 8 } 7 9 8 10 message GetCurrentResponse { 11 + repeated rockbox.v1alpha1.CurrentTrackResponse tracks = 1; 9 12 } 10 13 11 14 message GetResumeInfoRequest { ··· 36 39 } 37 40 38 41 message AmountResponse { 42 + int32 amount = 1; 39 43 } 40 44 41 45 message PlaylistResumeRequest {
+9 -3
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 2101 2101 } 2102 2102 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2103 2103 pub struct GetCurrentRequest {} 2104 - #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2105 - pub struct GetCurrentResponse {} 2104 + #[derive(Clone, PartialEq, ::prost::Message)] 2105 + pub struct GetCurrentResponse { 2106 + #[prost(message, repeated, tag = "1")] 2107 + pub tracks: ::prost::alloc::vec::Vec<CurrentTrackResponse>, 2108 + } 2106 2109 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2107 2110 pub struct GetResumeInfoRequest {} 2108 2111 #[derive(Clone, Copy, PartialEq, ::prost::Message)] ··· 2122 2125 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2123 2126 pub struct AmountRequest {} 2124 2127 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2125 - pub struct AmountResponse {} 2128 + pub struct AmountResponse { 2129 + #[prost(int32, tag = "1")] 2130 + pub amount: i32, 2131 + } 2126 2132 #[derive(Clone, Copy, PartialEq, ::prost::Message)] 2127 2133 pub struct PlaylistResumeRequest {} 2128 2134 #[derive(Clone, Copy, PartialEq, ::prost::Message)]
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

+60 -25
crates/rpc/src/playlist.rs
··· 1 1 use std::sync::{mpsc::Sender, Arc, Mutex}; 2 2 3 - use rockbox_sys::events::RockboxCommand; 3 + use rockbox_sys::{ 4 + events::RockboxCommand, 5 + types::{playlist_amount::PlaylistAmount, playlist_info::PlaylistInfo}, 6 + }; 4 7 5 - use crate::api::rockbox::v1alpha1::{playlist_service_server::PlaylistService, *}; 8 + use crate::{ 9 + api::rockbox::v1alpha1::{playlist_service_server::PlaylistService, *}, 10 + rockbox_url, 11 + }; 6 12 7 13 pub struct Playlist { 8 14 cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>, 15 + client: reqwest::Client, 9 16 } 10 17 11 18 impl Playlist { 12 - pub fn new(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> Self { 13 - Self { cmd_tx } 19 + pub fn new(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>, client: reqwest::Client) -> Self { 20 + Self { cmd_tx, client } 14 21 } 15 22 } 16 23 ··· 18 25 impl PlaylistService for Playlist { 19 26 async fn get_current( 20 27 &self, 21 - request: tonic::Request<GetCurrentRequest>, 28 + _request: tonic::Request<GetCurrentRequest>, 22 29 ) -> Result<tonic::Response<GetCurrentResponse>, tonic::Status> { 23 - Ok(tonic::Response::new(GetCurrentResponse::default())) 30 + let url = format!("{}/current_playlist", rockbox_url()); 31 + let response = self 32 + .client 33 + .get(url) 34 + .send() 35 + .await 36 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 37 + let data = response 38 + .json::<PlaylistInfo>() 39 + .await 40 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 41 + let tracks = data 42 + .entries 43 + .iter() 44 + .map(|track| CurrentTrackResponse::from(track.clone())) 45 + .collect::<Vec<CurrentTrackResponse>>(); 46 + Ok(tonic::Response::new(GetCurrentResponse { tracks })) 24 47 } 25 48 26 49 async fn get_resume_info( 27 50 &self, 28 - request: tonic::Request<GetResumeInfoRequest>, 51 + _request: tonic::Request<GetResumeInfoRequest>, 29 52 ) -> Result<tonic::Response<GetResumeInfoResponse>, tonic::Status> { 30 53 Ok(tonic::Response::new(GetResumeInfoResponse::default())) 31 54 } 32 55 33 56 async fn get_track_info( 34 57 &self, 35 - request: tonic::Request<GetTrackInfoRequest>, 58 + _request: tonic::Request<GetTrackInfoRequest>, 36 59 ) -> Result<tonic::Response<GetTrackInfoResponse>, tonic::Status> { 37 60 Ok(tonic::Response::new(GetTrackInfoResponse::default())) 38 61 } 39 62 40 63 async fn get_first_index( 41 64 &self, 42 - request: tonic::Request<GetFirstIndexRequest>, 65 + _request: tonic::Request<GetFirstIndexRequest>, 43 66 ) -> Result<tonic::Response<GetFirstIndexResponse>, tonic::Status> { 44 67 Ok(tonic::Response::new(GetFirstIndexResponse::default())) 45 68 } 46 69 47 70 async fn get_display_index( 48 71 &self, 49 - request: tonic::Request<GetDisplayIndexRequest>, 72 + _request: tonic::Request<GetDisplayIndexRequest>, 50 73 ) -> Result<tonic::Response<GetDisplayIndexResponse>, tonic::Status> { 51 74 Ok(tonic::Response::new(GetDisplayIndexResponse::default())) 52 75 } 53 76 54 77 async fn amount( 55 78 &self, 56 - request: tonic::Request<AmountRequest>, 79 + _request: tonic::Request<AmountRequest>, 57 80 ) -> Result<tonic::Response<AmountResponse>, tonic::Status> { 58 - Ok(tonic::Response::new(AmountResponse::default())) 81 + let url = format!("{}/playlist_amount", rockbox_url()); 82 + let response = self 83 + .client 84 + .get(url) 85 + .send() 86 + .await 87 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 88 + let data = response 89 + .json::<PlaylistAmount>() 90 + .await 91 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 92 + Ok(tonic::Response::new(AmountResponse { 93 + amount: data.amount, 94 + })) 59 95 } 60 96 61 97 async fn playlist_resume( 62 98 &self, 63 - request: tonic::Request<PlaylistResumeRequest>, 99 + _request: tonic::Request<PlaylistResumeRequest>, 64 100 ) -> Result<tonic::Response<PlaylistResumeResponse>, tonic::Status> { 65 101 self.cmd_tx 66 102 .lock() ··· 72 108 73 109 async fn resume_track( 74 110 &self, 75 - request: tonic::Request<ResumeTrackRequest>, 111 + _request: tonic::Request<ResumeTrackRequest>, 76 112 ) -> Result<tonic::Response<ResumeTrackResponse>, tonic::Status> { 77 - let params = request.into_inner(); 78 113 self.cmd_tx 79 114 .lock() 80 115 .unwrap() ··· 85 120 86 121 async fn set_modified( 87 122 &self, 88 - request: tonic::Request<SetModifiedRequest>, 123 + _request: tonic::Request<SetModifiedRequest>, 89 124 ) -> Result<tonic::Response<SetModifiedResponse>, tonic::Status> { 90 125 Ok(tonic::Response::new(SetModifiedResponse::default())) 91 126 } 92 127 93 128 async fn start( 94 129 &self, 95 - request: tonic::Request<StartRequest>, 130 + _request: tonic::Request<StartRequest>, 96 131 ) -> Result<tonic::Response<StartResponse>, tonic::Status> { 97 132 Ok(tonic::Response::new(StartResponse::default())) 98 133 } 99 134 100 135 async fn sync( 101 136 &self, 102 - request: tonic::Request<SyncRequest>, 137 + _request: tonic::Request<SyncRequest>, 103 138 ) -> Result<tonic::Response<SyncResponse>, tonic::Status> { 104 139 Ok(tonic::Response::new(SyncResponse::default())) 105 140 } 106 141 107 142 async fn remove_all_tracks( 108 143 &self, 109 - request: tonic::Request<RemoveAllTracksRequest>, 144 + _request: tonic::Request<RemoveAllTracksRequest>, 110 145 ) -> Result<tonic::Response<RemoveAllTracksResponse>, tonic::Status> { 111 146 Ok(tonic::Response::new(RemoveAllTracksResponse::default())) 112 147 } 113 148 114 149 async fn create_playlist( 115 150 &self, 116 - request: tonic::Request<CreatePlaylistRequest>, 151 + _request: tonic::Request<CreatePlaylistRequest>, 117 152 ) -> Result<tonic::Response<CreatePlaylistResponse>, tonic::Status> { 118 153 Ok(tonic::Response::new(CreatePlaylistResponse::default())) 119 154 } 120 155 121 156 async fn insert_track( 122 157 &self, 123 - request: tonic::Request<InsertTrackRequest>, 158 + _request: tonic::Request<InsertTrackRequest>, 124 159 ) -> Result<tonic::Response<InsertTrackResponse>, tonic::Status> { 125 160 Ok(tonic::Response::new(InsertTrackResponse::default())) 126 161 } 127 162 128 163 async fn insert_directory( 129 164 &self, 130 - request: tonic::Request<InsertDirectoryRequest>, 165 + _request: tonic::Request<InsertDirectoryRequest>, 131 166 ) -> Result<tonic::Response<InsertDirectoryResponse>, tonic::Status> { 132 167 Ok(tonic::Response::new(InsertDirectoryResponse::default())) 133 168 } 134 169 135 170 async fn insert_playlist( 136 171 &self, 137 - request: tonic::Request<InsertPlaylistRequest>, 172 + _request: tonic::Request<InsertPlaylistRequest>, 138 173 ) -> Result<tonic::Response<InsertPlaylistResponse>, tonic::Status> { 139 174 Ok(tonic::Response::new(InsertPlaylistResponse::default())) 140 175 } 141 176 142 177 async fn shuffle_playlist( 143 178 &self, 144 - request: tonic::Request<ShufflePlaylistRequest>, 179 + _request: tonic::Request<ShufflePlaylistRequest>, 145 180 ) -> Result<tonic::Response<ShufflePlaylistResponse>, tonic::Status> { 146 181 Ok(tonic::Response::new(ShufflePlaylistResponse::default())) 147 182 } 148 183 149 184 async fn warn_on_playlist_erase( 150 185 &self, 151 - request: tonic::Request<WarnOnPlaylistEraseRequest>, 186 + _request: tonic::Request<WarnOnPlaylistEraseRequest>, 152 187 ) -> Result<tonic::Response<WarnOnPlaylistEraseResponse>, tonic::Status> { 153 188 Ok(tonic::Response::new(WarnOnPlaylistEraseResponse::default())) 154 189 }
+1 -1
crates/rpc/src/server.rs
··· 46 46 .build_v1alpha()?, 47 47 ) 48 48 .add_service(tonic_web::enable(PlaylistServiceServer::new( 49 - Playlist::new(cmd_tx.clone()), 49 + Playlist::new(cmd_tx.clone(), client.clone()), 50 50 ))) 51 51 .add_service(tonic_web::enable(PlaybackServiceServer::new( 52 52 Playback::new(cmd_tx.clone(), client.clone()),
+14 -14
crates/rpc/src/sound.rs
··· 14 14 15 15 async fn sound_set( 16 16 &self, 17 - request: tonic::Request<SoundSetRequest>, 17 + _request: tonic::Request<SoundSetRequest>, 18 18 ) -> Result<tonic::Response<SoundSetResponse>, tonic::Status> { 19 19 Ok(tonic::Response::new(SoundSetResponse::default())) 20 20 } 21 21 22 22 async fn sound_current( 23 23 &self, 24 - request: tonic::Request<SoundCurrentRequest>, 24 + _request: tonic::Request<SoundCurrentRequest>, 25 25 ) -> Result<tonic::Response<SoundCurrentResponse>, tonic::Status> { 26 26 Ok(tonic::Response::new(SoundCurrentResponse::default())) 27 27 } 28 28 29 29 async fn sound_default( 30 30 &self, 31 - request: tonic::Request<SoundDefaultRequest>, 31 + _request: tonic::Request<SoundDefaultRequest>, 32 32 ) -> Result<tonic::Response<SoundDefaultResponse>, tonic::Status> { 33 33 Ok(tonic::Response::new(SoundDefaultResponse::default())) 34 34 } 35 35 36 36 async fn sound_min( 37 37 &self, 38 - request: tonic::Request<SoundMinRequest>, 38 + _request: tonic::Request<SoundMinRequest>, 39 39 ) -> Result<tonic::Response<SoundMinResponse>, tonic::Status> { 40 40 Ok(tonic::Response::new(SoundMinResponse::default())) 41 41 } 42 42 43 43 async fn sound_max( 44 44 &self, 45 - request: tonic::Request<SoundMaxRequest>, 45 + _request: tonic::Request<SoundMaxRequest>, 46 46 ) -> Result<tonic::Response<SoundMaxResponse>, tonic::Status> { 47 47 Ok(tonic::Response::new(SoundMaxResponse::default())) 48 48 } 49 49 50 50 async fn sound_unit( 51 51 &self, 52 - request: tonic::Request<SoundUnitRequest>, 52 + _request: tonic::Request<SoundUnitRequest>, 53 53 ) -> Result<tonic::Response<SoundUnitResponse>, tonic::Status> { 54 54 Ok(tonic::Response::new(SoundUnitResponse::default())) 55 55 } 56 56 57 57 async fn sound_val2_phys( 58 58 &self, 59 - request: tonic::Request<SoundVal2PhysRequest>, 59 + _request: tonic::Request<SoundVal2PhysRequest>, 60 60 ) -> Result<tonic::Response<SoundVal2PhysResponse>, tonic::Status> { 61 61 Ok(tonic::Response::new(SoundVal2PhysResponse::default())) 62 62 } 63 63 64 64 async fn get_pitch( 65 65 &self, 66 - request: tonic::Request<GetPitchRequest>, 66 + _request: tonic::Request<GetPitchRequest>, 67 67 ) -> Result<tonic::Response<GetPitchResponse>, tonic::Status> { 68 68 Ok(tonic::Response::new(GetPitchResponse::default())) 69 69 } 70 70 71 71 async fn set_pitch( 72 72 &self, 73 - request: tonic::Request<SetPitchRequest>, 73 + _request: tonic::Request<SetPitchRequest>, 74 74 ) -> Result<tonic::Response<SetPitchResponse>, tonic::Status> { 75 75 Ok(tonic::Response::new(SetPitchResponse::default())) 76 76 } 77 77 78 78 async fn beep_play( 79 79 &self, 80 - request: tonic::Request<BeepPlayRequest>, 80 + _request: tonic::Request<BeepPlayRequest>, 81 81 ) -> Result<tonic::Response<BeepPlayResponse>, tonic::Status> { 82 82 Ok(tonic::Response::new(BeepPlayResponse::default())) 83 83 } 84 84 85 85 async fn pcmbuf_fade( 86 86 &self, 87 - request: tonic::Request<PcmbufFadeRequest>, 87 + _request: tonic::Request<PcmbufFadeRequest>, 88 88 ) -> Result<tonic::Response<PcmbufFadeResponse>, tonic::Status> { 89 89 Ok(tonic::Response::new(PcmbufFadeResponse::default())) 90 90 } 91 91 92 92 async fn pcmbuf_set_low_latency( 93 93 &self, 94 - request: tonic::Request<PcmbufSetLowLatencyRequest>, 94 + _request: tonic::Request<PcmbufSetLowLatencyRequest>, 95 95 ) -> Result<tonic::Response<PcmbufSetLowLatencyResponse>, tonic::Status> { 96 96 Ok(tonic::Response::new(PcmbufSetLowLatencyResponse::default())) 97 97 } 98 98 99 99 async fn system_sound_play( 100 100 &self, 101 - request: tonic::Request<SystemSoundPlayRequest>, 101 + _request: tonic::Request<SystemSoundPlayRequest>, 102 102 ) -> Result<tonic::Response<SystemSoundPlayResponse>, tonic::Status> { 103 103 Ok(tonic::Response::new(SystemSoundPlayResponse::default())) 104 104 } 105 105 106 106 async fn keyclick_click( 107 107 &self, 108 - request: tonic::Request<KeyclickClickRequest>, 108 + _request: tonic::Request<KeyclickClickRequest>, 109 109 ) -> Result<tonic::Response<KeyclickClickResponse>, tonic::Status> { 110 110 Ok(tonic::Response::new(KeyclickClickResponse::default())) 111 111 }
+32 -2
crates/server/src/lib.rs
··· 1 1 use owo_colors::OwoColorize; 2 - use rockbox_sys::{self as rb, events::RockboxCommand}; 2 + use rockbox_sys::{self as rb, events::RockboxCommand, types::playlist_amount::PlaylistAmount}; 3 3 use std::{ 4 4 ffi::c_char, 5 5 io::{BufRead, BufReader, Write}, ··· 93 93 "/stop" => { 94 94 rb::playback::hard_stop(); 95 95 } 96 + "/playlist_amount" => { 97 + let amount = rb::playlist::amount(); 98 + let json = PlaylistAmount { amount }; 99 + let response = format!( 100 + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{}", 101 + serde_json::to_string(&json).unwrap() 102 + ); 103 + stream.write_all(response.as_bytes()).unwrap(); 104 + return; 105 + } 106 + "/current_playlist" => { 107 + let mut playlist = rb::playlist::get_current(); 108 + let mut entries = vec![]; 109 + let amount = rb::playlist::amount(); 110 + 111 + for i in 0..amount { 112 + let info = rb::playlist::get_track_info(i); 113 + let entry = rb::metadata::get_metadata(-1, &info.filename); 114 + entries.push(entry); 115 + } 116 + 117 + playlist.entries = entries; 118 + 119 + let response = format!( 120 + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n{}", 121 + serde_json::to_string(&playlist).unwrap() 122 + ); 123 + stream.write_all(response.as_bytes()).unwrap(); 124 + return; 125 + } 96 126 "/playlist_resume" => { 97 127 rb::playlist::resume(); 98 128 } ··· 173 203 return; 174 204 } 175 205 _ => { 176 - if path.starts_with("/play") { 206 + if path.starts_with("/play?") { 177 207 let params: Vec<_> = path.split('?').collect(); 178 208 let params: Vec<_> = params[1].split('&').collect(); 179 209 let elapsed = params[0].split('=').collect::<Vec<_>>()[1].parse().unwrap();
+4 -8
crates/sys/src/lib.rs
··· 16 16 pub mod tagcache; 17 17 pub mod types; 18 18 19 - const MAX_PATH: usize = 260; 19 + pub const MAX_PATH: usize = 260; 20 20 const ID3V2_BUF_SIZE: usize = 1800; 21 21 const MAX_PATHNAME: usize = 80; 22 22 const NB_SCREENS: usize = 2; ··· 128 128 pub fd: c_int, // int fd 129 129 pub control_fd: c_int, // int control_fd 130 130 pub max_playlist_size: c_int, // int max_playlist_size 131 - pub indices: *mut c_ulong, // unsigned long* indices 131 + pub indices: [c_ulong; 200], // unsigned long* indices 132 132 pub index: c_int, // int index 133 133 pub first_index: c_int, // int first_index 134 134 pub amount: c_int, // int amount ··· 1042 1042 // Playlist control 1043 1043 fn playlist_get_current() -> PlaylistInfo; 1044 1044 fn playlist_get_resume_info(resume_index: *mut c_int) -> c_int; 1045 - fn playlist_get_track_info( 1046 - playlist: PlaylistInfo, 1047 - index: c_int, 1048 - info: PlaylistTrackInfo, 1049 - ) -> c_int; 1045 + fn _get_track_info_from_current_playlist(index: i32) -> PlaylistTrackInfo; 1050 1046 fn playlist_get_first_index(playlist: *mut PlaylistInfo) -> c_int; 1051 1047 fn playlist_get_display_index() -> c_int; 1052 1048 fn playlist_amount() -> c_int; ··· 1191 1187 fn filetype_get_plugin(); 1192 1188 1193 1189 // Metadata 1194 - fn get_metadata(id3: *mut Mp3Entry, fd: c_int, trackname: *const c_char) -> c_uchar; 1190 + fn _get_metadata(fd: i32, trackname: *const c_char) -> Mp3Entry; 1195 1191 fn get_codec_string(codectype: c_int) -> *const c_char; 1196 1192 fn count_mp3_frames( 1197 1193 fd: c_int,
+4 -4
crates/sys/src/metadata.rs
··· 1 1 use std::ffi::{c_uchar, CStr, CString}; 2 2 3 - use crate::{Mp3Entry, ProgressFunc}; 3 + use crate::{types::mp3_entry::Mp3Entry, ProgressFunc}; 4 4 5 - pub fn get_metadata(id3: *mut Mp3Entry, fd: i32, trackname: &str) -> bool { 5 + pub fn get_metadata(fd: i32, trackname: &str) -> Mp3Entry { 6 6 let trackname = CString::new(trackname).unwrap(); 7 - let ret = unsafe { crate::get_metadata(id3, fd, trackname.as_ptr()) }; 8 - ret != 0 7 + let id3 = unsafe { crate::_get_metadata(fd, trackname.as_ptr()) }; 8 + id3.into() 9 9 } 10 10 11 11 pub fn get_codec_string(codectype: i32) -> String {
+13 -11
crates/sys/src/playlist.rs
··· 1 - use crate::{PlaylistInfo, PlaylistTrackInfo}; 1 + use crate::types::{playlist_info::PlaylistInfo, playlist_track_info::PlaylistTrackInfo}; 2 2 use std::ffi::{c_int, CString}; 3 3 4 4 pub fn get_current() -> PlaylistInfo { 5 - unsafe { crate::playlist_get_current() } 5 + let playlist = unsafe { crate::playlist_get_current() }; 6 + playlist.into() 6 7 } 7 8 8 9 pub fn get_resume_info(mut resume_index: i32) -> i32 { 9 10 unsafe { crate::playlist_get_resume_info(&mut resume_index as *mut i32 as *mut c_int) } 10 11 } 11 12 12 - pub fn get_track_info(playlist: PlaylistInfo, index: i32, info: PlaylistTrackInfo) -> i32 { 13 - unsafe { crate::playlist_get_track_info(playlist, index, info) } 13 + pub fn get_track_info(index: i32) -> PlaylistTrackInfo { 14 + let track_info = unsafe { crate::_get_track_info_from_current_playlist(index) }; 15 + track_info.into() 14 16 } 15 17 16 - pub fn get_first_index(info: *mut PlaylistInfo) -> i32 { 18 + pub fn get_first_index(info: *mut crate::PlaylistInfo) -> i32 { 17 19 unsafe { crate::playlist_get_first_index(info) } 18 20 } 19 21 ··· 33 35 unsafe { crate::playlist_resume_track(start_index, crc, elapsed, offset) } 34 36 } 35 37 36 - pub fn set_modified(playlist: *mut PlaylistInfo, modified: bool) { 38 + pub fn set_modified(playlist: *mut crate::PlaylistInfo, modified: bool) { 37 39 unsafe { crate::playlist_set_modified(playlist, modified as u8) } 38 40 } 39 41 ··· 41 43 unsafe { crate::playlist_start(start_index, elapsed, offset) } 42 44 } 43 45 44 - pub fn sync(playlist: *mut PlaylistInfo) { 46 + pub fn sync(playlist: *mut crate::PlaylistInfo) { 45 47 unsafe { crate::playlist_sync(playlist) } 46 48 } 47 49 48 - pub fn remove_all_tracks(playlist: *mut PlaylistInfo) -> i32 { 50 + pub fn remove_all_tracks(playlist: *mut crate::PlaylistInfo) -> i32 { 49 51 unsafe { crate::playlist_remove_all_tracks(playlist) } 50 52 } 51 53 ··· 56 58 } 57 59 58 60 pub fn insert_track( 59 - playlist: *mut PlaylistInfo, 61 + playlist: *mut crate::PlaylistInfo, 60 62 filename: &str, 61 63 position: i32, 62 64 queue: bool, ··· 75 77 } 76 78 77 79 pub fn insert_directory( 78 - playlist: *mut PlaylistInfo, 80 + playlist: *mut crate::PlaylistInfo, 79 81 dir: &str, 80 82 position: i32, 81 83 queue: bool, ··· 94 96 } 95 97 96 98 pub fn insert_playlist( 97 - playlist: *mut PlaylistInfo, 99 + playlist: *mut crate::PlaylistInfo, 98 100 filename: &str, 99 101 position: i32, 100 102 queue: bool,
+4 -1
crates/sys/src/types/mod.rs
··· 1 1 use serde::{Deserialize, Serialize}; 2 2 3 3 pub mod audio_status; 4 + pub mod file_position; 4 5 pub mod mp3_entry; 6 + pub mod playlist_amount; 7 + pub mod playlist_info; 8 + pub mod playlist_track_info; 5 9 pub mod system_status; 6 10 pub mod user_settings; 7 - pub mod file_position; 8 11 9 12 #[derive(Serialize, Deserialize)] 10 13 pub struct RockboxVersion {
+1 -1
crates/sys/src/types/mp3_entry.rs
··· 2 2 use crate::get_string_from_ptr; 3 3 use serde::{Deserialize, Serialize}; 4 4 5 - #[derive(Serialize, Deserialize)] 5 + #[derive(Debug, Serialize, Deserialize, Clone)] 6 6 pub struct Mp3Entry { 7 7 pub path: String, 8 8 pub title: String, // char* title
+6
crates/sys/src/types/playlist_amount.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(Debug, Serialize, Deserialize)] 4 + pub struct PlaylistAmount { 5 + pub amount: i32, 6 + }
+61
crates/sys/src/types/playlist_info.rs
··· 1 + use crate::types::mp3_entry::Mp3Entry; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + use crate::cast_ptr; 5 + 6 + #[derive(Serialize, Deserialize, Default)] 7 + pub struct PlaylistInfo { 8 + pub utf8: bool, // bool utf8 9 + pub control_created: bool, // bool control_created 10 + pub flags: u32, // unsigned int flags 11 + pub fd: i32, // int fd 12 + pub control_fd: i32, // int control_fd 13 + pub max_playlist_size: i32, // int max_playlist_size 14 + pub indices: Vec<u64>, // unsigned long* indices 15 + pub index: i32, // int index 16 + pub first_index: i32, // int first_index 17 + pub amount: i32, // int amount 18 + pub last_insert_pos: i32, // int last_insert_pos 19 + pub started: bool, // bool started 20 + pub last_shuffled_start: i32, // int last_shuffled_start 21 + pub seed: i32, // int seed 22 + pub dirlen: i32, // int dirlen 23 + pub filename: String, // char filename[MAX_PATH] 24 + pub control_filename: String, // char control_filename[sizeof(PLAYLIST_CONTROL_FILE) + 8] 25 + pub dcfrefs_handle: i32, // int dcfrefs_handle 26 + pub entries: Vec<Mp3Entry>, 27 + } 28 + 29 + impl From<crate::PlaylistInfo> for PlaylistInfo { 30 + fn from(info: crate::PlaylistInfo) -> Self { 31 + Self { 32 + utf8: info.utf8, 33 + control_created: info.control_created, 34 + flags: info.flags, 35 + fd: info.fd, 36 + control_fd: info.control_fd, 37 + max_playlist_size: info.max_playlist_size, 38 + indices: vec![], 39 + index: info.index, 40 + first_index: info.first_index, 41 + amount: info.amount, 42 + last_insert_pos: info.last_insert_pos, 43 + started: info.started, 44 + last_shuffled_start: info.last_shuffled_start, 45 + seed: info.seed, 46 + dirlen: info.dirlen, 47 + filename: unsafe { 48 + std::ffi::CStr::from_ptr(cast_ptr!(info.filename.as_ptr())) 49 + .to_string_lossy() 50 + .into_owned() 51 + }, 52 + control_filename: unsafe { 53 + std::ffi::CStr::from_ptr(cast_ptr!(info.control_filename.as_ptr())) 54 + .to_string_lossy() 55 + .into_owned() 56 + }, 57 + dcfrefs_handle: info.dcfrefs_handle, 58 + entries: vec![], 59 + } 60 + } 61 + }
+24
crates/sys/src/types/playlist_track_info.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(Debug, Serialize, Deserialize)] 4 + pub struct PlaylistTrackInfo { 5 + pub filename: String, 6 + pub attr: i32, 7 + pub index: i32, 8 + pub display_index: i32, 9 + } 10 + 11 + impl From<crate::PlaylistTrackInfo> for PlaylistTrackInfo { 12 + fn from(info: crate::PlaylistTrackInfo) -> Self { 13 + Self { 14 + filename: unsafe { 15 + std::ffi::CStr::from_ptr(crate::cast_ptr!(info.filename.as_ptr())) 16 + .to_string_lossy() 17 + .into_owned() 18 + }, 19 + attr: info.attr, 20 + index: info.index, 21 + display_index: info.display_index, 22 + } 23 + } 24 + }
+10
src/main.zig
··· 1 1 const std = @import("std"); 2 + const playlist = @import("rockbox/playlist.zig"); 3 + const metadata = @import("rockbox/metadata.zig"); 2 4 3 5 extern fn main_c() c_int; 4 6 extern fn parse_args(argc: usize, argv: [*]const [*]const u8) c_int; ··· 24 26 _ = parse_args(argc, &argv); 25 27 _ = main_c(); 26 28 } 29 + 30 + export fn _get_track_info_from_current_playlist(index: c_int) playlist.PlaylistTrackInfo { 31 + return playlist._get_track_info_from_current_playlist(index); 32 + } 33 + 34 + export fn _get_metadata(fd: c_int, trackname: [*]const u8) metadata.mp3entry { 35 + return metadata._get_metadata(fd, trackname); 36 + }
+85
src/rockbox/metadata.zig
··· 1 + pub const MAX_PATH = 260; 2 + pub const ID3V2_BUF_SIZE = 1800; 3 + 4 + const mp3_aa_type = enum(c_int) { 5 + AA_TYPE_UNSYNC = -1, 6 + AA_TYPE_UNKNOWN = 0, 7 + AA_TYPE_BMP, 8 + AA_TYPE_PNG, 9 + AA_TYPE_JPG, 10 + }; 11 + 12 + const mp3_albumart = extern struct { 13 + type: mp3_aa_type, 14 + size: c_int, 15 + pos: c_long, // Use c_long to match the typical size of off_t, but adjust as necessary 16 + }; 17 + 18 + pub const mp3entry = extern struct { 19 + path: [MAX_PATH]u8, 20 + title: ?*[*c]u8, 21 + artist: ?*[*c]u8, 22 + album: ?*[*c]u8, 23 + genre_string: ?*[*c]u8, 24 + disc_string: ?*[*c]u8, 25 + track_string: ?*[*c]u8, 26 + year_string: ?*[*c]u8, 27 + composer: ?*[*c]u8, 28 + comment: ?*[*c]u8, 29 + albumartist: ?*[*c]u8, 30 + grouping: ?*[*c]u8, 31 + discnum: c_int, 32 + tracknum: c_int, 33 + layer: c_int, 34 + year: c_int, 35 + id3version: u8, 36 + codectype: u32, 37 + bitrate: u32, 38 + frequency: u32, 39 + id3v2len: u32, 40 + id3v1len: u32, 41 + first_frame_offset: u32, 42 + filesize: u32, 43 + length: u32, 44 + elapsed: u32, 45 + lead_trim: c_int, 46 + tail_trim: c_int, 47 + samples: u64, 48 + frame_count: u32, 49 + bytesperframe: u32, 50 + vbr: bool, 51 + has_toc: bool, 52 + toc: [100]u8, 53 + needs_upsampling_correction: bool, 54 + id3v2buf: [ID3V2_BUF_SIZE]u8, 55 + id3v1buf: [4][92]u8, 56 + offset: u32, 57 + index: c_int, 58 + skip_resume_adjustments: bool, 59 + autoresumable: u8, 60 + tagcache_idx: c_long, 61 + rating: c_int, 62 + score: c_int, 63 + playcount: c_long, 64 + lastplayed: c_long, 65 + playtime: c_long, 66 + track_level: c_long, 67 + album_level: c_long, 68 + track_gain: c_long, 69 + album_gain: c_long, 70 + track_peak: c_long, 71 + album_peak: c_long, 72 + has_embedded_albumart: bool, 73 + albumart: mp3_albumart, 74 + has_embedded_cuesheet: bool, 75 + mb_track_id: ?*[*c]u8, 76 + is_asf_stream: bool, 77 + }; 78 + 79 + extern fn get_metadata(id3: *mp3entry, fd: c_int, trackname: [*]const u8) bool; 80 + 81 + pub fn _get_metadata(fd: c_int, trackname: [*]const u8) mp3entry { 82 + var id3: mp3entry = undefined; 83 + _ = get_metadata(&id3, fd, trackname); 84 + return id3; 85 + }
+42
src/rockbox/playlist.zig
··· 1 + const std = @import("std"); 2 + 3 + const MAX_PATH = 260; 4 + const PLAYLIST_CONTROL_FILE_SIZE = 256; 5 + 6 + pub const PlaylistInfo = extern struct { 7 + utf8: bool, 8 + control_created: bool, 9 + flags: c_uint, 10 + fd: c_int, 11 + control_fd: c_int, 12 + max_playlist_size: c_int, 13 + indices: ?*c_ulong, 14 + index: c_int, 15 + first_index: c_int, 16 + amount: c_int, 17 + last_insert_pos: c_int, 18 + started: bool, 19 + last_shuffled_start: c_int, 20 + seed: c_int, 21 + dcfrefs_handle: c_int, 22 + dirlen: c_int, 23 + filename: [MAX_PATH]u8, 24 + control_filename: [PLAYLIST_CONTROL_FILE_SIZE + 8]u8, 25 + }; 26 + 27 + pub const PlaylistTrackInfo = extern struct { 28 + filename: [260]u8, 29 + attr: c_int, 30 + index: c_int, 31 + display_index: c_int, 32 + }; 33 + 34 + extern fn playlist_get_current() *PlaylistInfo; 35 + extern fn playlist_get_track_info(playlist: *PlaylistInfo, index: c_int, info: *PlaylistTrackInfo) c_int; 36 + 37 + pub fn _get_track_info_from_current_playlist(index: c_int) PlaylistTrackInfo { 38 + const playlist = playlist_get_current(); 39 + var info: PlaylistTrackInfo = undefined; 40 + _ = playlist_get_track_info(playlist, index, &info); 41 + return info; 42 + }