Skip to content

Commit be67987

Browse files
committed
fix(player): add hls network error retry logic
closes #982
1 parent 4b6b3a5 commit be67987

5 files changed

Lines changed: 55 additions & 8 deletions

File tree

packages/vidstack/src/core/api/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export type MediaErrorCode = 1 | 2 | 3 | 4;
4949
export interface MediaErrorDetail {
5050
message: string;
5151
code: MediaErrorCode;
52+
error?: Error;
5253
mediaError?: MediaError;
5354
}
5455

packages/vidstack/src/providers/hls/hls.ts

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type * as HLS from 'hls.js';
22
import { effect, peek } from 'maverick.js';
3-
import { camelToKebabCase, DOMEvent, listenEvent } from 'maverick.js/std';
3+
import { camelToKebabCase, DOMEvent, isString, listenEvent } from 'maverick.js/std';
44

5+
import type { MediaSrc } from '../../core/api/types';
56
import { QualitySymbol } from '../../core/quality/symbols';
67
import { TextTrackSymbol } from '../../core/tracks/text/symbols';
78
import { TextTrack } from '../../core/tracks/text/text-track';
@@ -49,6 +50,7 @@ export class HLSController {
4950
ctx.player.dispatch(new DOMEvent('hls-instance', { detail: this._instance }));
5051

5152
this._instance.attachMedia(this._video);
53+
this._instance.on(ctor.Events.FRAG_LOADING, this._onFragLoading.bind(this));
5254
this._instance.on(ctor.Events.AUDIO_TRACK_SWITCHED, this._onAudioSwitch.bind(this));
5355
this._instance.on(ctor.Events.LEVEL_SWITCHED, this._onLevelSwitched.bind(this));
5456
this._instance.on(ctor.Events.LEVEL_LOADED, this._onLevelLoaded.bind(this));
@@ -209,20 +211,50 @@ export class HLSController {
209211
if (data.fatal) {
210212
switch (data.type) {
211213
case 'networkError':
212-
this._instance?.startLoad();
214+
this._onNetworkError(data.error);
213215
break;
214216
case 'mediaError':
215217
this._instance?.recoverMediaError();
216218
break;
217219
default:
218-
// We can't recover here - better course of action?
219-
this._instance?.destroy();
220-
this._instance = null;
220+
this._onFatalError(data.error);
221221
break;
222222
}
223223
}
224224
}
225225

226+
private _onFragLoading() {
227+
if (this._retryLoadingTimer >= 0) this._clearRetryTimer();
228+
}
229+
230+
private _retryLoadingTimer = -1;
231+
private _onNetworkError(error: Error) {
232+
this._clearRetryTimer();
233+
234+
this._instance?.startLoad();
235+
236+
this._retryLoadingTimer = window.setTimeout(() => {
237+
this._retryLoadingTimer = -1;
238+
this._onFatalError(error);
239+
}, 5000);
240+
}
241+
242+
private _clearRetryTimer() {
243+
clearTimeout(this._retryLoadingTimer);
244+
this._retryLoadingTimer = -1;
245+
}
246+
247+
private _onFatalError(error: Error) {
248+
// We can't recover here - better course of action?
249+
this._instance?.destroy();
250+
this._instance = null;
251+
this._ctx.delegate._notify('error', {
252+
message: error.message,
253+
code: 1,
254+
error: error,
255+
});
256+
}
257+
226258
private _enableAutoQuality() {
227259
if (this._instance) this._instance.currentLevel = -1;
228260
}
@@ -247,7 +279,14 @@ export class HLSController {
247279
}
248280
}
249281

282+
_loadSource(src: MediaSrc) {
283+
if (!isString(src.src)) return;
284+
this._clearRetryTimer();
285+
this._instance?.loadSource(src.src);
286+
}
287+
250288
_destroy() {
289+
this._clearRetryTimer();
251290
if (this._ctx) this._ctx.qualities[QualitySymbol._enableAuto] = undefined;
252291
this._instance?.destroy();
253292
this._instance = null;

packages/vidstack/src/providers/hls/lib-loader.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,11 @@ export class HLSLibLoader {
9696
}),
9797
);
9898

99-
this._ctx.delegate._notify('error', { message: error.message, code: 4 });
99+
this._ctx.delegate._notify('error', {
100+
message: error.message,
101+
code: 4,
102+
error,
103+
});
100104
}
101105
}
102106

packages/vidstack/src/providers/hls/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export class HLSProvider extends VideoProvider implements MediaProviderAdapter {
112112
override async loadSource(src: MediaSrc, preload?: HTMLMediaElement['preload']) {
113113
if (!isString(src.src)) return;
114114
this._media.preload = preload || '';
115-
this._controller.instance?.loadSource(src.src);
115+
this._controller._loadSource(src);
116116
this._currentSrc = src as MediaSrc<string>;
117117
}
118118

packages/vidstack/src/providers/vimeo/provider.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { TimeRange, type MediaSrc } from '../../core';
1111
import { QualitySymbol } from '../../core/quality/symbols';
1212
import { ListSymbol } from '../../foundation/list/symbols';
1313
import { RAFLoop } from '../../foundation/observers/raf-loop';
14+
import { coerceToError } from '../../utils/error';
1415
import { preconnect } from '../../utils/network';
1516
import { timedPromise } from '../../utils/promise';
1617
import { EmbedProvider } from '../embed/EmbedProvider';
@@ -272,6 +273,7 @@ export class VimeoProvider
272273
this._notify('error', {
273274
message: `Failed to fetch vimeo video info from \`${oembedSrc}\`.`,
274275
code: 1,
276+
error: coerceToError(e),
275277
});
276278
});
277279

@@ -401,8 +403,9 @@ export class VimeoProvider
401403
.catch((e) => {
402404
if (videoId !== this._videoId()) return;
403405
this._notify('error', {
404-
message: `Failed to fetch oembed data: ${e}`,
406+
message: `Failed to fetch oembed data`,
405407
code: 2,
408+
error: coerceToError(e),
406409
});
407410
});
408411
}

0 commit comments

Comments
 (0)