How analytics works on the Arc Audio Player
This guide explains how analytics work in the <arc-audio-player> and <koi-card-audio-player> web components, what the player captures automatically, and how a publisher embedding the player can track user plays and clicks themselves.
Two layers apply here:
- Playback quality telemetry, which the player collects automatically and reports to Arc XP for service monitoring. Enabled by default per player instance; you can opt out.
- Product analytics (your tool of choice), which you wire yourself. The library exposes the events you need so you can forward them to GA, Segment, Adobe, Amplitude, or anything else.
1. Built-in playback telemetry (QoE)
The player automatically collects playback quality-of-experience telemetry and reports it to Arc XP for platform-wide playback-health monitoring. Collection runs through Mux, a third-party QoE service, under Arc XP’s account: you have nothing to set up, and the data never reaches any analytics account of yours.
When it runs
Telemetry starts automatically when the player loads a clip by media-id (this also requires org-id):
<arc-audio-player org-id="your-org-id" media-id="some-media-id" env="prod"></arc-audio-player>Direct-stream playback (the stream attribute instead of media-id) sends no telemetry.
What the player collects
- 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, operating system, connection metadata
Turn off telemetry
To turn the built-in telemetry off, add the disable-default-analytics attribute (or set disableDefaultAnalytics: true inside config; the config value wins over the attribute when both exist). Turn it off for consent or data-processing reasons, or because you already run a separate analytics setup:
<arc-audio-player org-id="your-org-id" media-id="some-media-id" disable-default-analytics></arc-audio-player>Opting out only disables the telemetry. The arc-player-* events that follow still fire, so you can turn off the built-in reporting and run separate analytics on those events.
Telemetry scope
This telemetry reports playback service health: errors, buffering, and startup performance. To measure engagement (clicks, click-driven versus autoplay-driven plays, funnels, sign-ups, or attribution), wire the player’s events into a separate tool by using the patterns later in this guide.
2. Tracking playback yourself
Prefer the built-in playback events
The library already emits high-level playback events on the player element (bubbling), so in most cases you do not need the manual timeupdate math in the next section:
arc-player-playback-started: fires once when playback actually begins.arc-player-playback-progress: fires once per watched quartile, withdetail.quartile=25|50|75(the player ignores seeks, so scrubbing does not false-fire).arc-player-playback-completed: fires on completion.
Each event’s detail includes the player instance and a context snapshot. They bubble, so one document-level listener covers every player on the page:
document.addEventListener('arc-player-playback-started', (e) => { myAnalytics.track('audio_play', { mediaId: e.detail.player.getAttribute('media-id') });});Reach for the manual approach below only when you need custom thresholds.
Manual tracking with native media events
The library dispatches a arc-player-ready event on document after the player mounts. Its detail.player holds the custom-element instance, which exposes mediaElement (the underlying <audio> element) through a getter.
Native HTML5 media events offer another way to track reproductions:
<arc-audio-player org-id="..." media-id="..."></arc-audio-player>
<script> document.addEventListener('arc-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, and so on).
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. This approach pierces the shadow DOM, so internal markup can change between library versions. |
| Player-container clicks (a click that lands 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('arc-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 stays vendor-neutral for product analytics: any SDK works. The following example uses Google Analytics 4:
document.addEventListener('arc-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(...), and so on.
Reference: available events and properties
All event names use the standard arc-player-* form. Only arc-player-ready and arc-player-error have earlier koi-player-* aliases that still fire for back-compat, and the playback events have no earlier alias.
| Name | Where | What you get |
|---|---|---|
arc-player-ready | Dispatched once per player (on the element and document) | detail.player, the custom-element instance |
arc-player-error | Dispatched if the player fails to initialize | detail.player, detail.error |
arc-player-playback-started | Dispatched on the player element when playback begins | detail.player, detail.context |
arc-player-playback-progress | Dispatched once per watched quartile | detail.quartile (25/50/75), detail.player, detail.context |
arc-player-playback-completed | Dispatched on completion | detail.player, detail.context |
player.mediaElement | Getter on the audio player | The underlying <audio> element; use it to listen to native HTML5 media events |
player.shadowRoot | Open mode, direct access | Media-chrome control elements (use for click tracking) |