Recovery
Reloads the source after a failure that can pass, from the same position.
import { recovery } from "react-video-kit/recovery";Adds 473 B to a player588 B on its ownSource
Reloads the source when a connection drops, waiting longer between each try. Meanwhile the viewer sees a spinner, not an error. The file below never loads, so you can watch every retry fail and the error appear once they run out. Its timestamps show the waits growing.
"use client";import { useEffect, useState } from "react";import type { Player } from "react-video-kit/core";import { VideoPlayer } from "react-video-kit/player";import { recovery } from "react-video-kit/recovery";// This file can't be decoded, which isn't worth retrying, so the plugin wouldn't// normally touch it. retryOn overrides that, so there's something to watch.const plugins = [recovery({ retries: 3, delay: 1000, retryOn: () => true })];export default function Recovery() { const [run, setRun] = useState(0); const [player, setPlayer] = useState<Player | null>(null); const [log, setLog] = useState<string[]>([]); useEffect(() => { if (!player) return; let start: number | undefined; let last = ""; return player.subscribe(() => { const { status } = player.getState().media; const { attempt, retrying } = player.plugin(recovery).state(); let line = ""; if (status === "loading") line = retrying ? `Retry ${attempt} of 3` : "Loading"; if (status === "error") line = retrying ? "Failed, waiting to retry" : "Failed, out of retries"; if (!line || line === last) return; last = line; start ??= performance.now(); const seconds = (performance.now() - start) / 1000; setLog((lines) => [...lines, `${seconds.toFixed(1)}s ${line}`]); }); }, [player]); return ( <> <VideoPlayer key={run} ref={setPlayer} src="/media/broken.mp4" poster="/media/poster.jpg" plugins={plugins} load="visible" /> <div role="log" aria-label="Retries" className="mt-3 min-h-40 font-mono text-sm whitespace-pre text-current"> {log.map((line, index) => ( <p key={index}>{line}</p> ))} </div> <button type="button" className="mt-3 rounded-md border border-current px-3 py-1.5 text-sm text-current" onClick={() => { setLog([]); setRun(run + 1); }} > Run it again </button> </> );}A file that can't be decoded isn't normally retried, so this demo sets retryOn to retry everything.
Usage
recovery() is part of defaultPlugins(), so VideoPlayer with those already has it. On its own:
import { VideoPlayer } from "react-video-kit/player";import { recovery } from "react-video-kit/recovery";const plugins = [recovery({ retries: 5, delay: 500 })];<VideoPlayer src="/video.mp4" plugins={plugins} />;retries is how many times it reloads before giving up, 3 by default. delay is the first wait in milliseconds, 1000 by default, and it doubles for each retry. The defaults wait 1, 2, then 4 seconds.
A plugin can't be installed twice, so remove the default one to change its options:
import { defaultPlugins } from "react-video-kit/player";import { recovery } from "react-video-kit/recovery";const plugins = [ recovery({ retries: 5 }), ...defaultPlugins().filter(({ name }) => name !== "recovery"),];What's retried
Fatal errors marked retryable, which are failures that can pass: lost connections, failed downloads, and licence servers that couldn't be reached. Everything else is reported straight away, such as media-decode, source-no-provider, and drm-denied. Errors lists every code, and which of them are retried.
retryOn replaces that rule. It's only asked about fatal errors:
recovery({ retryOn: ({ code, retryable }) => retryable === true || code === "media-decode",});When retries run out
retrying goes back to false, and the error stays. VideoPlayer shows its error overlay, with an explanation, the error's code, and a Try again button. That button picks up where the failure happened, and plays again if the viewer was watching. It also starts a fresh set of retries.
While retries are running, the default skin hides the overlay and shows the spinner, using data-retrying on the root.
To show your own message, read media.error:
"use client";import * as Video from "react-video-kit";function ErrorMessage() { const error = Video.useVideoState((media) => (media.error?.fatal ? media.error : null)); return error ? ( <div role="alert" className="absolute inset-0 flex flex-col items-center justify-center gap-1 bg-black/60 p-6 text-center text-white"> <p className="text-sm font-medium">Sorry, this video didn't load.</p> <p className="max-w-md text-xs opacity-70">Quote {error.code} if you contact support.</p> </div> ) : null;}export default function FailedSource() { return ( <Video.Root src="/media/broken.mp4" load="visible"> <Video.Media width={640} height={360} poster="/media/poster.jpg" /> <ErrorMessage /> </Video.Root> );}To tell the viewer it's retrying, read the plugin's state:
"use client";import { useVideoState } from "react-video-kit";import { recovery } from "react-video-kit/recovery";function Reconnecting() { const retrying = useVideoState(recovery, (state) => state.retrying); const attempt = useVideoState(recovery, (state) => state.attempt); return retrying ? <p role="status">Reconnecting, try {attempt}</p> : null;}Behaviour
- A retry loads the source at the last position, and plays again if the viewer was watching or had pressed play. A video that was paused stays paused, unless the autoplay plugin starts it, because that runs for each load.
- While
navigator.onLineisfalse, the plugin waits for the browser'sonlineevent in place of a timer. That wait spends no attempt, even when there are none left. attemptreturns to 0 once playback gets more than 5 seconds past the failure. Reachingreadyisn't enough, so a source that fails again right after reloading can't loop forever.- Changing to a different source clears the count, the position, and any retry that's waiting.
- If
srcis a list and playback hasn't started, the player moves to the next source and this plugin isn't involved. Once the whole list has failed, a retry starts it again from the top and the count carries on, so a list whose sources all fail still runs out of retries. - Reloading from your own code when no retry is running starts a fresh set of retries.
- An ad pre-roll doesn't play again after a retry.
- hls.js recovers from media errors before this plugin hears about them.
API
Options
| Option | Type | Description |
|---|---|---|
| retries? | number | How many times to retry a failure before reporting it. |
| delay? | number | The first wait in milliseconds, doubling for each retry. |
| retryOn? | ((error: MediaErrorInfo) => boolean) | Decides which errors are worth retrying. By default, those marked retryable. |
State
Read it with useVideoState(recovery, (state) => …).
| Field | Type | Description |
|---|---|---|
| attempt | number | Retries used so far, back to 0 once playback recovers or the source changes. |
| retrying | boolean | True from a retryable failure until the video is ready again or the plugin gives up. |
Commands
This plugin has no commands of its own.