diff --git a/webapp/api.py b/webapp/api.py index 60362ad..93185a3 100644 --- a/webapp/api.py +++ b/webapp/api.py @@ -10,6 +10,7 @@ import logging import os import hashlib from urllib.parse import quote +from urllib.parse import urljoin from urllib.parse import urlparse import sys from contextlib import asynccontextmanager @@ -497,9 +498,52 @@ async def proxy_shorties_media(url: str, request: Request): raise HTTPException(status_code=resp.status, detail="Upstream error") content_type = resp.headers.get("Content-Type", "application/octet-stream") + + if ".m3u8" in parsed.path.lower() or "mpegurl" in content_type.lower(): + try: + body = await resp.text() + finally: + await resp.release() + await session.close() + + def _proxy_media_url(target_url: str) -> str: + return f"/api/proxy-shorties-media?url={quote(target_url, safe='')}" + + rewritten_lines: list[str] = [] + for raw_line in body.splitlines(): + stripped = raw_line.strip() + if not stripped: + rewritten_lines.append(raw_line) + continue + + if stripped.startswith("#EXT-X-KEY") and 'URI="' in raw_line: + prefix, rest = raw_line.split('URI="', 1) + original_uri, suffix = rest.split('"', 1) + absolute_uri = urljoin(url, original_uri) + rewritten_lines.append(f'{prefix}URI="{_proxy_media_url(absolute_uri)}"{suffix}') + continue + + if stripped.startswith("#"): + rewritten_lines.append(raw_line) + continue + + absolute_uri = urljoin(url, stripped) + rewritten_lines.append(_proxy_media_url(absolute_uri)) + + playlist_body = "\n".join(rewritten_lines) + return Response( + content=playlist_body, + media_type=content_type, + headers={ + "Cache-Control": "public, max-age=120", + "Access-Control-Allow-Origin": "*", + }, + ) + passthrough_headers = { "Cache-Control": "public, max-age=600", "Accept-Ranges": resp.headers.get("Accept-Ranges", "bytes"), + "Access-Control-Allow-Origin": "*", } if resp.headers.get("Content-Length"): passthrough_headers["Content-Length"] = resp.headers["Content-Length"] diff --git a/webapp/frontend/js/app.js b/webapp/frontend/js/app.js index 55c7045..3b7fd08 100644 --- a/webapp/frontend/js/app.js +++ b/webapp/frontend/js/app.js @@ -880,10 +880,12 @@ async function loadFurtokPage(feedEl) { } else { const poster = post.sample ? ` poster="${post.sample}"` : ''; const directMp4 = post.mp4_url || post.url || ''; - const hasHls = isShorties && Boolean(post.hls_url); - const videoSrc = hasHls ? '' : (isShorties ? API.proxyShortiesMediaUrl(directMp4) : post.url); - const hlsSrc = isShorties ? escapeHtml(post.hls_url || '') : ''; - mediaHtml = ``; + const proxyMp4 = isShorties ? API.proxyShortiesMediaUrl(directMp4) : post.url; + const proxyHls = isShorties && post.hls_url ? API.proxyShortiesMediaUrl(post.hls_url) : ''; + const useHls = isShorties && !directMp4 && Boolean(post.hls_url); + const videoSrc = useHls ? '' : proxyMp4; + const hlsSrc = isShorties ? escapeHtml(proxyHls) : ''; + mediaHtml = ``; } const title = post.title ? escapeHtml(post.title) : ''; @@ -918,8 +920,8 @@ async function loadFurtokPage(feedEl) { initShortiesVideoPlayback(video); } video.addEventListener('error', () => { - if (isShorties && video.dataset.directSrc && video.src !== video.dataset.directSrc) { - video.src = video.dataset.directSrc; + if (isShorties && video.dataset.proxySrc && video.src !== video.dataset.proxySrc) { + video.src = video.dataset.proxySrc; video.load(); } }); diff --git a/webapp/frontend/nginx.conf b/webapp/frontend/nginx.conf index 17c883b..82b5ed0 100644 --- a/webapp/frontend/nginx.conf +++ b/webapp/frontend/nginx.conf @@ -40,6 +40,9 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Telegram-Init-Data $http_x_telegram_init_data; + proxy_buffering off; + proxy_request_buffering off; + proxy_read_timeout 300s; } location / {