Enhance audio processing capabilities: add FFmpeg for audio transcoding, update database schema, and improve UI for audio file uploads

This commit is contained in:
2026-09-26 21:51:50 +02:00
parent eb83b16b54
commit 0f7d5e950c
8 changed files with 135 additions and 20 deletions
+33 -4
View File
@@ -2,7 +2,7 @@ const DB_NAME = 'faerro-kb-local';
const STORE_NAME = 'recordings';
const MAX_RECORDING_MS = 30 * 60 * 1000;
const elements = Object.fromEntries([
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'audio-level', 'audio-level-fill', 'capture-message',
'record-button', 'record-title', 'record-help', 'record-time', 'record-meter-fill', 'audio-level', 'audio-level-fill', 'capture-message', 'audio-file-input',
'queue-list', 'queue-empty', 'queue-count', 'sync-button', 'archive-list', 'archive-empty',
'network-state', 'storage-status',
].map((id) => [id, document.getElementById(id)]));
@@ -53,7 +53,9 @@ function updateNetwork() {
const online = navigator.onLine;
elements['network-state'].classList.toggle('online', online);
elements['network-state'].innerHTML = `<i></i>${online ? 'Connected' : 'Offline'}`;
elements['storage-status'].textContent = online ? 'Pending audio stays here until server confirmation.' : 'Offline: new audio is saved on this device.';
if (elements['storage-status']) {
elements['storage-status'].textContent = online ? 'Pending audio stays here until server confirmation.' : 'Offline: new audio is saved on this device.';
}
}
function formatDuration(milliseconds) {
@@ -170,7 +172,7 @@ async function saveRecording() {
setMessage('No audio was captured. Please try again.', true);
return;
}
const extension = blob.type.includes('webm') ? 'webm' : blob.type.includes('ogg') ? 'ogg' : 'm4a';
const extension = blob.type.includes('webm') ? 'webm' : blob.type.includes('ogg') ? 'ogg' : blob.type.includes('mp4') ? 'm4a' : 'audio';
const createdAt = new Date(recordingStartedAt).toISOString();
const recording = {
id: crypto.randomUUID(),
@@ -190,6 +192,27 @@ async function saveRecording() {
}
}
async function queueAudioFile(file) {
const createdAtMs = Date.now();
const createdAt = new Date(createdAtMs).toISOString();
const recording = {
id: crypto.randomUUID(),
blob: file,
createdAt,
filename: file.name,
durationMs: 0,
createdAtMs,
};
try {
await putQueued(recording);
setMessage(navigator.onLine ? 'Audio saved on device. Uploading now…' : 'Audio saved on device. It will upload when you reconnect.');
await renderQueue();
await syncQueue();
} catch {
setMessage('Could not save this file in browser storage. Check available device storage and try again.', true);
}
}
async function syncQueue() {
if (syncing || !navigator.onLine) return;
syncing = true;
@@ -247,7 +270,7 @@ async function renderQueue() {
status.textContent = navigator.onLine ? 'WAITING TO SYNC' : 'SAVED OFFLINE';
const meta = document.createElement('div');
meta.className = 'item-meta';
meta.textContent = `${prettyDate(recording.createdAt)} · ${formatDuration(recording.durationMs)}`;
meta.textContent = `${prettyDate(recording.createdAt)}${recording.durationMs ? ` · ${formatDuration(recording.durationMs)}` : ''}`;
const remove = createDeleteButton(`Delete queued note ${recording.filename}`);
remove.disabled = syncing;
remove.addEventListener('click', async () => {
@@ -355,6 +378,12 @@ async function loadAwaitingNotes() {
}
elements['record-button'].addEventListener('click', () => mediaRecorder?.state === 'recording' ? stopRecording() : startRecording());
elements['audio-file-input'].addEventListener('change', async (event) => {
const [file] = event.target.files;
if (file) await queueAudioFile(file);
event.target.value = '';
});
elements['sync-button'].addEventListener('click', syncQueue);
elements['sync-button'].addEventListener('click', syncQueue);
window.addEventListener('online', () => { updateNetwork(); syncQueue(); loadAwaitingNotes(); });
window.addEventListener('offline', updateNetwork);