feat: Add scrubber with A/B markers for video editing/downloading

This commit is contained in:
Claude Worker
2026-07-18 17:59:20 +00:00
parent 046aa67108
commit 70460232f0
2 changed files with 35 additions and 6 deletions

View File

@@ -740,6 +740,7 @@ function openVideoEditor(source) {
} }
const cuts = []; // [{start,end}] the user is removing const cuts = []; // [{start,end}] the user is removing
let lastMarker = null; // 'A' or 'B'
const body = document.createElement('div'); const body = document.createElement('div');
body.className = 'video-editor'; body.className = 'video-editor';
@@ -747,14 +748,17 @@ function openVideoEditor(source) {
<p class="ve-intro">Mark the parts to <strong>remove</strong>. Everything else is kept and saved as a new offline video.</p> <p class="ve-intro">Mark the parts to <strong>remove</strong>. Everything else is kept and saved as a new offline video.</p>
<div class="ve-scrubber-track"> <div class="ve-scrubber-track">
<div class="ve-scrubber-bar"></div> <div class="ve-scrubber-bar"></div>
<div class="ve-marker ve-marker-a"></div> <div class="ve-marker ve-marker-a">A</div>
<div class="ve-marker ve-marker-b"></div> <div class="ve-marker ve-marker-b">B</div>
</div> </div>
<div class="ve-add-row"> <div class="ve-add-row">
<label>From <input type="text" class="ve-from" placeholder="0:30" inputmode="numeric" /></label> <label>From <input type="text" class="ve-from" placeholder="0:30" inputmode="numeric" /></label>
<label>To <input type="text" class="ve-to" placeholder="1:15" inputmode="numeric" /></label> <label>To <input type="text" class="ve-to" placeholder="1:15" inputmode="numeric" /></label>
<button type="button" class="btn ve-add">Add cut</button> <button type="button" class="btn ve-add">Add cut</button>
</div> </div>
<div class="ve-move-row" style="margin-top:10px">
<button type="button" class="btn ve-move">Move video to last clicked marker</button>
</div>
<div class="ve-error" hidden></div> <div class="ve-error" hidden></div>
<div class="ve-cuts"></div> <div class="ve-cuts"></div>
<label class="ve-title-row">Name <input type="text" class="ve-title" maxlength="120" /></label> <label class="ve-title-row">Name <input type="text" class="ve-title" maxlength="120" /></label>
@@ -763,6 +767,7 @@ function openVideoEditor(source) {
const fromEl = body.querySelector('.ve-from'); const fromEl = body.querySelector('.ve-from');
const toEl = body.querySelector('.ve-to'); const toEl = body.querySelector('.ve-to');
const addBtn = body.querySelector('.ve-add'); const addBtn = body.querySelector('.ve-add');
const moveBtn = body.querySelector('.ve-move');
const errEl = body.querySelector('.ve-error'); const errEl = body.querySelector('.ve-error');
const cutsEl = body.querySelector('.ve-cuts'); const cutsEl = body.querySelector('.ve-cuts');
const titleEl = body.querySelector('.ve-title'); const titleEl = body.querySelector('.ve-title');
@@ -786,11 +791,28 @@ function openVideoEditor(source) {
const rect = trackEl.getBoundingClientRect(); const rect = trackEl.getBoundingClientRect();
const percent = (e.clientX - rect.left) / rect.width; const percent = (e.clientX - rect.left) / rect.width;
const time = Math.round(percent * duration); 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(); 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; fromEl.oninput = updateScrubber;
toEl.oninput = updateScrubber; toEl.oninput = updateScrubber;

View File

@@ -2278,10 +2278,17 @@ input[type="range"]::-webkit-slider-thumb:hover { transform: scale(1.25); }
.ve-marker { .ve-marker {
position: absolute; position: absolute;
top: 0; top: 0;
width: 4px; width: 20px;
height: 100%; height: 100%;
background: var(--accent); 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-a { left: 0; }
.ve-marker-b { right: 0; } .ve-marker-b { right: 0; }