Fix GPU encoding black frames and improve debug logging
- Add CUDA sync before encoding to ensure RGB->NV12 kernel completes - Add debug logging for frame data validation (sum check) - Handle GPUFrame objects in GPUHLSOutput.write() - Fix cv2.resize for CuPy arrays (use cupyx.scipy.ndimage.zoom) - Fix fused pipeline parameter ordering (geometric first, color second) - Add raindrop-style ripple with random position/freq/decay/amp - Generate final VOD playlist with #EXT-X-ENDLIST Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1137,6 +1137,52 @@ async def serve_hls_content(
|
||||
raise HTTPException(404, f"File not found: {filename}")
|
||||
|
||||
|
||||
@router.get("/{run_id}/playlist.m3u8")
|
||||
async def get_playlist(run_id: str, request: Request):
|
||||
"""Get live HLS playlist for a streaming run.
|
||||
|
||||
Returns the latest playlist content directly, allowing HLS players
|
||||
to poll this URL for updates without dealing with changing IPFS CIDs.
|
||||
"""
|
||||
import database
|
||||
import os
|
||||
import httpx
|
||||
from fastapi.responses import Response
|
||||
|
||||
await database.init_db()
|
||||
|
||||
pending = await database.get_pending_run(run_id)
|
||||
if not pending:
|
||||
raise HTTPException(404, "Run not found")
|
||||
|
||||
ipfs_playlist_cid = pending.get("ipfs_playlist_cid")
|
||||
if not ipfs_playlist_cid:
|
||||
raise HTTPException(404, "Playlist not yet available")
|
||||
|
||||
# Fetch playlist from local IPFS node
|
||||
ipfs_api = os.environ.get("IPFS_API_URL", "http://celery_ipfs:5001")
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient(timeout=10.0) as client:
|
||||
resp = await client.post(f"{ipfs_api}/api/v0/cat?arg={ipfs_playlist_cid}")
|
||||
if resp.status_code != 200:
|
||||
raise HTTPException(502, "Failed to fetch playlist from IPFS")
|
||||
playlist_content = resp.text
|
||||
except httpx.RequestError as e:
|
||||
raise HTTPException(502, f"IPFS error: {e}")
|
||||
|
||||
return Response(
|
||||
content=playlist_content,
|
||||
media_type="application/vnd.apple.mpegurl",
|
||||
headers={
|
||||
"Cache-Control": "no-cache, no-store, must-revalidate",
|
||||
"Pragma": "no-cache",
|
||||
"Expires": "0",
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{run_id}/ipfs-stream")
|
||||
async def get_ipfs_stream_info(run_id: str, request: Request):
|
||||
"""Get IPFS streaming info for a run.
|
||||
|
||||
@@ -103,15 +103,34 @@
|
||||
const video = document.getElementById('live-video');
|
||||
const statusEl = document.getElementById('stream-status');
|
||||
const loadingEl = document.getElementById('stream-loading');
|
||||
const hlsUrl = '/runs/{{ run.run_id }}/hls/stream.m3u8';
|
||||
// Use dynamic playlist endpoint with cache busting
|
||||
const baseUrl = '/runs/{{ run.run_id }}/playlist.m3u8';
|
||||
function getHlsUrl() {
|
||||
return baseUrl + '?_t=' + Date.now();
|
||||
}
|
||||
let hls = null;
|
||||
let retryCount = 0;
|
||||
const maxRetries = 120; // Try for up to 4 minutes
|
||||
let segmentsLoaded = 0;
|
||||
|
||||
// Custom playlist loader that adds cache-busting to every request
|
||||
class CacheBustingPlaylistLoader extends Hls.DefaultConfig.loader {
|
||||
load(context, config, callbacks) {
|
||||
if (context.type === 'manifest' || context.type === 'level') {
|
||||
const url = new URL(context.url, window.location.origin);
|
||||
url.searchParams.set('_t', Date.now());
|
||||
context.url = url.toString();
|
||||
}
|
||||
super.load(context, config, callbacks);
|
||||
}
|
||||
}
|
||||
|
||||
function initHls() {
|
||||
if (Hls.isSupported()) {
|
||||
hls = new Hls({
|
||||
// Custom loader to bust cache on playlist requests
|
||||
pLoader: CacheBustingPlaylistLoader,
|
||||
|
||||
// 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
|
||||
@@ -177,7 +196,7 @@
|
||||
// Exponential backoff with jitter
|
||||
const delay = Math.min(1000 * Math.pow(1.5, Math.min(retryCount, 6)), 10000);
|
||||
setTimeout(() => {
|
||||
hls.loadSource(hlsUrl);
|
||||
hls.loadSource(getHlsUrl());
|
||||
}, delay + Math.random() * 1000);
|
||||
} else {
|
||||
statusEl.textContent = 'Stream unavailable';
|
||||
@@ -246,11 +265,11 @@
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
hls.loadSource(hlsUrl);
|
||||
hls.loadSource(getHlsUrl());
|
||||
hls.attachMedia(video);
|
||||
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
|
||||
// Native HLS support (Safari)
|
||||
video.src = hlsUrl;
|
||||
video.src = getHlsUrl();
|
||||
video.addEventListener('loadedmetadata', function() {
|
||||
loadingEl.classList.add('hidden');
|
||||
statusEl.textContent = 'Playing';
|
||||
|
||||
Reference in New Issue
Block a user