h5p-offline-player by Missing Elements
Star

Embedding the player page

The player page served from a domain of its own and framed by your site. The player runs on its own origin, so a hostile archive reaches the player’s data and not the embedding site’s, and storage is partitioned per embedding site. The embedding page hears what it asks for: the frame’s height always, and the xAPI statements when the address names the page’s own origin. The address also takes the element’s display options: this example asks for H5P’s action bar with the download and rights-of-use buttons. In production, npx @missing-elements/h5p-embed writes this page as a static site for a domain your organisation runs.

Markup

HTML
<!-- This site's copy of the page. Your own player domain, from npx @missing-elements/h5p-embed,
     serves it at its root: https://h5p-player.example.net/?src=… -->
<iframe src="https://h5p-offline-player.vercel.app/embed?src=https://alekswebnet.github.io/h5p/gh-normalized.h5p&xapi=https://your-site.example"
        allow="fullscreen; microphone; camera" style="width: 100%; border: 0"></iframe>

<!-- Optional, as the element's attributes: &libraries=pack  &preload=auto
     &frame&copyright&export&icon  &fullscreen=off  &reporting
     &activity-id=<the IRI statements should name>  &custom-css=<your stylesheet's URL>
     &lang=<your page's language, for H5P's own buttons and dialogs> -->

<!-- Sizing: the frame reports its height in H5P's resizer protocol, and this one line
     applies it. Served from this site, so your page sends nothing to a third party;
     h5p.org's own h5p-resizer.js speaks the same protocol, if a page has it already. -->
<script src="https://h5p-offline-player.vercel.app/resizer.js"></script>

<!-- Or answer it yourself, and hear the statements: -->
<script>
  const frame = document.querySelector('iframe')
  addEventListener('message', ({ source, origin, data }) => {
    if (source !== frame.contentWindow || origin !== 'https://h5p-offline-player.vercel.app') return
    if (data.context === 'h5p' && data.action === 'hello') source.postMessage({ context: 'h5p', action: 'hello' }, origin)
    if (data.context === 'h5p' && data.action === 'resize') frame.style.height = data.scrollHeight + 'px'
    if (data.context === 'h5p-offline-player' && data.action === 'xapi') console.log(data.verb, data.statement)
  })
</script>

What this page received



      

The embedding page must be served over https (or be on localhost). The frame then registers its own Service Worker, in Safari and on iOS too. Where a browser still refuses, the embedded page shows a link that opens the player on its own.