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 / {