From ef4bc24edac739cc0efd2dff62b81a18e8490d0c Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 4 Feb 2026 01:03:16 +0000 Subject: [PATCH] Use GPUVideoSource for hardware-accelerated video decoding - CIDVideoSource now uses GPUVideoSource when GPU is available - Enables CUDA hardware decoding for video sources - Should significantly improve rendering performance Co-Authored-By: Claude Opus 4.5 --- app/templates/runs/detail.html | 51 +++++++++++++++++++++++++++------- tasks/streaming.py | 15 ++++++++-- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/app/templates/runs/detail.html b/app/templates/runs/detail.html index 40b0d32..339c437 100644 --- a/app/templates/runs/detail.html +++ b/app/templates/runs/detail.html @@ -112,21 +112,22 @@ function initHls() { if (Hls.isSupported()) { hls = new Hls({ - // Stability over low latency - buffer more for smoother playback - liveSyncDurationCount: 4, // Stay 4 segments behind live edge - liveMaxLatencyDurationCount: 8, // Max 8 segments behind + // Stay far behind live edge - rendering is slow (~0.1x speed) + // 10 segments = 40s of buffer before catching up + liveSyncDurationCount: 10, // Stay 10 segments behind live edge + liveMaxLatencyDurationCount: 20, // Allow up to 20 segments behind liveDurationInfinity: true, // Treat as infinite live stream // Large buffers to absorb rendering speed variations - maxBufferLength: 60, // Buffer up to 60s ahead - maxMaxBufferLength: 120, // Allow even more if needed - maxBufferSize: 60 * 1024 * 1024, // 60MB buffer + maxBufferLength: 120, // Buffer up to 120s ahead + maxMaxBufferLength: 180, // Allow even more if needed + maxBufferSize: 100 * 1024 * 1024, // 100MB buffer maxBufferHole: 0.5, // Tolerate small gaps // Back buffer for smooth seeking - backBufferLength: 30, + backBufferLength: 60, - // Playlist reload settings + // Playlist reload settings - check frequently for new segments manifestLoadingTimeOut: 10000, manifestLoadingMaxRetry: 4, levelLoadingTimeOut: 10000, @@ -202,9 +203,21 @@ } }); - // Handle video stalls + // Handle video stalls - check if we've caught up to live edge video.addEventListener('waiting', function() { - statusEl.textContent = 'Buffering...'; + // Check if we're near the live edge (within 2 segments) + if (hls && hls.liveSyncPosition) { + const liveEdge = hls.liveSyncPosition; + const currentTime = video.currentTime; + const behindLive = liveEdge - currentTime; + if (behindLive < 8) { // Less than 2 segments behind + statusEl.textContent = 'Waiting for rendering...'; + } else { + statusEl.textContent = 'Buffering...'; + } + } else { + statusEl.textContent = 'Buffering...'; + } statusEl.classList.remove('text-green-400'); statusEl.classList.add('text-yellow-400'); }); @@ -215,6 +228,24 @@ statusEl.classList.add('text-green-400'); }); + // Periodic check for catching up to live edge + setInterval(function() { + if (hls && !video.paused && hls.levels && hls.levels.length > 0) { + const buffered = video.buffered; + if (buffered.length > 0) { + const bufferEnd = buffered.end(buffered.length - 1); + const currentTime = video.currentTime; + const bufferAhead = bufferEnd - currentTime; + // If less than 4 seconds buffered, show warning + if (bufferAhead < 4) { + statusEl.textContent = 'Waiting for rendering...'; + statusEl.classList.remove('text-green-400'); + statusEl.classList.add('text-yellow-400'); + } + } + } + }, 1000); + hls.loadSource(hlsUrl); hls.attachMedia(video); } else if (video.canPlayType('application/vnd.apple.mpegurl')) { diff --git a/tasks/streaming.py b/tasks/streaming.py index 5066b5f..6ba0bef 100644 --- a/tasks/streaming.py +++ b/tasks/streaming.py @@ -134,9 +134,18 @@ class CIDVideoSource: raise ValueError(f"Could not resolve video source '{self.cid}' for actor_id={self.actor_id}") logger.info(f"CIDVideoSource._ensure_source: resolved to path={path}") - # Import from primitives where VideoSource is defined - from sexp_effects.primitive_libs.streaming import VideoSource - self._source = VideoSource(str(path), self.fps) + # Use GPU-accelerated video source if available + try: + from sexp_effects.primitive_libs.streaming_gpu import GPUVideoSource, GPU_AVAILABLE + if GPU_AVAILABLE: + logger.info(f"CIDVideoSource: using GPUVideoSource for {path}") + self._source = GPUVideoSource(str(path), self.fps, prefer_gpu=True) + else: + raise ImportError("GPU not available") + except (ImportError, Exception) as e: + logger.info(f"CIDVideoSource: falling back to CPU VideoSource ({e})") + from sexp_effects.primitive_libs.streaming import VideoSource + self._source = VideoSource(str(path), self.fps) def read_at(self, t: float): self._ensure_source()