From a0770029fbea6dbdaf9cc5b7e2a04ad82f5ea4c7 Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 15 Feb 2026 11:15:22 +0100 Subject: feat(timeline-editor): add replay button to restart from original play position Adds replay button that restarts playback from the position where Play was originally clicked. Button is always visible but disabled when no audio is loaded. Co-Authored-By: Claude Sonnet 4.5 --- tools/timeline_editor/index.html | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/tools/timeline_editor/index.html b/tools/timeline_editor/index.html index 1fbfcbf..4d6c81e 100644 --- a/tools/timeline_editor/index.html +++ b/tools/timeline_editor/index.html @@ -143,6 +143,7 @@ @@ -200,7 +201,7 @@ showBeats: true, quantizeUnit: 1, bpm: 120, isDragging: false, dragOffset: { x: 0, y: 0 }, lastActiveSeqIndex: -1, isDraggingHandle: false, handleType: null, handleDragOffset: 0, audioBuffer: null, audioDuration: 0, audioSource: null, audioContext: null, - isPlaying: false, playbackStartTime: 0, playbackOffset: 0, animationFrameId: null, + isPlaying: false, playbackStartTime: 0, playbackOffset: 0, playStartPosition: 0, animationFrameId: null, lastExpandedSeqIndex: -1, dragMoved: false }; @@ -227,6 +228,7 @@ stats: document.getElementById('stats'), playbackControls: document.getElementById('playbackControls'), playPauseBtn: document.getElementById('playPauseBtn'), + replayBtn: document.getElementById('replayBtn'), playbackTime: document.getElementById('playbackTime'), playbackIndicator: document.getElementById('playbackIndicator'), panelToggle: document.getElementById('panelToggle'), @@ -341,6 +343,7 @@ dom.playbackControls.style.display = 'flex'; dom.playbackIndicator.style.display = 'block'; dom.clearAudioBtn.disabled = false; + dom.replayBtn.disabled = false; showMessage(`Audio loaded: ${state.audioDuration.toFixed(2)}s`, 'success'); renderTimeline(); } catch (err) { @@ -474,9 +477,11 @@ function clearAudio() { stopPlayback(); state.audioBuffer = null; state.audioDuration = 0; state.playbackOffset = 0; + state.playStartPosition = 0; dom.playbackControls.style.display = 'none'; dom.playbackIndicator.style.display = 'none'; dom.clearAudioBtn.disabled = true; + dom.replayBtn.disabled = true; const ctx = dom.waveformCanvas.getContext('2d'); ctx.clearRect(0, 0, dom.waveformCanvas.width, dom.waveformCanvas.height); renderTimeline(); @@ -881,7 +886,20 @@ dom.clearAudioBtn.addEventListener('click', () => { clearAudio(); dom.audioInput.value = ''; }); dom.playPauseBtn.addEventListener('click', async () => { if (state.isPlaying) stopPlayback(); - else { if (state.playbackOffset >= state.audioDuration) state.playbackOffset = 0; await startPlayback(); } + else { + if (state.playbackOffset >= state.audioDuration) state.playbackOffset = 0; + state.playStartPosition = state.playbackOffset; + await startPlayback(); + } + }); + + dom.replayBtn.addEventListener('click', async () => { + stopPlayback(false); + state.playbackOffset = state.playStartPosition; + const replayBeats = timeToBeats(state.playbackOffset); + dom.playbackTime.textContent = `${state.playbackOffset.toFixed(2)}s (${replayBeats.toFixed(2)}b)`; + updateIndicatorPosition(replayBeats, false); + await startPlayback(); }); dom.waveformContainer.addEventListener('click', async e => { -- cgit v1.2.3