diff --git a/internal/cmd/daemon.go b/internal/cmd/daemon.go index ffb147e..e55e4ba 100644 --- a/internal/cmd/daemon.go +++ b/internal/cmd/daemon.go @@ -684,7 +684,11 @@ func runDaemonStart() error { failSession := func(sessionID, code, message string) { log.Printf("[hls %s] failed (%s): %s", agent.ShortID(sessionID), code, message) go func() { - rctx, cancel := context.WithTimeout(ctx, 10*time.Second) + // Fresh context on purpose: failures cluster exactly when the + // daemon ctx is being cancelled (shutdown kills in-flight + // session starts), and a report derived from it would die + // before reaching the web. The 10s cap still bounds it. + rctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := agentClient.ReportSessionError(rctx, sessionID, code, message); err != nil { log.Printf("[hls %s] session error report failed: %v", agent.ShortID(sessionID), err) @@ -692,6 +696,20 @@ func runDaemonStart() error { }() } + // markReady reports "first bytes are servable" for the no-transcode + // paths (direct-play, remux, debrid direct) — one place instead of a + // copy per branch. HLS sessions report via watchSessionReady instead + // (they wait for seg-0 + attach a health snapshot). + markReady := func(sessionID string) { + go func() { + rctx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + if err := agentClient.MarkSessionReady(rctx, sessionID, nil); err != nil { + log.Printf("[stream %s] mark-ready failed: %v", agent.ShortID(sessionID), err) + } + }() + } + // startHLSPlayback starts an HLS encode (local file or debrid URL) and // wires it into the StreamServer. Shared by the local-file HLS path and // the debrid HLS-from-URL path (hueco #2 / 2b) so both register, probe @@ -743,7 +761,7 @@ func runDaemonStart() error { if err != nil { playerSessionRegistry.remove(hlsCfg.SessionID) hlsCancel() - failSession(hlsCfg.SessionID, "start_failed", err.Error()) + failSession(hlsCfg.SessionID, sessErrStartFailed, err.Error()) return } if prewarm { @@ -783,17 +801,13 @@ func runDaemonStart() error { provider, perr := engine.NewDebridFileProvider(bctx, sess.DirectURL, sess.FileName, sess.FileSize, refresh) if perr != nil { playerSessionRegistry.remove(sess.SessionID) - failSession(sess.SessionID, "start_failed", fmt.Sprintf("debrid provider: %v", perr)) + failSession(sess.SessionID, sessErrStartFailed, fmt.Sprintf("debrid provider: %v", perr)) return } streamSrv.SetFile(provider, sess.TaskID) log.Printf("[stream %s] debrid direct-play: %s (%d bytes)", agent.ShortID(sess.SessionID), provider.FileName(), provider.FileSize()) - rctx, rcancel := context.WithTimeout(ctx, 10*time.Second) - defer rcancel() - if err := agentClient.MarkSessionReady(rctx, sess.SessionID, nil); err != nil { - log.Printf("[stream %s] mark-ready failed: %v", agent.ShortID(sess.SessionID), err) - } + markReady(sess.SessionID) }() return } @@ -806,7 +820,7 @@ func runDaemonStart() error { if sess.DirectURL != "" { // playMethod == "hls" implied (2a returned above) tcRuntime := buildTranscodeRuntime(ctx, cfg) if tcRuntime.FFmpegPath == "" || tcRuntime.FFprobePath == "" { - failSession(sess.SessionID, "ffmpeg_unavailable", "ffmpeg/ffprobe unavailable (debrid HLS)") + failSession(sess.SessionID, sessErrFfmpegMissing, "ffmpeg/ffprobe unavailable (debrid HLS)") return } hlsCtx, hlsCancel := context.WithCancel(ctx) @@ -833,7 +847,7 @@ func runDaemonStart() error { } if sess.FilePath == "" { - failSession(sess.SessionID, "start_failed", "empty file path") + failSession(sess.SessionID, sessErrStartFailed, "empty file path") return } // SAME base-path self-heal + stat-retry + dir resolution as the raw @@ -864,19 +878,13 @@ func runDaemonStart() error { log.Printf("[stream %s] direct-play: %s", agent.ShortID(sess.SessionID), filepath.Base(filePath)) // File is on disk → ready immediately. Tell the web so the player // attaches