Skip to content

Controls & theming

The managed player (and the hosted embed, which runs it) ships a branded, fully skinnable control bar: center play overlay, seek bar, play/pause, volume, live badge + go-live, quality/audio/subtitle menus, Picture-in-Picture, fullscreen, buffering spinner, and branded status cards for the ended, error, pre-play, and waiting-for-live states. It auto-hides during playback and is fully keyboard- and screen-reader-accessible.

Every color, radius, and font is a CSS variable — skin it two interchangeable ways.

1. Pass a theme

Merged over the Oddin defaults; omitted keys keep the brand value:

ts
await createPlayer({
  video,
  baseUrl,
  matchUrn,
  credential: { apiKey },
  theme: {
    accent: '#14b8a6', // play button, seek fill, live dot, focus ring
    accentText: '#04201c', // text/icon sitting on the accent
    surface: '#0f172a', // control-bar + menu background
    logoUrl: 'https://cdn.example.com/logo.svg', // optional corner watermark
  },
});

2. Or override the CSS variables

Handy for many players at once, dark/light switching, or an existing design system:

css
.havik-player {
  --havik-accent: #14b8a6;
  --havik-accent-text: #04201c;
  --havik-surface: #0f172a;
  --havik-radius: 4px;
}

The tokens

theme keyCSS variableDefault (Oddin)Used for
accent--havik-accent#E1B600 (gold)Play button, seek fill, live dot, focus
accentText--havik-accent-text#1B1C23 (navy)Text/icon on the accent
background--havik-bg#0e0f13Letterbox / behind-video background
surface--havik-surface#1B1C23Control-bar + menu surface
surfaceMuted--havik-surface-muted#2a2c36Hover / active surface
text--havik-text#ffffffPrimary text / icons
textMuted--havik-text-muted#9aa0adSecondary text (timestamps, inactive)
border--havik-borderrgba(255, 255, 255, 0.1)Hairline borders
radius--havik-radius8pxCorner radius for buttons / menus
fontFamily--havik-fontsystem UI stackControl-bar font
logoUrl--havik-logo— (none)Optional corner watermark (a URL)

ODDIN_THEME and the applyTheme(root, theme?) helper are exported from the package root if you want the values directly or need to skin a container yourself.

Status cards & copy

The stopped/pre-play states render branded full-bleed cards — ended, fatal error (with Retry when retryable), pre-play (poster + play CTA), and armed-waiting ("Starting soon…") — so a finished stream never leaves a frozen frame behind a dead play button. They pick up the theme and theme.logoUrl. Customize the copy with endedMessage / errorMessage, or set statusOverlays: false and drive your own UI from the ended/error events.

Opting out

ts
controls: 'native'; // the browser's <video controls> UI
controls: 'none'; // nothing — build your own on the Player API

Keyboard shortcuts with the custom bar: space/k play-pause, m mute, f fullscreen, c captions, l go-live, arrows seek/volume.

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