https://github.com/jimmycallin/react-hook-videojs
Easy React integration of Video.js using hooks.
https://github.com/jimmycallin/react-hook-videojs
hook hooks react react-hooks video videojs
Last synced: 3 months ago
JSON representation
Easy React integration of Video.js using hooks.
- Host: GitHub
- URL: https://github.com/jimmycallin/react-hook-videojs
- Owner: jimmycallin
- License: mit
- Created: 2018-11-28T01:59:06.000Z (over 7 years ago)
- Default Branch: main
- Last Pushed: 2026-03-01T01:32:39.000Z (4 months ago)
- Last Synced: 2026-03-01T02:22:05.591Z (4 months ago)
- Topics: hook, hooks, react, react-hooks, video, videojs
- Language: TypeScript
- Homepage:
- Size: 2.49 MB
- Stars: 56
- Watchers: 2
- Forks: 11
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-github-repos - jimmycallin/react-hook-videojs - Easy React integration of Video.js using hooks. (TypeScript)
README
# react-hook-videojs
> A React hook for integrating Video.js with React 19.
> It handles player setup, disposal, Strict Mode behavior, and options-driven reinitialization with a small, predictable API.
[](https://www.npmjs.com/package/react-hook-videojs)
Live example: [https://jimmycallin.github.io/react-hook-videojs/](https://jimmycallin.github.io/react-hook-videojs/)
PR preview (for open PRs): `https://jimmycallin.github.io/react-hook-videojs/pr-/`
## Quick links
- [Installation](#installation)
- [Quick start](#quick-start)
- [API reference](#api-reference)
- [Recipes](#recipes)
- [React Server Components (Next.js)](#react-server-components-nextjs)
- [Troubleshooting](#troubleshooting)
## Why this hook
- React-first Video.js lifecycle management (mount/init, unmount/dispose)
- Strict Mode-safe behavior in React 19 development
- Predictable reinitialization when options change
- Native `` props, children, and `ref` forwarding
- Minimal API: `const { Video, ready, player } = useVideoJS(...)`
---
## Installation
```bash
npm install react-hook-videojs video.js
# or
pnpm add react-hook-videojs video.js
```
**Peer requirements:** React ≥ 19, Video.js 7 or 8.
---
## Quick start
```tsx
import React, { useMemo } from "react";
import "video.js/dist/video-js.css";
import { useVideoJS } from "react-hook-videojs";
const MyPlayer = () => {
// Always memoize options so the player only reinitializes when values change.
const options = useMemo(
() => ({ sources: [{ src: "https://example.com/video.mp4" }] }),
[],
);
const { Video, ready, player } = useVideoJS(options);
return ;
};
```
`useVideoJS` returns three values:
| Return value | Type | Description |
| ------------ | ----------------------- | -------------------------------------------------------------------- |
| `Video` | `VideoComponent` | React component to render. Accepts all `` props and children. |
| `ready` | `boolean` | `true` once the player has fired its `"ready"` event. |
| `player` | `VideoJsPlayer \| null` | The Video.js player instance when ready, `null` otherwise. |
---
## API reference
### `useVideoJS(videoJsOptions, classNames?)`
```ts
import { useVideoJS } from "react-hook-videojs";
const { Video, ready, player } = useVideoJS(
videoJsOptions, // VideoJsPlayerOptions — must be memoized
classNames, // optional string — extra CSS classes on the element
);
```
**`videoJsOptions`** is passed directly to Video.js — see the [Video.js options reference](https://videojs.com/guides/options) for all available properties.
**Important:** wrap `videoJsOptions` in `React.useMemo` (or define it outside the component).
The hook deep-clones options and reinitializes when the options object changes;
passing a fresh object on every render recreates the player every render.
---
## Recipes
These patterns cover the most common integrations.
### Accessing player events
Use a `useEffect` that depends on the `player` value. The effect runs when the player becomes available and the cleanup function runs when it is disposed.
```tsx
import React, { useEffect, useMemo } from "react";
import { useVideoJS } from "react-hook-videojs";
const MyPlayer = ({ src }: { src: string }) => {
const options = useMemo(() => ({ sources: [{ src }] }), [src]);
const { Video, player } = useVideoJS(options);
useEffect(() => {
if (!player) return;
const onPlay = () => console.log("playing");
const onPause = () => console.log("paused");
const onEnded = () => console.log("ended");
const onTimeUpdate = () => console.log("currentTime", player.currentTime());
player.on("play", onPlay);
player.on("pause", onPause);
player.on("ended", onEnded);
player.on("timeupdate", onTimeUpdate);
return () => {
if (!player.isDisposed()) {
player.off("play", onPlay);
player.off("pause", onPause);
player.off("ended", onEnded);
player.off("timeupdate", onTimeUpdate);
}
};
}, [player]);
return ;
};
```
### Calling player methods imperatively
Use `player` for direct Video.js API calls.
```tsx
const { Video, ready, player } = useVideoJS(options);
const handlePlayPause = () => {
if (!player) return;
if (player.paused()) {
player.play();
} else {
player.pause();
}
};
const handleSeek = (seconds: number) => {
player?.currentTime(seconds);
};
const handleVolume = (level: number) => {
player?.volume(level); // 0.0 – 1.0
};
const handleRate = (rate: number) => {
player?.playbackRate(rate); // e.g. 0.5, 1, 1.5, 2
};
```
### Text tracks (captions / subtitles)
Pass a `` element as a child of ``:
```tsx
const { Video } = useVideoJS(options);
return (
);
```
### Native `` attributes
`Video` forwards all standard `` element attributes:
```tsx
```
### Forwarding a ref to the `` element
```tsx
import React, { useRef } from "react";
const MyPlayer = () => {
const videoRef = useRef(null);
const { Video } = useVideoJS(options);
return ;
};
```
### Switching sources
Update `sources` in options to trigger automatic reinitialization.
```tsx
const [src, setSrc] = useState("https://example.com/video-1.mp4");
const options = useMemo(() => ({ sources: [{ src }] }), [src]);
const { Video } = useVideoJS(options);
return (
<>
setSrc("https://example.com/video-2.mp4")}>
Switch source
>
);
```
### Playback rate menu
Pass `playbackRates` in options to add a playback speed menu to the Video.js control bar:
```tsx
const options = useMemo(
() => ({
sources: [{ src }],
controls: true,
playbackRates: [0.5, 0.75, 1, 1.25, 1.5, 2],
}),
[src],
);
```
### Mount / unmount
Conditionally render `` to mount and unmount the player.
The player is disposed on unmount and reinitialized on remount.
```tsx
const [visible, setVisible] = useState(true);
const { Video } = useVideoJS(options);
return (
<>
{visible && }
setVisible((v) => !v)}>Toggle player
>
);
```
### Poster image
```tsx
const options = useMemo(
() => ({
sources: [{ src }],
poster: "https://example.com/poster.jpg",
}),
[src],
);
```
### React Strict Mode
The hook is designed for React 19 Strict Mode, where components mount/unmount
twice in development. It handles this lifecycle without leaking player instances.
## React Server Components
`react-hook-videojs` is **client-only**.
- Use it inside a component with the `"use client"` directive.
- Do not call `useVideoJS` from a Server Component.
- Pass only serializable props from Server Components to Client Components.
### Recommended App Router pattern
Create a dedicated client wrapper for the player:
```tsx
// app/components/video-player-client.tsx
"use client";
import { useMemo } from "react";
import "video.js/dist/video-js.css";
import { useVideoJS } from "react-hook-videojs";
export function VideoPlayerClient({ src }: { src: string }) {
const options = useMemo(() => ({ sources: [{ src }] }), [src]);
const { Video } = useVideoJS(options);
return ;
}
```
Render that client component from a Server Component:
```tsx
// app/page.tsx (Server Component)
import { VideoPlayerClient } from "./components/video-player-client";
export default async function Page() {
const src = "https://example.com/video.mp4";
return ;
}
```
### RSC notes
- Build `videoJsOptions` in the client component with `useMemo`.
- If options come from the server, pass plain JSON-ish data (strings, numbers,
booleans, arrays, objects) and construct final Video.js options client-side.
- Avoid passing DOM nodes/functions across the server boundary.
---
## Run the example app
```bash
pnpm install
pnpm run dev
```
Open [http://localhost:5173](http://localhost:5173). The example demonstrates:
- Multiple video sources with live switching
- VTT caption track via `` child element
- Controls, autoplay, loop, muted, and poster options
- Mount/unmount lifecycle
- Imperative play/pause, seek, volume, and playback speed controls
- Live player state display (`ready`, `player`, `currentTime`, `duration`, …)
- Full event log (`play`, `pause`, `ended`, `timeupdate`, `error`, …)
### Run the React × Video.js compatibility matrix
```bash
pnpm run test:matrix:local
```
Runs the repository's local compatibility matrix script in a temporary worktree.
---
## Troubleshooting
### Player keeps reinitializing
`videoJsOptions` is probably recreated each render. Memoize it with `useMemo`.
### SSR / RSC error (`window` / DOM access)
`useVideoJS` is client-only. In Next.js / RSC, move it into a client component
and add the `"use client"` directive.
### Next.js App Router warning about non-serializable props
Server Components can only pass serializable props to Client Components. Pass
plain data (like `src`, `poster`, `playbackRates`) and create memoized
`videoJsOptions` in the client wrapper.
### Video.js styles look missing
Import Video.js CSS once in your app:
```tsx
import "video.js/dist/video-js.css";
```
---
## License
MIT © [jimmycallin](https://github.com/jimmycallin)