h5p-offline-player

Setup

Three ways to put it on a page

The player is one web component, one Service Worker script and a folder of H5P runtime files. Which setup you need depends on what you can host. In every case, setting src loads and plays, the way it does on <video>.

What has to be where

FileWhere it can liveWhy
h5p-player.js
the element
Anywhere: your bundle, or a CDN It is an ordinary ES module.
h5p-sw.js
the Service Worker
Your own origin Browsers refuse to register a worker from another origin. It is the one file that cannot come from a CDN.
frame-assets/
the H5P runtime
Next to the element by default, or a CDN via assets-base Plain static files, loaded into the frame the worker generates.

There are no fixed paths. The worker is registered with the scope <its folder>/h5p/, so it never claims a scope a worker of your own might hold, and the frame document is generated by the worker rather than served as a file.

A · With a bundler

Vite, webpack 5 and Rollup. Install it, import it once, use the element.

npm i @missing-elements/h5p-offline-player
import '@missing-elements/h5p-offline-player'
<h5p-player src="https://host.example/course.h5p" auto-resize></h5p-player>

That is the whole setup. The element finds its worker and its runtime files with new URL('./h5p-sw.js', import.meta.url), which these bundlers recognise: they copy the files into the build and rewrite the URLs. Already have a Service Worker on the site? Nothing changes. Each registration has its own scope, and the two never see each other's requests.

A bundler that does not follow that pattern, esbuild among them, leaves the files behind. Then serve the package's dist/ folder from a static path and say where it is:

<h5p-player src="…"
            sw="/vendor/h5p-player/h5p-sw.js"
            assets-base="/vendor/h5p-player/frame-assets/"></h5p-player>

B · No build step

Load the element from the CDN, and copy the one file that has to be yours onto the site.

<script type="module"
  src="https://cdn.jsdelivr.net/npm/@missing-elements/h5p-offline-player/dist/h5p-player.js"></script>

<h5p-player src="https://host.example/course.h5p" sw="/h5p-sw.js" auto-resize></h5p-player>

Download dist/h5p-sw.js from the same CDN path, place it on your site, and point sw at it. The frame assets keep loading from the CDN. Putting the worker at the root is safe: its scope becomes /h5p/, not /. The element warns in the console when the worker and the element are different versions, so update both together.

C · An iframe, nothing on your site

For a site that cannot host a file at all, frame the player page. It sizes itself through H5P's own resizer protocol and relays xAPI statements to your page on request. The embed example shows the markup and the two listeners.

<iframe src="https://h5p-offline-player.vercel.app/embed?src=https://host.example/course.h5p&xapi=https://your-site.example"
        allow="fullscreen" style="width: 100%; border: 0"></iframe>

Requirements

Listening to it

Everything the player learns arrives as a DOM event on the element. Nothing is stored.

const player = document.querySelector('h5p-player')

player.addEventListener('xapi', (event) => send(event.detail.statement))
player.addEventListener('finished', (event) => console.log(event.detail.statement.result))
player.addEventListener('error', (event) => {
  if (event.detail.code === 'no-cors') offerFilePicker()
})

// A file from disk, when the host has no CORS headers: read with slice(), never loaded whole.
input.addEventListener('change', () => { player.file = input.files[0] })

Attributes

srcThe package URL. Setting it loads; setting it again aborts and reloads.
auto-resizeFollow the content's own height. Without it, listen to the resize event and size the element yourself.
swWhere the worker script is. Default: h5p-sw.js next to the element. Must be same-origin.
assets-baseThe folder of the frame assets. Default: frame-assets/ next to the element. May be a CDN.
librarieshub, or the URL of a .h5p that carries library folders, for exports that ship without their own. Default: such packages are refused, with the missing libraries named.
preloadauto pulls large compressed media once the content is up, instead of when the learner presses play.
allow-originsExtra origins the frame's Content Security Policy should permit, space separated: a tenant's video host, an in-house CDN.

The full guide, including single-worker hosts and troubleshooting, is h5p-player-setup.md in the repository; the design is in the architecture document.