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>
This commit is contained in:
joren
2026-03-25 13:45:19 +01:00
parent 4ba6d00748
commit 6f11b364aa
13 changed files with 151 additions and 9 deletions

View File

@@ -1,14 +1,18 @@
mod decoder;
pub mod output;
use rb::{SpscRb, RB};
use std::sync::{
atomic::{AtomicBool, AtomicU64, AtomicU8, Ordering},
atomic::{AtomicBool, AtomicU32, AtomicU64, AtomicU8, Ordering},
Arc,
};
use std::time::Duration;
use crate::api::TrackDto;
/// Size of the visualizer ring buffer in f32 samples (~180ms at 44.1kHz stereo).
const VIZ_RING_SIZE: usize = 16 * 1024;
#[derive(Debug, Clone)]
pub enum PlayerCommand {
Play(TrackInfo),
@@ -50,10 +54,17 @@ pub struct PlayerStatus {
pub replaygain_gain: Arc<std::sync::Mutex<f32>>,
/// When false the audio output is torn down after each track, producing a gap.
pub gapless: Arc<AtomicBool>,
/// Visualizer ring buffer (consumer side, read by FFI).
pub viz_ring: Arc<SpscRb<f32>>,
pub viz_consumer: Arc<std::sync::Mutex<rb::Consumer<f32>>>,
pub viz_sample_rate: Arc<AtomicU32>,
pub viz_channels: Arc<AtomicU32>,
}
impl PlayerStatus {
pub fn new() -> Self {
let viz_ring = Arc::new(SpscRb::new(VIZ_RING_SIZE));
let viz_consumer = Arc::new(std::sync::Mutex::new(viz_ring.consumer()));
Self {
state: Arc::new(std::sync::Mutex::new(PlayerState::Idle)),
position_secs: Arc::new(AtomicU64::new(0)),
@@ -65,6 +76,10 @@ impl PlayerStatus {
seek_target_secs: Arc::new(AtomicU64::new(0)),
replaygain_gain: Arc::new(std::sync::Mutex::new(1.0)),
gapless: Arc::new(AtomicBool::new(false)),
viz_ring,
viz_consumer,
viz_sample_rate: Arc::new(AtomicU32::new(0)),
viz_channels: Arc::new(AtomicU32::new(0)),
}
}