Skip to content

Commit b96d9fb

Browse files
committed
Switch on FRAG_PARSING_ERROR or continue trying fragLoadPolicy.default.errorRetry.maxNumRetry (6) times 2/2
Fixes #5011
1 parent 11f9747 commit b96d9fb

14 files changed

+124
-139
lines changed

api-extractor/report/hls.js.api.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ export class AbrController implements AbrComponentAPI {
3333
get nextAutoLevel(): number;
3434
set nextAutoLevel(nextLevel: number);
3535
// (undocumented)
36-
protected onError(event: Events.ERROR, data: ErrorData): void;
37-
// (undocumented)
3836
protected onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData): void;
3937
// (undocumented)
4038
protected onFragLoaded(event: Events.FRAG_LOADED, { frag, part }: FragLoadedData): void;
@@ -2380,9 +2378,13 @@ export type LoaderOnTimeout<T extends LoaderContext> = (stats: LoaderStats, cont
23802378
//
23812379
// @public (undocumented)
23822380
export interface LoaderResponse {
2381+
// (undocumented)
2382+
code?: number;
23832383
// (undocumented)
23842384
data: string | ArrayBuffer | Object;
23852385
// (undocumented)
2386+
text?: string;
2387+
// (undocumented)
23862388
url: string;
23872389
}
23882390

src/controller/abr-controller.ts

-22
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ class AbrController implements AbrComponentAPI {
4747
hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
4848
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
4949
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
50-
hls.on(Events.ERROR, this.onError, this);
5150
}
5251

5352
protected unregisterListeners() {
@@ -56,7 +55,6 @@ class AbrController implements AbrComponentAPI {
5655
hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
5756
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
5857
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
59-
hls.off(Events.ERROR, this.onError, this);
6058
}
6159

6260
public destroy() {
@@ -309,26 +307,6 @@ class AbrController implements AbrComponentAPI {
309307
}
310308
}
311309

312-
protected onError(event: Events.ERROR, data: ErrorData) {
313-
// stop timer in case of frag loading error
314-
if (data.frag?.type === PlaylistLevelType.MAIN) {
315-
if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
316-
this.clearTimer();
317-
return;
318-
}
319-
switch (data.details) {
320-
case ErrorDetails.FRAG_LOAD_ERROR:
321-
case ErrorDetails.FRAG_LOAD_TIMEOUT:
322-
case ErrorDetails.KEY_LOAD_ERROR:
323-
case ErrorDetails.KEY_LOAD_TIMEOUT:
324-
this.clearTimer();
325-
break;
326-
default:
327-
break;
328-
}
329-
}
330-
}
331-
332310
private ignoreFragment(frag: Fragment): boolean {
333311
// Only count non-alt-audio frags which were actually buffered in our BW calculations
334312
return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment';

src/controller/audio-stream-controller.ts

+1
Original file line numberDiff line numberDiff line change
@@ -644,6 +644,7 @@ class AudioStreamController
644644
}
645645
switch (data.details) {
646646
case ErrorDetails.FRAG_PARSING_ERROR:
647+
case ErrorDetails.FRAG_DECRYPT_ERROR:
647648
case ErrorDetails.FRAG_LOAD_ERROR:
648649
case ErrorDetails.FRAG_LOAD_TIMEOUT:
649650
case ErrorDetails.KEY_LOAD_ERROR:

src/controller/base-playlist-controller.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,13 @@ export default class BasePlaylistController implements NetworkComponentAPI {
296296
errorDetails === ErrorDetails.LEVEL_LOAD_TIMEOUT ||
297297
errorDetails === ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT ||
298298
errorDetails === ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT;
299+
const httpStatus = errorEvent.response?.code;
299300
const retryConfig =
300301
playlistLoadPolicy.default[`${isTimeout ? 'timeout' : 'error'}Retry`];
301-
const retry = !!retryConfig && this.retryCount < retryConfig.maxNumRetry;
302-
// TODO: Don't try on bad network status
302+
const retry =
303+
!!retryConfig &&
304+
this.retryCount < retryConfig.maxNumRetry &&
305+
httpStatus !== 0;
303306
if (retry) {
304307
this.requestScheduled = -1;
305308
const retryCount = ++this.retryCount;
@@ -312,7 +315,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
312315
} else {
313316
// exponential backoff capped to max retry delay
314317
const backoffFactor =
315-
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
318+
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount - 1);
316319
const delay = Math.min(
317320
backoffFactor * retryConfig.retryDelayMs,
318321
retryConfig.maxRetryDelayMs

src/controller/base-stream-controller.ts

+19-11
Original file line numberDiff line numberDiff line change
@@ -593,7 +593,11 @@ export default class BaseStreamController
593593
}
594594
});
595595
this.hls.trigger(Events.KEY_LOADING, { frag });
596-
this.throwIfFragContextChanged('KEY_LOADING');
596+
if (this.fragCurrent === null) {
597+
keyLoadingPromise = Promise.reject(
598+
new Error(`frag load aborted, context changed in KEY_LOADING`)
599+
);
600+
}
597601
} else if (!frag.encrypted && details.encryptedFragments.length) {
598602
this.keyLoader.loadClear(frag, details.encryptedFragments);
599603
}
@@ -652,7 +656,13 @@ export default class BaseStreamController
652656
part,
653657
targetBufferTime,
654658
});
655-
this.throwIfFragContextChanged('FRAG_LOADING parts');
659+
if (this.fragCurrent === null) {
660+
return Promise.reject(
661+
new Error(
662+
`frag load aborted, context changed in FRAG_LOADING parts`
663+
)
664+
);
665+
}
656666
return result;
657667
} else if (
658668
!frag.url ||
@@ -708,15 +718,12 @@ export default class BaseStreamController
708718
.catch((error) => this.handleFragLoadError(error));
709719
}
710720
this.hls.trigger(Events.FRAG_LOADING, { frag, targetBufferTime });
711-
this.throwIfFragContextChanged('FRAG_LOADING');
712-
return result;
713-
}
714-
715-
private throwIfFragContextChanged(context: string): void | never {
716-
// exit if context changed during event loop
717721
if (this.fragCurrent === null) {
718-
throw new Error(`frag load aborted, context changed in ${context}`);
722+
return Promise.reject(
723+
new Error(`frag load aborted, context changed in FRAG_LOADING`)
724+
);
719725
}
726+
return result;
720727
}
721728

