React wrapper
@admmedia/eveplayer-react is the official React wrapper, maintained in a
separate repository. It is deliberately thin: it constructs one
EvePlayer per mount, keeps a handful of props in sync with the live
instance, forwards every player event to an on* callback, and exposes the
instance through a ref.
The player itself (playback, controls, ABR, HLS/DASH/CMAF) lives in
@admmedia/eveplayer. The wrapper adds no playback behaviour; it only maps
React props, refs and lifecycle onto the player's API.
Installation
- npm
- Yarn
- pnpm
npm install @admmedia/eveplayer-react @admmedia/eveplayer
yarn add @admmedia/eveplayer-react @admmedia/eveplayer
pnpm add @admmedia/eveplayer-react @admmedia/eveplayer
@admmedia/eveplayer, react and react-dom are peer dependencies:
install them alongside the wrapper. React 17, 18 and 19 are supported.
Import the player stylesheet once, anywhere in your client bundle:
import '@admmedia/eveplayer/style.css';
<EvePlayer>
import { EvePlayer } from '@admmedia/eveplayer-react';
import '@admmedia/eveplayer/style.css';
export function Demo() {
return (
<EvePlayer
style={{ maxWidth: 960 }}
options={{ widescreen: true, autoplay: false, muted: false }}
source={{
sources: [
{ src: 'https://example.com/video.m3u8', type: 'application/x-mpegURL' },
],
poster: 'https://example.com/poster.jpg',
}}
onPlay={() => console.log('playing')}
onTimeUpdate={({ currentTime }) => console.log('t =', currentTime)}
onError={(err) => console.error(err.category, err.code, err.message)}
/>
);
}
The component renders a single container element that the player mounts into. Every prop is optional.
Media & configuration
| Prop | Type | Behaviour |
|---|---|---|
source | SourceDescription | Forwarded to player.setSource() whenever it changes, compared by value (inline objects are fine). An empty sources array pauses the player instead of raising a "no compatible source" error. |
options | PlayerOptions | Construction-time options (widescreen, hideControls, playbackRates, language, labels, pip, chapters, …). Read once, when the player is created. Change the component key to rebuild with new options. |
currentTime | number | Controlled playback position in seconds. Seeks when it changes. |
loop | boolean | Kept in sync with player.loop while provided. |
muted | boolean | Kept in sync with player.muted while provided. Pushed only when the value changes, so a viewer who unmutes stays unmuted. |
poster | string | Applied via player.setPoster() when it changes. |
bookmarks | Bookmark[] | Progress-bar markers. Applied via player.setBookmarks() when the array changes (compared by value). |
Container
| Prop | Type | Behaviour |
|---|---|---|
className | string | Class on the container element. |
style | CSSProperties | Inline styles on the container element. |
children | ReactNode | Rendered inside the container, after Video.js's own DOM (e.g. absolutely-positioned overlays). |
className and style are where you size the player: with
options={{ widescreen: true }} the container is a 16:9 frame you can
reshape (style={{ aspectRatio: '4 / 3' }}), without it the box follows the
video. See Player sizing.
Event callbacks
Each maps 1:1 to an EvePlayer event and receives its payload verbatim
(see PlayerEventMap). Callbacks are read
from the latest render, so a fresh inline function each render is fine (the
player is not re-subscribed).
| Prop | Payload |
|---|---|
onReady | (player: EvePlayer) (fires once, after construction, before any media event) |
onPlay | None |
onPause | None |
onEnded | None |
onTimeUpdate | { currentTime: number } |
onReadyStateChange | { readyState: number } |
onLoadedMetadata | { size: PlayerSize } |
onAudioTrackChange | { track: AudioTrack } |
onTextTrackChange | { track: TextTrack } |
onError | PlayerError: { code, message, category } |
onSourceSet | { source: SourceDescription } |
onRateChange | { playbackRate: number } |
onVolumeChange | { volume: number; muted: boolean } |
onFullscreenChange | { isFullscreen: boolean } |
onPipChange | { isPip: boolean } |
onQualityChange | { level: QualityLevel } |
onChapterEnterCue | { cue: ChapterCue } |
onGapSkip | { from: number; to: number } |
onStall | { duration: number } |
onDispose | None (fires on unmount) |
onGapSkip and onStall are independent streaming-health signals, not always
correlated; one can fire without the other. onGapSkip fires when
@videojs/http-streaming auto-corrects a buffer gap (a manifest discontinuity or a
missing segment) on its own; it's not an error. onStall fires when playback resumes
after any mid-playback rebuffer, gap-related or not (e.g. plain network congestion);
it never fires for the initial buffering wait before the first onPlay.
Imperative access (ref)
The forwarded ref resolves to the live EvePlayer instance, or null
before mount. Use it for anything imperative (seeking, fullscreen, quality
control, reading state):
import { useRef } from 'react';
import { EvePlayer, type EvePlayerCore } from '@admmedia/eveplayer-react';
function WithControls() {
const ref = useRef<EvePlayerCore | null>(null);
return (
<>
<EvePlayer ref={ref} source={/* … */} />
<button onClick={() => ref.current?.play()}>Play</button>
<button onClick={() => ref.current?.requestFullscreen()}>Fullscreen</button>
<button onClick={() => console.log(ref.current?.getQualityLevels())}>
Log qualities
</button>
</>
);
}
onReady hands you the same instance if you prefer a callback to a ref.
useEvePlayer hook
For full control over the container element, use the hook directly. It takes a
ref to the mount element and the same props object as the component, and returns
the instance (or null until created). <EvePlayer> is just this hook plus a
forwardRef container.
import { useRef } from 'react';
import { useEvePlayer } from '@admmedia/eveplayer-react';
import '@admmedia/eveplayer/style.css';
function CustomShell() {
const containerRef = useRef<HTMLDivElement>(null);
const player = useEvePlayer(containerRef, {
options: { widescreen: true },
source: { sources: [{ src: '/video.m3u8', type: 'application/x-mpegURL' }] },
onPlay: () => console.log('playing'),
});
return <div ref={containerRef} className="my-player-frame" />;
}
Recipes
Rebuild with new options
options is construction-only, matching the player. Change the key to get a
fresh instance:
<EvePlayer key={language} options={{ language }} source={source} />
Controlled play / pause
Drive playback from the ref in an effect keyed on your own state:
useEffect(() => {
if (!player) return;
if (shouldPlay) void player.play();
else player.pause();
}, [player, shouldPlay]);
Chapters
<EvePlayer
source={{
sources,
textTracks: [{ kind: 'chapters', src: '/chapters.vtt', srclang: 'en' }],
}}
onChapterEnterCue={({ cue }) => setActiveChapter(cue.id)}
/>
See Text tracks & captions for the side-loading details.
SSR / Next.js
The component renders a plain <div> on the server and constructs the player in
an effect, so it is client-only by construction (no
dynamic(..., { ssr: false }) wrapper needed). Keep the
import '@admmedia/eveplayer/style.css' in a client module or your global
stylesheet. See SSR / Next.js for the underlying pattern.
TypeScript
Every public type from @admmedia/eveplayer that appears in a prop signature
(SourceDescription, PlayerOptions, PlayerError, QualityLevel,
AudioTrack, TextTrack, Bookmark, ChapterCue, PlayerEventMap,
PlayerSize, EvePlayer) is re-exported from @admmedia/eveplayer-react,
so you can import everything from one place. The wrapper also exports
EvePlayerProps and EvePlayerCallbacks.
The wrapper tracks the player's major line: eveplayer-react@1.x targets
eveplayer@1.x.