Commit Graph
47 Commits
Author SHA1 Message Date
vicrodh c70db4157a refactor(v2): migrate get_pipewire_sinks to v2
Path C: legacy CPAL output-sink enumeration was inlined directly in
commands/playback.rs (cfg-gated Linux + non-Linux variants), with
no equivalent in any domain crate. Extract a domain function
`qbz_audio::output_sinks::list_output_sinks` that returns the same
{name, description, volume, is_default} shape (mirrored as the new
public `OutputSinkInfo` struct) for both Linux and non-Linux
platforms, preserving the original logging cadence and the rule
that name == CPAL device name (so saved IDs reopen correctly).

Add v2_get_pipewire_sinks in commands_v2/audio.rs that calls into
qbz_audio directly — no `crate::commands::*` delegation. Updates the
two frontend call sites (AudioOutputBadges.loadStatus and
SettingsView.loadAudioDevices). Legacy command in
commands/playback.rs is preserved untouched; the audio-protected
files (pipewire_backend.rs, audio_settings.rs, player init_device)
are not modified.

cc
2026-04-26 12:03:28 -06:00
vicrodh 1abb57bd7f refactor(v2): migrate get_audio_output_status to v2
Path B: legacy command read state.player.state.current_device() and
state.player.state.is_playing() — both methods already exist on the
qbz-player Player.state in the V2 architecture. No domain extraction
needed; the v2 command reads them via CoreBridge.player() like the
existing v2_get_hardware_audio_status does.

Adds v2_get_audio_output_status returning V2AudioOutputStatus
(serialized to the same {device_name, is_playing} shape so the
AudioOutputBadges frontend type is unchanged). Updates both
AudioOutputBadges call sites (initial loadStatus + the 1s polling
interval). The legacy command in commands/playback.rs is preserved
untouched.

cc
2026-04-26 12:00:15 -06:00
vicrodh edededcf8c chore(cast): remove AirPlay subsystem (dead code)
AirPlay was scaffolded in both crates/qbz-cast/src/airplay and
src-tauri/src/cast/airplay but never worked at runtime: the RAOP
sender required an Apple MFi-licensed stack with no free/open
alternative, and the backend state was already disabled via the
commented `.manage(airplay_state)` line in src-tauri/src/lib.rs.
The CastPicker UI already hid the AirPlay tab pending streaming
support that was never going to ship.

Delete:
- crates/qbz-cast/src/airplay/        (entire directory)
- src-tauri/src/cast/airplay/         (entire directory)
- 10x v2_airplay_* commands from commands_v2/library.rs + their
  tauri::generate_handler entries
- AirPlayError from qbz-cast errors
- 'airplay' from CastProtocol + every case arm in castStore.ts,
  CastPicker.svelte, AudioOutputBadges.svelte
- Corresponding i18n keys if present

Zero behavior change for Chromecast / DLNA / QConnect / MPRIS. If
AirPlay ever becomes feasible we'll revisit from scratch with a
real streaming backend.

cc
2026-04-16 19:04:56 -06:00
vicrodh cc21a82d6c fix(ui): refresh audio output badges when playback starts
AudioOutputBadges (DAC/HW/EXC/CAST) used to update only on mouseover
or during the 1 Hz polling loop — which itself was gated to
dac_passthrough or ALSA, so PipeWire users without passthrough never
saw the active device reflect a new stream.

The comment in the file noted that an earlier $effect watching
is_playing caused an infinite loop because loadStatus() writes
outputStatus, which re-triggered the effect. That's fixed here by
subscribing to the player store with manual edge detection: we only
call loadStatus() when is_playing transitions false→true or when
track_id changes. Neither signal is written by loadStatus(), so
there is no loop.