722729
private doFragPartsLoad(
@@ -1388,7 +1395,7 @@ export default class BaseStreamController
13881395
}
13891396
// exponential backoff capped to max retry delay
13901397
const backoffFactor =
1391-
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
1398+
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, fragmentErrors);
13921399
const delay = Math.min(
13931400
backoffFactor * retryConfig.retryDelayMs,
13941401
retryConfig.maxRetryDelayMs
@@ -1520,7 +1527,7 @@ export default class BaseStreamController
15201527
);
15211528
if (parsed) {
15221529
level.fragmentError = 0;
1523-
} else {
1530+
} else if (this.transmuxer?.error === null) {
15241531
const error = new Error(
15251532
`Found no media in fragment ${frag.sn} of level ${level.id} resetting transmuxer to fallback to playlist timing`
15261533
);
@@ -1534,6 +1541,7 @@ export default class BaseStreamController
15341541
reason: `Found no media in msn ${frag.sn} of level "${level.url}"`,
15351542
});
15361543
this.resetTransmuxer();
1544+
// For this error fallthrough. Marking parsed will allow advancing to next fragment.
15371545
}
15381546
this.state = State.PARSED;
15391547
this.hls.trigger(Events.FRAG_PARSED, { frag, part });

src/controller/error-controller.ts

+1
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ export default class ErrorController {
159159
}
160160
break;
161161
case ErrorDetails.FRAG_PARSING_ERROR:
162+
case ErrorDetails.FRAG_DECRYPT_ERROR:
162163
case ErrorDetails.FRAG_LOAD_ERROR:
163164
case ErrorDetails.FRAG_LOAD_TIMEOUT:
164165
case ErrorDetails.KEY_LOAD_ERROR:

