fix: show friendly error when no audio input device is available (#1115)

This commit is contained in:
Marcelo Terreiro Prado 2026-03-22 20:53:55 -03:00 committed by GitHub
parent 05f6bdfb50
commit f6e71862ff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 83 additions and 5 deletions

View file

@ -1,7 +1,7 @@
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
use crate::apple_intelligence;
use crate::audio_feedback::{play_feedback_sound, play_feedback_sound_blocking, SoundType};
use crate::audio_toolkit::is_microphone_access_denied;
use crate::audio_toolkit::{is_microphone_access_denied, is_no_input_device_error};
use crate::managers::audio::AudioRecordingManager;
use crate::managers::history::HistoryManager;
use crate::managers::transcription::TranscriptionManager;
@ -436,6 +436,8 @@ impl ShortcutAction for TranscribeAction {
if let Some(err) = recording_error {
let error_type = if is_microphone_access_denied(&err) {
"microphone_permission_denied"
} else if is_no_input_device_error(&err) {
"no_input_device"
} else {
"unknown"
};

View file

@ -6,7 +6,7 @@ mod utils;
mod visualizer;
pub use device::{list_input_devices, list_output_devices, CpalDeviceInfo};
pub use recorder::{is_microphone_access_denied, AudioRecorder};
pub use recorder::{is_microphone_access_denied, is_no_input_device_error, AudioRecorder};
pub use resampler::FrameResampler;
pub use utils::{read_wav_samples, save_wav_file, verify_wav_file};
pub use visualizer::AudioVisualiser;

View file

@ -342,9 +342,16 @@ pub fn is_microphone_access_denied(error_message: &str) -> bool {
|| normalized.contains("0x80070005")
}
pub fn is_no_input_device_error(error_message: &str) -> bool {
let normalized = error_message.to_lowercase();
normalized.contains("no input device found")
|| (normalized.contains("failed to fetch preferred config")
&& normalized.contains("coreaudio"))
}
#[cfg(test)]
mod tests {
use super::is_microphone_access_denied;
use super::{is_microphone_access_denied, is_no_input_device_error};
#[test]
fn detects_access_is_denied() {
@ -365,6 +372,24 @@ mod tests {
fn does_not_match_unrelated_errors() {
assert!(!is_microphone_access_denied("device not found"));
}
#[test]
fn detects_no_input_device() {
assert!(is_no_input_device_error("No input device found"));
}
#[test]
fn detects_coreaudio_config_error() {
assert!(is_no_input_device_error(
"Failed to fetch preferred config: A backend-specific error has occurred: An unknown error unknown to the coreaudio-rs API occurred"
));
}
#[test]
fn does_not_match_other_errors_for_no_device() {
assert!(!is_no_input_device_error("permission denied"));
assert!(!is_no_input_device_error("device not found"));
}
}
fn run_consumer(

View file

@ -5,8 +5,9 @@ pub mod utils;
pub mod vad;
pub use audio::{
is_microphone_access_denied, list_input_devices, list_output_devices, read_wav_samples,
save_wav_file, verify_wav_file, AudioRecorder, CpalDeviceInfo,
is_microphone_access_denied, is_no_input_device_error, list_input_devices,
list_output_devices, read_wav_samples, save_wav_file, verify_wav_file, AudioRecorder,
CpalDeviceInfo,
};
pub use text::{apply_custom_words, filter_transcription_output};
pub use utils::get_cpal_host;

View file

@ -297,6 +297,18 @@ impl AudioRecordingManager {
let settings = get_settings(&self.app_handle);
let selected_device = self.get_effective_microphone_device(&settings);
// Pre-flight check: if no device was selected/configured AND no devices
// exist at all, fail early with a clear error instead of letting cpal
// produce a cryptic backend-specific message.
if selected_device.is_none() {
let has_any_device = list_input_devices()
.map(|devices| !devices.is_empty())
.unwrap_or(false);
if !has_any_device {
return Err(anyhow::anyhow!("No input device found"));
}
}
if let Some(rec) = recorder_opt.as_mut() {
rec.open(selected_device)
.map_err(|e| anyhow::anyhow!("Failed to open recorder: {}", e))?;

View file

@ -107,6 +107,10 @@ function App() {
defaultValue: t("errors.micPermissionDenied.generic"),
});
toast.error(t("errors.micPermissionDeniedTitle"), { description });
} else if (error_type === "no_input_device") {
toast.error(t("errors.noInputDeviceTitle"), {
description: t("errors.noInputDevice"),
});
} else {
toast.error(
t("errors.recordingFailed", { error: detail ?? "Unknown error" }),

View file

@ -581,6 +581,8 @@
"macos": "امنح الوصول إلى الميكروفون في إعدادات النظام ← الخصوصية والأمان ← الميكروفون.",
"linux": "امنح الوصول إلى الميكروفون في إعدادات الصوت أو الخصوصية في نظامك."
},
"noInputDeviceTitle": "لم يتم العثور على ميكروفون",
"noInputDevice": "لم يتم اكتشاف أي جهاز إدخال صوتي. يرجى توصيل ميكروفون أو سماعة رأس والمحاولة مرة أخرى.",
"recordingFailed": "فشل في بدء التسجيل: {{error}}",
"modelLoadFailed": "فشل في تحميل النموذج: {{model}}",
"modelLoadFailedUnknown": "نموذج غير معروف"

View file

@ -581,6 +581,8 @@
"macos": "Povolte přístup k mikrofonu v Nastavení systému → Soukromí a zabezpečení → Mikrofon.",
"linux": "Povolte přístup k mikrofonu v nastavení zvuku nebo soukromí vašeho systému."
},
"noInputDeviceTitle": "Mikrofon nebyl nalezen",
"noInputDevice": "Nebylo detekováno žádné zvukové vstupní zařízení. Připojte mikrofon nebo sluchátka a zkuste to znovu.",
"recordingFailed": "Nepodařilo se spustit nahrávání: {{error}}",
"modelLoadFailed": "Nepodařilo se načíst model: {{model}}",
"modelLoadFailedUnknown": "neznámý model"

View file

@ -581,6 +581,8 @@
"macos": "Erteilen Sie den Mikrofonzugriff in Systemeinstellungen → Datenschutz & Sicherheit → Mikrofon.",
"linux": "Erteilen Sie den Mikrofonzugriff in den Sound- oder Datenschutzeinstellungen Ihres Systems."
},
"noInputDeviceTitle": "Kein Mikrofon gefunden",
"noInputDevice": "Es wurde kein Audio-Eingabegerät erkannt. Bitte schließen Sie ein Mikrofon oder Headset an und versuchen Sie es erneut.",
"recordingFailed": "Aufnahme konnte nicht gestartet werden: {{error}}",
"modelLoadFailed": "Modell konnte nicht geladen werden: {{model}}",
"modelLoadFailedUnknown": "unbekanntes Modell"

View file

@ -581,6 +581,8 @@
"macos": "Grant microphone access in System Settings → Privacy & Security → Microphone.",
"linux": "Grant microphone access in your system's sound or privacy settings."
},
"noInputDeviceTitle": "No Microphone Found",
"noInputDevice": "No audio input device was detected. Please connect a microphone or headset and try again.",
"recordingFailed": "Failed to start recording: {{error}}",
"modelLoadFailed": "Failed to load model: {{model}}",
"modelLoadFailedUnknown": "unknown model"

View file

@ -581,6 +581,8 @@
"macos": "Conceda el acceso al micrófono en Ajustes del Sistema → Privacidad y seguridad → Micrófono.",
"linux": "Conceda el acceso al micrófono en la configuración de sonido o privacidad de su sistema."
},
"noInputDeviceTitle": "No se encontró micrófono",
"noInputDevice": "No se detectó ningún dispositivo de entrada de audio. Conecta un micrófono o auriculares e inténtalo de nuevo.",
"recordingFailed": "Error al iniciar la grabación: {{error}}",
"modelLoadFailed": "Error al cargar el modelo: {{model}}",
"modelLoadFailedUnknown": "modelo desconocido"

View file

@ -581,6 +581,8 @@
"macos": "Accordez l'accès au microphone dans Réglages du système → Confidentialité et sécurité → Microphone.",
"linux": "Accordez l'accès au microphone dans les paramètres de son ou de confidentialité de votre système."
},
"noInputDeviceTitle": "Aucun microphone trouvé",
"noInputDevice": "Aucun périphérique d'entrée audio n'a été détecté. Veuillez connecter un microphone ou un casque et réessayer.",
"recordingFailed": "Échec du démarrage de l'enregistrement : {{error}}",
"modelLoadFailed": "Échec du chargement du modèle : {{model}}",
"modelLoadFailedUnknown": "modèle inconnu"

View file

@ -581,6 +581,8 @@
"macos": "Concedi l'accesso al microfono in Impostazioni di Sistema → Privacy e sicurezza → Microfono.",
"linux": "Concedi l'accesso al microfono nelle impostazioni audio o sulla privacy del tuo sistema."
},
"noInputDeviceTitle": "Nessun microfono trovato",
"noInputDevice": "Non è stato rilevato alcun dispositivo di ingresso audio. Collega un microfono o delle cuffie e riprova.",
"recordingFailed": "Impossibile avviare la registrazione: {{error}}",
"modelLoadFailed": "Impossibile caricare il modello: {{model}}",
"modelLoadFailedUnknown": "modello sconosciuto"

View file

@ -581,6 +581,8 @@
"macos": "システム設定 → プライバシーとセキュリティ → マイクでマイクへのアクセスを許可してください。",
"linux": "システムのサウンドまたはプライバシー設定でマイクへのアクセスを許可してください。"
},
"noInputDeviceTitle": "マイクが見つかりません",
"noInputDevice": "オーディオ入力デバイスが検出されませんでした。マイクまたはヘッドセットを接続してから再試行してください。",
"recordingFailed": "録音の開始に失敗しました: {{error}}",
"modelLoadFailed": "モデルの読み込みに失敗しました: {{model}}",
"modelLoadFailedUnknown": "不明なモデル"

