From 70460232f06611c91869ae78ec6a38da1f4c04a2 Mon Sep 17 00:00:00 2001 From: Claude Worker Date: Sat, 18 Jul 2026 17:59:20 +0000 Subject: [PATCH] feat: Add scrubber with A/B markers for video editing/downloading --- frontend/app.js | 30 ++++++++++++++++++++++++++---- frontend/styles.css | 11 +++++++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/frontend/app.js b/frontend/app.js index 5b1c4e9..8dfdc3f 100755 --- a/frontend/app.js +++ b/frontend/app.js @@ -740,6 +740,7 @@ function openVideoEditor(source) { } const cuts = []; // [{start,end}] the user is removing + let lastMarker = null; // 'A' or 'B' const body = document.createElement('div'); body.className = 'video-editor'; @@ -747,14 +748,17 @@ function openVideoEditor(source) {

Mark the parts to remove. Everything else is kept and saved as a new offline video.

-
-
+
A
+
B
+
+ +
@@ -763,6 +767,7 @@ function openVideoEditor(source) { const fromEl = body.querySelector('.ve-from'); const toEl = body.querySelector('.ve-to'); const addBtn = body.querySelector('.ve-add'); + const moveBtn = body.querySelector('.ve-move'); const errEl = body.querySelector('.ve-error'); const cutsEl = body.querySelector('.ve-cuts'); const titleEl = body.querySelector('.ve-title'); @@ -786,11 +791,28 @@ function openVideoEditor(source) { const rect = trackEl.getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; const time = Math.round(percent * duration); - if (!fromEl.value) fromEl.value = VideoEdit.fmtTime(time); - else if (!toEl.value) toEl.value = VideoEdit.fmtTime(time); + + if (!fromEl.value || lastMarker === 'B') { + fromEl.value = VideoEdit.fmtTime(time); + lastMarker = 'A'; + } else { + toEl.value = VideoEdit.fmtTime(time); + lastMarker = 'B'; + } updateScrubber(); }; + markerA.onclick = (e) => { e.stopPropagation(); lastMarker = 'A'; }; + markerB.onclick = (e) => { e.stopPropagation(); lastMarker = 'B'; }; + + moveBtn.onclick = () => { + if (lastMarker === 'A' && fromEl.value) { + Player.seek(VideoEdit.parseTime(fromEl.value)); + } else if (lastMarker === 'B' && toEl.value) { + Player.seek(VideoEdit.parseTime(toEl.value)); + } + }; + fromEl.oninput = updateScrubber; toEl.oninput = updateScrubber; diff --git a/frontend/styles.css b/frontend/styles.css index 6b3c62f..e2d47f1 100755 --- a/frontend/styles.css +++ b/frontend/styles.css @@ -2278,10 +2278,17 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); } .ve-marker { position: absolute; top: 0; - width: 4px; + width: 20px; height: 100%; background: var(--accent); - pointer-events: none; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 10px; + font-weight: bold; + cursor: pointer; + pointer-events: auto; } .ve-marker-a { left: 0; } .ve-marker-b { right: 0; }