diff --git a/recorder.py b/recorder.py index 7e6d6c1..5a4b394 100644 --- a/recorder.py +++ b/recorder.py @@ -726,6 +726,18 @@ td { audio { width: 280px; max-width: 100%; + height: 48px; +} + +/* Die Positionsleiste der nativen Audio-Steuerung besser bedienbar machen. + Chromium/WebKit erlaubt zusätzlich eine direkte Vergrößerung der Timeline. */ +audio::-webkit-media-controls-timeline { + height: 8px; +} + +audio::-webkit-media-controls-current-time-display, +audio::-webkit-media-controls-time-remaining-display { + font-size: 13px; } a.button { @@ -1010,6 +1022,98 @@ function applyFilters() { ); applyFilters(); +// ------------------------------------------------------------ +// Wiedergabeposition merken +// ------------------------------------------------------------ +// Die Position wird pro Datei und Browser lokal gespeichert. +// Dadurch kann eine Aufnahme später an der zuletzt gehörten Stelle +// fortgesetzt werden, ohne dass dafür ein Server-Login oder eine +// Datenbank notwendig ist. +const PLAYBACK_KEY_PREFIX = 'event-audio-playback:'; +const PLAYBACK_SAVE_INTERVAL = 5000; + +function playbackKey(audio) { + const source = audio.querySelector('source'); + return source + ? PLAYBACK_KEY_PREFIX + source.getAttribute('src') + : null; +} + +function savePlaybackPosition(audio) { + const key = playbackKey(audio); + if (!key || !Number.isFinite(audio.currentTime) || audio.currentTime <= 0) { + return; + } + + try { + localStorage.setItem(key, JSON.stringify({ + position: audio.currentTime, + savedAt: Date.now() + })); + } catch (error) { + console.warn('Wiedergabeposition konnte nicht gespeichert werden:', error); + } +} + +function restorePlaybackPosition(audio) { + const key = playbackKey(audio); + if (!key) return; + + try { + const saved = JSON.parse(localStorage.getItem(key) || 'null'); + if (!saved || !Number.isFinite(saved.position) || saved.position <= 0) { + return; + } + + // Erst setzen, wenn die Metadaten/Dauer bekannt sind. + if (Number.isFinite(audio.duration) && audio.duration > 0) { + const position = Math.min(saved.position, Math.max(0, audio.duration - 0.5)); + if (position > 0) { + audio.currentTime = position; + } + } + } catch (error) { + console.warn('Wiedergabeposition konnte nicht wiederhergestellt werden:', error); + } +} + +document.querySelectorAll('.finished-row audio').forEach(audio => { + let lastSaved = 0; + + audio.addEventListener('loadedmetadata', () => { + restorePlaybackPosition(audio); + }); + + audio.addEventListener('timeupdate', () => { + const now = Date.now(); + if (now - lastSaved >= PLAYBACK_SAVE_INTERVAL) { + savePlaybackPosition(audio); + lastSaved = now; + } + }); + + audio.addEventListener('pause', () => { + if (!audio.ended) { + savePlaybackPosition(audio); + lastSaved = Date.now(); + } + }); + + audio.addEventListener('ended', () => { + const key = playbackKey(audio); + if (!key) return; + try { + localStorage.removeItem(key); + } catch (error) { + console.warn('Gespeicherte Wiedergabeposition konnte nicht gelöscht werden:', error); + } + }); +}); + +window.addEventListener('beforeunload', () => { + document.querySelectorAll('.finished-row audio').forEach(savePlaybackPosition); +}); + async function updateLiveSection() { try { const response = await fetch("/api/live", { cache: "no-store" });