From 6c34d44ceaf6cc96608f8a31afeb262ba48da065 Mon Sep 17 00:00:00 2001 From: Sebastjan Date: Sun, 7 Jun 2026 16:27:15 +0200 Subject: [PATCH] =?UTF-8?q?article.tsx:=20transform=20Bunny=20iframe=20?= =?UTF-8?q?=E2=86=92=20folxvideos=20HLS=20player=20when=20S3=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/src/pages/article.tsx | 58 ++++++++++++++++++++++++++++++++++++ package-lock.json | 7 +++++ package.json | 5 ++-- 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/client/src/pages/article.tsx b/client/src/pages/article.tsx index 8f5df52..f81db80 100644 --- a/client/src/pages/article.tsx +++ b/client/src/pages/article.tsx @@ -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 => 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( + '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 (
diff --git a/package-lock.json b/package-lock.json index bbd1629..8fb9f97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 6fdb4dc..8fffe39 100644 --- a/package.json +++ b/package.json @@ -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" } -} +} \ No newline at end of file