new version

This commit is contained in:
UrloMythus
2026-04-15 19:23:14 +02:00
parent 5120b19d0b
commit 8134936d59
135 changed files with 3013 additions and 1589 deletions
+72 -33
View File
@@ -159,6 +159,11 @@ class M3U8Processor:
request.url_for("hls_manifest_proxy").replace(scheme=get_original_scheme(request))
).replace("/hls/manifest.m3u8", "/hls/segment")
self.playlist_url = None # Will be set when processing starts
# Per HLS spec, any URI on the line immediately following #EXT-X-STREAM-INF
# is a variant sub-playlist, not a segment. Track this so proxy_content_url
# can route it to the manifest endpoint regardless of file extension or query
# params (e.g. VixCloud uses ?type=video which looks like a segment URL).
self._after_stream_inf = False
def _should_apply_start_offset(self, content: str) -> bool:
"""
@@ -656,10 +661,25 @@ class M3U8Processor:
str: The processed line.
"""
if "URI=" in line:
self._after_stream_inf = False
return await self.process_key_line(line, base_url)
elif not line.startswith("#") and line.strip():
if self._after_stream_inf:
# Per HLS spec §4.3.4.2, the URI on the line immediately following
# #EXT-X-STREAM-INF is always a variant sub-playlist, never a segment.
# Route it to the manifest proxy regardless of extension or query params
# (e.g. VixCloud uses ?type=video rather than a .m3u8 extension).
self._after_stream_inf = False
full_url = parse.urljoin(base_url, line)
return await self.proxy_url(full_url, base_url, use_full_url=True, is_playlist=True)
return await self.proxy_content_url(line, base_url)
else:
if line.startswith("#EXT-X-STREAM-INF"):
self._after_stream_inf = True
elif line.startswith("#"):
# Any other tag resets the flag — EXT-X-STREAM-INF must be
# immediately followed by a URI with no intervening tags.
self._after_stream_inf = False
return line
async def process_key_line(self, line: str, base_url: str) -> str:
@@ -691,12 +711,20 @@ class M3U8Processor:
# otherwise the proxied destination URL gets the wrong upstream hostname.
if self.key_url and line.startswith("#EXT-X-KEY"):
uri = uri._replace(scheme=self.key_url.scheme, netloc=self.key_url.netloc)
# Check if this is a DLHD stream with key params (needs stream endpoint for header computation)
query_params = dict(self.request.query_params)
is_dlhd_key_request = "dlhd_salt" in query_params and "/key/" in uri.geturl()
# Use stream endpoint for DLHD key URLs, manifest endpoint for others
resolved_key_url = uri.geturl()
if not uri.scheme:
resolved_key_url = parse.urljoin(base_url, resolved_key_url)
# #EXT-X-MEDIA and #EXT-X-I-FRAME-STREAM-INF carry sub-playlist URIs
# (audio/subtitle/i-frame rendition playlists) that must be routed through
# the manifest proxy so their own segment URLs get rewritten.
# All other tags with URI= (#EXT-X-KEY, #EXT-X-MAP, #EXT-X-SESSION-KEY)
# reference raw binary data and must go through the segment proxy so the
# raw bytes are returned to the player without M3U8 parsing.
is_sub_playlist = line.startswith(("#EXT-X-MEDIA", "#EXT-X-I-FRAME-STREAM-INF"))
new_uri = await self.proxy_url(
uri.geturl(), base_url, use_full_url=True, is_playlist=not is_dlhd_key_request
resolved_key_url, base_url, use_full_url=True, is_playlist=is_sub_playlist, is_key=not is_sub_playlist
)
line = line.replace(f'URI="{original_uri}"', f'URI="{new_uri}"')
return line
@@ -725,9 +753,18 @@ class M3U8Processor:
# Determine routing strategy based on configuration
routing_strategy = settings.m3u8_content_routing
# Check if we should force MediaFlow proxy for all playlist URLs
# If force_playlist_proxy is set, route all content through the proxy but
# distinguish actual playlists from segments: sending raw MPEG-TS bytes to
# the manifest endpoint (is_playlist=True) causes a "#EXTM3U not found" error
# because the endpoint tries to parse binary segment data as an HLS playlist.
# Segments disguised as .js/.css (e.g. chevy streams) must go to the segment
# endpoint (is_playlist=False) so the bytes are streamed back as-is.
if self.force_playlist_proxy:
return await self.proxy_url(full_url, base_url, use_full_url=True, is_playlist=True)
parsed_url = parse.urlparse(full_url)
content_is_playlist = parsed_url.path.endswith((".m3u", ".m3u8", ".m3u_plus")) or parse.parse_qs(
parsed_url.query
).get("type", [""])[0] in ["m3u", "m3u8", "m3u_plus"]
return await self.proxy_url(full_url, base_url, use_full_url=True, is_playlist=content_is_playlist)
# For playlist URLs, always use MediaFlow proxy regardless of strategy
# Check for actual playlist file extensions, not just substring matches
@@ -783,7 +820,14 @@ class M3U8Processor:
segment_urls.append(parse.urljoin(base_url, line))
return segment_urls
async def proxy_url(self, url: str, base_url: str, use_full_url: bool = False, is_playlist: bool = True) -> str:
async def proxy_url(
self,
url: str,
base_url: str,
use_full_url: bool = False,
is_playlist: bool = True,
is_key: bool = False,
) -> str:
"""
Proxies a URL, encoding it with the MediaFlow proxy URL.
@@ -792,6 +836,7 @@ class M3U8Processor:
base_url (str): The base URL to resolve relative URLs.
use_full_url (bool): Whether to use the URL as-is (True) or join with base_url (False).
is_playlist (bool): Whether this is a playlist URL (uses manifest endpoint) or segment URL (uses stream endpoint).
is_key (bool): Whether this is a key/init-segment URL (suppresses AES param injection).
Returns:
str: The proxied URL.
@@ -819,31 +864,25 @@ class M3U8Processor:
if is_playlist:
proxy_url = self.mediaflow_proxy_url
else:
# Check if this is a DLHD key URL (needs /stream endpoint for header computation)
is_dlhd_key = "dlhd_salt" in query_params and "/key/" in full_url
if is_dlhd_key:
# Use /stream endpoint for DLHD key URLs
proxy_url = self.mediaflow_proxy_url.replace("/hls/manifest.m3u8", "/stream")
else:
# Determine segment extension from the URL
# Default to .ts for traditional HLS, but detect fMP4 extensions
segment_ext = "ts"
url_lower = full_url.lower()
# Check for fMP4/CMAF extensions
if url_lower.endswith(".m4s"):
segment_ext = "m4s"
elif url_lower.endswith(".mp4"):
segment_ext = "mp4"
elif url_lower.endswith(".m4a"):
segment_ext = "m4a"
elif url_lower.endswith(".m4v"):
segment_ext = "m4v"
elif url_lower.endswith(".aac"):
segment_ext = "aac"
# Build segment proxy URL with correct extension
proxy_url = f"{self.segment_proxy_base_url}.{segment_ext}"
# Remove h_range header - each segment should handle its own range requests
query_params.pop("h_range", None)
# Determine segment extension from the URL
# Default to .ts for traditional HLS, but detect fMP4 extensions
segment_ext = "ts"
url_lower = full_url.lower()
# Check for fMP4/CMAF extensions
if url_lower.endswith(".m4s"):
segment_ext = "m4s"
elif url_lower.endswith(".mp4"):
segment_ext = "mp4"
elif url_lower.endswith(".m4a"):
segment_ext = "m4a"
elif url_lower.endswith(".m4v"):
segment_ext = "m4v"
elif url_lower.endswith(".aac"):
segment_ext = "aac"
# Build segment proxy URL with correct extension
proxy_url = f"{self.segment_proxy_base_url}.{segment_ext}"
# Remove h_range header - each segment should handle its own range requests
query_params.pop("h_range", None)
return encode_mediaflow_proxy_url(
proxy_url,