updated player

main
oscar 2 months ago
parent 0372886d88
commit 168fc40b40

@ -66,9 +66,9 @@
width: 80%;
max-width: 1000px;
background: #000;
border: 2px solid #444;
border-radius: 8px;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 0 20px rgba(0, 0, 0, .6);
}
.player video {
@ -79,35 +79,49 @@
.controls {
display: flex;
align-items: center;
gap: 10px;
background: #1b1b1b;
gap: 12px;
background: #181818;
padding: 10px;
border-top: 1px solid #333;
}
.controls button,
.controls select,
.controls input[type="range"] {
background: #222;
.controls button {
background: none;
border: none;
color: #eee;
border: 1px solid #444;
border-radius: 4px;
padding: 6px 8px;
font-size: 1.2rem;
cursor: pointer;
padding: 6px;
transition: color .2s, transform .2s;
}
.controls button:hover {
background: #2a2a2a;
color: #0af;
transform: scale(1.2);
}
.time {
font-variant-numeric: tabular-nums;
min-width: 90px;
text-align: right;
font-size: .9rem;
color: #aaa;
}
.range {
flex: 1;
appearance: none;
height: 4px;
background: #444;
border-radius: 2px;
cursor: pointer;
}
.range::-webkit-slider-thumb {
appearance: none;
width: 12px;
height: 12px;
border-radius: 50%;
background: #0af;
border: none;
}
.moments {
@ -132,7 +146,8 @@
border: 1px solid #0d3b63;
border-radius: 999px;
cursor: pointer;
font-size: 0.95rem;
font-size: 0.9rem;
transition: background .2s;
}
.moment-pill:hover {
@ -212,28 +227,18 @@
</div>
<div class="player">
<video id="player" muted poster="{{ video.thumbnail }}">
<video id="player" poster="/{{ video.thumbnail }}">
<source src="/video/stream/{{ video.video_id }}" type="video/mp4">
Your browser does not support the video tag.
</video>
<div class="controls">
<button id="playPause">▶️</button>
<span class="time" id="currentTime">00:00</span>
<input id="seek" class="range" type="range" min="0" max="0" value="0" step="1">
<input id="seek" class="range" type="range" min="0" max="0" value="0" step="0.1">
<span class="time" id="duration">00:00</span>
<input id="volume" type="range" min="0" max="1" step="0.01" value="1" title="Volume">
<select id="speed" title="Speed">
<option value="0.5">0.5×</option>
<option value="0.75">0.75×</option>
<option value="1" selected>1×</option>
<option value="1.25">1.25×</option>
<option value="1.5">1.5×</option>
<option value="2">2×</option>
</select>
<button id="addMoment" title="Add moment"> Add Moment</button>
<button id="muteBtn">🔊</button>
<button id="addMoment" title="Add moment"></button>
</div>
</div>
@ -242,6 +247,11 @@
<div id="momentsList"></div>
</div>
<div class="moments">
<h3>Moments</h3>
<div id="momentsList"></div>
</div>
<div class="meta">
<button onclick="openFolder('{{ video.filepath }}')">Open Folder</button>
<button onclick="deleteFile('{{ video.filepath }}','{{ video.video_id }}')" style="margin-left:8px">Delete
@ -271,15 +281,13 @@
const video = document.getElementById('player');
const playPause = document.getElementById('playPause');
const seek = document.getElementById('seek');
const vol = document.getElementById('volume');
const speed = document.getElementById('speed');
const cur = document.getElementById('currentTime');
const dur = document.getElementById('duration');
const muteBtn = document.getElementById('muteBtn');
const addBtn = document.getElementById('addMoment');
const list = document.getElementById('momentsList');
const videoId = "{{ video.video_id }}";
// format seconds → hh:mm:ss
function fmt(t) {
t = Math.max(0, Math.floor(t || 0));
const h = Math.floor(t / 3600);
@ -289,93 +297,78 @@
String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0');
}
// load existing moments
// --- playback controls ---
playPause.addEventListener('click', () => {
if (video.paused) { video.play(); }
else { video.pause(); }
});
video.addEventListener('play', () => playPause.textContent = '⏸');
video.addEventListener('pause', () => playPause.textContent = '▶️');
video.addEventListener('loadedmetadata', () => {
seek.max = video.duration;
dur.textContent = fmt(video.duration);
});
video.addEventListener('timeupdate', () => {
cur.textContent = fmt(video.currentTime);
seek.value = video.currentTime;
});
seek.addEventListener('input', () => video.currentTime = seek.value);
muteBtn.addEventListener('click', () => {
video.muted = !video.muted;
muteBtn.textContent = video.muted ? '🔇' : '🔊';
});
// --- keyboard shortcuts ---
document.addEventListener('keydown', e => {
if (e.target.tagName === "INPUT") return; // dont steal focus
switch (e.code) {
case "Space": e.preventDefault(); video.paused ? video.play() : video.pause(); break;
case "ArrowRight": video.currentTime = Math.min(video.duration, video.currentTime + 5); break;
case "ArrowLeft": video.currentTime = Math.max(0, video.currentTime - 5); break;
case "ArrowUp": video.volume = Math.min(1, video.volume + 0.1); break;
case "ArrowDown": video.volume = Math.max(0, video.volume - 0.1); break;
case "KeyM": video.muted = !video.muted; muteBtn.textContent = video.muted ? '🔇' : '🔊'; break;
}
});
// --- moments ---
async function loadMoments() {
list.innerHTML = '';
try {
const res = await fetch(`/api/moments/${encodeURIComponent(videoId)}`);
const data = await res.json();
if (!data.ok) throw new Error(data.error || 'Failed');
for (const m of data.moments) {
if (data.moments.length === 0) { list.textContent = 'No moments yet.'; return; }
data.moments.forEach(m => {
const pill = document.createElement('button');
pill.className = 'moment-pill';
pill.textContent = fmt(m.timestamp);
pill.dataset.ts = m.timestamp;
pill.addEventListener('click', () => {
video.currentTime = Number(pill.dataset.ts);
video.play();
playPause.textContent = '⏸';
});
pill.onclick = () => { video.currentTime = m.timestamp; video.play(); };
list.appendChild(pill);
}
if (list.children.length === 0) {
list.textContent = 'No moments yet.';
}
} catch (e) {
list.textContent = 'Could not load moments.';
console.error(e);
}
});
} catch (e) { list.textContent = 'Could not load moments.'; }
}
// add new moment at current time
async function addMoment() {
const ts = Math.floor(video.currentTime || 0);
try {
const res = await fetch(`/api/moments/${encodeURIComponent(videoId)}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ timestamp: ts })
});
const data = await res.json();
if (!data.ok) throw new Error(data.error || 'Failed');
// append immediately
if (!data.ok) throw new Error();
const pill = document.createElement('button');
pill.className = 'moment-pill';
pill.textContent = fmt(ts);
pill.dataset.ts = ts;
pill.addEventListener('click', () => {
video.currentTime = ts;
video.play();
playPause.textContent = '⏸';
});
pill.onclick = () => { video.currentTime = ts; video.play(); };
if (list.textContent === 'No moments yet.') list.textContent = '';
list.appendChild(pill);
} catch (e) {
alert('Could not add moment: ' + (e?.message || ''));
}
} catch (e) { alert('Could not add moment'); }
}
// player controls
playPause.addEventListener('click', () => {
if (video.paused) { video.play(); playPause.textContent = '⏸'; }
else { video.pause(); playPause.textContent = '▶️'; }
});
video.addEventListener('play', () => playPause.textContent = '⏸');
video.addEventListener('pause', () => playPause.textContent = '▶️');
video.addEventListener('loadedmetadata', () => {
seek.max = Math.floor(video.duration || 0);
dur.textContent = fmt(video.duration);
});
video.addEventListener('timeupdate', () => {
cur.textContent = fmt(video.currentTime);
if (!seek.matches(':active')) {
seek.value = Math.floor(video.currentTime || 0);
}
});
seek.addEventListener('input', () => {
video.currentTime = Number(seek.value || 0);
});
vol.addEventListener('input', () => video.volume = Number(vol.value));
speed.addEventListener('change', () => video.playbackRate = Number(speed.value));
addBtn.addEventListener('click', addMoment);
// kick off
loadMoments();
})();
</script>

Loading…
Cancel
Save