Skip to content

Troubleshooting

The short list, ordered by how often it's the answer.

Nothing plays, every call fails

Your origin isn't allow-listed. Devtools → Network shows the CORS preflight failing. The key's AllowedOrigins (and AllowedIframeParents for embeds) and the DRM CORS allow-list must all include your origin — onboard it and allow a few minutes for propagation.

Error decoder

All API errors are a typed PlaybackError with { code, httpStatus, retryAfterMs?, requestId? }:

CodeHTTPMeaningRetry?
INVALID_URN400malformed URNno
UNAUTHORIZED / FORBIDDEN401 / 403bad key / origin not allowed / DRM rejectedno
NOT_FOUND404unknown URN or not entitled (indistinguishable)no
GONE410ended past the catchup windowno
TOO_EARLY425upcoming, not live yetyes (waitForLive)
RATE_LIMITED429per-IP limiterback off
UNAVAILABLE503should-be-live, origin warming upyes

The ones that surprise people:

  • NOT_FOUND for a match you can see — the key isn't entitled to that tournament. 404 is deliberately indistinguishable from "unknown URN".
  • UNAUTHORIZED with a valid key — wrong environment: a pk_test_… key against production baseUrl or vice versa.
  • FORBIDDEN on the license request only — the license URL was modified, or a different api-key signed the license call than the playback call. POST the URL verbatim, same key everywhere.

Autoplay doesn't start

Browser sound policy. Set muted: true for autoplay, or start playback from a user gesture. The managed player fires autoplayblocked (the embed forwards it as oddin:autoplayblocked) — show tap-to-play and call play() from the tap.

Player sits in waiting forever

The match hasn't gone live; the SDK is armed (SSE or polling). That's working as designed — bound it with waitForLive.timeoutMs if your UI needs to give up, and show countdown UX from the waiting event's retryInMs.

Black video

  • With DRM, any browser — almost always CORS on the license POST: see DRM → CORS. Console will show an EME/CDM error.
  • Chrome/Firefox/Edge on iOS — no Widevine CDM exists there. Call detectDrmSupport() and route viewers to Safari on no-cdm.
  • Old Safari — the native fallback plays only OS-trusted passthrough; check the console.

Iframe embed loads but won't play

  • The parent page's origin is missing from AllowedIframeParents.
  • The iframe lacks allow="autoplay; encrypted-media" — without encrypted-media EME can't start in a cross-origin frame (mountEmbed sets this for you).

Latency is higher than expected

The default target is ~2 s behind the live edge (~4–5 s glass-to-glass). If you're seeing tens of seconds: check for a liveLatencyTarget/hlsConfig override, and confirm with player.on('stats', s => s.latencySeconds). After a stall the player catches up at 1.5× automatically — sustained high latency usually means sustained insufficient bandwidth (watch bandwidthKbps vs the current rendition).

Still stuck

Grab PlaybackError.requestId (present on API-originated errors), the match URN, and a timestamp, and write to havik-support@oddin.gg. Current platform health: status-dev.oddin-video.gg.

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