@oddin-gg/havik-player 2.0.0 → 2.0.2
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 +15 -15
- 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.js +88 -74
- package/dist/index.js.map +1 -1
- package/embed/index.html +1 -1
- package/examples/vanilla.html +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -819,17 +819,17 @@ function Ee(e, t) {
|
|
|
819
819
|
function De() {
|
|
820
820
|
return e.isSupported();
|
|
821
821
|
}
|
|
822
|
-
var Oe = 3, ke = 5, Ae = 3, je = 3e4, Me = 12e3, Ne = 3e3, B = 6e3, Pe = 4e3, Fe = 2, Ie = 5, Le =
|
|
823
|
-
function
|
|
822
|
+
var Oe = 3, ke = 5, Ae = 3, je = 3e4, Me = 12e3, Ne = 3e3, B = 6e3, Pe = 4e3, Fe = 2, Ie = 5, Le = 2, Re = 2, ze = 2e3, Be = 3e4, Ve = 2, He = 9, Ue = .5;
|
|
823
|
+
function We(e) {
|
|
824
824
|
if (e.liveSyncDurationCount != null || e.liveMaxLatencyDurationCount != null) {
|
|
825
825
|
delete e.liveSyncDuration, delete e.liveMaxLatencyDuration;
|
|
826
826
|
return;
|
|
827
827
|
}
|
|
828
|
-
e.liveSyncDuration ??=
|
|
829
|
-
let t = Math.max(12, e.liveSyncDuration +
|
|
830
|
-
(e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t),
|
|
828
|
+
e.liveSyncDuration ??= Ve;
|
|
829
|
+
let t = Math.max(12, e.liveSyncDuration + He);
|
|
830
|
+
(e.liveMaxLatencyDuration == null || e.liveMaxLatencyDuration <= e.liveSyncDuration) && (e.liveMaxLatencyDuration = t), Ge(e, e.liveSyncDuration);
|
|
831
831
|
}
|
|
832
|
-
function
|
|
832
|
+
function Ge(e, t) {
|
|
833
833
|
let n = (e, t, n) => Math.min(n, Math.max(t, e));
|
|
834
834
|
e.maxStarvationDelay ??= n(t / 2, .5, 4), e.maxLoadingDelay ??= n(t, 1, 4), e.abrEwmaFastLive ??= n(t / 2, 1, 3);
|
|
835
835
|
}
|
|
@@ -859,6 +859,9 @@ var V = class {
|
|
|
859
859
|
lastFragLoadingMs = 0;
|
|
860
860
|
starvedTicks = 0;
|
|
861
861
|
starvationKicks = 0;
|
|
862
|
+
kickPending = !1;
|
|
863
|
+
kickTicks = 0;
|
|
864
|
+
starvationRebuilds = 0;
|
|
862
865
|
chaseSuspended = !1;
|
|
863
866
|
savedLiveMaxLatency;
|
|
864
867
|
savedMaxSyncRate;
|
|
@@ -910,10 +913,12 @@ var V = class {
|
|
|
910
913
|
let t = e.liveSyncPosition;
|
|
911
914
|
if (typeof t != "number" || !Number.isFinite(t)) return;
|
|
912
915
|
let { video: n } = this.deps;
|
|
913
|
-
n.currentTime <= t +
|
|
916
|
+
n.currentTime <= t + Ue || (n.currentTime = t);
|
|
914
917
|
}
|
|
915
918
|
startHls() {
|
|
916
|
-
let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps
|
|
919
|
+
let { video: t, descriptor: n, credential: r, deviceId: i, userId: a, lowLatency: o, debug: s, hlsConfig: c, emit: l } = this.deps;
|
|
920
|
+
this.kickPending = !1, this.kickTicks = 0, this.starvedTicks = 0, this.lastFragLoadingMs = 0, this.lastEndSN = -1, this.lastAdvanceMs = 0;
|
|
921
|
+
let u = n.drmEnabled && !!(n.drm?.widevine?.licenseUrl || n.drm?.fairplay?.licenseUrl), d = {
|
|
917
922
|
debug: s === !0,
|
|
918
923
|
enableWorker: !o,
|
|
919
924
|
lowLatencyMode: o,
|
|
@@ -946,14 +951,14 @@ var V = class {
|
|
|
946
951
|
} } : {},
|
|
947
952
|
...c
|
|
948
953
|
};
|
|
949
|
-
|
|
954
|
+
We(d), this.sizeCapManaged = d.capLevelToPlayerSize !== !0;
|
|
950
955
|
let f = new e(d);
|
|
951
956
|
this.hls = f, f.on(e.Events.MANIFEST_PARSED, () => {
|
|
952
957
|
this.mediaRecoveries = 0, this.networkRecoveries = 0, this.applySizeCap(), l({ type: "ready" });
|
|
953
958
|
}), f.on(e.Events.FRAG_BUFFERED, (e, t) => {
|
|
954
|
-
t?.frag?.type === "main" && (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
|
|
955
|
-
this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.recoveryResetTimer = null;
|
|
956
|
-
}, je));
|
|
959
|
+
t?.frag?.type === "main" && (this.kickPending = !1, this.kickTicks = 0, (this.mediaRecoveries > 0 || this.networkRecoveries > 0 || this.licenseRecoveries > 0 || this.starvationKicks > 0 || this.starvationRebuilds > 0) && !this.recoveryResetTimer && (this.recoveryResetTimer = setTimeout(() => {
|
|
960
|
+
this.mediaRecoveries = 0, this.networkRecoveries = 0, this.licenseRecoveries = 0, this.starvationKicks = 0, this.starvationRebuilds = 0, this.recoveryResetTimer = null;
|
|
961
|
+
}, je)));
|
|
957
962
|
}), f.on(e.Events.FRAG_LOADING, (e, t) => {
|
|
958
963
|
t?.frag?.type === "main" && (this.lastFragLoadingMs = Date.now());
|
|
959
964
|
}), f.on(e.Events.FRAG_LOADED, (e, t) => {
|
|
@@ -967,7 +972,7 @@ var V = class {
|
|
|
967
972
|
type: "lowLatencyDetected",
|
|
968
973
|
value: !0
|
|
969
974
|
}));
|
|
970
|
-
}), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog = setInterval(() => {
|
|
975
|
+
}), f.on(e.Events.LEVEL_LOADED, (e, t) => this.trackEnd(t)), this.endWatchdog && clearInterval(this.endWatchdog), this.endWatchdog = setInterval(() => {
|
|
971
976
|
this.checkEndStale(), this.checkStarvation();
|
|
972
977
|
}, Ne), f.on(e.Events.LEVEL_SWITCHED, (e, t) => {
|
|
973
978
|
this.renditionSwitches += 1, l({
|
|
@@ -1031,7 +1036,7 @@ var V = class {
|
|
|
1031
1036
|
return;
|
|
1032
1037
|
}
|
|
1033
1038
|
this.capTimer && clearTimeout(this.capTimer);
|
|
1034
|
-
let t = e > this.sizeCapLevel ?
|
|
1039
|
+
let t = e > this.sizeCapLevel ? ze : Be;
|
|
1035
1040
|
this.capTimer = setTimeout(() => {
|
|
1036
1041
|
this.capTimer = null, this.computeSizeCapTarget() === e ? this.applySizeCap() : this.onMediaResize();
|
|
1037
1042
|
}, t);
|
|
@@ -1126,12 +1131,18 @@ var V = class {
|
|
|
1126
1131
|
this.starvedTicks = 0;
|
|
1127
1132
|
return;
|
|
1128
1133
|
}
|
|
1129
|
-
if (
|
|
1130
|
-
this.
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1134
|
+
if (this.kickPending && (this.kickTicks += 1), !(++this.starvedTicks < Fe)) {
|
|
1135
|
+
if (this.starvedTicks = 0, this.kickPending && this.kickTicks >= Le && (this.kickPending = !1, this.kickTicks = 0, this.starvationRebuilds < Re)) {
|
|
1136
|
+
this.starvationRebuilds += 1, console.warn("havik-player: restarting the load did not resume video (the wedge is in hls.js state that only a rebuild clears) — rebuilding the player"), this.rebuildInstance("starvation rebuild");
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
if (!(this.starvationKicks >= Ie)) {
|
|
1140
|
+
this.starvationKicks += 1, this.kickPending = !0, this.kickTicks = 0, console.warn("havik-player: video starved while the live playlist advances (wedged hls.js fragment pipeline) — restarting the load");
|
|
1141
|
+
try {
|
|
1142
|
+
e.stopLoad(), e.startLoad(-1);
|
|
1143
|
+
} catch (e) {
|
|
1144
|
+
console.warn("havik-player: starvation kick failed", e);
|
|
1145
|
+
}
|
|
1135
1146
|
}
|
|
1136
1147
|
}
|
|
1137
1148
|
}
|
|
@@ -1151,7 +1162,7 @@ var V = class {
|
|
|
1151
1162
|
}));
|
|
1152
1163
|
}
|
|
1153
1164
|
stop() {
|
|
1154
|
-
this.endWatchdog &&= (clearInterval(this.endWatchdog), null);
|
|
1165
|
+
this.endWatchdog &&= (clearInterval(this.endWatchdog), null), this.kickPending = !1, this.kickTicks = 0;
|
|
1155
1166
|
try {
|
|
1156
1167
|
this.hls?.stopLoad();
|
|
1157
1168
|
} catch {}
|
|
@@ -1179,16 +1190,19 @@ var V = class {
|
|
|
1179
1190
|
this.licenseRecoveries += 1;
|
|
1180
1191
|
let r = Math.min(8e3, 500 * 2 ** (this.licenseRecoveries - 1));
|
|
1181
1192
|
return this.licenseRetryTimer = setTimeout(() => {
|
|
1182
|
-
|
|
1183
|
-
try {
|
|
1184
|
-
this.hls.destroy();
|
|
1185
|
-
} catch (e) {
|
|
1186
|
-
console.warn("havik-player: hls.js destroy() threw during license retry", e);
|
|
1187
|
-
}
|
|
1188
|
-
this.hls = null, this.startHls();
|
|
1189
|
-
}
|
|
1193
|
+
this.licenseRetryTimer = null, this.rebuildInstance("license retry");
|
|
1190
1194
|
}, r), !0;
|
|
1191
1195
|
}
|
|
1196
|
+
rebuildInstance(e) {
|
|
1197
|
+
if (this.hls) {
|
|
1198
|
+
try {
|
|
1199
|
+
this.hls.destroy();
|
|
1200
|
+
} catch (t) {
|
|
1201
|
+
console.warn(`havik-player: hls.js destroy() threw during ${e}`, t);
|
|
1202
|
+
}
|
|
1203
|
+
this.hls = null, this.startHls();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1192
1206
|
onError(t) {
|
|
1193
1207
|
if (!t.fatal) return;
|
|
1194
1208
|
let n = this.hls;
|
|
@@ -1241,7 +1255,7 @@ var V = class {
|
|
|
1241
1255
|
let r = e.response?.code ?? 0, a = e.details ?? e.type, o = e.reason ?? e.error?.message ?? "";
|
|
1242
1256
|
return new n(t === "DRM" ? i(r) : r === 401 ? "UNAUTHORIZED" : r === 403 ? "FORBIDDEN" : "INTERNAL", r, `${t} error: ${a}${o ? ` (${o})` : ""}`);
|
|
1243
1257
|
}
|
|
1244
|
-
},
|
|
1258
|
+
}, Ke = 15e3, qe = 100, H = 1, U = {
|
|
1245
1259
|
idle: "PLAYER_STATE_IDLE",
|
|
1246
1260
|
waiting: "PLAYER_STATE_WAITING",
|
|
1247
1261
|
loading: "PLAYER_STATE_LOADING",
|
|
@@ -1251,7 +1265,7 @@ var V = class {
|
|
|
1251
1265
|
ended: "PLAYER_STATE_ENDED",
|
|
1252
1266
|
error: "PLAYER_STATE_ERROR"
|
|
1253
1267
|
};
|
|
1254
|
-
function
|
|
1268
|
+
function Je(e) {
|
|
1255
1269
|
try {
|
|
1256
1270
|
let t = new URL(e), [n, ...r] = t.hostname.split(".");
|
|
1257
1271
|
return !n || r.length === 0 || n !== "feed" && !n.startsWith("feed-") ? void 0 : (t.hostname = [n.replace(/^feed/, "beacons"), ...r].join("."), t.pathname = "", t.search = "", t.origin);
|
|
@@ -1259,7 +1273,7 @@ function Ke(e) {
|
|
|
1259
1273
|
return;
|
|
1260
1274
|
}
|
|
1261
1275
|
}
|
|
1262
|
-
var
|
|
1276
|
+
var Ye = class {
|
|
1263
1277
|
opts;
|
|
1264
1278
|
url;
|
|
1265
1279
|
now;
|
|
@@ -1294,7 +1308,7 @@ var qe = class {
|
|
|
1294
1308
|
constructor(e) {
|
|
1295
1309
|
this.opts = {
|
|
1296
1310
|
...e,
|
|
1297
|
-
heartbeatMs: e.heartbeatMs ??
|
|
1311
|
+
heartbeatMs: e.heartbeatMs ?? Ke
|
|
1298
1312
|
}, this.url = `${e.endpoint.replace(/\/$/, "")}/v1/beacons`, this.now = e.now ?? (() => Date.now()), this.bornMs = this.now();
|
|
1299
1313
|
}
|
|
1300
1314
|
start() {
|
|
@@ -1386,7 +1400,7 @@ var qe = class {
|
|
|
1386
1400
|
};
|
|
1387
1401
|
}
|
|
1388
1402
|
enqueue(e) {
|
|
1389
|
-
for (this.pending.push(this.wrap(e)); this.pending.length >
|
|
1403
|
+
for (this.pending.push(this.wrap(e)); this.pending.length > qe;) {
|
|
1390
1404
|
let e = this.pending.findIndex((e) => e.heartbeat || e.stateChange);
|
|
1391
1405
|
this.pending.splice(e >= 0 ? e : 0, 1);
|
|
1392
1406
|
}
|
|
@@ -1408,7 +1422,7 @@ var qe = class {
|
|
|
1408
1422
|
keepalive: !0
|
|
1409
1423
|
}).catch(() => {});
|
|
1410
1424
|
}
|
|
1411
|
-
},
|
|
1425
|
+
}, Xe = "2.0.2", W = {
|
|
1412
1426
|
accent: "#E1B600",
|
|
1413
1427
|
accentText: "#1B1C23",
|
|
1414
1428
|
background: "#0e0f13",
|
|
@@ -1441,15 +1455,15 @@ function K(e, t) {
|
|
|
1441
1455
|
}
|
|
1442
1456
|
//#endregion
|
|
1443
1457
|
//#region src/ui/styles.ts
|
|
1444
|
-
var q = "havik-player-styles",
|
|
1445
|
-
function
|
|
1458
|
+
var q = "havik-player-styles", Ze = "\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.havik-spinner{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";
|
|
1459
|
+
function Qe(e = document) {
|
|
1446
1460
|
if (e.getElementById(q)) return;
|
|
1447
1461
|
let t = e.createElement("style");
|
|
1448
|
-
t.id = q, t.textContent =
|
|
1462
|
+
t.id = q, t.textContent = Ze, e.head.appendChild(t);
|
|
1449
1463
|
}
|
|
1450
1464
|
//#endregion
|
|
1451
1465
|
//#region src/ui/icons.ts
|
|
1452
|
-
var
|
|
1466
|
+
var $e = {
|
|
1453
1467
|
play: "<path d=\"M8 5v14l11-7z\"/>",
|
|
1454
1468
|
pause: "<path d=\"M6 5h4v14H6zM14 5h4v14h-4z\"/>",
|
|
1455
1469
|
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\"/>",
|
|
@@ -1463,20 +1477,20 @@ var Ze = {
|
|
|
1463
1477
|
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\"/>"
|
|
1464
1478
|
};
|
|
1465
1479
|
function J(e) {
|
|
1466
|
-
return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${
|
|
1480
|
+
return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false" fill="currentColor">${$e[e]}</svg>`;
|
|
1467
1481
|
}
|
|
1468
1482
|
//#endregion
|
|
1469
1483
|
//#region src/ui/controls.ts
|
|
1470
|
-
var
|
|
1484
|
+
var et = 3e3, tt = /* @__PURE__ */ new Set([
|
|
1471
1485
|
"GONE",
|
|
1472
1486
|
"NOT_FOUND",
|
|
1473
1487
|
"INVALID_URN",
|
|
1474
1488
|
"UNAUTHORIZED"
|
|
1475
1489
|
]);
|
|
1476
|
-
function
|
|
1477
|
-
return new
|
|
1490
|
+
function nt(e) {
|
|
1491
|
+
return new rt(e);
|
|
1478
1492
|
}
|
|
1479
|
-
var
|
|
1493
|
+
var rt = class {
|
|
1480
1494
|
root;
|
|
1481
1495
|
video;
|
|
1482
1496
|
player;
|
|
@@ -1518,7 +1532,7 @@ var tt = class {
|
|
|
1518
1532
|
scrubbing = !1;
|
|
1519
1533
|
offFns = [];
|
|
1520
1534
|
constructor(e) {
|
|
1521
|
-
this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ??
|
|
1535
|
+
this.root = e.root, this.video = e.video, this.player = e.player, this.doc = e.root.ownerDocument ?? document, this.autoHideMs = e.autoHideMs ?? et, 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, Qe(this.doc), 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");
|
|
1522
1536
|
let t = (e, t, n) => {
|
|
1523
1537
|
let r = this.doc.createElement(e);
|
|
1524
1538
|
if (r.className = t, n) for (let [e, t] of Object.entries(n)) r.setAttribute(e, t);
|
|
@@ -1609,7 +1623,7 @@ var tt = class {
|
|
|
1609
1623
|
e.type = "button", e.className = "havik-status__btn havik-status__btn--play", e.setAttribute("aria-label", "Play"), e.innerHTML = J("play"), e.addEventListener("click", () => void this.player.play()), n.append(e);
|
|
1610
1624
|
} else {
|
|
1611
1625
|
let r = this.doc.createElement("div");
|
|
1612
|
-
if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError &&
|
|
1626
|
+
if (r.className = "havik-status__msg", r.textContent = t ?? "", n.append(r), e === "error" && this.lastError && !tt.has(this.lastError.code)) {
|
|
1613
1627
|
let e = this.doc.createElement("button");
|
|
1614
1628
|
e.type = "button", e.className = "havik-status__btn", e.innerHTML = `${J("retry")}<span>Retry</span>`, e.addEventListener("click", () => void this.player.retry()), n.append(e);
|
|
1615
1629
|
}
|
|
@@ -1700,17 +1714,17 @@ var tt = class {
|
|
|
1700
1714
|
renderQualityMenu() {
|
|
1701
1715
|
let e = this.player.getQualityLevels(), t = this.player.getCurrentQuality(), n = this.doc.createElement("div");
|
|
1702
1716
|
n.className = "havik-menu__title", n.textContent = "Quality", this.menu.replaceChildren(n, this.menuItem("Auto", t === -1, () => this.player.setQuality("auto")));
|
|
1703
|
-
for (let n of e) this.menu.append(this.menuItem(
|
|
1717
|
+
for (let n of e) this.menu.append(this.menuItem(it(n), t === n.index, () => this.player.setQuality(n.index)));
|
|
1704
1718
|
}
|
|
1705
1719
|
renderAudioMenu() {
|
|
1706
1720
|
let e = this.player.getAudioTracks(), t = this.doc.createElement("div");
|
|
1707
1721
|
t.className = "havik-menu__title", t.textContent = "Audio", this.menu.replaceChildren(t);
|
|
1708
|
-
for (let t of e) this.menu.append(this.menuItem(
|
|
1722
|
+
for (let t of e) this.menu.append(this.menuItem(at(t), t.default, () => this.player.setAudioTrack(t.id)));
|
|
1709
1723
|
}
|
|
1710
1724
|
renderCcMenu() {
|
|
1711
1725
|
let e = this.player.getTextTracks(), t = this.doc.createElement("div");
|
|
1712
1726
|
t.className = "havik-menu__title", t.textContent = "Subtitles", this.menu.replaceChildren(t, this.menuItem("Off", !1, () => this.player.setTextTrack(-1)));
|
|
1713
|
-
for (let t of e) this.menu.append(this.menuItem(
|
|
1727
|
+
for (let t of e) this.menu.append(this.menuItem(ot(t), !1, () => this.player.setTextTrack(t.id)));
|
|
1714
1728
|
}
|
|
1715
1729
|
onTracksChanged() {
|
|
1716
1730
|
this.openMenu && this.renderMenu(this.openMenu);
|
|
@@ -1815,27 +1829,27 @@ function Y(e) {
|
|
|
1815
1829
|
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");
|
|
1816
1830
|
return r > 0 ? `${r}:${i}:${a}` : `${i}:${a}`;
|
|
1817
1831
|
}
|
|
1818
|
-
function
|
|
1832
|
+
function it(e) {
|
|
1819
1833
|
return e.height ? `${e.height}p` : `${Math.round(e.bitrate / 1e3)} kbps`;
|
|
1820
1834
|
}
|
|
1821
|
-
function
|
|
1835
|
+
function at(e) {
|
|
1822
1836
|
return e.name || e.lang || `Track ${e.id}`;
|
|
1823
1837
|
}
|
|
1824
|
-
function
|
|
1838
|
+
function ot(e) {
|
|
1825
1839
|
return e.name || e.lang || `Track ${e.id}`;
|
|
1826
1840
|
}
|
|
1827
1841
|
//#endregion
|
|
1828
1842
|
//#region src/managed/index.ts
|
|
1829
|
-
var
|
|
1843
|
+
var st = 6e3, X = 45e3, Z = 2e3, ct = 3, lt = 2e3, ut = 48e4;
|
|
1830
1844
|
async function Q(e) {
|
|
1831
1845
|
if (!e.video || typeof e.video.play != "function") throw new n("INTERNAL", 0, "createPlayer: opts.video must be an HTMLVideoElement");
|
|
1832
1846
|
if (!e.baseUrl) throw new n("INTERNAL", 0, "createPlayer: baseUrl is required");
|
|
1833
1847
|
if (!e.matchUrn) throw new n("INVALID_URN", 0, "createPlayer: matchUrn is required");
|
|
1834
1848
|
if (!e.credential) throw new n("INTERNAL", 0, "createPlayer: credential is required");
|
|
1835
|
-
let t = new
|
|
1849
|
+
let t = new dt(e);
|
|
1836
1850
|
return await t.start(), t;
|
|
1837
1851
|
}
|
|
1838
|
-
var
|
|
1852
|
+
var dt = class {
|
|
1839
1853
|
opts;
|
|
1840
1854
|
abort = new AbortController();
|
|
1841
1855
|
listeners = {};
|
|
@@ -1959,7 +1973,7 @@ var lt = class {
|
|
|
1959
1973
|
!a && !p() && h();
|
|
1960
1974
|
}, e);
|
|
1961
1975
|
};
|
|
1962
|
-
_(
|
|
1976
|
+
_(st), this.armingSub = D({
|
|
1963
1977
|
eventsBaseUrl: t,
|
|
1964
1978
|
matchUrn: this.opts.matchUrn,
|
|
1965
1979
|
credential: this.opts.credential,
|
|
@@ -2005,14 +2019,14 @@ var lt = class {
|
|
|
2005
2019
|
if (this.analyticsSession?.noteDestroyed(), this.analyticsSession = null, this.detachSeekListener?.(), this.opts.analytics === !1) return;
|
|
2006
2020
|
let r = e.analytics?.sid;
|
|
2007
2021
|
if (!r) return;
|
|
2008
|
-
let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ??
|
|
2022
|
+
let i = (typeof this.opts.analytics == "object" ? this.opts.analytics.endpoint : void 0) ?? Je(this.opts.baseUrl);
|
|
2009
2023
|
if (!i) return;
|
|
2010
|
-
let a = this.opts.video, o = new
|
|
2024
|
+
let a = this.opts.video, o = new Ye({
|
|
2011
2025
|
endpoint: i,
|
|
2012
2026
|
sid: r,
|
|
2013
2027
|
apiKey: n,
|
|
2014
2028
|
player: t.name === "native" ? "PLAYER_SAFARI_NATIVE" : "PLAYER_HLSJS",
|
|
2015
|
-
sdkVersion:
|
|
2029
|
+
sdkVersion: Xe,
|
|
2016
2030
|
sample: () => {
|
|
2017
2031
|
let e = this.engine?.getStats() ?? F, t = a.buffered.length ? Math.max(0, a.buffered.end(a.buffered.length - 1) - a.currentTime) : 0;
|
|
2018
2032
|
return {
|
|
@@ -2089,7 +2103,7 @@ var lt = class {
|
|
|
2089
2103
|
r = 0, i = !1;
|
|
2090
2104
|
},
|
|
2091
2105
|
onError: () => {
|
|
2092
|
-
this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >=
|
|
2106
|
+
this.destroyed || e !== this.loadGen || i || this.endedHandled || (r += 1, r >= ct && (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.`))));
|
|
2093
2107
|
}
|
|
2094
2108
|
});
|
|
2095
2109
|
}
|
|
@@ -2135,7 +2149,7 @@ var lt = class {
|
|
|
2135
2149
|
}
|
|
2136
2150
|
t.controls = !1;
|
|
2137
2151
|
let n = (t.ownerDocument ?? document).createElement("div"), r = t.parentNode;
|
|
2138
|
-
r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar =
|
|
2152
|
+
r && r.insertBefore(n, t), n.appendChild(t), K(n, this.opts.theme), this.container = n, this.controlBar = nt({
|
|
2139
2153
|
root: n,
|
|
2140
2154
|
video: t,
|
|
2141
2155
|
player: this,
|
|
@@ -2159,7 +2173,7 @@ var lt = class {
|
|
|
2159
2173
|
}
|
|
2160
2174
|
startStats() {
|
|
2161
2175
|
this.stopStats();
|
|
2162
|
-
let e = Math.max(500, this.opts.statsIntervalMs ??
|
|
2176
|
+
let e = Math.max(500, this.opts.statsIntervalMs ?? lt);
|
|
2163
2177
|
this.statsTimer = setInterval(() => {
|
|
2164
2178
|
if (!this.engine) return;
|
|
2165
2179
|
let e = this.engine.getStats();
|
|
@@ -2171,7 +2185,7 @@ var lt = class {
|
|
|
2171
2185
|
}
|
|
2172
2186
|
scheduleLicenseRefresh() {
|
|
2173
2187
|
this.stopRefresh();
|
|
2174
|
-
let e = this.opts.licenseRefreshMs ??
|
|
2188
|
+
let e = this.opts.licenseRefreshMs ?? ut;
|
|
2175
2189
|
!e || !this._descriptor?.drmEnabled || (this.refreshTimer = setInterval(() => void this.refreshLicense(), e));
|
|
2176
2190
|
}
|
|
2177
2191
|
stopRefresh() {
|
|
@@ -2318,7 +2332,7 @@ function $(e) {
|
|
|
2318
2332
|
}
|
|
2319
2333
|
//#endregion
|
|
2320
2334
|
//#region src/embed/host.ts
|
|
2321
|
-
function
|
|
2335
|
+
function ft(e) {
|
|
2322
2336
|
if (!e.container || typeof e.container.appendChild != "function") throw Error("mountEmbed: opts.container must be a DOM element");
|
|
2323
2337
|
if (!e.src) throw Error("mountEmbed: opts.src is required");
|
|
2324
2338
|
if (!e.baseUrl || !e.matchUrn) throw Error("mountEmbed: opts.baseUrl and opts.matchUrn are required");
|
|
@@ -2377,8 +2391,8 @@ function ut(e) {
|
|
|
2377
2391
|
}
|
|
2378
2392
|
//#endregion
|
|
2379
2393
|
//#region src/embed/page.ts
|
|
2380
|
-
var
|
|
2381
|
-
function
|
|
2394
|
+
var pt = /^[A-Za-z0-9:_.-]{1,128}$/;
|
|
2395
|
+
function mt() {
|
|
2382
2396
|
let e = window.HAVIK_EMBED_CONFIG;
|
|
2383
2397
|
if (e) return {
|
|
2384
2398
|
baseUrl: e.baseUrl ?? "",
|
|
@@ -2400,9 +2414,9 @@ function ft() {
|
|
|
2400
2414
|
waitForLive: !0
|
|
2401
2415
|
};
|
|
2402
2416
|
}
|
|
2403
|
-
async function
|
|
2404
|
-
let e =
|
|
2405
|
-
|
|
2417
|
+
async function ht() {
|
|
2418
|
+
let e = mt();
|
|
2419
|
+
_t();
|
|
2406
2420
|
let t = document.createElement("video");
|
|
2407
2421
|
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);
|
|
2408
2422
|
let r = e.parentOrigin && e.parentOrigin !== "null" ? e.parentOrigin : null;
|
|
@@ -2457,7 +2471,7 @@ async function pt() {
|
|
|
2457
2471
|
code: e.code,
|
|
2458
2472
|
message: e.message
|
|
2459
2473
|
})), e.on("autoplayblocked", () => {
|
|
2460
|
-
i({ type: "oddin:autoplayblocked" }),
|
|
2474
|
+
i({ type: "oddin:autoplayblocked" }), gt(t, e);
|
|
2461
2475
|
});
|
|
2462
2476
|
}, s = async (r) => {
|
|
2463
2477
|
a?.destroy(), a = null;
|
|
@@ -2523,7 +2537,7 @@ async function pt() {
|
|
|
2523
2537
|
a?.retry();
|
|
2524
2538
|
break;
|
|
2525
2539
|
case "oddin:load":
|
|
2526
|
-
typeof e.matchUrn == "string" &&
|
|
2540
|
+
typeof e.matchUrn == "string" && pt.test(e.matchUrn) && s(e.matchUrn);
|
|
2527
2541
|
break;
|
|
2528
2542
|
case "oddin:destroy": c();
|
|
2529
2543
|
}
|
|
@@ -2535,18 +2549,18 @@ async function pt() {
|
|
|
2535
2549
|
protocol: 1
|
|
2536
2550
|
});
|
|
2537
2551
|
}
|
|
2538
|
-
function
|
|
2552
|
+
function gt(e, t) {
|
|
2539
2553
|
if (document.getElementById("havik-tap")) return;
|
|
2540
2554
|
let n = document.createElement("button");
|
|
2541
2555
|
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", () => {
|
|
2542
2556
|
n.remove(), t.play();
|
|
2543
2557
|
}), document.body.appendChild(n);
|
|
2544
2558
|
}
|
|
2545
|
-
function
|
|
2559
|
+
function _t() {
|
|
2546
2560
|
let e = document.createElement("style");
|
|
2547
2561
|
e.textContent = "html,body{margin:0;height:100%;background:#000;overflow:hidden}body{display:flex}", document.head.appendChild(e);
|
|
2548
2562
|
}
|
|
2549
2563
|
//#endregion
|
|
2550
|
-
export { W as ODDIN_THEME, n as PlaybackError, K as applyTheme,
|
|
2564
|
+
export { W as ODDIN_THEME, n as PlaybackError, K as applyTheme, ht as bootEmbed, he as clearDeviceId, Q as createPlayer, O as deriveEventsBaseUrl, xe as detectDrmSupport, T as enterVideoFullscreen, E as exitVideoFullscreen, le as fetchCatalog, de as fetchTournament, pe as flattenMatches, j as getDeviceId, Ce as isLowLatencyManifest, $ as isOddinMessage, _e as licenseRequestHeaders, ft as mountEmbed, v as pollToLive, b as resolveStream, D as subscribeLiveState, w as videoFullscreenActive, me as watchStatus };
|
|
2551
2565
|
|
|
2552
2566
|
//# sourceMappingURL=index.js.map
|