cc
2026-04-13 19:50:59 -06:00
vicrodh 93f6ebec28 fix: replace monospace font with app font across UI components
Mono font only kept in Settings > Dev > Logs (LogsModal),
command blocks, and keyboard shortcut keys. All other UI
elements (durations, metadata, badges) now use --font-sans.
2026-03-10 11:37:06 -06:00
vicrodh 3348108c40 perf: replace transition: all with specific properties (174 instances)
Replace transition: all across 63 files with explicit property lists
(color, background-color, border-color, opacity). Special cases:
- NowPlayingBar control-btn: includes transform for :active scale
- CoverflowPanel: transform + opacity for 3D carousel
- ImmersiveHeader: max-width + opacity for expand animation
2026-03-09 10:30:47 -06:00
vicrodh 8c626c0e6a fix: poll output device name so it updates during playback
The device name was only read on mount and on hover, so it stayed
as "No device" until the user hovered the badges area. Now the
1-second poll also reads get_audio_output_status (cheap in-memory
read) alongside hardware status, keeping the device name current.
2026-03-08 13:10:53 -06:00
vicrodh bdc24c5a2e fix: show configured device name when player has no active stream
Fall back to settings.output_device when the player hasn't reported
a device yet (before first playback). Previously showed "No device"
even though a device was configured in audio settings. Also change
the final fallback from "No device" to "System Default" since no
configured device means the system default is being used.
2026-03-08 13:04:51 -06:00
vicrodh 11b3486c63 fix(v2): close frontend/backend command audit gaps
- add/register missing v2 commands surfaced by audit
- migrate frontend invokes to v2 equivalents
- eliminate legacy invoke callsites in audit report
- harden invoke audit parser for generic/multiline invoke usage

cx
2026-02-18 12:59:28 -06:00
vicrodh e4fe4598d0 refactor(frontend): migrate playlist and settings commands to V2
Batch migrate frontend invocations to V2:
- Playlist commands (22 commands)
- Settings/preferences commands
- Cache and audio commands
- API commands (radio, genres, tags, etc.)

Progress: reduced legacy commands from ~228 to ~152
2026-02-16 02:40:50 -06:00
vicrodh 8becf36842 fix(frontend): resolve type errors in multiple components
- miniplayerService.ts: Use PhysicalSize/PhysicalPosition classes instead of plain objects
- AudioOutputBadges.svelte: Add null checks for settings
- SettingsView.svelte: Add gapless_enabled to AudioSettings interface, fix return types
- SearchView.svelte: Fix variable declaration ordering for $derived dependencies
- LocalLibraryView.svelte: Add tick import, fix loadAlbums -> loadLibraryData, wrap onclick handler

Reduces npm run check errors from 66 to 42.
2026-02-15 16:27:53 -06:00
vicrodh 87d03d427a refactor(frontend): migrate get_audio_settings to v2_get_audio_settings
Migrate all frontend get_audio_settings invoke calls to use V2:
- +page.svelte (normalization state loading)
- SettingsView.svelte (audio settings page)
- ExpandedPlayer.svelte (DAC passthrough status)
- AudioOutputBadges.svelte (output device status)
2026-02-15 04:12:37 -06:00
vicrodh 348e615d41 Fix infinite loop in AudioOutputBadges causing log spam
The $effect watching outputStatus.is_playing would call loadStatus(),
which updates outputStatus, triggering the effect again. This caused
~30 calls per second when hovering over the audio badges.

