feat(agent): session-ready webhook for SSE-driven player handshake (0.9.13)
Closes Fase 3.3b. Daemon now tells the server the moment a session's
first HLS segment + init.mp4 land on disk; the web side flips
streaming_session.ready_at = NOW(), which its SSE endpoint pushes to
subscribed players so the loading UI flips from "Preparando…" to
"Stream listo" without polling HEAD on the segment URL.
Surface:
- New Client.MarkSessionReady(ctx, sessionId) HTTP method →
POST /api/internal/agent/session-ready.
- New engine.HLSSession.ReadyCount() + FromCache() accessors so the
watcher goroutine doesn't reach into private state.
- New cmd.watchSessionReady(ctx, client, hsess, sessionId) goroutine
polls ReadyCount every 200 ms with a 60 s deadline + short-circuits
for cache-HIT sessions (ready the moment StartHLSSession returns).
- Daemon callback spawns it right after streamSrv.HLS().Register so
the watcher's lifecycle matches the session's.
Best-effort: a transient network failure on the webhook is logged + the
goroutine exits — the player's existing HEAD-probe retry path still
discovers ready state independently. The webhook is an acceleration,
not a hard dependency.
This commit is contained in:
parent
4f304fb13a
commit
4ccd37aa5d
5 changed files with 92 additions and 1 deletions
|
|
@ -612,6 +612,11 @@ func runDaemonStart() error {
|
|||
return
|
||||
}
|
||||
streamSrv.HLS().Register(hsess)
|
||||
// Tell the server seg-0 is on disk as soon as it lands so the
|
||||
// player's SSE subscription flips its "Preparando…" UI without
|
||||
// waiting for the browser HEAD-probe loop to discover it
|
||||
// independently. Cache-HIT sessions are ready immediately.
|
||||
go watchSessionReady(hlsCtx, agentClient, hsess, sess.SessionID)
|
||||
}()
|
||||
}
|
||||
|
||||
|
|
@ -940,3 +945,38 @@ func mirrorCORSOrigins(parent context.Context, cfg config.Config, userAgent stri
|
|||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// watchSessionReady polls HLSSession.ReadyCount until the first segment +
|
||||
// init.mp4 are on disk, then POSTs /api/internal/agent/session-ready so
|
||||
// the web side flips streaming_session.ready_at — which its SSE endpoint
|
||||
// pushes to subscribed players. Cache-HIT sessions are ready the moment
|
||||
// StartHLSSession returns and POST immediately.
|
||||
//
|
||||
// Bounded by a 60 s deadline so a permanently stuck encoder doesn't keep
|
||||
// a goroutine alive forever; if seg-0 never lands the player falls back
|
||||
// to its existing HEAD-probe retry path anyway.
|
||||
func watchSessionReady(ctx context.Context, client *agent.Client, hsess *engine.HLSSession, sessionID string) {
|
||||
deadline := time.Now().Add(60 * time.Second)
|
||||
ticker := time.NewTicker(200 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
// Cache HIT or seg-0 ready → notify + done.
|
||||
if hsess.FromCache() || hsess.ReadyCount() >= 1 {
|
||||
rctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
if err := client.MarkSessionReady(rctx, sessionID); err != nil {
|
||||
log.Printf("[hls %s] mark-ready failed: %v", agent.ShortID(sessionID), err)
|
||||
}
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
log.Printf("[hls %s] mark-ready: timeout waiting for seg-0", agent.ShortID(sessionID))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue