feat(stream): direct-play passthrough for browser-native files
Hueco #3 / 3a (CLI side). StreamSession gains PlayMethod; when the web sends "direct", the daemon serves the raw file over /stream (HTTP Range, no ffmpeg) instead of transcoding to HLS — zero CPU, instant seek. Runs before the ffmpeg-availability check so direct-play works even with transcode disabled. Legacy/empty PlayMethod keeps the HLS path, so an old web that never sends "direct" is unaffected.
This commit is contained in:
parent
3592b9f95a
commit
c8d7c4bba5
2 changed files with 31 additions and 0 deletions
|
|
@ -410,6 +410,13 @@ type StreamSession struct {
|
||||||
// AudioIndex selects the source audio track (-map 0:a:N). -1 means
|
// AudioIndex selects the source audio track (-map 0:a:N). -1 means
|
||||||
// "use the default/first track".
|
// "use the default/first track".
|
||||||
AudioIndex int `json:"audioIndex,omitempty"`
|
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.
|
// SyncResponse is returned by the server with all pending actions for the CLI.
|
||||||
|
|
|
||||||
|
|
@ -589,6 +589,30 @@ func runDaemonStart() error {
|
||||||
filePath = found
|
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 <video src> without burning its HEAD-probe retry budget.
|
||||||
|
go func() {
|
||||||
|
rctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := agentClient.MarkSessionReady(rctx, sess.SessionID); err != nil {
|
||||||
|
log.Printf("[stream %s] mark-ready failed: %v", agent.ShortID(sess.SessionID), err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
tcRuntime := buildTranscodeRuntime(ctx, cfg)
|
tcRuntime := buildTranscodeRuntime(ctx, cfg)
|
||||||
if tcRuntime.FFmpegPath == "" || tcRuntime.FFprobePath == "" {
|
if tcRuntime.FFmpegPath == "" || tcRuntime.FFprobePath == "" {
|
||||||
log.Printf("[hls %s] rejected: ffmpeg/ffprobe unavailable", agent.ShortID(sess.SessionID))
|
log.Printf("[hls %s] rejected: ffmpeg/ffprobe unavailable", agent.ShortID(sess.SessionID))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue