fix(stream): el modo copy ignora StartSec (offset EVENT rompe iOS nativo)
Un playlist EVENT cuyas entradas empiezan en 0 mientras los fragmentos llevan tfdt desplazado (-ss + -output_ts_offset) es exactamente la forma que el parser HLS nativo de iOS no traga: resume a 368s → error del player y bucle de re-bootstrap de sesión en iPhone (observado 2026-06-10). Copy produce siempre desde 0 con PTS absolutos reales: adelanta a la reproducción a velocidad de I/O, así que el punto de resume aparece en la timeline creciente en segundos y el seek de startPosition del player aterriza con normalidad. Test de resume actualizado: el playlist debe cubrir la timeline completa.
This commit is contained in:
parent
5a92df1e14
commit
9eb3e44153
2 changed files with 15 additions and 15 deletions
|
|
@ -205,7 +205,8 @@ type HLSSessionConfig struct {
|
|||
// file is remuxed at I/O speed, minutes at worst on a weak NAS);
|
||||
// - no HLS cache (re-generating costs no encode — caching would only
|
||||
// burn disk);
|
||||
// - StartSec is passed straight to `-ss` (keyframe-snapped by ffmpeg).
|
||||
// - StartSec is ignored: copy produces from 0 (outruns playback at I/O
|
||||
// speed); an offset EVENT playlist breaks iOS's native HLS parser.
|
||||
// See docs/plans/hls-copy-remux-replacement.md (web repo).
|
||||
VideoCopy bool
|
||||
}
|
||||
|
|
@ -1902,13 +1903,14 @@ func renderVideoPlaylist(durationSec float64, segCount int) string {
|
|||
func buildHLSCopyArgs(cfg HLSSessionConfig, probe *StreamProbe, tmpDir string) []string {
|
||||
args := []string{"-y", "-hide_banner", "-loglevel", "warning", "-stats"}
|
||||
|
||||
// Resume: input-side seek snaps to the keyframe at/before StartSec (demux
|
||||
// seek — instant). -output_ts_offset keeps the fragments' tfdt on the
|
||||
// absolute timeline so the player's clock matches the real position.
|
||||
if cfg.StartSec > 0 && cfg.StartSec < probe.DurationSec {
|
||||
ss := strconv.FormatFloat(cfg.StartSec, 'f', 3, 64)
|
||||
args = append(args, "-ss", ss)
|
||||
}
|
||||
// StartSec is INTENTIONALLY ignored in copy mode: an EVENT playlist whose
|
||||
// entries start at position 0 while the fragments carry an offset tfdt
|
||||
// (-ss + -output_ts_offset) is exactly the shape iOS's native HLS parser
|
||||
// chokes on (observed 2026-06-10: resume at 368s → player error + session
|
||||
// re-bootstrap loop on iPhone). Copy always produces from 0 with true
|
||||
// absolute PTS — it outruns playback at I/O speed, so the resume point
|
||||
// appears in the growing timeline within seconds and the player's own
|
||||
// startPosition seek lands normally.
|
||||
|
||||
if cfg.SourceURL != "" {
|
||||
args = append(args,
|
||||
|
|
@ -1919,9 +1921,6 @@ func buildHLSCopyArgs(cfg HLSSessionConfig, probe *StreamProbe, tmpDir string) [
|
|||
)
|
||||
}
|
||||
args = append(args, "-i", cfg.sourceRef())
|
||||
if cfg.StartSec > 0 && cfg.StartSec < probe.DurationSec {
|
||||
args = append(args, "-output_ts_offset", strconv.FormatFloat(cfg.StartSec, 'f', 3, 64))
|
||||
}
|
||||
|
||||
// Map video + selected audio (same clamping rules as the encode path).
|
||||
args = append(args, "-map", "0:v:0")
|
||||
|
|
|
|||
|
|
@ -205,8 +205,9 @@ func TestHLSCopy_ResumeStartSec(t *testing.T) {
|
|||
[]string{"-c:v", "libx264", "-preset", "ultrafast", "-pix_fmt", "yuv420p"},
|
||||
[]string{"-c:a", "aac", "-b:a", "128k"}, 12)
|
||||
_, pl := runCopySession(t, rt, src, 6)
|
||||
// Resume covers roughly the back half (keyframe-snapped, so allow the
|
||||
// full GOP of slack: 60 frames @30fps = 2s).
|
||||
// StartSec must be IGNORED in copy mode: the playlist covers the FULL
|
||||
// timeline from 0 (an offset EVENT playlist breaks iOS's native parser;
|
||||
// the player seeks to the resume point itself). Sum ≈ full 12s.
|
||||
var sum float64
|
||||
for _, line := range strings.Split(pl, "\n") {
|
||||
if strings.HasPrefix(line, "#EXTINF:") {
|
||||
|
|
@ -215,8 +216,8 @@ func TestHLSCopy_ResumeStartSec(t *testing.T) {
|
|||
sum += d
|
||||
}
|
||||
}
|
||||
if sum < 4 || sum > 9 {
|
||||
t.Errorf("resume EXTINF sum = %.2fs, want ≈6s (12s source, -ss 6, ±GOP)", sum)
|
||||
if sum < 10.5 || sum > 13.5 {
|
||||
t.Errorf("copy EXTINF sum = %.2fs, want ≈12s (StartSec ignored, full timeline)", sum)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue