React JS

Play Gumlet videos in React with the embed player or the native player.

React has two Gumlet players.

  • Embed player (@gumlet/react-embed-player) mounts the Gumlet player in an iframe. You pass a video ID. The player UI, DRM, and Insights come with the embed.
  • Native player (@gumlet/react-hls-player) renders a <video> element and plays HLS and DASH with Shaka Player. This is the React HLS player.

Which player to use

Embed playerNative player
Package@gumlet/react-embed-player@gumlet/react-hls-player
What it rendersThe Gumlet player in an iframeA <video> element in your component
How you select the videoGumlet video IDHLS or DASH URL from the dashboard (main.m3u8 or main.mpd)
Player interfaceGumlet controls, end screens, forms, and CTAsNative video controls, or controls you build
Next.jsClient-only import so the iframe mounts in the browserRender it in the browser. Shaka runs on the client
DRMThe embed requests licenses. Pass drm_token and expires when the video is protectedPass Widevine or FairPlay license URLs on drm. See License Server Setup
AnalyticsBuilt in. Pass viewer fields such as gm_user_idBuilt in. Insights is on by default
Control from Reactplay(), pause(), seek, and volume on the player refThe video element (playerRef) and the Shaka instance (getPlayerRef)
Ads, watermark, schema.org, Google Analytics, Facebook PixelProps on the embed componentAdd these in your app around the video element

Use the embed player when the page should show the Gumlet player: its controls, end screens, forms, CTAs, and Next.js setup.

Use the native player when the video element should live in your React tree. That covers custom controls, a custom layout, and direct access to Shaka.

Embed player

NPM

@gumlet/react-embed-player mounts the Gumlet player in React and Next.js. Server-rendered pages load the player in the browser.

Features

  • Server-Side Rendering Support: Fully compatible with SSR frameworks like Next.js
  • Video Streaming: High-quality video playback with adaptive bitrate streaming
  • DRM Protection: Support for Widevine & FairPlay DRM
  • Analytics Integration: Built-in Gumlet Insights for video analytics
  • Responsive Design: Mobile-first responsive video player
  • Multiple Video Formats: Support for various video formats and streaming protocols
  • Customizable Player: Extensive customization options for player appearance and behavior

Install the embed player

npm install @gumlet/react-embed-player

or

yarn add @gumlet/react-embed-player

Usage

Basic Implementation

import React from 'react';
import { GumletPlayer } from '@gumlet/react-embed-player';

function VideoPlayer() {
  return (
    <GumletPlayer
      videoID="64bfb0913ed6e5096d66dc1e"
      title="My Video Title"
      style={{
        height: "500px",
        width: "100%",
        position: "relative"
      }}
      autoplay={false}
      preload={true}
      muted={false}
    />
  );
}

export default VideoPlayer;

Advanced Implementation with SSR

import React, { useRef, useEffect, useState } from 'react';
import { GumletPlayer } from '@gumlet/react-embed-player';

function AdvancedVideoPlayer() {
  const playerRef = useRef(null);
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  const handlePlay = () => {
    if (playerRef.current) {
      playerRef.current.play();
    }
  };

  const handlePause = () => {
    if (playerRef.current) {
      playerRef.current.pause();
    }
  };

  if (!isClient) {
    return <div>Loading player...</div>;
  }

  return (
    <div>
      <GumletPlayer
        ref={playerRef}
        videoID="your-video-id"
        title="Advanced Video Player"
        style={{
          height: "500px",
          width: "100%",
          position: "relative"
        }}
        schemaOrgVideoObject={{
          "@context": "https://schema.org",
          "@type": "VideoObject",
          "name": "My Video",
          "description": "Video description",
          "embedUrl": "https://play.gumlet.io/embed/your-video-id"
        }}
        autoplay={false}
        preload={true}
        muted={false}
        onReady={() => console.log('Player ready')}
        onPlay={() => console.log('Video started playing')}
        onPause={() => console.log('Video paused')}
        onEnded={() => console.log('Video ended')}
        onTimeupdate={(data) => console.log('Current time:', data.seconds)}
        onProgress={(data) => console.log('Loading progress:', data.percent)}
      />
      
      <div style={{ marginTop: '20px' }}>
        <button onClick={handlePlay}>Play</button>
        <button onClick={handlePause}>Pause</button>
      </div>
    </div>
  );
}

