Skip to main content
Astro renders the Bunny Player iframe on the server and ships no JavaScript until you ask for some. This guide asks for very little: a <bunny-player> custom element that attaches player.js and turns the player’s events into DOM events.

Astro example on GitHub

Custom controls and an event log around this component. bun install && bun dev plays a demo video.

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

The embed URL waits in data-src until the element has a Player listening. An iframe with src in the HTML can finish loading first, and then ready never arrives.
src/components/BunnyPlayer.astro
3

Add it to a page

src/pages/index.astro
params takes any player parameter.

Control playback

ready hands over the Player as event.detail.
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:
Browsers block unmuted play() before the viewer has clicked anything. 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.
Pass the saved position back as params={{ t: savedSeconds }} to resume.

Signed embed URLs

With embed view token authentication on, sign the URL in the page frontmatter and pass token and expires through params. Render that page on demand. A prerendered page hands every visitor the same token, and it expires. The signing code is in Sign embed URLs on the server.

Load player.js from the CDN instead

We host a build of player.js that adds setPlaybackRate() and the playbackratechange event (Methods). Load it in your layout’s <head> with is:inline, which keeps Astro from bundling it and runs it ahead of the component’s script.
Swap the import for const playerjs = window.playerjs and declare the global.

Troubleshooting

The iframe loaded before player.js was listening. Keep the URL in data-src and let the element set src.A hidden tab also holds ready back until the viewer switches to it.
<ClientRouter /> runs page scripts once, leaving your ready listener on the previous page’s element. Register it inside an astro:page-load listener.
The library’s allowed domains, direct access block, or token authentication is rejecting the embed. See Embedding restrictions.
Last modified on October 6, 2026