Initial implementation of qobuz-qt
- Rust backend (qobuz-backend static lib): Qobuz API client (reqwest/tokio), Symphonia audio decoder, CPAL audio output, extern "C" FFI bridge - Qt 6 frontend mirroring spotify-qt layout: toolbar with playback controls, left library dock, central track list, right search panel - Auth: email/password login with MD5-signed requests; session token persisted via QSettings - Playback: double-click a track → Rust fetches stream URL → Symphonia decodes → CPAL outputs to default audio device - Dark Fusion palette matching spotify-qt feel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
81
rust/src/player/output.rs
Normal file
81
rust/src/player/output.rs
Normal file
@@ -0,0 +1,81 @@
|
||||
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>,
|
||||
_stream: cpal::Stream,
|
||||
}
|
||||
|
||||
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,
|
||||
_stream: stream,
|
||||
})
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user