feat(qconnect): gate volume on renderer volume_remote_control capability
Add volume_remote_control to QconnectRendererInfo plus the renderer_allows_remote_volume predicate (absent => allowed, only an explicit non-1 value disables). Parse it from device_info.capabilities in the ADD and UPDATE renderer paths, and short-circuit set_volume_if_remote when the active renderer disallows remote volume (returns handled no-op so the frontend does not fall back to local). Frontend surfaces volume_remote_control in the session snapshot, derives a unified volume lock + reason, guards handleVolumeChange, and shows a distinct player.volumeLockedRemote tooltip (added to all 5 locales). cc
This commit is contained in:
12 files changed
+111
-6
No files matched your search
@@ -106,6 +106,9 @@
|
||||
qconnectBusy?: boolean;
|
||||
showQconnectDevButton?: boolean;
|
||||
volumeLocked?: boolean;
|
||||
// P1-5: distinguishes the hardware (ALSA Direct) lock from the remote
|
||||
// renderer (cast device) lock so the tooltip can be specific.
|
||||
volumeLockedReason?: 'hardware' | 'remote';
|
||||
bufferProgress?: number | null;
|
||||
}
|
||||
|
||||
@@ -162,6 +165,7 @@
|
||||
qconnectBusy = false,
|
||||
showQconnectDevButton = false,
|
||||
volumeLocked = false,
|
||||
volumeLockedReason = 'hardware',
|
||||
bufferProgress = null,
|
||||
}: Props = $props();
|
||||
|
||||
@@ -823,7 +827,9 @@
|
||||
{#if volumeLocked}
|
||||
<button
|
||||
class="control-btn volume-btn"
|
||||
title={$translateStore('player.volumeLockedHw')}
|
||||
title={volumeLockedReason === 'remote'
|
||||
? $translateStore('player.volumeLockedRemote')
|
||||
: $translateStore('player.volumeLockedHw')}
|
||||
disabled
|
||||
>
|
||||
<Volume2 size={16} />
|
||||
|
||||
@@ -1540,6 +1540,7 @@
|
||||
"volumeUp": "Lauter",
|
||||
"volumeDown": "Leiser",
|
||||
"volumeLockedHw": "Lautstärke auf 100% fixiert — ALSA Direct hw: (am DAC/Hardware einstellen)",
|
||||
"volumeLockedRemote": "Lautstärkeregelung durch das Cast-Gerät deaktiviert",
|
||||
"clearQueue": "Warteschlange leeren",
|
||||
"queueEmpty": "Warteschlange ist leer",
|
||||
"queueEmptyDescription": "Suche nach Musik oder durchsuchen Sie Ihre Bibliothek, um Titel hinzuzufügen.",
|
||||
|
||||
@@ -1542,6 +1542,7 @@
|
||||
"volumeUp": "Increase volume",
|
||||
"volumeDown": "Decrease volume",
|
||||
"volumeLockedHw": "Volume locked at 100% — ALSA Direct hw: (adjust at DAC/hardware level)",
|
||||
"volumeLockedRemote": "Volume control disabled by the cast device",
|
||||
"clearQueue": "Clear Queue",
|
||||
"queueEmpty": "Queue is empty",
|
||||
"queueEmptyDescription": "Search for music or browse your library to add tracks.",
|
||||
|
||||
@@ -1540,6 +1540,7 @@
|
||||
"volumeUp": "Subir volumen",
|
||||
"volumeDown": "Bajar volumen",
|
||||
"volumeLockedHw": "Volumen fijo al 100% — ALSA Direct hw: (ajusta desde el DAC/hardware)",
|
||||
"volumeLockedRemote": "El control de volumen está deshabilitado por el dispositivo",
|
||||
"clearQueue": "Limpiar Cola",
|
||||
"queueEmpty": "La cola está vacía",
|
||||
"queueEmptyDescription": "Busca música o explora tu biblioteca para agregar pistas.",
|
||||
|
||||
@@ -1540,6 +1540,7 @@
|
||||
"volumeUp": "Augmenter le volume",
|
||||
"volumeDown": "Diminuer le volume",
|
||||
"volumeLockedHw": "Volume verrouillé à 100% — ALSA Direct hw: (réglez au niveau DAC/matériel)",
|
||||
"volumeLockedRemote": "Contrôle du volume désactivé par l'appareil de diffusion",
|
||||
"clearQueue": "Vider la file d'attente",
|
||||
"queueEmpty": "La file d'attente est vide",
|
||||
"queueEmptyDescription": "Recherchez de la musique ou parcourez votre bibliothèque pour ajouter des titres.",
|
||||
|
||||
@@ -1540,6 +1540,7 @@
|
||||
"volumeUp": "Aumentar volume",
|
||||
"volumeDown": "Diminuir volume",
|
||||
"volumeLockedHw": "Volume fixo em 100% — ALSA Direct hw: (ajuste no DAC/hardware)",
|
||||
"volumeLockedRemote": "Controle de volume desativado pelo dispositivo de transmissão",
|
||||
"clearQueue": "Limpar Fila",
|
||||
"queueEmpty": "A fila está vazia",
|
||||
"queueEmptyDescription": "Pesquise músicas ou navegue pela sua biblioteca para adicionar faixas.",
|
||||
|
||||
@@ -32,6 +32,9 @@ export type QconnectRendererInfo = {
|
||||
brand?: string | null;
|
||||
model?: string | null;
|
||||
device_type?: number | null;
|
||||
// P1-5: capabilities.volume_remote_control. 1 == ALLOWED. null/undefined ==
|
||||
// not advertised (treated as allowed). Any other value disables remote volume.
|
||||
volume_remote_control?: number | null;
|
||||
};
|
||||
|
||||
export type QconnectSessionSnapshot = {
|
||||
|
||||
+27
-3
@@ -1125,6 +1125,26 @@
|
||||
const qconnectPeerRendererActive = $derived(
|
||||
isQconnectPeerRendererActive(qobuzConnectSessionSnapshot)
|
||||
);
|
||||
// P1-5: respect the active renderer's volume_remote_control capability.
|
||||
// Absent (null/undefined) => allowed; only an explicit non-1 value disables.
|
||||
const qconnectRemoteVolumeAllowed = $derived.by(() => {
|
||||
if (!qconnectPeerRendererActive) return true;
|
||||
const snap = qobuzConnectSessionSnapshot;
|
||||
const activeId = snap?.active_renderer_id ?? null;
|
||||
const info = snap?.renderers?.find((r) => r.renderer_id === activeId) ?? null;
|
||||
const vrc = info?.volume_remote_control;
|
||||
return vrc == null || vrc === 1;
|
||||
});
|
||||
// P1-5: unified volume-lock state shared by the now-playing bar + immersive
|
||||
// player. Hardware lock applies only locally; remote lock applies when a peer
|
||||
// renderer is active and disallows remote volume.
|
||||
const qconnectVolumeLocked = $derived(
|
||||
(isAlsaDirectHw && !qconnectPeerRendererActive) ||
|
||||
(qconnectPeerRendererActive && !qconnectRemoteVolumeAllowed)
|
||||
);
|
||||
const qconnectVolumeLockedReason: 'hardware' | 'remote' = $derived(
|
||||
qconnectPeerRendererActive && !qconnectRemoteVolumeAllowed ? 'remote' : 'hardware'
|
||||
);
|
||||
const qconnectSuppressLocalPlaybackAutomation = $derived(
|
||||
shouldQconnectSuppressLocalPlaybackAutomation(
|
||||
isQobuzConnectConnected,
|
||||
@@ -2887,6 +2907,8 @@
|
||||
async function handleVolumeChange(newVolume: number) {
|
||||
// ALSA Direct hw: locks volume at 100% — unless controlling a remote renderer
|
||||
if (isAlsaDirectHw && !qconnectPeerRendererActive) return;
|
||||
// P1-5: the active remote renderer disallows remote volume control.
|
||||
if (qconnectPeerRendererActive && !qconnectRemoteVolumeAllowed) return;
|
||||
|
||||
try {
|
||||
const handledRemotely = await invoke<boolean>('v2_qconnect_set_volume_if_remote', { volume: newVolume });
|
||||
@@ -7141,7 +7163,8 @@
|
||||
onToggleQconnectConnection={handleQobuzConnectButton}
|
||||
qconnectBusy={qobuzConnectBusy}
|
||||
{showQconnectDevButton}
|
||||
volumeLocked={isAlsaDirectHw && !qconnectPeerRendererActive}
|
||||
volumeLocked={qconnectVolumeLocked}
|
||||
volumeLockedReason={qconnectVolumeLockedReason}
|
||||
{bufferProgress}
|
||||
/>
|
||||
{:else}
|
||||
@@ -7164,7 +7187,8 @@
|
||||
onToggleQconnectConnection={handleQobuzConnectButton}
|
||||
qconnectBusy={qobuzConnectBusy}
|
||||
{showQconnectDevButton}
|
||||
volumeLocked={isAlsaDirectHw && !qconnectPeerRendererActive}
|
||||
volumeLocked={qconnectVolumeLocked}
|
||||
volumeLockedReason={qconnectVolumeLockedReason}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -7198,7 +7222,7 @@
|
||||
{volume}
|
||||
onVolumeChange={handleVolumeChange}
|
||||
onToggleMute={handleToggleMute}
|
||||
volumeLocked={isAlsaDirectHw && !qconnectPeerRendererActive}
|
||||
volumeLocked={qconnectVolumeLocked}
|
||||
{isShuffle}
|
||||
onToggleShuffle={toggleShuffle}
|
||||
{repeatMode}
|
||||
|
||||
Reference in new issue
Block a user