View file

@ -581,6 +581,8 @@
"macos": "시스템 설정 → 개인 정보 보호 및 보안 → 마이크에서 마이크 접근을 허용하세요.",
"linux": "시스템의 소리 또는 개인 정보 설정에서 마이크 접근을 허용하세요."
},
"noInputDeviceTitle": "마이크를 찾을 수 없음",
"noInputDevice": "오디오 입력 장치가 감지되지 않았습니다. 마이크 또는 헤드셋을 연결한 후 다시 시도해 주세요.",
"recordingFailed": "녹음을 시작하지 못했습니다: {{error}}",
"modelLoadFailed": "모델을 불러오지 못했습니다: {{model}}",
"modelLoadFailedUnknown": "알 수 없는 모델"

View file

@ -581,6 +581,8 @@
"macos": "Przyznaj dostęp do mikrofonu w Ustawienia systemowe → Prywatność i ochrona → Mikrofon.",
"linux": "Przyznaj dostęp do mikrofonu w ustawieniach dźwięku lub prywatności systemu."
},
"noInputDeviceTitle": "Nie znaleziono mikrofonu",
"noInputDevice": "Nie wykryto żadnego urządzenia wejściowego audio. Podłącz mikrofon lub słuchawki i spróbuj ponownie.",
"recordingFailed": "Nie udało się rozpocząć nagrywania: {{error}}",
"modelLoadFailed": "Nie udało się załadować modelu: {{model}}",
"modelLoadFailedUnknown": "nieznany model"

