> ## 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 Vue and Nuxt
> Embed the Bunny Stream player in a Vue or Nuxt app and control playback with player.js events and methods.
This guide builds a Vue `BunnyPlayer` component that renders the Bunny Player iframe, emits its events, and exposes a [player.js](https://github.com/embedly/player.js) `Player` for controlling playback.
Nuxt can server-render it as is. player.js reads `window` on import, which is why the component imports it in `onMounted`, and the iframe waits for the library before it renders.
> [card] [Vue example on GitHub](https://github.com/BunnyWay/examples/tree/main/stream/player-vue)
>
> A Vite app with custom controls and an event log.
> [card] [Nuxt example on GitHub](https://github.com/BunnyWay/examples/tree/main/stream/player-nuxt)
>
> The same component in a Nuxt 4 app.
## Quickstart
**1. Install player.js**
```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
```
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: never[]) => void): void;
supports(kind: "method" | "event", name: string | string[]): boolean;
/** Send a raw command, for methods player.js does not expose such as setPlaybackRate. Getters answer through the callback. */
send(message: { method: string; value?: unknown }, callback?: (value: unknown) => void): 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;
addEvent(elem: EventTarget, type: string, handler: EventListener): void;
};
export default playerjs;
}
```
**2. Create the component**
```vue components/BunnyPlayer.vue theme={null}
```
In Nuxt, save it under `components/` and it's auto-imported. Skip ``: the server renders the placeholder and the browser swaps in the iframe.
**3. Render a video**
The library ID and video GUID are on the video's page in the dashboard.
```vue theme={null}
```
`params` takes any [player parameter](https://bunny.net/docs/stream/embedding.md#supported-parameters), such as `captions`, `t`, or `muted`.
## Control playback
The `ready` event carries the `Player`. Keep it in a `shallowRef`, which stops Vue wrapping it in a deep reactive proxy.
```vue theme={null}
```
Getters answer through a callback, since the value comes back from the iframe.
```ts theme={null}
player.value?.getCurrentTime((seconds) => console.log(seconds));
player.value?.getDuration((seconds) => console.log(seconds));
```
Playback speed is missing from the npm build (0.1.0), and `send()` covers the gap. The build we host adds `setPlaybackRate()`, `getPlaybackRate()` and `playbackratechange` ([Methods](https://bunny.net/docs/stream/playback-api.md#methods)).
```ts theme={null}
player.value?.send({ method: "setPlaybackRate", value: 1.5 });
```
Browsers block unmuted `play()` before the viewer has clicked anything. Call `mute()` first if playback has to start on its own. The [Playback control API](https://bunny.net/docs/stream/playback-api.md) lists every method and event.
## Track progress
`timeupdate` fires several times a second. Throttle it before it reaches your backend.
```vue theme={null}
```
Pass the saved position back as the `t` parameter to resume.
## Load player.js from the CDN instead
To drop the npm dependency, load our hosted build from the component.
```ts lib/load-playerjs.ts theme={null}
type PlayerJs = (typeof import("player.js"))["default"];
declare global {
interface Window {
playerjs: PlayerJs;
}
}
let loading: Promise | undefined;
export function loadPlayerjs() {
loading ??= new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://assets.mediadelivery.net/playerjs/playerjs-latest.min.js";
script.onload = () => resolve(window.playerjs);
script.onerror = (error) => {
loading = undefined;
reject(error);
};
document.head.append(script);
});
return loading;
}
```
Swap the dynamic import in `onMounted` for the loader.
```ts components/BunnyPlayer.vue theme={null}
import { loadPlayerjs } from "../lib/load-playerjs";
onMounted(async () => {
playerjs.value = await loadPlayerjs();
});
```
## Troubleshooting
> [accordion] ReferenceError: window is not defined
>
> player.js is being imported during server rendering. Keep `import("player.js")` inside `onMounted`. A static `import playerjs from "player.js"` at the top of `
> ```
> [accordion] The iframe shows a 403
>
> The library's allowed domains, direct access block, or token authentication is rejecting the embed. See [Embedding restrictions](https://bunny.net/docs/stream/embedding.md#embedding-restrictions).
This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.