Add caching and token fallback for /api/video streams

Co-authored-by: mathieumoran1 <[email protected]>
This commit is contained in:
Cursor Agent
2025-12-22 07:02:32 +00:00
co-authored by mathieumoran1
parent 8d93c31df8
commit fd881399cd
+55 -4
View File
@@ -511,6 +511,10 @@ function toVideoSessionUrl(videoSrc) {
} }
} }
// Best-effort cache to avoid spamming /api/video/session.
// Keyed by sessionUrl + token (token is already in-memory, not persisted here).
const _videoSessionCache = new Map(); // key -> expiresAt ms
async function ensureVideoSessionForSource(videoSrc, token) { async function ensureVideoSessionForSource(videoSrc, token) {
// Only needed for our protected /api/video/* streams. // Only needed for our protected /api/video/* streams.
if (!token) return; if (!token) return;
@@ -528,18 +532,64 @@ async function ensureVideoSessionForSource(videoSrc, token) {
} }
})(); })();
const cacheKey = `${sessionUrl}::${token}`;
const cachedUntil = _videoSessionCache.get(cacheKey) || 0;
if (Date.now() < cachedUntil) return;
try { try {
await fetch(sessionUrl, { await fetch(sessionUrl, {
method: 'POST', method: 'POST',
headers: token ? { 'Authorization': `Bearer ${token}` } : undefined, headers: token ? { 'Authorization': `Bearer ${token}` } : undefined,
...(isCrossOrigin ? { credentials: 'include' } : {}), // Always include credentials so the cookie is stored even if the browser
// treats this as cross-origin unexpectedly (hostname differences, etc).
credentials: 'include',
cache: 'no-store',
}); });
_videoSessionCache.set(cacheKey, Date.now() + 60_000); // 60s
} catch (_) { } catch (_) {
// best-effort // best-effort
} }
} }
async function playVodLink(vodLink) { function addTokenToApiVideoUrl(rawUrl, token) {
if (!rawUrl || !token) return rawUrl;
try {
const isRelative = String(rawUrl).trim().startsWith('/');
const u = new URL(String(rawUrl), window.location.origin);
u.searchParams.set('token', token);
return isRelative ? `${u.pathname}${u.search}${u.hash}` : u.toString();
} catch (_) {
// Fallback: naive append
const s = String(rawUrl);
const join = s.includes('?') ? '&' : '?';
return `${s}${join}token=${encodeURIComponent(token)}`;
}
}
async function probeApiVideoReadable(rawUrl) {
try {
const res = await fetch(rawUrl, {
method: 'GET',
headers: { Range: 'bytes=0-0' },
credentials: 'include',
cache: 'no-store',
});
return res.status !== 401;
} catch (_) {
return true; // don't block playback if probe fails (CORS/network/etc)
}
}
async function ensurePlayableApiVideoUrl(rawUrl, token) {
if (!isApiVideoUrl(rawUrl) || !token) return rawUrl;
await ensureVideoSessionForSource(rawUrl, token);
const ok = await probeApiVideoReadable(rawUrl);
if (ok) return rawUrl;
// Fallback: servers also accept ?token=... (avoids cookie timing/site issues).
return addTokenToApiVideoUrl(rawUrl, token);
}
async function playVodLink(vodLink, { token } = {}) {
const raw = normalizeVodLink(vodLink); const raw = normalizeVodLink(vodLink);
if (!raw) { if (!raw) {
showAlert('No VOD link configured for this movie.'); showAlert('No VOD link configured for this movie.');
@@ -570,7 +620,8 @@ async function playVodLink(vodLink) {
setSourceLabel('Server video stream (/api/video)'); setSourceLabel('Server video stream (/api/video)');
const videoEl = showVideoPlayer(); const videoEl = showVideoPlayer();
if (!videoEl) return; if (!videoEl) return;
videoEl.src = raw; const playable = await ensurePlayableApiVideoUrl(raw, token);
videoEl.src = playable;
videoEl.addEventListener('error', () => { videoEl.addEventListener('error', () => {
// If the browser can't play it (or CORS blocks), fall back to iframe. // If the browser can't play it (or CORS blocks), fall back to iframe.
setSourceLabel('Embed (fallback)'); setSourceLabel('Embed (fallback)');
@@ -744,7 +795,7 @@ window.onload = async function () {
return; return;
} }
await playVodLink(resolved.src); await playVodLink(resolved.src, { token });
// Only the <video> player supports reliable progress tracking (mp4/hls). // Only the <video> player supports reliable progress tracking (mp4/hls).
const videoEl = document.getElementById('video'); const videoEl = document.getElementById('video');