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)) }) }) })