fix(stream): iOS exige total concreto en el Content-Range del remux
iOS/WebKit abre todo <video src> con una sonda "bytes=0-1" y se niega a reproducir si el 206 no trae una longitud concreta en Content-Range — "/*" (total desconocido, el fix anterior del loop de re-seek) le hacía abortar y re-bootstrapear la sesión sin parar. Vuelve a anunciar siempre un total numérico (exacto si ffmpeg terminó, el estimado mientras crece). El loop de re-seek real no era el total anunciado sino el init segment malformado, ya arreglado con +delay_moov en buildFFmpegArgs. Test nuevo: la sonda 0-1 debe llevar total concreto.
This commit is contained in:
parent
b3487a22e8
commit
3fcfaaf234
2 changed files with 45 additions and 54 deletions
|
|
@ -135,12 +135,13 @@ func TestServeGrowing_BoundedRange(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestServeGrowing_UnknownTotalWhileNotFinal(t *testing.T) {
|
||||
// Not final: only 8 bytes produced, estimate says 100. The instance length
|
||||
// is genuinely unknown while the remux grows, so we advertise "/*" (RFC 7233
|
||||
// §4.2) instead of a total the native player would map its timeline onto and
|
||||
// re-seek against (the playback loop). The estimate is only an upper-bound
|
||||
// hint for `end`; body is what exists so far.
|
||||
func TestServeGrowing_EstimateUsedWhileNotFinal(t *testing.T) {
|
||||
// Not final: only 8 bytes produced, estimate says 100. We advertise the
|
||||
// estimate as the total — iOS/WebKit refuses to play a <video src> whose
|
||||
// "bytes=0-1" probe comes back without a concrete instance length, so "/*"
|
||||
// (unknown total) is not an option. The estimate need not be byte-exact;
|
||||
// the real re-seek loop was the malformed init segment (fixed by
|
||||
// +delay_moov), not the advertised total. Body is what exists so far.
|
||||
src := &fakeGrowing{data: []byte("01234567"), final: false, est: 100}
|
||||
ss := &StreamServer{}
|
||||
|
||||
|
|
@ -152,8 +153,8 @@ func TestServeGrowing_UnknownTotalWhileNotFinal(t *testing.T) {
|
|||
if res.StatusCode != http.StatusPartialContent {
|
||||
t.Fatalf("status = %d, want 206", res.StatusCode)
|
||||
}
|
||||
if got := res.Header.Get("Content-Range"); got != "bytes 0-99/*" {
|
||||
t.Errorf("Content-Range = %q, want bytes 0-99/* (unknown total)", got)
|
||||
if got := res.Header.Get("Content-Range"); got != "bytes 0-99/100" {
|
||||
t.Errorf("Content-Range = %q, want bytes 0-99/100 (estimate)", got)
|
||||
}
|
||||
// Not final → no exact Content-Length (chunked) so we never promise bytes
|
||||
// a still-running remux might not produce.
|
||||
|
|
@ -166,8 +167,8 @@ func TestServeGrowing_UnknownTotalWhileNotFinal(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestServeGrowing_HeadProbe(t *testing.T) {
|
||||
// HEAD while growing: total is unknown, so no Content-Length is promised
|
||||
// (advertising the estimate is the bug this fix removes).
|
||||
// HEAD: advertise the total (estimate while growing) so iOS gets the size
|
||||
// it needs from its probe.
|
||||
src := &fakeGrowing{data: make([]byte, 0), final: false, est: 4242}
|
||||
ss := &StreamServer{}
|
||||
|
||||
|
|
@ -179,29 +180,30 @@ func TestServeGrowing_HeadProbe(t *testing.T) {
|
|||
if res.StatusCode != http.StatusOK {
|
||||
t.Fatalf("HEAD status = %d, want 200", res.StatusCode)
|
||||
}
|
||||
if got := res.Header.Get("Content-Length"); got != "" {
|
||||
t.Errorf("HEAD Content-Length = %q, want empty (unknown total while growing)", got)
|
||||
if got := res.Header.Get("Content-Length"); got != "4242" {
|
||||
t.Errorf("HEAD Content-Length = %q, want 4242", got)
|
||||
}
|
||||
if rec.Body.Len() != 0 {
|
||||
t.Errorf("HEAD body = %d bytes, want 0", rec.Body.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestServeGrowing_HeadProbeFinal(t *testing.T) {
|
||||
// HEAD once final: the true total IS known, so advertise it.
|
||||
src := &fakeGrowing{data: make([]byte, 4242), final: true}
|
||||
func TestServeGrowing_ProbeRangeCarriesTotal(t *testing.T) {
|
||||
// The iOS "bytes=0-1" probe MUST come back with a concrete instance length
|
||||
// (bytes 0-1/<total>), or WebKit bails and re-bootstraps the session.
|
||||
src := &fakeGrowing{data: []byte("0123456789"), final: false, est: 6685677633}
|
||||
ss := &StreamServer{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodHead, "/stream", nil)
|
||||
req := httptest.NewRequest(http.MethodGet, "/stream", nil)
|
||||
req.Header.Set("Range", "bytes=0-1")
|
||||
rec := httptest.NewRecorder()
|
||||
ss.serveGrowing(rec, req, src)
|
||||
|
||||
res := rec.Result()
|
||||
if res.StatusCode != http.StatusOK {
|
||||
t.Fatalf("HEAD status = %d, want 200", res.StatusCode)
|
||||
if got := rec.Result().Header.Get("Content-Range"); got != "bytes 0-1/6685677633" {
|
||||
t.Errorf("Content-Range = %q, want bytes 0-1/6685677633 (concrete total for iOS)", got)
|
||||
}
|
||||
if got := res.Header.Get("Content-Length"); got != "4242" {
|
||||
t.Errorf("HEAD Content-Length = %q, want 4242 (final size known)", got)
|
||||
if body := rec.Body.String(); body != "01" {
|
||||
t.Errorf("body = %q, want 01 (the 2 probed bytes)", body)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue