summaryrefslogtreecommitdiff
path: root/tools/timeline_editor/index.html
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-15 11:15:22 +0100
committerskal <pascal.massimino@gmail.com>2026-02-15 11:15:22 +0100
commita0770029fbea6dbdaf9cc5b7e2a04ad82f5ea4c7 (patch)
tree2cf5725f188cee73192f9d169fc7d36d81c287b6 /tools/timeline_editor/index.html
parentb54d15620032d2c03d528478f2a6742747125097 (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.html22
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 => {