export default AdvancedVideoPlayer;

Next.js Implementation

import dynamic from 'next/dynamic';

const GumletPlayer = dynamic(
  () => import('@gumlet/react-embed-player').then(mod => mod.GumletPlayer),
  { ssr: false }
);

function NextjsVideoPlayer() {
  return (
    <GumletPlayer
      videoID="your-video-id"
      title="Next.js Video Player"
      style={{
        height: "500px",
        width: "100%",
        position: "relative"
      }}
      autoplay={false}
      preload={true}
    />
  );
}

export default NextjsVideoPlayer;

Embed player props

PropTypeDescriptionDefault
videoIDstringRequired - Video ID generated after processing on Gumlet—
titlestringTitle of the iframe"Gumlet video player"
styleobjectCSS styles for the iframe container{padding:"56.25% 0 0 0", position:"relative"}
schemaOrgVideoObjectobjectSchema.org structured data object{}
autoplaybooleanShould the video autoplayWorkspace default
preloadbooleanShould the video preloadWorkspace default
mutedbooleanShould the video be mutedWorkspace default
loopbooleanShould the video loopWorkspace default
thumbnailstringURL encoded thumbnail/poster URLAsset default
drm_tokenstringDRM token for protected contentnull
expiresnumberToken expiry time (epoch millis)null
vast_tag_urlstringURL encoded VAST tag URL for adsnull
start_high_resboolean(Deprecated - use lock_highest_res instead.) Start in highest resolutionfalse
lock_highest_resbooleanLock the video playback to the highest resolution.false
disable_seekbooleanDisable seek functionalityfalse
disable_player_controlsbooleanHide all player controlsfalse
watermark_textstringWatermark text overlaynull
facebook_pixel_idstringFacebook Pixel ID for analyticsnull
ga_tracking_idstringGoogle Analytics tracking IDnull
gm_user_idstringUser ID for Gumlet Insightsnull
gm_user_namestringUser name for Gumlet Insightsnull
gm_user_emailstringUser email for Gumlet Insightsnull
gm_custom_data_1stringCustom data for Gumlet Insightsnull
tnumberStart time in secondsnull

Methods

The player provides several methods to control playback programmatically:

Playback Control

  • play() - Start playing the video
  • pause() - Pause the video
  • getPaused() - Check if video is paused (returns boolean)

Audio Control

  • mute() - Mute the video
  • unmute() - Unmute the video
  • getMuted() - Check if video is muted (returns boolean)
  • setVolume(volume) - Set volume (0-100)
  • getVolume() - Get current volume (returns 0-100)

Time Control

  • getCurrentTime() - Get current playback time in seconds
  • setCurrentTime(seconds) - Seek to specific time
  • getDuration() - Get total video duration in seconds

Playback Rate

  • setPlaybackRate(rate) - Set playback speed
  • getPlaybackRate() - Get current playback rate

📡 Event Callbacks

The player emits various events that you can listen to:

Playback Events

  • onReady - Player is ready for interaction
  • onPlay - Video starts playing
  • onPause - Video is paused
  • onEnded - Video playback finished
  • onError - An error occurred

Progress Events

  • onProgress - Video loading progress
    { percent: 0.8 }
    
  • onTimeupdate - Current playback time update
    { seconds: 10, duration: 40 }
    
  • onSeeked - User seeked to different position
    { seconds: 10, duration: 50 }
    

Player State Events

  • onFullScreenChange - Fullscreen mode changed
    { isFullScreen: true }
    
  • onPipChange - Picture-in-picture mode changed
    { isPIP: true }
    
  • onVolumeChange - Volume level changed
    { volume: 50 }
    
  • onPlaybackRateChange - Playback rate changed
  • onAudioChange - Audio track changed
  • onQualityChange - Video quality changed

Custom Analytics

