fix(stream): hallazgos de la revisión crítica del modo copy
Some checks failed
CI / Test (push) Failing after 2m55s
CI / Build (push) Successful in 1m31s
CI / Build-1 (push) Successful in 1m57s
CI / Build-2 (push) Successful in 1m35s
CI / Build-3 (push) Successful in 1m37s
CI / Build-4 (push) Successful in 1m33s
CI / Build-5 (push) Successful in 1m34s
CI / Lint (push) Failing after 2m29s
CI / Coverage (push) Successful in 2m49s
CI / Vet (push) Successful in 2m0s
Some checks failed
CI / Test (push) Failing after 2m55s
CI / Build (push) Successful in 1m31s
CI / Build-1 (push) Successful in 1m57s
CI / Build-2 (push) Successful in 1m35s
CI / Build-3 (push) Successful in 1m37s
CI / Build-4 (push) Successful in 1m33s
CI / Build-5 (push) Successful in 1m34s
CI / Lint (push) Failing after 2m29s
CI / Coverage (push) Successful in 2m49s
CI / Vet (push) Successful in 2m0s
- log honesto de resume (copy codifica desde 0, no desde StartSec) - inyección EXT-X-START anclada a #EXTM3U con warning si falla - ServeSegment sin tope segmentCount en copy (ffmpeg adelanta al índice) - comentario types.go: gate por HLS_COPY_MIN_VERSION web-side
This commit is contained in:
parent
da6ee9fff5
commit
dc67f0d4ca
2 changed files with 42 additions and 13 deletions
|
|
@ -634,10 +634,11 @@ func StartHLSSession(ctx context.Context, cfg HLSSessionConfig) (*HLSSession, er
|
|||
// today's post-seek behaviour.
|
||||
startIdx := 0
|
||||
if cfg.VideoCopy {
|
||||
// Copy mode always numbers from 0: segment indices don't map to
|
||||
// uniform 2s slots, so a StartSec-derived index would be wrong. The
|
||||
// resume seek itself is handled inside buildHLSCopyArgs via `-ss`
|
||||
// (keyframe-snapped) + `-output_ts_offset`.
|
||||
// Copy mode always starts from 0: segment indices don't map to
|
||||
// uniform 2s slots, so a StartSec-derived index would be wrong.
|
||||
// StartSec is intentionally ignored (see buildHLSCopyArgs); the
|
||||
// player seeks to the resume point via its own startPosition once
|
||||
// the growing playlist reaches that position.
|
||||
} else if cfg.StartSec > 0 && cfg.StartSec < probe.DurationSec {
|
||||
startIdx = segmentIdxForTime(cfg.StartSec)
|
||||
if startIdx > segCount-1 {
|
||||
|
|
@ -704,7 +705,9 @@ func StartHLSSession(ctx context.Context, cfg HLSSessionConfig) (*HLSSession, er
|
|||
}
|
||||
startNote := ""
|
||||
if cfg.VideoCopy && cfg.StartSec > 0 {
|
||||
startNote = fmt.Sprintf(" start=%.0fs", cfg.StartSec)
|
||||
// Copy ignores StartSec on purpose (see buildHLSCopyArgs) — log the
|
||||
// requested resume point honestly so nobody reads "ffmpeg seeked".
|
||||
startNote = fmt.Sprintf(" resume=%.0fs requested (copy encodes from 0)", cfg.StartSec)
|
||||
} else if startIdx > 0 {
|
||||
startNote = fmt.Sprintf(" start=seg-%d@%.0fs", startIdx, segmentStartSec(startIdx))
|
||||
}
|
||||
|
|
@ -1108,14 +1111,22 @@ func (s *HLSSession) pollSegments(ctx context.Context) {
|
|||
}
|
||||
// Last segment is "ready" only when ffmpeg has exited (no successor
|
||||
// can ever appear) or when a later segment exists.
|
||||
if i == s.segmentCount-1 {
|
||||
//
|
||||
// For VideoCopy sessions, segmentCount is the encode-mode estimate
|
||||
// (ceil(dur/2s)) and is always larger than the real segment count
|
||||
// on wide-GOP sources (keyframe-cut → fewer segments). We must
|
||||
// NOT rely solely on `i == s.segmentCount-1` to detect the last
|
||||
// real segment — when exited and no successor exists the current
|
||||
// segment IS the last one, regardless of its index.
|
||||
noSuccessor := func() bool { _, e := os.Stat(next); return e != nil }
|
||||
if i == s.segmentCount-1 || (exited && noSuccessor()) {
|
||||
if !exited {
|
||||
break
|
||||
}
|
||||
highest = i + 1
|
||||
break
|
||||
}
|
||||
if _, err := os.Stat(next); err != nil {
|
||||
if noSuccessor() {
|
||||
break
|
||||
}
|
||||
highest = i + 1
|
||||
|
|
@ -1130,7 +1141,14 @@ func (s *HLSSession) pollSegments(ctx context.Context) {
|
|||
close(ch)
|
||||
}
|
||||
}
|
||||
if exited && highest >= s.segmentCount {
|
||||
// Exit when all expected segments are ready. For encode mode,
|
||||
// segmentCount is exact; for VideoCopy it's an overestimate, but the
|
||||
// `exited && noSuccessor()` branch above always marks the real last
|
||||
// segment, so highest will reach segmentCount only if the source
|
||||
// happens to have exactly that many keyframe segments — or never if
|
||||
// it has fewer. Exit also when exited and highest stopped advancing
|
||||
// (no more segments will ever appear).
|
||||
if exited && (highest >= s.segmentCount || highest == start) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -1219,9 +1237,16 @@ func (s *HLSSession) serveCopyPlaylist(w http.ResponseWriter, r *http.Request) {
|
|||
// harmless once the playlist is final.
|
||||
out := data
|
||||
if !strings.Contains(string(data), "#EXT-X-START") {
|
||||
out = []byte(strings.Replace(string(data),
|
||||
"#EXT-X-VERSION:7\n",
|
||||
"#EXT-X-VERSION:7\n#EXT-X-START:TIME-OFFSET=0,PRECISE=YES\n", 1))
|
||||
// Anchor on #EXTM3U (REQUIRED first line per RFC 8216) instead
|
||||
// of a specific VERSION value, so an ffmpeg that bumps the
|
||||
// playlist version can't silently skip the injection.
|
||||
replaced := strings.Replace(string(data),
|
||||
"#EXTM3U\n",
|
||||
"#EXTM3U\n#EXT-X-START:TIME-OFFSET=0,PRECISE=YES\n", 1)
|
||||
if replaced == string(data) {
|
||||
log.Printf("[hls %s] WARNING: EXT-X-START injection failed (no #EXTM3U header?)", shortHLSID(s.cfg.SessionID))
|
||||
}
|
||||
out = []byte(replaced)
|
||||
}
|
||||
_, _ = w.Write(out)
|
||||
return
|
||||
|
|
@ -1268,7 +1293,11 @@ func (s *HLSSession) ServeInit(w http.ResponseWriter, r *http.Request) {
|
|||
// in real time (~25 minutes wait at 1080p software encode).
|
||||
func (s *HLSSession) ServeSegment(w http.ResponseWriter, r *http.Request, idx int) {
|
||||
s.Touch()
|
||||
if idx < 0 || idx >= s.segmentCount {
|
||||
// segmentCount is exact for the encode mode (uniform 2s slots) but only an
|
||||
// ESTIMATE for copy mode (cuts go at source keyframes): a short-GOP source
|
||||
// can legitimately produce more segments than the estimate, and bounding
|
||||
// would 404 the real tail. Copy trusts ffmpeg's playlist as the authority.
|
||||
if idx < 0 || (!s.cfg.VideoCopy && idx >= s.segmentCount) {
|
||||
http.Error(w, "segment out of range", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue