React SDK
Official @videohati/react SDK — the VideohatiPlayer component plus upload, metadata, and webhook-delivery hooks for React 18+ and Next.js.
@videohati/react gives you a <VideohatiPlayer> component that wraps
@videohati/player, plus hooks for browser uploads, video
metadata, and webhook deliveries. Version 1.0.0-rc.0.
Install
React 18 or newer. The player is a peer dependency, so install both.
npm install @videohati/react @videohati/playerProvider
Wrap your app once. VideohatiProvider shares the API origin — and, for
prototypes, an optional API key — with the hooks.
| Prop | Default | What it does |
|---|---|---|
baseUrl | https://api.videohati.com | API origin. |
apiKey | — | Optional API key for browser calls. Discouraged. |
children | — | Your app. |
import { VideohatiProvider } from "@videohati/react";
<VideohatiProvider baseUrl="https://api.videohati.com">
<App />
</VideohatiProvider>;useVideohatiConfig() returns the current { baseUrl, apiKey? } if you need it.
Never ship a live API key to the browser. Playback uses server-created session
tokens. The apiKey prop is for prototypes with vh_test_ keys only.
Play a video
sessionToken comes from a server-side POST /v1/playback/sessions — use
@videohati/node in your route handler or server action.
"use client";
import { VideohatiPlayer } from "@videohati/react";
export function Lesson({ sessionToken }: { sessionToken: string }) {
return (
<VideohatiPlayer
videoId="01JZ9WV3N8GQ5T2M7K4C6XBARH"
projectId="01JZ9WV3N8GQ5T2M7K4C6XBARF"
sessionToken={sessionToken}
autoplay="muted"
lang="ar"
width={640}
height={360}
onStateChange={(state) => console.log(state)}
/>
);
}VideohatiPlayerProps:
| Prop | Type | What it does |
|---|---|---|
videoId | string (required) | The video to play. |
projectId | string (required) | The project that owns the video. |
sessionToken | string (required) | Token from a server-side playback session. |
autoplay | boolean | "muted" | "muted" autoplays muted; true tries unmuted; default false. |
width | number | Player width in pixels. |
height | number | Player height in pixels. |
lang | "ar" | "en" | UI language. Defaults to Arabic. |
viewerText | string | Watermark text; falls back to the session's viewer text. |
apiOrigin | string | API origin override (staging, local overrides). |
className | string | Class on the container element. |
style | CSSProperties | Inline style on the container. |
onStateChange | (state) => void | Fires on every playback state change. |
onError | ({ code, message }) => void | Fires on a playback error. |
onTimeUpdate | ({ currentTime, duration }) => void | Fires as the playhead moves. |
playerRef | (player | null) => void | Receives the underlying player instance — call play/seek/setQuality on it. |
Upload from the browser
useVideohatiUpload({ projectId?, partConcurrency? }) runs the same multipart
flow as the Node SDK (create → start → part uploads → complete), 3 parts
concurrently by default.
It returns:
| Field | Type | What it is |
|---|---|---|
upload | (file: File) => Promise<UploadResult | null> | Starts the upload. |
progress | UploadProgress | null | Live byte and part counts. |
state | "idle" | "creating" | "uploading" | "completing" | "done" | "error" | Current phase. |
error | { status, code, message } | null | The failure, if any. |
video | UploadResult | null | The completed video, once state is done. |
reset | () => void | Returns the hook to idle (ignored while busy). |
"use client";
import { useVideohatiUpload } from "@videohati/react";
function Uploader({ projectId }: { projectId: string }) {
const { upload, progress, state, error, video, reset } = useVideohatiUpload({
projectId,
});
return (
<form onSubmit={(e) => e.preventDefault()}>
<input
type="file"
accept="video/*"
disabled={state === "creating" || state === "uploading" || state === "completing"}
onChange={(e) => e.target.files?.[0] && upload(e.target.files[0])}
/>
{progress && (
<progress value={progress.uploadedBytes} max={progress.totalBytes} />
)}
{state === "done" && <p>Uploaded: {video?.videoId}</p>}
{state === "error" && <p role="alert">{error?.message}</p>}
{(state === "done" || state === "error") && (
<button type="button" onClick={reset}>
Upload another
</button>
)}
</form>
);
}Read video metadata
useVideohatiVideo(videoId, { projectId?, pollIntervalMs? }) reads a video and
keeps it fresh.
import { useVideohatiVideo } from "@videohati/react";
const { video, error, isLoading, refresh } = useVideohatiVideo(videoId, {
projectId,
});It is cached across mounts, revalidated on mount, and polled every 5 seconds
while video.state is uploading, uploaded, or encoding — so a processing
video updates on its own. Set pollIntervalMs: 0 to disable polling.
clearVideohatiVideoCache() resets the shared cache.
Webhook deliveries
useWebhookDeliveries(projectId, endpointId, { state?, eventType?, limit? })
reads one page of delivery attempts for a webhook endpoint.
import { useWebhookDeliveries } from "@videohati/react";
const { deliveries, nextCursor, error, isLoading, refresh } = useWebhookDeliveries(
projectId,
endpointId,
{ limit: 20 },
);Same caching as useVideohatiVideo. clearWebhookDeliveriesCache() resets its
cache.
Next.js App Router
The player and every hook are client-only — put them in a component with
"use client". Mint the session token on the server (a route handler or server
action) with @videohati/node, then pass it down as a prop. The full pattern is
in the Next.js embed guide.
Error handling
Hooks expose errors as { status, code, message } — code is the API's machine
code (for example account_not_approved, video_not_found). The player reports
playback errors through onError.
Verify webhooks
Webhook signature verification is server-side — use @videohati/node's
verifyWebhookSignature in your route handler. See the
webhooks guide.
Node.js SDK
Official @videohati/node SDK — create a client, upload video, create playback sessions, manage webhooks, and handle errors from a Node.js server.
Laravel SDK
Official videohati/laravel SDK — install, configure, upload video, create playback sessions, verify webhooks, and embed the player from a Laravel app.