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
-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)
+```