View file

@ -581,6 +581,8 @@
"macos": "Conceda o acesso ao microfone em Ajustes do Sistema → Privacidade e Segurança → Microfone.",
"linux": "Conceda o acesso ao microfone nas configurações de som ou privacidade do seu sistema."
},
"noInputDeviceTitle": "Nenhum microfone encontrado",
"noInputDevice": "Nenhum dispositivo de entrada de áudio foi detectado. Conecte um microfone ou fone de ouvido e tente novamente.",
"recordingFailed": "Falha ao iniciar a gravação: {{error}}",
"modelLoadFailed": "Falha ao carregar o modelo: {{model}}",
"modelLoadFailedUnknown": "modelo desconhecido"

View file

@ -581,6 +581,8 @@
"macos": "Предоставьте доступ к микрофону в Системные настройки → Конфиденциальность и безопасность → Микрофон.",
"linux": "Предоставьте доступ к микрофону в настройках звука или конфиденциальности вашей системы."
},
"noInputDeviceTitle": "Микрофон не найден",
"noInputDevice": "Аудиоустройство ввода не обнаружено. Подключите микрофон или гарнитуру и попробуйте снова.",
"recordingFailed": "Не удалось начать запись: {{error}}",
"modelLoadFailed": "Не удалось загрузить модель: {{model}}",
"modelLoadFailedUnknown": "неизвестная модель"

