> ## 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 Next.js > Embed the Bunny Stream player in a Next.js app with a client component that survives server rendering, and control playback with player.js. [player.js](https://github.com/embedly/player.js) controls the Bunny Player iframe, and server rendering gives it two rules. It reads `window` on import, which means importing it in the browser. It also has to load before the iframe does. player.js caches the iframe's `ready` message on import, and a `Player` created any time after that still connects. An iframe that finishes loading first, straight from the server HTML, never becomes ready. The component below renders a placeholder on the server, loads player.js in an effect, then mounts the iframe and creates the `Player` in the same commit. It works with the App Router and the Pages Router. For a client-only app, see the [React guide](https://bunny.net/docs/stream/player/react.md). > [card] [Next.js example on GitHub](https://github.com/BunnyWay/examples/tree/main/stream/player-nextjs) > > An App Router app with custom controls and an event log. ## 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 client component** ```tsx components/bunny-player.tsx theme={null} "use client"; import { useEffect, useRef, useState, type CSSProperties } from "react"; import type { Player, TimeUpdate } from "player.js"; type PlayerJs = (typeof import("player.js"))["default"]; export type BunnyPlayerProps = { libraryId: string; videoId: string; params?: Record; title?: string; onReady?: (player: Player) => void; onPlay?: () => void; onPause?: () => void; onEnded?: () => void; onTimeUpdate?: (time: TimeUpdate) => void; }; const frameStyle: CSSProperties = { display: "block", width: "100%", height: "auto", aspectRatio: "16 / 9", border: 0, background: "#000", }; export function BunnyPlayer({ libraryId, videoId, params, title = "Video player", onReady, onPlay, onPause, onEnded, onTimeUpdate, }: BunnyPlayerProps) { const iframeRef = useRef(null); const [playerjs, setPlayerjs] = useState(null); // Keep the latest callbacks without re-creating the player. const handlers = useRef({ onReady, onPlay, onPause, onEnded, onTimeUpdate }); useEffect(() => { handlers.current = { onReady, onPlay, onPause, onEnded, onTimeUpdate }; }); // player.js reads window when imported, so load it in the browser only. useEffect(() => { let cancelled = false; import("player.js").then((mod) => { if (!cancelled) setPlayerjs(mod.default); }); return () => { cancelled = true; }; }, []); const query = new URLSearchParams( Object.entries(params ?? {}).map(([key, value]) => [key, String(value)]), ).toString(); const src = `https://player.mediadelivery.net/embed/${libraryId}/${videoId}${query ? `?${query}` : ""}`; useEffect(() => { const iframe = iframeRef.current; if (!playerjs || !iframe) return; // player.js never removes the window listener each Player adds, // so grab it while constructing and remove it on cleanup. let onMessage: EventListener = () => {}; const addEvent = playerjs.addEvent; playerjs.addEvent = (elem, type, handler) => addEvent(elem, type, (onMessage = handler)); const player = new playerjs.Player(iframe); playerjs.addEvent = addEvent; player.on("ready", () => handlers.current.onReady?.(player)); player.on("play", () => handlers.current.onPlay?.()); player.on("pause", () => handlers.current.onPause?.()); player.on("ended", () => handlers.current.onEnded?.()); player.on("timeupdate", (time) => handlers.current.onTimeUpdate?.(time)); return () => window.removeEventListener("message", onMessage); }, [playerjs, src]); // Hold the space until player.js is loaded so the iframe cannot // finish loading before the Player exists. if (!playerjs) { return