Files
qobuz-qt/rust/src/player/output.rs
joren 6f11b364aa feat: show top tracks on artist profile with play/shuffle
- Adds extra=topTracks to artist/page API request
- Embeds a List::Tracks widget at the top of ArtistView showing
  the artist's most popular tracks, with Play and Shuffle buttons
- Bubbles playTrackRequested through MainContent up to MainWindow
- Also adds the viz PCM ring buffer FFI infrastructure (for future
  spectrum widget) to the Rust backend

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 13:45:19 +01:00

97 lines
2.7 KiB
Rust

use anyhow::{anyhow, Result};
use cpal::{
traits::{DeviceTrait, HostTrait, StreamTrait},
StreamConfig,
};
use rb::{RbConsumer, RbProducer, SpscRb, RB};
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use symphonia::core::audio::AudioBufferRef;
const RING_BUFFER_SIZE: usize = 32 * 1024;
pub struct AudioOutput {
ring_buf_producer: rb::Producer<f32>,
viz_producer: Option<rb::Producer<f32>>,
_stream: cpal::Stream,
pub sample_rate: u32,
pub channels: usize,
}
impl AudioOutput {
pub fn try_open(sample_rate: u32, channels: usize) -> Result<Self> {
let host = cpal::default_host();
let device = host
.default_output_device()
.ok_or_else(|| anyhow!("no output device"))?;
let config = StreamConfig {
channels: channels as u16,
sample_rate: cpal::SampleRate(sample_rate),
buffer_size: cpal::BufferSize::Default,
};
let ring = SpscRb::new(RING_BUFFER_SIZE);
let (producer, consumer) = (ring.producer(), ring.consumer());
let stream = device.build_output_stream(
&config,
move |data: &mut [f32], _| {
let n = consumer.read(data).unwrap_or(0);
for s in &mut data[n..] {
*s = 0.0;
}
},
|e| eprintln!("audio stream error: {e}"),
None,
)?;
stream.play()?;
Ok(Self {
ring_buf_producer: producer,
viz_producer: None,
_stream: stream,
sample_rate,
channels,
})
}
pub fn set_viz_producer(&mut self, producer: rb::Producer<f32>) {
self.viz_producer = Some(producer);
}
pub fn write(
&mut self,
decoded: AudioBufferRef<'_>,
volume: f32,
stop: &Arc<AtomicBool>,
) -> Result<()> {
let mut sample_buf = symphonia::core::audio::SampleBuffer::<f32>::new(
decoded.capacity() as u64,
*decoded.spec(),
);
sample_buf.copy_interleaved_ref(decoded);
let samples: Vec<f32> = sample_buf.samples().iter().map(|s| s * volume).collect();
// Best-effort copy for visualizer (non-blocking, ok to drop samples)
if let Some(ref mut viz) = self.viz_producer {
let _ = viz.write(&samples);
}
let mut remaining = &samples[..];
while !remaining.is_empty() {
if stop.load(Ordering::SeqCst) {
return Ok(());
}
match self.ring_buf_producer.write_blocking(remaining) {
Some(n) => remaining = &remaining[n..],
None => break,
}
}
Ok(())
}
}