feat(settings): add limit quality to device toggle and max sample rate tracking
Frontend changes for quality limiting feature: - Add limitQualityToDevice state and handler - Add AudioSettings interface fields for limit_quality_to_device and device_max_sample_rate - Update handleBackendDeviceChange to send max_sample_rate when device changes - Update handleOutputDeviceChange to find max_sample_rate from backend devices - Add Settings UI toggle for "Limit Quality to Device" This completes the quality limiting feature that prevents requesting tracks that exceed the device's sample rate capabilities, ensuring bit-perfect playback.
This commit is contained in:
1 parent
6d53907233
commit
39e8814848
2 files changed
+36
-3
No files matched your search
@@ -43,7 +43,7 @@ fn limit_quality_for_device(quality: Quality, max_sample_rate: Option<u32>) -> Q
|
|||||||
Quality::UltraHiRes | Quality::HiRes => {
|
Quality::UltraHiRes | Quality::HiRes => {
|
||||||
log::info!(
|
log::info!(
|
||||||
"[Quality Limit] Device max {}Hz, limiting {} to Lossless (44.1kHz)",
|
"[Quality Limit] Device max {}Hz, limiting {} to Lossless (44.1kHz)",
|
||||||
max_rate, quality.display_name()
|
max_rate, quality.label()
|
||||||
);
|
);
|
||||||
Quality::Lossless
|
Quality::Lossless
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -389,6 +389,7 @@
|
|||||||
let streamFirstTrack = $state(false);
|
let streamFirstTrack = $state(false);
|
||||||
let streamBufferSeconds = $state(3);
|
let streamBufferSeconds = $state(3);
|
||||||
let streamingOnly = $state(false);
|
let streamingOnly = $state(false);
|
||||||
|
let limitQualityToDevice = $state(true); // Enabled by default for bit-perfect
|
||||||
|
|
||||||
// Backend system state
|
// Backend system state
|
||||||
let availableBackends = $state<BackendInfo[]>([]);
|
let availableBackends = $state<BackendInfo[]>([]);
|
||||||
@@ -1085,6 +1086,8 @@
|
|||||||
stream_first_track: boolean;
|
stream_first_track: boolean;
|
||||||
stream_buffer_seconds: number;
|
stream_buffer_seconds: number;
|
||||||
streaming_only: boolean;
|
streaming_only: boolean;
|
||||||
|
limit_quality_to_device: boolean;
|
||||||
|
device_max_sample_rate: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BackendInfo {
|
interface BackendInfo {
|
||||||
@@ -1236,6 +1239,7 @@
|
|||||||
streamFirstTrack = settings.stream_first_track ?? false;
|
streamFirstTrack = settings.stream_first_track ?? false;
|
||||||
streamBufferSeconds = settings.stream_buffer_seconds ?? 3;
|
streamBufferSeconds = settings.stream_buffer_seconds ?? 3;
|
||||||
streamingOnly = settings.streaming_only ?? false;
|
streamingOnly = settings.streaming_only ?? false;
|
||||||
|
limitQualityToDevice = settings.limit_quality_to_device ?? true;
|
||||||
|
|
||||||
// Validate mutual exclusion: DAC Passthrough disables Gapless + Crossfade
|
// Validate mutual exclusion: DAC Passthrough disables Gapless + Crossfade
|
||||||
if (dacPassthrough) {
|
if (dacPassthrough) {
|
||||||
@@ -1260,16 +1264,25 @@
|
|||||||
const deviceName = sinkDescriptionToName.get(description);
|
const deviceName = sinkDescriptionToName.get(description);
|
||||||
const deviceToStore = description === 'System Default' ? null : deviceName;
|
const deviceToStore = description === 'System Default' ? null : deviceName;
|
||||||
|
|
||||||
|
// Try to find max sample rate from backendDevices if available
|
||||||
|
// This enables quality limiting for PipeWire mode when possible
|
||||||
|
const matchingDevice = backendDevices.find(d =>
|
||||||
|
d.name === deviceName || d.description === description
|
||||||
|
);
|
||||||
|
const maxSampleRate = matchingDevice?.max_sample_rate ?? null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Save the preference
|
// Save the preference
|
||||||
await invoke('set_audio_output_device', { device: deviceToStore });
|
await invoke('set_audio_output_device', { device: deviceToStore });
|
||||||
|
// Store device's max sample rate for quality limiting
|
||||||
|
await invoke('set_audio_device_max_sample_rate', { rate: maxSampleRate });
|
||||||
|
|
||||||
// Reinitialize audio with the selected device
|
// Reinitialize audio with the selected device
|
||||||
// CRITICAL: Pass the actual CPAL device name, not null
|
// CRITICAL: Pass the actual CPAL device name, not null
|
||||||
// CPAL can now find this device because we're using CPAL names
|
// CPAL can now find this device because we're using CPAL names
|
||||||
await invoke('reinit_audio_device', { device: deviceToStore });
|
await invoke('reinit_audio_device', { device: deviceToStore });
|
||||||
|
|
||||||
console.log('[Audio] Output device changed:', description, '(device:', deviceName ?? 'default', ')');
|
console.log('[Audio] Output device changed:', description, '(device:', deviceName ?? 'default', ', max_rate:', maxSampleRate ?? 'unknown', ')');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Audio] Failed to change audio output device:', err);
|
console.error('[Audio] Failed to change audio output device:', err);
|
||||||
}
|
}
|
||||||
@@ -1434,17 +1447,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleLimitQualityToDeviceChange(enabled: boolean) {
|
||||||
|
limitQualityToDevice = enabled;
|
||||||
|
try {
|
||||||
|
await invoke('set_audio_limit_quality_to_device', { enabled });
|
||||||
|
console.log('[Audio] Limit quality to device changed:', enabled);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Audio] Failed to change limit quality to device:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleBackendDeviceChange(deviceName: string) {
|
async function handleBackendDeviceChange(deviceName: string) {
|
||||||
outputDevice = deviceName;
|
outputDevice = deviceName;
|
||||||
|
|
||||||
// Get device ID from backendDevices using display name mapping
|
// Get device ID from backendDevices using display name mapping
|
||||||
const device = deviceByDisplayName.get(deviceName);
|
const device = deviceByDisplayName.get(deviceName);
|
||||||
const deviceId = deviceName === 'System Default' ? null : device?.id ?? null;
|
const deviceId = deviceName === 'System Default' ? null : device?.id ?? null;
|
||||||
|
const maxSampleRate = device?.max_sample_rate ?? null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await invoke('set_audio_output_device', { device: deviceId });
|
await invoke('set_audio_output_device', { device: deviceId });
|
||||||
|
// Store device's max sample rate for quality limiting
|
||||||
|
await invoke('set_audio_device_max_sample_rate', { rate: maxSampleRate });
|
||||||
await invoke('reinit_audio_device', { device: deviceId });
|
await invoke('reinit_audio_device', { device: deviceId });
|
||||||
console.log('[Audio] Backend device changed:', deviceName, '(id:', deviceId ?? 'default', ')');
|
console.log('[Audio] Backend device changed:', deviceName, '(id:', deviceId ?? 'default', ', max_rate:', maxSampleRate ?? 'unknown', ')');
|
||||||
|
|
||||||
// Stop current playback to prevent stuck/dead streams
|
// Stop current playback to prevent stuck/dead streams
|
||||||
try {
|
try {
|
||||||
@@ -2064,6 +2090,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<Toggle enabled={streamingOnly} onchange={handleStreamingOnlyChange} />
|
<Toggle enabled={streamingOnly} onchange={handleStreamingOnlyChange} />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="setting-info">
|
||||||
|
<span class="setting-label">Limit Quality to Device</span>
|
||||||
|
<span class="setting-desc">Prevents requesting tracks that exceed your device's max sample rate. Ensures bit-perfect playback by avoiding software resampling.</span>
|
||||||
|
</div>
|
||||||
|
<Toggle enabled={limitQualityToDevice} onchange={handleLimitQualityToDeviceChange} />
|
||||||
|
</div>
|
||||||
<div class="setting-row last">
|
<div class="setting-row last">
|
||||||
<span class="setting-label">{$t('settings.audio.currentSampleRate')}</span>
|
<span class="setting-label">{$t('settings.audio.currentSampleRate')}</span>
|
||||||
<span class="setting-value" class:muted={!hardwareStatus?.is_active}>
|
<span class="setting-value" class:muted={!hardwareStatus?.is_active}>
|
||||||
|
|||||||
Reference in new issue
Block a user