diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-15 11:15:22 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-15 11:15:22 +0100 |
| commit | a0770029fbea6dbdaf9cc5b7e2a04ad82f5ea4c7 (patch) | |
| tree | 2cf5725f188cee73192f9d169fc7d36d81c287b6 /tools/timeline_editor/index.html | |
| parent | b54d15620032d2c03d528478f2a6742747125097 (diff) | |
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 <noreply@anthropic.com>
Diffstat (limited to 'tools/timeline_editor/index.html')
| -rw-r--r-- | tools/timeline_editor/index.html | 22 |
1 files 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 @@ <div id="playbackControls" style="display: none; margin-left: 20px; gap: 10px; align-items: center;"> <span id="playbackTime">0.00s (0.00b)</span> <button id="playPauseBtn">▶ Play</button> + <button id="replayBtn" disabled>↻ Replay</button> </div> </div> @@ -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 => { |