<GumletPlayer
  videoID="your-video-id"
  gm_user_id="user123"
  gm_user_name="John Doe"
  gm_user_email="john@example.com"
  gm_custom_data_1="custom-value"
  ga_tracking_id="GA-XXXXX-X"
  facebook_pixel_id="123456789"
/>

Development

  1. Clone the repository
git clone https://github.com/gumlet/react-embed-player-ssr-example.git
cd react-embed-player-ssr-example
  1. Install dependencies
npm install
  1. Start the development server
npm run dev
  1. Open http://localhost:3000 in your browser

📝 Examples

This repository includes several examples:

  • Basic Player - Simple video player implementation
  • SSR Player - Server-side rendering compatible player
  • Next.js Player - Next.js specific implementation
  • Analytics Player - Player with analytics integration
  • Custom Controls - Player with custom control implementation

🔧 Troubleshooting

Common Issues

  1. Player not loading in SSR environment

    • Use dynamic imports with ssr: false
    • Check for window object availability
  2. Video not playing

    • Verify the videoID is correct
    • Check the browser console for errors
    • Ensure proper CORS settings
  3. DRM content not working

    • Check browser DRM support
    • Check DRM credentials in DRM settings

Check out the Github repository here: https://github.com/gumlet/react-embed-player-ssr-example

DRM Playback

Please note, this package automatically handles DRM playback and there is no extra configuration required on the player side. If you are facing any issues, please contact our support.

Native player (HLS player)

NPM

@gumlet/react-hls-player 3.0.0 is the native React player for Gumlet HLS and DASH, live and VOD. src is the HLS or DASH URL from the asset page in the Gumlet dashboard. Playback uses Shaka Player 5.1 with the same ABR, retry, and buffering settings as the Gumlet embed player. Widevine and FairPlay use Gumlet's license helpers. Gumlet Insights is on by default.

Package source: github.com/gumlet/react-hls-player.

Install the native player

npm i @gumlet/react-hls-player

Requires React 18+ and React DOM 18+. The package depends on shaka-player 5.1.

Version 3 plays streams with Shaka Player. Replace hlsConfig with playerConfig, getHLSRef with getPlayerRef, and preferNativeHls with drm.

Basic playback

import React from 'react';
import { createRoot } from 'react-dom/client';
import ReactHlsPlayer from '@gumlet/react-hls-player';

createRoot(document.getElementById('app')).render(
  <ReactHlsPlayer
    src="https://video.gumlet.io/5f462c1561cf8a766464ffc4/635789f017629894d4d125a4/main.m3u8"
    autoPlay={false}
    muted
    controls
    playsInline
    loop
    width="100%"
    height="auto"
  />
);

Shaka loads src. Use a Gumlet HLS or DASH URL from the asset page in the dashboard. The path is /{collection_id}/{asset_id}/main.m3u8 for HLS or main.mpd for DASH. A missing collection id or asset id is reported with onError and thrown before Shaka is created. The component leaves the video element's src unset and hands the URL to Shaka.

autoPlay calls play() after load() resolves. Pair it with muted when the browser blocks autoplay with sound. loop, playsInline, poster, preload, crossOrigin, width, and height are normal video attributes.

Changing muted or autoPlay updates the element and keeps the current Shaka instance. Changing src or drm creates a new Shaka instance.

Callbacks

Pass on* props for playback events. A new function identity on each render does not rebuild the player.

import React, { useState } from 'react';
import ReactHlsPlayer from '@gumlet/react-hls-player';

