Browse and listen to thousands of radio stations across the globe right from your terminal ๐ŸŒŽ ๐Ÿ“ป ๐ŸŽตโœจ
radio rust tokio web-radio command-line-tool tui

Merge pull request #34 from tsirysndr/fix/metadata

fix metadata not updated

authored by tsiry-sandratraina.com and committed by GitHub e21f6ced c489121e

Changed files
+48 -18
src
+45 -16
src/app.rs
··· 338 ) { 339 let new_state = cmd_rx.recv().await.unwrap(); 340 341 - // report metadata to OS 342 send_os_media_controls_command( 343 self.os_media_controls.as_mut(), 344 os_media_controls::Command::SetMetadata(souvlaki::MediaMetadata { 345 - title: (!new_state.now_playing.is_empty()).then_some(&new_state.now_playing), 346 - album: (!new_state.name.is_empty()).then_some(&new_state.name), 347 artist: None, 348 cover_url: None, 349 duration: None, 350 }), 351 ); 352 - // report started playing to OS 353 send_os_media_controls_command( 354 self.os_media_controls.as_mut(), 355 os_media_controls::Command::Play, 356 ); 357 - // report volume to OS 358 send_os_media_controls_command( 359 self.os_media_controls.as_mut(), 360 os_media_controls::Command::SetVolume(new_state.volume.volume_ratio() as f64), 361 ); 362 363 let new_state = Arc::new(Mutex::new(new_state)); 364 - 365 let id = id.to_string(); 366 let new_state_clone = new_state.clone(); 367 368 - thread::spawn(move || loop { 369 let rt = tokio::runtime::Runtime::new().unwrap(); 370 rt.block_on(async { 371 - let mut new_state = new_state_clone.lock().unwrap(); 372 - // Get current playing if available, otherwise use state's value 373 - new_state.now_playing = get_currently_playing(&id).await.unwrap_or_default(); 374 - drop(new_state); 375 - std::thread::sleep(Duration::from_millis(10000)); 376 }); 377 }); 378 379 let mut fps = 0; 380 let mut framerate = 0; 381 let mut last_poll = Instant::now(); 382 383 loop { 384 let channels = if self.graph.pause { 385 None 386 } else { 387 let Ok(audio_frame) = self.frame_rx.recv() else { 388 - // other thread has closed so application has 389 - // closed 390 return; 391 }; 392 Some(stream_to_matrix( ··· 441 size.y += 8; 442 } 443 let chart = Chart::new(datasets.iter().map(|x| x.into()).collect()) 444 - .x_axis(current_display.axis(&self.graph, Dimension::X)) // TODO allow to have axis sometimes? 445 .y_axis(current_display.axis(&self.graph, Dimension::Y)); 446 f.render_widget(chart, size) 447 } 448 }) 449 .unwrap(); 450 } 451 452 while let Some(event) = self ··· 481 } 482 } 483 } 484 - 485 fn current_display_mut(&mut self) -> Option<&mut dyn DisplayMode> { 486 match self.mode { 487 CurrentDisplayMode::Oscilloscope => {
··· 338 ) { 339 let new_state = cmd_rx.recv().await.unwrap(); 340 341 + let now_playing = new_state.now_playing.clone(); 342 + let name = new_state.name.clone(); 343 + // Report initial metadata to OS 344 send_os_media_controls_command( 345 self.os_media_controls.as_mut(), 346 os_media_controls::Command::SetMetadata(souvlaki::MediaMetadata { 347 + title: (!now_playing.is_empty()).then(|| now_playing.as_str()), 348 + album: (!name.is_empty()).then(|| name.as_str()), 349 artist: None, 350 cover_url: None, 351 duration: None, 352 }), 353 ); 354 + // Report started playing to OS 355 send_os_media_controls_command( 356 self.os_media_controls.as_mut(), 357 os_media_controls::Command::Play, 358 ); 359 + // Report volume to OS 360 send_os_media_controls_command( 361 self.os_media_controls.as_mut(), 362 os_media_controls::Command::SetVolume(new_state.volume.volume_ratio() as f64), 363 ); 364 365 let new_state = Arc::new(Mutex::new(new_state)); 366 let id = id.to_string(); 367 let new_state_clone = new_state.clone(); 368 369 + // Background thread to update now_playing 370 + thread::spawn(move || { 371 let rt = tokio::runtime::Runtime::new().unwrap(); 372 rt.block_on(async { 373 + loop { 374 + let mut new_state = new_state_clone.lock().unwrap(); 375 + // Get current playing if available, otherwise use default 376 + let now_playing = get_currently_playing(&id).await.unwrap_or_default(); 377 + if new_state.now_playing != now_playing { 378 + new_state.now_playing = now_playing; 379 + } 380 + drop(new_state); 381 + std::thread::sleep(Duration::from_millis(10000)); 382 + } 383 }); 384 }); 385 386 let mut fps = 0; 387 let mut framerate = 0; 388 let mut last_poll = Instant::now(); 389 + let mut last_metadata_update = Instant::now(); 390 + let mut last_now_playing = String::new(); 391 + const METADATA_UPDATE_INTERVAL: Duration = Duration::from_secs(1); // Check every second 392 393 loop { 394 let channels = if self.graph.pause { 395 None 396 } else { 397 let Ok(audio_frame) = self.frame_rx.recv() else { 398 + // other thread has closed so application has closed 399 return; 400 }; 401 Some(stream_to_matrix( ··· 450 size.y += 8; 451 } 452 let chart = Chart::new(datasets.iter().map(|x| x.into()).collect()) 453 + .x_axis(current_display.axis(&self.graph, Dimension::X)) 454 .y_axis(current_display.axis(&self.graph, Dimension::Y)); 455 f.render_widget(chart, size) 456 } 457 }) 458 .unwrap(); 459 + 460 + // Update metadata only if needed and at a controlled interval 461 + if last_metadata_update.elapsed() >= METADATA_UPDATE_INTERVAL { 462 + let state = new_state.lock().unwrap(); 463 + if state.now_playing != last_now_playing { 464 + let now_playing = state.now_playing.clone(); 465 + let name = state.name.clone(); 466 + send_os_media_controls_command( 467 + self.os_media_controls.as_mut(), 468 + os_media_controls::Command::SetMetadata(souvlaki::MediaMetadata { 469 + title: (!now_playing.is_empty()).then_some(now_playing.as_str()), 470 + album: (!name.is_empty()).then_some(name.as_str()), 471 + artist: None, 472 + cover_url: None, 473 + duration: None, 474 + }), 475 + ); 476 + last_now_playing = state.now_playing.clone(); 477 + } 478 + last_metadata_update = Instant::now(); 479 + } 480 } 481 482 while let Some(event) = self ··· 511 } 512 } 513 } 514 fn current_display_mut(&mut self) -> Option<&mut dyn DisplayMode> { 515 match self.mode { 516 CurrentDisplayMode::Oscilloscope => {
+3 -2
src/play.rs
··· 1 - use std::{thread, time::Duration}; 2 3 use anyhow::Error; 4 use hyper::header::HeaderValue; ··· 167 let mut terminal = tui::init()?; 168 app.run(&mut terminal, cmd_rx, sink_cmd_tx, &id).await; 169 tui::restore()?; 170 - Ok(()) 171 } 172 173 /// Command for a sink.
··· 1 + use std::{process, thread, time::Duration}; 2 3 use anyhow::Error; 4 use hyper::header::HeaderValue; ··· 167 let mut terminal = tui::init()?; 168 app.run(&mut terminal, cmd_rx, sink_cmd_tx, &id).await; 169 tui::restore()?; 170 + 171 + process::exit(0); 172 } 173 174 /// Command for a sink.