# Get started

> Add playback tracking to your player.

`@chunkify/player-analytics` is the playback tracker for Chunkify Player, HTML video, hls.js, Video.js 10, and Shaka Player.

Track watch time, startup delays, buffering, and playback errors. Send events to [Chunkify Analytics](/docs/integration/analytics/server), PostHog, Datadog RUM, an OpenTelemetry-compatible endpoint, or your own backend. You can send to several destinations at once.

<div class="not-prose my-6 grid grid-cols-2 sm:grid-cols-4 items-center gap-4 rounded-lg border border-border bg-white p-4" aria-label="Supported analytics destinations">
    <a href="/docs/integration/analytics/send-events#choose-a-destination" class="flex h-12 items-center justify-center rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary" aria-label="Set up Chunkify Analytics">
        <img src="/docs/assets/images/analytics/destinations/chunkify.svg" alt="Chunkify" width="128" height="40" class="h-10 w-32 object-contain"/>
    </a>
    <a href="/docs/integration/analytics/send-events#choose-a-destination" class="flex h-12 items-center justify-center rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary" aria-label="Set up PostHog">
        <img src="/docs/assets/images/analytics/destinations/posthog.svg" alt="PostHog" width="128" height="40" class="h-10 w-32 object-contain"/>
    </a>
    <a href="/docs/integration/analytics/send-events#choose-a-destination" class="flex h-12 items-center justify-center rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary" aria-label="Set up Datadog RUM">
        <img src="/docs/assets/images/analytics/destinations/datadog.png" alt="Datadog" width="128" height="40" class="h-10 w-32 object-contain"/>
    </a>
    <a href="/docs/integration/analytics/send-events#choose-a-destination" class="flex h-12 items-center justify-center rounded focus-visible:outline-2 focus-visible:outline-offset-4 focus-visible:outline-primary" aria-label="Set up OpenTelemetry delivery">
        <img src="/docs/assets/images/analytics/destinations/opentelemetry.svg" alt="OpenTelemetry" width="128" height="40" class="h-10 w-32 object-contain"/>
    </a>
</div>

The PostHog and Datadog RUM adapters use your existing browser clients. They do not require a Chunkify Analytics Server. See [Send events](/docs/integration/analytics/send-events) for destination setup.

## Install the tracker

Add the playback tracker to your application:

```sh
npm install @chunkify/player-analytics
```

Use it in browser code alongside your existing player. Your player and its playback URL should already be configured.

## Connect your player

Choose the example for your player. Each example logs events to the browser console so you can check collection before configuring a destination.

You can use any stable video identifier as `videoId`, including your application's own ID. Using a Chunkify Asset ID is optional. When it matches an Asset in your Chunkify Project, the analytics dashboard in the Chunkify web app can also show its title, poster, and timeline thumbnails when available, with a link to the Asset.

Replace the sample Asset ID in these examples with your chosen video ID. The tracker sends this value as `video_id` in each event.

