feat(stream): progressive fMP4 remux source for /stream (hueco #3 / 3b-i)
Agent side of 3b: serve a growing ffmpeg `-c copy` remux (mkv h264/aac → fragmented MP4) over /stream with no video re-encode. Dormant until the web sends PlayMethod="remux" (3b-ii), so this commit changes no live behavior. - GrowingSource interface + transcodeSource already satisfies it; estimate is the source file size for copy actions (≈ remux output) vs bitrate×duration for real transcodes. - NewRemuxSource: ffmpeg -c copy → growing fMP4 temp, returned as GrowingSource. - StreamServer.SetGrowingFile + serveGrowing: manual Range responder for a growing source (http.ServeContent needs a fixed size). 206 with an estimated total in Content-Range; chunked body while not final (never promise bytes a running remux might not produce); exact Content-Length once final. Blocks via ReadAt for not-yet-produced bytes; forward seek waits, backward seek instant. - daemon OnStreamSession: PlayMethod=="remux" → NewRemuxSource + SetGrowingFile + MarkSessionReady (after the ffmpeg check; copy still needs ffmpeg). - Tests: parseByteRange + serveGrowing (full/offset/bounded/estimate/HEAD/416).
This commit is contained in:
parent
6e8bca2ac4
commit
4a12f13b96
4 changed files with 450 additions and 6 deletions
|
|
@ -125,7 +125,20 @@ func newTranscodeSource(
|
|||
return nil, err
|
||||
}
|
||||
|
||||
estimate := estimateOutputSize(probe, opts)
|
||||
// Size estimate for the scrubber timeline. A copy remux (video not
|
||||
// re-encoded) lands within container overhead of the source file, so the
|
||||
// source size is a far better estimate than bitrate×duration — use it.
|
||||
// A real transcode re-encodes, so fall back to the bitrate×duration model.
|
||||
var estimate int64
|
||||
switch action {
|
||||
case ActionPassthrough, ActionRemux, ActionRemuxAudio:
|
||||
if fi, statErr := os.Stat(srcPath); statErr == nil {
|
||||
estimate = fi.Size()
|
||||
}
|
||||
}
|
||||
if estimate <= 0 {
|
||||
estimate = estimateOutputSize(probe, opts)
|
||||
}
|
||||
|
||||
t := &transcodeSource{
|
||||
tmpPath: tmpPath,
|
||||
|
|
@ -151,6 +164,18 @@ func newTranscodeSource(
|
|||
return t, nil
|
||||
}
|
||||
|
||||
// NewRemuxSource starts an ffmpeg `-c copy` remux of srcPath into a growing
|
||||
// fragmented-MP4 temp file and returns it as a GrowingSource for /stream
|
||||
// (hueco #3 / 3b). The video + audio are copied (never re-encoded), so this is
|
||||
// only valid when the codecs are already browser-native (h264 + aac) and only
|
||||
// the container needs changing — the web's decidePlayMethod enforces that
|
||||
// before sending PlayMethod="remux". The browser plays the result progressively
|
||||
// via byte-range. Caller MUST Close() it (kills ffmpeg + removes the temp file).
|
||||
func NewRemuxSource(ctx context.Context, srcPath string, probe *StreamProbe, ffmpegPath, displayName string) (GrowingSource, error) {
|
||||
opts := TranscodeOpts{Action: ActionRemux, FFmpegPath: ffmpegPath}
|
||||
return newTranscodeSource(ctx, srcPath, probe, ActionRemux, opts, displayName)
|
||||
}
|
||||
|
||||
// signalNotify wakes any goroutine blocked in ReadAt. Non-blocking: if a
|
||||
// notification is already pending the new event is folded into it (callers
|
||||
// always re-check size + final after waking, so a coalesced signal still
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue