@oddin-gg/havik-player 2.13.1 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/havik-player.global.js +2 -2
- package/dist/havik-player.global.js.map +1 -1
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +54 -52
- package/dist/index.js.map +1 -1
- package/docs/API.md +22 -0
- package/embed/index.html +1 -1
- package/examples/vanilla.html +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -224,6 +224,14 @@ export declare interface CreatePlayerOptions {
|
|
|
224
224
|
* 'custom'.
|
|
225
225
|
*/
|
|
226
226
|
statusOverlays?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Your page's Content-Security-Policy nonce, for the `<style>` tag the
|
|
229
|
+
* custom control bar injects. Needed only when `style-src` has no
|
|
230
|
+
* `'unsafe-inline'`: without the nonce the browser blocks the tag and the
|
|
231
|
+
* controls render unstyled. Pass the same value as your policy's
|
|
232
|
+
* `'nonce-…'` source. Ignored unless controls is 'custom'.
|
|
233
|
+
*/
|
|
234
|
+
cspNonce?: string;
|
|
227
235
|
/** Message on the end-of-stream card. Default: "This stream has ended". */
|
|
228
236
|
endedMessage?: string;
|
|
229
237
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -224,6 +224,14 @@ export declare interface CreatePlayerOptions {
|
|
|
224
224
|
* 'custom'.
|
|
225
225
|
*/
|
|
226
226
|
statusOverlays?: boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Your page's Content-Security-Policy nonce, for the `<style>` tag the
|
|
229
|
+
* custom control bar injects. Needed only when `style-src` has no
|
|
230
|
+
* `'unsafe-inline'`: without the nonce the browser blocks the tag and the
|
|
231
|
+
* controls render unstyled. Pass the same value as your policy's
|
|
232
|
+
* `'nonce-…'` source. Ignored unless controls is 'custom'.
|
|
233
|
+
*/
|
|
234
|
+
cspNonce?: string;
|
|
227
235
|
/** Message on the end-of-stream card. Default: "This stream has ended". */
|
|
228
236
|
endedMessage?: string;
|
|
229
237
|
/**
|
package/dist/index.js
CHANGED
|
@@ -2260,7 +2260,7 @@ async function $t() {
|
|
|
2260
2260
|
}
|
|
2261
2261
|
//#endregion
|
|
2262
2262
|
//#region src/core/version.ts
|
|
2263
|
-
var en = "2.
|
|
2263
|
+
var en = "2.14.0", tn = {
|
|
2264
2264
|
accent: "#E1B600",
|
|
2265
2265
|
accentText: "#1B1C23",
|
|
2266
2266
|
background: "#0e0f13",
|
|
@@ -2293,15 +2293,16 @@ function rn(e, t) {
|
|
|
2293
2293
|
}
|
|
2294
2294
|
//#endregion
|
|
2295
2295
|
//#region src/ui/styles.ts
|
|
2296
|
-
var an = "havik-player-styles", on = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n/* Absolutely centred, NOT a flex sibling of .havik-center: the hidden center\n button (.havik-gone) still occupies its 74px in the row, which pushed an\n in-flow spinner 37px right of centre. */\n.havik-spinner{position:absolute;inset:0;margin:auto;width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n";
|
|
2297
|
-
function
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2296
|
+
var an = "havik-player-styles", on = "\n.havik-player{position:relative;display:block;width:100%;background:var(--havik-bg,#0e0f13);\n color:var(--havik-text,#fff);font-family:var(--havik-font,system-ui,sans-serif);\n overflow:hidden;line-height:1.4;-webkit-tap-highlight-color:transparent}\n.havik-player video{display:block;width:100%;height:auto;aspect-ratio:16/9;\n object-fit:contain;background:#000;outline:none}\n.havik-player:focus{outline:none}\n.havik-player *{box-sizing:border-box}\n\n.havik-player:fullscreen,.havik-player:-webkit-full-screen{display:flex;align-items:center;\n justify-content:center;width:100vw;height:100vh;aspect-ratio:auto}\n.havik-player:fullscreen video,.havik-player:-webkit-full-screen video{width:100%;height:100%;\n aspect-ratio:auto;object-fit:contain}\n\n.havik-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;cursor:pointer}\n.havik-gradient{position:absolute;left:0;right:0;bottom:0;height:120px;pointer-events:none;\n background:linear-gradient(to top,rgba(0,0,0,.78),transparent);\n opacity:1;transition:opacity .25s}\n.havik-player.havik-hide .havik-gradient,\n.havik-player.havik-hide .havik-bar{opacity:0;pointer-events:none}\n.havik-player.havik-hide{cursor:none}\n\n.havik-center{width:74px;height:74px;border-radius:50%;border:0;cursor:pointer;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);\n display:flex;align-items:center;justify-content:center;\n box-shadow:0 6px 24px rgba(0,0,0,.45);transition:transform .15s,opacity .15s}\n.havik-center:hover{transform:scale(1.06)}\n.havik-center svg{width:38px;height:38px;margin-left:2px}\n.havik-center.havik-gone{opacity:0;pointer-events:none;transform:scale(.8)}\n\n/* Absolutely centred, NOT a flex sibling of .havik-center: the hidden center\n button (.havik-gone) still occupies its 74px in the row, which pushed an\n in-flow spinner 37px right of centre. */\n.havik-spinner{position:absolute;inset:0;margin:auto;width:56px;height:56px;border-radius:50%;\n border:4px solid rgba(255,255,255,.2);border-top-color:var(--havik-accent,#E1B600);\n animation:havik-spin .8s linear infinite}\n@keyframes havik-spin{to{transform:rotate(360deg)}}\n\n/* Status card — branded full-bleed overlay for the stopped/pre-play states\n (ended / fatal error / pre-play / armed-waiting). */\n.havik-status{position:absolute;inset:0;display:flex;flex-direction:column;gap:16px;\n align-items:center;justify-content:center;background:rgba(14,15,19,.82);text-align:center;padding:24px}\n.havik-status__logo{width:128px;height:46px;background:var(--havik-logo) center/contain no-repeat;opacity:.95}\n.havik-status__msg{color:var(--havik-text,#fff);font-size:15px;font-weight:600;max-width:480px}\n.havik-status__btn{display:inline-flex;align-items:center;gap:8px;border:0;cursor:pointer;\n padding:10px 18px;border-radius:var(--havik-radius,8px);font-weight:700;font-size:14px;\n background:var(--havik-accent,#E1B600);color:var(--havik-accent-text,#1B1C23);transition:transform .15s}\n.havik-status__btn:hover{transform:scale(1.04)}\n.havik-status__btn svg{width:18px;height:18px}\n/* Pre-play: lighter scrim so the poster shows through, and a round play CTA. */\n.havik-status--preplay{background:rgba(14,15,19,.4)}\n.havik-status--preplay .havik-status__btn--play{width:74px;height:74px;border-radius:50%;padding:0;\n justify-content:center;box-shadow:0 6px 24px rgba(0,0,0,.45)}\n.havik-status--preplay .havik-status__btn--play svg{width:38px;height:38px;margin-left:3px}\n\n.havik-watermark{position:absolute;top:12px;right:14px;height:22px;opacity:.85;pointer-events:none;\n content:'';background:var(--havik-logo) center/contain no-repeat;width:90px}\n/* A status card carries its own logo — the corner watermark would duplicate it. */\n.havik-player.havik-has-status .havik-watermark{display:none}\n\n.havik-bar{position:absolute;left:0;right:0;bottom:0;padding:0 12px 8px;\n opacity:1;transition:opacity .25s;z-index:2}\n.havik-seek{position:relative;height:16px;display:flex;align-items:center;cursor:pointer;margin-bottom:2px}\n.havik-seek__track{position:relative;width:100%;height:4px;border-radius:3px;background:rgba(255,255,255,.25)}\n.havik-seek__buffered{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:rgba(255,255,255,.4)}\n.havik-seek__played{position:absolute;left:0;top:0;height:100%;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-seek__thumb{position:absolute;top:50%;width:13px;height:13px;border-radius:50%;\n background:var(--havik-accent,#E1B600);transform:translate(-50%,-50%) scale(0);transition:transform .1s}\n.havik-seek:hover .havik-seek__thumb{transform:translate(-50%,-50%) scale(1)}\n\n.havik-controls{display:flex;align-items:center;gap:6px;height:40px}\n.havik-spacer{flex:1}\n.havik-btn{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;border-radius:var(--havik-radius,8px);\n padding:0;transition:background .12s,color .12s}\n.havik-btn:hover{background:rgba(255,255,255,.12)}\n.havik-btn:focus-visible{outline:2px solid var(--havik-accent,#E1B600);outline-offset:1px}\n.havik-btn svg{width:22px;height:22px}\n\n/* The volume level is filled in accent up to the thumb, like the seek bar's\n played span — a native range input paints no \"elapsed\" side, which left the\n level readable only from the dot's position. --havik-vol (0..1) is set by\n syncVolume(); the track width and thumb size live here as variables because\n the WebKit fill has to compute the thumb centre from both. */\n.havik-vol{display:flex;align-items:center}\n.havik-vol__slider{--havik-vol-w:70px;--havik-vol-thumb:12px;--havik-vol:1;\n --havik-vol-pos:calc(var(--havik-vol-thumb) / 2 + var(--havik-vol) * (var(--havik-vol-w) - var(--havik-vol-thumb)));\n width:0;opacity:0;transition:width .18s,opacity .18s;height:4px;cursor:pointer;\n appearance:none;-webkit-appearance:none;background:transparent;border-radius:3px;margin:0 4px}\n.havik-vol:hover .havik-vol__slider,.havik-vol:focus-within .havik-vol__slider{width:var(--havik-vol-w);opacity:1}\n/* WebKit/Blink have no progress pseudo-element: paint the fill as a hard-stop\n gradient that ends at the thumb centre. */\n.havik-vol__slider::-webkit-slider-runnable-track{height:4px;border-radius:3px;\n background:linear-gradient(to right,var(--havik-accent,#E1B600) var(--havik-vol-pos),\n rgba(255,255,255,.3) var(--havik-vol-pos))}\n/* Firefox fills natively, and tracks the thumb exactly. */\n.havik-vol__slider::-moz-range-track{height:4px;border-radius:3px;background:rgba(255,255,255,.3)}\n.havik-vol__slider::-moz-range-progress{height:4px;border-radius:3px;background:var(--havik-accent,#E1B600)}\n.havik-vol__slider::-webkit-slider-thumb{-webkit-appearance:none;width:var(--havik-vol-thumb);\n height:var(--havik-vol-thumb);border-radius:50%;background:var(--havik-accent,#E1B600);\n margin-top:calc((4px - var(--havik-vol-thumb)) / 2)}\n.havik-vol__slider::-moz-range-thumb{width:var(--havik-vol-thumb);height:var(--havik-vol-thumb);\n border:0;border-radius:50%;background:var(--havik-accent,#E1B600)}\n\n.havik-time{font-size:12px;color:var(--havik-text-muted,#9aa0ad);\n font-variant-numeric:tabular-nums;padding:0 6px;white-space:nowrap}\n.havik-live{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:700;\n letter-spacing:.4px;padding:0 8px;color:var(--havik-text,#fff)}\n.havik-live svg{width:9px;height:9px;color:var(--havik-text-muted,#9aa0ad)}\n.havik-live.is-live svg{color:var(--havik-accent,#E1B600)}\n.havik-live__go{margin-left:2px;border:0;cursor:pointer;background:transparent;color:var(--havik-text-muted,#9aa0ad);\n font:inherit;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;padding:2px 6px;border-radius:6px}\n.havik-live.is-live .havik-live__go{display:none}\n.havik-live__go:hover{color:var(--havik-accent,#E1B600)}\n\n.havik-menu{position:absolute;bottom:54px;right:12px;min-width:170px;max-height:230px;overflow:auto;\n background:var(--havik-surface,#1B1C23);border:1px solid var(--havik-border,rgba(255,255,255,.1));\n border-radius:var(--havik-radius,8px);box-shadow:0 10px 30px rgba(0,0,0,.5);padding:6px;z-index:3}\n.havik-menu[hidden]{display:none}\n.havik-menu__title{font-size:11px;text-transform:uppercase;letter-spacing:.6px;\n color:var(--havik-text-muted,#9aa0ad);padding:6px 8px 4px}\n.havik-menu__item{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;\n border:0;background:transparent;color:var(--havik-text,#fff);cursor:pointer;font:inherit;font-size:13px;\n text-align:left;padding:7px 8px;border-radius:6px}\n.havik-menu__item:hover{background:var(--havik-surface-muted,#2a2c36)}\n.havik-menu__item[aria-checked=\"true\"]{color:var(--havik-accent,#E1B600);font-weight:600}\n.havik-menu__item[aria-checked=\"true\"]::after{content:'✓'}\n", sn = /* @__PURE__ */ new WeakMap();
|
|
2297
|
+
function cn(e = document, t) {
|
|
2298
|
+
let n = e.querySelector(`style#${an}`);
|
|
2299
|
+
if (n && (!t || sn.get(n) === t)) return;
|
|
2300
|
+
let r = e.createElement("style");
|
|
2301
|
+
r.id = an, t && (r.nonce = t, r.setAttribute("nonce", t)), r.textContent = on, sn.set(r, t), n ? n.replaceWith(r) : e.head.appendChild(r);
|
|
2301
2302
|
}
|
|
2302
2303
|
//#endregion
|
|
2303
2304
|
//#region src/ui/icons.ts
|
|
2304
|
-
var
|
|
2305
|
+
var ln = {
|
|
2305
2306
|
play: "<path d=\"M8 5v14l11-7z\"/>",
|
|
2306
2307
|
pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
|
|
2307
2308
|
volumeHigh: "<path d=\"M3 10v4h4l5 5V5L7 10H3zm13.5 2a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4zm-2.5-9v2.06A7 7 0 0 1 14 19v2a9 9 0 0 0 0-18z\"/>",
|
|
@@ -2315,20 +2316,20 @@ var cn = {
|
|
|
2315
2316
|
retry: "<path d=\"M17.65 6.35A8 8 0 1 0 19.73 13h-2.08A6 6 0 1 1 12 6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z\"/>"
|
|
2316
2317
|
};
|
|
2317
2318
|
function Z(e) {
|
|
2318
|
-
return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${
|
|
2319
|
+
return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${ln[e]}</svg>`;
|
|
2319
2320
|
}
|
|
2320
2321
|
//#endregion
|
|
2321
2322
|
//#region src/ui/controls.ts
|
|
2322
|
-
var
|
|
2323
|
+
var un = 3e3, dn = /* @__PURE__ */ new Set([
|
|
2323
2324
|
"GONE",
|
|
2324
2325
|
"NOT_FOUND",
|
|
2325
2326
|
"INVALID_URN",
|
|
2326
2327
|
"UNAUTHORIZED"
|
|
2327
2328
|
]);
|
|
2328
|
-
function
|
|
2329
|
-
return new
|
|
2329
|
+
function fn(e) {
|
|
2330
|
+
return new pn(e);
|
|
2330
2331
|
}
|
|
2331
|
-
var
|
|
2332
|
+
var pn = class {
|
|
2332
2333
|
root;
|
|
2333
2334
|
video;
|
|
2334
2335
|
player;
|
|
@@ -2370,7 +2371,7 @@ var fn = class {
|
|
|
2370
2371
|
scrubbing = !1;
|
|
2371
2372
|
offFns = [];
|
|
2372
2373
|
constructor(e) {
|
|
2373
|
-
this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ??
|
|
2374
|
+
this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? un, this.logoUrl = e.logoUrl, this.statusOverlays = e.statusOverlays !== !1, this.endedMessage = e.endedMessage ?? "This stream has ended", this.interruptedMessage = e.interruptedMessage ?? "Reconnecting — please stand by", this.errorMessage = e.errorMessage, cn(this.doc, e.cspNonce), this.root.classList.add("havik-player"), this.root.hasAttribute("tabindex") || (this.root.tabIndex = 0), this.root.setAttribute("role", "region"), this.root.setAttribute("aria-label", "Video player");
|
|
2374
2375
|
let t = (e, t, n) => {
|
|
2375
2376
|
let r = this.doc.createElement(e);
|
|
2376
2377
|
if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
|
|
@@ -2461,7 +2462,7 @@ var fn = class {
|
|
|
2461
2462
|
e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = Z("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
|
|
2462
2463
|
} else {
|
|
2463
2464
|
let r = this.doc.createElement("div");
|
|
2464
|
-
if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !
|
|
2465
|
+
if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !dn.has(this.lastError.code)) {
|
|
2465
2466
|
let e = this.doc.createElement("button");
|
|
2466
2467
|
e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${Z("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
|
|
2467
2468
|
}
|
|
@@ -2502,7 +2503,7 @@ var fn = class {
|
|
|
2502
2503
|
let n = r.end(r.length - 1), i = Math.min(1, Math.max(0, (n - e.start) / t));
|
|
2503
2504
|
this.seekBuffered.style.width = `${(i * 100).toFixed(2)}%`;
|
|
2504
2505
|
}
|
|
2505
|
-
!this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${
|
|
2506
|
+
!this.player.getStats().isLive && Number.isFinite(this.video.duration) && (this.time.textContent = `${mn(this.video.currentTime)} / ${mn(this.video.duration)}`);
|
|
2506
2507
|
}
|
|
2507
2508
|
syncLive() {
|
|
2508
2509
|
let e = this.player.getStats(), t = e.isLive !== !1;
|
|
@@ -2552,17 +2553,17 @@ var fn = class {
|
|
|
2552
2553
|
renderQualityMenu() {
|
|
2553
2554
|
let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
|
|
2554
2555
|
n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
|
|
2555
|
-
for (let n of e) this.menu.append(this.menuItem(
|
|
2556
|
+
for (let n of e) this.menu.append(this.menuItem(hn(n), t === n.index, () => this.player.setQuality(n.index)));
|
|
2556
2557
|
}
|
|
2557
2558
|
renderAudioMenu() {
|
|
2558
2559
|
let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
|
|
2559
2560
|
t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
|
|
2560
|
-
for (let t of e) this.menu.append(this.menuItem(
|
|
2561
|
+
for (let t of e) this.menu.append(this.menuItem(gn(t), t.default, () => this.player.setAudioTrack(t.id)));
|
|
2561
2562
|
}
|
|
2562
2563
|
renderCcMenu() {
|
|
2563
2564
|
let e = this.player.getTextTracks(), t = this.doc.createElement("div");
|
|
2564
2565
|
t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
|
|
2565
|
-
for (let t of e) this.menu.append(this.menuItem(
|
|
2566
|
+
for (let t of e) this.menu.append(this.menuItem(_n(t), !1, () => this.player.setTextTrack(t.id)));
|
|
2566
2567
|
}
|
|
2567
2568
|
onTracksChanged() {
|
|
2568
2569
|
this.openMenu && this.renderMenu(this.openMenu);
|
|
@@ -2662,41 +2663,41 @@ var fn = class {
|
|
|
2662
2663
|
this.root.classList.remove("havik-player", "havik-hide", "havik-has-status");
|
|
2663
2664
|
}
|
|
2664
2665
|
};
|
|
2665
|
-
function
|
|
2666
|
+
function mn(e) {
|
|
2666
2667
|
(!Number.isFinite(e) || e < 0) && (e = 0);
|
|
2667
2668
|
let t = Math.floor(e % 60), n = Math.floor(e / 60 % 60), r = Math.floor(e / 3600), i = r > 0 ? String(n).padStart(2, "0") : String(n), a = String(t).padStart(2, "0");
|
|
2668
2669
|
return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
|
|
2669
2670
|
}
|
|
2670
|
-
function
|
|
2671
|
+
function hn(e) {
|
|
2671
2672
|
return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
|
|
2672
2673
|
}
|
|
2673
|
-
function
|
|
2674
|
+
function gn(e) {
|
|
2674
2675
|
return e.name || e.lang || `Track ${e.id}`;
|
|
2675
2676
|
}
|
|
2676
|
-
function
|
|
2677
|
+
function _n(e) {
|
|
2677
2678
|
return e.name || e.lang || `Track ${e.id}`;
|
|
2678
2679
|
}
|
|
2679
2680
|
//#endregion
|
|
2680
2681
|
//#region src/managed/index.ts
|
|
2681
|
-
var
|
|
2682
|
-
function
|
|
2682
|
+
var vn = 6e3, yn = 45e3, Q = 2e3, bn = 3, xn = 2e3, Sn = 48e4;
|
|
2683
|
+
function Cn(e) {
|
|
2683
2684
|
if (e.env !== void 0 && e.baseUrl !== void 0) throw new n("INTERNAL", 0, `createPlayer: pass env OR baseUrl, not both (got env=${JSON.stringify(e.env)} and baseUrl=${JSON.stringify(e.baseUrl)})`);
|
|
2684
2685
|
if (e.env !== void 0) return B(e.env);
|
|
2685
2686
|
if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: env or baseUrl is required (env: 'integration' | 'production')");
|
|
2686
2687
|
return e.baseUrl;
|
|
2687
2688
|
}
|
|
2688
|
-
async function
|
|
2689
|
+
async function wn(e) {
|
|
2689
2690
|
if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
|
|
2690
|
-
let t =
|
|
2691
|
+
let t = Cn(e);
|
|
2691
2692
|
if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
|
|
2692
2693
|
if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
|
|
2693
|
-
let r = new
|
|
2694
|
+
let r = new Tn({
|
|
2694
2695
|
...e,
|
|
2695
2696
|
baseUrl: t
|
|
2696
2697
|
});
|
|
2697
2698
|
return await r.start(), r;
|
|
2698
2699
|
}
|
|
2699
|
-
var
|
|
2700
|
+
var Tn = class {
|
|
2700
2701
|
opts;
|
|
2701
2702
|
abort = new AbortController();
|
|
2702
2703
|
listeners = {};
|
|
@@ -2784,7 +2785,7 @@ var wn = class {
|
|
|
2784
2785
|
setTimeout(() => {
|
|
2785
2786
|
a || this.destroyed || e !== this.loadGen || this.emit("waiting", {
|
|
2786
2787
|
phase: i ? "unavailable" : "tooEarly",
|
|
2787
|
-
retryInMs:
|
|
2788
|
+
retryInMs: yn,
|
|
2788
2789
|
attempt: 1,
|
|
2789
2790
|
elapsedMs: 0,
|
|
2790
2791
|
...!i && f !== void 0 && f > Date.now() ? { ttkMs: f - Date.now() } : {}
|
|
@@ -2823,13 +2824,13 @@ var wn = class {
|
|
|
2823
2824
|
!a && !p() && h();
|
|
2824
2825
|
}, e);
|
|
2825
2826
|
};
|
|
2826
|
-
_(
|
|
2827
|
+
_(vn), this.armingSub = T({
|
|
2827
2828
|
eventsBaseUrl: t,
|
|
2828
2829
|
matchUrn: this.opts.matchUrn,
|
|
2829
2830
|
credential: this.opts.credential,
|
|
2830
|
-
onAlive: () => _(
|
|
2831
|
+
onAlive: () => _(yn),
|
|
2831
2832
|
onState: (e) => {
|
|
2832
|
-
a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() *
|
|
2833
|
+
a || p() || (e.state === "live" || e.state === "gone") && setTimeout(g, Math.random() * Q);
|
|
2833
2834
|
},
|
|
2834
2835
|
onError: () => h()
|
|
2835
2836
|
});
|
|
@@ -2988,7 +2989,7 @@ var wn = class {
|
|
|
2988
2989
|
r = 0, i = !1;
|
|
2989
2990
|
},
|
|
2990
2991
|
onError: () => {
|
|
2991
|
-
this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >=
|
|
2992
|
+
this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= bn && (i = !0, this.emit("warning", new n("NETWORK", 0, `live-state events unreachable at ${t} after ${r} attempts — end-of-stream detection degraded to the HLS staleness watchdog. Check the events host, the api-key's allowed origins, and CSP connect-src.`))));
|
|
2992
2993
|
}
|
|
2993
2994
|
});
|
|
2994
2995
|
}
|
|
@@ -3010,7 +3011,7 @@ var wn = class {
|
|
|
3010
3011
|
scheduleRejoin(e) {
|
|
3011
3012
|
this.rejoinEnabled() && !this.rejoinTimer && (this.rejoinTimer = setTimeout(() => {
|
|
3012
3013
|
this.rejoinTimer = null, !(this.destroyed || e !== this.loadGen || this._state !== "ended") && this.rejoin();
|
|
3013
|
-
}, Math.random() *
|
|
3014
|
+
}, Math.random() * Q));
|
|
3014
3015
|
}
|
|
3015
3016
|
cancelRejoin() {
|
|
3016
3017
|
this.rejoinTimer &&= (clearTimeout(this.rejoinTimer), null);
|
|
@@ -3034,7 +3035,7 @@ var wn = class {
|
|
|
3034
3035
|
}
|
|
3035
3036
|
t.controls = !1;
|
|
3036
3037
|
let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
|
|
3037
|
-
r && r.insertBefore(n, t), n.appendChild(t), rn(n, this.opts.theme), this.container = n, this.controlBar =
|
|
3038
|
+
r && r.insertBefore(n, t), n.appendChild(t), rn(n, this.opts.theme), this.container = n, this.controlBar = fn({
|
|
3038
3039
|
root: n,
|
|
3039
3040
|
video: t,
|
|
3040
3041
|
player: this,
|
|
@@ -3042,7 +3043,8 @@ var wn = class {
|
|
|
3042
3043
|
statusOverlays: this.opts.statusOverlays,
|
|
3043
3044
|
endedMessage: this.opts.endedMessage,
|
|
3044
3045
|
interruptedMessage: this.opts.interruptedMessage,
|
|
3045
|
-
errorMessage: this.opts.errorMessage
|
|
3046
|
+
errorMessage: this.opts.errorMessage,
|
|
3047
|
+
cspNonce: this.opts.cspNonce
|
|
3046
3048
|
});
|
|
3047
3049
|
}
|
|
3048
3050
|
unmountControls() {
|
|
@@ -3064,7 +3066,7 @@ var wn = class {
|
|
|
3064
3066
|
}
|
|
3065
3067
|
startStats() {
|
|
3066
3068
|
this.stopStats();
|
|
3067
|
-
let e = Math.max(500, this.opts.statsIntervalMs ??
|
|
3069
|
+
let e = Math.max(500, this.opts.statsIntervalMs ?? xn);
|
|
3068
3070
|
this.statsTimer = setInterval(() => {
|
|
3069
3071
|
if (!this.engine) return;
|
|
3070
3072
|
let e = this.engine.getStats();
|
|
@@ -3076,7 +3078,7 @@ var wn = class {
|
|
|
3076
3078
|
}
|
|
3077
3079
|
scheduleLicenseRefresh() {
|
|
3078
3080
|
this.stopRefresh();
|
|
3079
|
-
let e = this.opts.licenseRefreshMs ??
|
|
3081
|
+
let e = this.opts.licenseRefreshMs ?? Sn;
|
|
3080
3082
|
e && this._descriptor?.drmEnabled && (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
|
|
3081
3083
|
}
|
|
3082
3084
|
stopRefresh() {
|
|
@@ -3231,7 +3233,7 @@ function $(e) {
|
|
|
3231
3233
|
}
|
|
3232
3234
|
//#endregion
|
|
3233
3235
|
//#region src/embed/host.ts
|
|
3234
|
-
function
|
|
3236
|
+
function En(e) {
|
|
3235
3237
|
if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
|
|
3236
3238
|
if (!e.src) throw Error("mountEmbed: opts.src is required");
|
|
3237
3239
|
if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
|
|
@@ -3290,15 +3292,15 @@ function Tn(e) {
|
|
|
3290
3292
|
}
|
|
3291
3293
|
//#endregion
|
|
3292
3294
|
//#region src/embed/page.ts
|
|
3293
|
-
var
|
|
3294
|
-
function
|
|
3295
|
+
var Dn = /^[A-Za-z0-9:_.-]{1,128}$/;
|
|
3296
|
+
function On(e) {
|
|
3295
3297
|
if (typeof e == "boolean") return e;
|
|
3296
3298
|
if (typeof e == "object" && e) {
|
|
3297
3299
|
let t = e.endpoint;
|
|
3298
3300
|
return typeof t == "string" ? { endpoint: t } : {};
|
|
3299
3301
|
}
|
|
3300
3302
|
}
|
|
3301
|
-
function
|
|
3303
|
+
function kn() {
|
|
3302
3304
|
let e = window.HAVIK_EMBED_CONFIG;
|
|
3303
3305
|
if (e) return {
|
|
3304
3306
|
baseUrl: e.baseUrl ?? "",
|
|
@@ -3308,7 +3310,7 @@ function On() {
|
|
|
3308
3310
|
autoplay: e.autoplay ?? !0,
|
|
3309
3311
|
muted: e.muted ?? !0,
|
|
3310
3312
|
waitForLive: e.waitForLive ?? !0,
|
|
3311
|
-
analytics:
|
|
3313
|
+
analytics: On(e.analytics)
|
|
3312
3314
|
};
|
|
3313
3315
|
let t = new URLSearchParams(location.search);
|
|
3314
3316
|
return {
|
|
@@ -3321,9 +3323,9 @@ function On() {
|
|
|
3321
3323
|
waitForLive: !0
|
|
3322
3324
|
};
|
|
3323
3325
|
}
|
|
3324
|
-
async function
|
|
3325
|
-
let e =
|
|
3326
|
-
|
|
3326
|
+
async function An() {
|
|
3327
|
+
let e = kn();
|
|
3328
|
+
Mn();
|
|
3327
3329
|
let t = document.createElement("video");
|
|
3328
3330
|
t.setAttribute("playsinline", ""), t.muted = e.muted ?? !0, t.style.cssText = "width:100%;height:100%;object-fit:contain;background:#000;aspect-ratio:auto", document.body.appendChild(t);
|
|
3329
3331
|
let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
|
|
@@ -3378,12 +3380,12 @@ async function kn() {
|
|
|
3378
3380
|
code: e.code,
|
|
3379
3381
|
message: e.message
|
|
3380
3382
|
})), e.on("autoplayblocked", () => {
|
|
3381
|
-
i({ type: "oddin:autoplayblocked" }),
|
|
3383
|
+
i({ type: "oddin:autoplayblocked" }), jn(t, e);
|
|
3382
3384
|
});
|
|
3383
3385
|
}, s = async (r) => {
|
|
3384
3386
|
a?.destroy(), a = null;
|
|
3385
3387
|
try {
|
|
3386
|
-
let n = await
|
|
3388
|
+
let n = await wn({
|
|
3387
3389
|
video: t,
|
|
3388
3390
|
baseUrl: e.baseUrl,
|
|
3389
3391
|
matchUrn: r,
|
|
@@ -3445,7 +3447,7 @@ async function kn() {
|
|
|
3445
3447
|
a?.retry();
|
|
3446
3448
|
break;
|
|
3447
3449
|
case "oddin:load":
|
|
3448
|
-
typeof e.matchUrn == "string" &&
|
|
3450
|
+
typeof e.matchUrn == "string" && Dn.test(e.matchUrn) && s(e.matchUrn);
|
|
3449
3451
|
break;
|
|
3450
3452
|
case "oddin:destroy": c();
|
|
3451
3453
|
}
|
|
@@ -3457,18 +3459,18 @@ async function kn() {
|
|
|
3457
3459
|
protocol: 1
|
|
3458
3460
|
});
|
|
3459
3461
|
}
|
|
3460
|
-
function
|
|
3462
|
+
function jn(e, t) {
|
|
3461
3463
|
if (document.getElementById("havik-tap")) return;
|
|
3462
3464
|
let n = document.createElement("button");
|
|
3463
3465
|
n.id = "havik-tap", n.textContent = "▶ Tap to play", n.style.cssText = "position:fixed;inset:0;width:100%;height:100%;border:0;background:rgba(0,0,0,.55);color:#fff;font:600 18px system-ui,sans-serif;cursor:pointer;z-index:9", n.addEventListener("click", () => {
|
|
3464
3466
|
n.remove(), t.play();
|
|
3465
3467
|
}), document.body.appendChild(n);
|
|
3466
3468
|
}
|
|
3467
|
-
function
|
|
3469
|
+
function Mn() {
|
|
3468
3470
|
let e = document.createElement("style");
|
|
3469
3471
|
e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
|
|
3470
3472
|
}
|
|
3471
3473
|
//#endregion
|
|
3472
|
-
export { tn as ODDIN_THEME, n as PlaybackError, rn as applyTheme, B as baseUrlForEnv,
|
|
3474
|
+
export { tn as ODDIN_THEME, n as PlaybackError, rn as applyTheme, B as baseUrlForEnv, An as bootEmbed, Te as clearDeviceId, wn as createPlayer, E as deriveEventsBaseUrl, He as detectDrmSupport, C as enterVideoFullscreen, w as exitVideoFullscreen, _e as fetchCatalog, ye as fetchTournament, xe as flattenMatches, k as getDeviceId, We as isLowLatencyManifest, $ as isOddinMessage, De as licenseRequestHeaders, En as mountEmbed, de as pollToLive, y as resolveStream, T as subscribeLiveState, S as videoFullscreenActive, Ce as watchStatus };
|
|
3473
3475
|
|
|
3474
3476
|
//# sourceMappingURL=index.js.map
|