feat(qbz): cap local playback requests at the detected output-device limit

Wires limit_quality_to_device to a real consumer on the LOCAL output path
(#638 fix 3, spec Phase C). New crate::device_cap module caches the DAC's
detected rate ceiling mapped to a Qobuz tier (spec C.3: >96k -> Hi-Res+ =
no effective cap; 96/88.2k -> Hi-Res; <=48k -> CD 16/44.1, bit depth
included since no 48 kHz tier exists). Detection is the proven read-only
query_dac_capabilities probe; it runs in spawn_blocking on EXPLICIT
triggers only (startup, the Settings toggle, output-device change, backend
change, reset-to-defaults) — never on the playback hot path or poll tick.
A stale cap after a hot-unplug until the next device change is the
accepted trade, same class as the HiFi wizard.

playback::local_playback_quality() returns (tier, QualityLimit): the
preference clamped by the cap, with the request-time cause for the badge
tooltip (tie reports LocalDeviceCap — the more specific constraint,
mirroring the spec's cast tie rule). All LOCAL request sites switch to it:
play_track_resolved, prefetch (fetch + throttle estimate), the gapless
pre-fetch, and the now-playing badge seed (which can now name the device
as the downgrade cause, msgid x8 locales).

PRECEDENCE (owner decision): the device actually playing governs.
playback_quality() stays the PURE preference and the cast path keeps
calling it — cast_service is untouched and the play_audible cast branch
returns before any local resolve, so the local DAC cap is structurally
unreachable from a cast. The two settings stay independent; nothing is
auto-synced. No downsampling anywhere: the cap only changes which tier is
REQUESTED; cached/offline bytes still play as-is.

Settings plumbing: SettingsState gains device-cap-summary/-detected
(property additions to an existing global — no app.slint export change);
build_snapshot fills them from the cache; refresh_device_cap re-probes and
re-pushes on the explicit triggers.

Protected-crate note: no protected file is touched — qbz-audio is only
READ through the dac_capabilities probe; qbz-player, playback_engine.rs,
alsa_direct.rs, alsa_backend.rs and the backends are untouched; no
sample-rate handling changes.

cc
This commit is contained in:
vicrodh committed 2026-07-20 15:06:31 -06:00
1 parent 2e55edea89
commit 534a2a0d5e
13 files changed
+330 -16

No files matched your search

@@ -4702,3 +4702,6 @@ msgstr "Kopieren"
msgid "Copied!"
msgstr "Kopiert!"
msgid "Limited by your output device's limit"
msgstr "Begrenzt durch das Limit Ihres Ausgabegeräts"
@@ -507,3 +507,6 @@ msgstr "offline copy — cap not applied"
msgid "cached copy — cap not applied"
msgstr "cached copy — cap not applied"
msgid "Limited by your output device's limit"
msgstr "Limited by your output device's limit"
@@ -4732,3 +4732,6 @@ msgstr "Copiar"
msgid "Copied!"
msgstr "¡Copiado!"
msgid "Limited by your output device's limit"
msgstr "Limitado por el límite de tu dispositivo de salida"
@@ -4702,3 +4702,6 @@ msgstr "Copier"
msgid "Copied!"
msgstr "Copié !"
msgid "Limited by your output device's limit"
msgstr "Limité par le plafond de votre périphérique de sortie"
@@ -4690,3 +4690,6 @@ msgstr "コピー"
msgid "Copied!"
msgstr "コピーしました!"
msgid "Limited by your output device's limit"
msgstr "出力デバイスの上限により制限されています"
@@ -4714,3 +4714,6 @@ msgstr "Kopiëren"
msgid "Copied!"
msgstr "Gekopieerd!"
msgid "Limited by your output device's limit"
msgstr "Beperkt door de limiet van je uitvoerapparaat"
@@ -4702,3 +4702,6 @@ msgstr "Copiar"
msgid "Copied!"
msgstr "Copiado!"
msgid "Limited by your output device's limit"
msgstr "Limitado pelo limite do seu dispositivo de saída"
@@ -4716,3 +4716,6 @@ msgstr "Копировать"
msgid "Copied!"
msgstr "Скопировано!"
msgid "Limited by your output device's limit"
msgstr "Ограничено пределом вашего устройства вывода"
+7
View File
@@ -2992,6 +2992,13 @@ export global SettingsState {
// Audio — toggles.
in-out property <bool> limit-quality-to-device: false;
// Detected local device limit (#638 fix 3), shown while the toggle above
// is on. `device-cap-summary` is the Rust-composed value line (e.g.
// "192 kHz · Hi-Res+"; empty = nothing detected yet → row hidden);
// `device-cap-detected` false = the probe fell back to an assumed common
// rate set, which surfaces the plain informative caveat (never an alert).
in-out property <string> device-cap-summary: "";
in-out property <bool> device-cap-detected: true;
in-out property <bool> alsa-hardware-volume: false;
// DSD delivery mode (DSD plan Phases 2-3): Convert to PCM / DoP /
// Native DSD. Only meaningful on the ALSA direct backend.