Skip to content

Commit

Permalink
Switch on FRAG_PARSING_ERROR or continue trying fragLoadPolicy.defaul…
Browse files Browse the repository at this point in the history
…t.errorRetry.maxNumRetry (6) times 2/2

Fixes #5011
  • Loading branch information
robwalch committed Feb 18, 2023
1 parent 11f9747 commit b96d9fb
Show file tree
Hide file tree
Showing 14 changed files with 124 additions and 139 deletions.
6 changes: 4 additions & 2 deletions api-extractor/report/hls.js.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@ export class AbrController implements AbrComponentAPI {
get nextAutoLevel(): number;
set nextAutoLevel(nextLevel: number);
// (undocumented)
protected onError(event: Events.ERROR, data: ErrorData): void;
// (undocumented)
protected onFragBuffered(event: Events.FRAG_BUFFERED, data: FragBufferedData): void;
// (undocumented)
protected onFragLoaded(event: Events.FRAG_LOADED, { frag, part }: FragLoadedData): void;
Expand Down Expand Up @@ -2380,9 +2378,13 @@ export type LoaderOnTimeout<T extends LoaderContext> = (stats: LoaderStats, cont
//
// @public (undocumented)
export interface LoaderResponse {
// (undocumented)
code?: number;
// (undocumented)
data: string | ArrayBuffer | Object;
// (undocumented)
text?: string;
// (undocumented)
url: string;
}

Expand Down
22 changes: 0 additions & 22 deletions src/controller/abr-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ class AbrController implements AbrComponentAPI {
hls.on(Events.FRAG_LOADED, this.onFragLoaded, this);
hls.on(Events.FRAG_BUFFERED, this.onFragBuffered, this);
hls.on(Events.LEVEL_LOADED, this.onLevelLoaded, this);
hls.on(Events.ERROR, this.onError, this);
}

protected unregisterListeners() {
Expand All @@ -56,7 +55,6 @@ class AbrController implements AbrComponentAPI {
hls.off(Events.FRAG_LOADED, this.onFragLoaded, this);
hls.off(Events.FRAG_BUFFERED, this.onFragBuffered, this);
hls.off(Events.LEVEL_LOADED, this.onLevelLoaded, this);
hls.off(Events.ERROR, this.onError, this);
}

public destroy() {
Expand Down Expand Up @@ -309,26 +307,6 @@ class AbrController implements AbrComponentAPI {
}
}

protected onError(event: Events.ERROR, data: ErrorData) {
// stop timer in case of frag loading error
if (data.frag?.type === PlaylistLevelType.MAIN) {
if (data.type === ErrorTypes.KEY_SYSTEM_ERROR) {
this.clearTimer();
return;
}
switch (data.details) {
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
case ErrorDetails.KEY_LOAD_ERROR:
case ErrorDetails.KEY_LOAD_TIMEOUT:
this.clearTimer();
break;
default:
break;
}
}
}

private ignoreFragment(frag: Fragment): boolean {
// Only count non-alt-audio frags which were actually buffered in our BW calculations
return frag.type !== PlaylistLevelType.MAIN || frag.sn === 'initSegment';
Expand Down
1 change: 1 addition & 0 deletions src/controller/audio-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,7 @@ class AudioStreamController
}
switch (data.details) {
case ErrorDetails.FRAG_PARSING_ERROR:
case ErrorDetails.FRAG_DECRYPT_ERROR:
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
case ErrorDetails.KEY_LOAD_ERROR:
Expand Down
9 changes: 6 additions & 3 deletions src/controller/base-playlist-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,10 +296,13 @@ export default class BasePlaylistController implements NetworkComponentAPI {
errorDetails === ErrorDetails.LEVEL_LOAD_TIMEOUT ||
errorDetails === ErrorDetails.AUDIO_TRACK_LOAD_TIMEOUT ||
errorDetails === ErrorDetails.SUBTITLE_TRACK_LOAD_TIMEOUT;
const httpStatus = errorEvent.response?.code;
const retryConfig =
playlistLoadPolicy.default[`${isTimeout ? 'timeout' : 'error'}Retry`];
const retry = !!retryConfig && this.retryCount < retryConfig.maxNumRetry;
// TODO: Don't try on bad network status
const retry =
!!retryConfig &&
this.retryCount < retryConfig.maxNumRetry &&
httpStatus !== 0;
if (retry) {
this.requestScheduled = -1;
const retryCount = ++this.retryCount;
Expand All @@ -312,7 +315,7 @@ export default class BasePlaylistController implements NetworkComponentAPI {
} else {
// exponential backoff capped to max retry delay
const backoffFactor =
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount - 1);
const delay = Math.min(
backoffFactor * retryConfig.retryDelayMs,
retryConfig.maxRetryDelayMs
Expand Down
30 changes: 19 additions & 11 deletions src/controller/base-stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,11 @@ export default class BaseStreamController
}
});
this.hls.trigger(Events.KEY_LOADING, { frag });
this.throwIfFragContextChanged('KEY_LOADING');
if (this.fragCurrent === null) {
keyLoadingPromise = Promise.reject(
new Error(`frag load aborted, context changed in KEY_LOADING`)
);
}
} else if (!frag.encrypted && details.encryptedFragments.length) {
this.keyLoader.loadClear(frag, details.encryptedFragments);
}
Expand Down Expand Up @@ -652,7 +656,13 @@ export default class BaseStreamController
part,
targetBufferTime,
});
this.throwIfFragContextChanged('FRAG_LOADING parts');
if (this.fragCurrent === null) {
return Promise.reject(
new Error(
`frag load aborted, context changed in FRAG_LOADING parts`
)
);
}
return result;
} else if (
!frag.url ||
Expand Down Expand Up @@ -708,15 +718,12 @@ export default class BaseStreamController
.catch((error) => this.handleFragLoadError(error));
}
this.hls.trigger(Events.FRAG_LOADING, { frag, targetBufferTime });
this.throwIfFragContextChanged('FRAG_LOADING');
return result;
}

