diff --git a/src/control.html b/src/control.html index 986964a..10e7cf8 100644 --- a/src/control.html +++ b/src/control.html @@ -80,7 +80,7 @@
    -
    Clic sul nome = avvia/riavvia · ▶/⏸ pausa · ⠿ riordina · ⌨ tasto rapido · 🎬 scena (effetto/testo/immagine, ognuno col proprio tempo) · ✨ Intermezzo = voce solo-effetti (niente audio) tra un brano e l'altro
    +
    Clic sul nome = avvia/riavvia · ▶/⏸ pausa · ⠿ riordina · ⌨ tasto rapido · 🎬 scena · ✨ Intermezzo = solo-effetti tra i brani · sulla forma d'onda: clic/trascina = sposta la riproduzione, maniglie 🟢🔴 = inizio/fine
    diff --git a/src/control.js b/src/control.js index 96565b9..05b2c99 100644 --- a/src/control.js +++ b/src/control.js @@ -276,31 +276,56 @@ function wireTrim(li, canvas, tr, i) { li.querySelector('.trim-here-e').addEventListener('click', () => { if (i === currentIndex) { tr.end = Math.max(0.5, playCur); commitTrim(tr, i, true); } }); li.querySelector('.trim-reset').addEventListener('click', () => { tr.start = 0; tr.end = 0; commitTrim(tr, i, true); }); - let dragH = null; + // Pointer on the waveform: grab a trim handle if near one, otherwise SEEK — + // scrub the playhead on the current track, or start a stopped track from there. + const HANDLE_PX = 10; + let mode = null; const dur = () => durations[tr.path] || (i === currentIndex ? playDur : 0) || 0; - const timeAt = (e) => { + const xToTime = (e) => { const rect = canvas.getBoundingClientRect(); - const f = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); - return f * dur(); + return Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)) * dur(); + }; + const scrub = (t) => { + if (i !== currentIndex) return; // preview seek only for the playing track + playCur = t; + send({ type: 'seek', time: t }); + drawWave(canvas, tr, true); }; canvas.addEventListener('pointerdown', (e) => { - if (!dur()) return; - const t = timeAt(e), s = tr.start || 0, en = tr.end > 0 ? tr.end : dur(); - dragH = Math.abs(t - s) <= Math.abs(t - en) ? 'start' : 'end'; + const d = dur(); + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const sx = d ? (tr.start || 0) / d * rect.width : 0; + const ex = d ? (tr.end > 0 ? tr.end : d) / d * rect.width : rect.width; + if (d && Math.abs(x - sx) <= HANDLE_PX) mode = 'start'; + else if (d && Math.abs(x - ex) <= HANDLE_PX) mode = 'end'; + else mode = 'seek'; canvas.setPointerCapture(e.pointerId); e.preventDefault(); + if (mode === 'seek') scrub(xToTime(e)); }); canvas.addEventListener('pointermove', (e) => { - if (!dragH) return; - const t = timeAt(e); - if (dragH === 'start') tr.start = Math.max(0, Math.min(t, (tr.end > 0 ? tr.end : dur()) - 0.5)); - else tr.end = Math.min(dur(), Math.max(t, (tr.start || 0) + 0.5)); + if (!mode) return; + const t = xToTime(e); + if (mode === 'start') { tr.start = Math.max(0, Math.min(t, (tr.end > 0 ? tr.end : dur()) - 0.5)); } + else if (mode === 'end') { tr.end = Math.min(dur(), Math.max(t, (tr.start || 0) + 0.5)); } + else { scrub(t); return; } drawWave(canvas, tr, i === currentIndex); sIn.value = fmtTime(Math.floor(tr.start || 0)); eIn.value = tr.end > 0 ? fmtTime(Math.floor(tr.end)) : ''; }); - const endDrag = () => { if (dragH) { dragH = null; commitTrim(tr, i, true); } }; + const endDrag = (e) => { + if (!mode) return; + if (mode === 'seek') { + const t = xToTime(e); + if (i === currentIndex) scrub(t); + else playIndex(i, t); // start a stopped track from the clicked point + } else { + commitTrim(tr, i, true); + } + mode = null; + }; canvas.addEventListener('pointerup', endDrag); - canvas.addEventListener('pointercancel', endDrag); + canvas.addEventListener('pointercancel', () => { mode = null; }); } let padCrossfadeMs = 0; // crossfade duration for pad triggers let capturingFor = -1; // index of the track awaiting a hotkey, or -1 @@ -477,7 +502,7 @@ function addInterlude() { savePlaylistState(); } -function playIndex(i) { +function playIndex(i, fromTime) { if (i < 0 || i >= playlist.length) return; clearGap(); const tr = playlist[i]; @@ -492,7 +517,9 @@ function playIndex(i) { playInterlude(tr); return; } - send({ type: tr.isVideo ? 'playVideoTrack' : 'playTrack', path: tr.path, start: tr.start || 0, end: tr.end || 0 }); + // fromTime (set by clicking the waveform) overrides the trim start for this run. + const startAt = (fromTime != null) ? Math.max(0, fromTime) : (tr.start || 0); + send({ type: tr.isVideo ? 'playVideoTrack' : 'playTrack', path: tr.path, start: startAt, end: tr.end || 0 }); startCues(tr); renderPlaylist(); } @@ -783,7 +810,7 @@ function renderPlaylist() { '⏱ ' + fmtTime(Math.floor(total)) + '' + '⧗ -' + fmtTime(Math.ceil(remaining)) + '' + '' + - '' + + '' + '
    ' + 'Inizio' + '' + diff --git a/src/output.js b/src/output.js index de74aa7..9e3bae6 100644 --- a/src/output.js +++ b/src/output.js @@ -359,6 +359,14 @@ djv.onControl(async (m) => { } catch (e) { djv.report({ type: 'error', message: e.message }); } break; case 'setTrim': audio.setTrim(m.start || 0, m.end || 0); break; + case 'seek': { + const el = audio.mediaEl; + if (el && isFinite(el.duration) && el.duration > 0) { + el.currentTime = Math.max(0, Math.min(el.duration - 0.05, m.time || 0)); + audio._trimFired = false; // re-arm the trim-end check after seeking + } + break; + } case 'probeDurations': probeDurations(m.paths || []); break; case 'togglePlay': { const playing = audio.togglePlay();