Skip to content

ReferenceMedia

PlayerJsVideo

Video component that plays any embed implementing the player.js protocol, such as Mux Player, Gumlet, FrameRate, Livid, Bunny Stream, and Streamable

player.js is a postMessage protocol for controlling embedded players, not a video host. Any embed that implements its receiver side plays through this one component: Mux Player’s iframe embed, Gumlet, FrameRate, Livid, Bunny Stream, Streamable, and others. Commands are posted to the embed directly, so no third-party script loads.

Import

pnpm add @videojs/playerjs-video
import { PlayerJsVideo } from '@videojs/react/media/playerjs-video';

Load a source

src takes the embed URL of any player.js receiver: an absolute http or https URL, with protocol-relative URLs (//host/…) read as https. Nothing inspects the URL to decide whether it speaks player.js; choosing this component is the opt-in. A src that isn’t an http(s) URL fails with MEDIA_ERR_SRC_NOT_SUPPORTED, and an embed that doesn’t implement the protocol never reports ready, so the element stays at readyState 0.

<PlayerJsVideo src="https://play.gumlet.io/embed/64bfb0913ed6e5096d66dc1e" />

Feature detection

Embeds implement the spec to different degrees. Each one lists the methods and events it supports in its ready message, and every command is checked against that list:

  • A missing getter leaves its value at the default, such as a duration of NaN. Some embeds implement a getter but can’t answer it yet: Streamable reports no duration until playback starts.
  • A missing setter is a no-op; the value stays wherever the embed has it.

supports(type, name) reports whether the embed advertised a method or event. type is 'method' or 'event', and name is one player.js name or an array of names that must all be supported. It returns false until the embed reports ready.

onLoadedMetadata fires when the embed reports ready, so its handler is the first point where the answer is meaningful. event.currentTarget is the playback adapter:

<PlayerJsVideo
  src="https://play.gumlet.io/embed/64bfb0913ed6e5096d66dc1e"
  onLoadedMetadata={(event) => {
    const canSeek = event.currentTarget.supports('method', 'setCurrentTime');
  }}
/>

Controls

Without controls, the element ignores pointer events, so a player skin above it receives every click. With controls, the embed’s own chrome takes pointer input.

player.js itself can’t hide an embed’s chrome, but for the services it recognizes, the element does it through the embed URL, as far as each service allows:

Service Without controls
Mux Player (player.mux.com) Nothing: its controls are hidden with the --controls CSS property, which the iframe doesn’t expose. Under a skin its control bar still stays out of sight, since the frame never gets the hover that reveals it, but its center play button shows while paused
Gumlet disable_player_controls=true: only the large center play button stays
FrameRate show_controls=0 and initial_play_btn=0: no control bar and no large start button
Livid controls=0: no chrome at all, the play button included. Livid applies it to videos on Pro and Premium accounts only
Bunny Stream No parameter hides the control bar, so it’s shrunk instead: compactControls=true, showSpeed=false, showHeatmap=false, chromecast=false, disableAirPlay=true
Streamable nocontrols=1: no chrome at all

Other services get their URL untouched; pass whatever they offer under source.engine.playerJs.

URL parameters

For the recognized services, autoplay, muted (either muted or defaultMuted), loop, and preload are written onto the embed URL in the service’s own spelling, and turned off explicitly where the service defaults to on: Gumlet and Streamable loop, and Bunny Stream autoplays. Livid and Mux Player also take preload as the element’s own value. The embed reads its URL once, so changing these later reaches the embed over the protocol (autoplay, muted, loop) or not until the next source (controls, preload); rebuilding the frame for them would restart the video.

Every other parameter a service documents works too, in either of two places. Written into src, a parameter is kept as written, even over one the element would derive. Under source.engine.playerJs, it overrides both. A parameter a service reads from the URL hash, like Livid’s start time #t=25, goes in src.

<PlayerJsVideo src="https://player.mediadelivery.net/embed/759/eb1c4f77-0cda-46be-b47d-1118ad7c2ffe?t=30s&captions=en" />

Behavior

  • autoplay, muted (seeded by defaultMuted), and loop are also applied over the protocol once the embed reports ready, which is how they reach services the element doesn’t recognize. An embed without setLoop is looped by replaying it on ended.
  • volume (0 to 1, sent as the spec’s 0 to 100), muted, and seeking through currentTime use the spec’s methods.
  • playbackRate reaches only embeds that advertise setPlaybackRate, an extension to the spec that Gumlet implements.
  • player.js reports no buffering, so a started embed counts as playing.
  • No text tracks: textTracks stays empty.
  • The protocol has no fullscreen command, so the element’s own requestFullscreen() fullscreens the iframe.
  • poster and playsInline are stored but have no effect on the embed. preload reaches Livid and Mux Player as is and Bunny Stream as preload=false when it’s "none", and no other service.
  • An embed error with code 1 surfaces as MEDIA_ERR_SRC_NOT_SUPPORTED. Codes 2 and 3 mean a command wasn’t supported; they’re ignored, with a warning in development. Any other code is a fatal error carrying the embed’s message.

Examples

Basic Usage

import { PlayerJsVideo } from '@videojs/react/media/playerjs-video';

export default function BasicUsage() {
  return (
    <div className="playerjs-video">
      <PlayerJsVideo src="https://play.gumlet.io/embed/64bfb0913ed6e5096d66dc1e" controls />
    </div>
  );
}

API Reference

Props

Accepts these Video.js-specific props:

PropTypeDefaultDetails
autoplaybooleanfalse
controlsbooleanfalse
defaultMutedbooleanfalse
loopbooleanfalse
mutedbooleanfalse
playsInlinebooleantrue
posterstring''
preloadMediaPreloadType'metadata'
sourceobjectnull
srcstring''

Engine options

source.engine.playerJs

player.js standardizes the messages an embed answers, not its URL, so there are no shared parameters. Every key under source.engine.playerJs is written onto the embed URL’s query as given, for whichever service serves it — anything in Gumlet’s, Bunny Stream’s, or Livid’s parameter lists, for example. Values are stringified (true becomes 'true'), an array repeats the parameter once per value, and null removes a parameter the URL would otherwise carry. These override both src and the parameters the element derives from its props. Changing them reloads the embed. Media Sources covers how engine options fit into a structured source.

referrerPolicy never reaches the URL; it sets the referrerpolicy of the iframe the component renders.

Prefer the autoplay, loop, muted, controls, and preload props over their URL spellings: the props also reach the embed over the protocol and keep the element’s state in step with it.

<PlayerJsVideo
  controls
  source={{
    src: 'https://play.gumlet.io/embed/64bfb0913ed6e5096d66dc1e',
    engine: { playerJs: { player_color: '#ff0000', disabled_player_control: ['cast', 'pip'] } },
  }}
/>
OptionTypeDetails
referrerPolicyReferrerPolicy | undefined

Refs

ref

Forwards to the rendered <iframe>, an HTMLIFrameElement. The iframe cannot play or seek; use mediaRef for playback.

const ref = useRef<HTMLIFrameElement>(null);

<PlayerJsVideo ref={ref} />

mediaRef

Receives the PlayerJsAdapter that drives the embed, which exposes media methods and properties such as play() and currentTime. It is set once the adapter has attached to the iframe, so its target is already that iframe, and it is cleared when the iframe unmounts.

import type { PlayerJsAdapter } from '@videojs/playerjs-video';

const mediaRef = useRef<PlayerJsAdapter>(null);

<PlayerJsVideo mediaRef={mediaRef} />

// In an event handler
mediaRef.current?.play();

Events

Handle standard media events with React event props such as onPlay and onTimeUpdate. The adapter dispatches them, not the iframe. For events without a React prop, call addEventListener on the adapter from mediaRef.