function PlayerWithLog({ src }) {
  const [log, setLog] = useState([]);

  function append(name, payload) {
    setLog((rows) => [
      ...rows,
      payload == null ? name : `${name} ${JSON.stringify(payload)}`,
    ]);
  }

  return (
    <>
      <ReactHlsPlayer
        src={src}
        controls
        muted
        playsInline
        onReady={() => append('onReady')}
        onPlay={() => append('onPlay')}
        onPause={() => append('onPause')}
        onPlaying={() => append('onPlaying')}
        onEnded={() => append('onEnded')}
        onWaiting={() => append('onWaiting')}
        onTimeUpdate={({ currentTime, duration }) =>
          append('onTimeUpdate', { currentTime, duration })
        }
        onProgress={({ percent }) => append('onProgress', { percent })}
        onSeeked={({ currentTime, duration }) =>
          append('onSeeked', { currentTime, duration })
        }
        onVolumeChange={({ volume, muted }) =>
          append('onVolumeChange', { volume, muted })
        }
        onManifestParsed={() => append('onManifestParsed')}
        onQualityChange={({ level, height, bitrate }) =>
          append('onQualityChange', { level, height, bitrate })
        }
        onError={(error) => append('onError', error)}
      />
      <pre>{log.join('\n')}</pre>
    </>
  );
}

onReady and onManifestParsed both fire when player.load() resolves. onQualityChange runs on Shaka adaptation. bitrate is the active variant's bandwidth. onError reports Shaka errors (source: 'shaka') and media-element errors (source: 'video').

Video element and Shaka instance

PropWhat you get
playerRef / refThe <video> element (play(), pause(), currentTime)
getPlayerRefThe Shaka Player. Called with null when that instance is destroyed.

playerRef is optional. The component keeps an internal ref when you omit it. The active player is also window.shaka and window.shakaPlayer.

import React from 'react';
import ReactHlsPlayer from '@gumlet/react-hls-player';

function MyCustomComponent() {
  const playerRef = React.useRef(null);

  return (
    <ReactHlsPlayer
      playerRef={playerRef}
      getPlayerRef={(player) => {
        if (player == null) {
          return;
        }
      }}
      src="https://video.gumlet.io/5f462c1561cf8a766464ffc4/635789f017629894d4d125a4/main.m3u8"
      controls
    />
  );
}

DRM

Generate license and certificate URLs with License Server Setup, then pass them on drm.

FairPlay is used when both fairplayLicenseUri and fairplayCertificateUri are set. Playback stays on Shaka MSE (streaming.useNativeHlsForFairPlay: false). License requests are JSON { spc } and responses are JSON { ckc }. When both FairPlay URIs are set, that load uses FairPlay.

Otherwise widevineLicenseUri configures com.widevine.alpha. widevineRobustness is L1, L2, custom, or omitted for the default ladder. organizationId and collectionId apply the same organization overrides as the embed player.

<ReactHlsPlayer
  src={src}
  drm={{
    widevineLicenseUri: 'https://widevine.gumlet.com/licence/<ORG_ID>/.....',
    widevineRobustness: 'L1',
    organizationId: 'org-id',
    collectionId: 'collection-id',
    fairplayLicenseUri: 'https://fairplay.gumlet.com/licence/<ORG_ID>/.....',
    fairplayCertificateUri: 'https://fairplay.gumlet.com/certificate/<ORG_ID>',
  }}
/>

Insights

Insights starts after the playback URL is parsed. enableInsights defaults to true. The collection id in src is the Insights workspace_id. customVideoId is the asset id unless you pass insights.customVideoId.

<ReactHlsPlayer
  src="https://video.gumlet.io/collection-id/asset-id/main.m3u8"
  insights={{
    property_id: 'property-id',
    customVideoTitle: 'Episode 3',
    customData1: 'experiment-a',
    userId: 'viewer-1',
  }}
/>

customPlayerName is gumlet-react-hls-player and customPlayerVersion is this package's version. Those values stay set by the package. workspace_id is derived from the URL and is not an insights field.

insights accepts the fields @gumlet/insights-js-core documents:

  • Session and page: property_id, debug, test, page_url, sessionID, userID, sendSessionRequest, deviceData, playerData, disable_analytics_v2, getUUID, customPageType, experimentName
  • Video: customVideoId, customVideoTitle, customVideoDurationMillis, customVideoSeries, customVideoProducer, customVideoLanguage, customVideoVariant, customVideoVariantName, customContentType, customEncodingVariant
  • User: userId, userName, userEMail or userEmail, userPhone, userProfileImage, address fields, userCity, userState, userCountry, userZipcode
  • Slots: customData1 through customData10

