Errors

Every error code, what it means, and how to handle it.

When a video can't play, the viewer gets a clear message in their own language instead of an endless spinner, and your code gets a stable code to act on. Try each failure below, and watch what onError receives.

0:00−0:000:00

    The error object

    Every error has the same shape, on media.error and in onError:

    ts
    interface MediaErrorInfo {  code: VideoErrorCode; // "media-decode"  message: string; // The browser's or engine's own words, for logs  fatal: boolean; // Did this stop playback?  retryable?: boolean; // Could trying again work?  nativeCode?: number | string; // The browser's or engine's own code  cause?: unknown; // The original error or event  source?: SourceObject; // The source that failed}

    code stays the same between versions, so it's safe to compare against. message isn't: it's whatever the browser or engine said, in English or not, and it can change. Log it, but don't show it to viewers, and don't match on it.

    What the viewer sees

    VideoPlayer and ErrorOverlay show a title and a line of explanation, translated by the i18n plugin, with the code underneath so that viewers can quote it to support. Try again only appears when trying again could work.

    The failureThe title
    Anything retryableThis video didn't load.
    drm-denied and drm-expiredYou don't have access to this video.
    drm-output-restrictedThis video can't be shown on this screen.
    Any other drm- codeThis protected video can't be played.
    Everything elseThis video can't be played.

    To change the words for a code, pass errorMessages to VideoPlayer, or messages to ErrorOverlay:

    tsx
    <VideoPlayer  src="/video.mp4"  errorMessages={{    "drm-denied": { title: "Subscribe to watch", detail: "Plans start at $5 a month." },  }}/>

    Handling errors

    Log them

    onError on VideoPlayer or Video.Root hears every error, fatal or not. It's the right place to tell an error service.

    tsx
    <VideoPlayer  src="/video.mp4"  onError={(error) => {    Sentry.captureMessage(`Video: ${error.code}`, {      level: error.fatal ? "error" : "warning",      extra: { message: error.message, nativeCode: error.nativeCode, source: error.source?.src },    });  }}/>

    Outside React, or for a player you made yourself, use player.onError, which returns a function that stops listening:

    ts
    const stop = player.onError((error) => console.warn(error.code, error.message));

    With the recovery plugin, onError hears each failed try, not only the last. A failed source that a fallback took over from arrives with fatal: false, because the viewer's video kept going.

    Sources can hold signed URLs, so check what source and message contain before sending them to another service.

    Show your own message

    Read media.error to replace the overlay. Check fatal, because the latest error may be one that playback carried on through.

    tsx
    function ErrorMessage() {  const error = useVideoState((media) => (media.error?.fatal ? media.error : null));  const { reload } = useCommands();  if (!error) return null;  return (    <div role="alert">      <p>Sorry, this video didn't load. ({error.code})</p>      {error.retryable === false ? null : <button onClick={() => reload()}>Try again</button>}    </div>  );}

    reload picks up where the failure happened, and plays again if the viewer was watching.

    Check a code

    react-video-kit/errors has the table below as data, for code that needs to know about a code it hasn't seen yet:

    ts
    import { ERROR_CODES, isRetryable } from "react-video-kit/errors";ERROR_CODES["media-network"]; // { fatal: true, retryable: true }isRetryable("drm-denied"); // falseisRetryable(error); // Uses error.retryable first, then the code

    It's a separate entry, so a player that doesn't import it doesn't carry the table.

    Report errors from a plugin

    Report a failure with context.error, using your own code, and it behaves like any other: a fatal one moves to the next fallback or stops playback, and onError hears it.

    ts
    context.error({ code: "my-plugin-expired", message: "The session ended.", fatal: true, retryable: false });

    Providers do the same through sink.error. From a load gate or a provider's load, throw a VideoKitError to choose the code:

    ts
    import { VideoKitError } from "react-video-kit/errors";throw new VideoKitError("my-token-failed", "The token server returned 500.", { retryable: true });

    Anything else thrown there is reported as source-load-failed. Leave retryable out if you can't say: recovery won't retry the error, and the overlay still offers Try again.

    Fatal and retried

    • Fatal means playback stopped. If the source was one of several, and nothing has played yet, the player tries the next one first.
    • Retried means retryable is true. The recovery plugin retries it by itself, and the overlay offers Try again once retries run out. Without that plugin, the overlay offers it straight away.

    The browser

    Reported by the browser's own media element, with its MediaError number as nativeCode.

    CodeWhenFatalRetriedWhat to do
    media-abortedThe browser stopped the download, usually because the viewer asked it to.YesYesNothing, unless it's frequent.
    media-networkThe connection failed partway through, or the viewer was offline when it started.YesYesNothing. recovery waits for the connection to return.
    media-decodeThe browser couldn't decode the file. It's damaged, or uses a codec feature this device doesn't have.YesNoRe-encode the file, or offer a fallback in another format.
    media-unsupportedThe file wouldn't load: it's missing, the server refused it, or the format isn't supported. Browsers report all of these the same way.YesNoCheck the URL, the server's response, and its CORS headers. Then check the format.
    media-unknownThe browser reported an error it gave no details for.YesNoCheck message and cause.

    Sources and playing

    CodeWhenFatalRetriedWhat to do
    source-no-providerNothing can play this source.YesNoAdd the hls or dash plugin, or give a source the browser can play.
    source-load-failedA plugin or provider threw while loading, without giving a code.YesYesCheck message, and the plugin's load gate.
    play-blockedThe browser refused to start, usually because of its autoplay policy.NoNoLet the viewer press play, or use the autoplay plugin, which tries again muted. media.playRejected also holds this until playback starts.
    play-failedplay() was refused for another reason.NoNoCheck nativeCode, which has the name of the browser's error.
    quality-switchA file chosen in the quality menu wouldn't load, so the previous one came back.NoNoCheck that file. Playback carries on.

    HLS

    Reported when hls.js gives up. It retries downloads and recovers from media errors on its own first, and what it recovers from isn't reported. nativeCode is its details, such as fragLoadError, and cause is its whole error, where cause.response.code has any HTTP status.

    CodeWhenFatalRetriedWhat to do
    hls-engine-loadhls.js didn't load.YesYesInstall hls.js, or check the library option. On a viewer's device, a lost connection does this too.
    hls-manifestThe manifest couldn't be downloaded.YesYesCheck the URL and CORS headers.
    hls-manifest-invalidThe manifest downloaded, but isn't valid.YesNoFix the packaging, and check that the URL doesn't return an HTML page.
    hls-playlistA media, audio, or subtitle playlist couldn't be downloaded, or a live one stopped updating.YesYesCheck the origin or the encoder.
    hls-segmentA segment couldn't be downloaded.YesYesCheck the CDN.
    hls-keyAn AES key couldn't be downloaded.YesYesCheck the key server and its CORS headers.
    hls-networkAny other download failed.YesYesCheck nativeCode.
    hls-codecsThe device can't play any of the stream's codecs.YesNoAdd a rendition this device can play, such as H.264 with AAC.
    hls-mediaDecoding failed, and recovering didn't help.YesNoCheck the segments' encoding.
    hls-unknownhls.js failed in another way.YesNoCheck nativeCode.

    DASH

    Reported when dash.js gives up. nativeCode is its error number. Errors from the media element itself use the media- codes.

    CodeWhenFatalRetriedWhat to do
    dash-engine-loaddash.js didn't load.YesYesInstall dashjs, or check the library option. On a viewer's device, a lost connection does this too.
    dash-manifestThe manifest couldn't be downloaded.YesYesCheck the URL and CORS headers.
    dash-manifest-invalidThe manifest downloaded, but isn't valid or has nothing to play.YesNoFix the packaging.
    dash-segmentA segment couldn't be downloaded.YesYesCheck the CDN.
    dash-networkAny other download failed.YesYesCheck nativeCode.
    dash-unsupportedThis browser has no Media Source, or can't play the stream's type.YesNoGive a fallback source.
    dash-mediaAdding media to the buffer failed.YesNoCheck the segments' encoding.
    dash-captionsCaptions in the stream couldn't be read.NoNoFix the captions. The video carries on without them.
    dash-time-syncThe manifest's time server didn't answer.NoNoCheck the UTCTiming server. Playback carries on with the manifest's timing.
    dash-unknowndash.js failed in another way.YesNoCheck nativeCode.

    DRM

    The same codes come from the DRM plugin in native playback, and from hls.js and dash.js.

    CodeWhenFatalRetriedWhat to do
    drm-unsupportedThis browser supports none of the configured key systems.YesNoAdd a key system it supports. Widevine, PlayReady, and FairPlay each cover different devices.
    drm-configThe settings don't cover this stream, such as a key system with no licence URL.YesNoCheck what's passed to drm().
    drm-certificateThe FairPlay certificate couldn't be downloaded or used.YesYesCheck certificateUrl and its CORS headers.
    drm-licenseThe licence server couldn't be reached, or returned an error.YesYesCheck licenseUrl, its CORS headers, and nativeCode for the status.
    drm-deniedThe licence server refused, with a 401 or 403.YesNoThe viewer isn't entitled to this video, or their token is wrong. Ask them to sign in, or use errorMessages to explain.
    drm-sessionThe browser's key session failed, usually because of a licence it couldn't use.YesNoCheck that the licence matches the content and key system.
    drm-output-restrictedThe key can't be used on this display, usually because of HDCP.YesNoOffer a rendition with lower requirements. The viewer may need to disconnect a display.
    drm-expiredThe licence has expired.YesNoIssue longer licences, or reload to get a new one.

    Ads

    The ads plugin fails open, so none of these stop the video. They also reach the plugin's own onError.

    CodeWhenFatalRetriedWhat to do
    ads-setupThe engine couldn't start, such as when an ad blocker stopped the IMA SDK.NoNoNothing for ad blockers. Otherwise, check the engine's setup.
    ads-requestThe ad tag failed. nativeCode has the HTTP status or the IMA error code.NoNoCheck the tag.
    ads-timeoutNo pre-roll started within timeout.NoNoRaise timeout if your ads are slow to arrive.
    ads-playbackAn ad failed partway through a break.NoNoCheck the ad's file.

    Casting

    The viewer stays on this device when casting fails, so none of these stop the video. nativeCode has the Cast SDK's own code.

    CodeWhenFatalRetriedWhat to do
    cast-sdkThe Cast SDK didn't load.NoNoNothing for ad blockers. Otherwise, check sdkUrl.
    cast-sessionCasting didn't start. A viewer closing the picker isn't reported.NoNoCheck nativeCode, such as receiver_unavailable.
    cast-loadThe receiver couldn't load the video.NoNoCheck that the receiver can reach the URL and play the format. The default receiver can't play DRM.

    Mistakes in code

    These are thrown as a VideoKitError while you're building, rather than reported to viewers. Each has a code, and a message saying how to fix it.

    CodeWhen
    plugin-reserved-nameA plugin is named media.
    plugin-duplicateThe same plugin is in plugins twice.
    plugin-no-codeA plugin crossed from a server component to the client and lost its functions. Create plugins in a client component.
    plugin-cyclePlugins require each other in a loop.
    plugin-missingA plugin requires another one that isn't installed.
    plugin-not-installedCode asked for a plugin's state or commands, and it isn't in plugins.
    part-outside-rootA part or hook isn't inside Video.Root.
    part-outside-menuA menu part isn't inside Video.Menu.
    part-outside-radio-groupVideo.MenuRadioItem isn't inside Video.MenuRadioGroup.
    part-outside-timelineA timeline part isn't inside Video.Timeline.
    provider-no-elementA provider that needs a video element was attached without one.

    API

    Functions

    isRetryable

    Whether trying again could work, from an error or a code. An error's own retryable wins, and a code this library doesn't know is taken as not retryable.

    (error: MediaErrorInfo | VideoErrorCode) => boolean

    Values

    DEVELOPER_ERROR_CODES

    Every code on a VideoKitError the library throws for a mistake in the code using it.

    readonly DeveloperErrorCode[]
    ERROR_CODES

    Every code the player and its plugins report on media.error and onError.

    Readonly<Record<KnownVideoErrorCode, ErrorCodeInfo>>
    VideoKitError

    What the library throws, with a code that stays the same between versions. A plugin can also throw one from a load gate or a provider to choose the code the player reports.

    typeof VideoKitError

    Types

    DeveloperErrorCode

    The codes on errors thrown for a mistake in the code using the library.

    DeveloperErrorCode
    ErrorCodeInfo
    FieldTypeDescription
    fatalbooleanTrue when this error stops playback.
    retryablebooleanTrue when trying again could work, so recovery retries it and the overlay offers **Try again**.
    KnownVideoErrorCode

    Every code the library reports. react-video-kit/errors lists them with their details.

    KnownVideoErrorCode
    MediaErrorInfo
    FieldTypeDescription
    codeVideoErrorCodeSays what went wrong, in a form that stays the same between versions.
    messagestringThe browser's or engine's own words, for logs. Viewers see translated text instead.
    fatalbooleanTrue when the error stopped playback, false when it can carry on.
    retryable?booleanTrue when trying again could work, which recovery and **Try again** go by. When it's left out, recovery doesn't retry and the overlay still offers **Try again**.
    nativeCode?string | numberThe browser's or engine's own code, such as 3 from a MediaError or fragLoadError from hls.js.
    cause?unknownThe original error object or event, for logs.
    source?SourceObjectThe source that failed, when known.
    VideoErrorCode

    A known code, or a plugin's own.

    VideoErrorCode
    VideoKitErrorOptions
    FieldTypeDescription
    retryable?booleanTrue when trying again could work, for failures reported while loading.
    nativeCode?string | numberThe browser's or engine's own code.
    cause?unknownThe original error.