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:
parent
81abc4acca
commit
0fc0e1c21a
5 changed files with 1032 additions and 5 deletions
|
|
@ -15,6 +15,7 @@ type StreamProbe struct {
|
|||
// VideoCodec lowercased — e.g. "h264", "hevc", "av1", "vp9", "mpeg4".
|
||||
VideoCodec string
|
||||
// AudioCodec lowercased — e.g. "aac", "ac3", "dts", "eac3", "opus".
|
||||
// Reflects the default/first audio track for legacy single-track callers.
|
||||
AudioCodec string
|
||||
// Width / Height of the primary video stream.
|
||||
Width int
|
||||
|
|
@ -27,6 +28,43 @@ type StreamProbe struct {
|
|||
DurationSec float64
|
||||
// Container is the file extension lowercased (".mp4", ".mkv", ".avi").
|
||||
Container string
|
||||
// AudioTracks lists every audio stream in source order. Index in this
|
||||
// slice == ffmpeg `-map 0:a:N` index (where N starts at 0).
|
||||
AudioTracks []ProbeAudioTrack
|
||||
// SubtitleTracks lists every subtitle stream in source order. Index in
|
||||
// this slice == ffmpeg `-map 0:s:N` index.
|
||||
SubtitleTracks []ProbeSubtitleTrack
|
||||
}
|
||||
|
||||
// ProbeAudioTrack is a slimmed AudioTrack view tied to ffmpeg stream index.
|
||||
type ProbeAudioTrack struct {
|
||||
Index int // 0-based audio stream index (ffmpeg -map 0:a:Index)
|
||||
Lang string // ISO 639-1
|
||||
Codec string // lowercased
|
||||
Channels int
|
||||
Title string
|
||||
Default bool
|
||||
}
|
||||
|
||||
// ProbeSubtitleTrack is a slimmed SubtitleTrack view tied to ffmpeg stream index.
|
||||
// Codec discriminates text (srt/ass/webvtt → extract to WebVTT) vs bitmap
|
||||
// (pgs/dvbsub → require burn-in).
|
||||
type ProbeSubtitleTrack struct {
|
||||
Index int // 0-based subtitle stream index (ffmpeg -map 0:s:Index)
|
||||
Lang string // ISO 639-1
|
||||
Codec string // lowercased — "subrip", "ass", "webvtt", "hdmv_pgs_subtitle", ...
|
||||
Title string
|
||||
Forced bool
|
||||
}
|
||||
|
||||
// IsTextSubtitle reports whether a subtitle codec can be extracted to WebVTT
|
||||
// without re-rendering. Bitmap subs (PGS, DVB) need burn-in.
|
||||
func (s ProbeSubtitleTrack) IsTextSubtitle() bool {
|
||||
switch s.Codec {
|
||||
case "subrip", "srt", "ass", "ssa", "webvtt", "mov_text":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TranscodeAction tells the streaming pipeline how to feed the file to
|
||||
|
|
@ -74,6 +112,29 @@ func ProbeFile(ctx context.Context, ffprobePath, filePath string) (*StreamProbe,
|
|||
}
|
||||
}
|
||||
probe.AudioCodec = strings.ToLower(picked.Codec)
|
||||
probe.AudioTracks = make([]ProbeAudioTrack, 0, len(mi.Audio))
|
||||
for i, a := range mi.Audio {
|
||||
probe.AudioTracks = append(probe.AudioTracks, ProbeAudioTrack{
|
||||
Index: i,
|
||||
Lang: a.Lang,
|
||||
Codec: strings.ToLower(a.Codec),
|
||||
Channels: a.Channels,
|
||||
Title: a.Title,
|
||||
Default: a.Default,
|
||||
})
|
||||
}
|
||||
}
|
||||
if len(mi.Subtitles) > 0 {
|
||||
probe.SubtitleTracks = make([]ProbeSubtitleTrack, 0, len(mi.Subtitles))
|
||||
for i, s := range mi.Subtitles {
|
||||
probe.SubtitleTracks = append(probe.SubtitleTracks, ProbeSubtitleTrack{
|
||||
Index: i,
|
||||
Lang: s.Lang,
|
||||
Codec: strings.ToLower(s.Codec),
|
||||
Title: s.Title,
|
||||
Forced: s.Forced,
|
||||
})
|
||||
}
|
||||
}
|
||||
return probe, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue