Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 48 additions & 59 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,51 @@

Self-hosted [H5P](https://h5p.org) without an H5P server. One web component plays `.h5p` files
from your own URLs or from disk, on static hosting you control: no Moodle, WordPress or Lumi, no
backend, no third-party service between your learners and your content.
backend, and no service of ours or H5P Group's between your learners and your content.

**[Live player demo](https://h5p-offline-player.vercel.app/)** ·
[Offline PWA app](https://h5p-offline-player.vercel.app/app/) ·
[Offline app](https://h5p-offline-player.vercel.app/app/) ·
[H5P to SCORM](https://h5p-offline-player.vercel.app/demo/scorm.html) ·
[Emded H5P Lovable demo](https://embed-h5p-file.lovable.app/) ·
[Lovable example](https://embed-h5p-file.lovable.app/) ·
[VS Code extension](https://marketplace.visualstudio.com/items?itemName=MissingElements.h5p-preview)

The library pack follows the H5P hub, and the Moodle's translations, **updates every week**. [Compatibility](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/compatibility.md) says what a version number
promises.
Version 1 since October 2026; [Compatibility](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/compatibility.md)
says what a version number promises. **Every week** a job rebuilds the library pack from the H5P
hub and the interface strings from Moodle's translations, opens a pull request when either
changed, and plays every content type in the pack, trusted and untrusted.

## Features

- **Plays the packages people have.** Large ones stream, and exports without their libraries, as
h5p.com and h5p.org make them, play against a [library pack](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/libraries.md)
of the 53 content types the H5P hub serves.
- **Plays packages you did not make** with [`untrusted`](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/untrusted.md): none of their
code runs, and their content is checked by a port of H5P's own validator.
- **Reports results you can audit:** xAPI statements that name the
[build of the package](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/revision.md) they came from, [cmi5](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/cmi5.md), [resume](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/resume.md), and a
[SCORM zip](https://github.com/missing-elements/h5p-offline-player/tree/main/packages/embed#a-scorm-package-for-an-lms): SCORM 1.2, checked in
Moodle, ILIAS, OpenOLAT, Chamilo, SCORM Cloud and Frappe LMS, or 2004, checked in Moodle and
SCORM Cloud.
- **No tracking:** no account, cookies or telemetry, and nothing it stores leaves the learner's
browser. The library pack comes from jsDelivr unless you serve a copy
([privacy](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/privacy.md)).
- **Works offline, in the content's language:** an installable app, and H5P's own buttons in
English and 56 other languages.
- **Tested in Chromium, Firefox and WebKit** on every change, and in Safari on macOS and iOS by
hand ([browsers](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/compatibility.md#browsers)).
- **Comes with tools:** [`h5p-verify`](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/verify.md) checks that a package plays,
[`h5p-normalize`](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/streaming-video.md) makes its video stream, and
[`h5p-author`](https://github.com/missing-elements/h5p-offline-player/tree/main/packages/author#readme) builds one from a short JSON source, which a
Moodle site accepts as an upload too.

It is a player, not an editor, and it stores no results: its events go wherever your page sends
them. Coming from [h5p-standalone](https://github.com/tunapanda/h5p-standalone)? This player
reads the `.h5p` itself, with nothing to unzip, and
[takes its options by the same names](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/h5p-standalone.md).

## Quick start

With a bundler (Vite, webpack 5, Rollup):
With Vite or webpack 5; both packages ship TypeScript declarations:

```bash
npm install @missing-elements/h5p-offline-player @missing-elements/h5p-runtime
Expand All @@ -38,59 +69,17 @@ player.addEventListener('xapi', (e) => console.log(e.detail.statement))
```

The bundler emits the Service Worker and the runtime (the H5P core, a package of its own because
it is GPL-3.0) as assets, so there is nothing to copy. The page must be on `https://` or
`localhost`, and a package from another origin needs CORS headers on its host. For exports
without their libraries, as h5p.com makes them, add `libraries="pack"`: the
[library pack](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/libraries.md), from jsDelivr or a copy on your
site.
[Setting up the player](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/setup.md) covers a site without
a bundler and the Embed setup; [the element's API](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/api.md)
lists every attribute, property and event.

On Lovable, one prompt is enough:
`Embed an H5P file using https://github.com/missing-elements/h5p-offline-player`, with the `.h5p`
file uploaded in the chat. [This example](https://embed-h5p-file.lovable.app/) was built that way.

## Which setup

An H5P package is JavaScript, and the player runs it on the origin that serves the frame.

| Your site | Setup |
|---|---|
| Needs the element's API (results to your backend, `resume="host"`, cmi5, a file picker) and plays packages you made | **Install** |
| Plays packages you did not make | **Install** with `untrusted` on them, or **Embed** |
| Has signed-in users | **Embed**, from a separate domain you run, or **Install** with every package `untrusted` |
| Cannot host a file (page builder, hosted CMS, LMS page) | **Embed** |

**Install** is the quick start above; `untrusted` on the element plays a package as data, none of
its own code running. **Embed** frames the player page that
[`@missing-elements/h5p-embed`](https://github.com/missing-elements/h5p-offline-player/tree/main/packages/embed#readme)
writes, from a domain that holds nothing else, so a package never sees your site's cookies,
storage or page.

## Features

- **Plays the packages people have.** Large ones stream, and exports without their libraries, as
h5p.com and h5p.org make them, play against a [library pack](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/libraries.md) of every
content type the H5P hub serves.
- **Plays packages you did not make** with [`untrusted`](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/untrusted.md): none of their
code runs, and their content is checked by a port of H5P's own validator.
- **Reports results you can audit:** xAPI statements that name the
[build of the package](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/revision.md) they came from, [cmi5](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/cmi5.md), [resume](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/resume.md), and a
[SCORM 1.2 package](https://github.com/missing-elements/h5p-offline-player/tree/main/packages/embed#a-scorm-package-for-an-lms) for Moodle, ILIAS, OpenOLAT,
Chamilo, SCORM Cloud or Frappe LMS.
- **No tracking:** no account, cookies or telemetry, and nothing it stores leaves the learner's
browser ([privacy](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/privacy.md)).
- **Works offline, in the content's language:** an installable app, and H5P's own buttons in any
of more than fifty languages.
- **Comes with tools:** [`h5p-verify`](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/verify.md) checks that a package plays,
[`h5p-normalize`](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/streaming-video.md) makes its video stream, and
[`h5p-author`](https://github.com/missing-elements/h5p-offline-player/tree/main/packages/author#readme) builds one from a short JSON source.

It is a player, not an editor, and it stores no results: its events go wherever your page sends
them. Coming from [h5p-standalone](https://github.com/tunapanda/h5p-standalone)? This player
reads the `.h5p` itself, with nothing to unzip, and
[takes its options by the same names](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/h5p-standalone.md).
it is GPL-3.0) as files, so there is nothing to copy. The page must be on `https://` or
`localhost`, and a package from another origin needs CORS headers on its host. A package that
carries its libraries plays as it is; one exported without them, as h5p.com exports, needs
`libraries="pack"`.

Packages you did not make, or a site with signed-in users: play every package `untrusted`, or
embed the player from a domain of its own, as
[Which setup](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/setup.md#which-setup) explains.
[Setting up the player](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/setup.md) also covers a page without a bundler, Rollup and
esbuild, and the one-prompt install on Lovable;
[the element's API](https://github.com/missing-elements/h5p-offline-player/blob/main/docs/api.md) lists every attribute, property and event.

## Documentation

Expand Down
4 changes: 3 additions & 1 deletion design/layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,9 @@ docs/ the README's longer sections, one file each: the eleme
install path in full, the same day, and since 2026-11-04 what Install and
Embed each give, when the README became a quick start, the Which setup
table, a feature list and links: the owner's choice of a short README
that routes to these files), streaming video, libraries, the
that routes to these files; since 2026-11-12 the Which setup table
too, when the README put its features first and kept the table's
answer in one line), streaming video, libraries, the
frame's CSP, packages you did not make (`untrusted` and the content check), the
revision on statements, resume, the verifier, cmi5, development,
troubleshooting, single-worker hosts and upgrades, compatibility (what a
Expand Down
4 changes: 2 additions & 2 deletions docs/privacy.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ The learner's name and email, if you set the `user` property, are not stored. Th
statement and nowhere else.

Any package played on the same origin can read all three. With `resume` on, or with packages you
did not make, that is a reason to use the Embed setup on a domain of its own; see *Which setup* in
the [README](https://github.com/missing-elements/h5p-offline-player#which-setup). A package played
did not make, that is a reason to use the Embed setup on a domain of its own; see
[Which setup](setup.md#which-setup). A package played
`untrusted` runs none of its own code, which is what could read them.

## What is sent, and to whom
Expand Down
24 changes: 18 additions & 6 deletions docs/setup.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
# Setting up the player

The README's [Which setup](../README.md#which-setup) chooses between installing the element on
your site and embedding the player page from a separate domain. Installed, you get the whole API,
and a package played as it is runs as your site. Embedded, a package never sees your site's
## Which setup

An H5P package is JavaScript, and the player runs it on the origin that serves the frame.

| Your site | Setup |
|---|---|
| Needs the element's API (results to your backend, `resume="host"`, cmi5, a file picker) and plays packages you made | **Install** |
| Plays packages you did not make | **Install** with `untrusted` on them, or **Embed** |
| Has signed-in users | **Embed**, from a separate domain you run, or **Install** with every package `untrusted` |
| Cannot host a file (page builder, hosted CMS, LMS page) | **Embed** |

Installed, you get the whole API, and a package played as it is runs as your site. Embedded, the
player page that [`@missing-elements/h5p-embed`](https://github.com/missing-elements/h5p-offline-player/tree/main/packages/embed#readme)
writes is framed from a domain that holds nothing else, so a package never sees your site's
cookies, storage or page, and you get its results as relayed xAPI. `untrusted` plays a package as
data, none of its own code running and its content checked, as an H5P plugin checks an author's
upload: [Packages you did not make](untrusted.md).
Expand All @@ -24,7 +35,7 @@ caching and upgrades.

## Install with a bundler

Vite, webpack 5, Rollup:
Vite or webpack 5:

```bash
npm install @missing-elements/h5p-offline-player @missing-elements/h5p-runtime
Expand All @@ -40,8 +51,9 @@ document.querySelector('h5p-player').runtime = runtime
The bundler emits the Service Worker, the runtime and its fonts as assets; nothing to copy. The
runtime is a separate package because it is GPL-3.0 (see the README's
[Licence](../README.md#licence)). With a bundler that does not emit `new URL(…, import.meta.url)`
assets, esbuild among them, serve both packages' `dist/` and set `sw` and `assets-base`, as under
*Install without a build step*.
assets, esbuild and Rollup among them, serve both packages' `dist/` and set `sw` and
`assets-base`, as under *Install without a build step*. Rollup with
`@web/rollup-plugin-import-meta-assets` stops at the element's default `frame-assets/` URL.

For [`libraries="pack"`](libraries.md) served from your own site rather than jsDelivr, install
`@missing-elements/h5p-libraries` too and hand over its `pack` export, which the bundler emits as
Expand Down
Loading