article.tsx: transform Bunny iframe → folxvideos HLS player when S3 available

This commit is contained in:
Sebastjan 2026-06-07 16:27:15 +02:00
parent c8b0989d4c
commit 6c34d44cea
3 changed files with 68 additions and 2 deletions

View File

@ -204,6 +204,64 @@ export default function ArticlePage() {
}
}, [article?.content]);
// Replace Bunny iframe embeds with native HLS player (folxvideos S3 origin)
useEffect(() => {
if (!article?.content) return;
let cancelled = false;
const loadHlsJs = (): Promise<any> => new Promise((resolve, reject) => {
if ((window as any).Hls) return resolve((window as any).Hls);
const s = document.createElement("script");
s.src = "https://cdn.jsdelivr.net/npm/hls.js@1.5.17/dist/hls.min.js";
s.async = true;
s.onload = () => resolve((window as any).Hls);
s.onerror = reject;
document.head.appendChild(s);
});
const transform = async () => {
const iframes = document.querySelectorAll<HTMLIFrameElement>(
'iframe[src*="mediadelivery.net/embed/"], iframe[src*="iframe.mediadelivery.net/embed/"]'
);
if (iframes.length === 0) return;
let Hls: any = null;
try { Hls = await loadHlsJs(); } catch {}
for (const iframe of Array.from(iframes)) {
if (cancelled) break;
const m = iframe.src.match(/embed\/\d+\/([a-f0-9-]+)/i);
if (!m) continue;
const guid = m[1];
const folxUrl = `https://folxvideos.b-cdn.net/folx-tv/${guid}/master.m3u8`;
try {
const head = await fetch(folxUrl, { method: "HEAD" });
if (!head.ok) continue;
} catch { continue; }
const video = document.createElement("video");
video.controls = true;
video.preload = "metadata";
video.playsInline = true;
video.style.cssText = iframe.getAttribute("style") || "width:100%;aspect-ratio:16/9;border:none;";
video.className = iframe.className || "";
if (Hls && Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(folxUrl);
hls.attachMedia(video);
} else if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = folxUrl;
} else {
continue; // can't play, keep iframe
}
iframe.replaceWith(video);
}
};
// Run after content is in DOM (next tick)
const t = setTimeout(transform, 50);
return () => { cancelled = true; clearTimeout(t); };
}, [article?.content]);
if (isLoading) {
return (
<div className="min-h-screen bg-background">

7
package-lock.json generated
View File

@ -56,6 +56,7 @@
"express": "^5.0.1",
"express-session": "^1.18.1",
"framer-motion": "^11.13.1",
"hls.js": "^1.5.17",
"input-otp": "^1.4.2",
"lucide-react": "^0.453.0",
"memorystore": "^1.6.7",
@ -6030,6 +6031,12 @@
"node": ">= 0.4"
}
},
"node_modules/hls.js": {
"version": "1.6.16",
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.16.tgz",
"integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==",
"license": "Apache-2.0"
},
"node_modules/http_ece": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http_ece/-/http_ece-1.2.0.tgz",

View File

@ -86,7 +86,8 @@
"wouter": "^3.3.5",
"ws": "^8.18.0",
"zod": "^3.25.76",
"zod-validation-error": "^3.5.4"
"zod-validation-error": "^3.5.4",
"hls.js": "^1.5.17"
},
"devDependencies": {
"@replit/vite-plugin-cartographer": "^0.4.4",
@ -121,4 +122,4 @@
"optionalDependencies": {
"bufferutil": "^4.0.8"
}
}
}