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 #141 from tsirysndr/feat/pcm-fifo

Add tracing support and FIFO PCM sink functionality

authored by

Tsiry Sandratraina and committed by
GitHub
2cd202d7 fe09cfab

+313 -105
+103 -8
Cargo.lock
··· 1214 1214 "bitflags 2.6.0", 1215 1215 "cexpr", 1216 1216 "clang-sys", 1217 - "itertools 0.10.5", 1217 + "itertools 0.12.1", 1218 1218 "lazy_static", 1219 1219 "lazycell", 1220 1220 "log", ··· 1416 1416 checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" 1417 1417 dependencies = [ 1418 1418 "memchr", 1419 - "regex-automata", 1419 + "regex-automata 0.4.8", 1420 1420 "serde", 1421 1421 ] 1422 1422 ··· 4579 4579 dependencies = [ 4580 4580 "chrono", 4581 4581 "log", 4582 - "nu-ansi-term", 4582 + "nu-ansi-term 0.50.1", 4583 4583 "regex", 4584 4584 "thiserror 2.0.18", 4585 4585 ] ··· 5087 5087 "aho-corasick", 5088 5088 "bstr", 5089 5089 "log", 5090 - "regex-automata", 5091 - "regex-syntax", 5090 + "regex-automata 0.4.8", 5091 + "regex-syntax 0.8.5", 5092 5092 ] 5093 5093 5094 5094 [[package]] ··· 6815 6815 checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" 6816 6816 6817 6817 [[package]] 6818 + name = "matchers" 6819 + version = "0.1.0" 6820 + source = "registry+https://github.com/rust-lang/crates.io-index" 6821 + checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 6822 + dependencies = [ 6823 + "regex-automata 0.1.10", 6824 + ] 6825 + 6826 + [[package]] 6818 6827 name = "matchit" 6819 6828 version = "0.7.3" 6820 6829 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7194 7203 "mockito", 7195 7204 "once_cell", 7196 7205 "reqwest", 7206 + "tracing", 7197 7207 ] 7198 7208 7199 7209 [[package]] ··· 7321 7331 7322 7332 [[package]] 7323 7333 name = "nu-ansi-term" 7334 + version = "0.46.0" 7335 + source = "registry+https://github.com/rust-lang/crates.io-index" 7336 + checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 7337 + dependencies = [ 7338 + "overload", 7339 + "winapi", 7340 + ] 7341 + 7342 + [[package]] 7343 + name = "nu-ansi-term" 7324 7344 version = "0.50.1" 7325 7345 source = "registry+https://github.com/rust-lang/crates.io-index" 7326 7346 checksum = "d4a28e057d01f97e61255210fcff094d74ed0466038633e95017f5beb68e4399" ··· 7691 7711 version = "0.5.1" 7692 7712 source = "registry+https://github.com/rust-lang/crates.io-index" 7693 7713 checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" 7714 + 7715 + [[package]] 7716 + name = "overload" 7717 + version = "0.1.1" 7718 + source = "registry+https://github.com/rust-lang/crates.io-index" 7719 + checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 7694 7720 7695 7721 [[package]] 7696 7722 name = "owo-colors" ··· 8839 8865 dependencies = [ 8840 8866 "aho-corasick", 8841 8867 "memchr", 8842 - "regex-automata", 8843 - "regex-syntax", 8868 + "regex-automata 0.4.8", 8869 + "regex-syntax 0.8.5", 8870 + ] 8871 + 8872 + [[package]] 8873 + name = "regex-automata" 8874 + version = "0.1.10" 8875 + source = "registry+https://github.com/rust-lang/crates.io-index" 8876 + checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 8877 + dependencies = [ 8878 + "regex-syntax 0.6.29", 8844 8879 ] 8845 8880 8846 8881 [[package]] ··· 8851 8886 dependencies = [ 8852 8887 "aho-corasick", 8853 8888 "memchr", 8854 - "regex-syntax", 8889 + "regex-syntax 0.8.5", 8855 8890 ] 8856 8891 8857 8892 [[package]] ··· 8862 8897 8863 8898 [[package]] 8864 8899 name = "regex-syntax" 8900 + version = "0.6.29" 8901 + source = "registry+https://github.com/rust-lang/crates.io-index" 8902 + checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 8903 + 8904 + [[package]] 8905 + name = "regex-syntax" 8865 8906 version = "0.8.5" 8866 8907 source = "registry+https://github.com/rust-lang/crates.io-index" 8867 8908 checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" ··· 9032 9073 "tonic-build", 9033 9074 "tonic-reflection", 9034 9075 "tonic-web", 9076 + "tracing", 9077 + "tracing-subscriber", 9035 9078 "warp", 9036 9079 "zip", 9037 9080 ] ··· 9068 9111 "rockbox-settings", 9069 9112 "rockbox-typesense", 9070 9113 "tokio", 9114 + "tracing", 9115 + "tracing-subscriber", 9071 9116 "uuid", 9072 9117 ] 9073 9118 ··· 9152 9197 "serde_json", 9153 9198 "sqlx", 9154 9199 "tokio", 9200 + "tracing", 9155 9201 ] 9156 9202 9157 9203 [[package]] ··· 9173 9219 "tokio", 9174 9220 "tokio-stream", 9175 9221 "tonic", 9222 + "tracing", 9176 9223 ] 9177 9224 9178 9225 [[package]] ··· 9186 9233 "rockbox-library", 9187 9234 "rockbox-rpc", 9188 9235 "tokio", 9236 + "tracing", 9189 9237 "urlencoding", 9190 9238 ] 9191 9239 ··· 9291 9339 "sqlx", 9292 9340 "threadpool", 9293 9341 "tokio", 9342 + "tracing", 9294 9343 "url", 9295 9344 "urlencoding", 9296 9345 ] ··· 9352 9401 "rockbox-library", 9353 9402 "serde", 9354 9403 "serde_json", 9404 + "tracing", 9355 9405 "uuid", 9356 9406 ] 9357 9407 ··· 10044 10094 dependencies = [ 10045 10095 "digest 0.10.7", 10046 10096 "keccak", 10097 + ] 10098 + 10099 + [[package]] 10100 + name = "sharded-slab" 10101 + version = "0.1.7" 10102 + source = "registry+https://github.com/rust-lang/crates.io-index" 10103 + checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 10104 + dependencies = [ 10105 + "lazy_static", 10047 10106 ] 10048 10107 10049 10108 [[package]] ··· 11877 11936 checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 11878 11937 dependencies = [ 11879 11938 "once_cell", 11939 + "valuable", 11940 + ] 11941 + 11942 + [[package]] 11943 + name = "tracing-log" 11944 + version = "0.2.0" 11945 + source = "registry+https://github.com/rust-lang/crates.io-index" 11946 + checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 11947 + dependencies = [ 11948 + "log", 11949 + "once_cell", 11950 + "tracing-core", 11951 + ] 11952 + 11953 + [[package]] 11954 + name = "tracing-subscriber" 11955 + version = "0.3.18" 11956 + source = "registry+https://github.com/rust-lang/crates.io-index" 11957 + checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" 11958 + dependencies = [ 11959 + "matchers", 11960 + "nu-ansi-term 0.46.0", 11961 + "once_cell", 11962 + "regex", 11963 + "sharded-slab", 11964 + "smallvec", 11965 + "thread_local", 11966 + "tracing", 11967 + "tracing-core", 11968 + "tracing-log", 11880 11969 ] 11881 11970 11882 11971 [[package]] ··· 12250 12339 version = "0.15.8" 12251 12340 source = "registry+https://github.com/rust-lang/crates.io-index" 12252 12341 checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" 12342 + 12343 + [[package]] 12344 + name = "valuable" 12345 + version = "0.1.1" 12346 + source = "registry+https://github.com/rust-lang/crates.io-index" 12347 + checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" 12253 12348 12254 12349 [[package]] 12255 12350 name = "value-bag"
+2
Cargo.toml
··· 161 161 "proposed", 162 162 ] } 163 163 tower-service = "0.3.2" 164 + tracing = "0.1" 165 + tracing-subscriber = { version = "0.3", features = ["env-filter"] } 164 166 twox-hash = "=1.6.3" 165 167 url = { version = "2.5", features = ["serde", "expose_internals"] } 166 168 uuid = { version = "1.3.0", features = ["v4"] }
+2
cli/Cargo.toml
··· 33 33 warp = "0.3.7" 34 34 dirs = "6.0.0" 35 35 rockbox-typesense = { path = "../crates/typesense" } 36 + tracing = { workspace = true } 37 + tracing-subscriber = { workspace = true } 36 38 37 39 [build-dependencies] 38 40 tonic-build = "0.12.3"
+9
cli/src/main.rs
··· 98 98 99 99 #[tokio::main] 100 100 async fn main() -> Result<(), Error> { 101 + let subscriber = tracing_subscriber::fmt() 102 + .with_writer(std::io::stderr) 103 + .with_env_filter( 104 + tracing_subscriber::EnvFilter::try_from_default_env() 105 + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), 106 + ) 107 + .finish(); 108 + let _ = tracing::subscriber::set_global_default(subscriber); 109 + 101 110 let args = std::env::args().collect::<Vec<String>>(); 102 111 if args.len() > 1 && args[1] == "run" { 103 112 let _args = args
+2
crates/cli/Cargo.toml
··· 18 18 dirs = "6.0.0" 19 19 uuid.workspace = true 20 20 libc.workspace = true 21 + tracing = { workspace = true } 22 + tracing-subscriber = { workspace = true }
+56 -33
crates/cli/src/lib.rs
··· 5 5 use rockbox_library::{create_connection_pool, repo}; 6 6 use rockbox_typesense::client::*; 7 7 use rockbox_typesense::types::*; 8 + use std::io::{BufRead, BufReader}; 8 9 use std::process::Stdio; 9 10 use std::sync::atomic::{AtomicI32, Ordering}; 10 11 use std::thread::sleep; 11 12 use std::time::Duration; 12 13 use std::{env, ffi::CStr}; 13 14 use std::{fs, thread}; 15 + use tracing::{error, info, warn}; 14 16 15 17 /// PID of the spawned typesense-server child, or -1 if not yet started. 16 18 static TYPESENSE_PID: AtomicI32 = AtomicI32::new(-1); ··· 32 34 33 35 #[no_mangle] 34 36 pub extern "C" fn parse_args(argc: usize, argv: *const *const u8) -> i32 { 37 + let subscriber = tracing_subscriber::fmt() 38 + .with_writer(std::io::stderr) 39 + .with_env_filter( 40 + tracing_subscriber::EnvFilter::try_from_default_env() 41 + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")), 42 + ) 43 + .finish(); 44 + let _ = tracing::subscriber::set_global_default(subscriber); 45 + 35 46 let string_array = unsafe { std::slice::from_raw_parts(argv, argc) }; 36 47 let args: Vec<&str> = string_array 37 48 .iter() ··· 71 82 libc::signal(libc::SIGTERM, handle_shutdown as libc::sighandler_t); 72 83 libc::signal(libc::SIGINT, handle_shutdown as libc::sighandler_t); 73 84 } 85 + 86 + // SDL (initialised after parse_args returns) installs its own SIGTERM/SIGINT 87 + // handlers and overwrites ours. Reinstall after a short delay so our 88 + // handler — which kills typesense-server and _exit()s — wins. 89 + #[cfg(unix)] 90 + thread::spawn(|| { 91 + sleep(Duration::from_secs(3)); 92 + unsafe { 93 + libc::signal(libc::SIGTERM, handle_shutdown as libc::sighandler_t); 94 + libc::signal(libc::SIGINT, handle_shutdown as libc::sighandler_t); 95 + } 96 + }); 74 97 75 98 thread::spawn(move || { 76 99 let home = env::var("HOME").unwrap(); ··· 78 101 match fs::create_dir_all(format!("{}/Music", home)) { 79 102 Ok(_) => {} 80 103 Err(e) => { 81 - eprintln!("Failed to create Music directory: {}", e); 104 + error!("Failed to create Music directory: {}", e); 82 105 } 83 106 } 84 107 ··· 98 121 let tracks = repo::track::all(pool.clone()).await?; 99 122 if tracks.is_empty() || update_library { 100 123 match scan_audio_files(pool.clone(), path.into()).await { 101 - Ok(_) => println!("Finished scanning audio files"), 102 - Err(e) => eprintln!("Failed to scan audio files: {}", e), 124 + Ok(_) => info!("Finished scanning audio files"), 125 + Err(e) => error!("Failed to scan audio files: {}", e), 103 126 } 104 127 let tracks = repo::track::all(pool.clone()).await?; 105 128 let albums = repo::album::all(pool.clone()).await?; ··· 120 143 thread::spawn(move || { 121 144 sleep(Duration::from_secs(5)); 122 145 match rockbox_rocksky::register_rockbox() { 123 - Ok(_) => println!("Successfully registered Rockbox with Rocksky server"), 124 - Err(e) => eprintln!("Failed to register Rockbox with Rocksky server: {}", e), 146 + Ok(_) => info!("Successfully registered Rockbox with Rocksky server"), 147 + Err(e) => error!("Failed to register Rockbox with Rocksky server: {}", e), 125 148 }; 126 149 }); 127 150 ··· 139 162 let port = std::env::var("ROCKBOX_TCP_PORT").unwrap_or_else(|_| "6063".to_string()); 140 163 let addr = format!("0.0.0.0:{}", port); 141 164 142 - println!( 143 - "{} server is running on {}", 144 - "Rockbox TCP".bright_purple(), 145 - addr.bright_green() 146 - ); 165 + info!("Rockbox TCP server is running on {}", addr); 147 166 148 167 let graphql_port = env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string()); 149 168 let addr = format!("{}:{}", "0.0.0.0", graphql_port); 150 169 151 - println!( 152 - "{} server is running on {}", 153 - "Rockbox GraphQL".bright_purple(), 154 - addr.bright_green() 155 - ); 170 + info!("Rockbox GraphQL server is running on {}", addr); 156 171 157 172 let rockbox_port: u16 = std::env::var("ROCKBOX_PORT") 158 173 .unwrap_or_else(|_| "6061".to_string()) ··· 161 176 162 177 let host_and_port = format!("0.0.0.0:{}", rockbox_port); 163 178 164 - println!( 165 - "{} server is running on {}", 166 - "Rockbox gRPC".bright_purple(), 167 - host_and_port.bright_green() 168 - ); 179 + info!("Rockbox gRPC server is running on {}", host_and_port); 169 180 170 - println!( 171 - "Rockbox Web UI is running on {} ⚡", 172 - "http://localhost:6062".bright_green() 173 - ); 181 + info!("Rockbox Web UI is running on http://localhost:6062"); 174 182 }); 175 183 176 184 thread::spawn(move || { ··· 179 187 let api_key = uuid::Uuid::new_v4().to_string(); 180 188 let api_key = std::env::var("RB_TYPESENSE_API_KEY").unwrap_or(api_key); 181 189 std::env::set_var("RB_TYPESENSE_API_KEY", &api_key); 182 - println!("Using Typesense API key: {}", api_key); 190 + info!("Using Typesense API key: {}", api_key); 183 191 184 192 let port = std::env::var("RB_TYPESENSE_PORT").unwrap_or_else(|_| "8109".to_string()); 185 193 std::env::set_var("RB_TYPESENSE_PORT", &port); ··· 202 210 .arg(format!("--api-port={port}")) 203 211 .env("TYPESENSE_API_KEY", &api_key) 204 212 .env("TYPESENSE_DATA_DIR", &data_dir) 205 - .stdout(Stdio::inherit()) 206 - .stderr(Stdio::inherit()); 213 + .stdout(Stdio::piped()) 214 + .stderr(Stdio::piped()); 207 215 208 216 #[cfg(target_os = "linux")] 209 217 unsafe { ··· 223 231 let mut child = cmd.spawn()?; 224 232 TYPESENSE_PID.store(child.id() as i32, Ordering::SeqCst); 225 233 234 + if let Some(stdout) = child.stdout.take() { 235 + thread::spawn(move || { 236 + for line in BufReader::new(stdout).lines().flatten() { 237 + tracing::debug!(target: "typesense", "{}", line); 238 + } 239 + }); 240 + } 241 + if let Some(stderr) = child.stderr.take() { 242 + thread::spawn(move || { 243 + for line in BufReader::new(stderr).lines().flatten() { 244 + tracing::warn!(target: "typesense", "{}", line); 245 + } 246 + }); 247 + } 248 + 226 249 // Poll instead of blocking in waitpid so SIGTERM can reach the process. 227 250 loop { 228 251 match child.try_wait() { 229 252 Ok(Some(status)) => { 230 - eprintln!("typesense-server exited: {status}"); 253 + warn!("typesense-server exited: {status}"); 231 254 break; 232 255 } 233 256 Ok(None) => sleep(Duration::from_millis(500)), 234 257 Err(e) => { 235 - eprintln!("typesense-server monitor error: {e}"); 258 + error!("typesense-server monitor error: {e}"); 236 259 break; 237 260 } 238 261 } ··· 246 269 #[no_mangle] 247 270 pub extern "C" fn save_remote_track_metadata(url: *const std::ffi::c_char) -> i32 { 248 271 if url.is_null() { 249 - eprintln!("save_remote_track_metadata: null url"); 272 + warn!("save_remote_track_metadata: null url"); 250 273 return -1; 251 274 } 252 275 ··· 254 277 let url = match url.to_str() { 255 278 Ok(url) => url, 256 279 Err(e) => { 257 - eprintln!("save_remote_track_metadata: invalid utf-8: {}", e); 280 + warn!("save_remote_track_metadata: invalid utf-8: {}", e); 258 281 return -1; 259 282 } 260 283 }; ··· 262 285 let rt = match tokio::runtime::Runtime::new() { 263 286 Ok(rt) => rt, 264 287 Err(e) => { 265 - eprintln!( 288 + error!( 266 289 "save_remote_track_metadata: failed to create runtime: {}", 267 290 e 268 291 ); ··· 276 299 }) { 277 300 Ok(()) => 0, 278 301 Err(e) => { 279 - eprintln!("save_remote_track_metadata: {}", e); 302 + error!("save_remote_track_metadata: {}", e); 280 303 -1 281 304 } 282 305 }
+1
crates/library/Cargo.toml
··· 20 20 serde_json = "1.0.128" 21 21 sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 22 22 tokio = {version = "1.36.0", features = ["full"]} 23 + tracing = { workspace = true }
+5 -4
crates/library/src/repo/album.rs
··· 1 1 use crate::entity::album::Album; 2 2 use sqlx::{Pool, Sqlite}; 3 + use tracing::warn; 3 4 4 5 pub async fn save(pool: Pool<Sqlite>, album: Album) -> Result<String, sqlx::Error> { 5 6 match sqlx::query( ··· 68 69 { 69 70 Ok(album) => Ok(album), 70 71 Err(e) => { 71 - eprintln!("Error finding album: {:?}", e); 72 + warn!("Error finding album: {:?}", e); 72 73 Err(e) 73 74 } 74 75 } ··· 89 90 { 90 91 Ok(albums) => Ok(albums), 91 92 Err(e) => { 92 - eprintln!("Error finding albums: {:?}", e); 93 + warn!("Error finding albums: {:?}", e); 93 94 Err(e) 94 95 } 95 96 } ··· 107 108 { 108 109 Ok(album) => Ok(album), 109 110 Err(e) => { 110 - eprintln!("Error finding album: {:?}", e); 111 + warn!("Error finding album: {:?}", e); 111 112 Err(e) 112 113 } 113 114 } ··· 124 125 { 125 126 Ok(albums) => Ok(albums), 126 127 Err(e) => { 127 - eprintln!("Error finding albums: {:?}", e); 128 + warn!("Error finding albums: {:?}", e); 128 129 Err(e) 129 130 } 130 131 }
+1
crates/mpd/Cargo.toml
··· 19 19 tokio = {version = "1.36.0", features = ["full"]} 20 20 tokio-stream = "0.1" 21 21 tonic = "0.12.2" 22 + tracing = { workspace = true }
+3 -2
crates/mpd/src/handlers/playback.rs
··· 4 4 SaveSettingsRequest, StartRequest, 5 5 }; 6 6 use tokio::sync::mpsc::Sender; 7 + use tracing::warn; 7 8 8 9 use crate::Context; 9 10 ··· 253 254 tx: Sender<String>, 254 255 ) -> Result<String, Error> { 255 256 // TODO: Implement seek 256 - println!("{}", request); 257 + warn!("handle_seek not implemented: {}", request); 257 258 258 259 if !ctx.batch { 259 260 tx.send("OK\n".to_string()).await?; ··· 268 269 tx: Sender<String>, 269 270 ) -> Result<String, Error> { 270 271 // TODO: Implement seekid 271 - println!("{}", request); 272 + warn!("handle_seekid not implemented: {}", request); 272 273 273 274 if !ctx.batch { 274 275 tx.send("OK\n".to_string()).await?;
+1
crates/mpris/Cargo.toml
··· 12 12 rockbox-library = {path= "../library"} 13 13 tokio = {version = "1.36.0", features = ["full"]} 14 14 urlencoding = "2.1.3" 15 + tracing = { workspace = true }
+6 -5
crates/mpris/src/lib.rs
··· 14 14 PreviousRequest, ResumeRequest, SaveSettingsRequest, 15 15 }; 16 16 use tokio::sync::Mutex; 17 + use tracing::warn; 17 18 18 19 pub mod macros; 19 20 ··· 94 95 1 => match player.set_playback_status(PlaybackStatus::Playing).await { 95 96 Ok(_) => {} 96 97 Err(e) => { 97 - eprintln!("Error: {}", e); 98 + warn!("Error: {}", e); 98 99 } 99 100 }, 100 101 3 => match player.set_playback_status(PlaybackStatus::Paused).await { 101 102 Ok(_) => {} 102 103 Err(e) => { 103 - eprintln!("Error: {}", e); 104 + warn!("Error: {}", e); 104 105 } 105 106 }, 106 107 _ => match player.set_playback_status(PlaybackStatus::Stopped).await { 107 108 Ok(_) => {} 108 109 Err(e) => { 109 - eprintln!("Error: {}", e); 110 + warn!("Error: {}", e); 110 111 } 111 112 }, 112 113 } ··· 146 147 match player.set_metadata(metadata).await { 147 148 Ok(_) => {} 148 149 Err(e) => { 149 - eprintln!("Error: {}", e); 150 + warn!("Error: {}", e); 150 151 } 151 152 } 152 153 ··· 154 155 match player.seeked(Time::from_millis(track.elapsed as i64)).await { 155 156 Ok(_) => {} 156 157 Err(e) => { 157 - eprintln!("Error: {}", e); 158 + warn!("Error: {}", e); 158 159 } 159 160 } 160 161 }
+8 -8
crates/mpris/src/macros.rs
··· 12 12 }) { 13 13 Ok(_) => {} 14 14 Err(e) => { 15 - eprintln!("Error: {}", e); 15 + tracing::warn!("Error: {}", e); 16 16 } 17 17 } 18 18 }); ··· 38 38 }) { 39 39 Ok(_) => {} 40 40 Err(e) => { 41 - eprintln!("Error: {}", e); 41 + tracing::warn!("Error: {}", e); 42 42 } 43 43 } 44 44 }); ··· 64 64 }) { 65 65 Ok(_) => {} 66 66 Err(e) => { 67 - eprintln!("Error: {}", e); 67 + tracing::warn!("Error: {}", e); 68 68 } 69 69 } 70 70 }); ··· 92 92 }) { 93 93 Ok(_) => {} 94 94 Err(e) => { 95 - eprintln!("Error: {}", e); 95 + tracing::warn!("Error: {}", e); 96 96 } 97 97 } 98 98 }); ··· 114 114 { 115 115 Ok(response) => response.into_inner().volume, 116 116 Err(e) => { 117 - eprintln!("Error: {}", e); 117 + tracing::warn!("Error: {}", e); 118 118 0 119 119 } 120 120 }; ··· 132 132 }) { 133 133 Ok(_) => {} 134 134 Err(e) => { 135 - eprintln!("Error: {}", e); 135 + tracing::warn!("Error: {}", e); 136 136 } 137 137 } 138 138 }); ··· 158 158 }) { 159 159 Ok(_) => {} 160 160 Err(e) => { 161 - eprintln!("Error: {}", e); 161 + tracing::warn!("Error: {}", e); 162 162 } 163 163 } 164 164 }); ··· 189 189 }) { 190 190 Ok(_) => {} 191 191 Err(e) => { 192 - eprintln!("Error: {}", e); 192 + tracing::warn!("Error: {}", e); 193 193 } 194 194 } 195 195 });
+1
crates/netstream/Cargo.toml
··· 11 11 reqwest = { version = "0.12.5", features = ["blocking", "rustls-tls"], default-features = false } 12 12 once_cell = "1.17.1" 13 13 libc = "0.2.168" 14 + tracing = "0.1" 14 15 15 16 [dev-dependencies] 16 17 mockito = "1.7.2"
+16 -11
crates/netstream/src/lib.rs
··· 5 5 use std::os::raw::c_char; 6 6 use std::sync::atomic::{AtomicI32, Ordering}; 7 7 use std::sync::{Arc, Mutex}; 8 + use tracing::{debug, warn}; 8 9 9 10 /// Sentinel handle ID returned on error. 10 11 const INVALID_HANDLE: i32 = -1; ··· 186 187 187 188 let state = match StreamState::new(url_str.clone()) { 188 189 Some(s) => { 189 - eprintln!( 190 + debug!( 190 191 "[netstream] rb_net_open: url={} content_length={:?} content_type={:?}", 191 192 url_str, s.content_length, s.content_type 192 193 ); 193 194 s 194 195 } 195 196 None => { 196 - eprintln!("[netstream] rb_net_open: FAILED url={}", url_str); 197 + warn!("[netstream] rb_net_open: FAILED url={}", url_str); 197 198 return INVALID_HANDLE; 198 199 } 199 200 }; ··· 203 204 .lock() 204 205 .unwrap() 205 206 .insert(handle, Arc::new(Mutex::new(state))); 206 - eprintln!( 207 + debug!( 207 208 "[netstream] rb_net_open: url={} -> handle={}", 208 209 url_str, handle 209 210 ); ··· 239 240 match read_as_file(resp, buf) { 240 241 Ok(bytes_read) => { 241 242 state.pos += bytes_read as u64; 242 - eprintln!( 243 + tracing::trace!( 243 244 "[netstream] rb_net_read: h={} n={} pos_before={} -> read={} pos_after={}", 244 - h, n, pos_before, bytes_read, state.pos 245 + h, 246 + n, 247 + pos_before, 248 + bytes_read, 249 + state.pos 245 250 ); 246 251 bytes_read as i64 247 252 } 248 253 Err(e) => { 249 - eprintln!( 254 + warn!( 250 255 "[netstream] rb_net_read: h={} n={} pos={} -> ERROR {:?}", 251 256 h, n, pos_before, e 252 257 ); ··· 312 317 313 318 // Fast-path: already there (no need to restart the request). 314 319 if new_pos == state.pos { 315 - eprintln!( 320 + debug!( 316 321 "[netstream] rb_net_lseek: h={} off={} whence={} -> already at pos={} (no-op)", 317 322 h, off, whence, state.pos 318 323 ); ··· 321 326 322 327 let old_pos = state.pos; 323 328 if state.seek_to(new_pos) { 324 - eprintln!( 329 + debug!( 325 330 "[netstream] rb_net_lseek: h={} off={} whence={} old_pos={} -> new_pos={}", 326 331 h, off, whence, old_pos, state.pos 327 332 ); 328 333 state.pos as i64 329 334 } else { 330 - eprintln!( 335 + warn!( 331 336 "[netstream] rb_net_lseek: h={} off={} whence={} old_pos={} -> FAILED", 332 337 h, off, whence, old_pos 333 338 ); ··· 351 356 .content_length 352 357 .map(|l| l as i64) 353 358 .unwrap_or(-1); 354 - eprintln!("[netstream] rb_net_len: h={} -> {}", h, len); 359 + debug!("[netstream] rb_net_len: h={} -> {}", h, len); 355 360 len 356 361 } 357 362 ··· 388 393 /// Close stream `h` and release its resources. 389 394 #[no_mangle] 390 395 pub extern "C" fn rb_net_close(h: i32) { 391 - eprintln!("[netstream] rb_net_close: h={}", h); 396 + debug!("[netstream] rb_net_close: h={}", h); 392 397 STREAMS.lock().unwrap().remove(&h); 393 398 } 394 399
+2
crates/rpc/src/lib.rs
··· 929 929 release_time: c.release_time, 930 930 attack_time: c.attack_time, 931 931 }), 932 + audio_output: None, 933 + fifo_path: None, 932 934 } 933 935 } 934 936 }
+1
crates/server/Cargo.toml
··· 40 40 tokio = {version = "1.36.0", features = ["full"]} 41 41 url = "2.3.1" 42 42 urlencoding = "2.1.3" 43 + tracing = { workspace = true }
+3 -2
crates/server/src/http.rs
··· 19 19 time::Duration, 20 20 }; 21 21 use threadpool::ThreadPool; 22 + use tracing::{debug, error}; 22 23 23 24 use crate::{ 24 25 kv::{build_tracks_kv, KV}, ··· 357 358 rb::system::sleep(rb::HZ); 358 359 } 359 360 Err(e) => { 360 - eprintln!("Error accepting connection: {}", e); 361 + error!("Error accepting connection: {}", e); 361 362 break; 362 363 } 363 364 } ··· 391 392 player: Arc<Mutex<Option<Box<dyn Player + Send>>>>, 392 393 kv: Arc<Mutex<KV<Track>>>, 393 394 ) { 394 - println!("{} {}", method.bright_cyan(), path); 395 + debug!("{} {}", method, path); 395 396 match self.router.route(method, path) { 396 397 Some((handler, params)) => { 397 398 let mut response = Response::new();
+10 -9
crates/server/src/lib.rs
··· 1 1 use anyhow::Error; 2 2 use handlers::*; 3 + use tracing::{error, warn}; 3 4 4 5 use http::RockboxHttpServer; 5 6 use lazy_static::lazy_static; ··· 59 60 pub extern "C" fn debugfn(args: *const c_char, value: c_int) { 60 61 let c_str = unsafe { std::ffi::CStr::from_ptr(args) }; 61 62 let str_slice = c_str.to_str().unwrap(); 62 - println!("{} {}", str_slice, value); 63 + tracing::debug!("{} {}", str_slice, value); 63 64 } 64 65 65 66 #[no_mangle] ··· 67 68 match rockbox_settings::load_settings(None) { 68 69 Ok(_) => {} 69 70 Err(e) => { 70 - println!("Warning loading settings: {}", e); 71 + warn!("Warning loading settings: {}", e); 71 72 } 72 73 } 73 74 ··· 134 135 match app.listen() { 135 136 Ok(_) => {} 136 137 Err(e) => { 137 - eprintln!("Error starting server: {}", e); 138 + error!("Error starting server: {}", e); 138 139 } 139 140 } 140 141 } ··· 219 220 match runtime.block_on(rockbox_rpc::server::start(cmd_tx.clone())) { 220 221 Ok(_) => {} 221 222 Err(e) => { 222 - eprintln!("Error starting server: {}", e); 223 + error!("Error starting server: {}", e); 223 224 } 224 225 } 225 226 }); ··· 232 233 match runtime.block_on(rockbox_graphql::server::start(cloned_cmd_tx.clone())) { 233 234 Ok(_) => {} 234 235 Err(e) => { 235 - eprintln!("Error starting server: {}", e); 236 + error!("Error starting server: {}", e); 236 237 } 237 238 } 238 239 }); ··· 247 248 move || match async_std::task::block_on(MprisServer::start()) { 248 249 Ok(_) => {} 249 250 Err(e) => { 250 - eprintln!("Error starting mpris server: {}", e); 251 + error!("Error starting mpris server: {}", e); 251 252 } 252 253 }, 253 254 ); ··· 261 262 match runtime.block_on(MpdServer::start()) { 262 263 Ok(_) => {} 263 264 Err(e) => { 264 - eprintln!("Error starting mpd server: {}", e); 265 + error!("Error starting mpd server: {}", e); 265 266 } 266 267 } 267 268 }); ··· 398 399 .block_on(scrobble(cloned_track, cloned_pool.clone())) 399 400 { 400 401 Ok(_) => {} 401 - Err(e) => eprintln!("{}", e), 402 + Err(e) => error!("{}", e), 402 403 } 403 404 }); 404 405 scrobbled_tracks.insert(metadata.id.clone()); ··· 536 537 if let Some(album) = album { 537 538 match rockbox_rocksky::scrobble(track, album).await { 538 539 Ok(_) => {} 539 - Err(e) => eprintln!("Failed to scrobble {}", e), 540 + Err(e) => error!("Failed to scrobble {}", e), 540 541 }; 541 542 } 542 543 }
+9 -1
crates/settings/src/lib.rs
··· 1 1 use anyhow::Error; 2 - use rockbox_sys::{self as rb, types::user_settings::NewGlobalSettings}; 2 + use rockbox_sys::{self as rb, sound::pcm, types::user_settings::NewGlobalSettings}; 3 3 4 4 pub fn load_settings(new_settings: Option<NewGlobalSettings>) -> Result<(), Error> { 5 5 let settings: NewGlobalSettings = match new_settings.clone() { ··· 27 27 } 28 28 29 29 rb::settings::save_settings(settings.clone(), new_settings.is_none()); 30 + 31 + if let Some(ref output) = settings.audio_output { 32 + if output == "fifo" { 33 + let path = settings.fifo_path.as_deref().unwrap_or("/tmp/rockbox.fifo"); 34 + pcm::fifo_set_path(path); 35 + pcm::switch_sink(pcm::PCM_SINK_FIFO); 36 + } 37 + } 30 38 31 39 rb::settings::apply_audio_settings(); 32 40
+2
crates/sys/src/lib.rs
··· 1146 1146 fn pcm_is_playing() -> c_uchar; 1147 1147 fn pcm_play_lock(); 1148 1148 fn pcm_play_unlock(); 1149 + fn pcm_switch_sink(sink: c_int) -> c_uchar; 1150 + fn pcm_fifo_set_path(path: *const c_char); 1149 1151 fn beep_play(frequency: c_uint, duration: c_uint, amplitude: c_uint); 1150 1152 fn dsp_set_crossfeed_type(r#type: c_int); 1151 1153 fn dsp_eq_enable(enable: c_uchar);
+16 -1
crates/sys/src/sound/pcm.rs
··· 1 - use std::ffi::c_void; 1 + use std::ffi::{c_void, CString}; 2 2 3 3 use crate::{PcmPlayCallbackType, PcmStatusCallbackType}; 4 + 5 + pub const PCM_SINK_BUILTIN: i32 = 0; 6 + pub const PCM_SINK_FIFO: i32 = 1; 4 7 5 8 pub fn apply_settings() { 6 9 unsafe { ··· 43 46 crate::pcm_play_unlock(); 44 47 } 45 48 } 49 + 50 + pub fn switch_sink(sink: i32) -> bool { 51 + unsafe { crate::pcm_switch_sink(sink) != 0 } 52 + } 53 + 54 + pub fn fifo_set_path(path: &str) { 55 + let cpath = CString::new(path).expect("path must not contain null bytes"); 56 + unsafe { crate::pcm_fifo_set_path(cpath.as_ptr()) } 57 + // Keep alive until C code finishes using it — it's only read during init, 58 + // so leaking is acceptable here for a startup-time config call. 59 + std::mem::forget(cpath); 60 + }
+6
crates/sys/src/types/user_settings.rs
··· 677 677 pub eq_band_settings: Option<Vec<EqBandSetting>>, 678 678 pub replaygain_settings: Option<ReplaygainSettings>, 679 679 pub compressor_settings: Option<CompressorSettings>, 680 + /// Audio output sink: "builtin" (default) or "fifo" 681 + pub audio_output: Option<String>, 682 + /// Path for the FIFO sink, e.g. "/tmp/rockbox.fifo" or "-" for stdout 683 + pub fifo_path: Option<String>, 680 684 } 681 685 682 686 impl From<UserSettings> for NewGlobalSettings { ··· 710 714 eq_band_settings: Some(settings.eq_band_settings), 711 715 replaygain_settings: Some(settings.replaygain_settings), 712 716 compressor_settings: Some(settings.compressor_settings), 717 + audio_output: None, 718 + fifo_path: None, 713 719 } 714 720 } 715 721 }
+1
crates/typesense/Cargo.toml
··· 17 17 serde.workspace = true 18 18 serde_json.workspace = true 19 19 uuid.workspace = true 20 + tracing = { workspace = true }
+18 -17
crates/typesense/src/client.rs
··· 1 1 use crate::types::*; 2 2 use anyhow::Error; 3 3 use reqwest::Client; 4 + use tracing::{debug, info, warn}; 4 5 5 6 pub async fn create_tracks_collection() -> Result<(), Error> { 6 7 let client = Client::new(); ··· 38 39 39 40 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 40 41 if api_key.is_err() { 41 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 42 + warn!("RB_TYPESENSE_API_KEY is not set."); 42 43 return Ok(()); 43 44 } 44 45 let api_key = api_key.unwrap(); ··· 49 50 .send() 50 51 .await?; 51 52 52 - println!("Create tracks collection response: {}", res.status()); 53 + debug!("Create tracks collection response: {}", res.status()); 53 54 54 55 Ok(()) 55 56 } ··· 78 79 79 80 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 80 81 if api_key.is_err() { 81 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 82 + warn!("RB_TYPESENSE_API_KEY is not set."); 82 83 return Ok(()); 83 84 } 84 85 let api_key = api_key.unwrap(); ··· 89 90 .send() 90 91 .await?; 91 92 92 - println!("Create albums collection response: {}", res.status()); 93 + debug!("Create albums collection response: {}", res.status()); 93 94 94 95 Ok(()) 95 96 } ··· 113 114 114 115 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 115 116 if api_key.is_err() { 116 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 117 + warn!("RB_TYPESENSE_API_KEY is not set."); 117 118 return Ok(()); 118 119 } 119 120 let api_key = api_key.unwrap(); ··· 124 125 .send() 125 126 .await?; 126 127 127 - println!("Create artists collection response: {}", res.status()); 128 + debug!("Create artists collection response: {}", res.status()); 128 129 129 130 Ok(()) 130 131 } ··· 145 146 146 147 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 147 148 if api_key.is_err() { 148 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 149 + warn!("RB_TYPESENSE_API_KEY is not set."); 149 150 return Ok(()); 150 151 } 151 152 let api_key = api_key.unwrap(); ··· 160 161 .send() 161 162 .await?; 162 163 163 - println!("Insert tracks response: {}", res.status()); 164 + info!("Insert tracks response: {}", res.status()); 164 165 165 166 Ok(()) 166 167 } ··· 181 182 182 183 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 183 184 if api_key.is_err() { 184 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 185 + warn!("RB_TYPESENSE_API_KEY is not set."); 185 186 return Ok(()); 186 187 } 187 188 let api_key = api_key.unwrap(); ··· 196 197 .send() 197 198 .await?; 198 199 199 - println!("Insert albums response: {}", res.status()); 200 + info!("Insert albums response: {}", res.status()); 200 201 201 202 Ok(()) 202 203 } ··· 217 218 218 219 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 219 220 if api_key.is_err() { 220 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 221 + warn!("RB_TYPESENSE_API_KEY is not set."); 221 222 return Ok(()); 222 223 } 223 224 let api_key = api_key.unwrap(); ··· 232 233 .send() 233 234 .await?; 234 235 235 - println!("Insert artists response: {}", res.status()); 236 + info!("Insert artists response: {}", res.status()); 236 237 237 238 Ok(()) 238 239 } ··· 247 248 248 249 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 249 250 if api_key.is_err() { 250 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 251 + warn!("RB_TYPESENSE_API_KEY is not set."); 251 252 return Ok(None); 252 253 } 253 254 let api_key = api_key.unwrap(); ··· 272 273 match serde_json::from_str::<TrackResult>(&text) { 273 274 Ok(result) => Ok(Some(result)), 274 275 Err(e) => { 275 - eprintln!("Failed to parse Typesense response: {}", e); 276 - eprintln!("Response body: {}", text); 276 + warn!("Failed to parse Typesense response: {}", e); 277 + warn!("Response body: {}", text); 277 278 Err(e.into()) 278 279 } 279 280 } ··· 289 290 290 291 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 291 292 if api_key.is_err() { 292 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 293 + warn!("RB_TYPESENSE_API_KEY is not set."); 293 294 return Ok(None); 294 295 } 295 296 let api_key = api_key.unwrap(); ··· 323 324 324 325 let api_key = std::env::var("RB_TYPESENSE_API_KEY"); 325 326 if api_key.is_err() { 326 - println!("Warning: RB_TYPESENSE_API_KEY is not set."); 327 + warn!("RB_TYPESENSE_API_KEY is not set."); 327 328 return Ok(None); 328 329 } 329 330 let api_key = api_key.unwrap();
+4 -3
crates/typesense/src/lib.rs
··· 2 2 fs, 3 3 process::{Command, Stdio}, 4 4 }; 5 + use tracing::info; 5 6 6 7 pub mod client; 7 8 pub mod types; ··· 30 31 if !data_dir.join("api-key").exists() { 31 32 let api_key = uuid::Uuid::new_v4().to_string(); 32 33 fs::write(data_dir.join("api-key"), &api_key)?; 33 - println!("Generated new Typesense API key: {}", api_key); 34 + info!("Generated new Typesense API key: {}", api_key); 34 35 if std::env::var("RB_TYPESENSE_API_KEY").is_err() { 35 36 std::env::set_var("RB_TYPESENSE_API_KEY", &api_key); 36 37 } 37 38 } else { 38 39 let api_key = fs::read_to_string(data_dir.join("api-key"))?; 39 - println!("Using existing Typesense API key: {}", api_key); 40 + info!("Using existing Typesense API key: {}", api_key); 40 41 if std::env::var("RB_TYPESENSE_API_KEY").is_err() { 41 42 std::env::set_var("RB_TYPESENSE_API_KEY", &api_key); 42 43 } 43 44 } 44 45 45 46 if cmd.wait()?.success() { 46 - println!("Typesense server is already installed and available in PATH."); 47 + info!("Typesense server is already installed and available in PATH."); 47 48 return Ok(()); 48 49 } 49 50
+3
firmware/SOURCES
··· 535 535 pcm_sampr.c 536 536 pcm.c 537 537 pcm_mixer.c 538 + #if (CONFIG_PLATFORM & PLATFORM_HOSTED) 539 + target/hosted/pcm-fifo.c 540 + #endif 538 541 #ifdef HAVE_SW_VOLUME_CONTROL 539 542 pcm_sw_volume.c 540 543 #endif /* HAVE_SW_VOLUME_CONTROL */
+9
firmware/export/pcm_sink.h
··· 52 52 53 53 enum pcm_sink_ids { 54 54 PCM_SINK_BUILTIN = 0, 55 + #if (CONFIG_PLATFORM & PLATFORM_HOSTED) 56 + PCM_SINK_FIFO, 57 + #endif 55 58 PCM_SINK_NUM 56 59 }; 57 60 58 61 /* defined in each platform pcm source */ 59 62 extern struct pcm_sink builtin_pcm_sink; 63 + 64 + #if (CONFIG_PLATFORM & PLATFORM_HOSTED) 65 + /* FIFO/pipe sink — writes raw S16LE stereo PCM to a named FIFO or stdout */ 66 + extern struct pcm_sink fifo_pcm_sink; 67 + void pcm_fifo_set_path(const char *path); 68 + #endif
+4
firmware/pcm.c
··· 31 31 #include "general.h" 32 32 #include "pcm-internal.h" 33 33 #include "pcm_mixer.h" 34 + #include "pcm_sink.h" 34 35 35 36 /** 36 37 * Aspects implemented in the target-specific portion: ··· 79 80 80 81 static struct pcm_sink* sinks[PCM_SINK_NUM] = { 81 82 [PCM_SINK_BUILTIN] = &builtin_pcm_sink, 83 + #if (CONFIG_PLATFORM & PLATFORM_HOSTED) 84 + [PCM_SINK_FIFO] = &fifo_pcm_sink, 85 + #endif 82 86 }; 83 87 static enum pcm_sink_ids cur_sink = PCM_SINK_BUILTIN; 84 88
+6 -1
firmware/target/hosted/sdl/pcm-sdl.c
··· 46 46 #include "pcm_mixer.h" 47 47 #include "pcm_sink.h" 48 48 49 - /*#define LOGF_ENABLE*/ 49 + #define LOGF_ENABLE 50 50 #include "logf.h" 51 51 52 52 #ifdef DEBUG ··· 123 123 #endif 124 124 125 125 /* Open the audio device and start playing sound! */ 126 + logf("pcm-sdl: opening device '%s' at %lu Hz", audiodev ? audiodev : "(default)", pcm_sampr); 126 127 #if SDL_MAJOR_VERSION > 1 127 128 if((pcm_devid = SDL_OpenAudioDevice(audiodev, 0, &wanted_spec, &obtained, SDL_AUDIO_ALLOW_SAMPLES_CHANGE)) == 0) { 128 129 #else ··· 131 132 panicf("Unable to open audio: %s", SDL_GetError()); 132 133 return; 133 134 } 135 + logf("pcm-sdl: device opened id=%u freq=%d fmt=%u ch=%u", 136 + (unsigned)pcm_devid, obtained.freq, (unsigned)obtained.format, 137 + (unsigned)obtained.channels); 134 138 135 139 switch (obtained.format) 136 140 { ··· 176 180 177 181 static void sink_dma_start(const void *addr, size_t size) 178 182 { 183 + logf("pcm-sdl: start devid=%u size=%zu", (unsigned)pcm_devid, size); 179 184 pcm_data = addr; 180 185 pcm_data_size = size; 181 186
+3
firmware/target/hosted/sdl/system-sdl.c
··· 273 273 SDL_DestroySemaphore(s); 274 274 #else 275 275 SDL_AddEventWatch(sdl_event_filter, NULL); 276 + /* On macOS the event thread is not created, so SDL_INIT_AUDIO is never 277 + initialized there. Do it here so SDL_OpenAudioDevice can succeed. */ 278 + SDL_InitSubSystem(SDL_INIT_AUDIO); 276 279 #endif 277 280 } 278 281