Skip to content

Getting Started

WaveformTracker is a headless, ~2KB analytics add-on for WaveformPlayer. It measures how much of your audio people actually listen to — real media-time, not wall-clock — and POSTs a compact JSON event to your endpoint (or hands it to your own callback) when a listener crosses a threshold you define.

It renders no DOM, ships no CSS, sets no cookies, and collects no PII. Drop it on a page, call init() once, and it auto-discovers every player.

Media-time, not wall-clock

Engagement accumulates from currentTime deltas. 1.5x/2x playback is fully credited; paused and idle time is not.

Seeks ignored

Any forward jump of 5s or more is treated as a seek and dropped — scrubbing never inflates your numbers.

Privacy-first

No cookies, no localStorage, no PII. The optional session id is a random in-memory string, regenerated every reload.

Survives unload

Terminal events use navigator.sendBeacon (with fetch + keepalive fallback) so they land even as the page navigates away.

Because it’s headless, the tracker is easiest to see with a live readout. The player below is wired to a tracker whose handler renders each captured event straight into the panel (no server) — the listen threshold is lowered to 5s so it fires quickly. Press play, let it run, pause, scrub, finish:

Captured events
  1. Press play — events stream in here.

In production you’d swap that handler for an endpoint and these exact payloads would POST to your server.

WaveformTracker credits media-time consumed, derived from the player’s waveformplayer:timeupdate events. On every update it computes the delta between the current and previous currentTime and accumulates it as elapsedTime — but only when the delta is a genuine forward step:

Delta between updates Credited? Why
0 < delta < 5s Yes Normal playback, including 1.5x / 2x (faster playback still advances currentTime)
delta <= 0 No A loop, rewind, or non-advance — nothing was consumed
delta >= 5s No Treated as a seek (see SEEK_THRESHOLD below) and dropped

The result is an honest “seconds of content heard” number. Pausing stops crediting and clears the delta baseline, but it does not reset the accumulated total — resuming continues where you left off. The accumulated total and the fired-event set reset only when the track ended fires, which re-arms every event for a replay.

WaveformTracker.SEEK_THRESHOLD // static class constant = 5

SEEK_THRESHOLD is the maximum forward currentTime jump (in seconds) still credited as playback. It is a class-level constant, not a config option — it lives on the class, so window.WaveformTracker.SEEK_THRESHOLD (the exported singleton) is undefined. There is no runtime knob to change it; 5 seconds is the fixed boundary between “playback” and “seek”. The player fires timeupdate far more often than once every 5 seconds, so real playback never trips it.

WaveformTracker has a peer dependency on @arraypress/waveform-player@^1.8.0. Load and initialize the player first — it dispatches the events the tracker consumes — then load the tracker.

Terminal window
npm install @arraypress/waveform-player @arraypress/waveform-tracker

The default export is a singleton instance, not the class — never new it. It is also assigned to window.WaveformTracker in browser builds.

import WaveformPlayer from '@arraypress/waveform-player';
import WaveformTracker from '@arraypress/waveform-tracker';
// Players exist on the page (auto-mounted via data-waveform-player, or constructed)…
WaveformTracker.init({ endpoint: '/api/track' });

You don’t attach the tracker to a player by hand. Calling init() does three things:

  1. Reads existing players. It immediately calls WaveformPlayer.getAllInstances() and tracks every player already on the page.

  2. Listens for new players. It adds capturing-phase listeners for waveformplayer:ready on document, so any player created later (lazy-mounted, SPA route change) is tracked automatically.

  3. Cleans up destroyed players. It listens for waveformplayer:destroy and untracks the player, removing its listeners — this prevents the tracker’s internal Map from leaking players (and their DOM) in single-page apps. Requires player v1.8.0+.

// Call this exactly once, as early as players may appear.
WaveformTracker.init({
endpoint: 'https://api.example.com/track',
events: { play: 3, listen: 30, complete: 90 },
metadata: { post_id: 456 }
});