feat(streaming): add HLS transport pipeline (daemon side)

Introduces an HLS-over-HTTP path as Plan B for in-browser streaming. The
WebRTC + MSE pipeline keeps working untouched; the new path is selected
when the backend sets transport="hls" on a streaming session.

Daemon scope:
- engine/hls.go: HLSSession + HLSSessionRegistry. Spawns ffmpeg with
  -f hls -hls_segment_type fmp4 + force_key_frames aligned with 4 s
  segments. Pre-renders master + media playlists from the probe duration
  so the browser knows the total timeline before any segment exists,
  fixing seek/duration/pause/multi-track issues seen with the live fMP4
  pipe.
- engine/probe.go: enumerate every audio + subtitle track instead of
  collapsing to a single default audio track.
- engine/stream_server.go: route /hls/<id>/{master.m3u8,video/...,
  subs/...} to the matching session. Emit a synthesised single-VTT
  subtitle playlist per text track; bitmap subs (PGS/DVB) skip silently.
- cmd/daemon.go: branch on WebRTCSession.Transport == "hls" to register
  an HLS session instead of running the legacy DataChannel pump.
- agent/types.go: WebRTCSession.Transport + AudioIndex fields.

Backend + web sides land in a follow-up commit.
This commit is contained in:
Deivid Soto 2026-05-07 16:10:22 +02:00
parent 81abc4acca
commit 0fc0e1c21a
5 changed files with 1032 additions and 5 deletions

View file

@ -351,12 +351,19 @@ type LibraryDeleteRequest struct {
FilePath string `json:"filePath"`
}
// WebRTCSession is a request to open a custom WebRTC DataChannel byte-stream
// to a browser player. The CLI must POST an SDP answer to
// /api/internal/stream/signal/<sessionId> and serve bytes from FilePath
// (or, when only InfoHash is set, from a download_task on disk).
// WebRTCSession is a request to open a streaming session for a browser
// player. Transport selects the on-the-wire protocol: empty/"webrtc" runs the
// legacy custom WebRTC DataChannel pipeline; "hls" spawns an HLS session
// (ffmpeg producing fragmented MP4 served over HTTP). The CLI must POST an
// SDP answer to /api/internal/stream/signal/<sessionId> for WebRTC sessions
// and register the HLS session in the StreamServer's HLS registry for HLS
// sessions; either way the source bytes come from FilePath (or, when only
// InfoHash is set, from a download_task on disk).
type WebRTCSession struct {
SessionID string `json:"sessionId"`
// Transport selects the streaming protocol. "" or "webrtc" → legacy
// WebRTC + MSE pipeline (Phase 1). "hls" → HLS over HTTP (Phase 2).
Transport string `json:"transport,omitempty"`
FilePath string `json:"filePath,omitempty"`
InfoHash string `json:"infoHash,omitempty"`
TaskID string `json:"taskId,omitempty"`
@ -365,6 +372,9 @@ type WebRTCSession struct {
// Quality target the daemon should aim for when transcoding. One of
// "2160p" | "1080p" | "720p" | "480p" | "original" | "" (defer to config).
Quality string `json:"quality,omitempty"`
// AudioIndex selects the source audio track (-map 0:a:N). -1 means
// "use the default/first track" (HLS) or ignored (WebRTC).
AudioIndex int `json:"audioIndex,omitempty"`
}
// SyncResponse is returned by the server with all pending actions for the CLI.