itube-modern-player 0.1.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +630 -0
  3. package/dist/core/dom.d.ts +18 -0
  4. package/dist/core/events.d.ts +10 -0
  5. package/dist/core/icons.d.ts +3 -0
  6. package/dist/core/labels.d.ts +2 -0
  7. package/dist/core/localeRegistry.d.ts +7 -0
  8. package/dist/core/locales.d.ts +11 -0
  9. package/dist/core.cjs +5 -0
  10. package/dist/core.cjs.map +1 -0
  11. package/dist/core.js +1668 -0
  12. package/dist/core.js.map +1 -0
  13. package/dist/coreEntry.d.ts +14 -0
  14. package/dist/features/ads/manager.d.ts +48 -0
  15. package/dist/features/ads/vast.d.ts +9 -0
  16. package/dist/features/chapters.d.ts +12 -0
  17. package/dist/features/heatmap.d.ts +17 -0
  18. package/dist/features/hls.d.ts +26 -0
  19. package/dist/features/thumbnails.d.ts +11 -0
  20. package/dist/global.d.ts +2 -0
  21. package/dist/index.cjs +2 -0
  22. package/dist/index.cjs.map +1 -0
  23. package/dist/index.d.ts +7 -0
  24. package/dist/index.js +28 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/itube-modern-player.iife.js +5 -0
  27. package/dist/itube-modern-player.iife.js.map +1 -0
  28. package/dist/labels-C3gAZEm-.js +41 -0
  29. package/dist/labels-C3gAZEm-.js.map +1 -0
  30. package/dist/labels-DTgTxMuq.cjs +2 -0
  31. package/dist/labels-DTgTxMuq.cjs.map +1 -0
  32. package/dist/lazy.cjs +2 -0
  33. package/dist/lazy.cjs.map +1 -0
  34. package/dist/lazy.d.ts +23 -0
  35. package/dist/lazy.js +60 -0
  36. package/dist/lazy.js.map +1 -0
  37. package/dist/locales.cjs +2 -0
  38. package/dist/locales.cjs.map +1 -0
  39. package/dist/locales.js +380 -0
  40. package/dist/locales.js.map +1 -0
  41. package/dist/localesEntry.d.ts +8 -0
  42. package/dist/player.d.ts +142 -0
  43. package/dist/style.css +1 -0
  44. package/dist/types.d.ts +443 -0
  45. package/dist/ui/controls.d.ts +90 -0
  46. package/dist/ui/menu.d.ts +29 -0
  47. package/dist/ui/overlays.d.ts +51 -0
  48. package/dist/ui/playlistPanel.d.ts +16 -0
  49. package/dist/ui/progress.d.ts +41 -0
  50. package/dist/ui/scenesPanel.d.ts +19 -0
  51. package/dist/vue.cjs +2 -0
  52. package/dist/vue.cjs.map +1 -0
  53. package/dist/vue.d.ts +67 -0
  54. package/dist/vue.js +89 -0
  55. package/dist/vue.js.map +1 -0
  56. package/package.json +86 -0
