Skip to content

Iframe embed

The lowest-touch integration: point an iframe at the hosted player page on player-dev.oddin-video.gg and, optionally, control it over an origin-verified postMessage bridge. Playback runs isolated in its own browsing context, with no build step and no dependency on your page's framework.

Before anything plays

The parent page's origin must be on your key's AllowedIframeParents allow-list, and on the DRM CORS allow-list. See Getting started.

Plain iframe, no JavaScript

html
<iframe
  src="https://player-dev.oddin-video.gg/embed/?baseUrl=https%3A%2F%2Ffeed-dev.oddin-video.gg&matchUrn=od%3Amatch%3A1234&apiKey=pk_test_your_key"
  allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
  allowfullscreen
  referrerpolicy="no-referrer"
  style="border: 0; width: 100%; aspect-ratio: 16 / 9"
></iframe>

The page boots a fully managed player (branded controls, go-live pickup, DRM, autoplay muted) and needs three query parameters. URL-encode the values:

ParameterExampleNotes
baseUrlhttps://feed-dev.oddin-video.ggRequired. Havik API environment.
matchUrnod:match:1234Required. The match to play.
apiKeypk_test_…Required. See the api-key and the URL.
parentOriginhttps://your-site.exampleEnables the postMessage bridge. Without it the bridge stays disabled (fails closed).
mutedfalseStarts muted unless explicitly false.

Without parentOrigin the player still runs, but the parent page cannot control it, which is fine for a plain drop-in. allow="autoplay; encrypted-media …" is required: without encrypted-media DRM cannot start inside a cross-origin iframe.

SDK embed with mountEmbed recommended

mountEmbed builds the iframe for you: the correct allow list, sandbox, referrer policy and URL encoding. It also gives you a typed handle over the postMessage bridge:

ts
import { mountEmbed } from '@oddin-gg/havik-player';

const embed = mountEmbed({
  container: document.querySelector('#player')!,
  src: 'https://player-dev.oddin-video.gg/embed/',
  baseUrl: 'https://feed-dev.oddin-video.gg',
  matchUrn: 'od:match:1234',
  apiKey: 'pk_test_your_key', // dev/demo only — see below
  onEvent: (msg) => {
    if (msg.type === 'oddin:state') console.log('state:', msg.state);
    if (msg.type === 'oddin:error') console.error(msg.code, msg.message);
  },
});

embed.play();
embed.setMuted(false);
embed.load('od:match:5678'); // switch match in place
// embed.destroy();

The handle mirrors the player API: play, pause, setMuted, setVolume, setQuality, setMaxBitrate, setAudioTrack, setTextTrack, seekToLive, enterPip/exitPip, retry, load and destroy. on(cb) subscribes to every frame event. Track lists arrive as an oddin:tracks event; use them to build quality/caption menus. Full option and message tables: API reference → Mode C.

Both ends verify event.origin on every message, and the embed fails closed: with no trusted parentOrigin it neither accepts commands nor posts events. The api-key is never accepted over postMessage.

The api-key and the URL

The hosted page reads its config from the query string, which places the api-key in the iframe URL. The key is publishable by design: the origin allow-list is what protects it, and the page sets referrerpolicy="no-referrer" so the URL never leaks through Referer. For development and demos that is enough.

For production, decide by your threat model:

  • Hosted page + query key. Acceptable when you're comfortable that the key is origin-locked anyway. The URL, and so the key, is visible in the page source and in devtools, as is a key shipped in your own bundle.

  • Self-hosted embed page + injected key strictest Serve the page from your own origin and inject the config server-side. The query string is then ignored entirely, so the key never appears in any URL and a tampered ?baseUrl= can't redirect it:

    html
    <script>
      window.HAVIK_EMBED_CONFIG = {
        baseUrl: 'https://feed-dev.oddin-video.gg',
        matchUrn: 'od:match:1234',
        apiKey: 'pk_live_…', // injected by your server
        parentOrigin: 'https://your-site.example',
      };
    </script>
    <script src="https://cdn.jsdelivr.net/npm/@oddin-gg/havik-player@1.3.0/dist/havik-player.global.js"></script>
    <script>
      HavikPlayer.bootEmbed();
    </script>

    A ready-to-host template lives in the repository at embed/index.html. Point mountEmbed's src at your page and omit apiKey. Pin the exact SDK version (and add Subresource Integrity) so the embed page can't skew ahead of your host SDK. You can also skip that manual step. Each npm release carries a copy of the template at node_modules/@oddin-gg/havik-player/embed/index.html, and each GitHub release attaches one too. Both come pinned to that release, with the integrity hash included.

Sizing

The player fills the iframe, so give the iframe a real size. The usual responsive pattern is aspect-ratio: 16 / 9; width: 100%. For a fixed-height slot, the video letterboxes on #0e0f13 (themeable when self-hosting).

Limits of the hosted page

The hosted page runs the player with Oddin's default skin and copy. If you need your own theme, overlays, or analytics hooks inside the frame, you have two options. Self-host the embed page, which is one HTML file, or move to the managed player. The managed player has the same capabilities in your own DOM.

ISC licensed. Bundles hls.js (Apache-2.0).