> ## Documentation Index
> Fetch the complete documentation index at: https://bunny.net/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Use Bunny Player with JavaScript

> Embed the Bunny Stream player with plain JavaScript or TypeScript, no framework, and control playback with player.js events and methods.

With no framework, Bunny Player comes down to one function. It appends the iframe and returns a [player.js](https://github.com/embedly/player.js) `Player` for talking to it.

<Card title="JavaScript example on GitHub" icon="github" href="https://github.com/BunnyWay/examples/tree/main/stream/player-vanilla" horizontal>
  A framework-free Vite app with custom controls and an event log.
</Card>

## Quickstart

<Steps>
  <Step title="Install player.js">
    <CodeGroup>
      ```bash npm theme={null}
      npm install player.js
      ```

      ```bash pnpm theme={null}
      pnpm add player.js
      ```

      ```bash yarn theme={null}
      yarn add player.js
      ```

      ```bash bun theme={null}
      bun add player.js
      ```
    </CodeGroup>

    player.js ships without types. Add a declaration file anywhere your `tsconfig.json` includes, for example `player.js.d.ts`. It covers the methods and events the Bunny Player supports:

    ```ts player.js.d.ts theme={null}
    declare module "player.js" {
      export type PlayerEvent =
        | "ready"
        | "play"
        | "pause"
        | "ended"
        | "timeupdate"
        | "progress"
        | "seeked"
        | "error"
        | "playbackratechange";

      export type TimeUpdate = { seconds: number; duration: number };
      export type Progress = { percent: number; seconds: number; duration: number };
      /** Present when a command fails. Empty when the media itself errors. */
      export type PlayerError = { code: number; msg: string };

      export class Player {
        constructor(iframe: HTMLIFrameElement | string);

        on(event: "ready", callback: () => void): void;
        on(event: "timeupdate", callback: (data: TimeUpdate) => void): void;
        on(event: "progress", callback: (data: Progress) => void): void;
        on(event: "playbackratechange", callback: (rate: number) => void): void;
        on(event: "error", callback: (error?: PlayerError) => void): void;
        on(event: PlayerEvent, callback: (data?: unknown) => void): void;
        off(event: PlayerEvent, callback?: (...args: unknown[]) => void): void;
        supports(kind: "method" | "event", name: string | string[]): boolean;
        /** Send a raw command, for methods player.js does not expose such as setPlaybackRate. */
        send(message: { method: string; value?: unknown }): void;

        play(): void;
        pause(): void;
        mute(): void;
        unmute(): void;
        setVolume(percent: number): void;
        setCurrentTime(seconds: number): void;
        setLoop(loop: boolean): void;

        getPaused(callback: (paused: boolean) => void): void;
        getMuted(callback: (muted: boolean) => void): void;
        getVolume(callback: (percent: number) => void): void;
        getDuration(callback: (seconds: number) => void): void;
        getCurrentTime(callback: (seconds: number) => void): void;
        getLoop(callback: (loop: boolean) => void): void;
      }

      const playerjs: { Player: typeof Player };
      export default playerjs;
    }
    ```
  </Step>

  <Step title="Create the player function">
    The `Player` has to exist before the iframe finishes loading. Creating both in one function takes care of the order.

    ```ts src/bunny-player.ts theme={null}
    import playerjs, { type Player } from "player.js";

    export type BunnyPlayerOptions = {
      libraryId: string;
      videoId: string;
      /** Player parameters such as autoplay, muted, captions, or t. */
      params?: Record<string, string | number | boolean>;
      title?: string;
    };

    export function createBunnyPlayer(
      container: HTMLElement,
      { libraryId, videoId, params, title = "Video player" }: BunnyPlayerOptions,
    ): Player {
      const query = new URLSearchParams(
        Object.entries(params ?? {}).map(([key, value]) => [key, String(value)]),
      ).toString();

      const iframe = document.createElement("iframe");
      iframe.src = `https://player.mediadelivery.net/embed/${libraryId}/${videoId}${query ? `?${query}` : ""}`;
      iframe.title = title;
      iframe.loading = "lazy";
      iframe.allow = "autoplay; encrypted-media; picture-in-picture; fullscreen";
      iframe.allowFullscreen = true;
      iframe.style.cssText = "display: block; width: 100%; aspect-ratio: 16 / 9; border: 0; background: #000;";
      container.append(iframe);

      return new playerjs.Player(iframe);
    }
    ```
  </Step>

  <Step title="Mount it">
    ```ts src/main.ts theme={null}
    import { createBunnyPlayer } from "./bunny-player";

    const player = createBunnyPlayer(document.querySelector("#player")!, {
      libraryId: "12345",
      videoId: "your-video-guid",
      params: { preload: true },
    });

    player.on("ready", () => console.log("ready"));
    ```

    `params` takes any [player parameter](/docs/stream/embedding#supported-parameters).
  </Step>
</Steps>

## Control playback

```ts theme={null}
player.on("ready", () => {
  document.querySelector("#toggle")!.addEventListener("click", () => {
    // Ask the player, because viewers can also use its own controls.
    player.getPaused((paused) => (paused ? player.play() : player.pause()));
  });
  document.querySelector("#restart")!.addEventListener("click", () => player.setCurrentTime(0));
});
```

Getters answer through a callback, since the value comes back from the iframe. Playback speed is missing from the npm build (0.1.0), and `send()` covers the gap:

```ts theme={null}
player.send({ method: "setPlaybackRate", value: 1.5 });
```

Browsers block unmuted `play()` before the viewer has clicked anything. Mute first if playback has to start on its own. The [Playback control API](/docs/stream/playback-api) lists every method and event.

## Track progress

`timeupdate` fires several times a second. Throttle it before it reaches your backend.

```ts theme={null}
let lastSaved = 0;

player.on("timeupdate", ({ seconds, duration }) => {
  if (seconds - lastSaved < 5) return;
  lastSaved = seconds;
  void saveProgress(seconds, duration);
});
```

Pass the saved position back as `params: { t: savedSeconds }` to resume.

## Use it without a bundler

We host a build of player.js that adds `setPlaybackRate()` and the `playbackratechange` event ([Methods](/docs/stream/playback-api#methods)). Classic scripts run as the browser parses them, which puts both scripts ahead of the iframe's load.

```html theme={null}
<head>
  <script src="https://assets.mediadelivery.net/playerjs/playerjs-latest.min.js"></script>
</head>
<body>
  <iframe
    id="player"
    src="https://player.mediadelivery.net/embed/12345/your-video-guid"
    allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
    allowfullscreen
  ></iframe>
  <script>
    const player = new playerjs.Player(document.querySelector("#player"));
    player.on("ready", () => console.log("ready"));
  </script>
</body>
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="The ready event never fires">
    The iframe loaded before player.js was listening. Module and `defer` scripts run after parsing, by which time an iframe in the HTML may be done. Create the iframe from script, as `createBunnyPlayer` does.

    A hidden tab also holds `ready` back until the viewer switches to it.
  </Accordion>

  <Accordion title="Events fire twice after replacing the video">
    Each `Player` leaves a `message` listener on `window` for good. Guard its handlers with a flag and flip it when you remove the iframe.
  </Accordion>

  <Accordion title="The iframe shows a 403">
    The library's allowed domains, direct access block, or token authentication is rejecting the embed. See [Embedding restrictions](/docs/stream/embedding#embedding-restrictions).
  </Accordion>
</AccordionGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.