@devix-technologies/react-gjirafa-vp-player
v1.0.32
Published
A lightweight React wrapper for the [Gjirafa VP Player SDK](https://vp.gjirafa.tech/documentation/docs/web-player/).
Keywords
Readme
React Gjirafa VP Player
A lightweight React wrapper for the Gjirafa VP Player SDK.
v2.0.0 - Simplified Architecture
Version 2.0.0 is a major simplification. The wrapper is now a thin bridge to the GTech JS player:
- No configuration interference - GTech admin panel is the source of truth
- Auto-initialization - Managed scripts auto-init with embedded config
- Event forwarding - React callbacks for SDK events
Installation
npm install @devix-technologies/react-gjirafa-vp-player
# or
pnpm add @devix-technologies/react-gjirafa-vp-playerQuick Start
import { VPPlayer } from "@devix-technologies/react-gjirafa-vp-player";
// Using scriptId + videoId (recommended)
<VPPlayer
scriptId="ptkzurnx"
videoId="vjsobqhe"
onPlay={() => console.log('Playing!')}
/>
// OR using full script URL
<VPPlayer
scriptUrl="https://host.vpplayer.tech/player/ptkzurnx/vjsobqhe.js"
onPlay={() => console.log('Playing!')}
/>How It Works
- Wrapper generates a unique
divIdfor the container - Script URL:
https://host.vpplayer.tech/player/{scriptId}/{videoId}.js?divId={ourDivId} - Script loads and auto-initializes with GTech admin config
- Wrapper does NOT call
.setup()- script already did it - Wrapper attaches event listeners for React callbacks
Usage Examples
Web Player
<VPPlayer
scriptId="ptkzurnx"
videoId="vjsobqhe"
onPlay={() => analytics.track("play")}
onQuartile25={() => analytics.track("25% watched")}
onProgress20s={() => analytics.track("20s milestone")}
/>Vertical Player
<VPPlayer scriptId="rbqcdwzlg" videoId="vjsobqhe" isVertical={true} />Reels Mode (TikTok-style)
<VPPlayer
scriptId="rbqcdwzlg"
videoId="vjsobqhe"
isReels={true}
thumbnailUrl="https://example.com/thumb.jpg"
onClose={() => setShowPlayer(false)}
/>Manual Playlist (Custom Videos Array)
For vertical player with your own videos array (not using GTech admin playlists):
<VPPlayer
scriptId="rbqcdwzlg"
projectId="agmipnzb"
isVertical={true}
playlist={{
videos: [
{
videoId: "1",
title: "Big Buck Bunny",
file: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
thumbnailUrl: "https://example.com/thumb1.jpg",
duration: 498,
},
{
videoId: "2",
title: "Elephants Dream",
file: "https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4",
thumbnailUrl: "https://example.com/thumb2.jpg",
duration: 738,
},
],
startIndex: 0,
}}
onNext={() => console.log("Next video")}
onVideoStarted={(data) => console.log("Playing:", data.title)}
/>Note: Manual playlist mode calls player.setup() internally with the built config. This is required because the VP Vertical Player SDK needs video.file to be set even in playlist mode.
Custom Container Sizing
The player fills its container (100% width/height). Control sizing with a wrapper:
// Responsive 16:9
<div style={{ width: '100%', maxWidth: '800px', aspectRatio: '16/9' }}>
<VPPlayer scriptId="ptkzurnx" videoId="vjsobqhe" />
</div>
// Fixed size
<div style={{ width: '400px', height: '225px' }}>
<VPPlayer scriptId="ptkzurnx" videoId="vjsobqhe" />
</div>Video Locking
Video locking lets you lock a portion of each video (e.g. require watching N seconds or N% before skipping). The SDK supports this via a shouldLockVideo callback and removeVideoLock(). The recommended approach is to set the callback inside onVideoSwitch so locking applies after each video switch.
Ref methods
shouldLockVideo(callback)– Set the callback that returns lock config. Pass a function that returns{ isEnabled, type, value }, ornullto clear. Matches SDK naming.removeVideoLock()– Clear any active lock.
Lock config shape
{ isEnabled: boolean; type: "seconds" | "percentage"; value: number }Recommended pattern: lock on video switch
Use onVideoSwitch to set or clear shouldLockVideo after each switch. In the callback you can decide whether to lock (e.g. from app state) and for how long.
const playerRef = useRef<VPPlayerRef>(null);
<VPPlayer
ref={playerRef}
scriptId="ptkzurnx"
videoId="vjsobqhe"
config={{ /* ... */ }}
onVideoSwitch={() => {
const shouldLock = true; // your logic, e.g. from state
if (shouldLock) {
playerRef.current?.shouldLockVideo(() => ({
isEnabled: true,
type: "seconds",
value: 20,
}));
} else {
playerRef.current?.removeVideoLock();
playerRef.current?.shouldLockVideo(() => ({
isEnabled: false,
type: "seconds",
value: 0,
}));
}
}}
/>To only lock for a period after each switch, enable locking in onVideoSwitch with the desired value (e.g. 20 seconds). To disable locking for the next video, call removeVideoLock() and set shouldLockVideo to return isEnabled: false.
Ads
Ad breaks come from the fetched config, and the player boots its ad stack
whenever video.advertising is true. Two things change that.
Ad-free viewers
Pass disableAds for anyone whose subscription has paid the ads away. The
wrapper merges video.advertising: false into the config, so the ad stack never
starts and the IMA SDK is never even requested.
<VPPlayer
scriptId="ptkzurnx"
videoId="vjsobqhe"
disableAds={hasPremiumAccess || hasAdFree}
config={{ config: { autoplay: true } }}
/>Stubbed IMA SDK (VP-589)
The player reads google.ima.ImaSdkSettings.VpaidMode.ENABLED without guarding
it. When something has replaced the SDK with a stub — an ad blocker surrogate, a
browser tracking-protection shim, an HTTPS-filtering antivirus, a truncated
response — that read throws inside an async setup chain nothing catches, and the
viewer is left with a black frame and a spinner for good.
A missing SDK is harmless: the player detects it and skips the ad break by itself. Only a half-present one is fatal.
So when the config carries an ad break, the wrapper probes the SDK before
setup() and disables advertising if it finds a stub. Playback survives; the ad
does not. The probe is skipped entirely for configs with no ad break, so an
ad-free video costs no extra request.
The probe requests the same SDK the player does, over https. Should the player
ever move to a different SDK URL, the probe would be checking a file nobody
uses; it warns on the console when the URL it knows about loads but defines no
SDK, so a stale constant is visible rather than silent. IMA_SDK_URL in
constants/vpPlayer.ts carries the command for re-checking it against the
player bundle.
See the Custom Features/Ad SDK Guard stories.
Props Reference
Identification (one required)
| Prop | Type | Description |
| ----------- | -------- | ---------------------------------- |
| scriptId | string | GTech script ID (e.g., "ptkzurnx") |
| videoId | string | GTech video ID (e.g., "vjsobqhe") |
| scriptUrl | string | Full managed script URL |
Optional
| Prop | Type | Description |
| --------------- | ---------------------- | ---------------------------------------------------- |
| projectId | string | GTech project ID (required for manual playlist) |
| playlist | ManualPlaylistConfig | Custom videos array for manual playlist mode |
| disableAds | boolean | Drop the ad break for an ad-free viewer |
| playerId | string | Custom container ID (auto-generated if not provided) |
| isVertical | boolean | Force vertical player mode |
| isReels | boolean | Enable Reels overlay mode |
| thumbnailUrl | string | Thumbnail for Reels mode |
| className | string | CSS class for container |
| hiddenClasses | string[] | SDK element classes to hide |
Event Callbacks
| Prop | Horizontal | Vertical | Description |
| ---------------- | :--------: | :------: | ----------------------------------------------------- |
| onReady | ✓ | ✓ | Player initialized |
| onPlay | ✓ | ✓ | Playback started (initial play) |
| onPause | ✓ | ✓ | Playback paused (user-initiated) |
| onResume | ✗ | ✓ | Playback resumed after pause (vertical only) |
| onComplete | ✓ | ✗ | Video completed (horizontal only) |
| onError | ✓ | ✓ | Error occurred |
| onVideoStarted | ✓ | ✓ | Video metadata available (returns CurrentVideoData) |
| onTimeUpdate | ✓ | ✓ | Continuous time updates (returns seconds) |
| onQuartile25 | ✓ | ✓ | 25% watched |
| onQuartile50 | ✓ | ✓ | 50% watched |
| onQuartile75 | ✓ | ✓ | 75% watched |
| onNext | ✓ | ✓ | Next video in playlist |
| onPrevious | ✗ | ✓ | Previous video (vertical only) |
| onProgress10s | ✓ | ✓ | Every ~10 seconds (returns seconds) |
| onProgress20s | ✓ | ✓ | 20 second milestone (returns seconds) |
| onVideoSwitch | ✓ | ✓ | SDK vp-video-switch (use for video locking) |
| onClose | - | - | Reels overlay closed |
Event Implementation Notes
Horizontal Player uses standard VP Player SDK events via .on() listener:
ready,play,pause,complete,video-started,video-state,time,playlistItem,error- Quartiles:
analytics-25%-completed,analytics-50%-completed,analytics-75%-completed
Vertical Player uses a combination of .on() listener and the global vp-event handler:
Standard events via .on():
vp-first-frame,vp-video-started,vp-video-state,vp-time,vp-video-switch,error- Quartiles:
analytics-25%-completed,analytics-50%-completed,analytics-75%-completed
Special handling via vp-event handler (not exposed via standard .on()):
The following events are intercepted from the global vp-event emission because the VP Vertical Player SDK doesn't expose them via the standard .on() listener:
| Event | SDK Event | Special Logic |
| ---------- | ------------------ | -------------------------------------------------------------- |
| onPlay | vp-state-playing | Fires on initial play (when not resuming from pause) |
| onPause | vp-state-paused | Only fires on user-initiated pause (not on video loop/end) |
| onResume | vp-state-playing | Fires when resuming after user-initiated pause |
User Interaction Tracking:
To distinguish between user actions and automatic playback events (like video loops), we track the vp-user-interaction event:
- When
vp-user-interactionfires beforevp-state-paused→ user clicked pause →onPausefires - When
vp-user-interactionfires beforevp-state-playing(after pause) → user clicked play →onResumefires - When
vp-state-pausedfires without prior user interaction → video ended/looped → noonPause(silent) - When
vp-state-playingfires without prior pause → initial play or loop restart →onPlayfires
This ensures analytics events like player_pause and player_resume only fire for actual user actions, not automatic video looping.
Key Differences:
onComplete- Only available on horizontal player (vertical auto-advances to next video)onResume- Only available on vertical player (horizontal has no pause/resume distinction)onPrevious- Only available on vertical player (horizontal has no direction detection)onNexton horizontal fires for ANY playlist change (next button, auto-advance after complete)- Direction detection on vertical uses
player._switchDirectionproperty (viaqueueMicrotask) to differentiateonNextvsonPrevious
URL Helpers
import {
getFullyManagedPlayerScriptUrl,
getVerticalPlayerScriptUrl,
appendDivIdToUrl,
} from "@devix-technologies/react-gjirafa-vp-player";
// Build managed script URL
const url = getFullyManagedPlayerScriptUrl("ptkzurnx", "vjsobqhe");
// => "https://host.vpplayer.tech/player/ptkzurnx/vjsobqhe.js"
// Build vertical player URL
const verticalUrl = getVerticalPlayerScriptUrl("rbqcdwzlg");
// => "https://host.vpplayer.tech/vertical-player/rbqcdwzlg.js"Advanced: Direct Hook Usage
For programmatic control, use the hook directly:
import {
useVPPlayerLogic,
PlayerContainer,
} from "@devix-technologies/react-gjirafa-vp-player";
function CustomPlayer() {
const { playerRef, playerInstanceRef, isScriptLoaded, generatedPlayerId } =
useVPPlayerLogic({
scriptId: "ptkzurnx",
videoId: "vjsobqhe",
});
return (
<div>
<PlayerContainer
id={generatedPlayerId}
ref={playerRef}
width="100%"
height="auto"
$hiddenClasses={[]}
/>
<button onClick={() => playerInstanceRef.current?.play?.()}>Play</button>
<button onClick={() => playerInstanceRef.current?.pause?.()}>
Pause
</button>
</div>
);
}Migration from v1.x
See MIGRATION.md for detailed upgrade instructions.
Quick Summary
// BEFORE (v1.x)
<VPPlayer
videoId="vjsobqhe"
projectId="abc123"
scriptUrl="https://host.vpplayer.tech/player/ptkzurnx.js"
pureMode={true}
config={{ video: { autoplay: true } }}
onPlayerPlay={() => console.log('play')}
/>
// AFTER (v2.0.0)
<VPPlayer
scriptId="ptkzurnx"
videoId="vjsobqhe"
onPlay={() => console.log('play')}
/>License
MIT
