How analytics works on koi-player
This guide explains how analytics work in the <koi-audio-player> and <koi-card-audio-player> web components, what is captured automatically, and how a publisher embedding the player can track user reproductions and clicks themselves.
There are two layers:
- Media QoE analytics (Mux) — already wired into the library. Captures plays, pauses, seeks, completion, buffering, startup time, and errors. Enabled by default per player instance.
- Product analytics (your tool of choice) — not wired. The library exposes the events you need so you can forward them to GA, Segment, Adobe, Amplitude, or anything else.
1. Built-in Mux analytics (QoE)
The library auto-instruments the underlying <audio> element with Mux via src/analytics.ts.
Enabling
Mux initialization is automatic when both attributes are present:
<koi-audio-player org-id="your-org-id" media-id="some-media-id" env="prod"></koi-audio-player>org-id— maps to Muxsub_property_idand is required.media-id— maps to Muxvideo_idand is required.env—"prod"or"sandbox". Each routes to a different Mux environment key (seegetPublicKeyinsrc/analytics.ts).- If
streamis used instead ofmedia-id, Mux is not initialized.
What Mux records automatically
No code on your side. Mux captures:
- Views and play starts
- Pause, seek (seeking / seeked), resume
- Completion (ended)
- Rebuffering count and duration
- Startup time, time to first frame
- Playback errors
- Device, browser, OS, connection metadata
Data shows up in your Mux dashboard keyed off the org-id you set.
Sampling
The sampleRate value in the player’s meta object controls how often Mux is initialized (probabilistic, per page load). When a session is sampled out, the library dispatches a koi-analytics-skipped event internally for its own use; that event bus is not currently exported from the package, so external consumers cannot observe it without a library change.
What Mux is NOT for
Mux is media QoE. It will not tell you:
- Which button the user clicked
- Whether a play came from a user click vs. autoplay
- Funnel conversions, sign-ups, scroll depth, attribution
For any of those, use the product-analytics integration patterns below.
2. Tracking audio reproductions yourself
The library dispatches a koi-player-ready event on document once the player has mounted. Its detail.player is the custom-element instance, which exposes mediaElement (the underlying <audio> element) via a getter.
Native HTML5 media events are the cleanest way to track reproductions:
<koi-audio-player org-id="..." media-id="..."></koi-audio-player>
<script> document.addEventListener('koi-player-ready', (e) => { const player = e.detail.player; const audio = player.mediaElement; const id = player.getAttribute('media-id');
audio.addEventListener('play', () => myAnalytics.track('audio_play', { mediaId: id })); audio.addEventListener('pause', () => myAnalytics.track('audio_pause', { mediaId: id, t: audio.currentTime })); audio.addEventListener('ended', () => myAnalytics.track('audio_complete', { mediaId: id }));
// Quartile progress const fired = new Set(); audio.addEventListener('timeupdate', () => { if (!audio.duration) return; const pct = (audio.currentTime / audio.duration) * 100; [25, 50, 75].forEach((q) => { if (pct >= q && !fired.has(q)) { fired.add(q); myAnalytics.track('audio_progress', { mediaId: id, quartile: q }); } }); }); });</script>Replace myAnalytics.track(...) with your provider’s call (gtag, analytics.track, window.adobeDataLayer.push, etc.).
3. Tracking clicks
| Click scope | How to hook it |
|---|---|
| Play / pause button (distinguish user click from autoplay / scripted play) | Reach into the player’s open shadow root: player.shadowRoot.querySelector('media-play-button') and add a pointerdown listener. The native play event alone cannot tell you whether the user clicked. |
| Other control-bar buttons (volume, mute, fullscreen) | Same shadow-root pattern: query the media-chrome custom elements (media-mute-button, media-volume-range, …) inside player.shadowRoot and attach click/change listeners. Be aware this is shadow-DOM piercing — internal markup can change between library versions. |
| Player-container clicks (any click on the host element) | Listen on the host element itself: player.addEventListener('click', …). Clicks on shadow internals retarget to the host, so this catches generic clicks without piercing the shadow DOM. |
Worked example — distinguishing user-driven play from autoplay
document.addEventListener('koi-player-ready', (e) => { const player = e.detail.player; const audio = player.mediaElement; const playBtn = player.shadowRoot.querySelector('media-play-button'); let userClicked = false;
if (playBtn) { playBtn.addEventListener('pointerdown', () => { userClicked = true; }); }
audio.addEventListener('play', () => { myAnalytics.track('audio_play', { mediaId: player.getAttribute('media-id'), source: userClicked ? 'user_click' : 'programmatic', }); userClicked = false; });});4. Wiring to a specific vendor
The player is intentionally vendor-neutral for product analytics — any SDK works. Example using Google Analytics 4:
document.addEventListener('koi-player-ready', (e) => { const player = e.detail.player; const audio = player.mediaElement; const id = player.getAttribute('media-id');
audio.addEventListener('play', () => gtag('event', 'audio_play', { media_id: id })); audio.addEventListener('ended', () => gtag('event', 'audio_complete', { media_id: id }));});Swap gtag(...) for Segment’s analytics.track(...), Adobe’s _satellite.track(...), Amplitude’s amplitude.track(...), etc.
Reference: event surfaces available today
| Surface | Where | What you get |
|---|---|---|
document event koi-player-ready | Dispatched once per player from BasePlayer.finalizeSetup | detail.player — the custom-element instance |
document event koi-player-error | Dispatched if the player fails to initialize | detail.player, detail.error |
player.mediaElement | Getter on KoiAudioPlayer | The underlying <audio> element — listen to native HTML5 media events here |
player.shadowRoot | Open mode — direct access | Media-chrome control elements (use for click tracking) |