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 — no build step, 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 you get a player, not a remote control — fine for a pure drop-in. allow="autoplay; encrypted-media …" is required: without encrypted-media DRM cannot start inside a cross-origin iframe.

SDK embed — mountEmbed recommended

mountEmbed builds the iframe for you — correct allow list, sandbox, referrer policy, URL encoding — and 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, destroy — and 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 this is exactly right.

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 thus the key) is visible in the page source and browser devtools — which is equally true of a key shipped in your bundle.

  • Self-hosted embed page + injected key strictest — serve the page from your own origin and inject 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.

Sizing

The player fills the iframe. Give the iframe a real size — the classic 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, either self-host the embed page (it's one HTML file) or step up to the managed player — same capabilities, your DOM.

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