Error handling
Typed errors, the inline unavailable surface, the error boundary, and retry.
The web player handles two distinct failure classes:
- Playback errors (access denied, not found, network, etc.): surfaced as a
typed
errorfrom the hook, rendered inline as an unavailable surface. - Unexpected render crashes: caught by a
MoviieErrorBoundaryso they never take down the host app.
Inline unavailable surface
<MoviieVideo> renders an inline unavailable surface automatically: no extra
setup. When useMoviiePlayer resolves an error, the component shows a
viewer-facing title and description, plus a Try again button for retryable
errors. This mirrors the gated and error states the iframe embed shows.
const moviie = useMoviiePlayer({ embedId })
// the unavailable surface renders automatically when moviie.error is set
return <MoviieVideo {...moviie} aspectRatio={16 / 9} />Typed errors
Each error carries a code and maps to a typed class exported from
@moviie/player-sdk (re-exported by @moviie/player-react).
| Code | Class | When it occurs | Retryable |
|---|---|---|---|
auth | MoviieAuthError | Invalid or missing publishable key (mvi_pub_*). | |
playback_token | MoviiePlaybackTokenError | A private video needs a valid viewer token. | |
not_found | MoviieNotFoundError | The embed ID does not exist or was removed. | |
bundle_blocked | MoviieBundleBlockedError | The client is not in the embed's allowlist. | |
referrer_blocked | MoviieReferrerBlockedError | The request origin is not in the embed's referrer allowlist. | |
direct_access_blocked | MoviieDirectAccessBlockedError | The video was opened directly by URL while that is blocked. | |
web_distribution_blocked | MoviieWebDistributionBlockedError | Web playback is not enabled for this video. | |
video_not_ready | MoviieVideoNotReadyError | The video is still processing. | ✓ |
subscription_inactive | MoviieSubscriptionInactiveError | The organization's subscription is paused or expired. | |
insufficient_credits | MoviieInsufficientCreditsError | A feature is temporarily unavailable for this organization. | |
network | MoviieNetworkError | The request failed (timeout, unreachable, etc.). | ✓ |
rate_limit | MoviieRateLimitError | Too many requests in a short period. | ✓ |
unknown | Error (fallback) | Any unrecognized error. | ✓ |
Reading and detecting errors
useMoviiePlayer (and useMoviiePlayback) expose error and isLoading:
const { error, isLoading, playback } = useMoviiePlayer({ embedId })
if (error) {
console.log(error.code) // e.g. "bundle_blocked"
console.log(error.message) // human-readable message
}Match by class or by code:
import {
MoviieAuthError,
MoviieBundleBlockedError,
MoviieNetworkError,
} from "@moviie/player-react"
if (error instanceof MoviieAuthError) {
// invalid key: check your publishable key
}
if (error instanceof MoviieNetworkError) {
// transient: a retry is appropriate
}
if (error?.code === "referrer_blocked") {
// configure the embed's allowed referrers in the dashboard
}Custom unavailable UI
To render your own surface instead of the built-in one, branch on error
yourself. resolveUnavailableSurface returns the same per-code title,
description, and canRetry flag the player uses:
import { resolveUnavailableSurface, useMoviiePlayer } from "@moviie/player-react"
function Player({ embedId }: { embedId: string }) {
const moviie = useMoviiePlayer({ embedId })
if (moviie.error) {
const surface = resolveUnavailableSurface(moviie.error)
return (
<div role="alert">
<h2>{surface.title}</h2>
<p>{surface.description}</p>
{surface.canRetry && <button onClick={moviie.retry}>Try again</button>}
</div>
)
}
return <MoviieVideo {...moviie} aspectRatio={16 / 9} />
}errorCodeOf(error) is also exported if you only need the resolved code.
Retry
For retryable errors, the inline surface's Try again button calls
moviie.retry(), which re-triggers the playback fetch. You can call retry
yourself from custom UI (as above) or after invalidating your own cache.
<MoviieErrorBoundary>
<MoviieVideo> already wraps itself in a MoviieErrorBoundary, so an unexpected
render crash shows a styled fallback instead of breaking your app. Wire your
reporter with onError:
<MoviieVideo {...moviie} onError={(error, info) => Sentry.captureException(error)} />Use the boundary directly to protect a larger tree or to supply a custom fallback:
import { MoviieErrorBoundary, MoviieVideo } from "@moviie/player-react"
<MoviieErrorBoundary
onError={(error) => Sentry.captureException(error)}
fallback={({ error, reset }) => (
<div role="alert">
<p>The player ran into a problem.</p>
<button onClick={reset}>Reload</button>
</div>
)}
>
<MoviieVideo {...moviie} aspectRatio={16 / 9} />
</MoviieErrorBoundary>The library never reports for you
@moviie/player-react surfaces errors to the host app and never initializes a
reporter (such as Sentry) itself. Wire onError to your own pipeline.