From f89396ceede728a0d591ddc465dc4285f7c9513e Mon Sep 17 00:00:00 2001 From: Deivid Soto Date: Thu, 11 Jun 2026 00:02:53 +0200 Subject: [PATCH] =?UTF-8?q?fix(stream):=20downmix=20est=C3=A9reo=20en=20el?= =?UTF-8?q?=20audio=20re-encodeado=20del=20modo=20copy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sin -ac 2 una fuente 5.1 (AC3/EAC3) producía AAC de 6 canales del encoder nativo de ffmpeg, que WebKit/Apple HLS rechaza al sniffar el primer segmento: en el access log de Safari se ve master → index → init → seg-0 dos veces y silencio. Era el discriminador exacto del patrón de campo: episodios con AAC estéreo (copy de audio) reproducían en iPhone; todas las películas 5.1 fallaban. Verificado con Safari/macOS via WebDriver-less access log: con -ac 2 la progresión de segmentos avanza con normalidad. Espeja los flags del path de encode (aac 192k 48kHz estéreo). Test smoke ampliado: el re-encode debe llevar -ac 2. --- internal/engine/hls.go | 7 ++++++- internal/engine/hls_copy_smoke_test.go | 6 ++++++ internal/engine/stream_server.go | 8 ++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/internal/engine/hls.go b/internal/engine/hls.go index fc5a297..4cf6576 100644 --- a/internal/engine/hls.go +++ b/internal/engine/hls.go @@ -1968,7 +1968,12 @@ func buildHLSCopyArgs(cfg HLSSessionConfig, probe *StreamProbe, tmpDir string) [ if strings.EqualFold(audioCodec, "aac") { args = append(args, "-c:a", "copy") } else { - args = append(args, "-c:a", "aac", "-b:a", "192k") + // Mirror the encode path exactly: AAC stereo 48k. WITHOUT -ac 2 a 5.1 + // source produces 6-channel ffmpeg-native AAC, which WebKit/Apple HLS + // rejects at the first media segment (observed via Safari access log: + // master → index → init → seg-0 fetched twice, then silence — every + // 5.1 movie failed on iPhone while stereo-AAC episodes played). + args = append(args, "-c:a", "aac", "-b:a", "192k", "-ar", "48000", "-ac", "2") } args = append(args, diff --git a/internal/engine/hls_copy_smoke_test.go b/internal/engine/hls_copy_smoke_test.go index 203dcb3..8bc867c 100644 --- a/internal/engine/hls_copy_smoke_test.go +++ b/internal/engine/hls_copy_smoke_test.go @@ -183,6 +183,12 @@ func TestHLSCopy_H264Ac3TranscodesAudio(t *testing.T) { if !containsSeq(args, "-c:a", "aac") { t.Errorf("expected -c:a aac for AC3 source, args: %v", args) } + // MUST downmix to stereo: 6-channel ffmpeg-native AAC is rejected by + // WebKit/Apple HLS at the first media segment (every 5.1 movie failed on + // iPhone while stereo-AAC sources played — confirmed via Safari access log). + if !containsSeq(args, "-ac", "2") { + t.Errorf("expected -ac 2 (stereo downmix) for re-encoded audio, args: %v", args) + } } func TestHLSCopy_Hevc10Eac3_IncidentShape(t *testing.T) { diff --git a/internal/engine/stream_server.go b/internal/engine/stream_server.go index 43486a9..3478d54 100644 --- a/internal/engine/stream_server.go +++ b/internal/engine/stream_server.go @@ -690,6 +690,14 @@ func (ss *StreamServer) HLSURLsJSON(sessionID string) string { func (ss *StreamServer) hlsHandler(w http.ResponseWriter, r *http.Request) { ss.lastActivity.Store(time.Now().UnixNano()) + // Debug access log (UNARR_HLS_DEBUG=1): which client fetches which HLS + // resource. Off by default — a player polls the playlist every few + // seconds and segments stream constantly, far too chatty for normal logs. + if os.Getenv("UNARR_HLS_DEBUG") == "1" { + host, _, _ := net.SplitHostPort(r.RemoteAddr) + log.Printf("[hls-debug] %s %s from %s UA=%q", r.Method, r.URL.Path, host, r.Header.Get("User-Agent")) + } + if ss.writeCORSHeaders(w, r, "Content-Length, Content-Range, Accept-Ranges") { return }