npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-core

Or 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 (customData110, 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

booleantrue 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 | | customData1customData10 | 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 install

Start dev server

npm start

Runs 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 mode

Output 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 test

149 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-fix

Architecture

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' event

Key design decisions

  • GumletInsights wraps Analytics — Validation, lifecycle management (attach/detach), and the public API surface live in GumletInsights. Analytics and its adapters are internal implementation detail.
  • Single state machine for all playersGumletStateMachine (jsm v3) is shared across all three adapters. Player-specific behaviour lives entirely in the adapter layer.
  • lastKnownCurrentTime_video.currentTime resets to 0 when a player replaces its MediaSource — before error and pause events fire. Adapters cache the last non-zero value from timeupdate events and use that for error timestamps instead.
  • sourceSwitching_ flag — A MediaSource replacement causes the browser to fire emptied → pause → playing — none of which are user actions. The flag (set on emptied, cleared only when timeupdate fires with currentTime > 0) suppresses spurious pause and playing analytics events during the switch.
  • Heartbeat isolationheartbeatTimestamp is independent of onEnterStateTimestamp. Quality-change and mute self-transitions reset onEnterStateTimestamp (for state-duration maths) but must never reset the 10-second heartbeat window.
  • Non-fatal vs fatal errors — Shaka RECOVERABLE (severity 1) errors use a playerNonFatalError self-transition: event_error is still sent, but the state machine stays in its current state so event_error_recovered can 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.