From 79fc6fed6039bfc6763f6225c1b66ac8247f3df0 Mon Sep 17 00:00:00 2001 From: Deluan Date: Thu, 24 Sep 2026 17:05:05 -0400 Subject: [PATCH] fix(stream): keep the headroom in the estimated content length 5686c352c changed the estimate from bitRate / 8 * 1024 to * 1000, reading the 1024 as a unit mistake. It was headroom: the body also carries the ID3 tag, the Xing frame and frame padding, so a nominal-size estimate is short, and net/http aborts a response that writes past its declared Content-Length. Measured on a live server, a 429s track at 64kbps was estimated at 3,439,840 bytes against a 3,441,569-byte body, and the stream was cut with 'http: wrote more than the declared Content-Length'. Restores the 1024, says why next to it, and turns the test into one that asserts the headroom rather than a formula. --- core/stream/media_streamer.go | 5 ++++- core/stream/media_streamer_test.go | 7 +++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/core/stream/media_streamer.go b/core/stream/media_streamer.go index 01b67030a..3efd67a81 100644 --- a/core/stream/media_streamer.go +++ b/core/stream/media_streamer.go @@ -146,8 +146,11 @@ func (s *Stream) Duration() float32 { return s.mf.Duration } func (s *Stream) ContentType() string { return mime.TypeByExtension("." + s.format) } func (s *Stream) Name() string { return s.mf.Title + "." + s.format } func (s *Stream) ModTime() time.Time { return s.mf.UpdatedAt } + +// EstimatedContentLength deliberately overshoots by 2.4%: the body also carries tags and +// padding, and net/http aborts a response that writes past its declared length. func (s *Stream) EstimatedContentLength() int { - return int(s.mf.Duration * float32(s.bitRate) / 8 * 1000) + return int(s.mf.Duration * float32(s.bitRate) / 8 * 1024) } // Serve writes the stream to the HTTP response. For seekable streams it uses http.ServeContent diff --git a/core/stream/media_streamer_test.go b/core/stream/media_streamer_test.go index e74fb10d4..d0b694613 100644 --- a/core/stream/media_streamer_test.go +++ b/core/stream/media_streamer_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "os" + "strconv" "testing/iotest" "time" @@ -184,7 +185,7 @@ var _ = Describe("MediaStreamer", func() { Expect(err).To(HaveOccurred()) }) - It("estimates the content length from a bitrate in kilobits of 1000 bits", func() { + It("estimates a content length above the nominal size, so the body never outruns it", func() { hundredSeconds := *mf hundredSeconds.Duration = 100 s := stream.NewStream(&hundredSeconds, "mp3", 128, io.NopCloser(bytes.NewReader(nil))) @@ -193,7 +194,9 @@ var _ = Describe("MediaStreamer", func() { _, _ = s.Serve(ctx, w, r) - Expect(w.Header().Get("Content-Length")).To(Equal("1600000")) + length, err := strconv.Atoi(w.Header().Get("Content-Length")) + Expect(err).ToNot(HaveOccurred()) + Expect(length).To(BeNumerically(">", 100*128*1000/8)) }) }) })