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
+57 -7
View File
@@ -52,6 +52,7 @@ class DASHPreBuffer(BasePrebuffer):
max_memory_percent=settings.dash_prebuffer_max_memory_percent,
emergency_threshold=settings.dash_prebuffer_emergency_threshold,
segment_ttl=settings.dash_segment_cache_ttl,
prebuffer_lock_timeout=settings.dash_prebuffer_lock_timeout,
)
self.inactivity_timeout = settings.dash_prebuffer_inactivity_timeout
@@ -100,9 +101,26 @@ class DASHPreBuffer(BasePrebuffer):
logger.warning(f"No profiles found in MPD for prebuffering: {mpd_url}")
return
# Now get segments for each profile by parsing with profile_id
# Early-out for SegmentBase VOD content.
# SegmentBase profiles have initRange set (byte range within a large single file).
# Prebuffering them means downloading 100 MB+ per profile — not useful.
# If every profile is SegmentBase, skip the expensive per-profile segment
# parsing entirely and only prewarm init segments via the direct cache path.
all_segment_base = all(p.get("initRange") is not None for p in base_profiles)
if all_segment_base and not is_live:
logger.info(
f"[prebuffer_dash_manifest] Skipping SegmentBase VOD: {mpd_url} "
f"({len(base_profiles)} profiles, all SegmentBase)"
)
return
# Now get segments for each profile by parsing with profile_id.
# Skip SegmentBase profiles — their "segments" are whole-file downloads.
profiles_with_segments = []
for profile in base_profiles:
if profile.get("initRange") is not None:
# SegmentBase profile — skip expensive segment parsing
continue
profile_id = profile.get("id")
if profile_id:
parsed_with_segments = await get_cached_mpd(
@@ -122,8 +140,10 @@ class DASHPreBuffer(BasePrebuffer):
"last_access": time.time(),
}
# Prebuffer init segments and media segments
await self._prebuffer_profiles(profiles_with_segments, headers, is_live)
# For live streams we only prewarm init segments by default; media prefetch
# is driven by player/playlist requests to avoid lock contention storms.
include_media = not is_live or settings.dash_live_initial_media_prebuffer
await self._prebuffer_profiles(profiles_with_segments, headers, is_live, include_media=include_media)
# Start cleanup task if not running
self._ensure_cleanup_task_running()
@@ -140,6 +160,7 @@ class DASHPreBuffer(BasePrebuffer):
profiles: List[dict],
headers: Dict[str, str],
is_live: bool = False,
include_media: bool = True,
) -> None:
"""
Pre-buffer init segments and media segments for all profiles.
@@ -151,6 +172,7 @@ class DASHPreBuffer(BasePrebuffer):
profiles: List of parsed profiles with resolved URLs
headers: Headers to use for requests
is_live: Whether this is a live stream
include_media: Whether media segments should be prebuffered (init segments are always prewarmed)
"""
if self._should_skip_for_memory():
logger.warning("Memory usage too high, skipping prebuffer")
@@ -161,13 +183,29 @@ class DASHPreBuffer(BasePrebuffer):
init_urls = []
for profile in profiles:
# Skip SegmentBase profiles entirely.
# SegmentBase "segments" are byte-range slices of a single large file
# (e.g. a25.mp4 = 122 MB). Prebuffering them without a Range header
# would download the entire file per profile — potentially GB of data
# for all audio tracks before a single second of playback is served.
# SegmentBase is identified by the profile having initRange (byte range
# within the base file) or by segments having mediaRange set.
segments = profile.get("segments", [])
is_segment_base = profile.get("initRange") is not None or (
segments and segments[0].get("mediaRange") is not None
)
if is_segment_base:
logger.debug(f"[_prebuffer_profiles] Skipping SegmentBase profile: {profile.get('id')}")
continue
# Collect init segment URL
init_url = profile.get("initUrl")
if init_url:
init_urls.append(init_url)
# Get segments to prebuffer
segments = profile.get("segments", [])
if not include_media:
continue
if not segments:
continue
@@ -283,7 +321,13 @@ class DASHPreBuffer(BasePrebuffer):
if segment_urls:
logger.debug(f"Prefetching {len(segment_urls)} upcoming segments from index {current_index + 1}")
# Run prefetch in background
asyncio.create_task(self.prebuffer_segments_batch(segment_urls, headers, max_concurrent=3))
asyncio.create_task(
self.prebuffer_segments_batch(
segment_urls,
headers,
max_concurrent=max(settings.dash_prefetch_max_concurrent, 1),
)
)
except Exception as e:
logger.warning(f"Failed to prefetch upcoming segments: {e}")
@@ -322,7 +366,13 @@ class DASHPreBuffer(BasePrebuffer):
if segment_urls:
logger.debug(f"Live playlist prefetch: {len(segment_urls)} segments")
asyncio.create_task(self.prebuffer_segments_batch(segment_urls, headers, max_concurrent=3))
asyncio.create_task(
self.prebuffer_segments_batch(
segment_urls,
headers,
max_concurrent=max(settings.dash_prefetch_max_concurrent, 1),
)
)
def _ensure_cleanup_task_running(self) -> None:
"""Ensure the cleanup task is running."""