itube-modern-player 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -1,33 +1,33 @@
1
- import { d as V } from "./labels-C3gAZEm-.js";
2
- function a(o, t, e) {
3
- const i = document.createElement(o);
1
+ import { d as q } from "./labels-DZFT0XMa.js";
2
+ function o(l, t, e) {
3
+ const i = document.createElement(l);
4
4
  if (t && (i.className = t), e)
5
5
  for (const [s, n] of Object.entries(e)) i.setAttribute(s, n);
6
6
  return i;
7
7
  }
8
- function f(o, t, e) {
9
- const i = a("button", `imp-btn ${o}`, { type: "button", "aria-label": t, title: t });
8
+ function f(l, t, e) {
9
+ const i = o("button", `imp-btn ${l}`, { type: "button", "aria-label": t, title: t });
10
10
  return i.innerHTML = e, i;
11
11
  }
12
- function M(o, t, e) {
13
- o.innerHTML = t, e !== void 0 && (o.setAttribute("aria-label", e), o.setAttribute("title", e));
12
+ function E(l, t, e) {
13
+ l.innerHTML = t, e !== void 0 && (l.setAttribute("aria-label", e), l.setAttribute("title", e));
14
14
  }
15
- function w(o, t, e) {
16
- return Math.min(e, Math.max(t, o));
15
+ function w(l, t, e) {
16
+ return Math.min(e, Math.max(t, l));
17
17
  }
18
- function x(o) {
19
- if (!Number.isFinite(o) || o < 0) return "0:00";
20
- const t = Math.floor(o % 60), e = Math.floor(o / 60 % 60), i = Math.floor(o / 3600), s = i > 0 ? String(e).padStart(2, "0") : String(e), n = String(t).padStart(2, "0");
18
+ function x(l) {
19
+ if (!Number.isFinite(l) || l < 0) return "0:00";
20
+ const t = Math.floor(l % 60), e = Math.floor(l / 60 % 60), i = Math.floor(l / 3600), s = i > 0 ? String(e).padStart(2, "0") : String(e), n = String(t).padStart(2, "0");
21
21
  return i > 0 ? `${i}:${s}:${n}` : `${s}:${n}`;
22
22
  }
23
- function $(o) {
24
- const t = o.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:[.,](\d{1,3}))?$/);
23
+ function I(l) {
24
+ const t = l.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:[.,](\d{1,3}))?$/);
25
25
  if (!t) return null;
26
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
27
  return e * 3600 + i * 60 + s + n / 1e3;
28
28
  }
29
- function Q(o) {
30
- const t = [], e = o.replace(/\r\n?/g, `
29
+ function Q(l) {
30
+ const t = [], e = l.replace(/\r\n?/g, `
31
31
  `).split(/\n\n+/);
32
32
  for (const i of e) {
33
33
  const s = i.split(`
@@ -35,7 +35,7 @@ function Q(o) {
35
35
  if (s.length === 0) continue;
36
36
  let n = s.findIndex((p) => p.includes("-->"));
37
37
  if (n === -1) continue;
38
- const [r, l] = s[n].split("-->"), h = $(r), c = $((l ?? "").split(" ")[1] ?? l ?? "") ?? $(l ?? "");
38
+ const [r, a] = s[n].split("-->"), h = I(r), c = I((a ?? "").split(" ")[1] ?? a ?? "") ?? I(a ?? "");
39
39
  if (h === null || c === null) continue;
40
40
  const u = s.slice(n + 1).join(`
41
41
  `).trim();
@@ -43,14 +43,14 @@ function Q(o) {
43
43
  }
44
44
  return t;
45
45
  }
46
- function G(o, t) {
46
+ function X(l, t) {
47
47
  try {
48
- return new URL(o, new URL(t, window.location.href)).toString();
48
+ return new URL(l, new URL(t, window.location.href)).toString();
49
49
  } catch {
50
- return o;
50
+ return l;
51
51
  }
52
52
  }
53
- class X {
53
+ class K {
54
54
  constructor() {
55
55
  this.listeners = /* @__PURE__ */ new Map();
56
56
  }
@@ -82,7 +82,7 @@ class X {
82
82
  this.listeners.clear();
83
83
  }
84
84
  }
85
- const v = (o, t = "0 0 24 24") => `<svg viewBox="${t}" fill="currentColor" aria-hidden="true" focusable="false">${o}</svg>`, K = {
85
+ const v = (l, t = "0 0 24 24") => `<svg viewBox="${t}" fill="currentColor" aria-hidden="true" focusable="false">${l}</svg>`, J = {
86
86
  play: v('<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
87
  pause: v('<rect x="6" y="5" width="4" height="14" rx="1"/><rect x="14" y="5" width="4" height="14" rx="1"/>'),
88
88
  replay: v('<path d="M12 5V2.5L7.5 6 12 9.5V7a5 5 0 1 1-5 5H5a7 7 0 1 0 7-7Z"/>'),
@@ -113,25 +113,25 @@ const v = (o, t = "0 0 24 24") => `<svg viewBox="${t}" fill="currentColor" aria-
113
113
  report: v('<path d="M5 3h2v18H5V3Zm4 1h10l-2.5 4L19 12H9V4Z"/>'),
114
114
  more: v('<circle cx="12" cy="5" r="2"/><circle cx="12" cy="12" r="2"/><circle cx="12" cy="19" r="2"/>'),
115
115
  close: v('<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"/>')
116
- }, P = { en: V };
117
- function Lt(o, t) {
118
- P[o] = t;
116
+ }, P = { en: q };
117
+ function _t(l, t) {
118
+ P[l] = t;
119
119
  }
120
- function St(o) {
121
- for (const [t, e] of Object.entries(o))
120
+ function Lt(l) {
121
+ for (const [t, e] of Object.entries(l))
122
122
  e && (P[t] = e);
123
123
  }
124
- function J(o) {
125
- return o && P[o] || V;
124
+ function Y(l) {
125
+ return l && P[l] || q;
126
126
  }
127
- function _t() {
127
+ function Bt() {
128
128
  return Object.keys(P);
129
129
  }
130
- function Y(o, t, e = 100) {
131
- if (o.length === 0 || !Number.isFinite(t) || t <= 0) return [];
130
+ function tt(l, t, e = 100) {
131
+ if (l.length === 0 || !Number.isFinite(t) || t <= 0) return [];
132
132
  const i = new Array(e).fill(0);
133
133
  let s = !1;
134
- for (const c of o) {
134
+ for (const c of l) {
135
135
  if (!Number.isFinite(c.time) || c.time < 0 || c.time > t) continue;
136
136
  const u = Math.max(0, c.value);
137
137
  if (u === 0) continue;
@@ -140,93 +140,93 @@ function Y(o, t, e = 100) {
140
140
  }
141
141
  if (!s) return [];
142
142
  const n = [0.06, 0.24, 0.4, 0.24, 0.06], r = i.map(
143
- (c, u) => n.reduce((p, m, b) => p + m * (i[u + b - 2] ?? 0), 0)
144
- ), l = Math.max(...r);
145
- if (l <= 0) return [];
143
+ (c, u) => n.reduce((p, m, y) => p + m * (i[u + y - 2] ?? 0), 0)
144
+ ), a = Math.max(...r);
145
+ if (a <= 0) return [];
146
146
  const h = 0.08;
147
- return r.map((c) => h + (1 - h) * (c / l));
147
+ return r.map((c) => h + (1 - h) * (c / a));
148
148
  }
149
- function tt(o, t = 1e3, e = 100) {
150
- if (o.length === 0) return "";
151
- const i = t / (o.length - 1 || 1);
149
+ function et(l, t = 1e3, e = 100) {
150
+ if (l.length === 0) return "";
151
+ const i = t / (l.length - 1 || 1);
152
152
  let s = `M 0 ${e}`;
153
- return o.forEach((n, r) => {
153
+ return l.forEach((n, r) => {
154
154
  s += ` L ${(r * i).toFixed(1)} ${(e - n * e).toFixed(1)}`;
155
155
  }), s += ` L ${t} ${e} Z`, s;
156
156
  }
157
- async function et(o, t) {
158
- if (o.src)
157
+ async function it(l, t) {
158
+ if (l.src)
159
159
  return {
160
- roll: o.roll,
161
- mediaUrl: o.src,
162
- clickThrough: o.clickUrl,
160
+ roll: l.roll,
161
+ mediaUrl: l.src,
162
+ clickThrough: l.clickUrl,
163
163
  impressions: [],
164
164
  tracking: {}
165
165
  };
166
- if (!o.vastTag) throw new Error('Ad roll has neither "vastTag" nor "src"');
167
- return z(o, o.vastTag, t, 0, { impressions: [], tracking: {} });
166
+ if (!l.vastTag) throw new Error('Ad roll has neither "vastTag" nor "src"');
167
+ return z(l, l.vastTag, t, 0, { impressions: [], tracking: {} });
168
168
  }
169
- async function z(o, t, e, i, s) {
169
+ async function z(l, t, e, i, s) {
170
170
  if (i > e.maxWrapperDepth) throw new Error("VAST wrapper depth limit exceeded");
171
171
  const n = new AbortController(), r = setTimeout(() => n.abort(), e.requestTimeout);
172
- let l;
172
+ let a;
173
173
  try {
174
174
  const g = await fetch(t, { signal: n.signal, credentials: "omit" });
175
175
  if (!g.ok) throw new Error(`VAST request failed (${g.status})`);
176
- l = await g.text();
176
+ a = await g.text();
177
177
  } finally {
178
178
  clearTimeout(r);
179
179
  }
180
- const h = new DOMParser().parseFromString(l, "text/xml");
180
+ const h = new DOMParser().parseFromString(a, "text/xml");
181
181
  if (h.querySelector("parsererror")) throw new Error("VAST response is not valid XML");
182
182
  const c = h.querySelector("VAST > Ad");
183
183
  if (!c) throw new Error("VAST response contains no ads");
184
- it(c, s);
184
+ st(c, s);
185
185
  const u = c.querySelector(":scope > Wrapper");
186
186
  if (u) {
187
- const g = L(u.querySelector("VASTAdTagURI"));
187
+ const g = S(u.querySelector("VASTAdTagURI"));
188
188
  if (!g) throw new Error("VAST wrapper without VASTAdTagURI");
189
- return z(o, g, e, i + 1, s);
189
+ return z(l, g, e, i + 1, s);
190
190
  }
191
191
  const p = c.querySelector(":scope > InLine");
192
192
  if (!p) throw new Error("VAST ad has neither InLine nor Wrapper");
193
193
  const m = p.querySelector("Creatives > Creative > Linear");
194
194
  if (!m) throw new Error("VAST ad has no Linear creative");
195
- const b = st(m);
196
- if (!b) throw new Error("VAST ad has no playable MediaFile");
195
+ const y = nt(m);
196
+ if (!y) throw new Error("VAST ad has no playable MediaFile");
197
197
  return {
198
- roll: o.roll,
199
- mediaUrl: b.url,
200
- mediaType: b.type,
201
- clickThrough: L(m.querySelector("VideoClicks > ClickThrough")) ?? o.clickUrl,
202
- duration: N(L(m.querySelector(":scope > Duration"))),
203
- skipOffset: nt(
198
+ roll: l.roll,
199
+ mediaUrl: y.url,
200
+ mediaType: y.type,
201
+ clickThrough: S(m.querySelector("VideoClicks > ClickThrough")) ?? l.clickUrl,
202
+ duration: N(S(m.querySelector(":scope > Duration"))),
203
+ skipOffset: rt(
204
204
  m.getAttribute("skipoffset"),
205
- N(L(m.querySelector(":scope > Duration")))
205
+ N(S(m.querySelector(":scope > Duration")))
206
206
  ),
207
207
  impressions: s.impressions,
208
208
  tracking: s.tracking,
209
- adTitle: L(p.querySelector(":scope > AdTitle")) ?? void 0
209
+ adTitle: S(p.querySelector(":scope > AdTitle")) ?? void 0
210
210
  };
211
211
  }
212
- function it(o, t) {
212
+ function st(l, t) {
213
213
  var e, i;
214
- for (const s of o.querySelectorAll("Impression")) {
215
- const n = L(s);
214
+ for (const s of l.querySelectorAll("Impression")) {
215
+ const n = S(s);
216
216
  n && t.impressions.push(n);
217
217
  }
218
- for (const s of o.querySelectorAll("Linear > TrackingEvents > Tracking")) {
219
- const n = s.getAttribute("event"), r = L(s);
218
+ for (const s of l.querySelectorAll("Linear > TrackingEvents > Tracking")) {
219
+ const n = s.getAttribute("event"), r = S(s);
220
220
  !n || !r || ["start", "firstQuartile", "midpoint", "thirdQuartile", "complete", "skip", "pause", "resume"].includes(n) && ((e = t.tracking)[n] ?? (e[n] = [])).push(r);
221
221
  }
222
- for (const s of o.querySelectorAll("Linear > VideoClicks > ClickTracking")) {
223
- const n = L(s);
222
+ for (const s of l.querySelectorAll("Linear > VideoClicks > ClickTracking")) {
223
+ const n = S(s);
224
224
  n && ((i = t.tracking).click ?? (i.click = [])).push(n);
225
225
  }
226
226
  }
227
- function st(o) {
228
- const e = [...o.querySelectorAll("MediaFiles > MediaFile")].map((r) => ({
229
- url: L(r) ?? "",
227
+ function nt(l) {
228
+ const e = [...l.querySelectorAll("MediaFiles > MediaFile")].map((r) => ({
229
+ url: S(r) ?? "",
230
230
  type: r.getAttribute("type") ?? void 0,
231
231
  delivery: r.getAttribute("delivery") ?? "",
232
232
  bitrate: Number(r.getAttribute("bitrate") ?? 0),
@@ -235,33 +235,33 @@ function st(o) {
235
235
  (r) => !r.type || /(video\/(mp4|webm|ogg)|application\/(x-mpegurl|vnd\.apple\.mpegurl))/i.test(r.type)
236
236
  );
237
237
  if (e.length === 0) return null;
238
- e.sort((r, l) => r.bitrate - l.bitrate);
238
+ e.sort((r, a) => r.bitrate - a.bitrate);
239
239
  const i = e.filter((r) => r.delivery !== "streaming"), s = i.length > 0 ? i : e, n = s[Math.floor(s.length / 2)];
240
240
  return { url: n.url, type: n.type };
241
241
  }
242
- function N(o) {
243
- if (!o) return;
244
- const t = o.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
242
+ function N(l) {
243
+ if (!l) return;
244
+ const t = l.trim().match(/^(?:(\d+):)?(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
245
245
  if (t)
246
246
  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);
247
247
  }
248
- function nt(o, t) {
249
- if (!o) return;
250
- const e = o.trim().match(/^(\d+(?:\.\d+)?)%$/);
251
- return e ? t !== void 0 ? Number(e[1]) / 100 * t : void 0 : N(o);
248
+ function rt(l, t) {
249
+ if (!l) return;
250
+ const e = l.trim().match(/^(\d+(?:\.\d+)?)%$/);
251
+ return e ? t !== void 0 ? Number(e[1]) / 100 * t : void 0 : N(l);
252
252
  }
253
- function T(o) {
254
- if (o)
255
- for (const t of o)
253
+ function M(l) {
254
+ if (l)
255
+ for (const t of l)
256
256
  try {
257
257
  new Image().src = t;
258
258
  } catch {
259
259
  }
260
260
  }
261
- function L(o) {
262
- return o?.textContent?.trim().replace(/^<!\[CDATA\[|\]\]>$/g, "").trim() || null;
261
+ function S(l) {
262
+ return l?.textContent?.trim().replace(/^<!\[CDATA\[|\]\]>$/g, "").trim() || null;
263
263
  }
264
- class rt {
264
+ class ot {
265
265
  constructor(t, e) {
266
266
  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 = {
267
267
  skipDelay: 5,
@@ -280,7 +280,7 @@ class rt {
280
280
  */
281
281
  ensureAdVideo() {
282
282
  if (!this.adVideo) {
283
- this.adVideo = a("video", "imp-ad__video", { playsinline: "" });
283
+ this.adVideo = o("video", "imp-ad__video", { playsinline: "" });
284
284
  try {
285
285
  this.adVideo.load();
286
286
  } catch {
@@ -328,11 +328,11 @@ class rt {
328
328
  for (const n of t) {
329
329
  this.playedRolls.add(n);
330
330
  try {
331
- const r = await et(n, this.opts);
331
+ const r = await it(n, this.opts);
332
332
  await this.playAd(r);
333
333
  } catch (r) {
334
- const l = r instanceof Error ? r : new Error(String(r));
335
- this.host.emitter.emit("aderror", { roll: n, error: l });
334
+ const a = r instanceof Error ? r : new Error(String(r));
335
+ this.host.emitter.emit("aderror", { roll: n, error: a });
336
336
  }
337
337
  if (this.destroyed) return;
338
338
  }
@@ -345,28 +345,28 @@ class rt {
345
345
  }
346
346
  playAd(t) {
347
347
  return new Promise((e) => {
348
- const { labels: i } = this.host, s = a("div", "imp-ad"), n = this.ensureAdVideo(), r = new AbortController(), l = r.signal;
348
+ const { labels: i } = this.host, s = o("div", "imp-ad"), n = this.ensureAdVideo(), r = new AbortController(), a = r.signal;
349
349
  n.src = t.mediaUrl, n.muted = this.host.contentVideo.muted, n.volume = this.host.contentVideo.volume;
350
- const h = a("div", "imp-spinner imp-ad__spinner"), c = a("div", "imp-ad__hud"), u = a("span", "imp-ad__badge");
350
+ const h = o("div", "imp-spinner imp-ad__spinner"), c = o("div", "imp-ad__hud"), u = o("span", "imp-ad__badge");
351
351
  u.textContent = t.adTitle ? `${i.adLabel} · ${t.adTitle}` : i.adLabel;
352
- const p = a("span", "imp-ad__countdown");
352
+ const p = o("span", "imp-ad__countdown");
353
353
  c.append(u, p);
354
- const m = a("div", "imp-ad__actions");
355
- let b = null;
356
- t.clickThrough && (b = a("button", "imp-ad__visit", { type: "button" }), b.textContent = i.visitAdvertiser, m.append(b));
357
- const g = a("button", "imp-ad__skip", { type: "button" });
354
+ const m = o("div", "imp-ad__actions");
355
+ let y = null;
356
+ t.clickThrough && (y = o("button", "imp-ad__visit", { type: "button" }), y.textContent = i.visitAdvertiser, m.append(y));
357
+ const g = o("button", "imp-ad__skip", { type: "button" });
358
358
  g.hidden = !0, m.append(g), s.append(n, h, c, m), this.host.container.append(s), this.layer = s;
359
- const B = t.skipOffset ?? this.opts.skipDelay, d = /* @__PURE__ */ new Set(), y = (k) => {
360
- d.has(k) || (d.add(k), T(t.tracking[k]));
359
+ const T = t.skipOffset ?? this.opts.skipDelay, d = /* @__PURE__ */ new Set(), b = (k) => {
360
+ d.has(k) || (d.add(k), M(t.tracking[k]));
361
361
  };
362
- let A = Date.now(), F = -1;
362
+ let A = Date.now(), V = -1;
363
363
  const j = setInterval(() => {
364
364
  if (n.paused) {
365
365
  A = Date.now();
366
366
  return;
367
367
  }
368
- if (n.currentTime !== F) {
369
- F = n.currentTime, A = Date.now();
368
+ if (n.currentTime !== V) {
369
+ V = n.currentTime, A = Date.now();
370
370
  return;
371
371
  }
372
372
  Date.now() - A >= this.opts.mediaTimeout && (this.host.emitter.emit("aderror", {
@@ -376,38 +376,38 @@ class rt {
376
376
  }, 500), R = () => {
377
377
  clearInterval(j), r.abort(), n.pause(), n.removeAttribute("src"), n.load(), s.remove(), this.layer = null, e();
378
378
  }, H = (k) => {
379
- k ? (y("skip"), this.host.emitter.emit("adskip", { ad: t })) : y("complete"), this.host.emitter.emit("adend", { ad: t }), R();
379
+ k ? (b("skip"), this.host.emitter.emit("adskip", { ad: t })) : b("complete"), this.host.emitter.emit("adend", { ad: t }), R();
380
380
  };
381
381
  n.addEventListener("playing", () => {
382
- T(t.impressions), y("start"), this.host.emitter.emit("adstart", { ad: t });
383
- }, { once: !0, signal: l }), n.addEventListener("playing", () => {
382
+ M(t.impressions), b("start"), this.host.emitter.emit("adstart", { ad: t });
383
+ }, { once: !0, signal: a }), n.addEventListener("playing", () => {
384
384
  h.hidden = !0;
385
- }, { signal: l }), n.addEventListener("waiting", () => {
385
+ }, { signal: a }), n.addEventListener("waiting", () => {
386
386
  h.hidden = !1;
387
- }, { signal: l }), n.addEventListener("timeupdate", () => {
388
- const k = n.currentTime, _ = n.duration;
389
- if (Number.isFinite(_) && _ > 0 && (p.textContent = x(Math.max(0, _ - k)), k / _ >= 0.25 && y("firstQuartile"), k / _ >= 0.5 && y("midpoint"), k / _ >= 0.75 && y("thirdQuartile")), B >= 0) {
390
- const D = Math.ceil(B - k);
387
+ }, { signal: a }), n.addEventListener("timeupdate", () => {
388
+ const k = n.currentTime, L = n.duration;
389
+ if (Number.isFinite(L) && L > 0 && (p.textContent = x(Math.max(0, L - k)), k / L >= 0.25 && b("firstQuartile"), k / L >= 0.5 && b("midpoint"), k / L >= 0.75 && b("thirdQuartile")), T >= 0) {
390
+ const D = Math.ceil(T - k);
391
391
  D > 0 ? (g.hidden = !1, g.disabled = !0, g.textContent = `${i.skipAdIn} ${D}`) : (g.hidden = !1, g.disabled = !1, g.textContent = i.skipAd);
392
392
  }
393
- }, { signal: l }), n.addEventListener("ended", () => H(!1), { signal: l }), n.addEventListener("error", () => {
393
+ }, { signal: a }), n.addEventListener("ended", () => H(!1), { signal: a }), n.addEventListener("error", () => {
394
394
  this.host.emitter.emit("aderror", {
395
395
  roll: { roll: t.roll },
396
396
  error: new Error("Ad media failed to play")
397
397
  }), R();
398
- }, { signal: l }), g.addEventListener("click", () => H(!0));
398
+ }, { signal: a }), g.addEventListener("click", () => H(!0));
399
399
  const O = () => {
400
- t.clickThrough && (y("click"), this.host.emitter.emit("adclick", { ad: t }), window.open(t.clickThrough, "_blank", "noopener"), n.pause());
400
+ t.clickThrough && (b("click"), this.host.emitter.emit("adclick", { ad: t }), window.open(t.clickThrough, "_blank", "noopener"), n.pause());
401
401
  };
402
- n.addEventListener("click", O, { signal: l }), b?.addEventListener("click", O);
402
+ n.addEventListener("click", O, { signal: a }), y?.addEventListener("click", O);
403
403
  let Z = !1, C = !1;
404
404
  n.addEventListener("playing", () => {
405
405
  Z = !0;
406
- }, { once: !0, signal: l }), n.addEventListener("pause", () => {
407
- n.ended || !s.isConnected || (s.classList.add("imp-ad--paused"), Z && !C && (C = !0, T(t.tracking.pause), this.host.emitter.emit("adpause", { ad: t })));
408
- }, { signal: l }), n.addEventListener("play", () => {
409
- s.classList.remove("imp-ad--paused"), C && (C = !1, T(t.tracking.resume), this.host.emitter.emit("adresume", { ad: t }));
410
- }, { signal: l }), s.addEventListener("click", (k) => {
406
+ }, { once: !0, signal: a }), n.addEventListener("pause", () => {
407
+ n.ended || !s.isConnected || (s.classList.add("imp-ad--paused"), Z && !C && (C = !0, M(t.tracking.pause), this.host.emitter.emit("adpause", { ad: t })));
408
+ }, { signal: a }), n.addEventListener("play", () => {
409
+ s.classList.remove("imp-ad--paused"), C && (C = !1, M(t.tracking.resume), this.host.emitter.emit("adresume", { ad: t }));
410
+ }, { signal: a }), s.addEventListener("click", (k) => {
411
411
  (k.target === s || s.classList.contains("imp-ad--paused")) && n.paused && n.play().catch(() => {
412
412
  });
413
413
  }), n.play().catch(() => {
@@ -423,8 +423,8 @@ class rt {
423
423
  this.destroyed = !0, this.adVideo?.pause(), this.layer?.remove(), this.layer = null, this.adVideo = null;
424
424
  }
425
425
  }
426
- function ot(o, t) {
427
- const e = [...o].sort((s, n) => s.start - n.start), i = [];
426
+ function W(l, t) {
427
+ const e = [...l].sort((s, n) => s.start - n.start), i = [];
428
428
  for (let s = 0; s < e.length; s++) {
429
429
  const n = Math.max(0, e[s].start);
430
430
  if (Number.isFinite(t) && n >= t) continue;
@@ -433,41 +433,41 @@ function ot(o, t) {
433
433
  }
434
434
  return i;
435
435
  }
436
- async function lt(o) {
437
- const t = await fetch(o);
436
+ async function lt(l) {
437
+ const t = await fetch(l);
438
438
  if (!t.ok) throw new Error(`Failed to load chapters VTT (${t.status})`);
439
439
  const e = await t.text();
440
440
  return Q(e).map((i) => ({ start: i.start, end: i.end, title: i.text }));
441
441
  }
442
- function U(o, t) {
443
- for (const e of o)
442
+ function U(l, t) {
443
+ for (const e of l)
444
444
  if (t >= e.start && t < e.end) return e;
445
445
  return null;
446
446
  }
447
- function at(o, t) {
448
- return t ? /application\/(x-mpegurl|vnd\.apple\.mpegurl)/i.test(t) : /\.m3u8(\?|#|$)/i.test(o);
447
+ function at(l, t) {
448
+ return t ? /application\/(x-mpegurl|vnd\.apple\.mpegurl)/i.test(t) : /\.m3u8(\?|#|$)/i.test(l);
449
449
  }
450
- let S;
450
+ let _;
451
451
  async function ht() {
452
- if (S !== void 0) return S;
453
- const o = globalThis.Hls;
454
- if (o)
455
- return S = o, S;
452
+ if (_ !== void 0) return _;
453
+ const l = globalThis.Hls;
454
+ if (l)
455
+ return _ = l, _;
456
456
  try {
457
- S = (await import("hls.js/light")).default;
457
+ _ = (await import("hls.js/light")).default;
458
458
  } catch {
459
- S = null;
459
+ _ = null;
460
460
  }
461
- return S;
461
+ return _;
462
462
  }
463
- async function ct(o, t, e, i) {
463
+ async function ct(l, t, e, i) {
464
464
  if (at(t, e)) {
465
- const s = o.canPlayType("application/vnd.apple.mpegurl"), n = s ? null : await ht();
466
- return n && n.isSupported() ? dt(n, o, t, i) : s ? (o.src = t, q(o)) : (i.onError("HLS is not supported in this browser and hls.js could not be loaded."), q(o));
465
+ const s = l.canPlayType("application/vnd.apple.mpegurl"), n = s ? null : await ht();
466
+ return n && n.isSupported() ? dt(n, l, t, i) : s ? (l.src = t, $(l)) : (i.onError("HLS is not supported in this browser and hls.js could not be loaded."), $(l));
467
467
  }
468
- return o.src = t, q(o);
468
+ return l.src = t, $(l);
469
469
  }
470
- function q(o) {
470
+ function $(l) {
471
471
  return {
472
472
  kind: "native",
473
473
  levels: [],
@@ -475,12 +475,12 @@ function q(o) {
475
475
  setLevel() {
476
476
  },
477
477
  destroy() {
478
- o.removeAttribute("src"), o.load();
478
+ l.removeAttribute("src"), l.load();
479
479
  }
480
480
  };
481
481
  }
482
- function dt(o, t, e, i) {
483
- const s = new o({ enableWorker: !0 }), n = {
482
+ function dt(l, t, e, i) {
483
+ const s = new l({ enableWorker: !0 }), n = {
484
484
  kind: "hls",
485
485
  levels: [],
486
486
  selected: -1,
@@ -491,30 +491,30 @@ function dt(o, t, e, i) {
491
491
  s.destroy(), t.removeAttribute("src"), t.load();
492
492
  }
493
493
  };
494
- s.on(o.Events.MANIFEST_PARSED, () => {
494
+ s.on(l.Events.MANIFEST_PARSED, () => {
495
495
  n.levels = s.levels.map((h, c) => ({
496
496
  index: c,
497
497
  label: h.height ? `${h.height}p` : `${Math.round(h.bitrate / 1e3)} kbps`
498
498
  })).reverse(), i.onLevels(n.levels);
499
- }), s.on(o.Events.LEVEL_SWITCHED, (h, c) => {
499
+ }), s.on(l.Events.LEVEL_SWITCHED, (h, c) => {
500
500
  const u = s.levels[c.level];
501
501
  u && i.onLevelSwitch(u.height ? `${u.height}p` : `${Math.round(u.bitrate / 1e3)} kbps`);
502
502
  });
503
- let r = 0, l = !1;
504
- return s.on(o.Events.ERROR, (h, c) => {
503
+ let r = 0, a = !1;
504
+ return s.on(l.Events.ERROR, (h, c) => {
505
505
  if (c.fatal)
506
506
  switch (c.type) {
507
- case o.ErrorTypes.NETWORK_ERROR:
507
+ case l.ErrorTypes.NETWORK_ERROR:
508
508
  s.startLoad();
509
509
  break;
510
- case o.ErrorTypes.MEDIA_ERROR:
510
+ case l.ErrorTypes.MEDIA_ERROR:
511
511
  r < 3 ? (r++, s.recoverMediaError()) : (i.onError(`HLS media error: ${c.details}`, c), s.destroy());
512
512
  break;
513
513
  default:
514
- if (l)
514
+ if (a)
515
515
  i.onError(`HLS fatal error: ${c.details}`, c), s.destroy();
516
516
  else {
517
- l = !0;
517
+ a = !0;
518
518
  try {
519
519
  s.loadSource(e), s.startLoad();
520
520
  } catch {
@@ -524,7 +524,7 @@ function dt(o, t, e, i) {
524
524
  }
525
525
  }), s.loadSource(e), s.attachMedia(t), n;
526
526
  }
527
- class I {
527
+ class F {
528
528
  constructor(t) {
529
529
  this.cues = t;
530
530
  }
@@ -533,16 +533,16 @@ class I {
533
533
  if (!e.ok) throw new Error(`Failed to load thumbnails VTT (${e.status})`);
534
534
  const i = await e.text(), s = [];
535
535
  for (const n of Q(i)) {
536
- const [r, l] = n.text.trim().split("#");
536
+ const [r, a] = n.text.trim().split("#");
537
537
  if (!r) continue;
538
538
  const h = {
539
539
  start: n.start,
540
540
  end: n.end,
541
- src: G(r, t)
542
- }, c = l?.match(/xywh=(\d+),(\d+),(\d+),(\d+)/);
541
+ src: X(r, t)
542
+ }, c = a?.match(/xywh=(\d+),(\d+),(\d+),(\d+)/);
543
543
  c && (h.xywh = { x: Number(c[1]), y: Number(c[2]), w: Number(c[3]), h: Number(c[4]) }), s.push(h);
544
544
  }
545
- return s.sort((n, r) => n.start - r.start), new I(s);
545
+ return s.sort((n, r) => n.start - r.start), new F(s);
546
546
  }
547
547
  cueAt(t) {
548
548
  let e = 0, i = this.cues.length - 1;
@@ -556,9 +556,9 @@ class I {
556
556
  }
557
557
  }
558
558
  const ut = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m9 6 6 6-6 6"/></svg>', pt = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 6-6 6 6 6"/></svg>';
559
- class E {
559
+ class B {
560
560
  constructor(t) {
561
- this.anchor = t, this.outsideListener = null, this.root = a("div", "imp-menu"), this.root.hidden = !0, this.backdrop = a("div", "imp-menu__backdrop"), this.backdrop.hidden = !0, this.backdrop.addEventListener("pointerdown", (e) => {
561
+ this.anchor = t, this.outsideListener = null, this.root = o("div", "imp-menu"), this.root.hidden = !0, this.backdrop = o("div", "imp-menu__backdrop"), this.backdrop.hidden = !0, this.backdrop.addEventListener("pointerdown", (e) => {
562
562
  e.preventDefault(), this.close();
563
563
  }), t.append(this.backdrop);
564
564
  }
@@ -583,38 +583,57 @@ class E {
583
583
  }
584
584
  renderSettingsRoot(t) {
585
585
  this.root.textContent = "";
586
- const e = a("div", "imp-menu__section");
586
+ const e = o("div", "imp-menu__section");
587
587
  for (const i of t) {
588
- const s = a("button", "imp-menu__item imp-menu__row", { type: "button" }), n = a("span", "imp-menu__label");
588
+ if (i.toggle) {
589
+ e.append(this.buildToggleRow(i, i.toggle));
590
+ continue;
591
+ }
592
+ const s = o("button", "imp-menu__item imp-menu__row", { type: "button" }), n = o("span", "imp-menu__label");
589
593
  n.textContent = i.label;
590
- const r = a("span", "imp-menu__value");
591
- r.textContent = i.value;
592
- const l = a("span", "imp-menu__chevron");
593
- l.innerHTML = ut, s.append(n, r, l), s.addEventListener("click", () => this.renderSettingsDetail(t, i)), e.append(s);
594
+ const r = o("span", "imp-menu__value");
595
+ r.textContent = i.value ?? "";
596
+ const a = o("span", "imp-menu__chevron");
597
+ a.innerHTML = ut, s.append(n, r, a), s.addEventListener("click", () => this.renderSettingsDetail(t, i)), e.append(s);
594
598
  }
595
599
  this.root.append(e);
596
600
  }
601
+ /** Toggle row: label + an on/off switch. Stays in the menu (no close). */
602
+ buildToggleRow(t, e) {
603
+ const i = o("button", "imp-menu__item imp-menu__row imp-menu__toggle", {
604
+ type: "button",
605
+ role: "switch",
606
+ "aria-checked": String(e.value)
607
+ }), s = o("span", "imp-menu__label");
608
+ s.textContent = t.label;
609
+ const n = o("span", "imp-switch");
610
+ return n.append(o("span", "imp-switch__knob")), i.classList.toggle("imp-menu__toggle--on", e.value), i.append(s, n), i.addEventListener("click", () => {
611
+ const r = i.getAttribute("aria-checked") !== "true";
612
+ i.setAttribute("aria-checked", String(r)), i.classList.toggle("imp-menu__toggle--on", r), e.onChange(r);
613
+ }), i;
614
+ }
597
615
  renderSettingsDetail(t, e) {
616
+ if (!e.section) return;
598
617
  this.root.textContent = "";
599
- const i = a("button", "imp-menu__item imp-menu__back", { type: "button" }), s = a("span", "imp-menu__chevron");
618
+ const i = o("button", "imp-menu__item imp-menu__back", { type: "button" }), s = o("span", "imp-menu__chevron");
600
619
  s.innerHTML = pt;
601
- const n = a("span", "imp-menu__label");
620
+ const n = o("span", "imp-menu__label");
602
621
  n.textContent = e.label, i.append(s, n), i.addEventListener("click", () => this.renderSettingsRoot(t)), this.root.append(i), this.root.append(this.renderSection(e.section())), this.root.scrollTop = 0;
603
622
  }
604
623
  /** Build one option block (radio items) from a section. */
605
624
  renderSection(t) {
606
- const e = a("div", "imp-menu__section");
625
+ const e = o("div", "imp-menu__section");
607
626
  if (t.title) {
608
- const i = a("div", "imp-menu__title");
627
+ const i = o("div", "imp-menu__title");
609
628
  i.textContent = t.title, e.append(i);
610
629
  }
611
630
  for (const i of t.items) {
612
- const s = a("button", "imp-menu__item", { type: "button", role: "menuitemradio" });
631
+ const s = o("button", "imp-menu__item", { type: "button", role: "menuitemradio" });
613
632
  if (s.setAttribute("aria-checked", String(i.active)), i.active && s.classList.add("imp-menu__item--active"), i.icon) {
614
- const r = a("span", "imp-menu__icon");
615
- i.icon.trimStart().startsWith("<svg") ? r.innerHTML = i.icon : r.append(a("img", "", { src: i.icon, alt: "" })), s.append(r);
633
+ const r = o("span", "imp-menu__icon");
634
+ i.icon.trimStart().startsWith("<svg") ? r.innerHTML = i.icon : r.append(o("img", "", { src: i.icon, alt: "" })), s.append(r);
616
635
  }
617
- const n = a("span", "imp-menu__label");
636
+ const n = o("span", "imp-menu__label");
618
637
  n.textContent = i.label, s.append(n), s.addEventListener("click", () => {
619
638
  t.onSelect(i.value), this.close();
620
639
  }), e.append(s);
@@ -639,7 +658,7 @@ class E {
639
658
  }
640
659
  class mt {
641
660
  constructor(t) {
642
- this.cb = t, this.segments = [], this.duration = 0, this.chapters = [], this.thumbnails = null, this.scrubbing = !1, this.root = a("div", "imp-progress", { role: "slider", "aria-label": "Seek", tabindex: "-1" }), this.buffered = a("div", "imp-progress__buffered"), this.track = a("div", "imp-progress__track"), this.handle = a("div", "imp-progress__handle"), this.tooltipThumb = a("div", "imp-progress__thumb"), this.tooltipChapter = a("div", "imp-progress__tooltip-chapter"), this.tooltipTime = a("div", "imp-progress__tooltip-time"), this.tooltip = a("div", "imp-progress__tooltip"), this.tooltip.append(this.tooltipThumb, this.tooltipChapter, this.tooltipTime), this.heatmap = a("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();
661
+ this.cb = t, this.segments = [], this.duration = 0, this.chapters = [], this.thumbnails = null, this.scrubbing = !1, this.root = o("div", "imp-progress", { role: "slider", "aria-label": "Seek", tabindex: "-1" }), this.buffered = o("div", "imp-progress__buffered"), this.track = o("div", "imp-progress__track"), this.handle = o("div", "imp-progress__handle"), this.tooltipThumb = o("div", "imp-progress__thumb"), this.tooltipChapter = o("div", "imp-progress__tooltip-chapter"), this.tooltipTime = o("div", "imp-progress__tooltip-time"), this.tooltip = o("div", "imp-progress__tooltip"), this.tooltip.append(this.tooltipThumb, this.tooltipChapter, this.tooltipTime), this.heatmap = o("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();
643
662
  }
644
663
  /** Render the popularity curve (empty array hides it). */
645
664
  setHeatmap(t) {
@@ -647,7 +666,7 @@ class mt {
647
666
  this.heatmap.hidden = !0, this.heatmap.textContent = "";
648
667
  return;
649
668
  }
650
- this.heatmap.innerHTML = `<svg viewBox="0 0 1000 100" preserveAspectRatio="none" aria-hidden="true"><path d="${tt(t)}"/></svg>`, this.heatmap.hidden = !1;
669
+ this.heatmap.innerHTML = `<svg viewBox="0 0 1000 100" preserveAspectRatio="none" aria-hidden="true"><path d="${et(t)}"/></svg>`, this.heatmap.hidden = !1;
651
670
  }
652
671
  setDuration(t) {
653
672
  this.duration = Number.isFinite(t) ? t : 0, this.root.classList.toggle("imp-progress--live", !Number.isFinite(t)), this.chapters.length === 0 && this.setChapters([]);
@@ -660,10 +679,10 @@ class mt {
660
679
  n.start > s && i.push({ start: s, end: n.start, title: "" }), i.push(n), s = n.end;
661
680
  this.duration > 0 && s < this.duration && i.push({ start: s, end: this.duration, title: "" });
662
681
  for (const n of i) {
663
- const r = a("div", "imp-progress__segment");
682
+ const r = o("div", "imp-progress__segment");
664
683
  r.style.flexGrow = String(Math.max(n.end - n.start, 0.01));
665
- const l = a("div", "imp-progress__fill");
666
- r.append(l), this.track.append(r), this.segments.push({ chapter: n, root: r, fill: l });
684
+ const a = o("div", "imp-progress__fill");
685
+ r.append(a), this.track.append(r), this.segments.push({ chapter: n, root: r, fill: a });
667
686
  }
668
687
  }
669
688
  setThumbnails(t) {
@@ -705,8 +724,8 @@ class mt {
705
724
  this.tooltipChapter.textContent = i?.title ?? "", this.tooltipChapter.hidden = !i?.title;
706
725
  const s = this.thumbnails?.cueAt(e) ?? null;
707
726
  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");
708
- const n = this.root.getBoundingClientRect(), r = w(t.clientX - n.left, 0, n.width), l = this.tooltip.offsetWidth / 2;
709
- this.tooltip.style.left = `${w(r, l, Math.max(l, n.width - l))}px`;
727
+ const n = this.root.getBoundingClientRect(), r = w(t.clientX - n.left, 0, n.width), a = this.tooltip.offsetWidth / 2;
728
+ this.tooltip.style.left = `${w(r, a, Math.max(a, n.width - a))}px`;
710
729
  }
711
730
  hideTooltip() {
712
731
  this.tooltip.classList.remove("imp-progress__tooltip--visible");
@@ -714,9 +733,9 @@ class mt {
714
733
  }
715
734
  class vt {
716
735
  constructor(t) {
717
- 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.gearBtn = null, this.gearMenu = null, this.gear = { speed: !1, quality: !1, subtitles: !1 }, this.nextPreviewEl = null, this.nextPreviewOpts = 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;
736
+ this.player = t, this.rightItems = /* @__PURE__ */ new Map(), 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.gearBtn = null, this.gearMenu = null, this.gear = { speed: !1, quality: !1, subtitles: !1 }, this.nextPreviewEl = null, this.nextPreviewOpts = null, this.scenesBtn = null, this.sceneTypeBtn = null, this.sceneTypeMenu = 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;
718
737
  const { labels: e, icons: i } = t, s = t.controlsOptions;
719
- this.root = a("div", "imp-controls"), this.progress = new mt({
738
+ this.root = o("div", "imp-controls"), this.progress = new mt({
720
739
  onSeek: (d) => t.seek(d),
721
740
  onScrubStart: () => {
722
741
  this.wasPlayingBeforeScrub = !t.paused, t.pause();
@@ -725,16 +744,16 @@ class vt {
725
744
  this.wasPlayingBeforeScrub && t.play();
726
745
  }
727
746
  }), s.progress && this.root.append(this.progress.root);
728
- const n = a("div", "imp-controls__row");
747
+ const n = o("div", "imp-controls__row");
729
748
  this.row = n, this.root.append(n);
730
- const r = a("div", "imp-controls__group"), l = a("div", "imp-controls__group");
731
- this.rightGroup = l;
732
- const h = a("div", "imp-controls__spacer");
733
- this.chapterLabel = a("div", "imp-controls__chapter"), n.append(r, h, l), h.append(this.chapterLabel);
749
+ const r = o("div", "imp-controls__group"), a = o("div", "imp-controls__group");
750
+ this.rightGroup = a;
751
+ const h = o("div", "imp-controls__spacer");
752
+ this.chapterLabel = o("div", "imp-controls__chapter"), n.append(r, h, a), h.append(this.chapterLabel);
734
753
  const c = typeof s.seekButtons == "object" ? s.seekButtons : {}, u = c.back ?? t.seekStep, p = c.forward ?? t.seekStep;
735
- if (s.playlist && t.hasPlaylist && !s.hidePrev && (this.prevBtn = f("imp-btn--prev", e.previous, i.previous), this.prevBtn.addEventListener("click", () => t.previous()), r.append(this.prevBtn)), s.seekButtons && (this.seekBackBtn = f("imp-btn--seek-back", `${e.seekBack} ${u}s`, i.seekBack), this.addStepBadge(this.seekBackBtn, u), this.seekBackBtn.addEventListener("click", () => t.skip(-u)), r.append(this.seekBackBtn)), s.play && (this.playBtn = f("imp-btn--play", e.play, i.play), this.playBtn.addEventListener("click", () => t.togglePlay()), r.append(this.playBtn)), s.seekButtons && (this.seekFwdBtn = f("imp-btn--seek-forward", `${e.seekForward} ${p}s`, i.seekForward), this.addStepBadge(this.seekFwdBtn, p), this.seekFwdBtn.addEventListener("click", () => t.skip(p)), r.append(this.seekFwdBtn)), s.playlist && t.hasPlaylist && (this.nextBtn = f("imp-btn--next", e.next, i.next), this.nextBtn.addEventListener("click", () => t.next()), r.append(this.nextBtn)), s.volume) {
736
- const d = a("div", "imp-volume");
737
- this.muteBtn = f("imp-btn--mute", e.mute, i.volumeHigh), this.muteBtn.addEventListener("click", () => t.toggleMute()), this.volumeSlider = a("input", "imp-volume__slider", {
754
+ if (this.seekLabelFn = c.label, s.playlist && t.hasPlaylist && !s.hidePrev && (this.prevBtn = f("imp-btn--prev", e.previous, i.previous), this.prevBtn.addEventListener("click", () => t.previous()), r.append(this.prevBtn)), s.seekButtons && (this.seekBackBtn = f("imp-btn--seek-back", `${e.seekBack} ${u}s`, i.seekBack), this.addStepBadge(this.seekBackBtn, u, "back"), this.seekBackBtn.addEventListener("click", () => t.skip(-u)), r.append(this.seekBackBtn)), s.play && (this.playBtn = f("imp-btn--play", e.play, i.play), this.playBtn.addEventListener("click", () => t.togglePlay()), r.append(this.playBtn)), s.seekButtons && (this.seekFwdBtn = f("imp-btn--seek-forward", `${e.seekForward} ${p}s`, i.seekForward), this.addStepBadge(this.seekFwdBtn, p, "forward"), this.seekFwdBtn.addEventListener("click", () => t.skip(p)), r.append(this.seekFwdBtn)), s.playlist && t.hasPlaylist && (this.nextBtn = f("imp-btn--next", e.next, i.next), this.nextBtn.addEventListener("click", () => t.next()), r.append(this.nextBtn)), s.volume) {
755
+ const d = o("div", "imp-volume");
756
+ this.muteBtn = f("imp-btn--mute", e.mute, i.volumeHigh), this.muteBtn.addEventListener("click", () => t.toggleMute()), this.volumeSlider = o("input", "imp-volume__slider", {
738
757
  type: "range",
739
758
  min: "0",
740
759
  max: "1",
@@ -744,22 +763,22 @@ class vt {
744
763
  t.setVolume(Number(this.volumeSlider.value));
745
764
  }), d.append(this.muteBtn, this.volumeSlider), r.append(d);
746
765
  }
747
- s.time && (this.timeLabel = a("div", "imp-controls__time"), this.liveBadge = a("span", "imp-controls__live"), this.liveBadge.textContent = e.live, this.liveBadge.hidden = !0, r.append(this.timeLabel, this.liveBadge)), this.buildLikeDislike(l);
748
- for (const d of (t.actionsOptions.custom ?? []).filter((y) => y.placement === "bar")) {
749
- const y = f(`imp-btn--custom imp-btn--custom-${d.id}`, d.title, d.icon ?? i.more);
750
- y.addEventListener("click", () => t.emit("customaction", { id: d.id })), l.append(y), this.registerCollapsible({
766
+ s.time && (this.timeLabel = o("div", "imp-controls__time"), this.liveBadge = o("span", "imp-controls__live"), this.liveBadge.textContent = e.live, this.liveBadge.hidden = !0, r.append(this.timeLabel, this.liveBadge)), this.buildLikeDislike();
767
+ for (const d of (t.actionsOptions.custom ?? []).filter((b) => b.placement === "bar")) {
768
+ const b = f(`imp-btn--custom imp-btn--custom-${d.id}`, d.title, d.icon ?? i.more);
769
+ b.addEventListener("click", () => t.emit("customaction", { id: d.id })), this.rightItems.set(`custom:${d.id}`, b), this.registerCollapsible({
751
770
  key: `custom:${d.id}`,
752
- el: y,
771
+ el: b,
753
772
  priority: 55,
754
773
  available: () => !0,
755
774
  section: () => this.simpleSection(`custom:${d.id}`, d.title, d.icon ?? i.more, () => t.emit("customaction", { id: d.id }))
756
775
  });
757
776
  }
758
- const m = (d) => d === !1 ? "off" : d === "bar" ? "bar" : "gear", b = m(s.subtitles), g = m(s.quality), B = m(s.speed);
759
- if (this.gear = { speed: B === "gear", quality: g === "gear", subtitles: b === "gear" }, b === "bar") {
760
- this.subtitlesBtn = f("imp-btn--subtitles", e.subtitles, i.subtitles), this.subtitlesMenu = new E(this.subtitlesBtn);
761
- const d = a("div", "imp-controls__menu-anchor");
762
- d.append(this.subtitlesBtn, this.subtitlesMenu.root), this.subtitlesBtn.addEventListener("click", () => this.toggleSubtitlesMenu()), l.append(d), this.registerCollapsible({
777
+ const m = (d) => d === !1 ? "off" : d === "bar" ? "bar" : "gear", y = m(s.subtitles), g = m(s.quality), T = m(s.speed);
778
+ if (this.gear = { speed: T === "gear", quality: g === "gear", subtitles: y === "gear" }, y === "bar") {
779
+ this.subtitlesBtn = f("imp-btn--subtitles", e.subtitles, i.subtitles), this.subtitlesMenu = new B(this.subtitlesBtn);
780
+ const d = o("div", "imp-controls__menu-anchor");
781
+ d.append(this.subtitlesBtn, this.subtitlesMenu.root), this.subtitlesBtn.addEventListener("click", () => this.toggleSubtitlesMenu()), this.rightItems.set("subtitles", d), this.registerCollapsible({
763
782
  key: "subtitles",
764
783
  el: d,
765
784
  priority: 30,
@@ -768,9 +787,9 @@ class vt {
768
787
  });
769
788
  }
770
789
  if (g === "bar") {
771
- this.qualityBtn = f("imp-btn--quality", e.quality, i.settings), this.qualityMenu = new E(this.qualityBtn);
772
- const d = a("div", "imp-controls__menu-anchor");
773
- d.append(this.qualityBtn, this.qualityMenu.root), this.qualityBtn.addEventListener("click", () => this.toggleQualityMenu()), l.append(d), this.registerCollapsible({
790
+ this.qualityBtn = f("imp-btn--quality", e.quality, i.settings), this.qualityMenu = new B(this.qualityBtn);
791
+ const d = o("div", "imp-controls__menu-anchor");
792
+ d.append(this.qualityBtn, this.qualityMenu.root), this.qualityBtn.addEventListener("click", () => this.toggleQualityMenu()), this.rightItems.set("quality", d), this.registerCollapsible({
774
793
  key: "quality",
775
794
  el: d,
776
795
  priority: 10,
@@ -778,10 +797,10 @@ class vt {
778
797
  section: () => this.qualitySection()
779
798
  });
780
799
  }
781
- if (B === "bar") {
782
- this.settingsBtn = f("imp-btn--speed", e.speed, i.speed), this.settingsMenu = new E(this.settingsBtn);
783
- const d = a("div", "imp-controls__menu-anchor");
784
- d.append(this.settingsBtn, this.settingsMenu.root), this.settingsBtn.addEventListener("click", () => this.toggleSettingsMenu()), l.append(d), this.registerCollapsible({
800
+ if (T === "bar") {
801
+ this.settingsBtn = f("imp-btn--speed", e.speed, i.speed), this.settingsMenu = new B(this.settingsBtn);
802
+ const d = o("div", "imp-controls__menu-anchor");
803
+ d.append(this.settingsBtn, this.settingsMenu.root), this.settingsBtn.addEventListener("click", () => this.toggleSettingsMenu()), this.rightItems.set("speed", d), this.registerCollapsible({
785
804
  key: "speed",
786
805
  el: d,
787
806
  priority: 40,
@@ -789,15 +808,26 @@ class vt {
789
808
  section: () => this.speedSection()
790
809
  });
791
810
  }
792
- if (s.scenes && (this.scenesBtn = f("imp-btn--scenes", e.scenes, i.scenes), this.scenesBtn.addEventListener("click", () => t.toggleScenesPanel()), l.append(this.scenesBtn), this.registerCollapsible({
811
+ if (s.scenes && (this.scenesBtn = f("imp-btn--scenes", e.scenes, i.scenes), this.scenesBtn.addEventListener("click", () => t.toggleScenesPanel()), this.rightItems.set("scenes", this.scenesBtn), this.registerCollapsible({
793
812
  key: "scenes",
794
813
  el: this.scenesBtn,
795
814
  priority: 25,
796
815
  available: () => t.chapterList.length > 0,
797
816
  section: () => this.simpleSection("scenes", e.scenes, i.scenes, () => t.toggleScenesPanel())
798
- })), s.playlist && t.hasPlaylist) {
817
+ })), s.sceneTypes !== !1) {
818
+ this.sceneTypeBtn = o("button", "imp-btn imp-btn--scene-types", { type: "button" }), this.sceneTypeMenu = new B(this.sceneTypeBtn);
819
+ const d = o("div", "imp-controls__menu-anchor");
820
+ d.append(this.sceneTypeBtn, this.sceneTypeMenu.root), this.sceneTypeBtn.addEventListener("click", () => this.toggleSceneTypeMenu()), this.rightItems.set("sceneTypes", d), this.refreshSceneTypeButton(), this.registerCollapsible({
821
+ key: "sceneTypes",
822
+ el: d,
823
+ priority: 26,
824
+ available: () => t.sceneTypes.length > 0,
825
+ section: () => this.sceneTypeSection()
826
+ });
827
+ }
828
+ if (s.playlist && t.hasPlaylist) {
799
829
  const d = f("imp-btn--list", e.playlist, i.list);
800
- d.addEventListener("click", () => t.togglePlaylistPanel()), l.append(d), this.registerCollapsible({
830
+ d.addEventListener("click", () => t.togglePlaylistPanel()), this.rightItems.set("playlist", d), this.registerCollapsible({
801
831
  key: "list",
802
832
  el: d,
803
833
  priority: 50,
@@ -807,7 +837,7 @@ class vt {
807
837
  }
808
838
  if (s.pip && "requestPictureInPicture" in HTMLVideoElement.prototype) {
809
839
  const d = f("imp-btn--pip", e.pip, i.pip);
810
- d.addEventListener("click", () => void t.togglePip()), l.append(d), this.registerCollapsible({
840
+ d.addEventListener("click", () => void t.togglePip()), this.rightItems.set("pip", d), this.registerCollapsible({
811
841
  key: "pip",
812
842
  el: d,
813
843
  priority: 20,
@@ -815,7 +845,7 @@ class vt {
815
845
  section: () => this.simpleSection("pip", e.pip, i.pip, () => void t.togglePip())
816
846
  });
817
847
  }
818
- if (s.fullscreen && (this.fullscreenBtn = f("imp-btn--fullscreen", e.fullscreen, i.fullscreen), this.fullscreenBtn.addEventListener("click", () => void t.toggleFullscreen()), l.append(this.fullscreenBtn)), this.prevBtn && this.registerCollapsible({
848
+ if (s.fullscreen && (this.fullscreenBtn = f("imp-btn--fullscreen", e.fullscreen, i.fullscreen), this.fullscreenBtn.addEventListener("click", () => void t.toggleFullscreen()), this.rightItems.set("fullscreen", this.fullscreenBtn)), this.prevBtn && this.registerCollapsible({
819
849
  key: "prev",
820
850
  el: this.prevBtn,
821
851
  priority: 70,
@@ -847,18 +877,18 @@ class vt {
847
877
  section: () => this.simpleSection("seekFwd", `${e.seekForward} ${p}s`, i.seekForward, () => t.skip(p))
848
878
  });
849
879
  }
850
- if (this.center = a("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) {
880
+ if (this.center = o("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) {
851
881
  const d = this.makeCenterButton("seek-back", `${e.seekBack} ${u}s`, i.seekBack);
852
- this.addStepBadge(d, u), d.addEventListener("click", () => t.skip(-u)), this.center.append(d);
882
+ this.addStepBadge(d, u, "back"), d.addEventListener("click", () => t.skip(-u)), this.center.append(d);
853
883
  }
854
884
  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) {
855
885
  const d = this.makeCenterButton("seek-forward", `${e.seekForward} ${p}s`, i.seekForward);
856
- this.addStepBadge(d, p), d.addEventListener("click", () => t.skip(p)), this.center.append(d);
886
+ this.addStepBadge(d, p, "forward"), d.addEventListener("click", () => t.skip(p)), this.center.append(d);
857
887
  }
858
888
  if (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.setupNextPreview(), this.gear.speed || this.gear.quality || this.gear.subtitles) {
859
- this.gearBtn = f("imp-btn--settings", e.settings, i.settings), this.gearMenu = new E(this.gearBtn);
860
- const d = a("div", "imp-controls__menu-anchor");
861
- d.append(this.gearBtn, this.gearMenu.root), this.gearBtn.addEventListener("click", () => this.toggleGearMenu()), l.append(d), this.registerCollapsible({
889
+ this.gearBtn = f("imp-btn--settings", e.settings, i.settings), this.gearMenu = new B(this.gearBtn);
890
+ const d = o("div", "imp-controls__menu-anchor");
891
+ d.append(this.gearBtn, this.gearMenu.root), this.gearBtn.addEventListener("click", () => this.toggleGearMenu()), this.rightItems.set("gear", d), this.registerCollapsible({
862
892
  key: "gear",
863
893
  el: d,
864
894
  priority: 40,
@@ -866,20 +896,40 @@ class vt {
866
896
  section: () => this.gearSections()
867
897
  });
868
898
  }
869
- this.buildMoreDropdown(l), 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);
899
+ this.layoutRightCluster(a), 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);
870
900
  }
871
901
  registerCollapsible(t) {
872
902
  this.collapsibles.push(t);
873
903
  }
904
+ /**
905
+ * Append the right-cluster controls. Built-in order = creation order; if
906
+ * `controls.order` is set, its keys go first (in that order), then any
907
+ * unlisted controls keep their default position. (⋯ is appended separately.)
908
+ */
909
+ layoutRightCluster(t) {
910
+ const e = [...this.rightItems.keys()], i = this.player.controlsOptions.order;
911
+ let s = e;
912
+ if (i.length > 0) {
913
+ const n = /* @__PURE__ */ new Set();
914
+ s = [];
915
+ for (const r of i)
916
+ this.rightItems.has(r) && !n.has(r) && (s.push(r), n.add(r));
917
+ for (const r of e) n.has(r) || s.push(r);
918
+ }
919
+ for (const n of s) {
920
+ const r = this.rightItems.get(n);
921
+ r && t.append(r);
922
+ }
923
+ }
874
924
  /** Center-cluster button — deliberately NOT `.imp-btn` (own sizing/look). */
875
925
  makeCenterButton(t, e, i) {
876
- const s = a("button", `imp-center-btn imp-center-btn--${t}`, { type: "button", "aria-label": e, title: e });
926
+ const s = o("button", `imp-center-btn imp-center-btn--${t}`, { type: "button", "aria-label": e, title: e });
877
927
  return s.innerHTML = i, s;
878
928
  }
879
- /** Overlay the seek step value (e.g. "15") inside the circular-arrow icon. */
880
- addStepBadge(t, e) {
881
- const i = a("span", "imp-seek-num");
882
- i.textContent = String(e), t.append(i);
929
+ /** Caption under the seek arrow. Default "−15s"/"+15s"; overridable via `controls.seekButtons.label`. */
930
+ addStepBadge(t, e, i) {
931
+ const s = o("span", "imp-seek-num");
932
+ s.textContent = this.seekLabelFn ? this.seekLabelFn(e, i) : `${i === "back" ? "−" : "+"}${e}${this.player.labels.secondsShort}`, t.append(s);
883
933
  }
884
934
  // === gear (unified settings) ==========================================
885
935
  /** Sections for the gear when overflowed into ⋯ (expanded, no drill-down). */
@@ -890,7 +940,11 @@ class vt {
890
940
  /** Gear drill-down rows: one per setting, each showing its current value. */
891
941
  gearEntries() {
892
942
  const t = this.player, e = [];
893
- if (this.gear.quality && t.qualityLevels.length > 0) {
943
+ if (t.hasPlaylist && e.push({
944
+ key: "autoplay",
945
+ label: t.labels.autoplay,
946
+ toggle: { value: t.autoAdvance, onChange: (i) => t.setAutoAdvance(i) }
947
+ }), this.gear.quality && t.qualityLevels.length > 0) {
894
948
  const i = t.currentQuality === -1 ? t.labels.qualityAuto : t.qualityLevels.find((s) => s.index === t.currentQuality)?.label ?? t.labels.qualityAuto;
895
949
  e.push({ key: "quality", label: t.labels.quality, value: i, section: () => this.qualitySection() });
896
950
  }
@@ -904,6 +958,38 @@ class vt {
904
958
  }
905
959
  return e;
906
960
  }
961
+ // === scene types ======================================================
962
+ /** Update the scene-type button to the active group's icon + title. */
963
+ refreshSceneTypeButton() {
964
+ const t = this.sceneTypeBtn;
965
+ if (!t) return;
966
+ const e = this.player.activeSceneType;
967
+ if (t.textContent = "", !e) return;
968
+ if (e.icon) {
969
+ const s = o("span", "imp-scene-type__icon");
970
+ e.icon.trimStart().startsWith("<svg") ? s.innerHTML = e.icon : s.append(o("img", "", { src: e.icon, alt: "" })), t.append(s);
971
+ }
972
+ const i = o("span", "imp-scene-type__label");
973
+ i.textContent = e.title, t.append(i), t.setAttribute("aria-label", `${this.player.labels.sceneTypes}: ${e.title}`), t.title = e.title;
974
+ }
975
+ sceneTypeSection() {
976
+ const t = this.player;
977
+ return {
978
+ title: t.labels.sceneTypes,
979
+ items: t.sceneTypes.map((e) => ({
980
+ label: e.title,
981
+ value: e.id,
982
+ icon: e.icon,
983
+ active: e.id === (t.activeSceneType?.id ?? "")
984
+ })),
985
+ onSelect: (e) => {
986
+ t.setSceneGroup(e), this.refreshSceneTypeButton();
987
+ }
988
+ };
989
+ }
990
+ toggleSceneTypeMenu() {
991
+ !this.sceneTypeMenu || this.player.sceneTypes.length === 0 || (this.closeMenus(this.sceneTypeMenu), this.sceneTypeMenu.toggle([this.sceneTypeSection()]));
992
+ }
907
993
  toggleGearMenu() {
908
994
  if (!this.gearMenu) return;
909
995
  const t = this.gearEntries();
@@ -919,7 +1005,7 @@ class vt {
919
1005
  title: i.title ?? !0,
920
1006
  duration: i.duration ?? !0,
921
1007
  meta: i.meta ?? !0
922
- }, this.nextPreviewEl = a("div", "imp-next-preview"), this.nextPreviewEl.hidden = !0, t.container.append(this.nextPreviewEl), this.nextBtn && this.bindNextPreview(this.nextBtn), this.centerNextBtn && this.bindNextPreview(this.centerNextBtn);
1008
+ }, this.nextPreviewEl = o("div", "imp-next-preview"), this.nextPreviewEl.hidden = !0, t.container.append(this.nextPreviewEl), this.nextBtn && this.bindNextPreview(this.nextBtn), this.centerNextBtn && this.bindNextPreview(this.centerNextBtn);
923
1009
  }
924
1010
  bindNextPreview(t) {
925
1011
  t.addEventListener("pointerenter", (e) => {
@@ -932,62 +1018,62 @@ class vt {
932
1018
  const s = this.player.nextSource;
933
1019
  if (!s) return;
934
1020
  e.textContent = "";
935
- const n = a("div", "imp-next-preview__kicker");
1021
+ const n = o("div", "imp-next-preview__kicker");
936
1022
  if (n.textContent = this.player.labels.next, e.append(n), i.thumbnail && s.poster) {
937
- const m = a("div", "imp-next-preview__thumb");
1023
+ const m = o("div", "imp-next-preview__thumb");
938
1024
  if (m.style.backgroundImage = `url("${s.poster}")`, i.duration && s.duration) {
939
- const b = a("span", "imp-next-preview__duration");
940
- b.textContent = x(s.duration), m.append(b);
1025
+ const y = o("span", "imp-next-preview__duration");
1026
+ y.textContent = x(s.duration), m.append(y);
941
1027
  }
942
1028
  e.append(m);
943
1029
  }
944
1030
  if (i.title && s.title) {
945
- const m = a("div", "imp-next-preview__title");
1031
+ const m = o("div", "imp-next-preview__title");
946
1032
  m.textContent = s.title, e.append(m);
947
1033
  }
948
1034
  if (i.meta && s.previewMeta?.length) {
949
- const m = a("div", "imp-next-preview__meta");
1035
+ const m = o("div", "imp-next-preview__meta");
950
1036
  m.textContent = s.previewMeta.join(" · "), e.append(m);
951
1037
  }
952
1038
  e.hidden = !1;
953
- const r = this.player.container.getBoundingClientRect(), l = t.getBoundingClientRect(), h = 8, c = e.offsetWidth, u = l.left - r.left + l.width / 2 - c / 2, p = Math.max(h, Math.min(u, r.width - c - h));
954
- e.style.left = `${p}px`, e.style.bottom = `${r.height - (l.top - r.top) + 10}px`;
1039
+ const r = this.player.container.getBoundingClientRect(), a = t.getBoundingClientRect(), h = 8, c = e.offsetWidth, u = a.left - r.left + a.width / 2 - c / 2, p = Math.max(h, Math.min(u, r.width - c - h));
1040
+ e.style.left = `${p}px`, e.style.bottom = `${r.height - (a.top - r.top) + 10}px`;
955
1041
  }
956
1042
  hideNextPreview() {
957
1043
  this.nextPreviewEl && (this.nextPreviewEl.hidden = !0);
958
1044
  }
959
1045
  /** like/dislike — visible buttons (collapsible), highlightable via `setLikeState`. */
960
- buildLikeDislike(t) {
961
- const e = this.player, i = e.actionsOptions, { labels: s, icons: n } = e;
962
- if (i.like) {
963
- this.likeBtn = f("imp-btn--like", s.like, n.like), this.likeBtn.addEventListener("click", () => e.emit("action", { id: "like" })), this.likeCountEl = this.attachCountTooltip(this.likeBtn, i.likeCount);
964
- const r = this.wrapWithTooltip(this.likeBtn, this.likeCountEl);
965
- t.append(r), this.registerCollapsible({
1046
+ buildLikeDislike() {
1047
+ const t = this.player, e = t.actionsOptions, { labels: i, icons: s } = t;
1048
+ if (e.like) {
1049
+ this.likeBtn = f("imp-btn--like", i.like, s.like), this.likeBtn.addEventListener("click", () => t.emit("action", { id: "like" })), this.likeCountEl = this.attachCountTooltip(this.likeBtn, e.likeCount);
1050
+ const n = this.wrapWithTooltip(this.likeBtn, this.likeCountEl);
1051
+ this.rightItems.set("like", n), this.registerCollapsible({
966
1052
  key: "like",
967
- el: r,
1053
+ el: n,
968
1054
  priority: 62,
969
1055
  available: () => !0,
970
- section: () => this.simpleSection("like", s.like, n.like, () => e.emit("action", { id: "like" }))
1056
+ section: () => this.simpleSection("like", i.like, s.like, () => t.emit("action", { id: "like" }))
971
1057
  });
972
1058
  }
973
- if (i.dislike) {
974
- this.dislikeBtn = f("imp-btn--dislike", s.dislike, n.dislike), this.dislikeBtn.addEventListener("click", () => e.emit("action", { id: "dislike" })), this.dislikeCountEl = this.attachCountTooltip(this.dislikeBtn, i.dislikeCount);
975
- const r = this.wrapWithTooltip(this.dislikeBtn, this.dislikeCountEl);
976
- t.append(r), this.registerCollapsible({
1059
+ if (e.dislike) {
1060
+ this.dislikeBtn = f("imp-btn--dislike", i.dislike, s.dislike), this.dislikeBtn.addEventListener("click", () => t.emit("action", { id: "dislike" })), this.dislikeCountEl = this.attachCountTooltip(this.dislikeBtn, e.dislikeCount);
1061
+ const n = this.wrapWithTooltip(this.dislikeBtn, this.dislikeCountEl);
1062
+ this.rightItems.set("dislike", n), this.registerCollapsible({
977
1063
  key: "dislike",
978
- el: r,
1064
+ el: n,
979
1065
  priority: 60,
980
1066
  available: () => !0,
981
- section: () => this.simpleSection("dislike", s.dislike, n.dislike, () => e.emit("action", { id: "dislike" }))
1067
+ section: () => this.simpleSection("dislike", i.dislike, s.dislike, () => t.emit("action", { id: "dislike" }))
982
1068
  });
983
1069
  }
984
1070
  }
985
1071
  attachCountTooltip(t, e) {
986
- const i = a("span", "imp-count-tooltip");
1072
+ const i = o("span", "imp-count-tooltip");
987
1073
  return this.setCountText(i, e), i;
988
1074
  }
989
1075
  wrapWithTooltip(t, e) {
990
- const i = a("div", "imp-action");
1076
+ const i = o("div", "imp-action");
991
1077
  return i.append(t, e), i;
992
1078
  }
993
1079
  setCountText(t, e) {
@@ -1007,20 +1093,20 @@ class vt {
1007
1093
  for (const h of (i.custom ?? []).filter((c) => c.placement !== "bar"))
1008
1094
  this.actionItems.push({ value: `custom:${h.id}`, label: h.title, icon: h.icon, run: () => e.emit("customaction", { id: h.id }) });
1009
1095
  const r = f("imp-btn--more", s.more, n.more);
1010
- this.moreMenu = new E(r);
1011
- const l = a("div", "imp-controls__menu-anchor imp-controls__more");
1012
- l.append(r, this.moreMenu.root), r.addEventListener("click", () => {
1096
+ this.moreMenu = new B(r);
1097
+ const a = o("div", "imp-controls__menu-anchor imp-controls__more");
1098
+ a.append(r, this.moreMenu.root), r.addEventListener("click", () => {
1013
1099
  this.closeMenus(this.moreMenu), this.moreMenu?.toggle(this.buildMoreSections());
1014
- }), t.append(l), this.moreWrap = l;
1100
+ }), t.append(a), this.moreWrap = a;
1015
1101
  }
1016
1102
  /** ⋯ menu = overflowed controls (in display order) + consumer actions. */
1017
1103
  buildMoreSections() {
1018
- const t = [], e = /* @__PURE__ */ new Map(), i = [], s = ["prev", "next", "seekBack", "seekFwd", "like", "dislike", "scenes", "pip", "list", "subtitles", "speed", "quality", "gear"], n = (l) => {
1019
- const h = s.indexOf(l);
1104
+ const t = [], e = /* @__PURE__ */ new Map(), i = [], s = ["prev", "next", "seekBack", "seekFwd", "like", "dislike", "scenes", "sceneTypes", "pip", "list", "subtitles", "speed", "quality", "gear"], n = (a) => {
1105
+ const h = s.indexOf(a);
1020
1106
  return h === -1 ? s.length : h;
1021
- }, r = this.collapsibles.filter((l) => this.overflowed.has(l.key) && l.available()).sort((l, h) => n(l.key) - n(h.key));
1022
- for (const l of r) {
1023
- const h = l.section();
1107
+ }, r = this.collapsibles.filter((a) => this.overflowed.has(a.key) && a.available()).sort((a, h) => n(a.key) - n(h.key));
1108
+ for (const a of r) {
1109
+ const h = a.section();
1024
1110
  if (h)
1025
1111
  for (const c of Array.isArray(h) ? h : [h])
1026
1112
  if (!c.title && c.items.length === 1) {
@@ -1029,12 +1115,12 @@ class vt {
1029
1115
  } else
1030
1116
  t.push(c);
1031
1117
  }
1032
- if (i.length > 0 && t.unshift({ title: "", items: i, onSelect: (l) => e.get(l)?.() }), this.actionItems.length > 0) {
1033
- const l = new Map(this.actionItems.map((h) => [h.value, h.run]));
1118
+ if (i.length > 0 && t.unshift({ title: "", items: i, onSelect: (a) => e.get(a)?.() }), this.actionItems.length > 0) {
1119
+ const a = new Map(this.actionItems.map((h) => [h.value, h.run]));
1034
1120
  t.push({
1035
1121
  title: "",
1036
1122
  items: this.actionItems.map(({ value: h, label: c, icon: u }) => ({ value: h, label: c, icon: u, active: !1 })),
1037
- onSelect: (h) => l.get(h)?.()
1123
+ onSelect: (h) => a.get(h)?.()
1038
1124
  });
1039
1125
  }
1040
1126
  return t;
@@ -1089,26 +1175,26 @@ class vt {
1089
1175
  reflow() {
1090
1176
  const t = window.innerWidth <= 767, i = this.player.controlsOptions.seekPlacement !== "bar" || t;
1091
1177
  this.center.style.display = i ? "flex" : "none", this.centerPrevBtn && (this.centerPrevBtn.style.display = t ? "" : "none"), this.centerNextBtn && (this.centerNextBtn.style.display = t ? "" : "none"), this.playBtn && (this.playBtn.style.display = t ? "none" : "");
1092
- const s = (l) => l === "seekBack" || l === "seekFwd" ? i : l === "prev" || l === "next" ? t : !1;
1178
+ const s = (a) => a === "seekBack" || a === "seekFwd" ? i : a === "prev" || a === "next" ? t : !1;
1093
1179
  this.overflowed.clear();
1094
- for (const l of this.collapsibles) {
1095
- if (s(l.key)) {
1096
- l.el.style.display = "none";
1180
+ for (const a of this.collapsibles) {
1181
+ if (s(a.key)) {
1182
+ a.el.style.display = "none";
1097
1183
  continue;
1098
1184
  }
1099
- const h = l.available();
1100
- l.el.style.display = h ? "" : "none", l.el.classList.remove("imp-collapsed");
1185
+ const h = a.available();
1186
+ a.el.style.display = h ? "" : "none", a.el.classList.remove("imp-collapsed");
1101
1187
  }
1102
- const n = this.collapsibles.filter((l) => l.available() && !s(l.key)).sort((l, h) => l.priority - h.priority);
1188
+ const n = this.collapsibles.filter((a) => a.available() && !s(a.key)).sort((a, h) => a.priority - h.priority);
1103
1189
  let r = n.length;
1104
1190
  for (; r-- > 0 && this.overflowsRow(); ) {
1105
- const l = n.find((h) => !this.overflowed.has(h.key));
1106
- if (!l) break;
1107
- l.el.style.display = "none", l.el.classList.add("imp-collapsed"), this.overflowed.add(l.key);
1191
+ const a = n.find((h) => !this.overflowed.has(h.key));
1192
+ if (!a) break;
1193
+ a.el.style.display = "none", a.el.classList.add("imp-collapsed"), this.overflowed.add(a.key);
1108
1194
  }
1109
1195
  if (this.moreWrap) {
1110
- const l = this.actionItems.length > 0 || this.overflowed.size > 0;
1111
- this.moreWrap.style.display = l ? "" : "none";
1196
+ const a = this.actionItems.length > 0 || this.overflowed.size > 0;
1197
+ this.moreWrap.style.display = a ? "" : "none";
1112
1198
  }
1113
1199
  }
1114
1200
  overflowsRow() {
@@ -1129,7 +1215,7 @@ class vt {
1129
1215
  this.chapterLabel.textContent = e?.title ?? "";
1130
1216
  }),
1131
1217
  t.on("fullscreenchange", ({ active: e }) => {
1132
- this.fullscreenBtn && M(this.fullscreenBtn, e ? t.icons.fullscreenExit : t.icons.fullscreen, e ? t.labels.exitFullscreen : t.labels.fullscreen), this.scheduleReflow();
1218
+ this.fullscreenBtn && E(this.fullscreenBtn, e ? t.icons.fullscreenExit : t.icons.fullscreen, e ? t.labels.exitFullscreen : t.labels.fullscreen), this.scheduleReflow();
1133
1219
  }),
1134
1220
  t.on("playlistitemchange", () => {
1135
1221
  this.syncPlaylistButtons(), this.scheduleReflow();
@@ -1141,23 +1227,23 @@ class vt {
1141
1227
  }
1142
1228
  syncPlayState() {
1143
1229
  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];
1144
- this.playBtn && M(this.playBtn, e, i), this.centerPlayBtn && M(this.centerPlayBtn, e, i);
1230
+ this.playBtn && E(this.playBtn, e, i), this.centerPlayBtn && E(this.centerPlayBtn, e, i);
1145
1231
  }
1146
1232
  syncVolume() {
1147
1233
  if (!this.muteBtn) return;
1148
1234
  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;
1149
- M(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}%`);
1235
+ 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}%`);
1150
1236
  }
1151
1237
  /** Called by the player when per-source data (chapters/quality/subtitles) changes. */
1152
1238
  syncFeatureButtons() {
1153
- this.scheduleReflow();
1239
+ this.refreshSceneTypeButton(), this.scheduleReflow();
1154
1240
  }
1155
1241
  syncPlaylistButtons() {
1156
1242
  const t = this.player;
1157
1243
  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);
1158
1244
  }
1159
1245
  closeMenus(t) {
1160
- for (const e of [this.gearMenu, this.settingsMenu, this.subtitlesMenu, this.qualityMenu, this.moreMenu])
1246
+ for (const e of [this.gearMenu, this.settingsMenu, this.subtitlesMenu, this.qualityMenu, this.sceneTypeMenu, this.moreMenu])
1161
1247
  e && e !== t && e.close();
1162
1248
  }
1163
1249
  toggleSettingsMenu() {
@@ -1171,12 +1257,12 @@ class vt {
1171
1257
  }
1172
1258
  destroy() {
1173
1259
  for (const t of this.disposers) t();
1174
- this.disposers = [], this.resizeObserver?.disconnect(), this.onWindowResize && window.removeEventListener("resize", this.onWindowResize), this.gearMenu?.destroy(), this.settingsMenu?.destroy(), this.subtitlesMenu?.destroy(), this.qualityMenu?.destroy(), this.moreMenu?.destroy(), this.nextPreviewEl?.remove(), this.root.remove();
1260
+ this.disposers = [], this.resizeObserver?.disconnect(), this.onWindowResize && window.removeEventListener("resize", this.onWindowResize), this.gearMenu?.destroy(), this.sceneTypeMenu?.destroy(), this.settingsMenu?.destroy(), this.subtitlesMenu?.destroy(), this.qualityMenu?.destroy(), this.moreMenu?.destroy(), this.nextPreviewEl?.remove(), this.root.remove();
1175
1261
  }
1176
1262
  }
1177
1263
  class ft {
1178
1264
  constructor(t) {
1179
- this.root = a("div", "imp-poster"), this.image = a("img", "imp-poster__image", { alt: "", decoding: "async" }), this.image.hidden = !0;
1265
+ this.root = o("div", "imp-poster"), this.image = o("img", "imp-poster__image", { alt: "", decoding: "async" }), this.image.hidden = !0;
1180
1266
  const e = f("imp-poster__play", t.labels.play, t.icons.bigPlay);
1181
1267
  e.addEventListener("click", () => void t.play()), this.root.append(this.image, e), this.root.addEventListener("click", (i) => {
1182
1268
  (i.target === this.root || i.target === this.image) && t.play();
@@ -1194,11 +1280,11 @@ class ft {
1194
1280
  }
1195
1281
  class gt {
1196
1282
  constructor(t) {
1197
- this.labels = t, this.channelUrl = null, this.custom = null, this.visible = { title: !0, description: !0, sponsor: !0 }, this.root = a("div", "imp-pause-screen"), this.root.hidden = !0, this.defaultContent = a("div", "imp-pause-screen__default"), this.channel = a("button", "imp-channel", { type: "button" }), this.channel.hidden = !0, this.channelAvatar = a("div", "imp-channel__avatar"), this.channelName = a("div", "imp-channel__name"), this.channel.append(this.channelName, this.channelAvatar), this.channel.addEventListener("click", () => {
1283
+ this.labels = t, this.channelUrl = null, this.custom = null, this.visible = { title: !0, description: !0, sponsor: !0 }, this.root = o("div", "imp-pause-screen"), this.root.hidden = !0, this.defaultContent = o("div", "imp-pause-screen__default"), this.channel = o("button", "imp-channel", { type: "button" }), this.channel.hidden = !0, this.channelAvatar = o("div", "imp-channel__avatar"), this.channelName = o("div", "imp-channel__name"), this.channel.append(this.channelName, this.channelAvatar), this.channel.addEventListener("click", () => {
1198
1284
  this.channelUrl && window.open(this.channelUrl, "_blank", "noopener");
1199
1285
  });
1200
- const e = a("div", "imp-pause-screen__heading");
1201
- this.title = a("div", "imp-pause-screen__title"), this.description = a("div", "imp-pause-screen__description"), this.sponsor = a("a", "imp-sponsor", { target: "_blank", rel: "nofollow noopener" }), this.sponsor.hidden = !0, this.sponsorLabel = a("span", "imp-sponsor__label"), this.sponsorText = a("span", "imp-sponsor__text"), this.sponsor.append(this.sponsorLabel, this.sponsorText), this.sponsor.addEventListener("click", (i) => i.stopPropagation()), e.append(this.sponsor, this.title, this.description), this.defaultContent.append(e, this.channel), this.root.append(this.defaultContent);
1286
+ const e = o("div", "imp-pause-screen__heading");
1287
+ this.title = o("div", "imp-pause-screen__title"), this.description = o("div", "imp-pause-screen__description"), this.sponsor = o("a", "imp-sponsor", { target: "_blank", rel: "nofollow noopener" }), this.sponsor.hidden = !0, this.sponsorLabel = o("span", "imp-sponsor__label"), this.sponsorText = o("span", "imp-sponsor__text"), this.sponsor.append(this.sponsorLabel, this.sponsorText), this.sponsor.addEventListener("click", (i) => i.stopPropagation()), e.append(this.sponsor, this.title, this.description), this.defaultContent.append(e, this.channel), this.root.append(this.defaultContent);
1202
1288
  }
1203
1289
  setCustomContent(t) {
1204
1290
  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);
@@ -1229,9 +1315,9 @@ class gt {
1229
1315
  this.root.hidden = !0;
1230
1316
  }
1231
1317
  }
1232
- class bt {
1318
+ class yt {
1233
1319
  constructor(t) {
1234
- this.player = t, this.clickBehavior = "player", this.root = a("div", "imp-related"), this.root.hidden = !0, this.heading = a("div", "imp-related__title"), this.grid = a("div", "imp-related__grid");
1320
+ this.player = t, this.clickBehavior = "player", this.root = o("div", "imp-related"), this.root.hidden = !0, this.heading = o("div", "imp-related__title"), this.grid = o("div", "imp-related__grid");
1235
1321
  const e = f("imp-related__close", "Close", t.icons.close);
1236
1322
  e.addEventListener("click", () => this.hide()), this.root.append(e, this.heading, this.grid);
1237
1323
  }
@@ -1243,12 +1329,12 @@ class bt {
1243
1329
  }
1244
1330
  }
1245
1331
  buildCard(t) {
1246
- const e = a("button", "imp-related__card", { type: "button" }), i = a("div", "imp-related__thumb");
1332
+ const e = o("button", "imp-related__card", { type: "button" }), i = o("div", "imp-related__thumb");
1247
1333
  if (t.poster && (i.style.backgroundImage = `url("${t.poster}")`), t.duration) {
1248
- const n = a("span", "imp-related__duration");
1334
+ const n = o("span", "imp-related__duration");
1249
1335
  n.textContent = t.duration, i.append(n);
1250
1336
  }
1251
- const s = a("div", "imp-related__card-title");
1337
+ const s = o("div", "imp-related__card-title");
1252
1338
  return s.textContent = t.title, e.append(i, s), e.addEventListener("click", () => {
1253
1339
  this.player.emit("relatedclick", { item: t }), this.hide(), this.activate(t);
1254
1340
  }), e;
@@ -1278,39 +1364,76 @@ class bt {
1278
1364
  this.root.hidden = !0;
1279
1365
  }
1280
1366
  }
1281
- class yt {
1367
+ class bt {
1368
+ constructor(t) {
1369
+ this.player = t, this.root = o("div", "imp-upnext"), this.root.hidden = !0;
1370
+ }
1371
+ get visible() {
1372
+ return !this.root.hidden;
1373
+ }
1374
+ show() {
1375
+ const t = this.player.nextSource;
1376
+ if (!t) return;
1377
+ const { labels: e } = this.player;
1378
+ this.root.textContent = "";
1379
+ const i = o("div", "imp-upnext__card"), s = o("div", "imp-upnext__kicker");
1380
+ if (s.textContent = e.next, i.append(s), t.poster) {
1381
+ const r = o("button", "imp-upnext__thumb", { type: "button", "aria-label": e.playNext });
1382
+ if (r.style.backgroundImage = `url("${t.poster}")`, t.duration) {
1383
+ const h = o("span", "imp-upnext__duration");
1384
+ h.textContent = x(t.duration), r.append(h);
1385
+ }
1386
+ const a = o("span", "imp-upnext__thumb-play");
1387
+ a.innerHTML = this.player.icons.bigPlay, r.append(a), r.addEventListener("click", () => this.player.next()), i.append(r);
1388
+ }
1389
+ if (t.title) {
1390
+ const r = o("div", "imp-upnext__title");
1391
+ r.textContent = t.title, i.append(r);
1392
+ }
1393
+ if (t.previewMeta?.length) {
1394
+ const r = o("div", "imp-upnext__meta");
1395
+ r.textContent = t.previewMeta.join(" · "), i.append(r);
1396
+ }
1397
+ const n = o("button", "imp-upnext__btn", { type: "button" });
1398
+ n.textContent = e.playNext, n.addEventListener("click", () => this.player.next()), i.append(n), this.root.append(i), this.root.hidden = !1;
1399
+ }
1400
+ hide() {
1401
+ this.root.hidden = !0;
1402
+ }
1403
+ }
1404
+ class kt {
1282
1405
  constructor(t, e = "sidebar", i) {
1283
- this.player = t, this.root = a("div", `imp-playlist imp-playlist--${e}`), this.root.hidden = !0;
1284
- const s = a("div", "imp-playlist__header"), n = a("div", "imp-playlist__heading");
1406
+ this.player = t, this.root = o("div", `imp-playlist imp-playlist--${e}`), this.root.hidden = !0;
1407
+ const s = o("div", "imp-playlist__header"), n = o("div", "imp-playlist__heading");
1285
1408
  if (i) {
1286
- const h = a("div", "imp-playlist__kicker");
1409
+ const h = o("div", "imp-playlist__kicker");
1287
1410
  h.textContent = t.labels.playlist;
1288
- const c = a("div", "imp-playlist__name");
1411
+ const c = o("div", "imp-playlist__name");
1289
1412
  c.textContent = i, n.append(h, c);
1290
1413
  } else
1291
1414
  n.textContent = t.labels.playlist;
1292
- const r = a("div", "imp-playlist__tools");
1415
+ const r = o("div", "imp-playlist__tools");
1293
1416
  this.shuffleBtn = f("imp-playlist__shuffle", t.labels.shuffle, t.icons.shuffle), this.shuffleBtn.addEventListener("click", () => {
1294
1417
  t.setShuffle(!t.shuffle), this.syncModes();
1295
1418
  }), this.repeatBtn = f("imp-playlist__repeat", t.labels.repeat, t.icons.repeat), this.repeatBtn.addEventListener("click", () => {
1296
1419
  t.setRepeat(!t.repeat), this.syncModes();
1297
1420
  });
1298
- const l = f("imp-playlist__close", "Close", t.icons.close);
1299
- l.addEventListener("click", () => this.hide()), r.append(this.shuffleBtn, this.repeatBtn, l), s.append(n, r), this.list = a("div", "imp-playlist__list"), this.root.append(s, this.list), this.rebuild(), this.syncModes();
1421
+ const a = f("imp-playlist__close", "Close", t.icons.close);
1422
+ a.addEventListener("click", () => this.hide()), r.append(this.shuffleBtn, this.repeatBtn, a), s.append(n, r), this.list = o("div", "imp-playlist__list"), this.root.append(s, this.list), this.rebuild(), this.syncModes();
1300
1423
  }
1301
1424
  syncModes() {
1302
1425
  this.shuffleBtn.classList.toggle("imp-btn--active", this.player.shuffle), this.repeatBtn.classList.toggle("imp-btn--active", this.player.repeat);
1303
1426
  }
1304
1427
  rebuild() {
1305
1428
  this.list.textContent = "", this.player.playlist.forEach((t, e) => {
1306
- const i = a("button", "imp-playlist__item", { type: "button" });
1429
+ const i = o("button", "imp-playlist__item", { type: "button" });
1307
1430
  e === this.player.index && i.classList.add("imp-playlist__item--active");
1308
- const s = a("div", "imp-playlist__thumb");
1431
+ const s = o("div", "imp-playlist__thumb");
1309
1432
  t.poster && (s.style.backgroundImage = `url("${t.poster}")`);
1310
- const n = a("div", "imp-playlist__meta"), r = a("div", "imp-playlist__title");
1433
+ const n = o("div", "imp-playlist__meta"), r = o("div", "imp-playlist__title");
1311
1434
  if (r.textContent = t.title ?? `#${e + 1}`, n.append(r), t.duration) {
1312
- const l = a("div", "imp-playlist__duration");
1313
- l.textContent = x(t.duration), n.append(l);
1435
+ const a = o("div", "imp-playlist__duration");
1436
+ a.textContent = x(t.duration), n.append(a);
1314
1437
  }
1315
1438
  i.append(s, n), i.addEventListener("click", () => {
1316
1439
  this.player.playItem(e), window.innerWidth <= 767 && this.hide();
@@ -1330,28 +1453,28 @@ class yt {
1330
1453
  this.visible ? this.hide() : this.show();
1331
1454
  }
1332
1455
  }
1333
- class kt {
1456
+ class wt {
1334
1457
  constructor(t, e = "bottom") {
1335
- this.player = t, this.items = [], this.root = a("div", `imp-playlist imp-scenes imp-playlist--${e}`), this.root.hidden = !0;
1336
- const i = a("div", "imp-playlist__header"), s = a("span");
1458
+ this.player = t, this.items = [], this.root = o("div", `imp-playlist imp-scenes imp-playlist--${e}`), this.root.hidden = !0;
1459
+ const i = o("div", "imp-playlist__header"), s = o("span");
1337
1460
  s.textContent = t.labels.scenes;
1338
1461
  const n = f("imp-playlist__close", "Close", t.icons.close);
1339
- n.addEventListener("click", () => this.hide()), i.append(s, n), this.list = a("div", "imp-playlist__list"), this.root.append(i, this.list), t.on("chapterchange", () => this.syncActive());
1462
+ n.addEventListener("click", () => this.hide()), i.append(s, n), this.list = o("div", "imp-playlist__list"), this.root.append(i, this.list), t.on("chapterchange", () => this.syncActive());
1340
1463
  }
1341
1464
  /** Re-render from the player's current chapters + thumbnail track. */
1342
1465
  rebuild() {
1343
1466
  this.list.textContent = "", this.items = [];
1344
1467
  const t = this.player.thumbnails;
1345
1468
  for (const e of this.player.chapterList) {
1346
- const i = a("button", "imp-playlist__item", { type: "button" }), s = a("div", "imp-playlist__thumb"), n = t?.cueAt(e.start + 0.01) ?? null;
1469
+ const i = o("button", "imp-playlist__item", { type: "button" }), s = o("div", "imp-playlist__thumb"), n = t?.cueAt(e.start + 0.01) ?? null;
1347
1470
  if (n?.xywh) {
1348
- const c = a("div", "imp-scenes__sprite");
1471
+ const c = o("div", "imp-scenes__sprite");
1349
1472
  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);
1350
1473
  } else n && (s.style.backgroundImage = `url("${n.src}")`);
1351
- const r = a("div", "imp-playlist__meta"), l = a("div", "imp-playlist__title");
1352
- l.textContent = e.title || x(e.start);
1353
- const h = a("div", "imp-playlist__duration");
1354
- h.textContent = x(e.start), r.append(l, h), i.append(s, r), i.addEventListener("click", () => {
1474
+ const r = o("div", "imp-playlist__meta"), a = o("div", "imp-playlist__title");
1475
+ a.textContent = e.title || x(e.start);
1476
+ const h = o("div", "imp-playlist__duration");
1477
+ h.textContent = x(e.start), r.append(a, h), i.append(s, r), i.addEventListener("click", () => {
1355
1478
  this.player.seek(e.start), this.player.play(), window.innerWidth <= 767 && this.hide();
1356
1479
  }), this.list.append(i), this.items.push(i);
1357
1480
  }
@@ -1376,10 +1499,10 @@ class kt {
1376
1499
  this.visible ? this.hide() : this.show();
1377
1500
  }
1378
1501
  }
1379
- function W(o) {
1380
- return o ? typeof o == "string" ? [{ src: o, label: "Subtitles" }] : Array.isArray(o) ? o : [o] : [];
1502
+ function G(l) {
1503
+ return l ? typeof l == "string" ? [{ src: l, label: "Subtitles" }] : Array.isArray(l) ? l : [l] : [];
1381
1504
  }
1382
- const wt = {
1505
+ const xt = {
1383
1506
  play: !0,
1384
1507
  progress: !0,
1385
1508
  time: !0,
@@ -1392,38 +1515,41 @@ const wt = {
1392
1515
  // legacy alias for `speed`; resolved in the constructor
1393
1516
  quality: "gear",
1394
1517
  scenes: !0,
1518
+ sceneTypes: !0,
1395
1519
  heatmap: !0,
1396
1520
  subtitles: "gear",
1397
1521
  seekButtons: !0,
1398
1522
  // Prev/seek/play/next as a centered overlay on every viewport.
1399
1523
  seekPlacement: "overlay",
1524
+ order: [],
1525
+ // empty → built-in right-cluster order
1400
1526
  playlist: !0,
1401
1527
  hidePrev: !0,
1402
1528
  nextPreview: !0,
1403
1529
  hideDelay: 2500
1404
1530
  };
1405
- class Bt {
1531
+ class Tt {
1406
1532
  constructor(t, e = {}) {
1407
- this.scrubbing = !1, this.emitter = new X(), 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.decodeRecoveries = 0, this.adManager = null, this.playedOnce = !1, this.idleTimer = null, this.destroyed = !1;
1533
+ this.scrubbing = !1, this.emitter = new K(), this.abort = new AbortController(), this.sources = [], this.currentIndex = -1, this.sourceController = null, this.loadToken = 0, this.chapters = [], this.currentChapter = null, this.sceneGroupList = [], this.activeSceneGroupId = "", this.progressiveQuality = -1, this.thumbTrack = null, this.shuffleMode = !1, this.autoAdvanceEnabled = !0, this.decodeRecoveries = 0, this.adManager = null, this.playedOnce = !1, this.idleTimer = null, this.destroyed = !1;
1408
1534
  const i = typeof t == "string" ? document.querySelector(t) : t;
1409
1535
  if (!i) throw new Error(`[itube-player] mount target not found: ${String(t)}`);
1410
- this.mount = i, this.options = e, this.labels = { ...V, ...J(e.language), ...e.labels }, this.icons = { ...K, ...e.icons };
1536
+ this.mount = i, this.options = e, this.labels = { ...q, ...Y(e.language), ...e.labels }, this.icons = { ...J, ...e.icons };
1411
1537
  const s = e.controls ?? {}, n = s.speed ?? s.settings ?? "gear";
1412
- this.controlsOptions = { ...wt, ...s, speed: n, settings: n }, this.actionsOptions = e.actions ?? {}, this.playbackRates = e.playbackRates ?? [0.5, 0.75, 1, 1.25, 1.5, 2], this.seekStep = e.seekStep ?? 15, 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 = a("div", "imp-player", { tabindex: "0" }), e.className && this.container.classList.add(e.className);
1538
+ this.controlsOptions = { ...xt, ...s, speed: n, settings: n }, this.actionsOptions = e.actions ?? {}, this.playbackRates = e.playbackRates ?? [0.5, 0.75, 1, 1.25, 1.5, 2], this.seekStep = e.seekStep ?? 15, this.playlistOptions = { title: "", autoAdvance: !0, loop: !1, shuffle: !1, startIndex: 0, layout: "sidebar", ...e.playlist }, this.shuffleMode = this.playlistOptions.shuffle, this.autoAdvanceEnabled = this.playlistOptions.autoAdvance, this.sources = e.source ? Array.isArray(e.source) ? [...e.source] : [e.source] : [], this.container = o("div", "imp-player", { tabindex: "0" }), e.className && this.container.classList.add(e.className);
1413
1539
  const r = e.styling;
1414
1540
  if (r?.themeColor && this.container.style.setProperty("--imp-accent", r.themeColor), r?.likeColor && this.container.style.setProperty("--imp-like", r.likeColor), r?.dislikeColor && this.container.style.setProperty("--imp-dislike", r.dislikeColor), r?.borderRadius !== void 0) {
1415
1541
  const p = typeof r.borderRadius == "number" ? `${r.borderRadius}px` : r.borderRadius;
1416
1542
  this.container.style.setProperty("--imp-radius", p);
1417
1543
  }
1418
- r?.playButtonStyle === "inverted" && this.container.classList.add("imp-player--play-inverted"), this.video = a("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 = w(e.volume ?? 1, 0, 1);
1419
- const l = a("div", "imp-layer");
1420
- this.spinner = a("div", "imp-spinner"), this.spinner.hidden = !0, this.errorBox = a("div", "imp-error"), this.errorBox.hidden = !0, this.pauseScreen = new gt(this.labels), this.poster = new ft(this), this.related = new bt(this), this.related.setOptions(e.related), this.controls = new vt(this), this.playlistPanel = new yt(this, this.playlistOptions.layout, this.playlistOptions.title || void 0), this.scenesPanel = new kt(this, e.scenes?.layout ?? "bottom");
1421
- const h = a("div", "imp-layer__middle");
1544
+ r?.playButtonStyle === "inverted" && this.container.classList.add("imp-player--play-inverted"), this.video = o("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 = w(e.volume ?? 1, 0, 1);
1545
+ const a = o("div", "imp-layer");
1546
+ this.spinner = o("div", "imp-spinner"), this.spinner.hidden = !0, this.errorBox = o("div", "imp-error"), this.errorBox.hidden = !0, this.pauseScreen = new gt(this.labels), this.poster = new ft(this), this.related = new yt(this), this.related.setOptions(e.related), this.upNext = new bt(this), this.controls = new vt(this), this.playlistPanel = new kt(this, this.playlistOptions.layout, this.playlistOptions.title || void 0), this.scenesPanel = new wt(this, e.scenes?.layout ?? "bottom");
1547
+ const h = o("div", "imp-layer__middle");
1422
1548
  h.append(this.spinner, this.errorBox, this.controls.center);
1423
- const c = a("div", "imp-layer__bottom");
1424
- c.append(this.controls.root), l.append(this.pauseScreen.root, this.related.root, h, c), this.container.append(this.video, l, 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)) : e.pauseScreen && typeof e.pauseScreen == "object" && this.pauseScreen.setVisibility(e.pauseScreen);
1549
+ const c = o("div", "imp-layer__bottom");
1550
+ c.append(this.controls.root), a.append(this.pauseScreen.root, this.related.root, this.upNext.root, h, c), this.container.append(this.video, a, 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)) : e.pauseScreen && typeof e.pauseScreen == "object" && this.pauseScreen.setVisibility(e.pauseScreen);
1425
1551
  const u = e.adConfig ?? e.ads;
1426
- u && u.adList.length > 0 && (this.adManager = new rt(
1552
+ u && u.adList.length > 0 && (this.adManager = new ot(
1427
1553
  { container: this.container, contentVideo: this.video, emitter: this.emitter, labels: this.labels, closeIcon: this.icons.close },
1428
1554
  u
1429
1555
  )), this.emitter.on("error", ({ message: p }) => {
@@ -1542,7 +1668,7 @@ class Bt {
1542
1668
  }
1543
1669
  // === subtitles ========================================================
1544
1670
  get subtitleTracks() {
1545
- return W(this.source?.subtitles);
1671
+ return G(this.source?.subtitles);
1546
1672
  }
1547
1673
  get activeSubtitle() {
1548
1674
  const t = this.video.textTracks;
@@ -1653,6 +1779,23 @@ class Bt {
1653
1779
  get chapter() {
1654
1780
  return this.currentChapter;
1655
1781
  }
1782
+ // === scene types (groups) =============================================
1783
+ /** Typed scene breakdowns attached to the current source (may be empty). */
1784
+ get sceneTypes() {
1785
+ return this.sceneGroupList;
1786
+ }
1787
+ /** Currently selected scene type, or null when there are no groups. */
1788
+ get activeSceneType() {
1789
+ return this.sceneGroupList.find((t) => t.id === this.activeSceneGroupId) ?? null;
1790
+ }
1791
+ /** Switch the active scene type — rebuilds the timeline segments + scenes panel. */
1792
+ setSceneGroup(t) {
1793
+ t === this.activeSceneGroupId || !this.sceneGroupList.some((e) => e.id === t) || (this.activeSceneGroupId = t, this.applyActiveSceneGroup(), this.emitter.emit("scenetypechange", { group: this.activeSceneType }));
1794
+ }
1795
+ /** Normalize the active group's scenes into `chapters` and refresh the UI. */
1796
+ applyActiveSceneGroup() {
1797
+ this.chapters = W(this.activeSceneType?.scenes ?? [], this.video.duration), this.currentChapter = null, this.controls.progress.setChapters(this.chapters), this.controls.syncFeatureButtons(), this.scenesPanel.visible && this.scenesPanel.rebuild();
1798
+ }
1656
1799
  // === actions ==========================================================
1657
1800
  /**
1658
1801
  * Share the current video: native share sheet when the device has one,
@@ -1694,11 +1837,11 @@ class Bt {
1694
1837
  async loadItem(t, e) {
1695
1838
  const i = ++this.loadToken, s = this.sources[t];
1696
1839
  if (!s) return;
1697
- this.currentIndex = t, this.sourceController?.destroy(), this.sourceController = null, this.chapters = [], this.currentChapter = null, this.progressiveQuality = -1, this.playedOnce = !1;
1840
+ this.currentIndex = t, this.sourceController?.destroy(), this.sourceController = null, this.chapters = [], this.currentChapter = null, this.sceneGroupList = [], this.activeSceneGroupId = "", this.progressiveQuality = -1, this.playedOnce = !1;
1698
1841
  for (const h of [...this.video.querySelectorAll("track")]) h.remove();
1699
- this.adManager?.resetForNewSource(), this.related.hide(), this.pauseScreen.hide(), this.scenesPanel.hide(), this.thumbTrack = null, this.errorBox.hidden = !0, this.decodeRecoveries = 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();
1700
- for (const h of W(s.subtitles)) {
1701
- const c = a("track", "", {
1842
+ this.adManager?.resetForNewSource(), this.related.hide(), this.upNext.hide(), this.pauseScreen.hide(), this.scenesPanel.hide(), this.thumbTrack = null, this.errorBox.hidden = !0, this.decodeRecoveries = 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();
1843
+ for (const h of G(s.subtitles)) {
1844
+ const c = o("track", "", {
1702
1845
  kind: "subtitles",
1703
1846
  src: h.src,
1704
1847
  label: h.label,
@@ -1712,7 +1855,7 @@ class Bt {
1712
1855
  this.progressiveQuality = h, n = s.qualities[h].src, r = s.qualities[h].type ?? r;
1713
1856
  }
1714
1857
  this.video.preload = this.adManager?.hasPendingPreRoll ? "auto" : "metadata";
1715
- const l = await ct(this.video, n, r, {
1858
+ const a = await ct(this.video, n, r, {
1716
1859
  onLevels: () => {
1717
1860
  i === this.loadToken && this.controls.syncFeatureButtons();
1718
1861
  },
@@ -1720,15 +1863,21 @@ class Bt {
1720
1863
  onError: (h, c) => this.emitter.emit("error", { message: h, cause: c })
1721
1864
  });
1722
1865
  if (i !== this.loadToken) {
1723
- l.destroy();
1866
+ a.destroy();
1724
1867
  return;
1725
1868
  }
1726
- if (this.sourceController = l, this.emitter.emit("sourcechange", { source: s, index: t }), s.thumbnails && I.load(s.thumbnails).then((h) => {
1869
+ if (this.sourceController = a, this.emitter.emit("sourcechange", { source: s, index: t }), s.thumbnails && F.load(s.thumbnails).then((h) => {
1727
1870
  i === this.loadToken && (this.thumbTrack = h, this.controls.progress.setThumbnails(h), this.scenesPanel.visible && this.scenesPanel.rebuild());
1728
- }).catch((h) => this.emitter.emit("error", { message: "Failed to load thumbnails track", cause: h })), s.chapters) {
1871
+ }).catch((h) => this.emitter.emit("error", { message: "Failed to load thumbnails track", cause: h })), s.sceneGroups && s.sceneGroups.length > 0) {
1872
+ this.sceneGroupList = s.sceneGroups, this.activeSceneGroupId = s.sceneGroups[0].id;
1873
+ const h = () => {
1874
+ i === this.loadToken && this.applyActiveSceneGroup();
1875
+ };
1876
+ Number.isFinite(this.video.duration) && this.video.duration > 0 ? h() : this.video.addEventListener("loadedmetadata", h, { once: !0, signal: this.abort.signal });
1877
+ } else if (s.chapters) {
1729
1878
  const h = (c) => {
1730
1879
  const u = () => {
1731
- i === this.loadToken && (this.chapters = ot(c, this.video.duration), this.controls.progress.setChapters(this.chapters), this.controls.syncFeatureButtons(), this.scenesPanel.visible && this.scenesPanel.rebuild());
1880
+ i === this.loadToken && (this.chapters = W(c, this.video.duration), this.controls.progress.setChapters(this.chapters), this.controls.syncFeatureButtons(), this.scenesPanel.visible && this.scenesPanel.rebuild());
1732
1881
  };
1733
1882
  Number.isFinite(this.video.duration) && this.video.duration > 0 ? u() : this.video.addEventListener("loadedmetadata", u, { once: !0, signal: this.abort.signal });
1734
1883
  };
@@ -1736,7 +1885,7 @@ class Bt {
1736
1885
  }
1737
1886
  if (s.heatmap && s.heatmap.length > 0 && this.controlsOptions.heatmap) {
1738
1887
  const h = s.heatmap, c = () => {
1739
- i === this.loadToken && this.controls.progress.setHeatmap(Y(h, this.video.duration));
1888
+ i === this.loadToken && this.controls.progress.setHeatmap(tt(h, this.video.duration));
1740
1889
  };
1741
1890
  Number.isFinite(this.video.duration) && this.video.duration > 0 ? c() : this.video.addEventListener("loadedmetadata", c, { once: !0, signal: this.abort.signal });
1742
1891
  }
@@ -1747,7 +1896,7 @@ class Bt {
1747
1896
  bindVideoEvents() {
1748
1897
  const { signal: t } = this.abort, e = this.video;
1749
1898
  e.addEventListener("play", () => {
1750
- this.playedOnce = !0, this.poster.hide(), this.pauseScreen.hide(), this.related.visible || this.related.hide(), this.related.hide(), e.readyState < 3 && (this.spinner.hidden = !1), this.emitter.emit("play", void 0), this.scheduleIdle();
1899
+ this.playedOnce = !0, this.poster.hide(), this.pauseScreen.hide(), this.related.visible || this.related.hide(), this.related.hide(), this.upNext.hide(), e.readyState < 3 && (this.spinner.hidden = !1), this.emitter.emit("play", void 0), this.scheduleIdle();
1751
1900
  }, { signal: t }), e.addEventListener("pause", () => {
1752
1901
  this.spinner.hidden = !0, 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());
1753
1902
  }, { signal: t }), e.addEventListener("ended", () => {
@@ -1774,7 +1923,7 @@ class Bt {
1774
1923
  this.decodeRecoveries++;
1775
1924
  const n = e.currentTime, r = !e.paused;
1776
1925
  e.load();
1777
- const l = () => {
1926
+ const a = () => {
1778
1927
  try {
1779
1928
  e.currentTime = n;
1780
1929
  } catch {
@@ -1782,7 +1931,7 @@ class Bt {
1782
1931
  r && e.play().catch(() => {
1783
1932
  });
1784
1933
  };
1785
- e.readyState >= 1 ? l() : e.addEventListener("loadedmetadata", l, { once: !0, signal: this.abort.signal });
1934
+ e.readyState >= 1 ? a() : e.addEventListener("loadedmetadata", a, { once: !0, signal: this.abort.signal });
1786
1935
  return;
1787
1936
  }
1788
1937
  this.emitter.emit("error", { message: i.message || `Media error (code ${i.code})`, cause: i });
@@ -1792,13 +1941,24 @@ class Bt {
1792
1941
  }
1793
1942
  async handleEnded() {
1794
1943
  if (this.emitter.emit("ended", void 0), this.showControlsNow(), !(this.adManager && (await this.adManager.playPostRoll(), this.destroyed))) {
1795
- if (this.playlistOptions.autoAdvance && this.hasNext) {
1944
+ if (this.autoAdvanceEnabled && this.hasNext) {
1796
1945
  this.next();
1797
1946
  return;
1798
1947
  }
1948
+ if (!this.autoAdvanceEnabled && this.hasNext) {
1949
+ this.upNext.show();
1950
+ return;
1951
+ }
1799
1952
  (this.options.related?.showOn?.includes("ended") ?? !!this.options.related) && this.related.show();
1800
1953
  }
1801
1954
  }
1955
+ /** Autoplay-next state (gear switch). Off → show the up-next card on end. */
1956
+ get autoAdvance() {
1957
+ return this.autoAdvanceEnabled;
1958
+ }
1959
+ setAutoAdvance(t) {
1960
+ this.autoAdvanceEnabled = t, t && this.upNext.hide();
1961
+ }
1802
1962
  // === controls visibility ==============================================
1803
1963
  bindIdleHide() {
1804
1964
  const { signal: t } = this.abort, e = () => {
@@ -1863,22 +2023,22 @@ class Bt {
1863
2023
  }
1864
2024
  }
1865
2025
  export {
1866
- X as Emitter,
1867
- Bt as Player,
1868
- I as ThumbnailTrack,
1869
- Y as buildHeatmapValues,
2026
+ K as Emitter,
2027
+ Tt as Player,
2028
+ F as ThumbnailTrack,
2029
+ tt as buildHeatmapValues,
1870
2030
  U as chapterAt,
1871
- K as defaultIcons,
1872
- V as defaultLabels,
2031
+ J as defaultIcons,
2032
+ q as defaultLabels,
1873
2033
  x as formatTime,
1874
- J as getLocale,
1875
- tt as heatmapPath,
2034
+ Y as getLocale,
2035
+ et as heatmapPath,
1876
2036
  at as isHlsSource,
1877
2037
  lt as loadChaptersVtt,
1878
- ot as normalizeChapters,
1879
- Lt as registerLocale,
1880
- St as registerLocales,
1881
- _t as registeredLanguages,
1882
- et as resolveVast
2038
+ W as normalizeChapters,
2039
+ _t as registerLocale,
2040
+ Lt as registerLocales,
2041
+ Bt as registeredLanguages,
2042
+ it as resolveVast
1883
2043
  };
1884
2044
  //# sourceMappingURL=core.js.map