feat: Add scrubber with A/B markers for video editing/downloading
This commit is contained in:
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
Reference in New Issue
Block a user