Fix ALSA device filtering and quality badge hardware sample rate
Problem 1: ALSA backend showed only 1 device or listed plugins/virtual devices that don't work for playback (null, lavrate, samplerate, etc.). Solution: Filter aplay -L output to only include actual playback devices: - Hardware devices (hw:, plughw:, sysdefault:CARD=, front:CARD=) - System defaults (default, pipewire, pulse) - Digital outputs (hdmi:, iec958:, surround) Problem 2: Quality badge showed file sample rate (e.g., 44.1kHz) instead of actual hardware sample rate (e.g., 192kHz) when DAC Passthrough was active. Solution: ExpandedPlayer now polls hardware_sample_rate every second when DAC Passthrough is enabled and displays the real hardware rate instead of the file metadata rate. Changes: - alsa_backend.rs: Add is_valid_device() filter for playback devices - ExpandedPlayer.svelte: Poll get_hardware_audio_status every 1s - ExpandedPlayer.svelte: Use displaySamplingRate (hardware rate when DAC on) This ensures the quality badge accurately reflects bit-perfect playback status.
This commit is contained in:
1 parent
c0a5736c0f
commit
01022f2eea
2 files changed
+70
-7
No files matched your search
@@ -44,11 +44,11 @@ impl AlsaBackend {
|
||||
Ok(Self { host })
|
||||
}
|
||||
|
||||
/// Enumerate ALSA devices using aplay -L for complete device list
|
||||
/// Enumerate ALSA devices using aplay -L, filtering only playback devices
|
||||
fn enumerate_alsa_devices(&self) -> BackendResult<Vec<AudioDevice>> {
|
||||
let mut devices = Vec::new();
|
||||
|
||||
// Use aplay -L to get all ALSA device names (including virtual devices)
|
||||
// Use aplay -L to get all ALSA device names
|
||||
let output = Command::new("aplay")
|
||||
.arg("-L")
|
||||
.output()
|
||||
@@ -62,12 +62,27 @@ impl AlsaBackend {
|
||||
let mut current_device: Option<String> = None;
|
||||
let mut current_description: Option<String> = None;
|
||||
|
||||
// Helper to check if device is valid for playback
|
||||
let is_valid_device = |id: &str| -> bool {
|
||||
// Only include actual hardware devices and useful virtual devices
|
||||
id == "default"
|
||||
|| id == "pipewire"
|
||||
|| id == "pulse"
|
||||
|| id.starts_with("hw:")
|
||||
|| id.starts_with("plughw:")
|
||||
|| id.starts_with("sysdefault:CARD=")
|
||||
|| id.starts_with("default:CARD=")
|
||||
|| id.starts_with("front:CARD=")
|
||||
|| id.starts_with("surround")
|
||||
|| id.starts_with("hdmi:")
|
||||
|| id.starts_with("iec958:")
|
||||
};
|
||||
|
||||
for line in stdout.lines() {
|
||||
if line.is_empty() {
|
||||
// Empty line marks end of device entry
|
||||
if let (Some(id), Some(desc)) = (current_device.take(), current_description.take()) {
|
||||
// Skip null device
|
||||
if id != "null" {
|
||||
if is_valid_device(&id) {
|
||||
let is_default = id == "default";
|
||||
devices.push(AudioDevice {
|
||||
id: id.clone(),
|
||||
@@ -90,7 +105,7 @@ impl AlsaBackend {
|
||||
|
||||
// Don't forget last device
|
||||
if let (Some(id), Some(desc)) = (current_device, current_description) {
|
||||
if id != "null" {
|
||||
if is_valid_device(&id) {
|
||||
let is_default = id == "default";
|
||||
devices.push(AudioDevice {
|
||||
id: id.clone(),
|
||||
@@ -102,7 +117,7 @@ impl AlsaBackend {
|
||||
}
|
||||
}
|
||||
|
||||
log::info!("[ALSA Backend] Enumerated {} devices via aplay -L", devices.len());
|
||||
log::info!("[ALSA Backend] Enumerated {} devices via aplay -L (filtered)", devices.len());
|
||||
for (idx, dev) in devices.iter().enumerate() {
|
||||
log::info!(" [{}] {} - {}", idx, dev.name, dev.description.as_deref().unwrap_or(""));
|
||||
}
|
||||
|
||||
@@ -1,9 +1,20 @@
|
||||
<script lang="ts">
|
||||
import { invoke } from '@tauri-apps/api/core';
|
||||
import { X, Shuffle, SkipBack, Play, Pause, SkipForward, Repeat, Heart, List, Maximize2, MoreHorizontal, Cast } from 'lucide-svelte';
|
||||
import QualityBadge from './QualityBadge.svelte';
|
||||
import LyricsLines from './lyrics/LyricsLines.svelte';
|
||||
import { startActiveLineUpdates } from '$lib/stores/lyricsStore';
|
||||
|
||||
interface HardwareAudioStatus {
|
||||
hardware_sample_rate: number | null;
|
||||
hardware_format: string | null;
|
||||
is_active: boolean;
|
||||
}
|
||||
|
||||
interface AudioSettings {
|
||||
dac_passthrough: boolean;
|
||||
}
|
||||
|
||||
interface LyricsLine {
|
||||
text: string;
|
||||
}
|
||||
@@ -89,10 +100,47 @@
|
||||
let volumeRef: HTMLDivElement | null = $state(null);
|
||||
let isDraggingProgress = $state(false);
|
||||
let isDraggingVolume = $state(false);
|
||||
let hardwareSampleRate = $state<number | null>(null);
|
||||
let dacPassthrough = $state(false);
|
||||
|
||||
const progress = $derived((currentTime / duration) * 100 || 0);
|
||||
const hasLyrics = $derived(lyricsLines.length > 0);
|
||||
|
||||
// Use hardware sample rate when DAC passthrough is active, otherwise use track sample rate
|
||||
const displaySamplingRate = $derived(
|
||||
dacPassthrough && hardwareSampleRate
|
||||
? hardwareSampleRate / 1000 // Convert Hz to kHz
|
||||
: samplingRate
|
||||
);
|
||||
|
||||
// Poll hardware sample rate when DAC passthrough is active
|
||||
$effect(() => {
|
||||
if (!isOpen) return;
|
||||
|
||||
async function loadStatus() {
|
||||
try {
|
||||
const [settings, hwStatus] = await Promise.all([
|
||||
invoke<AudioSettings>('get_audio_settings'),
|
||||
invoke<HardwareAudioStatus>('get_hardware_audio_status').catch(() => null)
|
||||
]);
|
||||
dacPassthrough = settings.dac_passthrough;
|
||||
hardwareSampleRate = hwStatus?.hardware_sample_rate ?? null;
|
||||
} catch (err) {
|
||||
console.error('Failed to load audio status:', err);
|
||||
}
|
||||
}
|
||||
|
||||
loadStatus();
|
||||
|
||||
const pollInterval = setInterval(() => {
|
||||
if (dacPassthrough) {
|
||||
loadStatus();
|
||||
}
|
||||
}, 1000); // Poll every second when DAC passthrough is active
|
||||
|
||||
return () => clearInterval(pollInterval);
|
||||
});
|
||||
|
||||
// Ensure lyrics updates run when ExpandedPlayer is open with synced lyrics
|
||||
$effect(() => {
|
||||
if (isOpen && isPlaying && lyricsSynced) {
|
||||
@@ -200,7 +248,7 @@
|
||||
<h2 class="artist">{artist}</h2>
|
||||
<h3 class="album">{album}</h3>
|
||||
<div class="quality-info">
|
||||
<QualityBadge {quality} {bitDepth} {samplingRate} />
|
||||
<QualityBadge {quality} {bitDepth} samplingRate={displaySamplingRate} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in new issue
Block a user