Removed the problematic $effect - mouseenter handler and 1s polling
for hardware status are sufficient.
2026-02-12 13:45:58 -06:00
vicrodh 2fe573ec84 Merge branch 'feature/ui-fixes' into pre-release
# Conflicts:
#	src/lib/components/AudioOutputBadges.svelte
#	src/lib/components/views/AlbumDetailView.svelte
#	src/lib/components/views/PlaylistDetailView.svelte
2026-02-09 10:59:04 -06:00
vicrodh a237be3a83 Differentiate DAC Passthrough tooltip from ALSA Direct bit-perfect
DAC badge tooltip now says "Active" instead of "Bit-perfect" since
PipeWire passthrough is best-effort, not guaranteed bit-perfect like
ALSA Direct hw:X,Y. Added a brief note in Settings when DAC Passthrough
is enabled clarifying the distinction. Also i18n'd the hardcoded
dacPassthroughTooltipOverride string in SettingsView.
2026-02-07 20:37:31 -06:00
vicrodh be56ecff90 i18n: replace hardcoded strings in AudioOutputBadges tooltip
Moved all hardcoded English/Spanish tooltip text to i18n keys
under audioBadges section in all 4 locale files.
Removed dead dacTooltip derived property (unused in template).
2026-02-07 18:11:49 -06:00
vicrodh 62f2e3dffc feat(ui): show ALSA device descriptions in audio badge tooltip
Load ALSA device descriptions from backend and display fancy names
in the tooltip, with fallback to heuristic names when unavailable
2026-02-01 06:03:44 -06:00
vicrodh 046e333f18 fix(theme): make DAC/EXC tooltip responsive to theme changes 2026-02-01 05:01:37 -06:00
vicrodh eaf250383a fix: move loadStatus call to mouseenter handler to prevent re-render loop 2026-01-31 23:51:12 -06:00
vicrodh ae81b1c3c4 Fix quality setting and device name display issues (#34)
- Clear both L1 memory and L2 disk caches when quality setting changes
  This ensures cached tracks at wrong quality are removed
- Fix device name not showing in popup on first hover
  Now refreshes status on each hover instead of only when null
- Add quality downgrade indicator when stream quality is lower than
  track's original quality (shows yellow arrow with tooltip)
- Store original track quality values for comparison with actual stream
2026-01-31 23:46:02 -06:00
vicrodh 7ee12ea1b2 feat(cast): show cast badge and DLNA/Cast icon in AudioOutputBadges
- Add cast-aware badge that shows protocol name (DLNA/CAST/AIR)
- Show protocol-specific SVG icons in tooltip (dlna.svg, cast_audio.svg)
- Hide DAC passthrough and exclusive mode badges when casting
- Stop hardware polling when casting to avoid misleading info
- Add purple theme styling for cast badge consistent with cast UI

cpvsc
2026-01-26 10:16:39 -06:00
vicrodh 848322681d refactor: replace hardcoded rgba white with theme-aware alpha variables
Add --alpha-* CSS variables (4-95%) to app.css that adapt to theme:
- Dark themes: white-based rgba
- Light themes: black-based rgba (auto-inverts for proper contrast)

Mass-replaced 100+ instances of rgba(255,255,255,...) across 23 files
with var(--alpha-*) for consistent theming support.

This fixes borders, overlays, and UI elements being invisible or
having poor contrast in light themes.
2026-01-24 11:35:54 -06:00
vicrodh ebbf5a6e3a Make ticker return faster like typewriter carriage
Adjusted keyframes so scroll is slow (50% of time) and return is quick (10% of time), mimicking mechanical typewriter carriage return.
2026-01-19 13:32:10 -06:00
vicrodh fa7cb51e91 Add pauses to ticker animation for smoother cycling
Animation now pauses at start and end positions before moving, eliminating jarring jumps when loop restarts.
2026-01-19 13:31:07 -06:00
vicrodh 29dfb78124 Reduce ticker animation speed to improve readability
Reduced speed from 80 to 40 pixels per second to make device name more readable and reduce stuttering.
2026-01-19 13:29:52 -06:00
vicrodh 0ee2891f44 Reduce badge tooltip width to 2/3 of original size 2026-01-19 13:28:52 -06:00
vicrodh de08d841b0 Fix ticker animation to trigger on tooltip open, not hover
Animation now starts automatically when tooltip appears if device name overflows, since tooltip has pointer-events: none and cannot be hovered.
2026-01-19 13:28:01 -06:00
vicrodh 5f3b4631e6 Add ticker animation for long device names in badge tooltip
Adds scrolling animation when device names overflow tooltip width, borrowed from AlbumCard component pattern. Also fixes accessibility warning by adding role and aria-label to badge container.
2026-01-19 13:26:56 -06:00
vicrodh ea7614f639 Fix badge issues: restore lightweight 1s polling for hardwareStatus only
This reverts the event-driven approach (9242b95) and restores lightweight
polling, but ONLY for hardwareStatus.

Problems with event-driven approach:
1. False yellow warnings: hardwareStatus had stale data from previous song
   - Hardware: 88200 Hz (correct)
   - Cached hardwareStatus: 44100 Hz (from previous song)
   - Badge logic: 88.2 !== 44.1 → WARNING (false positive)

2. Lost device name: outputStatus never updated after mount
   - loadStatus() only called once on mount
   - outputStatus.device_name became stale
   - Badge tooltip showed "Default" instead of "hw:4,0"

Solution - Lightweight polling (best of both worlds):
- Call loadStatus() once on mount (loads everything)
- Poll ONLY hardwareStatus every 1s (no device enumeration)
- get_hardware_audio_status just reads /proc/asound (very cheap)
- No CPAL device enumeration (no console spam)
- No get_pipewire_sinks calls (no ALSA errors)

Benefits:
- Badge always shows current hardware status (accurate resampling detection)
- Device name preserved (loaded once, doesn't change)
- Zero console spam (no device enumeration)
- Very lightweight (just read /proc/asound file)
- ~1000x cheaper than full loadStatus() polling

This gives us accurate bit-perfect validation without the overhead.
2026-01-19 13:11:28 -06:00
vicrodh 9242b95939 Replace 1s polling with event-driven updates on sample rate change
Changed hardware status polling from continuous (every 1s) to event-driven
(only when sample rate changes).

Rationale:
- Hardware sample rate ONLY changes when playing new song with different rate
- Continuous polling was overkill - 3600 unnecessary calls per hour
- If system is properly configured, status doesn't change mid-song
- Polling was originally added for "instant feedback" but wastes resources

Changes:
- Removed setInterval polling (lines 162-171)
- Added $effect that watches samplingRate prop changes
- Only calls get_hardware_audio_status when:
  * samplingRate changes (new song or format change)
  * Using DAC Passthrough or ALSA Direct (bit-perfect modes)
- Tracks previousSamplingRate to detect actual changes

Benefits:
- Zero backend calls during playback (unless rate changes)
- Still validates bit-perfect when it matters (new sample rate)
- Massive reduction in /proc/asound reads
- Better battery life, lower CPU usage
- Same UX - badge updates when song changes

Testing plan:
- Play songs with same sample rate → zero backend calls
- Play songs with different rates → update on each rate change
- Verify badge still shows correct status
- If issues found, can revert to 1s polling
2026-01-19 12:13:14 -06:00
vicrodh 19a571dddc Remove that was spamming backend calls on playback
The (() => { if (outputStatus?.is_playing) loadStatus(); }) was
causing massive console spam ("spam masivo") that continued even after
removing mouse from badge.

Root causes:
1.  was cherry-picked from feature/ui-fixes for badge auto-update
2. But now we have 1s polling that already keeps data fresh
3. outputStatus object was being mutated/recreated frequently
4. This triggered  repeatedly, calling loadStatus()
5. Each loadStatus() enumerated 12 CPAL devices + PipeWire sinks
6. CPAL enumeration causes ALSA errors: "Cannot get card index for 0"

Previous commit (0c3ca16) removed loadStatus() from onmouseenter, but
spam continued because  kept triggering during playback.

Solution:
- Removed  completely (lines 150-156)
- loadStatus() now only called once on mount
- Polling interval (1s) only calls get_hardware_audio_status
- get_hardware_audio_status just reads /proc/asound (no device enumeration)

Result:
- Console spam eliminated completely
- Badge still updates every 1s via polling
- Zero CPAL/ALSA enumeration during playback
- Massive reduction in CPU/battery usage
2026-01-19 12:07:30 -06:00
vicrodh 0c3ca16550 Fix badge hover spam - remove loadStatus() on mouseenter
Root cause:
- onmouseenter was calling loadStatus() which enumerates ALL devices
- loadStatus() calls: get_audio_output_devices (12 devices), get_pipewire_sinks, get_hardware_audio_status
- Each hover triggered 3 expensive backend calls + CPAL/ALSA enumeration
- User reported "spam masivo en la consola" when hovering badge

Solution:
- Removed loadStatus() call from onmouseenter (line 180)
- Only set isHovering = true to show tooltip
- Polling interval (1s) already keeps hardware status fresh
- Tooltip now shows cached data instead of triggering new enumeration

Impact:
- Hover now instant with zero backend calls
- Console spam eliminated
- Battery/CPU usage reduced significantly
- Tooltip still shows current data via 1s polling
2026-01-19 12:06:21 -06:00
vicrodh 311a2a7be1 Update badge status immediately when playback starts
Add effect that reloads audio settings and status when playback begins, ensuring DAC/EXC badges show correct state without requiring hover interaction.
2026-01-19 08:39:14 -06:00
vicrodh 8b825856d2 UX improvements for ALSA Direct settings
1. Change hardware volume to visible help text (like Offline settings)
   - More critical info should be always visible
   - Clearer explanation of experimental nature

2. Add blue HW badge for ALSA Direct mode
   - Replaces DAC badge (mutually exclusive)
   - Blue color simulates audio equipment LED
   - Shows 'Hardware Direct: Bit-perfect ALSA' in tooltip
   - Displays device name and sample rate
   - Only visible when: ALSA Direct + hw plugin

3. Gapless logic already correct
   - Only restricts gapless with DAC Passthrough (PipeWire)
   - ALSA Direct can do gapless (same sample rate)
   - Note: Different sample rates need investigation
2026-01-19 08:35:15 -06:00
vicrodh b786fe06e3 Update badge status immediately when playback starts
Add effect that reloads audio settings and status when playback begins, ensuring DAC/EXC badges show correct state without requiring hover interaction.
2026-01-19 04:14:34 -06:00
vicrodh 676d58af0f Add 1-second polling for DAC Passthrough badge updates
User requested faster badge color changes. Previously badge only updated
on mount or hover, causing 2+ second delays.

Solution: Poll hardware status every 1 second when DAC Passthrough is
enabled. This provides instant visual feedback without affecting playback:
- Only polls when dac_passthrough is true (conditional)
- Only reads hardware status (no playback impact)
- Cleanup on component unmount
- Fast enough for instant feedback, light enough for performance

Addresses user feedback: badge updates now appear instantly instead of
requiring hover or waiting multiple seconds.
2026-01-17 17:31:37 -06:00
vicrodh 13e2ea708a Fix critical device selection bug in audio settings
PROBLEM:
When user selected a specific audio device (e.g., Cambridge Audio DAC),
audio would always play through system default device (laptop speakers)
instead of the selected device.

ROOT CAUSE:
All calls to reinit_audio_device() were passing device: null instead of
the actual selected device name. When device: null is passed, the Player
falls back to system default regardless of user selection.

Affected functions:
- handleOutputDeviceChange() - Primary bug, selected device ignored
- handleExclusiveModeChange() - Reset to default when toggling
- handleDacPassthroughChange() - Reset to default when toggling
- handleGaplessPlaybackChange() - Reset to default when auto-disabling DAC
- handleCrossfadeChange() - Reset to default when auto-disabling DAC

FIX:
Created getCurrentDeviceSinkName() helper that:
- Returns the internal sink name for selected device
- Returns null only for "System Default" selection
- Used consistently across all audio setting changes

SECONDARY CHANGES:
Improved DAC Passthrough warning messages in AudioOutputBadges:
- "ERROR: PipeWire is resampling" instead of generic warning
- Clearer tooltip guidance for fixing resampling issues
- Better distinction between active/warning/disabled states

This ensures device selection persists across all audio setting changes
and provides clearer feedback when bit-perfect playback is compromised.
2026-01-16 21:57:41 -06:00
vicrodh 40cd6d7303 fix: improve tooltip UX and enforce mutual exclusion on load
Tooltip improvements:
- Consolidate duplicate tooltips into single unified tooltip
- Show device info + DAC status + sample rate in one place
- Remove native 'title' attributes that were causing conflicts
- Add visual indicators (colored dots) for setting states
- Display resampling warning with actual rates when detected
- Better structured tooltip with sections

Mutual exclusion enforcement:
- Validate settings on load in loadAudioSettings()
- Auto-disable Gapless and Crossfade if DAC Passthrough is enabled
- Prevents invalid state from persisted settings

This fixes the issue where users could have incompatible settings
active simultaneously after app restart.
2026-01-16 21:39:48 -06:00
vicrodh a95575b7df fix: remove invalid currentTrack import from AudioOutputBadges
AudioOutputBadges was importing currentTrack from playerStore, which doesn't
export a reactive store. This caused the app to crash with a 500 error.

Solution: Pass samplingRate as a prop from NowPlayingBar instead of trying
to import track data directly. This matches the existing pattern used by
other components like QualityBadge.
2026-01-16 21:26:16 -06:00
vicrodh c810663c6f feat: implement 3-state DAC badge and hardware sample rate detection
Backend changes:
- Add audio_diagnostics module to read actual hardware sample rate from /proc/asound
- New command get_hardware_audio_status() parses hw_params for real-time monitoring
- Add glob dependency for file pattern matching

Frontend (AudioOutputBadges):
- Implement 3-state DAC badge system:
  - Green: DAC passthrough working correctly (rates match)
  - Yellow: DAC passthrough ON but resampling detected (warning)
  - Gray: DAC passthrough OFF (intentionally disabled)
- Dynamic tooltips showing actual hardware sample rate
- Compare file sample rate vs hardware sample rate
- Increase tooltip z-index to 9999 to prevent occlusion issues
- Add pointer-events: none to tooltip to prevent interaction issues

Frontend (SettingsView):
- Replace hardcoded "192 kHz" with dynamic hardware sample rate
- Load and display actual hardware audio status
- Show format details (e.g., S32_LE) when available
- Display "No active playback" when no audio is playing
- Add muted styling for inactive state

Frontend (QualityBadge):
- Add native tooltip showing tier and format
- Add cursor: help to indicate interactivity

i18n:
- Add "noActivePlayback" translations (EN/ES)

This allows users to verify bit-perfect playback is working and detect
resampling issues that would compromise audio quality.
2026-01-16 21:14:22 -06:00
vicrodh 09881856d0 fix: DAC badge now activates based on setting alone
Remove isExternal requirement for DAC Passthrough badge activation.
The badge now lights up when DAC Passthrough setting is enabled,
regardless of whether the device is detected as external or internal.

This fixes the issue where the badge would remain off even when
DAC passthrough was working (confirmed by 192kHz reaching hardware).
2026-01-16 20:56:02 -06:00
vicrodh 2d908c7b12 Remove unnecessary polling from AudioOutputBadges, refresh on hover instead 2026-01-11 12:32:23 -06:00
vicrodh b6b019e659 Add PipeWire device names via pactl and fix badge margins 2026-01-11 11:37:45 -06:00
vicrodh 78e3af691f Reposition DAC/EXC badges below QualityBadge in vertical layout
Changed quality-indicator to column flex layout with badges row below
the QualityBadge. Both badges now expand to fill the width equally,
matching the QualityBadge width.
2026-01-11 11:30:05 -06:00
vicrodh dd758d3523 Fix session restore and audio badges visibility
Session restore:
- Add pendingSessionRestore state to playerStore
- When restored track is played, load from Qobuz first
- Seek to saved position after track loads

Audio badges:
- Always show DAC/EXC badges (very muted when off)
- Adjust margins and sizing for better alignment
2026-01-11 11:19:34 -06:00
vicrodh 3fd3d25bdc Add device validation before using saved audio device
- Validate device has supported output configs before using
- Fall back to default if saved device is invalid
- Reduces polling frequency to 10 seconds
2026-01-11 11:10:02 -06:00
vicrodh dd9d5558fa Add audio output badges and pretty device names
- Add AudioOutputBadges component showing DAC/Exclusive mode status
- Badges are reactive to actual player state, not just settings
- Add device tooltip on hover showing current output device
- Create audioDeviceNames helper for ALSA name to friendly name mapping
- Use pretty names in Settings dropdown while storing raw names
- Add get_audio_output_status backend command
- Store current device name in player SharedState
2026-01-11 11:00:31 -06:00