The Insights dashboard stores a smaller set of these fields. See Custom Data Options for the fields that appear in breakdowns.

Turn Insights off with enableInsights={false}. The playback URL is still parsed before Shaka starts.

<ReactHlsPlayer src={src} enableInsights={false} />

Playback settings

Every load applies the embed player's Shaka settings:

  • ABR with a 3 Mbps default estimate and the network-information API off
  • Manifest retries: 6 attempts, 1s base delay, 30s timeout
  • Segment retries: 6 attempts, 500ms base delay, 20s timeout
  • rebufferingGoal: 4, bufferingGoal: 20
  • manifest.disableText and StubTextDisplayer, so Shaka leaves native <track> captions in place

startHighestRes keeps ABR off while load() runs, then selects the tallest playable variant. A portrait aspectRatio such as 9:16 turns off restrictToElementSize.

playerConfig is an optional partial Shaka PlayerConfiguration. It is applied first. Embed settings and DRM are applied after it, so those values stay in effect.

<ReactHlsPlayer
  src={src}
  aspectRatio="16:9"
  startHighestRes
  playerConfig={{ streaming: { bufferingGoal: 30 } }}
/>

Fatal Shaka errors (severity CRITICAL) call load(src) again up to maxFatalRetries (default 3). Fatal retries skip DRM codes 6000–6010 and 3014–3016. Those errors are reported through onError.

An .m3u8 URL loads shaka-player.hls.js. An .mpd URL loads shaka-player.dash.js. debug loads that build's debug file and sets its log level to DEBUG. Leave debug off to use the matching release build. This flag is separate from insights.debug.

<ReactHlsPlayer src={src} debug />

Native player props

Other video attributes pass through to <video>, except onPlay, onPause, onPlaying, onWaiting, onEnded, onError, onTimeUpdate, onProgress, onSeeked, and onVolumeChange. Those are the typed callbacks in this table.

PropDescription
src string, requiredGumlet HLS or DASH URL from the dashboard: /{collection_id}/{asset_id}/main.m3u8 or main.mpd.
autoPlay booleanCall play() after load() resolves. Default false.
muted booleanMute the element before play().
controls booleanNative controls. Default false.
loop, playsInline, poster, preload, crossOrigin, width, heightPassed through to <video>.
playerConfigPartial Shaka config, applied before embed settings.
playerRefRef to the <video> element (same node as ref).
refThe <video> element.
getPlayerRef(player: shaka.Player | null) => void
aspectRatio stringPortrait W:H or W/H disables ABR restrictToElementSize.
startHighestRes booleanABR off through load(), then lock the tallest variant.
drmDrmConfig: Widevine and FairPlay fields.
enableInsights booleanStart Gumlet Insights after the URL is parsed. Default true.
insightsInsights fields. Workspace id and player name/version are set by this package.
debug booleanLoad the debug file for this manifest and set Shaka's log level to DEBUG. Default false.
maxFatalRetries numberFatal retries before giving up. Default 3.
onReadyload() resolved.
onPlay, onPause, onPlaying, onEnded, onWaitingVideo playback state.
onTimeUpdate{ currentTime, duration }
onProgress{ percent } from currentTime / duration.
onSeeked{ currentTime, duration }
onVolumeChange{ volume, muted }
onManifestParsedFires with onReady when load() resolves.
onQualityChange{ level, height, bitrate } after adaptation.
onError{ fatal, source, data?, mediaError?, message? }

Exported types: HlsPlayerProps, DrmConfig, InsightsConfig, AnalyticsConfig, PlayerConfig, TimeEventData, ProgressEventData, VolumeEventData, QualityChangeData, PlayerErrorData.

Demo

The repository includes a local demo. From a checkout, npm start opens it on port 8000. Load (or Enter in the stream URL field) applies the URL and any Widevine or FairPlay URIs and mounts the player. Use it again after Destroy Video. Checkboxes toggle muted, autoPlay, and startHighestRes. Leave the DRM fields blank for clear playback. onTimeUpdate and onProgress are sampled so the event log stays readable.