From 246fb4e7da97057ef4557fafca4230bb87d7251b Mon Sep 17 00:00:00 2001 From: Gabe Duarte Date: Mon, 19 Jan 2026 05:44:50 +0100 Subject: [PATCH] Fix audio playback in History tab on Linux (#617) * Add Portuguese translations for permission error messages Add error message translations for the permission check flow in the Portuguese locale, covering both check failures and request failures. * Fix history audio playback on Linux WebKitGTK cannot play audio files via the asset:// protocol (or at least not reliably), returning MEDIA_ERR_SRC_NOT_SUPPORTED. Additionally, convertFileSrc was double- encoding the path (/ became %2F). Changed to read audio files directly using the fs plugin and create blob URLs instead. Also added APPDATA to fs:scope permissions to allow reading recordings from the app data directory. * Fix audio playback on macOS by using asset protocol on non-Linux The blob URL approach for audio playback works on Linux but breaks macOS. This reverts the blob change to use convertFileSrc with the asset protocol on macOS/Windows while keeping the blob approach for Linux where it's needed. Also adds proper cleanup of blob URLs to prevent memory leaks when history entries are unmounted. --- src-tauri/capabilities/default.json | 6 +++- .../settings/history/HistorySettings.tsx | 35 +++++++++++++++++-- src/i18n/locales/pt/translation.json | 6 +++- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 84593ed..84974d5 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -15,6 +15,10 @@ "global-shortcut:allow-unregister-all", "macos-permissions:default", "fs:read-files", - "fs:allow-resource-read-recursive" + "fs:allow-resource-read-recursive", + { + "identifier": "fs:scope", + "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**/*" }] + } ] } diff --git a/src/components/settings/history/HistorySettings.tsx b/src/components/settings/history/HistorySettings.tsx index 50686dc..4de7170 100644 --- a/src/components/settings/history/HistorySettings.tsx +++ b/src/components/settings/history/HistorySettings.tsx @@ -5,9 +5,13 @@ import { Button } from "../../ui/Button"; import { Copy, Star, Check, Trash2, FolderOpen } from "lucide-react"; import { convertFileSrc } from "@tauri-apps/api/core"; import { listen } from "@tauri-apps/api/event"; +import { platform } from "@tauri-apps/plugin-os"; +import { readFile } from "@tauri-apps/plugin-fs"; import { commands, type HistoryEntry } from "@/bindings"; import { formatDateTime } from "@/utils/dateFormat"; +const IS_LINUX = platform() === "linux"; + interface OpenRecordingsButtonProps { onClick: () => void; label: string; @@ -93,7 +97,14 @@ export const HistorySettings: React.FC = () => { try { const result = await commands.getAudioFilePath(fileName); if (result.status === "ok") { - return convertFileSrc(`${result.data}`, "asset"); + if (IS_LINUX) { + const fileData = await readFile(result.data); + const blob = new Blob([fileData], { type: "audio/wav" }); + + return URL.createObjectURL(blob); + } + + return convertFileSrc(result.data, "asset"); } return null; } catch (error) { @@ -222,12 +233,30 @@ const HistoryEntryComponent: React.FC = ({ const [showCopied, setShowCopied] = useState(false); useEffect(() => { + let cancelled = false; + let urlToRevoke: string | null = null; + const loadAudio = async () => { const url = await getAudioUrl(entry.file_name); - setAudioUrl(url); + + if (!cancelled) { + urlToRevoke = url; + setAudioUrl(url); + } else if (url?.startsWith("blob:")) { + URL.revokeObjectURL(url); + } }; + loadAudio(); - }, [entry.file_name, getAudioUrl]); + + return () => { + cancelled = true; + + if (urlToRevoke?.startsWith("blob:")) { + URL.revokeObjectURL(urlToRevoke); + } + }; + }, [entry.file_name]); const handleCopyText = () => { onCopyText(); diff --git a/src/i18n/locales/pt/translation.json b/src/i18n/locales/pt/translation.json index 47397e8..ced57d1 100644 --- a/src/i18n/locales/pt/translation.json +++ b/src/i18n/locales/pt/translation.json @@ -71,7 +71,11 @@ "grant": "Conceder Permissão", "granted": "Concedido", "waiting": "Aguardando...", - "allGranted": "Tudo pronto!" + "allGranted": "Tudo pronto!", + "errors": { + "checkFailed": "Erro ao verificar permissões. Por favor, tente novamente.", + "requestFailed": "Erro ao solicitar permissão. Por favor, tente novamente." + } } }, "modelSelector": {