Skip to main content
The Bunny Player is an iframe, and player.js gives you a Player object for talking to it over postMessage. This guide builds a BunnyPlayer component with Svelte 5 runes that renders the iframe, forwards its events as callback props, and hands you that object. The component also works under SvelteKit server rendering. player.js is imported in onMount, because it reads window when imported, and the iframe renders only after the library is available, because a Player has to exist before its iframe finishes loading.

Quickstart

1

Install 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:
player.js.d.ts
2

Create the component

src/lib/components/BunnyPlayer.svelte
{#key src} replaces the iframe when the video changes, so the effect runs again with a fresh Player. The callback props are read inside the event handlers, so passing a new inline function doesn’t re-create the player.
3

Render a video

Load the video ID in +page.server.ts and pass it through data:
src/routes/lessons/[id]/+page.svelte
The library ID already appears in every embed URL, so a PUBLIC_ variable is fine. params accepts any player parameter, such as captions, t, or muted.

Control playback

onready hands you the Player. Keep it in state and call its methods from your own controls:
Getters take a callback, because the answer comes back from the iframe:
The player.js package on npm has no playback speed method, so send the command directly. The build bunny.net hosts adds setPlaybackRate() and getPlaybackRate(); see Methods.
Browsers block unmuted play() until the viewer has interacted with the page, so call mute() first if playback has to start without a click. Playback control API lists every method and event.

Track progress

timeupdate fires several times a second with { seconds, duration }. Throttle it before writing to your backend, for example through an API route:
Pass the saved position back as the t parameter to resume from there.

Multiple players on one page

player.js matches the ready message to an iframe by its src, so two iframes with identical URLs confuse it. Give each one a parameter the player ignores, such as params={{ instance: crypto.randomUUID() }} computed once per component.

Load player.js from the CDN instead

To skip the npm dependency, load the hosted build in src/app.html:
src/app.html
Replace the dynamic import in onMount with playerjs = window.playerjs and declare the global in src/app.d.ts:
src/app.d.ts

Troubleshooting

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.
The iframe finished loading before the Player existed. Render the iframe only after player.js has loaded, as the component above does, so both happen in the same update.
Every Player adds a message listener to window that is never removed. The active flag in the effect cleanup keeps stale instances quiet, so check that yours flips it.
The library’s allowed domains, direct access block, or token authentication is rejecting the embed. See Embedding restrictions.
Last modified on September 21, 2026