Skip to main content
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 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.

SvelteKit example on GitHub

A SvelteKit app with custom controls and an event log.

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, 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.
.env
Declare it in src/env.ts. public: true lets browser code import it, and static: true inlines it at build time.
src/env.ts
4

Render a video

Load the video ID in +page.server.ts.
src/routes/lessons/[id]/+page.server.ts
The page reads it from data.
src/routes/lessons/[id]/+page.svelte
The library ID is public in every embed URL, so it is safe to send to the browser. params takes any player parameter, such as captions, t, or muted.

Control playback

onready hands you the Player. Keep it in state for your own controls.
Getters answer through a callback, since the value comes back from the iframe.
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).
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 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.
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.
src/lib/load-playerjs.ts
Swap the dynamic import in onMount for the loader.
src/lib/components/BunnyPlayer.svelte

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 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.
The library’s allowed domains, direct access block, or token authentication is rejecting the embed. See Embedding restrictions.
Last modified on October 7, 2026