{"record":{"id":"764fb4475e8e853a","repo":"bilibili/flv.js","slug":"fetch-stream-meet-early-eof","errorCode":null,"errorMessage":"Fetch stream meet Early-EOF","messagePattern":"Fetch stream meet Early-EOF","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/io/fetch-stream-loader.js","lineNumber":199,"sourceCode":"                    this._abortController.abort();\n                } catch (e) {}\n            }\n        }\n    }\n\n    _pump(reader) {  // ReadableStreamReader\n        return reader.read().then((result) => {\n            if (result.done) {\n                // First check received length\n                if (this._contentLength !== null && this._receivedLength < this._contentLength) {\n                    // Report Early-EOF\n                    this._status = LoaderStatus.kError;\n                    let type = LoaderErrors.EARLY_EOF;\n                    let info = {code: -1, msg: 'Fetch stream meet Early-EOF'};\n                    if (this._onError) {\n                        this._onError(type, info);\n                    } else {\n                        throw new RuntimeException(info.msg);\n                    }\n                } else {\n                    // OK. Download complete\n                    this._status = LoaderStatus.kComplete;\n                    if (this._onComplete) {\n                        this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);\n                    }\n                }\n            } else {\n                if (this._abortController && this._abortController.signal.aborted) {\n                    this._status = LoaderStatus.kComplete;\n                    return;\n                } else if (this._requestAbort === true) {\n                    this._status = LoaderStatus.kComplete;\n                    return reader.cancel();\n                }\n\n                this._status = LoaderStatus.kBuffering;","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/bilibili/flv.js/blob/42343088f22619cf014a057b3878fe3d320016a6/src/io/fetch-stream-loader.js#L181-L217","documentation":"FetchStreamLoader's internal _pump loop reads the fetch body via reader.read(); when the stream ends before the expected content-length has been received, it treats it as a premature connection termination. The loader sets status to kError and fires _onError with LoaderErrors.EARLY_EOF (code -1), or throws a RuntimeException if no error callback was registered. This means the server (or a proxy) closed the connection mid-download.","triggerScenarios":"Calling create() on a FetchStreamLoader and, during streaming, the response body ReadableStream ends while _receivedLength < _contentLength; typically response.headers content-length was advertised but the connection dropped before that many bytes arrived. Also raised when res.body is null/unreadable mid-pump or the reader's done flag arrives early.","commonSituations":"Unstable mobile networks, proxy/CDN idle timeouts (nginx, Cloudflare) cutting long streaming responses, server crashes or restarts mid-transfer, load balancers killing slow connections, or servers that send an incorrect Content-Length header.","solutions":["Add an onError handler to the loader (or listen for the player's LOAD_ERROR/NetworkError) and implement a retry that re-opens the stream from the last received byte offset using a Range request","Verify no intermediary (proxy/CDN/load balancer) has an idle/timeout limit shorter than the download duration and raise it","Confirm the server sends a correct Content-Length or use chunked transfer encoding consistently","Switch loader type (e.g. to RangeLoader/XHR-based loader) via config for flaky networks"],"exampleFix":"// before\nloader.create(url, { from: 0, to: -1 }); // no error handling; throws RuntimeException\n// after\nloader._onError = (type, info) => {\n  if (type === LoaderErrors.EARLY_EOF) {\n    // resume from last received offset with a Range request\n    loader.create(url, { from: loader._receivedLength, to: -1 });\n  }\n};\nloader.create(url, { from: 0, to: -1 });","handlingStrategy":"retry","validationCode":"const res = await fetch(url);\nif (!res.ok || !res.body) throw new Error('bad response');\nconst len = Number(res.headers.get('content-length'));\nif (Number.isNaN(len)) console.warn('no content-length; EOF detection limited');","typeGuard":null,"tryCatchPattern":"loader._onError = (type, info) => {\n  if (type === LoaderErrors.EARLY_EOF) {\n    setTimeout(() => loader.create(url, { from: lastReceivedOffset, to: -1 }), backoffMs);\n  }\n};","preventionTips":["Always register an onError callback so EARLY_EOF becomes recoverable","Track received byte offset so retries can resume via Range requests","Raise proxy/CDN idle timeouts for long media transfers","Monitor server Content-Length correctness"],"tags":["network","fetch","streaming","early-eof"],"backgroundTag":"early-eof","analyzedSha":"42343088f22619cf014a057b3878fe3d320016a6","analyzedAt":"2026-09-01T00:27:28.147Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}