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.
This commit is contained in:
Gabe Duarte 2026-01-19 05:44:50 +01:00 committed by GitHub
parent 28d1d814b7
commit 246fb4e7da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 42 additions and 5 deletions

View file

@ -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/**/*" }]
}
]
}

View file

@ -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<HistoryEntryProps> = ({
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();

View file

@ -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": {