> ## 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 React > Embed the Bunny Stream player in a React app and control playback with player.js events and methods. React renders the Bunny Player iframe like any other element. The `BunnyPlayer` component below adds [player.js](https://github.com/embedly/player.js), forwards the player's events as props, and hands you a `Player` for controlling playback. It assumes a client-rendered app, such as one built with Vite. For server rendering, including Remix and React Router framework mode, follow the [Next.js guide](https://bunny.net/docs/stream/player/nextjs.md). > [card] [React example on GitHub](https://github.com/BunnyWay/examples/tree/main/stream/player-react) > > A Vite 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 component** The `Player` is created once the iframe is in the DOM. Callbacks live in a ref, which stops inline props from re-creating the player on every render. ```tsx components/bunny-player.tsx theme={null} import { useEffect, useRef } from "react"; import playerjs, { type Player, type TimeUpdate } from "player.js"; 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; }; export function BunnyPlayer({ libraryId, videoId, params, title = "Video player", onReady, onPlay, onPause, onEnded, onTimeUpdate, }: BunnyPlayerProps) { const iframeRef = useRef(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 }; }); 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 (!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); }, [src]); return (