private throwIfFragContextChanged(context: string): void | never {
// exit if context changed during event loop
if (this.fragCurrent === null) {
throw new Error(`frag load aborted, context changed in ${context}`);
return Promise.reject(
new Error(`frag load aborted, context changed in FRAG_LOADING`)
);
}
return result;
}

private doFragPartsLoad(
Expand Down Expand Up @@ -1388,7 +1395,7 @@ export default class BaseStreamController
}
// exponential backoff capped to max retry delay
const backoffFactor =
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, retryCount);
retryConfig.backoff === 'linear' ? 1 : Math.pow(2, fragmentErrors);
const delay = Math.min(
backoffFactor * retryConfig.retryDelayMs,
retryConfig.maxRetryDelayMs
Expand Down Expand Up @@ -1520,7 +1527,7 @@ export default class BaseStreamController
);
if (parsed) {
level.fragmentError = 0;
} else {
} else if (this.transmuxer?.error === null) {
const error = new Error(
`Found no media in fragment ${frag.sn} of level ${level.id} resetting transmuxer to fallback to playlist timing`
);
Expand All @@ -1534,6 +1541,7 @@ export default class BaseStreamController
reason: `Found no media in msn ${frag.sn} of level "${level.url}"`,
});
this.resetTransmuxer();
// For this error fallthrough. Marking parsed will allow advancing to next fragment.
}
this.state = State.PARSED;
this.hls.trigger(Events.FRAG_PARSED, { frag, part });
Expand Down
1 change: 1 addition & 0 deletions src/controller/error-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ export default class ErrorController {
}
break;
case ErrorDetails.FRAG_PARSING_ERROR:
case ErrorDetails.FRAG_DECRYPT_ERROR:
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
case ErrorDetails.KEY_LOAD_ERROR:
Expand Down
1 change: 1 addition & 0 deletions src/controller/stream-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,7 @@ export default class StreamController
}
switch (data.details) {
case ErrorDetails.FRAG_PARSING_ERROR:
case ErrorDetails.FRAG_DECRYPT_ERROR:
case ErrorDetails.FRAG_LOAD_ERROR:
case ErrorDetails.FRAG_LOAD_TIMEOUT:
case ErrorDetails.KEY_LOAD_ERROR:
Expand Down
14 changes: 11 additions & 3 deletions src/demux/transmuxer-interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { RationalTimestamp } from '../utils/timescale-conversion';
const MediaSource = getMediaSource() || { isTypeSupported: () => false };

export default class TransmuxerInterface {
public error: Error | null = null;
private hls: Hls;
private id: PlaylistLevelType;
private observer: HlsEventEmitter;
Expand Down Expand Up @@ -49,6 +50,9 @@ export default class TransmuxerInterface {
data = data || {};
data.frag = this.frag;
data.id = this.id;
if (ev === Events.ERROR) {
this.error = data.error;
}
this.hls.trigger(ev, data);
};

Expand All @@ -75,16 +79,18 @@ export default class TransmuxerInterface {
this.onwmsg = this.onWorkerMessage.bind(this);
worker.addEventListener('message', this.onwmsg);
worker.onerror = (event) => {
const error = new Error(
`${event.message} (${event.filename}:${event.lineno})`
);
this.useWorker = false;
this.error = null;
logger.warn('Exception in webworker, fallback to inline');
this.hls.trigger(Events.ERROR, {
type: ErrorTypes.OTHER_ERROR,
details: ErrorDetails.INTERNAL_EXCEPTION,
fatal: false,
event: 'demuxerWorker',
error: new Error(
`${event.message} (${event.filename}:${event.lineno})`
),
error,
});
};
worker.postMessage({
Expand All @@ -103,6 +109,7 @@ export default class TransmuxerInterface {
// revoke the Object URL that was used to create transmuxer worker, so as not to leak it
self.URL.revokeObjectURL(worker.objectURL);
}
this.error = null;
this.transmuxer = new Transmuxer(
this.observer,
typeSupported,
Expand Down Expand Up @@ -303,6 +310,7 @@ export default class TransmuxerInterface {
if (!this.hls) {
return;
}
this.error = error;
this.hls.trigger(Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
Expand Down
9 changes: 1 addition & 8 deletions src/demux/transmuxer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export default class Transmuxer {
if (resetMuxers) {
const error = this.configureTransmuxer(uintData);
if (error) {
logger.warn(`[transmuxer] ${error.message}`);
this.observer.emit(Events.ERROR, Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
Expand Down Expand Up @@ -232,14 +233,6 @@ export default class Transmuxer {
const { demuxer, remuxer } = this;
if (!demuxer || !remuxer) {
// If probing failed, then Hls.js has been given content its not able to handle
const error = new Error('no demuxer matching with content found');
this.observer.emit(Events.ERROR, Events.ERROR, {
type: ErrorTypes.MEDIA_ERROR,
details: ErrorDetails.FRAG_PARSING_ERROR,
fatal: false,
error,
reason: error.message,
});
stats.executeEnd = now();
return [emptyResult(chunkMeta)];
}
Expand Down
Loading

0 comments on commit b96d9fb

Please sign in to comment.