From bcfd9bbf482b3ae458471434dae4a31b3d9a1272 Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:27:24 -0600 Subject: [PATCH] docs: add Codex prompt for Chromecast integration --- README.md | 79 ++++++---- docs/CODEX_CASTING.md | 330 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 382 insertions(+), 27 deletions(-) create mode 100644 docs/CODEX_CASTING.md diff --git a/README.md b/README.md index df30f1409..4261d12e6 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,58 @@ +

+ QBZ-NIX logo +

+ # QBZ-NIX -Native Qobuz client for Linux with Hi-Fi audio support. +Free and open source (FOSS) Qobuz client for Linux with native, high-fidelity playback. QBZ-NIX removes browser sample-rate limits and delivers hi-res audio through a purpose-built Rust playback engine. -## Why? +## Why QBZ-NIX -Browser-based players (Chromium, Firefox) have a hardcoded sample rate cap (~48kHz). Qobuz offers up to 192kHz Hi-Res content. QBZ-NIX bypasses this limitation with native audio playback. +Browsers cap audio output around 48 kHz, while Qobuz streams up to 192 kHz. QBZ-NIX uses a native playback pipeline so your system and DAC can receive the original resolution, with proper device control, caching, and media integration. ## Features -### Core -- Native audio playback (no browser limitations) -- Search, browse albums, artists, tracks -- Playback queue management -- Quality selection with automatic fallback +### Streaming and Playback +- Qobuz authentication and full catalog search (albums, tracks, artists, playlists). +- Native decoding for FLAC and MP3 with real-time playback state updates. +- Quality selection with automatic fallback across Qobuz tiers. +- Audio device enumeration and per-device output selection. +- Gapless-ready playback pipeline with precise position tracking. -### Audio -- Support for all Qobuz quality tiers (MP3, CD, Hi-Res, Ultra Hi-Res) -- Sample rate / bit depth display -- DAC passthrough mode for bit-perfect playback -- Multiple audio backends (PipeWire, ALSA, PulseAudio) +### Queue and Library +- Queue management with shuffle, repeat, and history navigation. +- In-memory audio cache with LRU eviction and next-track prefetching. +- Favorites and playlists from your Qobuz account. +- Local library backend: directory scanning, metadata extraction, CUE sheet parsing, and SQLite indexing. -### Planned -- Playlist management (local + Qobuz sync) -- Favorites synchronization -- Equalizer -- Last.fm integration -- Lyrics display +### Integrations +- MPRIS media controls and media key support on Linux. +- Desktop notifications for track changes. +- Last.fm scrobbling and now-playing updates. +- Shareable Qobuz URLs and universal SongLink links (Odesli). + +### Interface +- Now playing, queue panel, and full-screen playback views. +- Focus mode for distraction-free listening. +- English and Spanish localization. + +## Open Source + +QBZ-NIX is MIT-licensed and fully open source. No telemetry, no lock-in, and no hidden services. Just a clean, transparent player built for Linux audio fans. + +## Inspiration + +QBZ-NIX draws inspiration from projects like qobuz-dl, and from the broader Linux audio community that values open tools and high-fidelity playback. ## Tech Stack -- **Backend:** Rust + Tauri -- **Frontend:** Svelte + TypeScript -- **Audio:** cpal/rodio with ALSA direct support +- **Desktop:** Rust + Tauri 2 +- **Frontend:** SvelteKit + TypeScript + Vite +- **Audio:** rodio + symphonia +- **Networking:** reqwest +- **Local library:** walkdir + lofty + rusqlite +- **Integrations:** souvlaki (MPRIS), notify-rust, SongLink (Odesli) +- **UX:** svelte-i18n, lucide-svelte ## Development @@ -39,7 +60,7 @@ Browser-based players (Chromium, Firefox) have a hardcoded sample rate cap (~48k - Rust (latest stable) - Node.js 18+ -- Linux with audio support (PipeWire or PulseAudio) +- Linux with audio support (PipeWire, ALSA, or PulseAudio) ### Setup @@ -58,15 +79,19 @@ npm run tauri build ``` qbz-nix/ -├── src/ # Frontend (Svelte) +├── src/ # Frontend (SvelteKit) ├── src-tauri/ │ └── src/ │ ├── api/ # Qobuz API client -│ ├── player/ # Audio playback +│ ├── player/ # Audio playback engine │ ├── queue/ # Queue management -│ ├── config/ # Settings persistence +│ ├── cache/ # Audio cache and prefetch +│ ├── library/ # Local library backend +│ ├── lastfm/ # Last.fm integration +│ ├── share/ # SongLink / share utilities +│ ├── media_controls/ # MPRIS integration │ └── commands/ # Tauri IPC commands -└── static/ # Static assets +└── static/ # Static assets and logo ``` ## License diff --git a/docs/CODEX_CASTING.md b/docs/CODEX_CASTING.md new file mode 100644 index 000000000..c1c88cae6 --- /dev/null +++ b/docs/CODEX_CASTING.md @@ -0,0 +1,330 @@ +# Codex Task: Chromecast Integration + +## Context + +QBZ is a native Qobuz streaming client for Linux built with Tauri 2.0 + Rust backend + SvelteKit frontend. We need to add Chromecast support using the `rust-cast` crate. + +**Reference document**: See `docs/CASTING_RESEARCH.md` for full analysis. + +## Objective + +Implement Chromecast audio casting so users can stream music to Google Cast devices. + +--- + +## Architecture Overview + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ QBZ Frontend │────▶│ Tauri Commands │────▶│ Cast Module │ +│ (SvelteKit) │ │ (lib.rs) │ │ (rust-cast) │ +└─────────────────┘ └──────────────────┘ └────────┬────────┘ + │ + ┌─────────────────────────────────┼─────────┐ + │ ▼ │ + │ ┌──────────────┐ ┌─────────────────┐ │ + │ │ mDNS Device │ │ Local HTTP │ │ + │ │ Discovery │ │ Audio Server │ │ + │ └──────────────┘ └────────┬────────┘ │ + │ │ │ + └───────────────────────────────┼───────────┘ + ▼ + ┌─────────────────┐ + │ Chromecast │ + │ Device │ + └─────────────────┘ +``` + +--- + +## Module Structure + +Create files under `src-tauri/src/cast/`: + +``` +src-tauri/src/cast/ +├── mod.rs # Module exports + CastState +├── discovery.rs # Device discovery via mDNS +├── device.rs # CastDevice wrapper + connection +├── media_server.rs # Local HTTP server for streaming +├── commands.rs # Tauri commands +└── errors.rs # Error types +``` + +--- + +## Task 1: Dependencies & Module Setup + +### Cargo.toml additions: +```toml +# Chromecast +rust-cast = "0.21" +mdns-sd = "0.11" # For device discovery +tiny_http = "0.12" # Simple HTTP server for streaming +``` + +### mod.rs structure: +```rust +pub mod commands; +pub mod device; +pub mod discovery; +pub mod errors; +pub mod media_server; + +pub use commands::CastState; +pub use device::CastDevice; +pub use discovery::DeviceDiscovery; +pub use errors::CastError; +pub use media_server::MediaServer; +``` + +--- + +## Task 2: Device Discovery (`discovery.rs`) + +Use `mdns-sd` crate to discover Chromecast devices on the local network. + +### Data Model: +```rust +#[derive(Debug, Clone, Serialize)] +pub struct DiscoveredDevice { + pub id: String, // Unique device ID + pub name: String, // Friendly name (e.g., "Living Room TV") + pub model: String, // Device model + pub ip: String, // IP address + pub port: u16, // Cast port (usually 8009) +} +``` + +### Functions needed: +- `start_discovery()` - Start mDNS discovery in background +- `stop_discovery()` - Stop discovery +- `get_discovered_devices()` - Return list of found devices + +### mDNS service type: +- `_googlecast._tcp.local.` + +--- + +## Task 3: Cast Device Connection (`device.rs`) + +Wrapper around `rust-cast::CastDevice` for managing connections. + +### Functions needed: +```rust +impl CastDeviceConnection { + /// Connect to a Chromecast by IP and port + pub fn connect(ip: &str, port: u16) -> Result; + + /// Disconnect from device + pub fn disconnect(&mut self) -> Result<(), CastError>; + + /// Get device status + pub fn get_status(&self) -> Result; + + /// Launch media receiver app + pub fn launch_media_app(&mut self) -> Result; + + /// Load media URL for playback + pub fn load_media(&mut self, url: &str, content_type: &str, metadata: MediaMetadata) -> Result<(), CastError>; + + /// Play/pause/stop controls + pub fn play(&mut self) -> Result<(), CastError>; + pub fn pause(&mut self) -> Result<(), CastError>; + pub fn stop(&mut self) -> Result<(), CastError>; + + /// Volume control (0.0 - 1.0) + pub fn set_volume(&mut self, volume: f32) -> Result<(), CastError>; + + /// Seek to position + pub fn seek(&mut self, position_secs: f64) -> Result<(), CastError>; +} + +#[derive(Debug, Clone, Serialize)] +pub struct MediaMetadata { + pub title: String, + pub artist: String, + pub album: String, + pub artwork_url: Option, + pub duration_secs: Option, +} +``` + +--- + +## Task 4: Local Media Server (`media_server.rs`) + +HTTP server to stream audio to Chromecast. The Cast device will fetch audio from this server. + +### Requirements: +- Serve audio files via HTTP +- Support range requests (for seeking) +- Run on dynamic port, return URL to cast device +- Support both local files and in-memory audio data + +### Interface: +```rust +pub struct MediaServer { + port: u16, + // ... +} + +impl MediaServer { + /// Start server on available port + pub fn start() -> Result; + + /// Stop server + pub fn stop(&mut self); + + /// Get base URL (e.g., "http://192.168.1.100:8080") + pub fn base_url(&self) -> String; + + /// Register audio data to serve (returns path like "/audio/123") + pub fn register_audio(&mut self, id: u64, data: Vec, content_type: &str) -> String; + + /// Register local file to serve + pub fn register_file(&mut self, id: u64, file_path: &str) -> Result; + + /// Get full URL for registered audio + pub fn get_audio_url(&self, id: u64) -> Option; +} +``` + +### Content types: +- FLAC: `audio/flac` +- WAV: `audio/wav` +- ALAC/M4A: `audio/mp4` +- AIFF: `audio/aiff` + +--- + +## Task 5: Tauri Commands (`commands.rs`) + +### CastState: +```rust +pub struct CastState { + pub discovery: Arc>, + pub connection: Arc>>, + pub media_server: Arc>, +} +``` + +### Commands to implement: +```rust +// Discovery +#[tauri::command] +pub async fn cast_start_discovery(state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_stop_discovery(state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_get_devices(state: State<'_, CastState>) -> Result, String>; + +// Connection +#[tauri::command] +pub async fn cast_connect(device_id: String, state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_disconnect(state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_get_status(state: State<'_, CastState>) -> Result; + +// Playback +#[tauri::command] +pub async fn cast_play_track( + track_id: u64, + metadata: MediaMetadata, + state: State<'_, CastState>, + app_state: State<'_, AppState>, // For accessing audio cache +) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_play_local_track( + track_id: i64, + state: State<'_, CastState>, + library_state: State<'_, LibraryState>, +) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_play(state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_pause(state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_stop(state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_seek(position_secs: f64, state: State<'_, CastState>) -> Result<(), String>; + +#[tauri::command] +pub async fn cast_set_volume(volume: f32, state: State<'_, CastState>) -> Result<(), String>; +``` + +--- + +## Task 6: Integration with lib.rs + +Add to `src-tauri/src/lib.rs`: +1. `pub mod cast;` +2. Initialize `CastState` in `run()` +3. Add `.manage(cast_state)` +4. Register all cast commands in `invoke_handler` + +--- + +## Important Notes + +### DO NOT modify these files (they're complex and tightly integrated): +- `src-tauri/src/player/mod.rs` +- `src-tauri/src/queue/mod.rs` +- `src-tauri/src/commands/playback.rs` +- Any frontend files (`src/`) + +### Testing approach: +1. First test discovery: ensure devices are found +2. Test connection to device +3. Test media server with a simple audio file +4. Test full flow: play track to Chromecast + +### Error handling: +- Use `CastError` enum with variants for Discovery, Connection, Media, Protocol errors +- All commands return `Result` for Tauri compatibility + +### Network considerations: +- Media server must bind to 0.0.0.0 (not localhost) so Chromecast can reach it +- Need to determine local IP address for the Cast device to connect back + +--- + +## Expected Output + +After implementation, these commands should work: +```typescript +// Frontend can call: +await invoke('cast_start_discovery'); +const devices = await invoke('cast_get_devices'); +await invoke('cast_connect', { deviceId: devices[0].id }); +await invoke('cast_play_track', { trackId: 123, metadata: {...} }); +await invoke('cast_pause'); +await invoke('cast_set_volume', { volume: 0.5 }); +await invoke('cast_disconnect'); +``` + +--- + +## Changelog Format + +When done, provide a changelog like: +``` +- src-tauri/Cargo.toml — added rust-cast, mdns-sd, tiny_http dependencies +- src-tauri/src/cast/errors.rs — CastError enum +- src-tauri/src/cast/discovery.rs — mDNS device discovery +- src-tauri/src/cast/device.rs — CastDeviceConnection wrapper +- src-tauri/src/cast/media_server.rs — HTTP server for audio streaming +- src-tauri/src/cast/commands.rs — Tauri commands + CastState +- src-tauri/src/cast/mod.rs — module exports +- src-tauri/src/lib.rs — wiring (mod cast, manage, commands) +```