chore: refactor async device discovery, tracing, and player Arc state

This commit is contained in:
2026-01-13 17:15:59 +01:00
parent ab3a86041a
commit efdba35b77
4 changed files with 181 additions and 58 deletions

View File

@@ -86,10 +86,11 @@ pub struct PlayerController {
pub tx: mpsc::Sender<PlayerCommand>,
}
pub fn spawn_player_thread(shared: &'static PlayerShared) -> PlayerController {
pub fn spawn_player_thread(shared: std::sync::Arc<PlayerShared>) -> PlayerController {
let (tx, rx) = mpsc::channel::<PlayerCommand>();
std::thread::spawn(move || player_thread(shared, rx));
let shared_for_thread = std::sync::Arc::clone(&shared);
std::thread::spawn(move || player_thread(shared_for_thread, rx));
PlayerController { tx }
}
@@ -113,14 +114,14 @@ fn volume_from_bits(bits: u32) -> f32 {
f32::from_bits(bits)
}
fn set_status(shared: &'static PlayerShared, status: PlayerStatus) {
fn set_status(shared: &std::sync::Arc<PlayerShared>, status: PlayerStatus) {
let mut s = shared.state.lock().unwrap();
if s.status != status {
s.status = status;
}
}
fn set_error(shared: &'static PlayerShared, message: String) {
fn set_error(shared: &std::sync::Arc<PlayerShared>, message: String) {
let mut s = shared.state.lock().unwrap();
s.status = PlayerStatus::Error;
s.error = Some(message);
@@ -219,7 +220,7 @@ struct Pipeline {
}
impl Pipeline {
fn start(shared: &'static PlayerShared, url: String, mode: PipelineMode) -> Result<Self, String> {
fn start(shared: std::sync::Arc<PlayerShared>, url: String, mode: PipelineMode) -> Result<Self, String> {
let (device, sample_format, cfg, sample_rate, channels) = match mode {
PipelineMode::WithOutput => {
let host = cpal::default_host();
@@ -265,7 +266,7 @@ impl Pipeline {
// Decoder thread: spawns ffmpeg, reads PCM, writes into ring buffer.
let stop_for_decoder = Arc::clone(&stop_flag);
let shared_for_decoder = shared;
let shared_for_decoder = std::sync::Arc::clone(&shared);
let decoder_url = url.clone();
let cast_tx_for_decoder = Arc::clone(&cast_tx);
let decoder_join = std::thread::spawn(move || {
@@ -280,7 +281,7 @@ impl Pipeline {
break;
}
set_status(shared_for_decoder, PlayerStatus::Buffering);
set_status(&shared_for_decoder, PlayerStatus::Buffering);
let ffmpeg = ffmpeg_command();
let ffmpeg_disp = ffmpeg.to_string_lossy();
@@ -314,7 +315,7 @@ impl Pipeline {
Err(e) => {
// If ffmpeg isn't available, this is a hard failure.
set_error(
shared_for_decoder,
&shared_for_decoder,
format!(
"Failed to start ffmpeg ({ffmpeg_disp}): {e}. Set RADIOPLAYER_FFMPEG, bundle ffmpeg next to the app, or install ffmpeg on PATH."
),
@@ -326,7 +327,7 @@ impl Pipeline {
let mut stdout = match child.stdout.take() {
Some(s) => s,
None => {
set_error(shared_for_decoder, "ffmpeg stdout not available".to_string());
set_error(&shared_for_decoder, "ffmpeg stdout not available".to_string());
let _ = child.kill();
break;
}
@@ -355,7 +356,7 @@ impl Pipeline {
if stop_for_decoder.load(Ordering::SeqCst) {
break 'outer;
}
set_status(shared_for_decoder, PlayerStatus::Buffering);
set_status(&shared_for_decoder, PlayerStatus::Buffering);
std::thread::sleep(Duration::from_millis(backoff_ms));
backoff_ms = (backoff_ms * 2).min(5000);
continue 'outer;
@@ -400,7 +401,7 @@ impl Pipeline {
// Move to Playing once we've decoded a small buffer.
if pushed_since_start >= playing_threshold_samples {
set_status(shared_for_decoder, PlayerStatus::Playing);
set_status(&shared_for_decoder, PlayerStatus::Playing);
}
}
}
@@ -413,7 +414,8 @@ impl Pipeline {
let mut cons = cons_opt.take().expect("cons must exist for WithOutput");
// Audio callback: drain ring buffer and write to output.
let shared_for_cb = shared;
let shared_for_cb = std::sync::Arc::clone(&shared);
let shared_for_cb_err = std::sync::Arc::clone(&shared_for_cb);
let stop_for_cb = Arc::clone(&stop_flag);
let volume_for_cb = Arc::clone(&volume_bits);
@@ -421,7 +423,7 @@ impl Pipeline {
let err_fn = move |err| {
let msg = format!("Audio output error: {err}");
set_error(shared_for_cb, msg);
set_error(&shared_for_cb_err, msg);
};
let built = match sample_format {
@@ -448,7 +450,7 @@ impl Pipeline {
if underrun != last_was_underrun {
last_was_underrun = underrun;
set_status(
shared_for_cb,
&shared_for_cb,
if underrun {
PlayerStatus::Buffering
} else {
@@ -485,7 +487,7 @@ impl Pipeline {
if underrun != last_was_underrun {
last_was_underrun = underrun;
set_status(
shared_for_cb,
&shared_for_cb,
if underrun {
PlayerStatus::Buffering
} else {
@@ -523,7 +525,7 @@ impl Pipeline {
if underrun != last_was_underrun {
last_was_underrun = underrun;
set_status(
shared_for_cb,
&shared_for_cb,
if underrun {
PlayerStatus::Buffering
} else {
@@ -653,14 +655,14 @@ impl Pipeline {
}
}
fn stop(mut self, shared: &'static PlayerShared) {
fn stop(mut self, shared: &std::sync::Arc<PlayerShared>) {
self.stop_flag.store(true, Ordering::SeqCst);
self.stop_cast_tap();
// dropping stream stops audio
if let Some(j) = self.decoder_join.take() {
let _ = j.join();
}
set_status(shared, PlayerStatus::Stopped);
set_status(&shared, PlayerStatus::Stopped);
}
fn set_volume(&self, volume: f32) {
@@ -668,7 +670,7 @@ impl Pipeline {
}
}
fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand>) {
fn player_thread(shared: std::sync::Arc<PlayerShared>, rx: mpsc::Receiver<PlayerCommand>) {
// Step 2: FFmpeg decode + CPAL playback.
let mut pipeline: Option<Pipeline> = None;
let mut pipeline_cast_owned = false;
@@ -676,7 +678,7 @@ fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand
match cmd {
PlayerCommand::Play { url } => {
if let Some(p) = pipeline.take() {
p.stop(shared);
p.stop(&shared);
}
pipeline_cast_owned = false;
@@ -688,7 +690,7 @@ fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand
s.status = PlayerStatus::Buffering;
}
match Pipeline::start(shared, url, PipelineMode::WithOutput) {
match Pipeline::start(std::sync::Arc::clone(&shared), url, PipelineMode::WithOutput) {
Ok(p) => {
// Apply current volume to pipeline atomics.
let vol = { shared.state.lock().unwrap().volume };
@@ -696,14 +698,14 @@ fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand
pipeline = Some(p);
}
Err(e) => {
set_error(shared, e);
set_error(&shared, e);
pipeline = None;
}
}
}
PlayerCommand::PlayCast { url } => {
if let Some(p) = pipeline.take() {
p.stop(shared);
p.stop(&shared);
}
pipeline_cast_owned = true;
@@ -715,21 +717,21 @@ fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand
s.status = PlayerStatus::Buffering;
}
match Pipeline::start(shared, url, PipelineMode::Headless) {
match Pipeline::start(std::sync::Arc::clone(&shared), url, PipelineMode::Headless) {
Ok(p) => {
let vol = { shared.state.lock().unwrap().volume };
p.set_volume(vol);
pipeline = Some(p);
}
Err(e) => {
set_error(shared, e);
set_error(&shared, e);
pipeline = None;
}
}
}
PlayerCommand::Stop => {
if let Some(p) = pipeline.take() {
p.stop(shared);
p.stop(&shared);
} else {
let mut s = shared.state.lock().unwrap();
s.status = PlayerStatus::Stopped;
@@ -762,7 +764,7 @@ fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand
}
if pipeline_cast_owned {
if let Some(p) = pipeline.take() {
p.stop(shared);
p.stop(&shared);
}
pipeline_cast_owned = false;
}
@@ -772,8 +774,8 @@ fn player_thread(shared: &'static PlayerShared, rx: mpsc::Receiver<PlayerCommand
}
if let Some(p) = pipeline.take() {
p.stop(shared);
p.stop(&shared);
} else {
set_status(shared, PlayerStatus::Stopped);
set_status(&shared, PlayerStatus::Stopped);
}
}