From 6c756a256908c8a72bef4b2bf3f82eb5830375f7 Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Wed, 10 Jun 2026 23:51:14 +0200 Subject: [PATCH] fix(stream): EXT-X-START=0 en el playlist copy mientras crece MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hasta que llega ENDLIST la sesión copy es un EVENT creciente y algunos players nativos (iOS) tratan un playlist sin terminar como LIVE: se enganchan al borde en vez de a la posición 0. EXT-X-START:TIME-OFFSET=0 (RFC 8216 §4.3.5.2) fija el arranque explícitamente; inofensivo cuando el playlist ya es final. Coincide con el patrón observado: episodios cortos (ENDLIST en segundos) reproducían en iPhone, películas (EVENT durante minutos) no. --- internal/engine/hls.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/internal/engine/hls.go b/internal/engine/hls.go index cf9c506..fc5a297 100644 --- a/internal/engine/hls.go +++ b/internal/engine/hls.go @@ -1212,7 +1212,18 @@ func (s *HLSSession) serveCopyPlaylist(w http.ResponseWriter, r *http.Request) { for { data, err := os.ReadFile(path) if err == nil && len(data) > 0 { - _, _ = w.Write(data) + // Until ENDLIST lands a copy session is a growing EVENT playlist, + // and some native players (iOS) treat any not-yet-ended playlist + // like LIVE and join at the live edge instead of position 0. + // EXT-X-START pins the start to 0 explicitly (RFC 8216 §4.3.5.2); + // 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)) + } + _, _ = w.Write(out) return } if r.Context().Err() != nil || time.Now().After(deadline) {