@gumlet/insights-js-core
v2.2.1
Published
Gumlet Insights allows you to collect data from Video playback.
Downloads
1,777
Readme
@gumlet/insights-js-core
Browser-side video analytics SDK for Gumlet Insights. Collects playback lifecycle events — startup, play, pause, seek, rebuffering, quality change, error, end — and sends them to the Gumlet ingest API.
Supports HTML5, HLS.js, and Shaka Player.
Requirements
- Node.js ≥ 20.x
- One of the three supported players loaded in the page before this SDK
Installation
npm install @gumlet/insights-js-coreOr load the pre-built IIFE bundle directly in a <script> tag (sets window.gumlet.GumletInsights):
<script src="dist/main.iife.js"></script>Quick start
ESM / npm
import { GumletInsights } from '@gumlet/insights-js-core';
const insights = new GumletInsights({ property_id: 'YOUR_PROPERTY_ID' });
// HTML5 native video element — pass the element directly
await insights.attach(document.querySelector('video'));
// HLS.js — pass the Hls instance (media element auto-detected from hls.media)
await insights.attach(hls);
// Shaka Player — mediaElement is required
await insights.attach(player, { mediaElement: videoEl });CDN / IIFE
<script src="dist/main.iife.js"></script>
<script type="module">
const insights = new gumlet.GumletInsights({ property_id: 'YOUR_PROPERTY_ID' });
await insights.attach(player, { mediaElement: videoEl });
</script>Full custom metadata (local / ingest testing)
To exercise every optional metadata slot (customData1–10, user, video, player, experiment) with distinct placeholder strings, spread fullCustomAnalyticsConfig, then set property_id. User and session IDs follow the normal cookie-based flow (omit test: true — that mode forces random UUIDs instead of gumlet_user_id / gumlet_session_id). Use a dedicated property_id for lab traffic if you need to separate it in dashboards.
ESM
import { GumletInsights, fullCustomAnalyticsConfig } from '@gumlet/insights-js-core';
const insights = new GumletInsights({
...fullCustomAnalyticsConfig,
property_id: 'YOUR_PROPERTY_ID',
});IIFE — after loading main.iife.js, use window.gumlet.insightsFullCustomTestConfig:
const insights = new gumlet.GumletInsights({
...gumlet.insightsFullCustomTestConfig,
property_id: 'YOUR_PROPERTY_ID',
});API reference
new GumletInsights(config)
Creates the analytics instance. If property_id is missing or empty, a console.error is logged and the SDK is disabled — all subsequent calls become no-ops.
insights.attach(player?, opts?)
Connects the SDK to a player. Runs all validation checks before attaching.
| player value | Behaviour |
|---|---|
| Hls instance | HLS.js adapter |
| shaka.Player instance | Shaka adapter — opts.mediaElement required |
| HTMLVideoElement | HTML5 adapter |
| null / undefined | HTML5 adapter — opts.mediaElement required |
opts:
| Option | Type | Description |
|--------|------|-------------|
| mediaElement | HTMLVideoElement | The <video> element. Required for Shaka; recommended for HLS.js. |
| starttime | number | Unix epoch ms marking the session start. Defaults to Date.now(). |
Calling attach() while already attached logs a warning and detaches the previous player first.
insights.detach()
Removes all event listeners and disconnects from the player. Safe to call when nothing is attached.
insights.isAttached
boolean — true after a successful attach(), false after detach() or a failed attach.
insights.getImpressionId()
Returns the current impression ID string, or undefined if the SDK failed to initialize.
insights.updateConfig(partial)
Merges a partial config into the running session — updates custom data slots, video metadata, user metadata, and player metadata simultaneously.
insights.updateConfig({
custom_data_1: 'experiment-a',
customVideoTitle: 'Episode 3',
});Individual update helpers
insights.updateCustomData({ custom_data_1: 'value' });
insights.updateCustomVideoData({ customVideoTitle: 'Episode 2' });
insights.updateCustomUserData({ userName: 'alice' });
insights.updateCustomPlayerData({ customPlayerName: 'shaka-v4' });Validation
attach() checks the player and options before connecting. On failure it logs a [GumletInsights]-prefixed console.error and returns without sending any events.
| Scenario | Error message |
|---|---|
| SDK not initialized (property_id empty/missing) | attach() called but the SDK is not initialized. Check that you passed a valid property_id |
| null / undefined with no mediaElement | HTML5 mode requires opts.mediaElement (an HTMLVideoElement) |
| opts.mediaElement is not an HTMLVideoElement | opts.mediaElement must be an HTMLVideoElement, got: [object …] |
| Unrecognized player type | Unrecognized player type (…). Supported: HLS.js, Shaka Player, HTMLVideoElement… |
| HLS.js player missing core API (likely destroyed) | HLS.js player is not in a valid state. The player may have been destroyed. |
| Shaka without resolvable video element | Shaka Player requires opts.mediaElement — the HTMLVideoElement Shaka is playing into |
| Shaka player with isDestroyed() === true | Shaka Player is not in a valid state. The player may have been destroyed. |
| Already attached (not an error — just a warning) | attach() called while a player is already attached. Detaching previous player first. |
Configuration
All options are passed to the GumletInsights constructor:
| Key | Type | Required | Description |
|-----|------|----------|-------------|
| property_id | string | Yes | Gumlet Insights property ID |
| debug | boolean | No | Enable verbose console logging |
| test | boolean | No | Use a test user/session ID (events flagged, not counted in production) |
| page_url | string | No | Override the detected page URL (useful in iframes / SSR) |
| sessionID | string | No | Override the auto-generated session ID |
| userID | string | No | Override the auto-generated user ID |
| sendSessionRequest | boolean | No | Set to false to skip the session-init HTTP call (default true) |
| disable_analytics_v2 | boolean | No | When true, skips the v2 mirror (…/v2) for session / session_event beacons. By default every GumletInsights instance dual-sends v1 + v2 unless this kill-switch is set. |
| getUUID | () => string | No | Custom UUID generator for event and playback IDs |
| customData1 … customData10 | string | No | Arbitrary metadata attached to every event |
| customVideoId | string | No | Override video ID (auto-detected from video.gumlet.io URLs) |
| customVideoTitle | string | No | Video title |
| customVideoDurationMillis | string | No | Override video duration |
| customVideoSeries | string | No | Series/playlist name |
| customVideoProducer | string | No | Producer / uploader name |
| customVideoLanguage | string | No | Video language |
| customVideoVariantName | string | No | A/B variant label |
| customVideoVariant | string | No | A/B variant value |
| customContentType | string | No | Content category (e.g. "live", "vod") |
| customEncodingVariant | string | No | Encoding profile label |
| userId | string | No | Custom user id (custom_user_id). Not the Gumlet anonymous user_id UUID. |
| userEMail / userEmail | string | No | Viewer email → user_email / custom_user_email on ingest (either spelling; userEMail wins if both are set). |
| userName / userCity / … | string | No | Other user identity metadata |
| customPlayerName / customPageType / experimentName | string | No | Player / experiment metadata |
Analytics events emitted
| Wire event | Trigger |
|------------|---------|
| event_setup | SDK initialised |
| event_player_ready | Player reported ready |
| event_playback_ready | Source loaded |
| event_play | User pressed play (first play or resume) |
| event_playing | First frame rendered / resumed after pause or seek |
| event_playback_started | Startup latency measurement point |
| event_pause | Playback paused |
| event_rebuffer_start | Stall / buffering begun |
| event_rebuffer_end | Buffering recovered |
| event_seeked | Seek completed |
| event_playback_update | Heartbeat every ~10 seconds of watch time |
| event_error | Fatal or non-fatal player error (always sent) |
| event_error_recovered | Player resumed after a fatal error only |
| event_ended | Video played to completion |
| event_mute / event_unmute | Mute state changed |
| event_audio_language_changed | Audio language switched (language_from / language_to; current audio_language on same beacon) |
| event_subtitle_language_changed | Subtitle/caption language switched or off (language_from / language_to; current subtitle_language) |
Every session_event also carries audio_language (active audio) and subtitle_language when captions are on (player_language_code is still sent as a legacy alias of audio_language).
Error severity (Shaka Player)
event_error fires for all Shaka errors regardless of severity. The state machine handles them differently:
| Severity | Value | State machine effect |
|----------|-------|----------------------|
| RECOVERABLE | 1 | Self-transition — state unchanged, event_error_recovered never fires |
| CRITICAL | 2 | Transitions to ERROR state; event_error_recovered fires if playback resumes |
Development
Install dependencies
npm installStart dev server
npm startRuns the TypeScript build in watch mode and serves the demo pages at http://localhost:8080. Opens html/hlsjs.html automatically. Navigate between players using the header on any demo page:
| URL | Player |
|-----|--------|
| /html/html5.html | Native <video> element |
| /html/hlsjs.html | HLS.js — Angel One (multi audio + subtitles), quality + language dropdowns |
| /html/shaka.html | Shaka — same Angel One DASH, quality + language dropdowns, error triggers |
Each page includes a live event log (clock timestamp · event family · event name · previous event · millis from previous event · playback time). Click any row to inspect the full payload.
Build
npm run build:release # production bundle → dist/
npm run build:debug # watch modeOutput files:
| File | Format | Use |
|------|--------|-----|
| dist/main.mjs | ESM | npm / bundlers |
| dist/main.iife.js | IIFE | CDN <script> tag |
| dist/main.cjs | CJS | CommonJS |
Test
npm test149 tests across 12 test files:
| Suite | Coverage |
|-------|----------|
| SessionManager | Identity, custom data, session expiry |
| SampleBuilder | Payload assembly, playback_time_instant_millis |
| EventPublisher | Duration guard, 24-hour live-stream guard |
| GumletStateMachine | Full lifecycle, rebuffering, error recovery, non-fatal errors, seek-while-paused, quality-change self-transitions |
| HTML5Adapter | DOM event → analytics event mapping, destroy() |
| HlsjsAdapter | Quality level extraction, error forwarding |
| ShakaAdapter | MIME type detection, variant track parsing, error pipeline |
| GumletInsights | All attach() validation paths, double-attach, detach, impression ID |
| Heartbeat timer | Regression tests for quality-change and mute resetting the window |
Lint
npm run lint # check
npm run lint:fix # auto-fixArchitecture
GumletInsights (public API — attach/detach/validate)
└── Analytics (orchestrator)
├── SessionManager — identity (userId, sessionId, playbackId), custom metadata
├── SampleBuilder — playback-state fields, payload assembly (getSample)
├── EventPublisher — HTTP dispatch (EventsCall), send-and-clear, unload
└── GumletStateMachine — jsm v3 FSM: SETUP → READY → STARTUP → PLAYING → … → END/ERROR
├── HTML5Adapter — native HTMLVideoElement events (abstract base)
├── HlsjsAdapter — extends HTML5Adapter; hooks Hls.js engine events
└── ShakaAdapter — extends HTML5Adapter; hooks Shaka 'error' eventKey design decisions
GumletInsightswrapsAnalytics— Validation, lifecycle management (attach/detach), and the public API surface live inGumletInsights.Analyticsand its adapters are internal implementation detail.- Single state machine for all players —
GumletStateMachine(jsm v3) is shared across all three adapters. Player-specific behaviour lives entirely in the adapter layer. lastKnownCurrentTime_—video.currentTimeresets to0when a player replaces itsMediaSource— before error and pause events fire. Adapters cache the last non-zero value fromtimeupdateevents and use that for error timestamps instead.sourceSwitching_flag — AMediaSourcereplacement causes the browser to fireemptied → pause → playing— none of which are user actions. The flag (set onemptied, cleared only whentimeupdatefires withcurrentTime > 0) suppresses spurious pause and playing analytics events during the switch.- Heartbeat isolation —
heartbeatTimestampis independent ofonEnterStateTimestamp. Quality-change and mute self-transitions resetonEnterStateTimestamp(for state-duration maths) but must never reset the 10-second heartbeat window. - Non-fatal vs fatal errors — Shaka
RECOVERABLE(severity 1) errors use aplayerNonFatalErrorself-transition:event_erroris still sent, but the state machine stays in its current state soevent_error_recoveredcan never fire spuriously.
Project structure
src/
core/ — GumletInsights (public API), Analytics, SessionManager,
SampleBuilder, EventPublisher, GumletInsightsExport (CDN entry)
adapters/ — HTML5Adapter (base), HlsjsAdapter, ShakaAdapter
stateMachine/ — GumletStateMachine (jsm v3)
cast/ — CastClient, CastReceiver (implemented; wiring pending — see TODO.md)
enums/ — Events, GumletEnum (wire names), MIMETypes, Players, StreamTypes
types/ — AnalyticsConfig, SamplePayload, IAdapter, IStateMachine, ambient .d.ts
utils/ — Logger, Utils, Settings, PlayerDetector, DetectDevice, …
html/ — manual test pages (html5, hlsjs, shaka) + event-logger.js
tests/ — unit tests (phase1 … phase5 + stage1 regression)
dist/ — built output (main.mjs, main.iife.js, main.cjs)
CHANGELOG.md — full technical history for agents and developers
TODO.md — known issues and pending work
V2_REVAMP_PLAN.md — 8-phase revamp plan (read-only reference)Legacy API
The pre-v2 gumlet.insights(config).registerXxxPlayer(...) API is still available for CDN users and will not be removed:
// Still works — backward compatible
const analytics = gumlet.insights({ property_id: 'YOUR_ID' });
analytics.registerShakaPlayer(player, { mediaElement: video });
analytics.registerHLSJSPlayer(hls);
analytics.registerHTML5Player(video);New integrations should use GumletInsights instead.
License
MIT © Gumlet Pte. Ltd.