<tabs>
    <tab title="Chunkify Player">
        Follow the [Player setup](/docs/integration/player) if needed, then add an ID to your player:

        ```html
        <chunkify-player
          id="player"
          src="https://media.example.com/videos/manifest.m3u8"
          crossorigin="anonymous"
          aspect-ratio="landscape"
          controls
          playsinline
        ></chunkify-player>
        ```

        In your browser entry file, attach analytics after the component is registered:

        ```javascript
        import '@chunkify/player';
        import { monitor } from '@chunkify/player-analytics';

        await customElements.whenDefined('chunkify-player');
        const player = document.querySelector('#player');
        if (!player) throw new Error('Missing #player element');

        const destination = { send: (event) => console.log(event) };
        const session = monitor(player, {
            videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
            destinations: [destination],
        });
        ```

        `monitor()` follows the player's active media element when it switches between HLS and regular video files.
    </tab>
    <tab title="React">
        With the [Chunkify Player React wrapper](/docs/integration/player#add-the-player), use a stable ref callback to attach analytics when the player mounts. React 19 calls the returned cleanup when it unmounts.

        ```tsx
        'use client';

        import { useCallback } from 'react';
        import {
            ChunkifyPlayer,
            type ChunkifyPlayerRef,
        } from '@chunkify/player-react';
        import { monitor, type AnalyticsDestination } from '@chunkify/player-analytics';

        export default function Video() {
            const attachAnalytics = useCallback((player: ChunkifyPlayerRef | null) => {
                if (!player) return;

                const destination: AnalyticsDestination = {
                    send: (event) => console.log(event),
                };
                const session = monitor(player, {
                    videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
                    destinations: [destination],
                });

                return () => session.destroy();
            }, []);

            return (
                <ChunkifyPlayer
                    ref={attachAnalytics}
                    src="https://media.example.com/videos/manifest.m3u8"
                    crossOrigin="anonymous"
                    aspectRatio="landscape"
                    controls
                    playsInline
                />
            );
        }
        ```
    </tab>
    <tab title="HTML video">
        `monitor()` also accepts a native `<video>` element. For example, add an MP4 video to your page:

        ```html
        <video
          id="player"
          src="https://media.example.com/videos/video.mp4"
          controls
          playsinline
        ></video>
        ```

        Attach analytics in your browser entry file:

        ```javascript
        import { monitor } from '@chunkify/player-analytics';

        const video = document.querySelector('video#player');
        if (!video) throw new Error('Missing video#player element');

        const destination = { send: (event) => console.log(event) };
        const session = monitor(video, {
            videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
            destinations: [destination],
        });
        ```
    </tab>
    <tab title="hls.js">
        Use `monitorHlsJs()` with the `Hls` instance that manages your video. Attach analytics before loading the stream. This example assumes your page has `<video id="player" controls playsinline></video>`.

        ```javascript
        import Hls from 'hls.js';
        import { monitorHlsJs } from '@chunkify/player-analytics';

        const video = document.querySelector('video#player');
        if (!video) throw new Error('Missing video#player element');
        if (!Hls.isSupported()) throw new Error('hls.js is not supported in this browser');

        const hls = new Hls();
        const destination = { send: (event) => console.log(event) };
        const session = monitorHlsJs(hls, {
            videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
            destinations: [destination],
        });

        hls.attachMedia(video);
        hls.loadSource('https://media.example.com/videos/manifest.m3u8');
        ```

        Call `session.destroy()` before destroying the `Hls` instance. Use `session.sourceChanged()` when replacing the source for the same monitored video.
    </tab>
    <tab title="Video.js 10">
        With a [Video.js 10 HTML player](https://videojs.org/docs/framework/html/guides/architecture) already configured, pass its HLS media element to `monitorVideoJs()`. It accepts `<hlsjs-video>`, `<hls-video>`, and `<native-hls-video>`.

        For a player using `<hlsjs-video>`, add this to your browser entry file:

        ```javascript
        import { monitorVideoJs } from '@chunkify/player-analytics';

        await customElements.whenDefined('hlsjs-video');
        const media = document.querySelector('hlsjs-video');
        if (!media) throw new Error('Missing hlsjs-video element');

        const destination = { send: (event) => console.log(event) };
        const session = monitorVideoJs(media, {
            videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
            destinations: [destination],
        });
        ```

        The connector targets Video.js 10's HTML media components. Pass the media element inside the player. For a plain `<video>`, use the HTML video example.
    </tab>
    <tab title="Shaka Player">
        Use `monitorShaka()` after attaching Shaka to the video and before calling `load()`. This example assumes your page has `<video id="player" controls playsinline></video>`.

        ```javascript
        import shaka from 'shaka-player';
        import { monitorShaka } from '@chunkify/player-analytics';

        shaka.polyfill.installAll();
        const video = document.querySelector('video#player');
        if (!video) throw new Error('Missing video#player element');
        if (!shaka.Player.isBrowserSupported()) {
            throw new Error('Shaka Player is not supported in this browser');
        }

        const player = new shaka.Player();
        await player.attach(video);

        const destination = { send: (event) => console.log(event) };
        const session = monitorShaka(player, {
            videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
            destinations: [destination],
        });

        try {
            await player.load('https://media.example.com/videos/manifest.m3u8');
        } catch (error) {
            console.error('Could not load the video', error);
        }
        ```

        Shaka starts a view when it loads a source. Call `session.destroy()` before destroying the Shaka player.

        See [Shaka's setup guide](https://shaka-project.github.io/shaka-player/docs/api/tutorial-basic-usage.html) for its player configuration.
    </tab>
</tabs>

Press Play and check the console. A playback attempt emits `chunkify.video.view_started`. Playing, pausing, and seeking produce further events.

Cumulative snapshots are sent every 30 seconds of active playing time by default, and when tab visibility changes during an attempt.

Keep the returned `session` and call `session.destroy()` when removing the player or disabling analytics. This ends the current view and removes the analytics listeners. The React example handles this through its ref cleanup.

Call `session.startAttempt()` immediately before an application-initiated `play()` to include attempts that fail before a browser play event.

When switching to a different video, destroy the current session and attach a new one with that video's `videoId`.

## Configure collection

All player connectors accept the same options:

| Option | Purpose and default |
| --- | --- |
| `videoId` | Required. Identifies the video in each event. |
| `destinations` | Required. A non-empty array of adapters or objects with a `send(event)` method. |
| `videoTitle` | Optional human-readable title shown next to `videoId` in reports, where you can also filter and group by it. Each view keeps the title it started with. Send the same title everywhere the video plays, such as its Chunkify Asset title, so filtering by title counts all of its views: views sent without a title, or with another one, are only found by `videoId`. Longer titles are truncated to 256 characters. |
| `userId` | Optional ID of the signed-in user in your application, 1 to 256 characters. Use a pseudonymous ID, not an email address. |
| `custom` | Optional [custom dimensions](#custom-dimensions), such as a plan or an A/B test variant, sent on each view's start. |
| `persistViewerId` | Keeps the generated anonymous viewer ID in `localStorage` across page loads. Defaults to `true`; set `false` until the viewer consents to storage. |
| `environment` | Optional label, such as `production`, included in events. |
| `snapshotIntervalMs` | Active playing time between snapshots, in integer milliseconds. Defaults to `30000`; range `1000` to `2147483647`. |
| `includeEvents` | [Event types to send](/docs/integration/analytics/events#choose-events). All nine types are enabled by default. An empty array sends nothing. |

Subtitle language and playback speed are recorded automatically from the media element, including the choices made in Chunkify Player's menus. See [Subtitles and playback speed](/docs/integration/analytics/events#subtitles-and-playback-speed).

### Custom dimensions

Use `custom` to label views with your own dimensions, then filter and group reports by them. Set it once when you start monitoring; it applies to every view in that session.

```javascript
const session = monitor(player, {
    videoId: 'ast_3Ljtoej7c3Y1plabZoElbQwdwLh',
    destinations: [destination],
    custom: { plan: 'premium', ab_test: 'B', seats: 5, trial: false },
});
```

`custom` accepts up to 10 keys. Each key uses 1 to 40 lowercase letters, digits, or underscores. Each value is text of 1 to 256 characters, a finite number, or a boolean. Monitoring throws an error at startup when `custom` breaks these limits; `null` or an empty object sends no dimensions.

The tracker sends `custom` unchanged on `view_started` only. Other events in the view share its `view_id`, and reports read each view's dimensions from its start.

<warning>
Do not put personal data such as names or email addresses in custom values. Use `userId` with a pseudonymous ID to identify signed-in users.
</warning>

See [Events and metrics](/docs/integration/analytics/events) for an event selection example, the complete event list, payloads, and available measurements.

Continue with [Send events](/docs/integration/analytics/send-events) to connect your analytics service, send to multiple destinations, or customize event fields.
