feat(hls): resume-aware first spawn + capped-CRF/CQ rate control
- HLSSessionConfig.StartSec (sync StreamSession.startSec): el primer ffmpeg arranca ya seekeado en el punto de resume (-ss + -output_ts_offset + -start_number, misma maquinaria que el seek-restart) en vez de encodear desde seg-0 para morir en el seek-restart inmediato del player (doble spawn, resume lento). readyMax se pre-siembra al índice de arranque; el ready-watcher compara ReadyCount() > WriterStartIdx() para no marcar "ready" antes del primer segmento real. startSec >= duración → arranque desde 0 (resume obsoleto de un fichero reemplazado). - Rate control: capped constant-quality donde el encoder lo hace bien — libx264 -crf 23, NVENC -cq 23 -b:v 0 — con el mismo -maxrate de siempre y -bufsize 2x (antes 1x estrangulaba picos). Escenas fáciles emiten muchos menos bits (menos stalls vía funnel/LTE); el peor caso no cambia. QSV/VideoToolbox/VAAPI conservan el triple de bitrate fijo probado (sus knobs de calidad tienen gotchas de vendor). - Limpieza: wrapper buildHLSFFmpegArgs y guard startIdx<0 muertos.
This commit is contained in:
parent
f7ca282ca0
commit
9b97aedfe4
5 changed files with 259 additions and 16 deletions
|
|
@ -790,6 +790,7 @@ func runDaemonStart() error {
|
|||
Quality: sess.Quality,
|
||||
AudioIndex: sess.AudioIndex,
|
||||
BurnSubtitleIndex: sess.BurnSubtitleIndex,
|
||||
StartSec: sess.StartSec,
|
||||
Transcode: tcRuntime,
|
||||
Cache: hlsCache,
|
||||
// 2c: refresh the debrid link if it expires mid-transcode; the
|
||||
|
|
@ -925,6 +926,7 @@ func runDaemonStart() error {
|
|||
Quality: sess.Quality,
|
||||
AudioIndex: sess.AudioIndex,
|
||||
BurnSubtitleIndex: sess.BurnSubtitleIndex,
|
||||
StartSec: sess.StartSec,
|
||||
Transcode: tcRuntime,
|
||||
Cache: hlsCache,
|
||||
}, hlsCtx, hlsCancel)
|
||||
|
|
@ -1449,8 +1451,13 @@ func watchSessionReady(ctx context.Context, client *agent.Client, hsess *engine.
|
|||
if hsess.IsClosed() {
|
||||
return
|
||||
}
|
||||
// Phase 1: cache HIT or seg-0 ready → flip the "Preparando…" UI now.
|
||||
if !readyPosted && (hsess.FromCache() || hsess.ReadyCount() >= 1) {
|
||||
// Phase 1: cache HIT or first segment ready → flip the "Preparando…"
|
||||
// UI now. Compare against WriterStartIdx, not `>= 1`: a resume
|
||||
// session (StartSec) pre-seeds readyMax to the start index, so
|
||||
// ReadyCount() is ≥ 1 before ffmpeg has written a single byte —
|
||||
// `>= 1` would fire "ready" instantly and freeze the player waiting
|
||||
// on a segment that doesn't exist yet.
|
||||
if !readyPosted && (hsess.FromCache() || hsess.ReadyCount() > hsess.WriterStartIdx()) {
|
||||
postReady(nil)
|
||||
readyPosted = true
|
||||
// Cache replay has no live encode → no telemetry to report, done.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue