Skip to main content

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.

note

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 install @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​

PropTypeBehaviour
sourceSourceDescriptionForwarded 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.
optionsPlayerOptionsConstruction-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.
currentTimenumberControlled playback position in seconds. Seeks when it changes.
loopbooleanKept in sync with player.loop while provided.
mutedbooleanKept in sync with player.muted while provided. Pushed only when the value changes, so a viewer who unmutes stays unmuted.
posterstringApplied via player.setPoster() when it changes.
bookmarksBookmark[]Progress-bar markers. Applied via player.setBookmarks() when the array changes (compared by value).

Container​

PropTypeBehaviour
classNamestringClass on the container element.
styleCSSPropertiesInline styles on the container element.
childrenReactNodeRendered 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).

PropPayload
onReady(player: EvePlayer) (fires once, after construction, before any media event)
onPlayNone
onPauseNone
onEndedNone
onTimeUpdate{ currentTime: number }
onReadyStateChange{ readyState: number }
onLoadedMetadata{ size: PlayerSize }
onAudioTrackChange{ track: AudioTrack }
onTextTrackChange{ track: TextTrack }
onErrorPlayerError: { 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 }
onDisposeNone (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.