View file

@ -581,6 +581,8 @@
"macos": "Sistem Ayarları → Gizlilik ve Güvenlik → Mikrofon bölümünden mikrofon erişimini verin.",
"linux": "Sisteminizin ses veya gizlilik ayarlarından mikrofon erişimini verin."
},
"noInputDeviceTitle": "Mikrofon bulunamadı",
"noInputDevice": "Ses giriş aygıtı algılanamadı. Lütfen bir mikrofon veya kulaklık bağlayın ve tekrar deneyin.",
"recordingFailed": "Kayıt başlatılamadı: {{error}}",
"modelLoadFailed": "Model yüklenemedi: {{model}}",
"modelLoadFailedUnknown": "bilinmeyen model"

View file

@ -581,6 +581,8 @@
"macos": "Надайте доступ до мікрофона в Системні налаштування → Конфіденційність і безпека → Мікрофон.",
"linux": "Надайте доступ до мікрофона в налаштуваннях звуку або конфіденційності вашої системи."
},
"noInputDeviceTitle": "Мікрофон не знайдено",
"noInputDevice": "Не виявлено жодного пристрою введення звуку. Підключіть мікрофон або гарнітуру та спробуйте знову.",
"recordingFailed": "Не вдалося розпочати запис: {{error}}",
"modelLoadFailed": "Не вдалося завантажити модель: {{model}}",
"modelLoadFailedUnknown": "невідома модель"

View file

@ -581,6 +581,8 @@
"macos": "Cấp quyền truy cập micrô trong Cài đặt hệ thống → Quyền riêng tư & Bảo mật → Micrô.",
"linux": "Cấp quyền truy cập micrô trong cài đặt âm thanh hoặc quyền riêng tư của hệ thống."
},
"noInputDeviceTitle": "Không tìm thấy micrô",
"noInputDevice": "Không phát hiện thiết bị đầu vào âm thanh nào. Vui lòng kết nối micrô hoặc tai nghe và thử lại.",
"recordingFailed": "Không thể bắt đầu ghi âm: {{error}}",
"modelLoadFailed": "Không thể tải mô hình: {{model}}",
"modelLoadFailedUnknown": "mô hình không xác định"

View file

@ -581,6 +581,8 @@
"macos": "在系統設定 → 隱私與安全性 → 麥克風中授予麥克風存取權限。",
"linux": "在系統的聲音或隱私設定中授予麥克風存取權限。"
},
"noInputDeviceTitle": "找不到麥克風",
"noInputDevice": "未偵測到音訊輸入裝置。請連接麥克風或耳機後重試。",
"recordingFailed": "錄音啟動失敗: {{error}}",
"modelLoadFailed": "載入模型失敗: {{model}}",
"modelLoadFailedUnknown": "未知模型"

View file

@ -581,6 +581,8 @@
"macos": "在系统设置 → 隐私与安全性 → 麦克风中授予麦克风访问权限。",
"linux": "在系统的声音或隐私设置中授予麦克风访问权限。"
},
"noInputDeviceTitle": "未找到麦克风",
"noInputDevice": "未检测到音频输入设备。请连接麦克风或耳机后重试。",
"recordingFailed": "录音启动失败: {{error}}",
"modelLoadFailed": "加载模型失败: {{model}}",
"modelLoadFailedUnknown": "未知模型"