Tracker
[Unreleased]
Section titled “[Unreleased]”[1.1.0] — 2026-09-24
Section titled “[1.1.0] — 2026-09-24”- Listening in a background tab is counted. Browsers throttle hidden tabs,
so the player’s timeupdates can stall and then report minutes of playback as
one jump — which the tracker discarded as a seek. A forward jump over 5s is
now credited when it fits the real time that passed (at the current playback
rate, with 50% headroom plus
SEEK_SLACK, 1s), and only treated as a seek when it clearly exceeds it. The stretch between the last timeupdate andendedis credited the same way. Small jumps behave as before. - Live streams send
duration: 0instead ofnull. A stream’s duration isInfinity, which JSON serialises asnull; payloads now always carry a number, with0meaning unknown.completeis explicitly skipped when the duration isn’t finite (it could never fire, but only by accident). reset()fully detaches the tracker. It cleared the config but left the documentready/destroylisteners attached, so a player created afterwards was still tracked and its first timeupdate threw (this.configwasnull).reset()now removes those listeners, andtrackPlayer()without a config (beforeinit()or afterreset()) warns and does nothing.- Thresholds crossed in the last second before
ended,pauseor destroy are no longer lost. The checks run at most once a second, andendedonly looked atcompletebefore resetting, so aplayorlistenthreshold crossed inside that window was dropped.ended,pauseanduntrackPlayer()(whichwaveformplayer:destroycalls) now run every check unthrottled first.
Changed
Section titled “Changed”completeneeds real listening, not just the playhead position. It fired as soon as the position reached the threshold, so scrubbing to 95% (or to the end, viaended) counted as a completion with a second heard. It now also requires the listener to have heard half the audio up to the threshold (COMPLETE_ENGAGEMENT, 0.5), in media time: withcomplete: 90on a 4-minute track, 108s. Both the timeupdate andendedpaths apply it. Expect fewercompleteevents: skip-to-the-end sessions no longer count.- A threshold of
0fires instead of disabling the event.events.play: 0(orlisten: 0,complete: 0) was falsy and silently turned the event off; it now fires on the first check after playback starts. Omitting the key,nullorfalsestill disables an event. init()can be called more than once. Each call added another pair of document listeners, so every player was handled twice per extra call. A secondinit()now reconfigures the tracker in place: the new config applies to players already tracked, and the session id is kept, since it’s the same page session. Afterreset(),init()starts afresh with a new id.- Cross-origin endpoints receive
listenandcompleteviafetchkeepaliveinstead ofsendBeacon. A beacon’sapplication/jsonbody isn’t CORS-safelisted and beacons always send credentials, so an endpoint on another origin answeringAccess-Control-Allow-Origin: *never got those events — andsendBeaconstill reported success, so nothing fell back. Terminal events to another origin now usefetchwithkeepalive: true(stillContent-Type: application/json); the endpoint must answer the CORS preflight forContent-Type. Same-origin endpoints keep usingsendBeacon(unless customheadersare set), and it remains the fallback whenfetchis unavailable. - The package declares
"type": "module"and anexportsmap.importresolves to the ESM build andrequireto the new CJS build;./dist/*,./src/*and./package.jsonstay reachable. Paths outside those are no longer importable.
- A CommonJS build,
dist/waveform-tracker.cjs, now themainentry.mainpointed at the IIFE bundle, sorequire('@arraypress/waveform-tracker')returned{}. It now returns{ default: tracker }, matching@arraypress/waveform-player’s CJS build. prepublishOnlyruns the tests and a fresh build, sonpm publishcan’t ship a staledist/.
[1.0.2] — 2026-09-24
Section titled “[1.0.2] — 2026-09-24”- Per-track state resets when the player’s URL changes. A player outlives
many tracks, and
load()/loadTrack()swap the URL mid-play without anendedin between, so the new track inherited the previous one’s elapsed time (firinglistenearly) and its sent events (never firingplayorlistenat all). Directload(url)swaps also need@arraypress/waveform-player1.27.1, whereload()records the new URL. - Destroyed players are released. The tracker listens for
waveformplayer:destroyand untracks the player, so SPAs that create and destroy players no longer leak them. completeonendedworks in external audio mode, reading the final time from the event detail instead ofplayer.audio.
Changed
Section titled “Changed”- Engagement is counted in media time, from
currentTimedeltas, so 1.5x/2x playback is credited for the content actually heard. Forward jumps over 5s are treated as seeks and not credited. listenandcompleteare delivered withsendBeacon(falling back tofetchwithkeepalive) so they survive page navigation.- Configuration problems and delivery failures always log, via
console.warn/console.errorwith a[WaveformTracker]prefix; tracing stays behinddebug. - Peer dependency raised to
@arraypress/waveform-player^1.8.0.
[1.0.1] — 2026-06-27
Section titled “[1.0.1] — 2026-06-27”Changed
Section titled “Changed”- Peer dependency raised to
@arraypress/waveform-player^1.7.2. No source changes.
[1.0.0] — 2025-09-16
Section titled “[1.0.0] — 2025-09-16”- Initial release:
play,listenandcompleteevents for every WaveformPlayer on the page, delivered to an endpoint or a custom handler.