Calendar: several NE Hub calendars with colors, per-person view/edit sharing and per-calendar subscribe links; Movies & TV: converted video keeps the right time and seeks inside what the stream has

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-22 12:38:15 -04:00
1 parent d25eae5920
commit d813884074
8 files changed
+343 -46

No files matched your search

+10 -5
View File
@@ -204,7 +204,10 @@ function videoMount() {
/** Media events, bound once to the video element (which outlives the player page while the mini player is showing). */
function videoBindMedia(el) {
const msg = t => { const e = $('#vmsg'); if (e) e.textContent = t || ''; };
el.addEventListener('timeupdate', () => { if (vp && vp.el === el && !el.seeking) vp.pos = vp.base + el.currentTime; vdUi(); });
el.addEventListener('timeupdate', () => { if (vp && vp.el === el && vp.mode === 'hls' && vp.learn && el.currentTime > 0 && !el.seeking) { // Plex's converted stream may count from 0 (what we asked for starts there) or carry on from the real position: tell which, once
const first = el.buffered.length ? el.buffered.start(0) : el.currentTime;
vp.base = vp.learn > 5 ? (first > vp.learn - 10 ? 0 : Math.floor(vp.learn)) : 0; vp.learn = 0; }
if (vp && vp.el === el && !el.seeking) vp.pos = vp.base + el.currentTime; vdUi(); });
el.addEventListener('play', () => { vdUi(); if (vp && vp.wake) vp.wake(); });
el.addEventListener('pause', () => { vdUi(); if (vp && vp.wake) vp.wake(); if (!el.ended) vdReport('paused'); });
el.addEventListener('volumechange', () => { const mu = $('#vmute'); if (mu) mu.innerHTML = icon(el.muted || el.volume === 0 ? 'mute' : 'vol'); });
@@ -281,8 +284,10 @@ function videoSeek(abs) {
if (!vp) return; const el = vp.el, t = (vp.m.duration || 0) / 1000;
abs = Math.max(0, t ? Math.min(abs, t - 1) : abs);
if (vp.mode === 'direct') { el.currentTime = abs; return; }
const rel = abs - vp.base;
if (rel >= 0 && isFinite(el.duration) && rel < el.duration - 2) el.currentTime = rel; else videoStart(abs);
const rel = abs - vp.base, sk = el.seekable;
// Only inside what the converted stream really has (its range may begin late when it started part-way); anything else restarts the conversion there.
const inside = sk && sk.length ? rel >= sk.start(0) - 0.5 && rel <= sk.end(sk.length - 1) - 2 : rel >= 0 && isFinite(el.duration) && rel < el.duration - 2;
if (inside) el.currentTime = rel; else videoStart(abs);
}
function videoSkip(d) {
if (!vp) return; const to = Math.max(0, vp.base + vp.el.currentTime + d), t = (vp.m.duration || 0) / 1000 || vp.el.duration || 0;
@@ -320,10 +325,10 @@ async function videoStart(sec) {
vp.session = crypto.randomUUID ? crypto.randomUUID() : String(Date.now() + Math.random());
const q = QUALITIES.find(x => x[0] === vp.q) || QUALITIES[0], direct = !vp.forceHls && (vp.q === 'original' || (vp.q === 'auto' && canDirect(media) && !vp.audio && !vp.sub));
if (direct) {
vp.mode = 'direct'; vp.base = 0; el.src = '/plex' + part.key; if (sec > 1) el.addEventListener('loadedmetadata', () => { el.currentTime = sec; }, { once: true });
vp.mode = 'direct'; vp.base = 0; vp.learn = 0; el.src = '/plex' + part.key; if (sec > 1) el.addEventListener('loadedmetadata', () => { el.currentTime = sec; }, { once: true });
const t = $('#vmsg'); if (t) t.textContent = '';
} else {
vp.mode = 'hls'; vp.base = sec;
vp.mode = 'hls'; vp.base = sec; vp.learn = sec || 0.001;
const o = q[2] || QUALITIES[0][2], p = new URLSearchParams({ path: '/library/metadata/' + vp.id, mediaIndex: 0, partIndex: 0, protocol: 'hls', fastSeek: 1, directPlay: 0, directStream: 1, subtitleSize: 100, audioBoost: 100, offset: Math.floor(sec), session: vp.session, maxVideoBitrate: o.kbps, 'X-Plex-Platform': 'Chrome' });
if (o.res) p.set('videoResolution', o.res); if (vp.audio) p.set('audioStreamID', vp.audio); if (vp.sub) { p.set('subtitleStreamID', vp.sub); p.set('subtitles', 'burn'); }
const url = '/plex/video/:/transcode/universal/start.m3u8?' + p; const t = $('#vmsg'); if (t) t.textContent = 'Preparing the stream…';