Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions packages/mux-player/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ const muxMediaErrorToDialogTitle = (mediaError: MediaError, translate = false) =
return i18n(`{category} does not exist`, translate).format({ category });
}
if (mediaError.muxCode === MuxErrorCode.NETWORK_NOT_READY) {
return i18n(`{category} is not currently available`, translate).format({ category });
const mediaType = mediaError.streamType === 'live' ? 'Live stream' : 'Video';
return i18n(`{mediaType} is not currently available`, translate).format({ mediaType });
}
}

Expand Down Expand Up @@ -101,9 +102,6 @@ const muxMediaErrorToDialogMessage = (mediaError: MediaError, translate = false)
if (mediaError.muxCode === MuxErrorCode.NETWORK_NOT_FOUND) {
return '';
}
if (mediaError.muxCode === MuxErrorCode.NETWORK_NOT_READY) {
return i18n(`The live stream or video file are not yet ready.`, translate);
}
return mediaError.message;
}

Expand Down
1 change: 1 addition & 0 deletions packages/playback-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export class MediaError extends Error {
context?: string;
fatal: boolean;
data?: any;
streamType?: 'live' | 'on-demand' | 'unknown';

constructor(message?: Stringable, code: number = MediaError.MEDIA_ERR_CUSTOM, fatal?: boolean, context?: string) {
super(message);
Expand Down
70 changes: 67 additions & 3 deletions packages/playback-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ const isAndroidLike =
// NOTE: Exporting for testing
export const muxMediaState: WeakMap<
HTMLMediaElement,
Partial<MuxMediaProps> & { seekable?: TimeRanges; liveEdgeStartOffset?: number }
Partial<MuxMediaProps> & { seekable?: TimeRanges; liveEdgeStartOffset?: number; retryCount?: number }
> = new WeakMap();

const MUX_VIDEO_DOMAIN = 'mux.com';
Expand Down Expand Up @@ -524,7 +524,7 @@ export const initialize = (props: Partial<MuxMediaPropsInternal>, mediaEl: HTMLM

props.drmTypeCb = drmTypeCb;

muxMediaState.set(mediaEl as HTMLMediaElement, {});
muxMediaState.set(mediaEl as HTMLMediaElement, { retryCount: 0 });
const nextHlsInstance = setupHls(props, mediaEl);
const setPreload = setupPreload(props as Pick<MuxMediaProps, 'preload' | 'src'>, mediaEl, nextHlsInstance);
setupMux(props, mediaEl, nextHlsInstance);
Expand Down Expand Up @@ -1251,8 +1251,71 @@ export const loadMedia = (
});

hls.on(Hls.Events.ERROR, (_event, data) => {
saveAndDispatchError(mediaEl, getErrorFromHlsErrorData(data, props));
const error = getErrorFromHlsErrorData(data, props);

if (error.muxCode === MuxErrorCode.NETWORK_NOT_READY) {
const maxRetries = 6; // 5 minutes and 5 seconds total (5s, 60s, 60s, 60s, 60s, 60s)
const state = muxMediaState.get(mediaEl) ?? {};
const retryCount = state.retryCount ?? 0;

if (retryCount < maxRetries) {
// First retry is 5 seconds, subsequent retries are 60 seconds
const retryDelay = retryCount === 0 ? 5000 : 60000;

// New error with the retry delay
const retryDelayError = new MediaError(
`Retrying in ${retryDelay / 1000} seconds...`,
error.code,
error.fatal
);
Object.assign(retryDelayError, error);
saveAndDispatchError(mediaEl, retryDelayError);

setTimeout(() => {
state.retryCount = retryCount + 1;
if (data.details === 'manifestLoadError' && data.url) {
hls.loadSource(data.url);
hls.attachMedia(mediaEl);
} else {
hls.recoverMediaError();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this shouldn't be used here. recoverMediaError() is an attempt to recover a media decode error is my understanding or at least it's definitely not for a network error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, I'll remove it

}, retryDelay);
return;
} else {
state.retryCount = 0;
// New error with the retry link
const retryLinkError = new MediaError(
'Try again later or <a href="#" onclick="window.location.reload(); return false;" style="color: #4a90e2;">click here to retry</a>',
error.code,
error.fatal
);
Object.assign(retryLinkError, error);
saveAndDispatchError(mediaEl, retryLinkError);
return;
}
}
saveAndDispatchError(mediaEl, error);
});

hls.on(Hls.Events.MANIFEST_LOADED, () => {
// Clear error state and UI
const state = muxMediaState.get(mediaEl);
if (state) {
state.error = null;
state.retryCount = 0;
}

const clearError = new MediaError('', MediaError.MEDIA_ERR_CUSTOM, false);
clearError.muxCode = MuxErrorCode.NOT_AN_ERROR;
mediaEl.dispatchEvent(
new CustomEvent('error', {
detail: clearError,
composed: true,
bubbles: true,
})
);
});

mediaEl.addEventListener('error', handleInternalError);
addEventListenerWithTeardown(mediaEl, 'waiting', maybeDispatchEndedCallback);

Expand Down Expand Up @@ -1386,6 +1449,7 @@ const getErrorFromHlsErrorData = (
props: Partial<Pick<MuxMediaPropsInternal, 'playbackId' | 'drmToken' | 'playbackToken' | 'tokens'>>
) => {
console.error('getErrorFromHlsErrorData()', data);

const ErrorCodeMap: Partial<Record<ValueOf<typeof Hls.ErrorTypes>, 0 | 1 | 2 | 3 | 4 | 5>> = {
[Hls.ErrorTypes.NETWORK_ERROR]: MediaError.MEDIA_ERR_NETWORK,
[Hls.ErrorTypes.MEDIA_ERROR]: MediaError.MEDIA_ERR_DECODE,
Expand Down
12 changes: 10 additions & 2 deletions packages/playback-core/src/request-errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
parseJwt,
toPlaybackIdParts,
} from './util';
import { isKeyOf, MuxMediaPropsInternal } from './types';
import { isKeyOf, MuxMediaPropsInternal, StreamTypes } from './types';
import type { MuxErrorCategoryValue } from './errors';
import { errorCategoryToTokenNameOrPrefix, MediaError, MuxErrorCategory, MuxErrorCode } from './errors';

Expand Down Expand Up @@ -39,7 +39,9 @@ export const categoryToToken = (
export const getErrorFromResponse = (
resp: Pick<Response, 'status' | 'url'> | Pick<LoaderResponse, 'code' | 'url'>,
category: MuxErrorCategoryValue,
muxMediaEl: Partial<Pick<MuxMediaPropsInternal, 'playbackId' | 'drmToken' | 'playbackToken' | 'tokens'>>,
muxMediaEl: Partial<
Pick<MuxMediaPropsInternal, 'playbackId' | 'drmToken' | 'playbackToken' | 'tokens' | 'streamType'>
>,
fatal?: boolean,
translate = false,
offline = !globalThis.navigator?.onLine // NOTE: Passing this in for testing purposes
Expand Down Expand Up @@ -205,6 +207,12 @@ export const getErrorFromResponse = (
const mediaError = new MediaError(message, mediaErrorCode, fatal ?? true, context);
mediaError.errorCategory = category;
mediaError.muxCode = MuxErrorCode.NETWORK_NOT_READY;
mediaError.streamType =
muxMediaEl.streamType === StreamTypes.LIVE
? 'live'
: muxMediaEl.streamType === StreamTypes.ON_DEMAND
? 'on-demand'
: 'unknown';
mediaError.data = resp;
return mediaError;
}
Expand Down
Loading