VideohatiDocs
SDKs

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/player

Provider

Wrap your app once. VideohatiProvider shares the API origin — and, for prototypes, an optional API key — with the hooks.

PropDefaultWhat it does
baseUrlhttps://api.videohati.comAPI origin.
apiKeyOptional API key for browser calls. Discouraged.
childrenYour 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:

PropTypeWhat it does
videoIdstring (required)The video to play.
projectIdstring (required)The project that owns the video.
sessionTokenstring (required)Token from a server-side playback session.
autoplayboolean | "muted""muted" autoplays muted; true tries unmuted; default false.
widthnumberPlayer width in pixels.
heightnumberPlayer height in pixels.
lang"ar" | "en"UI language. Defaults to Arabic.
viewerTextstringWatermark text; falls back to the session's viewer text.
apiOriginstringAPI origin override (staging, local overrides).
classNamestringClass on the container element.
styleCSSPropertiesInline style on the container.
onStateChange(state) => voidFires on every playback state change.
onError({ code, message }) => voidFires on a playback error.
onTimeUpdate({ currentTime, duration }) => voidFires as the playhead moves.
playerRef(player | null) => voidReceives 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:

FieldTypeWhat it is
upload(file: File) => Promise<UploadResult | null>Starts the upload.
progressUploadProgress | nullLive byte and part counts.
state"idle" | "creating" | "uploading" | "completing" | "done" | "error"Current phase.
error{ status, code, message } | nullThe failure, if any.
videoUploadResult | nullThe completed video, once state is done.
reset() => voidReturns 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.