package/dist/core.js ADDED
@@ -0,0 +1,1668 @@
1
+ import { d as I } from "./labels-C3gAZEm-.js";
2
+ function l(r, t, e) {
3
+ const i = document.createElement(r);
4
+ if (t && (i.className = t), e)
5
+ for (const [s, n] of Object.entries(e)) i.setAttribute(s, n);
6
+ return i;
7
+ }
8
+ function m(r, t, e) {
9
+ const i = l("button", `imp-btn ${r}`, { type: "button", "aria-label": t, title: t });
10
+ return i.innerHTML = e, i;
11
+ }
12
+ function E(r, t, e) {
13
+ r.innerHTML = t, e !== void 0 && (r.setAttribute("aria-label", e), r.setAttribute("title", e));
14
+ }
15
+ function y(r, t, e) {
16
+ return Math.min(e, Math.max(t, r));
17
+ }
18
+ function w(r) {
19
+ if (!Number.isFinite(r) || r < 0) return "0:00";
20
+ const t = Math.floor(r % 60), e = Math.floor(r / 60 % 60), i = Math.floor(r / 3600), s = i > 0 ? String(e).padStart(2, "0") : String(e), n = String(t).padStart(2, "0");
21
+ return i > 0 ? `${i}:${s}:${n}` : `${s}:${n}`;
22
+ }
23
+ function R(r) {
24
+ const t = r.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:[.,](\d{1,3}))?$/);
25
+ if (!t) return null;
26
+ const e = t[1] ? Number(t[1]) : 0, i = Number(t[2]), s = Number(t[3]), n = t[4] ? Number(t[4].padEnd(3, "0")) : 0;
27
+ return e * 3600 + i * 60 + s + n / 1e3;
28
+ }
29
+ function U(r) {
30
+ const t = [], e = r.replace(/\r\n?/g, `
31
+ `).split(/\n\n+/);
32
+ for (const i of e) {
33
+ const s = i.split(`
34
+ `).filter((f) => f.trim() !== "");
35
+ if (s.length === 0) continue;
36
+ let n = s.findIndex((f) => f.includes("-->"));
37
+ if (n === -1) continue;
38
+ const [o, a] = s[n].split("-->"), h = R(o), c = R((a ?? "").split(" ")[1] ?? a ?? "") ?? R(a ?? "");
39
+ if (h === null || c === null) continue;
40
+ const u = s.slice(n + 1).join(`
41
+ `).trim();
42
+ u && t.push({ start: h, end: c, text: u });
43
+ }
44
+ return t;
45
+ }
46
+ function G(r, t) {
47
+ try {
48
+ return new URL(r, new URL(t, window.location.href)).toString();
49
+ } catch {
50
+ return r;
51
+ }
52
+ }
53
+ class Y {
54
+ constructor() {
55
+ this.listeners = /* @__PURE__ */ new Map();
56
+ }
57
+ /** Subscribe. Returns an unsubscribe function. */
58
+ on(t, e) {
59
+ let i = this.listeners.get(t);
60
+ return i || (i = /* @__PURE__ */ new Set(), this.listeners.set(t, i)), i.add(e), () => this.off(t, e);
61
+ }
62
+ once(t, e) {
63
+ const i = this.on(t, (s) => {
64
+ i(), e(s);
65
+ });
66
+ return i;
67
+ }
68
+ off(t, e) {
69
+ this.listeners.get(t)?.delete(e);
70
+ }
71
+ emit(t, e) {
72
+ const i = this.listeners.get(t);
73
+ if (i)
74
+ for (const s of [...i])
75
+ try {
76
+ s(e);
77
+ } catch (n) {
78
+ console.error("[itube-player] listener error", n);
79
+ }
80
+ }
81
+ removeAll() {
82
+ this.listeners.clear();
83
+ }
84
+ }
85
+ const p = (r, t = "0 0 24 24") => `<svg viewBox="${t}" fill="currentColor" aria-hidden="true" focusable="false">${r}</svg>`, J = {
86
+ play: p('<path d="M8 5.14v13.72c0 .8.87 1.3 1.56.88l10.54-6.86a1.05 1.05 0 0 0 0-1.76L9.56 4.26C8.87 3.84 8 4.34 8 5.14Z"/>'),
87
+ pause: p('<rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/>'),
88
+ replay: p('<path d="M12 5V2.5L7.5 6 12 9.5V7a5 5 0 1 1-5 5H5a7 7 0 1 0 7-7Z"/>'),
89
+ bigPlay: p('<path d="M8 5.14v13.72c0 .8.87 1.3 1.56.88l10.54-6.86a1.05 1.05 0 0 0 0-1.76L9.56 4.26C8.87 3.84 8 4.34 8 5.14Z"/>'),
90
+ volumeHigh: p('<path d="M4 9v6h3.5L12 19.5v-15L7.5 9H4Z"/><path d="M14.5 8.6a4.5 4.5 0 0 1 0 6.8v-1.9a2.5 2.5 0 0 0 0-3v-1.9Z"/><path d="M14.5 5.2a8 8 0 0 1 0 13.6v-2a6 6 0 0 0 0-9.6v-2Z"/>'),
91
+ volumeLow: p('<path d="M4 9v6h3.5L12 19.5v-15L7.5 9H4Z"/><path d="M14.5 8.6a4.5 4.5 0 0 1 0 6.8v-1.9a2.5 2.5 0 0 0 0-3v-1.9Z"/>'),
92
+ volumeMute: p('<path d="M4 9v6h3.5L12 19.5v-15L7.5 9H4Z"/><path d="m15.3 9.3 1.4 1.4 1.4-1.4 1.4 1.4-1.4 1.4 1.4 1.4-1.4 1.4-1.4-1.4-1.4 1.4-1.4-1.4 1.4-1.4-1.4-1.4 1.4-1.4Z"/>'),
93
+ fullscreen: p('<path d="M5 5h5v2H7v3H5V5Zm9 0h5v5h-2V7h-3V5ZM5 14h2v3h3v2H5v-5Zm12 0h2v5h-5v-2h3v-3Z"/>'),
94
+ fullscreenExit: p('<path d="M10 10H5V8h3V5h2v5Zm4 0V5h2v3h3v2h-5Zm-4 4v5H8v-3H5v-2h5Zm4 0h5v2h-3v3h-2v-5Z"/>'),
95
+ pip: p('<path d="M3 5h18v14H3V5Zm2 2v10h14V7H5Z"/><rect x="12" y="11" width="6" height="4"/>'),
96
+ settings: p('<path d="M12 8.5A3.5 3.5 0 1 0 12 15.5 3.5 3.5 0 0 0 12 8.5Zm0 2a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Z"/><path d="M10.3 2.8 9.9 5.1a7 7 0 0 0-1.5.86l-2.2-.8-1.7 3 1.8 1.5a7 7 0 0 0 0 1.7l-1.8 1.5 1.7 3 2.2-.8c.46.36.97.65 1.5.86l.4 2.3h3.4l.4-2.3a7 7 0 0 0 1.5-.86l2.2.8 1.7-3-1.8-1.5a7 7 0 0 0 0-1.7l1.8-1.5-1.7-3-2.2.8a7 7 0 0 0-1.5-.86l-.4-2.3h-3.4Zm1.7 5.7a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Z"/>'),
97
+ speed: p('<path d="M12 4a9 9 0 0 0-9 9 8.96 8.96 0 0 0 1.62 5.16l1.64-1.15A7 7 0 0 1 5 13a7 7 0 1 1 12.74 4.01l1.64 1.15A8.96 8.96 0 0 0 21 13a9 9 0 0 0-9-9Z"/><path d="m15.6 8.3-3.95 3.13a1.5 1.5 0 1 0 1.92 1.92L16.7 9.4a.78.78 0 0 0-1.1-1.1Z"/>'),
98
+ scenes: p('<path d="M3 5h18v14H3V5Zm2 2v10h3V7H5Zm5 0v10h9V7h-9Zm-5 3h3v1H5v-1Zm0 3h3v1H5v-1Z"/>'),
99
+ shuffle: p('<path d="M4 6h2.6c1.5 0 2.9.7 3.8 1.9l4.1 5.4a2.75 2.75 0 0 0 2.2 1.1H19l-1.8-1.8 1.4-1.4 4.2 4.2-4.2 4.2-1.4-1.4L19 16.4h-2.3a4.75 4.75 0 0 1-3.8-1.9L8.8 9.1A2.75 2.75 0 0 0 6.6 8H4V6Z"/><path d="M19 7.6h-2.3c-.86 0-1.67.41-2.18 1.1l-.93 1.23-1.25-1.65.58-.77A4.75 4.75 0 0 1 16.7 5.6H19L17.2 3.8l1.4-1.4 4.2 4.2-4.2 4.2-1.4-1.4L19 7.6Z" transform="translate(0 1.2)"/>'),
100
+ repeat: p('<path d="M7 7h10v2.5L21 6l-4-3.5V5H5v6h2V7Zm10 10H7v-2.5L3 18l4 3.5V19h12v-6h-2v4Z"/>'),
101
+ subtitles: p('<path d="M3 5h18v14H3V5Zm2 2v10h14V7H5Zm2 3h6v2H7v-2Zm8 0h2v2h-2v-2ZM7 14h2v2H7v-2Zm4 0h6v2h-6v-2Z"/>'),
102
+ list: p('<path d="M4 6h2v2H4V6Zm4 0h12v2H8V6ZM4 11h2v2H4v-2Zm4 0h12v2H8v-2ZM4 16h2v2H4v-2Zm4 0h12v2H8v-2Z"/>'),
103
+ next: p('<path d="M6 5.5v13c0 .77.84 1.25 1.5.85l9-6.5a1 1 0 0 0 0-1.7l-9-6.5c-.66-.4-1.5.08-1.5.85Z"/><rect x="17" y="5" width="2.5" height="14" rx="1"/>'),
104
+ previous: p('<path d="M18 5.5v13c0 .77-.84 1.25-1.5.85l-9-6.5a1 1 0 0 1 0-1.7l9-6.5c.66-.4 1.5.08 1.5.85Z"/><rect x="4.5" y="5" width="2.5" height="14" rx="1"/>'),
105
+ seekForward: p('<path d="M12 4V1.5L17 5.5 12 9.5V7a5.5 5.5 0 1 0 5.5 5.5H20A8 8 0 1 1 12 4Z"/>'),
106
+ seekBack: p('<path d="M12 4V1.5L7 5.5 12 9.5V7a5.5 5.5 0 1 1-5.5 5.5H4A8 8 0 1 0 12 4Z"/>'),
107
+ like: p('<path d="M9 21H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h4v11Zm2 0h7.1a2 2 0 0 0 2-1.6l1.3-7a2 2 0 0 0-2-2.4H14V5.5A2.5 2.5 0 0 0 11.5 3l-.5.1V10h-.9L11 21Z"/>'),
108
+ dislike: p('<path d="M15 3h4a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2h-4V3Zm-2 0H5.9a2 2 0 0 0-2 1.6l-1.3 7a2 2 0 0 0 2 2.4H10v4.5A2.5 2.5 0 0 0 12.5 21l.5-.1V14h.9L13 3Z"/>'),
109
+ addTo: p('<path d="M4 6h12v2H4V6Zm0 4h12v2H4v-2Zm0 4h8v2H4v-2Zm14 0v-4h2v4h4v2h-4v4h-2v-4h-4v-2h4Z"/>'),
110
+ share: p('<path d="M18 8a3 3 0 1 0-2.83-4H15a3 3 0 0 0 .14 1.06L8.4 8.94a3 3 0 1 0 0 6.12l6.74 3.88A3 3 0 1 0 16 16.6l-6.74-3.88a3.03 3.03 0 0 0 0-1.44L16 7.4c.55.38 1.24.6 2 .6Z"/>'),
111
+ report: p('<path d="M5 3h2v18H5V3Zm4 1h10l-2.5 4L19 12H9V4Z"/>'),
112
+ more: p('<circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/>'),
113
+ close: p('<path d="m6.4 5 5.6 5.6L17.6 5 19 6.4 13.4 12l5.6 5.6-1.4 1.4-5.6-5.6L6.4 19 5 17.6 10.6 12 5 6.4 6.4 5Z"/>')
114
+ }, T = { en: I };
115
+ function Lt(r, t) {
116
+ T[r] = t;
117
+ }
118
+ function St(r) {
119
+ for (const [t, e] of Object.entries(r))
120
+ e && (T[t] = e);
121
+ }
122
+ function tt(r) {
123
+ return r && T[r] || I;
124
+ }
125
+ function xt() {
126
+ return Object.keys(T);
127
+ }
128
+ function et(r, t, e = 100) {
129
+ if (r.length === 0 || !Number.isFinite(t) || t <= 0) return [];
130
+ const i = new Array(e).fill(0);
131
+ let s = !1;
132
+ for (const c of r) {
133
+ if (!Number.isFinite(c.time) || c.time < 0 || c.time > t) continue;
134
+ const u = Math.max(0, c.value);
135
+ if (u === 0) continue;
136
+ const f = Math.min(e - 1, Math.floor(c.time / t * e));
137
+ i[f] += u, s = !0;
138
+ }
139
+ if (!s) return [];
140
+ const n = [0.06, 0.24, 0.4, 0.24, 0.06], o = i.map(
141
+ (c, u) => n.reduce((f, d, b) => f + d * (i[u + b - 2] ?? 0), 0)
142
+ ), a = Math.max(...o);
143
+ if (a <= 0) return [];
144
+ const h = 0.08;
145
+ return o.map((c) => h + (1 - h) * (c / a));
146
+ }
147
+ function it(r, t = 1e3, e = 100) {
148
+ if (r.length === 0) return "";
149
+ const i = t / (r.length - 1 || 1);
150
+ let s = `M 0 ${e}`;
151
+ return r.forEach((n, o) => {
152
+ s += ` L ${(o * i).toFixed(1)} ${(e - n * e).toFixed(1)}`;
153
+ }), s += ` L ${t} ${e} Z`, s;
154
+ }
155
+ async function st(r, t) {
156
+ if (r.src)
157
+ return {
158
+ roll: r.roll,
159
+ mediaUrl: r.src,
160
+ clickThrough: r.clickUrl,
161
+ impressions: [],
162
+ tracking: {}
163
+ };
164
+ if (!r.vastTag) throw new Error('Ad roll has neither "vastTag" nor "src"');
165
+ return j(r, r.vastTag, t, 0, { impressions: [], tracking: {} });
166
+ }
167
+ async function j(r, t, e, i, s) {
168
+ if (i > e.maxWrapperDepth) throw new Error("VAST wrapper depth limit exceeded");
169
+ const n = new AbortController(), o = setTimeout(() => n.abort(), e.requestTimeout);
170
+ let a;
171
+ try {
172
+ const v = await fetch(t, { signal: n.signal, credentials: "omit" });
173
+ if (!v.ok) throw new Error(`VAST request failed (${v.status})`);
174
+ a = await v.text();
175
+ } finally {
176
+ clearTimeout(o);
177
+ }
178
+ const h = new DOMParser().parseFromString(a, "text/xml");
179
+ if (h.querySelector("parsererror")) throw new Error("VAST response is not valid XML");
180
+ const c = h.querySelector("VAST > Ad");
181
+ if (!c) throw new Error("VAST response contains no ads");
182
+ nt(c, s);
183
+ const u = c.querySelector(":scope > Wrapper");
184
+ if (u) {
185
+ const v = k(u.querySelector("VASTAdTagURI"));
186
+ if (!v) throw new Error("VAST wrapper without VASTAdTagURI");
187
+ return j(r, v, e, i + 1, s);
188
+ }
189
+ const f = c.querySelector(":scope > InLine");
190
+ if (!f) throw new Error("VAST ad has neither InLine nor Wrapper");
191
+ const d = f.querySelector("Creatives > Creative > Linear");
192
+ if (!d) throw new Error("VAST ad has no Linear creative");
193
+ const b = rt(d);
194
+ if (!b) throw new Error("VAST ad has no playable MediaFile");
195
+ return {
196
+ roll: r.roll,
197
+ mediaUrl: b.url,
198
+ mediaType: b.type,
199
+ clickThrough: k(d.querySelector("VideoClicks > ClickThrough")) ?? r.clickUrl,
200
+ duration: V(k(d.querySelector(":scope > Duration"))),
201
+ skipOffset: ot(
202
+ d.getAttribute("skipoffset"),
203
+ V(k(d.querySelector(":scope > Duration")))
204
+ ),
205
+ impressions: s.impressions,
206
+ tracking: s.tracking,
207
+ adTitle: k(f.querySelector(":scope > AdTitle")) ?? void 0
208
+ };
209
+ }
210
+ function nt(r, t) {
211
+ var e, i;
212
+ for (const s of r.querySelectorAll("Impression")) {
213
+ const n = k(s);
214
+ n && t.impressions.push(n);
215
+ }
216
+ for (const s of r.querySelectorAll("Linear > TrackingEvents > Tracking")) {
217
+ const n = s.getAttribute("event"), o = k(s);
218
+ !n || !o || ["start", "firstQuartile", "midpoint", "thirdQuartile", "complete", "skip", "pause", "resume"].includes(n) && ((e = t.tracking)[n] ?? (e[n] = [])).push(o);
219
+ }
220
+ for (const s of r.querySelectorAll("Linear > VideoClicks > ClickTracking")) {
221
+ const n = k(s);
222
+ n && ((i = t.tracking).click ?? (i.click = [])).push(n);
223
+ }
224
+ }
225
+ function rt(r) {
226
+ const e = [...r.querySelectorAll("MediaFiles > MediaFile")].map((o) => ({
227
+ url: k(o) ?? "",
228
+ type: o.getAttribute("type") ?? void 0,
229
+ delivery: o.getAttribute("delivery") ?? "",
230
+ bitrate: Number(o.getAttribute("bitrate") ?? 0),
231
+ apiFramework: o.getAttribute("apiFramework") ?? ""
232
+ })).filter((o) => o.url && o.apiFramework.toUpperCase() !== "VPAID").filter(
233
+ (o) => !o.type || /(video\/(mp4|webm|ogg)|application\/(x-mpegurl|vnd\.apple\.mpegurl))/i.test(o.type)
234
+ );
235
+ if (e.length === 0) return null;
236
+ e.sort((o, a) => o.bitrate - a.bitrate);
237
+ const i = e.filter((o) => o.delivery !== "streaming"), s = i.length > 0 ? i : e, n = s[Math.floor(s.length / 2)];
238
+ return { url: n.url, type: n.type };
239
+ }
240
+ function V(r) {
241
+ if (!r) return;
242
+ const t = r.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
243
+ if (t)
244
+ return (t[1] ? Number(t[1]) * 3600 : 0) + Number(t[2]) * 60 + Number(t[3]) + (t[4] ? Number(t[4].padEnd(3, "0")) / 1e3 : 0);
245
+ }
246
+ function ot(r, t) {
247
+ if (!r) return;
248
+ const e = r.trim().match(/^(\d+(?:\.\d+)?)%$/);
249
+ return e ? t !== void 0 ? Number(e[1]) / 100 * t : void 0 : V(r);
250
+ }
251
+ function C(r) {
252
+ if (r)
253
+ for (const t of r)
254
+ try {
255
+ new Image().src = t;
256
+ } catch {
257
+ }
258
+ }
259
+ function k(r) {
260
+ return r?.textContent?.trim().replace(/^<!\[CDATA\[|\]\]>$/g, "").trim() || null;
261
+ }
262
+ class lt {
263
+ constructor(t, e) {
264
+ this.host = t, this.playedRolls = /* @__PURE__ */ new Set(), this.sourcesSeen = 0, this.activeBreak = null, this.layer = null, this.adVideo = null, this.destroyed = !1, this.opts = {
265
+ skipDelay: 5,
266
+ maxWrapperDepth: 3,
267
+ requestTimeout: 8e3,
268
+ mediaTimeout: 1e4,
269
+ playOn: "every",
270
+ ...e
271
+ };
272
+ }
273
+ /**
274
+ * Create (once) and "bless" the ad <video> synchronously inside the user
275
+ * gesture that started playback. A bare `load()` during the gesture lets
276
+ * the later `play()` succeed even though VAST fetches happen in between —
277
+ * the same trick every ad SDK uses for iOS/strict autoplay policies.
278
+ */
279
+ ensureAdVideo() {
280
+ if (!this.adVideo) {
281
+ this.adVideo = l("video", "imp-ad__video", { playsinline: "" });
282
+ try {
283
+ this.adVideo.load();
284
+ } catch {
285
+ }
286
+ }
287
+ return this.adVideo;
288
+ }
289
+ get adPlaying() {
290
+ return this.activeBreak !== null;
291
+ }
292
+ /** True while a pre-roll is still due for the current source. */
293
+ get hasPendingPreRoll() {
294
+ return this.pending("preRoll").length > 0;
295
+ }
296
+ /** Called on every source change. Honors the `playOn` frequency setting. */
297
+ resetForNewSource() {
298
+ if (this.sourcesSeen++, this.opts.playOn === "first" && this.sourcesSeen > 1) {
299
+ for (const t of this.opts.adList) this.playedRolls.add(t);
300
+ return;
301
+ }
302
+ this.playedRolls.clear();
303
+ }
304
+ async playPreRoll() {
305
+ await this.playRolls(this.pending("preRoll"));
306
+ }
307
+ async playPostRoll() {
308
+ await this.playRolls(this.pending("postRoll"));
309
+ }
310
+ /** Called from timeupdate; fires due mid-rolls. */
311
+ checkMidRolls(t) {
312
+ if (this.adPlaying) return;
313
+ const e = this.pending("midRoll").filter((i) => i.timer !== void 0 && t >= i.timer);
314
+ e.length > 0 && this.playRolls(e);
315
+ }
316
+ pending(t) {
317
+ return this.opts.adList.filter((e) => e.roll === t && !this.playedRolls.has(e));
318
+ }
319
+ async playRolls(t) {
320
+ if (t.length === 0 || this.destroyed) return;
321
+ if (this.activeBreak) return this.activeBreak;
322
+ this.ensureAdVideo();
323
+ const e = (async () => {
324
+ const i = this.host.contentVideo, s = !i.paused && !i.ended;
325
+ i.pause();
326
+ for (const n of t) {
327
+ this.playedRolls.add(n);
328
+ try {
329
+ const o = await st(n, this.opts);
330
+ await this.playAd(o);
331
+ } catch (o) {
332
+ const a = o instanceof Error ? o : new Error(String(o));
333
+ this.host.emitter.emit("aderror", { roll: n, error: a });
334
+ }
335
+ if (this.destroyed) return;
336
+ }
337
+ (s || t[0].roll === "preRoll") && i.play().catch(() => {
338
+ });
339
+ })();
340
+ return this.activeBreak = e.finally(() => {
341
+ this.activeBreak = null;
342
+ }), this.activeBreak;
343
+ }
344
+ playAd(t) {
345
+ return new Promise((e) => {
346
+ const { labels: i } = this.host, s = l("div", "imp-ad"), n = this.ensureAdVideo(), o = new AbortController(), a = o.signal;
347
+ n.src = t.mediaUrl, n.muted = this.host.contentVideo.muted, n.volume = this.host.contentVideo.volume;
348
+ const h = l("div", "imp-spinner imp-ad__spinner"), c = l("div", "imp-ad__hud"), u = l("span", "imp-ad__badge");
349
+ u.textContent = t.adTitle ? `${i.adLabel} · ${t.adTitle}` : i.adLabel;
350
+ const f = l("span", "imp-ad__countdown");
351
+ c.append(u, f);
352
+ const d = l("div", "imp-ad__actions");
353
+ let b = null;
354
+ t.clickThrough && (b = l("button", "imp-ad__visit", { type: "button" }), b.textContent = i.visitAdvertiser, d.append(b));
355
+ const v = l("button", "imp-ad__skip", { type: "button" });
356
+ v.hidden = !0, d.append(v), s.append(n, h, c, d), this.host.container.append(s), this.layer = s;
357
+ const H = t.skipOffset ?? this.opts.skipDelay, N = /* @__PURE__ */ new Set(), L = (g) => {
358
+ N.has(g) || (N.add(g), C(t.tracking[g]));
359
+ };
360
+ let P = Date.now(), Z = -1;
361
+ const X = setInterval(() => {
362
+ if (n.paused) {
363
+ P = Date.now();
364
+ return;
365
+ }
366
+ if (n.currentTime !== Z) {
367
+ Z = n.currentTime, P = Date.now();
368
+ return;
369
+ }
370
+ Date.now() - P >= this.opts.mediaTimeout && (this.host.emitter.emit("aderror", {
371
+ roll: { roll: t.roll },
372
+ error: new Error(`Ad media stalled for ${this.opts.mediaTimeout} ms — skipping`)
373
+ }), A());
374
+ }, 500), A = () => {
375
+ clearInterval(X), o.abort(), n.pause(), n.removeAttribute("src"), n.load(), s.remove(), this.layer = null, e();
376
+ }, O = (g) => {
377
+ g ? (L("skip"), this.host.emitter.emit("adskip", { ad: t })) : L("complete"), this.host.emitter.emit("adend", { ad: t }), A();
378
+ };
379
+ n.addEventListener("playing", () => {
380
+ C(t.impressions), L("start"), this.host.emitter.emit("adstart", { ad: t });
381
+ }, { once: !0, signal: a }), n.addEventListener("playing", () => {
382
+ h.hidden = !0;
383
+ }, { signal: a }), n.addEventListener("waiting", () => {
384
+ h.hidden = !1;
385
+ }, { signal: a }), n.addEventListener("timeupdate", () => {
386
+ const g = n.currentTime, x = n.duration;
387
+ if (Number.isFinite(x) && x > 0 && (f.textContent = w(Math.max(0, x - g)), g / x >= 0.25 && L("firstQuartile"), g / x >= 0.5 && L("midpoint"), g / x >= 0.75 && L("thirdQuartile")), H >= 0) {
388
+ const z = Math.ceil(H - g);
389
+ z > 0 ? (v.hidden = !1, v.disabled = !0, v.textContent = `${i.skipAdIn} ${z}`) : (v.hidden = !1, v.disabled = !1, v.textContent = i.skipAd);
390
+ }
391
+ }, { signal: a }), n.addEventListener("ended", () => O(!1), { signal: a }), n.addEventListener("error", () => {
392
+ this.host.emitter.emit("aderror", {
393
+ roll: { roll: t.roll },
394
+ error: new Error("Ad media failed to play")
395
+ }), A();
396
+ }, { signal: a }), v.addEventListener("click", () => O(!0));
397
+ const D = () => {
398
+ t.clickThrough && (L("click"), this.host.emitter.emit("adclick", { ad: t }), window.open(t.clickThrough, "_blank", "noopener"), n.pause());
399
+ };
400
+ n.addEventListener("click", D, { signal: a }), b?.addEventListener("click", D);
401
+ let W = !1, B = !1;
402
+ n.addEventListener("playing", () => {
403
+ W = !0;
404
+ }, { once: !0, signal: a }), n.addEventListener("pause", () => {
405
+ n.ended || !s.isConnected || (s.classList.add("imp-ad--paused"), W && !B && (B = !0, C(t.tracking.pause), this.host.emitter.emit("adpause", { ad: t })));
406
+ }, { signal: a }), n.addEventListener("play", () => {
407
+ s.classList.remove("imp-ad--paused"), B && (B = !1, C(t.tracking.resume), this.host.emitter.emit("adresume", { ad: t }));
408
+ }, { signal: a }), s.addEventListener("click", (g) => {
409
+ (g.target === s || s.classList.contains("imp-ad--paused")) && n.paused && n.play().catch(() => {
410
+ });
411
+ }), n.play().catch(() => {
412
+ s.classList.add("imp-ad--paused");
413
+ });
414
+ });
415
+ }
416
+ /** Skip-button icon access for potential theming. */
417
+ get closeIcon() {
418
+ return this.host.closeIcon;
419
+ }
420
+ destroy() {
421
+ this.destroyed = !0, this.adVideo?.pause(), this.layer?.remove(), this.layer = null, this.adVideo = null;
422
+ }
423
+ }
424
+ function at(r, t) {
425
+ const e = [...r].sort((s, n) => s.start - n.start), i = [];
426
+ for (let s = 0; s < e.length; s++) {
427
+ const n = Math.max(0, e[s].start);
428
+ if (Number.isFinite(t) && n >= t) continue;
429
+ let o = e[s].end ?? e[s + 1]?.start ?? t;
430
+ Number.isFinite(t) && (o = Math.min(o, t)), !(o <= n) && i.push({ start: n, end: o, title: e[s].title });
431
+ }
432
+ return i;
433
+ }
434
+ async function ht(r) {
435
+ const t = await fetch(r);
436
+ if (!t.ok) throw new Error(`Failed to load chapters VTT (${t.status})`);
437
+ const e = await t.text();
438
+ return U(e).map((i) => ({ start: i.start, end: i.end, title: i.text }));
439
+ }
440
+ function K(r, t) {
441
+ for (const e of r)
442
+ if (t >= e.start && t < e.end) return e;
443
+ return null;
444
+ }
445
+ function ct(r, t) {
446
+ return t ? /application\/(x-mpegurl|vnd\.apple\.mpegurl)/i.test(t) : /\.m3u8(\?|#|$)/i.test(r);
447
+ }
448
+ let S;
449
+ async function dt() {
450
+ if (S !== void 0) return S;
451
+ const r = globalThis.Hls;
452
+ if (r)
453
+ return S = r, S;
454
+ try {
455
+ S = (await import("hls.js")).default;
456
+ } catch {
457
+ S = null;
458
+ }
459
+ return S;
460
+ }
461
+ async function ut(r, t, e, i) {
462
+ if (ct(t, e)) {
463
+ const s = r.canPlayType("application/vnd.apple.mpegurl"), n = s ? null : await dt();
464
+ return n && n.isSupported() ? pt(n, r, t, i) : s ? (r.src = t, $(r)) : (i.onError("HLS is not supported in this browser and hls.js could not be loaded."), $(r));
465
+ }
466
+ return r.src = t, $(r);
467
+ }
468
+ function $(r) {
469
+ return {
470
+ kind: "native",
471
+ levels: [],
472
+ selected: -1,
473
+ setLevel() {
474
+ },
475
+ destroy() {
476
+ r.removeAttribute("src"), r.load();
477
+ }
478
+ };
479
+ }
480
+ function pt(r, t, e, i) {
481
+ const s = new r({ enableWorker: !0 }), n = {
482
+ kind: "hls",
483
+ levels: [],
484
+ selected: -1,
485
+ setLevel(h) {
486
+ n.selected = h, s.currentLevel = h;
487
+ },
488
+ destroy() {
489
+ s.destroy(), t.removeAttribute("src"), t.load();
490
+ }
491
+ };
492
+ s.on(r.Events.MANIFEST_PARSED, () => {
493
+ n.levels = s.levels.map((h, c) => ({
494
+ index: c,
495
+ label: h.height ? `${h.height}p` : `${Math.round(h.bitrate / 1e3)} kbps`
496
+ })).reverse(), i.onLevels(n.levels);
497
+ }), s.on(r.Events.LEVEL_SWITCHED, (h, c) => {
498
+ const u = s.levels[c.level];
499
+ u && i.onLevelSwitch(u.height ? `${u.height}p` : `${Math.round(u.bitrate / 1e3)} kbps`);
500
+ });
501
+ let o = 0, a = !1;
502
+ return s.on(r.Events.ERROR, (h, c) => {
503
+ if (c.fatal)
504
+ switch (c.type) {
505
+ case r.ErrorTypes.NETWORK_ERROR:
506
+ s.startLoad();
507
+ break;
508
+ case r.ErrorTypes.MEDIA_ERROR:
509
+ o < 3 ? (o++, s.recoverMediaError()) : (i.onError(`HLS media error: ${c.details}`, c), s.destroy());
510
+ break;
511
+ default:
512
+ if (a)
513
+ i.onError(`HLS fatal error: ${c.details}`, c), s.destroy();
514
+ else {
515
+ a = !0;
516
+ try {
517
+ s.loadSource(e), s.startLoad();
518
+ } catch {
519
+ i.onError(`HLS fatal error: ${c.details}`, c), s.destroy();
520
+ }
521
+ }
522
+ }
523
+ }), s.loadSource(e), s.attachMedia(t), n;
524
+ }
525
+ class q {
526
+ constructor(t) {
527
+ this.cues = t;
528
+ }
529
+ static async load(t) {
530
+ const e = await fetch(t);
531
+ if (!e.ok) throw new Error(`Failed to load thumbnails VTT (${e.status})`);
532
+ const i = await e.text(), s = [];
533
+ for (const n of U(i)) {
534
+ const [o, a] = n.text.trim().split("#");
535
+ if (!o) continue;
536
+ const h = {
537
+ start: n.start,
538
+ end: n.end,
539
+ src: G(o, t)
540
+ }, c = a?.match(/xywh=(\d+),(\d+),(\d+),(\d+)/);
541
+ c && (h.xywh = { x: Number(c[1]), y: Number(c[2]), w: Number(c[3]), h: Number(c[4]) }), s.push(h);
542
+ }
543
+ return s.sort((n, o) => n.start - o.start), new q(s);
544
+ }
545
+ cueAt(t) {
546
+ let e = 0, i = this.cues.length - 1;
547
+ for (; e <= i; ) {
548
+ const s = e + i >> 1, n = this.cues[s];
549
+ if (t < n.start) i = s - 1;
550
+ else if (t >= n.end) e = s + 1;
551
+ else return n;
552
+ }
553
+ return null;
554
+ }
555
+ }
556
+ class M {
557
+ constructor(t) {
558
+ this.anchor = t, this.outsideListener = null, this.root = l("div", "imp-menu"), this.root.hidden = !0, this.backdrop = l("div", "imp-menu__backdrop"), this.backdrop.hidden = !0, this.backdrop.addEventListener("pointerdown", (e) => {
559
+ e.preventDefault(), this.close();
560
+ }), t.append(this.backdrop);
561
+ }
562
+ get open() {
563
+ return !this.root.hidden;
564
+ }
565
+ toggle(t) {
566
+ this.open ? this.close() : this.show(t);
567
+ }
568
+ show(t) {
569
+ this.root.textContent = "";
570
+ for (const e of t) {
571
+ if (e.items.length === 0) continue;
572
+ const i = l("div", "imp-menu__section");
573
+ if (e.title) {
574
+ const s = l("div", "imp-menu__title");
575
+ s.textContent = e.title, i.append(s);
576
+ }
577
+ for (const s of e.items) {
578
+ const n = l("button", "imp-menu__item", { type: "button", role: "menuitemradio" });
579
+ if (n.setAttribute("aria-checked", String(s.active)), s.active && n.classList.add("imp-menu__item--active"), s.icon) {
580
+ const a = l("span", "imp-menu__icon");
581
+ s.icon.trimStart().startsWith("<svg") ? a.innerHTML = s.icon : a.append(l("img", "", { src: s.icon, alt: "" })), n.append(a);
582
+ }
583
+ const o = l("span", "imp-menu__label");
584
+ o.textContent = s.label, n.append(o), n.addEventListener("click", () => {
585
+ e.onSelect(s.value), this.close();
586
+ }), i.append(n);
587
+ }
588
+ this.root.append(i);
589
+ }
590
+ this.root.hidden = !1, this.backdrop.hidden = !1, this.outsideListener = (e) => {
591
+ const i = e.target;
592
+ !this.root.contains(i) && !this.anchor.contains(i) && this.close();
593
+ }, setTimeout(() => {
594
+ this.outsideListener && document.addEventListener("pointerdown", this.outsideListener);
595
+ }, 0);
596
+ }
597
+ close() {
598
+ this.root.hidden = !0, this.backdrop.hidden = !0, this.outsideListener && (document.removeEventListener("pointerdown", this.outsideListener), this.outsideListener = null);
599
+ }
600
+ destroy() {
601
+ this.close(), this.root.remove();
602
+ }
603
+ }
604
+ class mt {
605
+ constructor(t) {
606
+ this.cb = t, this.segments = [], this.duration = 0, this.chapters = [], this.thumbnails = null, this.scrubbing = !1, this.root = l("div", "imp-progress", { role: "slider", "aria-label": "Seek", tabindex: "-1" }), this.buffered = l("div", "imp-progress__buffered"), this.track = l("div", "imp-progress__track"), this.handle = l("div", "imp-progress__handle"), this.tooltipThumb = l("div", "imp-progress__thumb"), this.tooltipChapter = l("div", "imp-progress__tooltip-chapter"), this.tooltipTime = l("div", "imp-progress__tooltip-time"), this.tooltip = l("div", "imp-progress__tooltip"), this.tooltip.append(this.tooltipThumb, this.tooltipChapter, this.tooltipTime), this.heatmap = l("div", "imp-progress__heatmap"), this.heatmap.hidden = !0, this.root.append(this.heatmap, this.buffered, this.track, this.handle, this.tooltip), this.setChapters([]), this.bindPointer();
607
+ }
608
+ /** Render the popularity curve (empty array hides it). */
609
+ setHeatmap(t) {
610
+ if (t.length === 0) {
611
+ this.heatmap.hidden = !0, this.heatmap.textContent = "";
612
+ return;
613
+ }
614
+ this.heatmap.innerHTML = `<svg viewBox="0 0 1000 100" preserveAspectRatio="none" aria-hidden="true"><path d="${it(t)}"/></svg>`, this.heatmap.hidden = !1;
615
+ }
616
+ setDuration(t) {
617
+ this.duration = Number.isFinite(t) ? t : 0, this.root.classList.toggle("imp-progress--live", !Number.isFinite(t)), this.chapters.length === 0 && this.setChapters([]);
618
+ }
619
+ setChapters(t) {
620
+ this.chapters = t, this.track.textContent = "", this.segments = [];
621
+ const e = t.length > 0 ? t : [{ start: 0, end: this.duration || 1, title: "" }], i = [];
622
+ let s = 0;
623
+ for (const n of e)
624
+ n.start > s && i.push({ start: s, end: n.start, title: "" }), i.push(n), s = n.end;
625
+ this.duration > 0 && s < this.duration && i.push({ start: s, end: this.duration, title: "" });
626
+ for (const n of i) {
627
+ const o = l("div", "imp-progress__segment");
628
+ o.style.flexGrow = String(Math.max(n.end - n.start, 0.01));
629
+ const a = l("div", "imp-progress__fill");
630
+ o.append(a), this.track.append(o), this.segments.push({ chapter: n, root: o, fill: a });
631
+ }
632
+ }
633
+ setThumbnails(t) {
634
+ this.thumbnails = t;
635
+ }
636
+ update(t, e, i) {
637
+ if (e !== this.duration && Number.isFinite(e) && this.setDuration(e), this.scrubbing) return;
638
+ this.render(t);
639
+ const s = this.duration > 0 ? y(i / this.duration, 0, 1) : 0;
640
+ this.buffered.style.width = `${s * 100}%`, this.root.setAttribute("aria-valuemin", "0"), this.root.setAttribute("aria-valuemax", String(Math.round(this.duration))), this.root.setAttribute("aria-valuenow", String(Math.round(t))), this.root.setAttribute("aria-valuetext", `${w(t)} / ${w(this.duration)}`);
641
+ }
642
+ render(t) {
643
+ for (const { chapter: i, fill: s } of this.segments) {
644
+ const n = i.end - i.start, o = n > 0 ? y((t - i.start) / n, 0, 1) : 0;
645
+ s.style.transform = `scaleX(${o})`;
646
+ }
647
+ const e = this.duration > 0 ? y(t / this.duration, 0, 1) : 0;
648
+ this.handle.style.left = `${e * 100}%`;
649
+ }
650
+ timeFromEvent(t) {
651
+ const e = this.root.getBoundingClientRect();
652
+ return (e.width > 0 ? y((t.clientX - e.left) / e.width, 0, 1) : 0) * this.duration;
653
+ }
654
+ bindPointer() {
655
+ this.root.addEventListener("pointerdown", (t) => {
656
+ this.duration <= 0 || (t.preventDefault(), this.scrubbing = !0, this.root.classList.add("imp-progress--scrubbing"), this.root.setPointerCapture(t.pointerId), this.cb.onScrubStart(), this.render(this.timeFromEvent(t)), this.showTooltip(t));
657
+ }), this.root.addEventListener("pointermove", (t) => {
658
+ this.duration <= 0 || (this.showTooltip(t), this.scrubbing && this.render(this.timeFromEvent(t)));
659
+ }), this.root.addEventListener("pointerup", (t) => {
660
+ this.scrubbing && (this.scrubbing = !1, this.root.classList.remove("imp-progress--scrubbing"), this.cb.onSeek(this.timeFromEvent(t)), this.cb.onScrubEnd());
661
+ }), this.root.addEventListener("pointercancel", () => {
662
+ this.scrubbing = !1, this.root.classList.remove("imp-progress--scrubbing"), this.cb.onScrubEnd();
663
+ }), this.root.addEventListener("pointerleave", () => this.hideTooltip());
664
+ }
665
+ showTooltip(t) {
666
+ const e = this.timeFromEvent(t);
667
+ this.tooltipTime.textContent = w(e);
668
+ const i = K(this.chapters, e);
669
+ this.tooltipChapter.textContent = i?.title ?? "", this.tooltipChapter.hidden = !i?.title;
670
+ const s = this.thumbnails?.cueAt(e) ?? null;
671
+ s ? (this.tooltipThumb.hidden = !1, this.tooltipThumb.style.backgroundImage = `url("${s.src}")`, s.xywh ? (this.tooltipThumb.style.width = `${s.xywh.w}px`, this.tooltipThumb.style.height = `${s.xywh.h}px`, this.tooltipThumb.style.backgroundPosition = `-${s.xywh.x}px -${s.xywh.y}px`, this.tooltipThumb.style.backgroundSize = "auto") : (this.tooltipThumb.style.width = "160px", this.tooltipThumb.style.height = "90px", this.tooltipThumb.style.backgroundPosition = "center", this.tooltipThumb.style.backgroundSize = "cover")) : this.tooltipThumb.hidden = !0, this.tooltip.classList.add("imp-progress__tooltip--visible");
672
+ const n = this.root.getBoundingClientRect(), o = y(t.clientX - n.left, 0, n.width), a = this.tooltip.offsetWidth / 2;
673
+ this.tooltip.style.left = `${y(o, a, Math.max(a, n.width - a))}px`;
674
+ }
675
+ hideTooltip() {
676
+ this.tooltip.classList.remove("imp-progress__tooltip--visible");
677
+ }
678
+ }
679
+ const _ = class _ {
680
+ constructor(t) {
681
+ this.player = t, this.subtitlesBtn = null, this.settingsBtn = null, this.settingsMenu = null, this.subtitlesMenu = null, this.moreMenu = null, this.moreWrap = null, this.qualityBtn = null, this.qualityMenu = null, this.scenesBtn = null, this.likeBtn = null, this.dislikeBtn = null, this.likeCountEl = null, this.dislikeCountEl = null, this.prevBtn = null, this.nextBtn = null, this.seekBackBtn = null, this.seekFwdBtn = null, this.centerPlayBtn = null, this.centerPrevBtn = null, this.centerNextBtn = null, this.collapsibles = [], this.overflowed = /* @__PURE__ */ new Set(), this.resizeObserver = null, this.reflowScheduled = !1, this.actionItems = [], this.wasPlayingBeforeScrub = !1, this.disposers = [], this.onWindowResize = null;
682
+ const { labels: e, icons: i } = t, s = t.controlsOptions;
683
+ this.root = l("div", "imp-controls"), this.progress = new mt({
684
+ onSeek: (d) => t.seek(d),
685
+ onScrubStart: () => {
686
+ this.wasPlayingBeforeScrub = !t.paused, t.pause();
687
+ },
688
+ onScrubEnd: () => {
689
+ this.wasPlayingBeforeScrub && t.play();
690
+ }
691
+ }), s.progress && this.root.append(this.progress.root);
692
+ const n = l("div", "imp-controls__row");
693
+ this.row = n, this.root.append(n);
694
+ const o = l("div", "imp-controls__group"), a = l("div", "imp-controls__group");
695
+ this.rightGroup = a;
696
+ const h = l("div", "imp-controls__spacer");
697
+ this.chapterLabel = l("div", "imp-controls__chapter"), n.append(o, h, a), h.append(this.chapterLabel);
698
+ const c = typeof s.seekButtons == "object" ? s.seekButtons : {}, u = c.back ?? t.seekStep, f = c.forward ?? t.seekStep;
699
+ if (s.playlist && t.hasPlaylist && (this.prevBtn = m("imp-btn--prev", e.previous, i.previous), this.prevBtn.addEventListener("click", () => t.previous()), o.append(this.prevBtn)), s.seekButtons && (this.seekBackBtn = m("imp-btn--seek-back", `${e.seekBack} ${u}s`, i.seekBack), this.seekBackBtn.addEventListener("click", () => t.skip(-u)), o.append(this.seekBackBtn)), s.play && (this.playBtn = m("imp-btn--play", e.play, i.play), this.playBtn.addEventListener("click", () => t.togglePlay()), o.append(this.playBtn)), s.seekButtons && (this.seekFwdBtn = m("imp-btn--seek-forward", `${e.seekForward} ${f}s`, i.seekForward), this.seekFwdBtn.addEventListener("click", () => t.skip(f)), o.append(this.seekFwdBtn)), s.playlist && t.hasPlaylist && (this.nextBtn = m("imp-btn--next", e.next, i.next), this.nextBtn.addEventListener("click", () => t.next()), o.append(this.nextBtn)), s.volume) {
700
+ const d = l("div", "imp-volume");
701
+ this.muteBtn = m("imp-btn--mute", e.mute, i.volumeHigh), this.muteBtn.addEventListener("click", () => t.toggleMute()), this.volumeSlider = l("input", "imp-volume__slider", {
702
+ type: "range",
703
+ min: "0",
704
+ max: "1",
705
+ step: "0.05",
706
+ "aria-label": "Volume"
707
+ }), this.volumeSlider.addEventListener("input", () => {
708
+ t.setVolume(Number(this.volumeSlider.value));
709
+ }), d.append(this.muteBtn, this.volumeSlider), o.append(d);
710
+ }
711
+ if (s.time && (this.timeLabel = l("div", "imp-controls__time"), this.liveBadge = l("span", "imp-controls__live"), this.liveBadge.textContent = e.live, this.liveBadge.hidden = !0, o.append(this.timeLabel, this.liveBadge)), this.buildLikeDislike(a), s.subtitles) {
712
+ this.subtitlesBtn = m("imp-btn--subtitles", e.subtitles, i.subtitles), this.subtitlesMenu = new M(this.subtitlesBtn);
713
+ const d = l("div", "imp-controls__menu-anchor");
714
+ d.append(this.subtitlesBtn, this.subtitlesMenu.root), this.subtitlesBtn.addEventListener("click", () => this.toggleSubtitlesMenu()), a.append(d), this.registerCollapsible({
715
+ key: "subtitles",
716
+ el: d,
717
+ priority: 30,
718
+ available: () => t.subtitleTracks.length > 0,
719
+ section: () => this.subtitlesSection()
720
+ });
721
+ }
722
+ if (s.settings) {
723
+ this.settingsBtn = m("imp-btn--settings", e.settings, i.speed), this.settingsMenu = new M(this.settingsBtn);
724
+ const d = l("div", "imp-controls__menu-anchor");
725
+ d.append(this.settingsBtn, this.settingsMenu.root), this.settingsBtn.addEventListener("click", () => this.toggleSettingsMenu()), a.append(d), this.registerCollapsible({
726
+ key: "settings",
727
+ el: d,
728
+ priority: 40,
729
+ available: () => !0,
730
+ section: () => this.speedSection()
731
+ });
732
+ }
733
+ if (s.quality) {
734
+ this.qualityBtn = m("imp-btn--quality", e.quality, i.settings), this.qualityMenu = new M(this.qualityBtn);
735
+ const d = l("div", "imp-controls__menu-anchor");
736
+ d.append(this.qualityBtn, this.qualityMenu.root), this.qualityBtn.addEventListener("click", () => this.toggleQualityMenu()), a.append(d), this.registerCollapsible({
737
+ key: "quality",
738
+ el: d,
739
+ priority: 10,
740
+ available: () => t.qualityLevels.length > 0,
741
+ section: () => this.qualitySection()
742
+ });
743
+ }
744
+ if (s.scenes && (this.scenesBtn = m("imp-btn--scenes", e.scenes, i.scenes), this.scenesBtn.addEventListener("click", () => t.toggleScenesPanel()), a.append(this.scenesBtn), this.registerCollapsible({
745
+ key: "scenes",
746
+ el: this.scenesBtn,
747
+ priority: 25,
748
+ available: () => t.chapterList.length > 0,
749
+ section: () => this.simpleSection("scenes", e.scenes, i.scenes, () => t.toggleScenesPanel())
750
+ })), s.playlist && t.hasPlaylist) {
751
+ const d = m("imp-btn--list", e.playlist, i.list);
752
+ d.addEventListener("click", () => t.togglePlaylistPanel()), a.append(d), this.registerCollapsible({
753
+ key: "list",
754
+ el: d,
755
+ priority: 50,
756
+ available: () => !0,
757
+ section: () => this.simpleSection("list", e.playlist, i.list, () => t.togglePlaylistPanel())
758
+ });
759
+ }
760
+ if (s.pip && "requestPictureInPicture" in HTMLVideoElement.prototype) {
761
+ const d = m("imp-btn--pip", e.pip, i.pip);
762
+ d.addEventListener("click", () => void t.togglePip()), a.append(d), this.registerCollapsible({
763
+ key: "pip",
764
+ el: d,
765
+ priority: 20,
766
+ available: () => !0,
767
+ section: () => this.simpleSection("pip", e.pip, i.pip, () => void t.togglePip())
768
+ });
769
+ }
770
+ if (s.fullscreen && (this.fullscreenBtn = m("imp-btn--fullscreen", e.fullscreen, i.fullscreen), this.fullscreenBtn.addEventListener("click", () => void t.toggleFullscreen()), a.append(this.fullscreenBtn)), this.prevBtn && this.registerCollapsible({
771
+ key: "prev",
772
+ el: this.prevBtn,
773
+ priority: 70,
774
+ available: () => !0,
775
+ section: () => t.hasPrevious ? this.simpleSection("prev", e.previous, i.previous, () => t.previous()) : null
776
+ }), this.nextBtn && this.registerCollapsible({
777
+ key: "next",
778
+ el: this.nextBtn,
779
+ priority: 72,
780
+ available: () => !0,
781
+ section: () => t.hasNext ? this.simpleSection("next", e.next, i.next, () => t.next()) : null
782
+ }), this.seekBackBtn) {
783
+ const d = this.seekBackBtn;
784
+ this.registerCollapsible({
785
+ key: "seekBack",
786
+ el: d,
787
+ priority: 80,
788
+ available: () => Number.isFinite(t.duration) && t.duration > 0,
789
+ section: () => this.simpleSection("seekBack", `${e.seekBack} ${u}s`, i.seekBack, () => t.skip(-u))
790
+ });
791
+ }
792
+ if (this.seekFwdBtn) {
793
+ const d = this.seekFwdBtn;
794
+ this.registerCollapsible({
795
+ key: "seekFwd",
796
+ el: d,
797
+ priority: 82,
798
+ available: () => Number.isFinite(t.duration) && t.duration > 0,
799
+ section: () => this.simpleSection("seekFwd", `${e.seekForward} ${f}s`, i.seekForward, () => t.skip(f))
800
+ });
801
+ }
802
+ if (this.center = l("div", "imp-center-controls"), s.playlist && t.hasPlaylist && (this.centerPrevBtn = this.makeCenterButton("prev", e.previous, i.previous), this.centerPrevBtn.addEventListener("click", () => t.previous()), this.center.append(this.centerPrevBtn)), s.seekButtons) {
803
+ const d = this.makeCenterButton("seek-back", `${e.seekBack} ${u}s`, i.seekBack);
804
+ d.addEventListener("click", () => t.skip(-u)), this.center.append(d);
805
+ }
806
+ if (s.play && (this.centerPlayBtn = this.makeCenterButton("play", e.play, i.play), this.centerPlayBtn.addEventListener("click", () => t.togglePlay()), this.center.append(this.centerPlayBtn)), s.seekButtons) {
807
+ const d = this.makeCenterButton("seek-forward", `${e.seekForward} ${f}s`, i.seekForward);
808
+ d.addEventListener("click", () => t.skip(f)), this.center.append(d);
809
+ }
810
+ s.playlist && t.hasPlaylist && (this.centerNextBtn = this.makeCenterButton("next", e.next, i.next), this.centerNextBtn.addEventListener("click", () => t.next()), this.center.append(this.centerNextBtn)), this.center.style.display = "none", this.buildMoreDropdown(a), this.bind(), this.syncVolume(), this.syncPlayState(), this.setLikeState(t.actionsOptions.likeState ?? null), typeof ResizeObserver < "u" && (this.resizeObserver = new ResizeObserver(() => this.scheduleReflow()), this.resizeObserver.observe(t.container)), this.onWindowResize = () => this.scheduleReflow(), window.addEventListener("resize", this.onWindowResize);
811
+ }
812
+ registerCollapsible(t) {
813
+ this.collapsibles.push(t);
814
+ }
815
+ /** Center-cluster button — deliberately NOT `.imp-btn` (own sizing/look). */
816
+ makeCenterButton(t, e, i) {
817
+ const s = l("button", `imp-center-btn imp-center-btn--${t}`, { type: "button", "aria-label": e, title: e });
818
+ return s.innerHTML = i, s;
819
+ }
820
+ /** like/dislike — visible buttons (collapsible), highlightable via `setLikeState`. */
821
+ buildLikeDislike(t) {
822
+ const e = this.player, i = e.actionsOptions, { labels: s, icons: n } = e;
823
+ if (i.like) {
824
+ this.likeBtn = m("imp-btn--like", s.like, n.like), this.likeBtn.addEventListener("click", () => e.emit("action", { id: "like" })), this.likeCountEl = this.attachCountTooltip(this.likeBtn, i.likeCount);
825
+ const o = this.wrapWithTooltip(this.likeBtn, this.likeCountEl);
826
+ t.append(o), this.registerCollapsible({
827
+ key: "like",
828
+ el: o,
829
+ priority: 62,
830
+ available: () => !0,
831
+ section: () => this.simpleSection("like", s.like, n.like, () => e.emit("action", { id: "like" }))
832
+ });
833
+ }
834
+ if (i.dislike) {
835
+ this.dislikeBtn = m("imp-btn--dislike", s.dislike, n.dislike), this.dislikeBtn.addEventListener("click", () => e.emit("action", { id: "dislike" })), this.dislikeCountEl = this.attachCountTooltip(this.dislikeBtn, i.dislikeCount);
836
+ const o = this.wrapWithTooltip(this.dislikeBtn, this.dislikeCountEl);
837
+ t.append(o), this.registerCollapsible({
838
+ key: "dislike",
839
+ el: o,
840
+ priority: 60,
841
+ available: () => !0,
842
+ section: () => this.simpleSection("dislike", s.dislike, n.dislike, () => e.emit("action", { id: "dislike" }))
843
+ });
844
+ }
845
+ }
846
+ attachCountTooltip(t, e) {
847
+ const i = l("span", "imp-count-tooltip");
848
+ return this.setCountText(i, e), i;
849
+ }
850
+ wrapWithTooltip(t, e) {
851
+ const i = l("div", "imp-action");
852
+ return i.append(t, e), i;
853
+ }
854
+ setCountText(t, e) {
855
+ const i = e == null || e === "" ? "" : String(e);
856
+ t.textContent = i, t.hidden = i === "";
857
+ }
858
+ setLikeCounts(t, e) {
859
+ this.likeCountEl && t !== void 0 && this.setCountText(this.likeCountEl, t), this.dislikeCountEl && e !== void 0 && this.setCountText(this.dislikeCountEl, e);
860
+ }
861
+ setLikeState(t) {
862
+ this.likeBtn?.classList.toggle("imp-btn--active", t === "like"), this.dislikeBtn?.classList.toggle("imp-btn--active", t === "dislike");
863
+ }
864
+ // === ⋯ menu ===========================================================
865
+ buildMoreDropdown(t) {
866
+ const e = this.player, i = e.actionsOptions, { labels: s, icons: n } = e;
867
+ i.addTo && this.actionItems.push({ value: "addTo", label: s.addTo, icon: n.addTo, run: () => e.emit("action", { id: "addTo" }) }), i.share && this.actionItems.push({ value: "share", label: s.share, icon: n.share, run: () => void e.share() }), i.report && this.actionItems.push({ value: "report", label: s.report, icon: n.report, run: () => e.emit("action", { id: "report" }) });
868
+ for (const h of i.custom ?? [])
869
+ this.actionItems.push({ value: `custom:${h.id}`, label: h.title, icon: h.icon, run: () => e.emit("customaction", { id: h.id }) });
870
+ const o = m("imp-btn--more", s.more, n.more);
871
+ this.moreMenu = new M(o);
872
+ const a = l("div", "imp-controls__menu-anchor imp-controls__more");
873
+ a.append(o, this.moreMenu.root), o.addEventListener("click", () => {
874
+ this.closeMenus(this.moreMenu), this.moreMenu?.toggle(this.buildMoreSections());
875
+ }), t.append(a), this.moreWrap = a;
876
+ }
877
+ /** ⋯ menu = overflowed controls (in display order) + consumer actions. */
878
+ buildMoreSections() {
879
+ const t = [], e = /* @__PURE__ */ new Map(), i = [], s = ["prev", "next", "seekBack", "seekFwd", "like", "dislike", "scenes", "pip", "list", "subtitles", "settings", "quality"], n = this.collapsibles.filter((o) => this.overflowed.has(o.key) && o.available()).sort((o, a) => s.indexOf(o.key) - s.indexOf(a.key));
880
+ for (const o of n) {
881
+ const a = o.section();
882
+ if (a)
883
+ if (!a.title && a.items.length === 1) {
884
+ const h = a.items[0];
885
+ i.push(h), e.set(h.value, () => a.onSelect(h.value));
886
+ } else
887
+ t.push(a);
888
+ }
889
+ if (i.length > 0 && t.unshift({ title: "", items: i, onSelect: (o) => e.get(o)?.() }), this.actionItems.length > 0) {
890
+ const o = new Map(this.actionItems.map((a) => [a.value, a.run]));
891
+ t.push({
892
+ title: "",
893
+ items: this.actionItems.map(({ value: a, label: h, icon: c }) => ({ value: a, label: h, icon: c, active: !1 })),
894
+ onSelect: (a) => o.get(a)?.()
895
+ });
896
+ }
897
+ return t;
898
+ }
899
+ simpleSection(t, e, i, s) {
900
+ return { title: "", items: [{ value: t, label: e, icon: i, active: !1 }], onSelect: () => s() };
901
+ }
902
+ speedSection() {
903
+ const t = this.player;
904
+ return {
905
+ title: t.labels.speed,
906
+ items: t.playbackRates.map((e) => ({
907
+ label: e === 1 ? "1× (normal)" : `${e}×`,
908
+ value: String(e),
909
+ active: t.playbackRate === e
910
+ })),
911
+ onSelect: (e) => t.setPlaybackRate(Number(e))
912
+ };
913
+ }
914
+ qualitySection() {
915
+ const t = this.player;
916
+ return {
917
+ title: t.labels.quality,
918
+ items: [
919
+ ...t.qualityAutoAvailable ? [{ label: t.labels.qualityAuto, value: "-1", active: t.currentQuality === -1 }] : [],
920
+ ...t.qualityLevels.map((e) => ({ label: e.label, value: String(e.index), active: t.currentQuality === e.index }))
921
+ ],
922
+ onSelect: (e) => t.setQuality(Number(e))
923
+ };
924
+ }
925
+ subtitlesSection() {
926
+ const t = this.player;
927
+ return {
928
+ title: t.labels.subtitles,
929
+ items: [
930
+ { label: t.labels.subtitlesOff, value: "-1", active: t.activeSubtitle === -1 },
931
+ ...t.subtitleTracks.map((e, i) => ({ label: e.label, value: String(i), active: t.activeSubtitle === i }))
932
+ ],
933
+ onSelect: (e) => t.setSubtitle(Number(e))
934
+ };
935
+ }
936
+ // === dynamic overflow =================================================
937
+ scheduleReflow() {
938
+ this.reflowScheduled || (this.reflowScheduled = !0, requestAnimationFrame(() => {
939
+ this.reflowScheduled = !1, this.reflow();
940
+ }));
941
+ }
942
+ /**
943
+ * Hide controls that don't fit (lowest priority first) and remember them so
944
+ * the ⋯ menu can offer them. Unavailable controls are hidden outright.
945
+ */
946
+ reflow() {
947
+ const t = window.innerWidth <= 767;
948
+ this.center.style.display = t ? "flex" : "none", this.playBtn && (this.playBtn.style.display = t ? "none" : ""), this.overflowed.clear();
949
+ for (const s of this.collapsibles) {
950
+ if (t && _.CENTER_KEYS.has(s.key)) {
951
+ s.el.style.display = "none";
952
+ continue;
953
+ }
954
+ const n = s.available();
955
+ s.el.style.display = n ? "" : "none", s.el.classList.remove("imp-collapsed");
956
+ }
957
+ const e = this.collapsibles.filter((s) => s.available() && !(t && _.CENTER_KEYS.has(s.key))).sort((s, n) => s.priority - n.priority);
958
+ let i = e.length;
959
+ for (; i-- > 0 && this.overflowsRow(); ) {
960
+ const s = e.find((n) => !this.overflowed.has(n.key));
961
+ if (!s) break;
962
+ s.el.style.display = "none", s.el.classList.add("imp-collapsed"), this.overflowed.add(s.key);
963
+ }
964
+ if (this.moreWrap) {
965
+ const s = this.actionItems.length > 0 || this.overflowed.size > 0;
966
+ this.moreWrap.style.display = s ? "" : "none";
967
+ }
968
+ }
969
+ overflowsRow() {
970
+ const t = this.row.getBoundingClientRect().right;
971
+ return this.rightGroup.getBoundingClientRect().right > t + 1;
972
+ }
973
+ bind() {
974
+ const t = this.player;
975
+ this.disposers.push(
976
+ t.on("timeupdate", ({ currentTime: e, duration: i }) => {
977
+ this.progress.update(e, i, t.bufferedEnd), this.timeLabel && (t.live ? (this.timeLabel.hidden = !0, this.liveBadge.hidden = !1) : (this.timeLabel.hidden = !1, this.liveBadge.hidden = !0, this.timeLabel.textContent = `${w(e)} / ${w(i)}`));
978
+ }),
979
+ t.on("play", () => this.syncPlayState()),
980
+ t.on("pause", () => this.syncPlayState()),
981
+ t.on("ended", () => this.syncPlayState()),
982
+ t.on("volumechange", () => this.syncVolume()),
983
+ t.on("chapterchange", ({ chapter: e }) => {
984
+ this.chapterLabel.textContent = e?.title ?? "";
985
+ }),
986
+ t.on("fullscreenchange", ({ active: e }) => {
987
+ this.fullscreenBtn && E(this.fullscreenBtn, e ? t.icons.fullscreenExit : t.icons.fullscreen, e ? t.labels.exitFullscreen : t.labels.fullscreen), this.scheduleReflow();
988
+ }),
989
+ t.on("playlistitemchange", () => {
990
+ this.syncPlaylistButtons(), this.scheduleReflow();
991
+ }),
992
+ t.on("sourcechange", () => {
993
+ this.syncPlaylistButtons(), this.scheduleReflow();
994
+ })
995
+ ), this.syncPlaylistButtons(), this.reflow();
996
+ }
997
+ syncPlayState() {
998
+ const t = this.player, [e, i] = t.ended ? [t.icons.replay, t.labels.replay] : t.paused ? [t.icons.play, t.labels.play] : [t.icons.pause, t.labels.pause];
999
+ this.playBtn && E(this.playBtn, e, i), this.centerPlayBtn && E(this.centerPlayBtn, e, i);
1000
+ }
1001
+ syncVolume() {
1002
+ if (!this.muteBtn) return;
1003
+ const t = this.player, e = t.muted ? 0 : t.volume, i = e === 0 ? t.icons.volumeMute : e < 0.5 ? t.icons.volumeLow : t.icons.volumeHigh;
1004
+ E(this.muteBtn, i, t.muted ? t.labels.unmute : t.labels.mute), this.volumeSlider.value = String(e), this.volumeSlider.style.setProperty("--imp-volume-fill", `${e * 100}%`);
1005
+ }
1006
+ /** Called by the player when per-source data (chapters/quality/subtitles) changes. */
1007
+ syncFeatureButtons() {
1008
+ this.scheduleReflow();
1009
+ }
1010
+ syncPlaylistButtons() {
1011
+ const t = this.player;
1012
+ this.prevBtn && (this.prevBtn.disabled = !t.hasPrevious), this.nextBtn && (this.nextBtn.disabled = !t.hasNext), this.centerPrevBtn && (this.centerPrevBtn.disabled = !t.hasPrevious), this.centerNextBtn && (this.centerNextBtn.disabled = !t.hasNext);
1013
+ }
1014
+ closeMenus(t) {
1015
+ for (const e of [this.settingsMenu, this.subtitlesMenu, this.qualityMenu, this.moreMenu])
1016
+ e && e !== t && e.close();
1017
+ }
1018
+ toggleSettingsMenu() {
1019
+ this.settingsMenu && (this.closeMenus(this.settingsMenu), this.settingsMenu.toggle([this.speedSection()]));
1020
+ }
1021
+ toggleQualityMenu() {
1022
+ !this.qualityMenu || this.player.qualityLevels.length === 0 || (this.closeMenus(this.qualityMenu), this.qualityMenu.toggle([this.qualitySection()]));
1023
+ }
1024
+ toggleSubtitlesMenu() {
1025
+ !this.subtitlesMenu || this.player.subtitleTracks.length === 0 || (this.closeMenus(this.subtitlesMenu), this.subtitlesMenu.toggle([this.subtitlesSection()]));
1026
+ }
1027
+ destroy() {
1028
+ for (const t of this.disposers) t();
1029
+ this.disposers = [], this.resizeObserver?.disconnect(), this.onWindowResize && window.removeEventListener("resize", this.onWindowResize), this.settingsMenu?.destroy(), this.subtitlesMenu?.destroy(), this.qualityMenu?.destroy(), this.moreMenu?.destroy(), this.root.remove();
1030
+ }
1031
+ };
1032
+ _.CENTER_KEYS = /* @__PURE__ */ new Set(["seekBack", "seekFwd", "prev", "next"]);
1033
+ let F = _;
1034
+ class ft {
1035
+ constructor(t) {
1036
+ this.root = l("div", "imp-poster"), this.image = l("div", "imp-poster__image");
1037
+ const e = m("imp-poster__play", t.labels.play, t.icons.bigPlay);
1038
+ e.addEventListener("click", () => void t.play()), this.root.append(this.image, e), this.root.addEventListener("click", (i) => {
1039
+ (i.target === this.root || i.target === this.image) && t.play();
1040
+ });
1041
+ }
1042
+ setSource(t) {
1043
+ this.image.style.backgroundImage = t?.poster ? `url("${t.poster}")` : "";
1044
+ }
1045
+ show() {
1046
+ this.root.hidden = !1;
1047
+ }
1048
+ hide() {
1049
+ this.root.hidden = !0;
1050
+ }
1051
+ }
1052
+ class vt {
1053
+ constructor(t) {
1054
+ this.labels = t, this.channelUrl = null, this.custom = null, this.root = l("div", "imp-pause-screen"), this.root.hidden = !0, this.defaultContent = l("div", "imp-pause-screen__default"), this.channel = l("button", "imp-channel", { type: "button" }), this.channel.hidden = !0, this.channelAvatar = l("div", "imp-channel__avatar"), this.channelName = l("div", "imp-channel__name"), this.channel.append(this.channelName, this.channelAvatar), this.channel.addEventListener("click", () => {
1055
+ this.channelUrl && window.open(this.channelUrl, "_blank", "noopener");
1056
+ });
1057
+ const e = l("div", "imp-pause-screen__heading");
1058
+ this.title = l("div", "imp-pause-screen__title"), this.description = l("div", "imp-pause-screen__description"), this.sponsor = l("a", "imp-sponsor", { target: "_blank", rel: "nofollow noopener" }), this.sponsor.hidden = !0, this.sponsorLabel = l("span", "imp-sponsor__label"), this.sponsorText = l("span", "imp-sponsor__text"), this.sponsor.append(this.sponsorLabel, this.sponsorText), this.sponsor.addEventListener("click", (i) => i.stopPropagation()), e.append(this.title, this.description, this.sponsor), this.defaultContent.append(e, this.channel), this.root.append(this.defaultContent);
1059
+ }
1060
+ setCustomContent(t) {
1061
+ this.custom?.remove(), this.custom = t, t && (t.classList.add("imp-pause-screen__custom"), this.root.append(t)), this.defaultContent.hidden = t !== null, this.root.classList.toggle("imp-pause-screen--custom", t !== null);
1062
+ }
1063
+ /** True while consumer-provided custom content is mounted. */
1064
+ get hasCustom() {
1065
+ return this.custom !== null;
1066
+ }
1067
+ setSource(t) {
1068
+ this.title.textContent = t?.title ?? "", this.description.textContent = t?.description ?? "";
1069
+ const e = t?.sponsor;
1070
+ this.sponsor.hidden = !e, e && (this.sponsor.href = e.url, this.sponsorText.textContent = e.text, this.sponsorLabel.textContent = e.label ?? this.labels.sponsored);
1071
+ const i = t?.channel;
1072
+ this.channel.hidden = !i, this.channelUrl = i?.url ?? null, this.channel.classList.toggle("imp-channel--link", !!i?.url), i && (this.channelName.textContent = i.name, this.channelAvatar.className = `imp-channel__avatar imp-channel__avatar--${i.avatarShape ?? "circle"}`, i.avatar ? (this.channelAvatar.textContent = "", this.channelAvatar.style.backgroundImage = `url("${i.avatar}")`) : (this.channelAvatar.style.backgroundImage = "", this.channelAvatar.textContent = (i.name.trim()[0] ?? "?").toUpperCase()));
1073
+ }
1074
+ show() {
1075
+ this.root.hidden = !1;
1076
+ }
1077
+ hide() {
1078
+ this.root.hidden = !0;
1079
+ }
1080
+ }
1081
+ class gt {
1082
+ constructor(t) {
1083
+ this.player = t, this.root = l("div", "imp-related"), this.root.hidden = !0, this.heading = l("div", "imp-related__title"), this.grid = l("div", "imp-related__grid");
1084
+ const e = m("imp-related__close", "Close", t.icons.close);
1085
+ e.addEventListener("click", () => this.hide()), this.root.append(e, this.heading, this.grid);
1086
+ }
1087
+ setOptions(t) {
1088
+ if (this.grid.textContent = "", !!t) {
1089
+ this.heading.textContent = t.title ?? this.player.labels.related;
1090
+ for (const e of t.items)
1091
+ this.grid.append(this.buildCard(e));
1092
+ }
1093
+ }
1094
+ buildCard(t) {
1095
+ const e = l("button", "imp-related__card", { type: "button" }), i = l("div", "imp-related__thumb");
1096
+ if (t.poster && (i.style.backgroundImage = `url("${t.poster}")`), t.duration) {
1097
+ const n = l("span", "imp-related__duration");
1098
+ n.textContent = t.duration, i.append(n);
1099
+ }
1100
+ const s = l("div", "imp-related__card-title");
1101
+ return s.textContent = t.title, e.append(i, s), e.addEventListener("click", () => {
1102
+ this.player.emit("relatedclick", { item: t }), this.hide(), t.source ? (this.player.load(t.source), this.player.play()) : t.url && window.open(t.url, "_blank", "noopener");
1103
+ }), e;
1104
+ }
1105
+ get visible() {
1106
+ return !this.root.hidden;
1107
+ }
1108
+ show() {
1109
+ this.grid.childElementCount !== 0 && (this.root.hidden = !1, this.player.emit("relatedshow", void 0));
1110
+ }
1111
+ hide() {
1112
+ this.root.hidden = !0;
1113
+ }
1114
+ }
1115
+ class yt {
1116
+ constructor(t, e = "sidebar", i) {
1117
+ this.player = t, this.root = l("div", `imp-playlist imp-playlist--${e}`), this.root.hidden = !0;
1118
+ const s = l("div", "imp-playlist__header"), n = l("div", "imp-playlist__heading");
1119
+ if (i) {
1120
+ const h = l("div", "imp-playlist__kicker");
1121
+ h.textContent = t.labels.playlist;
1122
+ const c = l("div", "imp-playlist__name");
1123
+ c.textContent = i, n.append(h, c);
1124
+ } else
1125
+ n.textContent = t.labels.playlist;
1126
+ const o = l("div", "imp-playlist__tools");
1127
+ this.shuffleBtn = m("imp-playlist__shuffle", t.labels.shuffle, t.icons.shuffle), this.shuffleBtn.addEventListener("click", () => {
1128
+ t.setShuffle(!t.shuffle), this.syncModes();
1129
+ }), this.repeatBtn = m("imp-playlist__repeat", t.labels.repeat, t.icons.repeat), this.repeatBtn.addEventListener("click", () => {
1130
+ t.setRepeat(!t.repeat), this.syncModes();
1131
+ });
1132
+ const a = m("imp-playlist__close", "Close", t.icons.close);
1133
+ a.addEventListener("click", () => this.hide()), o.append(this.shuffleBtn, this.repeatBtn, a), s.append(n, o), this.list = l("div", "imp-playlist__list"), this.root.append(s, this.list), this.rebuild(), this.syncModes();
1134
+ }
1135
+ syncModes() {
1136
+ this.shuffleBtn.classList.toggle("imp-btn--active", this.player.shuffle), this.repeatBtn.classList.toggle("imp-btn--active", this.player.repeat);
1137
+ }
1138
+ rebuild() {
1139
+ this.list.textContent = "", this.player.playlist.forEach((t, e) => {
1140
+ const i = l("button", "imp-playlist__item", { type: "button" });
1141
+ e === this.player.index && i.classList.add("imp-playlist__item--active");
1142
+ const s = l("div", "imp-playlist__thumb");
1143
+ t.poster && (s.style.backgroundImage = `url("${t.poster}")`);
1144
+ const n = l("div", "imp-playlist__meta"), o = l("div", "imp-playlist__title");
1145
+ if (o.textContent = t.title ?? `#${e + 1}`, n.append(o), t.duration) {
1146
+ const a = l("div", "imp-playlist__duration");
1147
+ a.textContent = w(t.duration), n.append(a);
1148
+ }
1149
+ i.append(s, n), i.addEventListener("click", () => {
1150
+ this.player.playItem(e);
1151
+ }), this.list.append(i);
1152
+ });
1153
+ }
1154
+ get visible() {
1155
+ return !this.root.hidden;
1156
+ }
1157
+ show() {
1158
+ this.rebuild(), this.root.hidden = !1;
1159
+ }
1160
+ hide() {
1161
+ this.root.hidden = !0;
1162
+ }
1163
+ toggle() {
1164
+ this.visible ? this.hide() : this.show();
1165
+ }
1166
+ }
1167
+ class bt {
1168
+ constructor(t, e = "bottom") {
1169
+ this.player = t, this.items = [], this.root = l("div", `imp-playlist imp-scenes imp-playlist--${e}`), this.root.hidden = !0;
1170
+ const i = l("div", "imp-playlist__header"), s = l("span");
1171
+ s.textContent = t.labels.scenes;
1172
+ const n = m("imp-playlist__close", "Close", t.icons.close);
1173
+ n.addEventListener("click", () => this.hide()), i.append(s, n), this.list = l("div", "imp-playlist__list"), this.root.append(i, this.list), t.on("chapterchange", () => this.syncActive());
1174
+ }
1175
+ /** Re-render from the player's current chapters + thumbnail track. */
1176
+ rebuild() {
1177
+ this.list.textContent = "", this.items = [];
1178
+ const t = this.player.thumbnails;
1179
+ for (const e of this.player.chapterList) {
1180
+ const i = l("button", "imp-playlist__item", { type: "button" }), s = l("div", "imp-playlist__thumb"), n = t?.cueAt(e.start + 0.01) ?? null;
1181
+ if (n?.xywh) {
1182
+ const c = l("div", "imp-scenes__sprite");
1183
+ c.style.width = `${n.xywh.w}px`, c.style.height = `${n.xywh.h}px`, c.style.backgroundImage = `url("${n.src}")`, c.style.backgroundPosition = `-${n.xywh.x}px -${n.xywh.y}px`, c.style.setProperty("--imp-sprite-w", String(n.xywh.w)), s.append(c);
1184
+ } else n && (s.style.backgroundImage = `url("${n.src}")`);
1185
+ const o = l("div", "imp-playlist__meta"), a = l("div", "imp-playlist__title");
1186
+ a.textContent = e.title || w(e.start);
1187
+ const h = l("div", "imp-playlist__duration");
1188
+ h.textContent = w(e.start), o.append(a, h), i.append(s, o), i.addEventListener("click", () => {
1189
+ this.player.seek(e.start), this.player.play();
1190
+ }), this.list.append(i), this.items.push(i);
1191
+ }
1192
+ this.syncActive();
1193
+ }
1194
+ syncActive() {
1195
+ const t = this.player.chapter, e = this.player.chapterList;
1196
+ this.items.forEach((i, s) => {
1197
+ i.classList.toggle("imp-playlist__item--active", t !== null && e[s] === t);
1198
+ });
1199
+ }
1200
+ get visible() {
1201
+ return !this.root.hidden;
1202
+ }
1203
+ show() {
1204
+ this.rebuild(), this.root.hidden = !1;
1205
+ }
1206
+ hide() {
1207
+ this.root.hidden = !0;
1208
+ }
1209
+ toggle() {
1210
+ this.visible ? this.hide() : this.show();
1211
+ }
1212
+ }
1213
+ function Q(r) {
1214
+ return r ? typeof r == "string" ? [{ src: r, label: "Subtitles" }] : Array.isArray(r) ? r : [r] : [];
1215
+ }
1216
+ const kt = {
1217
+ play: !0,
1218
+ progress: !0,
1219
+ time: !0,
1220
+ volume: !0,
1221
+ fullscreen: !0,
1222
+ pip: !0,
1223
+ settings: !0,
1224
+ quality: !1,
1225
+ scenes: !0,
1226
+ heatmap: !0,
1227
+ subtitles: !0,
1228
+ seekButtons: !0,
1229
+ playlist: !0,
1230
+ hideDelay: 2500
1231
+ };
1232
+ class _t {
1233
+ constructor(t, e = {}) {
1234
+ this.scrubbing = !1, this.emitter = new Y(), this.abort = new AbortController(), this.sources = [], this.currentIndex = -1, this.sourceController = null, this.loadToken = 0, this.chapters = [], this.currentChapter = null, this.progressiveQuality = -1, this.thumbTrack = null, this.shuffleMode = !1, this.adManager = null, this.playedOnce = !1, this.idleTimer = null, this.destroyed = !1;
1235
+ const i = typeof t == "string" ? document.querySelector(t) : t;
1236
+ if (!i) throw new Error(`[itube-player] mount target not found: ${String(t)}`);
1237
+ this.mount = i, this.options = e, this.labels = { ...I, ...tt(e.language), ...e.labels }, this.icons = { ...J, ...e.icons }, this.controlsOptions = { ...kt, ...e.controls }, this.actionsOptions = e.actions ?? {}, this.playbackRates = e.playbackRates ?? [0.5, 0.75, 1, 1.25, 1.5, 2], this.seekStep = e.seekStep ?? 10, this.playlistOptions = { title: "", autoAdvance: !0, loop: !1, shuffle: !1, startIndex: 0, layout: "sidebar", ...e.playlist }, this.shuffleMode = this.playlistOptions.shuffle, this.sources = e.source ? Array.isArray(e.source) ? [...e.source] : [e.source] : [], this.container = l("div", "imp-player", { tabindex: "0" }), e.className && this.container.classList.add(e.className), e.styling?.themeColor && this.container.style.setProperty("--imp-accent", e.styling.themeColor), e.styling?.likeColor && this.container.style.setProperty("--imp-like", e.styling.likeColor), e.styling?.dislikeColor && this.container.style.setProperty("--imp-dislike", e.styling.dislikeColor), this.video = l("video", "imp-video"), e.playsInline !== !1 && this.video.setAttribute("playsinline", ""), e.crossOrigin !== void 0 && (this.video.crossOrigin = e.crossOrigin), this.video.preload = "metadata", e.loop && (this.video.loop = !0), e.muted && (this.video.muted = !0), this.video.volume = y(e.volume ?? 1, 0, 1);
1238
+ const s = l("div", "imp-layer");
1239
+ this.spinner = l("div", "imp-spinner"), this.spinner.hidden = !0, this.errorBox = l("div", "imp-error"), this.errorBox.hidden = !0, this.pauseScreen = new vt(this.labels), this.poster = new ft(this), this.related = new gt(this), this.related.setOptions(e.related), this.controls = new F(this), this.playlistPanel = new yt(this, this.playlistOptions.layout, this.playlistOptions.title || void 0), this.scenesPanel = new bt(this, e.scenes?.layout ?? "bottom");
1240
+ const n = l("div", "imp-layer__middle");
1241
+ n.append(this.spinner, this.errorBox, this.controls.center);
1242
+ const o = l("div", "imp-layer__bottom");
1243
+ o.append(this.controls.root), s.append(this.pauseScreen.root, this.related.root, n, o), this.container.append(this.video, s, this.poster.root, this.playlistPanel.root, this.scenesPanel.root), this.mount.append(this.container), e.pauseScreen instanceof HTMLElement ? this.pauseScreen.setCustomContent(e.pauseScreen) : typeof e.pauseScreen == "function" && this.pauseScreen.setCustomContent(e.pauseScreen(this));
1244
+ const a = e.adConfig ?? e.ads;
1245
+ a && a.adList.length > 0 && (this.adManager = new lt(
1246
+ { container: this.container, contentVideo: this.video, emitter: this.emitter, labels: this.labels, closeIcon: this.icons.close },
1247
+ a
1248
+ )), this.emitter.on("error", ({ message: h }) => {
1249
+ this.errorBox.textContent = h, this.errorBox.hidden = !1, this.spinner.hidden = !0, this.poster.hide();
1250
+ }), this.bindVideoEvents(), this.bindIdleHide(), e.keyboard !== !1 && this.bindKeyboard(), this.video.addEventListener("click", () => {
1251
+ this.playedOnce && !this.adPlaying && this.togglePlay();
1252
+ }, { signal: this.abort.signal }), this.pauseScreen.root.addEventListener("click", (h) => {
1253
+ this.pauseScreen.hasCustom || h.target === this.pauseScreen.root && this.togglePlay();
1254
+ }, { signal: this.abort.signal }), this.sources.length > 0 && this.loadItem(y(this.playlistOptions.startIndex, 0, this.sources.length - 1), !!e.autoplay), this.emitter.emit("ready", { player: this });
1255
+ }
1256
+ // === events ===========================================================
1257
+ on(t, e) {
1258
+ return this.emitter.on(t, e);
1259
+ }
1260
+ once(t, e) {
1261
+ return this.emitter.once(t, e);
1262
+ }
1263
+ off(t, e) {
1264
+ this.emitter.off(t, e);
1265
+ }
1266
+ /** @internal — UI modules emit through the player. */
1267
+ emit(t, e) {
1268
+ this.emitter.emit(t, e);
1269
+ }
1270
+ // === playback =========================================================
1271
+ get paused() {
1272
+ return this.video.paused;
1273
+ }
1274
+ get ended() {
1275
+ return this.video.ended;
1276
+ }
1277
+ get currentTime() {
1278
+ return this.video.currentTime;
1279
+ }
1280
+ get duration() {
1281
+ return this.video.duration || 0;
1282
+ }
1283
+ get live() {
1284
+ return this.video.duration === 1 / 0;
1285
+ }
1286
+ get bufferedEnd() {
1287
+ const t = this.video.buffered;
1288
+ return t.length > 0 ? t.end(t.length - 1) : 0;
1289
+ }
1290
+ get adPlaying() {
1291
+ return this.adManager?.adPlaying ?? !1;
1292
+ }
1293
+ async play() {
1294
+ this.destroyed || this.adPlaying || this.adManager && (await this.adManager.playPreRoll(), this.destroyed || !this.video.paused) || await this.video.play();
1295
+ }
1296
+ pause() {
1297
+ this.adPlaying || this.video.pause();
1298
+ }
1299
+ togglePlay() {
1300
+ this.video.paused || this.video.ended ? this.play() : this.pause();
1301
+ }
1302
+ seek(t) {
1303
+ Number.isFinite(this.video.duration) && (this.video.currentTime = y(t, 0, this.video.duration));
1304
+ }
1305
+ /** Relative seek, e.g. `skip(-10)`. */
1306
+ skip(t) {
1307
+ this.seek(this.video.currentTime + t);
1308
+ }
1309
+ // === volume ===========================================================
1310
+ get volume() {
1311
+ return this.video.volume;
1312
+ }
1313
+ get muted() {
1314
+ return this.video.muted;
1315
+ }
1316
+ setVolume(t) {
1317
+ this.video.volume = y(t, 0, 1), t > 0 && (this.video.muted = !1);
1318
+ }
1319
+ setMuted(t) {
1320
+ this.video.muted = t;
1321
+ }
1322
+ toggleMute() {
1323
+ this.video.muted = !this.video.muted;
1324
+ }
1325
+ // === rate / quality ===================================================
1326
+ get playbackRate() {
1327
+ return this.video.playbackRate;
1328
+ }
1329
+ setPlaybackRate(t) {
1330
+ this.video.playbackRate = t, this.emitter.emit("ratechange", { rate: t });
1331
+ }
1332
+ get qualityLevels() {
1333
+ if (this.sourceController?.kind === "hls") return this.sourceController.levels;
1334
+ const t = this.source?.qualities;
1335
+ return t ? t.map((e, i) => ({ index: i, label: e.label ?? String(e.quality ?? i) })) : [];
1336
+ }
1337
+ get qualityAutoAvailable() {
1338
+ return this.sourceController?.kind === "hls";
1339
+ }
1340
+ get currentQuality() {
1341
+ return this.sourceController?.kind === "hls" ? this.sourceController.selected : this.progressiveQuality;
1342
+ }
1343
+ setQuality(t) {
1344
+ if (this.sourceController?.kind === "hls") {
1345
+ this.sourceController.setLevel(t);
1346
+ const n = t === -1 ? this.labels.qualityAuto : this.sourceController.levels.find((o) => o.index === t)?.label ?? String(t);
1347
+ this.emitter.emit("qualitychange", { label: n });
1348
+ return;
1349
+ }
1350
+ const e = this.source?.qualities;
1351
+ if (!e || !e[t] || t === this.progressiveQuality) return;
1352
+ const i = this.video.currentTime, s = !this.video.paused && !this.video.ended;
1353
+ this.progressiveQuality = t, this.video.src = e[t].src, this.video.currentTime = i, s && this.video.play().catch(() => {
1354
+ }), this.emitter.emit("qualitychange", { label: e[t].label ?? String(e[t].quality ?? t) });
1355
+ }
1356
+ // === subtitles ========================================================
1357
+ get subtitleTracks() {
1358
+ return Q(this.source?.subtitles);
1359
+ }
1360
+ get activeSubtitle() {
1361
+ const t = this.video.textTracks;
1362
+ for (let e = 0; e < t.length; e++)
1363
+ if (t[e].mode === "showing") return e;
1364
+ return -1;
1365
+ }
1366
+ setSubtitle(t) {
1367
+ const e = this.video.textTracks;
1368
+ for (let i = 0; i < e.length; i++)
1369
+ e[i].mode = i === t ? "showing" : "disabled";
1370
+ this.emitter.emit("subtitlechange", { track: this.subtitleTracks[t] ?? null });
1371
+ }
1372
+ // === playlist =========================================================
1373
+ get playlist() {
1374
+ return this.sources;
1375
+ }
1376
+ get index() {
1377
+ return this.currentIndex;
1378
+ }
1379
+ get source() {
1380
+ return this.sources[this.currentIndex] ?? null;
1381
+ }
1382
+ get hasPlaylist() {
1383
+ return this.sources.length > 1;
1384
+ }
1385
+ get hasNext() {
1386
+ return this.shuffleMode || this.playlistOptions.loop ? this.sources.length > 1 : this.currentIndex < this.sources.length - 1;
1387
+ }
1388
+ get hasPrevious() {
1389
+ return this.playlistOptions.loop ? this.sources.length > 1 : this.currentIndex > 0;
1390
+ }
1391
+ /** Shuffle mode: `next()` and auto-advance pick a random item. */
1392
+ get shuffle() {
1393
+ return this.shuffleMode;
1394
+ }
1395
+ setShuffle(t) {
1396
+ this.shuffleMode = t;
1397
+ }
1398
+ /** Repeat mode: after the last item the list starts over instead of ending. */
1399
+ get repeat() {
1400
+ return this.playlistOptions.loop;
1401
+ }
1402
+ setRepeat(t) {
1403
+ this.playlistOptions.loop = t;
1404
+ }
1405
+ next() {
1406
+ if (this.hasNext) {
1407
+ if (this.shuffleMode && this.sources.length > 1) {
1408
+ let t = this.currentIndex;
1409
+ for (; t === this.currentIndex; ) t = Math.floor(Math.random() * this.sources.length);
1410
+ this.playItem(t);
1411
+ return;
1412
+ }
1413
+ this.playItem((this.currentIndex + 1) % this.sources.length);
1414
+ }
1415
+ }
1416
+ previous() {
1417
+ this.hasPrevious && this.playItem((this.currentIndex - 1 + this.sources.length) % this.sources.length);
1418
+ }
1419
+ /** Jump to a playlist item and start playback. */
1420
+ playItem(t) {
1421
+ t < 0 || t >= this.sources.length || this.loadItem(t, !0).then(() => {
1422
+ this.emitter.emit("playlistitemchange", { source: this.sources[t], index: t });
1423
+ });
1424
+ }
1425
+ togglePlaylistPanel() {
1426
+ this.scenesPanel.hide(), this.playlistPanel.toggle();
1427
+ }
1428
+ toggleScenesPanel() {
1429
+ this.playlistPanel.hide(), this.scenesPanel.toggle();
1430
+ }
1431
+ /** Thumbnail track of the current source (used by the seek tooltip and the scenes panel). */
1432
+ get thumbnails() {
1433
+ return this.thumbTrack;
1434
+ }
1435
+ /**
1436
+ * Replace what the player is playing. A single source resets the playlist
1437
+ * to one item; an array installs a new playlist.
1438
+ */
1439
+ load(t, e = 0) {
1440
+ this.sources = Array.isArray(t) ? [...t] : [t], this.playlistPanel.rebuild(), this.loadItem(y(e, 0, this.sources.length - 1), !1);
1441
+ }
1442
+ // === fullscreen / pip =================================================
1443
+ get isFullscreen() {
1444
+ return document.fullscreenElement === this.container;
1445
+ }
1446
+ async toggleFullscreen() {
1447
+ this.isFullscreen ? await document.exitFullscreen() : this.container.requestFullscreen ? await this.container.requestFullscreen() : this.video.webkitEnterFullscreen?.();
1448
+ }
1449
+ async togglePip() {
1450
+ document.pictureInPictureElement === this.video ? await document.exitPictureInPicture() : await this.video.requestPictureInPicture();
1451
+ }
1452
+ // === chapters =========================================================
1453
+ /** Normalized chapters of the current source (after metadata is known). */
1454
+ get chapterList() {
1455
+ return this.chapters;
1456
+ }
1457
+ get chapter() {
1458
+ return this.currentChapter;
1459
+ }
1460
+ // === actions ==========================================================
1461
+ /**
1462
+ * Share the current video: native share sheet when the device has one,
1463
+ * otherwise the `action` event with `id: "share"` so the app can show
1464
+ * its own dialog.
1465
+ */
1466
+ async share() {
1467
+ const t = {
1468
+ title: this.source?.title ?? document.title,
1469
+ url: window.location.href
1470
+ };
1471
+ if (typeof navigator.share == "function") {
1472
+ try {
1473
+ await navigator.share(t);
1474
+ } catch {
1475
+ }
1476
+ return;
1477
+ }
1478
+ this.emitter.emit("action", { id: "share" });
1479
+ }
1480
+ /** Highlight the like or dislike button (`null` clears both). */
1481
+ setLikeState(t) {
1482
+ this.controls.setLikeState(t);
1483
+ }
1484
+ /** Update like/dislike counters shown as tooltips above the buttons (`undefined` leaves one untouched). */
1485
+ setLikeCounts(t, e) {
1486
+ this.controls.setLikeCounts(t, e);
1487
+ }
1488
+ // === pause screen =====================================================
1489
+ /** Inject custom pause-screen content (used by the Vue wrapper's slot). */
1490
+ setPauseScreenContent(t) {
1491
+ this.pauseScreen.setCustomContent(t);
1492
+ }
1493
+ // === teardown =========================================================
1494
+ destroy() {
1495
+ this.destroyed || (this.destroyed = !0, this.loadToken++, this.adManager?.destroy(), this.controls.destroy(), this.sourceController?.destroy(), this.abort.abort(), this.idleTimer && clearTimeout(this.idleTimer), this.container.remove(), this.emitter.emit("destroy", void 0), this.emitter.removeAll());
1496
+ }
1497
+ // === internals ========================================================
1498
+ async loadItem(t, e) {
1499
+ const i = ++this.loadToken, s = this.sources[t];
1500
+ if (!s) return;
1501
+ this.currentIndex = t, this.sourceController?.destroy(), this.sourceController = null, this.chapters = [], this.currentChapter = null, this.progressiveQuality = -1, this.playedOnce = !1;
1502
+ for (const h of [...this.video.querySelectorAll("track")]) h.remove();
1503
+ this.adManager?.resetForNewSource(), this.related.hide(), this.pauseScreen.hide(), this.scenesPanel.hide(), this.thumbTrack = null, this.errorBox.hidden = !0, this.video.poster = s.poster ?? "", this.poster.setSource(s), this.pauseScreen.setSource(s), this.controls.progress.setChapters([]), this.controls.progress.setThumbnails(null), this.controls.progress.setHeatmap([]), e ? this.poster.hide() : this.poster.show(), this.playlistPanel.rebuild();
1504
+ for (const h of Q(s.subtitles)) {
1505
+ const c = l("track", "", {
1506
+ kind: "subtitles",
1507
+ src: h.src,
1508
+ label: h.label,
1509
+ ...h.srclang ? { srclang: h.srclang } : {}
1510
+ });
1511
+ h.default && c.setAttribute("default", ""), this.video.append(c);
1512
+ }
1513
+ let n = s.src, o = s.type;
1514
+ if (s.qualities && s.qualities.length > 0) {
1515
+ const h = Math.max(0, s.qualities.findIndex((c) => c.src === s.src));
1516
+ this.progressiveQuality = h, n = s.qualities[h].src, o = s.qualities[h].type ?? o;
1517
+ }
1518
+ this.video.preload = this.adManager?.hasPendingPreRoll ? "auto" : "metadata";
1519
+ const a = await ut(this.video, n, o, {
1520
+ onLevels: () => {
1521
+ i === this.loadToken && this.controls.syncFeatureButtons();
1522
+ },
1523
+ onLevelSwitch: (h) => this.emitter.emit("qualitychange", { label: h }),
1524
+ onError: (h, c) => this.emitter.emit("error", { message: h, cause: c })
1525
+ });
1526
+ if (i !== this.loadToken) {
1527
+ a.destroy();
1528
+ return;
1529
+ }
1530
+ if (this.sourceController = a, this.emitter.emit("sourcechange", { source: s, index: t }), s.thumbnails && q.load(s.thumbnails).then((h) => {
1531
+ i === this.loadToken && (this.thumbTrack = h, this.controls.progress.setThumbnails(h), this.scenesPanel.visible && this.scenesPanel.rebuild());
1532
+ }).catch((h) => this.emitter.emit("error", { message: "Failed to load thumbnails track", cause: h })), s.chapters) {
1533
+ const h = (c) => {
1534
+ const u = () => {
1535
+ i === this.loadToken && (this.chapters = at(c, this.video.duration), this.controls.progress.setChapters(this.chapters), this.controls.syncFeatureButtons(), this.scenesPanel.visible && this.scenesPanel.rebuild());
1536
+ };
1537
+ Number.isFinite(this.video.duration) && this.video.duration > 0 ? u() : this.video.addEventListener("loadedmetadata", u, { once: !0, signal: this.abort.signal });
1538
+ };
1539
+ typeof s.chapters == "string" ? ht(s.chapters).then((c) => h(c)).catch((c) => this.emitter.emit("error", { message: "Failed to load chapters track", cause: c })) : h(s.chapters);
1540
+ }
1541
+ if (s.heatmap && s.heatmap.length > 0 && this.controlsOptions.heatmap) {
1542
+ const h = s.heatmap, c = () => {
1543
+ i === this.loadToken && this.controls.progress.setHeatmap(et(h, this.video.duration));
1544
+ };
1545
+ Number.isFinite(this.video.duration) && this.video.duration > 0 ? c() : this.video.addEventListener("loadedmetadata", c, { once: !0, signal: this.abort.signal });
1546
+ }
1547
+ e && this.play().catch(() => {
1548
+ i === this.loadToken && this.poster.show();
1549
+ });
1550
+ }
1551
+ bindVideoEvents() {
1552
+ const { signal: t } = this.abort, e = this.video;
1553
+ e.addEventListener("play", () => {
1554
+ this.playedOnce = !0, this.poster.hide(), this.pauseScreen.hide(), this.related.visible || this.related.hide(), this.related.hide(), this.emitter.emit("play", void 0), this.scheduleIdle();
1555
+ }, { signal: t }), e.addEventListener("pause", () => {
1556
+ this.emitter.emit("pause", void 0), this.showControlsNow(), !(this.video.ended || this.scrubbing || this.adPlaying || !this.playedOnce) && (this.options.pauseScreen !== !1 && this.pauseScreen.show(), this.options.related?.showOn?.includes("pause") && this.related.show());
1557
+ }, { signal: t }), e.addEventListener("ended", () => {
1558
+ this.handleEnded();
1559
+ }, { signal: t }), e.addEventListener("timeupdate", () => {
1560
+ this.emitter.emit("timeupdate", { currentTime: e.currentTime, duration: e.duration || 0 }), this.adManager?.checkMidRolls(e.currentTime);
1561
+ const i = K(this.chapters, e.currentTime);
1562
+ i !== this.currentChapter && (this.currentChapter = i, this.emitter.emit("chapterchange", { chapter: i }));
1563
+ }, { signal: t }), e.addEventListener("progress", () => {
1564
+ this.emitter.emit("progress", { buffered: this.bufferedEnd });
1565
+ }, { signal: t }), e.addEventListener("volumechange", () => {
1566
+ this.emitter.emit("volumechange", { volume: e.volume, muted: e.muted });
1567
+ }, { signal: t }), e.addEventListener("seeking", () => this.emitter.emit("seeking", { currentTime: e.currentTime }), { signal: t }), e.addEventListener("seeked", () => this.emitter.emit("seeked", { currentTime: e.currentTime }), { signal: t }), e.addEventListener("waiting", () => {
1568
+ this.spinner.hidden = !1;
1569
+ }, { signal: t });
1570
+ for (const i of ["playing", "canplay", "seeked"])
1571
+ e.addEventListener(i, () => {
1572
+ this.spinner.hidden = !0;
1573
+ }, { signal: t });
1574
+ e.addEventListener("error", () => {
1575
+ const i = e.error;
1576
+ i && this.sourceController?.kind !== "hls" && (e.getAttribute("src") !== null || e.currentSrc) && this.emitter.emit("error", { message: i.message || `Media error (code ${i.code})`, cause: i });
1577
+ }, { signal: t }), e.addEventListener("enterpictureinpicture", () => this.emitter.emit("pipchange", { active: !0 }), { signal: t }), e.addEventListener("leavepictureinpicture", () => this.emitter.emit("pipchange", { active: !1 }), { signal: t }), document.addEventListener("fullscreenchange", () => {
1578
+ this.emitter.emit("fullscreenchange", { active: this.isFullscreen }), this.container.classList.toggle("imp-player--fullscreen", this.isFullscreen);
1579
+ }, { signal: t });
1580
+ }
1581
+ async handleEnded() {
1582
+ if (this.emitter.emit("ended", void 0), this.showControlsNow(), !(this.adManager && (await this.adManager.playPostRoll(), this.destroyed))) {
1583
+ if (this.playlistOptions.autoAdvance && this.hasNext) {
1584
+ this.next();
1585
+ return;
1586
+ }
1587
+ (this.options.related?.showOn?.includes("ended") ?? !!this.options.related) && this.related.show();
1588
+ }
1589
+ }
1590
+ // === controls visibility ==============================================
1591
+ bindIdleHide() {
1592
+ const { signal: t } = this.abort, e = () => {
1593
+ this.showControlsNow(), this.scheduleIdle();
1594
+ };
1595
+ this.container.addEventListener("pointermove", e, { signal: t }), this.container.addEventListener("pointerdown", e, { signal: t }), this.container.addEventListener("keydown", e, { signal: t }), this.container.addEventListener("pointerleave", () => {
1596
+ this.video.paused || this.container.classList.add("imp-player--idle");
1597
+ }, { signal: t });
1598
+ }
1599
+ showControlsNow() {
1600
+ this.container.classList.remove("imp-player--idle");
1601
+ }
1602
+ scheduleIdle() {
1603
+ this.idleTimer && clearTimeout(this.idleTimer), this.idleTimer = setTimeout(() => {
1604
+ !this.video.paused && !this.destroyed && this.container.classList.add("imp-player--idle");
1605
+ }, this.controlsOptions.hideDelay);
1606
+ }
1607
+ // === keyboard =========================================================
1608
+ bindKeyboard() {
1609
+ this.container.addEventListener("keydown", (t) => {
1610
+ const e = t.target;
1611
+ if (e.closest("input, select, textarea, [contenteditable]")) return;
1612
+ const s = e.closest("button") !== null;
1613
+ switch (t.key) {
1614
+ case " ":
1615
+ case "k":
1616
+ if (t.key === " " && s) return;
1617
+ t.preventDefault(), this.togglePlay();
1618
+ break;
1619
+ case "ArrowLeft":
1620
+ t.preventDefault(), this.skip(-this.seekStep);
1621
+ break;
1622
+ case "ArrowRight":
1623
+ t.preventDefault(), this.skip(this.seekStep);
1624
+ break;
1625
+ case "ArrowUp":
1626
+ t.preventDefault(), this.setVolume(this.volume + 0.1);
1627
+ break;
1628
+ case "ArrowDown":
1629
+ t.preventDefault(), this.setVolume(this.volume - 0.1);
1630
+ break;
1631
+ case "m":
1632
+ this.toggleMute();
1633
+ break;
1634
+ case "f":
1635
+ this.toggleFullscreen();
1636
+ break;
1637
+ case "Home":
1638
+ this.seek(0);
1639
+ break;
1640
+ case "End":
1641
+ this.seek(this.duration);
1642
+ break;
1643
+ default:
1644
+ /^[0-9]$/.test(t.key) && Number.isFinite(this.video.duration) && this.seek(Number(t.key) / 10 * this.video.duration);
1645
+ }
1646
+ }, { signal: this.abort.signal });
1647
+ }
1648
+ }
1649
+ export {
1650
+ Y as Emitter,
1651
+ _t as Player,
1652
+ q as ThumbnailTrack,
1653
+ et as buildHeatmapValues,
1654
+ K as chapterAt,
1655
+ J as defaultIcons,
1656
+ I as defaultLabels,
1657
+ w as formatTime,
1658
+ tt as getLocale,
1659
+ it as heatmapPath,
1660
+ ct as isHlsSource,
1661
+ ht as loadChaptersVtt,
1662
+ at as normalizeChapters,
1663
+ Lt as registerLocale,
1664
+ St as registerLocales,
1665
+ xt as registeredLanguages,
1666
+ st as resolveVast
1667
+ };
1668
+ //# sourceMappingURL=core.js.map