diff --git a/internal/agent/types.go b/internal/agent/types.go index d45b1b3..3a401f3 100644 --- a/internal/agent/types.go +++ b/internal/agent/types.go @@ -410,6 +410,13 @@ type StreamSession struct { // AudioIndex selects the source audio track (-map 0:a:N). -1 means // "use the default/first track". AudioIndex int `json:"audioIndex,omitempty"` + // PlayMethod is how the daemon should serve this session: + // "" — default (HLS transcode); also what legacy servers send. + // "direct" — the source is already browser-native (the web decided this + // from library scan metadata + an agent-version gate). Serve + // the raw file over /stream (HTTP Range, no ffmpeg) instead of + // transcoding to HLS. See hueco #3 phase 3a in the roadmap. + PlayMethod string `json:"playMethod,omitempty"` } // SyncResponse is returned by the server with all pending actions for the CLI. diff --git a/internal/cmd/daemon.go b/internal/cmd/daemon.go index a373d7b..11ccf0c 100644 --- a/internal/cmd/daemon.go +++ b/internal/cmd/daemon.go @@ -589,6 +589,30 @@ func runDaemonStart() error { filePath = found } + // Direct-play (hueco #3 / 3a): the web decided this source is already + // browser-native (mp4 h264/aac 8-bit SDR) from library scan metadata, + // gated on agent version. Serve the raw file over /stream (HTTP Range, + // no ffmpeg) instead of transcoding to HLS — zero CPU, instant seek. + // Runs BEFORE the ffmpeg-availability check on purpose: direct-play + // needs no ffmpeg, so it must work even when transcode is disabled. + if sess.PlayMethod == "direct" { + streamSrv.SetFile(engine.NewDiskFileProvider(filePath), sess.TaskID) + // cancel just clears the served file so daemon shutdown / drain + // stops exposing it on /stream. There's no ffmpeg child to kill. + playerSessionRegistry.add(sess.SessionID, func() { streamSrv.ClearFile() }) + 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