> ## 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 Svelte and SvelteKit > Embed the Bunny Stream player in a Svelte or SvelteKit app and control playback with player.js events and methods. This guide builds a `BunnyPlayer` component with Svelte 5 runes. It renders the Bunny Player iframe, passes its events to callback props, and hands you a [player.js](https://github.com/embedly/player.js) `Player` for controlling playback. SvelteKit renders it on the server without changes. The component imports player.js in `onMount`, since the library reads `window` on import, and holds the iframe back until the library is ready. > [card] [SvelteKit example on GitHub](https://github.com/BunnyWay/examples/tree/main/stream/player-sveltekit) > > A SvelteKit 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** ```svelte src/lib/components/BunnyPlayer.svelte theme={null} {#if playerjs} {#key src} {/key} {:else} {/if} ``` `{#key src}` replaces the iframe when the video changes, and the effect runs again with a fresh `Player`. Callback props are read inside the event handlers, which means a new inline function won't re-create the player. **3. Add your library ID** Create `.env` with the library ID from your library's **API** page. ```bash .env theme={null} PUBLIC_BUNNY_LIBRARY_ID=12345 ``` Declare it in `src/env.ts`. `public: true` lets browser code import it, and `static: true` inlines it at build time. ```ts src/env.ts theme={null} import { defineEnvVars } from "@sveltejs/kit/env"; export const variables = defineEnvVars({ PUBLIC_BUNNY_LIBRARY_ID: { public: true, static: true }, }); ``` **4. Render a video** Load the video ID in `+page.server.ts`. ```ts src/routes/lessons/[id]/+page.server.ts theme={null} import { error } from "@sveltejs/kit"; import type { PageServerLoad } from "./$types"; // Swap this for your database. const lessons: Record = { intro: { title: "Introduction", videoId: "your-video-guid" }, }; export const load: PageServerLoad = ({ params }) => { const lesson = lessons[params.id]; if (!lesson) error(404, "Lesson not found"); return { lesson }; }; ``` The page reads it from `data`. ```svelte src/routes/lessons/[id]/+page.svelte theme={null}

{data.lesson.title}

console.log("Video finished")} /> ``` The library ID is public in every embed URL, so it is safe to send to the browser. `params` takes any [player parameter](https://bunny.net/docs/stream/embedding.md#supported-parameters), such as `captions`, `t`, or `muted`. ## Control playback `onready` hands you the `Player`. Keep it in state for your own controls. ```svelte theme={null} (player = instance)} onplay={() => (playing = true)} onpause={() => (playing = false)} /> ``` Getters answer through a callback, since the value comes back from the iframe. ```ts theme={null} player?.getCurrentTime((seconds) => console.log(seconds)); player?.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?.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, here through an API route. ```svelte theme={null} { if (Math.abs(seconds - lastSaved) < 5) return; lastSaved = seconds; save(seconds, duration); }} /> ``` 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 src/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 `onMount` for the loader. ```ts src/lib/components/BunnyPlayer.svelte theme={null} import { loadPlayerjs } from "../load-playerjs"; onMount(async () => { playerjs = await loadPlayerjs(); }); ``` ## Troubleshooting > [accordion] ReferenceError: window is not defined > > player.js is being imported during server rendering. Keep `import("player.js")` inside `onMount`. A static `import playerjs from "player.js"` at the top of the script block runs on the server in SvelteKit. > [accordion] onready never fires > > The iframe loaded before player.js. Render it only once player.js has loaded, as the component above does. > > A hidden tab also holds `ready` back until the viewer switches to it. > [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.