src/controller/stream-controller.ts

+1
Original file line numberDiff line numberDiff line change
@@ -854,6 +854,7 @@ export default class StreamController
854854
}
855855
switch (data.details) {
856856
case ErrorDetails.FRAG_PARSING_ERROR:
857+
case ErrorDetails.FRAG_DECRYPT_ERROR:
857858
case ErrorDetails.FRAG_LOAD_ERROR:
858859
case ErrorDetails.FRAG_LOAD_TIMEOUT:
859860
case ErrorDetails.KEY_LOAD_ERROR:

src/demux/transmuxer-interface.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import type { RationalTimestamp } from '../utils/timescale-conversion';
2020
const MediaSource = getMediaSource() || { isTypeSupported: () => false };
2121

2222
export default class TransmuxerInterface {
23+
public error: Error | null = null;
2324
private hls: Hls;
2425
private id: PlaylistLevelType;
2526
private observer: HlsEventEmitter;
@@ -49,6 +50,9 @@ export default class TransmuxerInterface {
4950
data = data || {};
5051
data.frag = this.frag;
5152
data.id = this.id;
53+
if (ev === Events.ERROR) {
54+
this.error = data.error;
55+
}
5256
this.hls.trigger(ev, data);
5357
};
5458

@@ -75,16 +79,18 @@ export default class TransmuxerInterface {
7579
this.onwmsg = this.onWorkerMessage.bind(this);
7680
worker.addEventListener('message', this.onwmsg);
7781
worker.onerror = (event) => {
82+
const error = new Error(
83+
`${event.message} (${event.filename}:${event.lineno})`
84+
);
7885
this.useWorker = false;
86+
this.error = null;
7987
logger.warn('Exception in webworker, fallback to inline');
8088
this.hls.trigger(Events.ERROR, {
8189
type: ErrorTypes.OTHER_ERROR,
8290
details: ErrorDetails.INTERNAL_EXCEPTION,
8391
fatal: false,
8492
event: 'demuxerWorker',
85-
error: new Error(
86-
`${event.message} (${event.filename}:${event.lineno})`
87-
),
93+
error,
8894
});
8995
};
9096
worker.postMessage({
@@ -103,6 +109,7 @@ export default class TransmuxerInterface {
103109
// revoke the Object URL that was used to create transmuxer worker, so as not to leak it
104110
self.URL.revokeObjectURL(worker.objectURL);
105111
}
112+
this.error = null;
106113
this.transmuxer = new Transmuxer(
107114
this.observer,
108115
typeSupported,
@@ -303,6 +310,7 @@ export default class TransmuxerInterface {
303310
if (!this.hls) {
304311
return;
305312
}
313+
this.error = error;
306314
this.hls.trigger(Events.ERROR, {
307315
type: ErrorTypes.MEDIA_ERROR,
308316
details: ErrorDetails.FRAG_PARSING_ERROR,

src/demux/transmuxer.ts

+1-8
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ export default class Transmuxer {
150150
if (resetMuxers) {
151151
const error = this.configureTransmuxer(uintData);
152152
if (error) {
153+
logger.warn(`[transmuxer] ${error.message}`);
153154
this.observer.emit(Events.ERROR, Events.ERROR, {
154155
type: ErrorTypes.MEDIA_ERROR,
155156
details: ErrorDetails.FRAG_PARSING_ERROR,
@@ -232,14 +233,6 @@ export default class Transmuxer {
232233
const { demuxer, remuxer } = this;
233234
if (!demuxer || !remuxer) {
234235
// If probing failed, then Hls.js has been given content its not able to handle
235-
const error = new Error('no demuxer matching with content found');
236-
this.observer.emit(Events.ERROR, Events.ERROR, {
237-
type: ErrorTypes.MEDIA_ERROR,
238-
details: ErrorDetails.FRAG_PARSING_ERROR,
239-
fatal: false,
240-
error,
241-
reason: error.message,
242-
});
243236
stats.executeEnd = now();
244237
return [emptyResult(chunkMeta)];
245238
}

0 commit comments

Comments
 (0)