Movies & TV: browse and play your Plex movies and shows (resume, progress to Plex, next episode, quality, audio and subtitles, direct play or HLS conversion)
build-container / build (push) Successful in 20m38s

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
nmemmertandClaude Sonnet 5.5 committed 2026-10-22 10:49:53 -04:00
1 parent 84a0ea10e9
commit f3216b2f3b
11 files changed
+300 -9

No files matched your search

+6 -2
View File
@@ -401,7 +401,7 @@ async function plexProxy(req, res) {
const ac = new AbortController();
res.on('close', () => ac.abort());
// A server that accepts the connection but never answers must not leave the player waiting forever: give up after 15 s without a response.
const attempt = async s => { const hc = new AbortController(), t = setTimeout(() => hc.abort(), 15000); try { return await tryFetch(s, AbortSignal.any([ac.signal, hc.signal])); } finally { clearTimeout(t); } };
const attempt = async s => { const hc = new AbortController(), t = setTimeout(() => hc.abort(), req.url.startsWith('/plex/video/') ? 45000 : 15000); try { return await tryFetch(s, AbortSignal.any([ac.signal, hc.signal])); } finally { clearTimeout(t); } };
const tryFetch = (s, signal) => fetch(s.srv + req.url.slice('/plex'.length), { signal, method: req.method,
headers: { ...PLEX_HEADERS, 'X-Plex-Token': s.stk, Accept: req.headers.accept || 'application/json', 'Accept-Encoding': 'identity', ...(req.headers.range ? { range: req.headers.range } : {}) } });
let up = null;
@@ -418,6 +418,10 @@ async function plexProxy(req, res) {
const encoded = !!up.headers.get('content-encoding');
for (const h of ['content-type', 'content-length', 'content-range', 'accept-ranges']) { if (encoded && h === 'content-length') continue; const v = up.headers.get(h); if (v) out[h] = v; }
if (req.url.startsWith('/plex/photo/')) out['cache-control'] = 'private, max-age=86400';
if (/mpegurl/i.test(up.headers.get('content-type') || '') && up.body) { // a playlist: its absolute addresses must stay under /plex so the browser asks the hub, not the site root
const text = (await up.text()).replace(/^\/(?!plex\/)/gm, '/plex/').replace(/URI="\/(?!plex\/)/g, 'URI="/plex/');
delete out['content-length']; res.writeHead(up.status, out); return res.end(text);
}
res.writeHead(up.status, out);
if (up.body) Readable.fromWeb(up.body).on('error', () => res.destroy()).pipe(res); else res.end();
} catch (e) { if (!res.headersSent) { res.writeHead(502); res.end(); } else res.destroy(); }
@@ -480,7 +484,7 @@ const OO_OK = !!JWT_SECRET || process.env.ALLOW_UNSIGNED_OO === '1';
const ACCESS = [
[/^\/sf\//, ['files', 'office']],
[/^\/api\/oo\/config$/, ['office', 'files']],
[/^\/(api\/plex\/|plex\/)/, ['music']],
[/^\/(api\/plex\/|plex\/)/, ['music', 'video']],
[/^\/api\/audiobooks\//, ['audio']],
[/^\/api\/signal\//, ['signal']],
[/^\/api\/lyrics$/, ['music']],