@ssgc/hls-player 0.1.1

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.
@@ -0,0 +1,936 @@
1
+ import { jsxs as C, jsx as b } from "react/jsx-runtime";
2
+ import { useRef as k, useState as L, useEffect as x, useCallback as J, useMemo as ae } from "react";
3
+ import { Pause as De, Play as Ie } from "lucide-react";
4
+ import M from "hls.js";
5
+ function Ne({
6
+ enabled: e,
7
+ label: r,
8
+ hint: n,
9
+ live: t = !1,
10
+ onGoLive: i
11
+ }) {
12
+ return /* @__PURE__ */ C(
13
+ "button",
14
+ {
15
+ type: "button",
16
+ onClick: (a) => {
17
+ a.stopPropagation(), i();
18
+ },
19
+ onMouseDown: (a) => a.stopPropagation(),
20
+ disabled: !e,
21
+ title: n,
22
+ style: {
23
+ display: "flex",
24
+ alignItems: "center",
25
+ gap: 6,
26
+ padding: "5px 10px",
27
+ background: e && !t ? "rgba(211,47,47,0.85)" : "rgba(0,0,0,0.55)",
28
+ color: e ? "#fff" : "rgba(255,255,255,0.45)",
29
+ border: "1px solid rgba(255,255,255,0.22)",
30
+ borderRadius: 6,
31
+ cursor: e ? "pointer" : "default",
32
+ fontSize: 12,
33
+ fontWeight: 600,
34
+ letterSpacing: "0.02em",
35
+ whiteSpace: "nowrap",
36
+ backdropFilter: "blur(4px)",
37
+ WebkitBackdropFilter: "blur(4px)"
38
+ },
39
+ children: [
40
+ !t && /* @__PURE__ */ b(
41
+ "span",
42
+ {
43
+ style: {
44
+ width: 7,
45
+ height: 7,
46
+ borderRadius: "50%",
47
+ background: e ? "#fff" : "rgba(255,255,255,0.45)"
48
+ }
49
+ }
50
+ ),
51
+ r
52
+ ]
53
+ }
54
+ );
55
+ }
56
+ const Ae = [1, 2, 3, 5, 10], Be = 30, Fe = 600;
57
+ function pe(e) {
58
+ return Math.min(Be * e, Fe);
59
+ }
60
+ const Ce = 60 * 1e3 * 1e3, Oe = 600 * 1e3 * 1e3;
61
+ function ge(e) {
62
+ return Math.min(Ce * e, Oe);
63
+ }
64
+ const He = 5, se = 2, le = 250;
65
+ function Ue(e) {
66
+ const r = e.currentTime;
67
+ for (let n = 0; n < e.buffered.length; n += 1)
68
+ if (e.buffered.start(n) <= r + 0.01 && r <= e.buffered.end(n))
69
+ return Math.max(0, e.buffered.end(n) - r);
70
+ return 0;
71
+ }
72
+ function me(e) {
73
+ return e.seekable.length === 0 ? null : e.seekable.end(e.seekable.length - 1);
74
+ }
75
+ function ce(e) {
76
+ const r = me(e);
77
+ return r === null ? null : Math.max(0, r - e.currentTime);
78
+ }
79
+ function V(e) {
80
+ const r = me(e);
81
+ r !== null && (e.currentTime = Math.max(0, r - 1));
82
+ }
83
+ function We() {
84
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
85
+ }
86
+ function Q(e) {
87
+ if (!e || !isFinite(e) || e <= 0)
88
+ return "0:00";
89
+ const r = Math.floor(e), n = Math.floor(r / 60), t = r % 60;
90
+ return `${n}:${t.toString().padStart(2, "0")}`;
91
+ }
92
+ function ue(e) {
93
+ return `${e.replace(/#reload\d*$/, "")}#reload${Date.now()}`;
94
+ }
95
+ function $e(e) {
96
+ return e.replace(/#reload\d*$/, "");
97
+ }
98
+ const de = 300, je = 210;
99
+ function ze({
100
+ videoRef: e,
101
+ isPlaying: r,
102
+ currentTime: n,
103
+ duration: t,
104
+ isLive: i,
105
+ goLive: l
106
+ }) {
107
+ const a = k(null), [p, d] = L(0);
108
+ x(() => {
109
+ const u = a.current;
110
+ if (!u || typeof ResizeObserver > "u")
111
+ return;
112
+ const o = new ResizeObserver(([g]) => {
113
+ d(g.contentRect.width);
114
+ });
115
+ return o.observe(u), () => o.disconnect();
116
+ }, []);
117
+ const c = t > 0, f = p > 0, m = !f || p >= de, y = !f || p >= je, h = () => {
118
+ const u = e.current;
119
+ u && (u.paused ? u.play().catch(() => {
120
+ }) : u.pause());
121
+ }, S = (u) => {
122
+ const o = e.current;
123
+ !o || !isFinite(o.duration) || (o.currentTime = Math.max(0, Math.min(1, u)) * o.duration);
124
+ };
125
+ return /* @__PURE__ */ C(
126
+ "div",
127
+ {
128
+ ref: a,
129
+ style: {
130
+ position: "absolute",
131
+ left: 12,
132
+ right: 12,
133
+ bottom: 12,
134
+ display: "flex",
135
+ alignItems: "center",
136
+ gap: f && p < de ? 8 : 12,
137
+ minWidth: 0,
138
+ pointerEvents: "auto"
139
+ },
140
+ children: [
141
+ /* @__PURE__ */ b(
142
+ "button",
143
+ {
144
+ type: "button",
145
+ onClick: (u) => {
146
+ u.stopPropagation(), h();
147
+ },
148
+ onMouseDown: (u) => u.stopPropagation(),
149
+ style: {
150
+ display: "flex",
151
+ flex: "0 0 auto",
152
+ background: "rgba(0,0,0,0.6)",
153
+ color: "#fff",
154
+ border: "none",
155
+ padding: "6px 10px",
156
+ borderRadius: 6,
157
+ cursor: "pointer"
158
+ },
159
+ "aria-label": r ? "Pause" : "Play",
160
+ title: r ? "Pause" : "Play",
161
+ children: r ? /* @__PURE__ */ b(De, { size: 16 }) : /* @__PURE__ */ b(Ie, { size: 16 })
162
+ }
163
+ ),
164
+ c ? /* @__PURE__ */ b(
165
+ "div",
166
+ {
167
+ onClick: (u) => {
168
+ u.stopPropagation();
169
+ const o = u.currentTarget.getBoundingClientRect();
170
+ S((u.clientX - o.left) / o.width);
171
+ },
172
+ onMouseDown: (u) => u.stopPropagation(),
173
+ onKeyDown: (u) => {
174
+ const o = e.current;
175
+ if (!o || !isFinite(o.duration))
176
+ return;
177
+ const g = o.duration > 0 ? o.duration * 0.02 : 5;
178
+ u.key === "ArrowRight" ? (u.stopPropagation(), o.currentTime = Math.min(
179
+ o.duration,
180
+ o.currentTime + g
181
+ )) : u.key === "ArrowLeft" && (u.stopPropagation(), o.currentTime = Math.max(0, o.currentTime - g));
182
+ },
183
+ role: "slider",
184
+ tabIndex: 0,
185
+ "aria-label": "Seek",
186
+ "aria-valuemin": 0,
187
+ "aria-valuemax": t,
188
+ "aria-valuenow": n,
189
+ style: {
190
+ // Basis of zero with no minimum, so the bar gives up its width to
191
+ // the buttons and the clock instead of pushing them off the edge.
192
+ flex: "1 1 0",
193
+ minWidth: 0,
194
+ height: 8,
195
+ background: "rgba(255,255,255,0.12)",
196
+ borderRadius: 6,
197
+ position: "relative",
198
+ overflow: "hidden",
199
+ cursor: "pointer"
200
+ },
201
+ title: "Seek",
202
+ children: /* @__PURE__ */ b(
203
+ "div",
204
+ {
205
+ style: {
206
+ position: "absolute",
207
+ left: 0,
208
+ top: 0,
209
+ bottom: 0,
210
+ // Live streams can report a position a shade past the duration;
211
+ // clamped so the fill never runs past its track.
212
+ width: `${t > 0 ? Math.min(100, Math.max(0, n / t * 100)) : 0}%`,
213
+ background: "rgba(66,153,225,0.85)",
214
+ borderRadius: 6
215
+ }
216
+ }
217
+ )
218
+ }
219
+ ) : /* @__PURE__ */ b("span", { style: { flex: "1 1 0", minWidth: 0 } }),
220
+ (y || !c && i) && /* @__PURE__ */ b(
221
+ "div",
222
+ {
223
+ style: {
224
+ flex: "0 0 auto",
225
+ color: "#fff",
226
+ fontSize: 12,
227
+ fontVariantNumeric: "tabular-nums",
228
+ whiteSpace: "nowrap"
229
+ },
230
+ children: c ? m ? `${Q(n)}` : Q(n) : i ? "Live" : Q(n)
231
+ }
232
+ ),
233
+ l && /* @__PURE__ */ b("div", { style: { display: "flex", flex: "0 0 auto" }, children: l })
234
+ ]
235
+ }
236
+ );
237
+ }
238
+ function Ge() {
239
+ return /* @__PURE__ */ C(
240
+ "svg",
241
+ {
242
+ width: "14",
243
+ height: "14",
244
+ viewBox: "0 0 24 24",
245
+ fill: "none",
246
+ stroke: "currentColor",
247
+ strokeWidth: "2",
248
+ strokeLinecap: "round",
249
+ "aria-hidden": "true",
250
+ children: [
251
+ /* @__PURE__ */ b("path", { d: "M5 16a7 7 0 0 1 14 0" }),
252
+ /* @__PURE__ */ b("line", { x1: "12", y1: "16", x2: "9", y2: "9" }),
253
+ /* @__PURE__ */ b("circle", { cx: "12", cy: "16", r: "1.5", fill: "currentColor", stroke: "none" })
254
+ ]
255
+ }
256
+ );
257
+ }
258
+ function Xe({
259
+ speed: e,
260
+ setSpeed: r,
261
+ showSpeedMenu: n,
262
+ setShowSpeedMenu: t,
263
+ speedMenuRef: i,
264
+ options: l = Ae,
265
+ disabled: a = !1,
266
+ disabledHint: p = "Speed is not available on a live stream"
267
+ }) {
268
+ return /* @__PURE__ */ C(
269
+ "div",
270
+ {
271
+ ref: i,
272
+ style: { position: "absolute", top: 10, right: 10, zIndex: 2 },
273
+ onMouseDown: (d) => d.stopPropagation(),
274
+ children: [
275
+ /* @__PURE__ */ C(
276
+ "button",
277
+ {
278
+ type: "button",
279
+ onClick: (d) => {
280
+ d.stopPropagation(), t((c) => !c);
281
+ },
282
+ disabled: a,
283
+ title: a ? p : "Playback speed",
284
+ "aria-haspopup": "menu",
285
+ "aria-expanded": n && !a,
286
+ style: {
287
+ display: "flex",
288
+ alignItems: "center",
289
+ gap: 5,
290
+ padding: "5px 10px",
291
+ background: "rgba(0,0,0,0.60)",
292
+ color: a ? "rgba(255,255,255,0.40)" : "#fff",
293
+ border: "1px solid rgba(255,255,255,0.22)",
294
+ borderRadius: 6,
295
+ cursor: a ? "default" : "pointer",
296
+ fontSize: 13,
297
+ fontWeight: 600,
298
+ backdropFilter: "blur(4px)",
299
+ WebkitBackdropFilter: "blur(4px)"
300
+ },
301
+ children: [
302
+ /* @__PURE__ */ b(Ge, {}),
303
+ e,
304
+ "×"
305
+ ]
306
+ }
307
+ ),
308
+ n && !a && /* @__PURE__ */ b(
309
+ "div",
310
+ {
311
+ role: "menu",
312
+ style: {
313
+ position: "absolute",
314
+ top: "calc(100% + 6px)",
315
+ right: 0,
316
+ background: "rgba(18,18,18,0.92)",
317
+ border: "1px solid rgba(255,255,255,0.14)",
318
+ borderRadius: 8,
319
+ padding: "4px 0",
320
+ minWidth: 90,
321
+ backdropFilter: "blur(10px)",
322
+ WebkitBackdropFilter: "blur(10px)",
323
+ boxShadow: "0 4px 16px rgba(0,0,0,0.45)"
324
+ },
325
+ children: l.map((d) => /* @__PURE__ */ C(
326
+ "button",
327
+ {
328
+ type: "button",
329
+ role: "menuitemradio",
330
+ "aria-checked": d === e,
331
+ onClick: (c) => {
332
+ c.stopPropagation(), r(d), t(!1);
333
+ },
334
+ style: {
335
+ display: "flex",
336
+ alignItems: "center",
337
+ gap: 8,
338
+ width: "100%",
339
+ padding: "7px 14px",
340
+ background: d === e ? "rgba(26,115,232,0.55)" : "transparent",
341
+ border: "none",
342
+ color: d === e ? "#fff" : "rgba(255,255,255,0.75)",
343
+ fontWeight: d === e ? 700 : 400,
344
+ fontSize: 13,
345
+ cursor: "pointer",
346
+ textAlign: "left"
347
+ },
348
+ children: [
349
+ /* @__PURE__ */ b("span", { style: { width: 12, fontSize: 10, color: "#4caf50" }, children: d === e ? "✓" : "" }),
350
+ d,
351
+ "×"
352
+ ]
353
+ },
354
+ d
355
+ ))
356
+ }
357
+ )
358
+ ]
359
+ }
360
+ );
361
+ }
362
+ function Je({
363
+ src: e,
364
+ videoRef: r,
365
+ hlsRef: n,
366
+ engineRef: t,
367
+ speedRef: i,
368
+ applyPlaybackSpeed: l,
369
+ clearJumpScan: a,
370
+ autoPlay: p,
371
+ onError: d,
372
+ debug: c = !1
373
+ }) {
374
+ const f = k(c);
375
+ f.current = c;
376
+ const m = (...s) => {
377
+ f.current && console.warn(...s);
378
+ }, y = (...s) => {
379
+ f.current && console.error(...s);
380
+ }, [h, S] = L(e ?? ""), [u, o] = L(null), [g, w] = L(null), _ = k(null), O = k(!1), P = k(d);
381
+ return P.current = d, x(() => {
382
+ S(e ?? "");
383
+ }, [e]), x(() => {
384
+ const s = r.current;
385
+ if (!s || !h)
386
+ return;
387
+ const N = (E) => {
388
+ _.current = E, o(E);
389
+ }, U = (E) => {
390
+ t.current = E, w(E);
391
+ };
392
+ N(null), t.current = null, w(null), a(), n.current && (n.current.destroy(), n.current = null);
393
+ const W = () => {
394
+ t.current === "Native HLS" && _.current === null && N(!isFinite(s.duration)), l(s, i.current);
395
+ };
396
+ s.addEventListener("loadedmetadata", W);
397
+ const H = () => {
398
+ s.removeEventListener("loadedmetadata", W);
399
+ }, $ = $e(h);
400
+ if (!O.current && We() && s.canPlayType("application/vnd.apple.mpegurl")) {
401
+ U("Native HLS"), s.src = $, s.playbackRate = i.current, s.play().catch(() => {
402
+ });
403
+ let E = 0, T = 0, D = 0;
404
+ const A = setInterval(() => {
405
+ s.paused || (s.currentTime === E && s.readyState >= 2 ? (T += 1, T >= 2 && (_.current === !0 ? (m("[HlsPlayer/Native] Stall — jumping to live edge"), V(s)) : m("[HlsPlayer/Native] Stall — resuming recorded playback"), s.playbackRate = i.current, s.play().catch(() => {
406
+ }), T = 0)) : T = 0, E = s.currentTime);
407
+ }, 3e3), B = () => {
408
+ if (s.error?.code === 4 && /empty src/i.test(s.error?.message || "")) {
409
+ m("[HlsPlayer/Native] Ignoring spurious empty-src error", s.error);
410
+ return;
411
+ }
412
+ if (D += 1, m("[HlsPlayer/Native] Media error", s.error), P.current?.(s.error), D > 5) {
413
+ m(
414
+ "[HlsPlayer/Native] Giving up after repeated errors — canPlayType claimed HLS support but playback never worked. Falling back to HLS.js."
415
+ ), O.current = !0, clearInterval(A), s.removeEventListener("error", B), S((F) => ue(F));
416
+ return;
417
+ }
418
+ const R = s.src;
419
+ s.src = "", setTimeout(() => {
420
+ s.src = R, _.current === !0 && V(s), s.playbackRate = i.current, s.play().catch(() => {
421
+ });
422
+ }, 1500);
423
+ };
424
+ return s.addEventListener("error", B), () => {
425
+ clearInterval(A), s.removeEventListener("error", B), H(), s.src = "";
426
+ };
427
+ }
428
+ if (M.isSupported()) {
429
+ U("HLS.js");
430
+ let E = 0, T = null;
431
+ const D = (F) => {
432
+ T || (m("[HlsPlayer/HLS.js] Hard reload:", F), T = setTimeout(() => {
433
+ T = null, S((v) => ue(v));
434
+ }, 1500));
435
+ }, A = pe(i.current), B = {
436
+ enableWorker: !0,
437
+ lowLatencyMode: !1,
438
+ liveSyncDurationCount: 3,
439
+ liveMaxLatencyDurationCount: 6,
440
+ fragLoadingMaxRetry: 2,
441
+ fragLoadingRetryDelay: 500,
442
+ fragLoadingMaxRetryTimeout: 4e3,
443
+ manifestLoadingMaxRetry: 3,
444
+ levelLoadingMaxRetry: 3,
445
+ maxBufferLength: A,
446
+ maxMaxBufferLength: A,
447
+ maxBufferSize: ge(i.current)
448
+ }, R = new M(B);
449
+ return n.current = R, R.loadSource($), R.attachMedia(s), R.on(M.Events.LEVEL_LOADED, (F, v) => {
450
+ N(v.details.live);
451
+ }), R.on(M.Events.MANIFEST_PARSED, () => {
452
+ l(s, i.current), p && s.play().catch(() => {
453
+ });
454
+ }), R.on(M.Events.ERROR, (F, v) => {
455
+ if ((v.details === M.ErrorDetails.FRAG_LOAD_ERROR || v.details === M.ErrorDetails.FRAG_LOAD_TIMEOUT) && !v.fatal) {
456
+ setTimeout(() => {
457
+ _.current === !0 && V(s), l(s, i.current), s.play().catch(() => {
458
+ });
459
+ }, 500);
460
+ return;
461
+ }
462
+ if (v.fatal)
463
+ switch (y("[HlsPlayer/HLS.js] Fatal error", v.type, v.details), P.current?.(v), v.type) {
464
+ case M.ErrorTypes.MEDIA_ERROR:
465
+ E < 3 ? (E += 1, R.recoverMediaError()) : D("repeated media errors");
466
+ break;
467
+ case M.ErrorTypes.NETWORK_ERROR:
468
+ v.details === M.ErrorDetails.MANIFEST_LOAD_ERROR || v.details === M.ErrorDetails.MANIFEST_LOAD_TIMEOUT ? D("manifest unreachable") : setTimeout(() => R.startLoad(), 2e3);
469
+ break;
470
+ default:
471
+ D("unrecoverable error");
472
+ break;
473
+ }
474
+ }), () => {
475
+ T && clearTimeout(T), a(), H(), R.destroy(), n.current = null;
476
+ };
477
+ }
478
+ return m("[HlsPlayer] HLS is not supported in this browser"), P.current?.(new Error("HLS playback is not supported in this browser.")), () => {
479
+ H();
480
+ };
481
+ }, [h]), { url: h, isLive: u, engine: g };
482
+ }
483
+ const he = 5, Ve = 1e3;
484
+ function Ke(e, r, n = he) {
485
+ const [t, i] = L(null);
486
+ x(() => {
487
+ if (r !== !0) {
488
+ i(null);
489
+ return;
490
+ }
491
+ const a = () => {
492
+ const d = e.current;
493
+ i(d ? ce(d) : null);
494
+ };
495
+ a();
496
+ const p = setInterval(a, Ve);
497
+ return () => clearInterval(p);
498
+ }, [r, e]);
499
+ const l = J(() => {
500
+ const a = e.current;
501
+ a && (V(a), a.play().catch(() => {
502
+ }), i(ce(a)));
503
+ }, [e]);
504
+ return {
505
+ secondsBehind: t,
506
+ behindLiveEdge: t !== null && t > n,
507
+ goLive: l
508
+ };
509
+ }
510
+ function Ye(e, r, n) {
511
+ const t = k(n);
512
+ x(() => {
513
+ t.current = n;
514
+ }), x(() => {
515
+ if (!r)
516
+ return;
517
+ const i = (l) => {
518
+ e.current && !e.current.contains(l.target) && t.current();
519
+ };
520
+ return document.addEventListener("mousedown", i), () => document.removeEventListener("mousedown", i);
521
+ }, [r, e]);
522
+ }
523
+ function Ze({
524
+ videoRef: e,
525
+ hlsRef: r,
526
+ engineRef: n,
527
+ initialSpeed: t = 1,
528
+ onSpeedChange: i
529
+ }) {
530
+ const [l, a] = L(t), [p, d] = L(!1), c = k(t), f = k(null), m = k(null), y = k(i);
531
+ y.current = i;
532
+ const h = J(() => {
533
+ m.current && (clearInterval(m.current), m.current = null);
534
+ }, []), S = J(
535
+ (o, g) => {
536
+ if (h(), n.current !== "HLS.js" || g < He) {
537
+ o.playbackRate = g;
538
+ return;
539
+ }
540
+ o.playbackRate = se;
541
+ const w = (g - se) * (le / 1e3);
542
+ m.current = setInterval(() => {
543
+ if (o.paused || o.seeking)
544
+ return;
545
+ const O = Math.max(0, Ue(o) - 2), P = Math.min(w, O);
546
+ if (P <= 0)
547
+ return;
548
+ const s = o.seekable.length > 0 ? o.seekable.end(o.seekable.length - 1) : o.duration, N = o.currentTime + P;
549
+ if (isFinite(s) && N >= s - 0.5) {
550
+ h();
551
+ return;
552
+ }
553
+ o.currentTime = N;
554
+ }, le);
555
+ },
556
+ [h, n]
557
+ ), u = J((o) => {
558
+ a(o);
559
+ }, []);
560
+ return x(() => {
561
+ c.current = l, y.current?.(l);
562
+ }, [l]), x(() => {
563
+ const o = e.current;
564
+ if (!o)
565
+ return;
566
+ S(o, l);
567
+ const g = r.current;
568
+ if (g) {
569
+ const w = pe(l);
570
+ g.config.maxBufferLength = w, g.config.maxMaxBufferLength = w, g.config.maxBufferSize = ge(l);
571
+ }
572
+ return () => h();
573
+ }, [l, S, h, r, e]), Ye(f, p, () => d(!1)), {
574
+ speed: l,
575
+ setSpeed: u,
576
+ showSpeedMenu: p,
577
+ setShowSpeedMenu: d,
578
+ speedMenuRef: f,
579
+ speedRef: c,
580
+ applyPlaybackSpeed: S,
581
+ clearJumpScan: h
582
+ };
583
+ }
584
+ const ee = 120, qe = /^#EXTINF:([\d.]+),?/, Qe = /seg_(\d{8})_(\d{6})_/;
585
+ function et(e) {
586
+ const r = Qe.exec(e);
587
+ if (!r)
588
+ return null;
589
+ const [, n, t] = r, i = Number(n.slice(0, 4)), l = Number(n.slice(4, 6)), a = Number(n.slice(6, 8)), p = Number(t.slice(0, 2)), d = Number(t.slice(2, 4)), c = Number(t.slice(4, 6)), f = new Date(i, l - 1, a, p, d, c);
590
+ return Number.isNaN(f.getTime()) || f.getFullYear() !== i || f.getMonth() !== l - 1 || f.getDate() !== a || f.getHours() !== p || f.getMinutes() !== d || f.getSeconds() !== c ? null : f.getTime();
591
+ }
592
+ function tt(e) {
593
+ const r = (e ?? "").split(/\r?\n/).filter((a) => a.trim() !== ""), n = [], t = [];
594
+ let i = null, l = !1;
595
+ for (const a of r) {
596
+ const p = qe.exec(a);
597
+ if (p) {
598
+ l = !0, i = Number(p[1]);
599
+ continue;
600
+ }
601
+ if (a.startsWith("#")) {
602
+ l || n.push(a);
603
+ continue;
604
+ }
605
+ i !== null && (t.push({
606
+ durationSec: i,
607
+ uri: a,
608
+ timestamp: et(a)
609
+ }), i = null);
610
+ }
611
+ return { headerLines: n, segments: t };
612
+ }
613
+ function rt(e, r = ee) {
614
+ const n = [];
615
+ let t = [], i = 0;
616
+ for (const l of e)
617
+ t.push(l), i += l.durationSec, i >= r && (n.push(t), t = [], i = 0);
618
+ return t.length > 0 && n.push(t), n;
619
+ }
620
+ function nt(e, r) {
621
+ const n = [...e];
622
+ for (const t of r)
623
+ n.push(`#EXTINF:${t.durationSec},`, t.uri);
624
+ return n.push("#EXT-X-ENDLIST"), `${n.join(`
625
+ `)}
626
+ `;
627
+ }
628
+ const fe = (e) => e === null ? "--:--" : new Date(e).toTimeString().slice(0, 5);
629
+ function ot(e, r = ee) {
630
+ return (e ?? []).flatMap((t, i) => {
631
+ const { spaceId: l, cameraId: a, feed: p } = t, { headerLines: d, segments: c } = tt(p);
632
+ return c.length === 0 ? [] : rt(c, r).map(
633
+ (f, m) => {
634
+ const y = f[0].timestamp, h = f.reduce(
635
+ (u, o) => u + o.durationSec,
636
+ 0
637
+ ), S = y === null ? null : y + h * 1e3;
638
+ return {
639
+ // The result index is part of the id because one camera can appear
640
+ // in the response more than once, over different time ranges — and
641
+ // then space + camera + chunk index alone is not unique.
642
+ id: `${l}-${a}-${i}-${m}`,
643
+ spaceId: l,
644
+ cameraId: a,
645
+ startTime: y,
646
+ endTime: S,
647
+ durationSec: h,
648
+ startLabel: fe(y),
649
+ endLabel: fe(S),
650
+ segmentCount: f.length,
651
+ playlistText: nt(d, f)
652
+ };
653
+ }
654
+ );
655
+ }).sort((t, i) => t.startTime === null || i.startTime === null ? 0 : t.startTime - i.startTime);
656
+ }
657
+ function it(e) {
658
+ const r = new Blob([new TextEncoder().encode(e)], {
659
+ type: "application/vnd.apple.mpegurl"
660
+ });
661
+ return URL.createObjectURL(r);
662
+ }
663
+ function at(e, r) {
664
+ return e.length === 0 ? null : r == null ? e[0] : e.find(
665
+ (t) => t.startTime !== null && t.endTime !== null && r >= t.startTime && r < t.endTime
666
+ ) ?? e[0];
667
+ }
668
+ function st({
669
+ search: e,
670
+ at: r,
671
+ chunkSeconds: n = ee,
672
+ onError: t
673
+ }) {
674
+ const [i, l] = L([]), [a, p] = L(!1), [d, c] = L(null), [f, m] = L(null), y = k(t);
675
+ y.current = t, x(() => {
676
+ if (!e) {
677
+ l([]), p(!1), c(null);
678
+ return;
679
+ }
680
+ let o = !0;
681
+ return p(!0), c(null), e().then((g) => {
682
+ o && l(Array.isArray(g) ? g : []);
683
+ }).catch((g) => {
684
+ o && (l([]), c(g), y.current?.(g));
685
+ }).finally(() => {
686
+ o && p(!1);
687
+ }), () => {
688
+ o = !1;
689
+ };
690
+ }, [e]);
691
+ const h = ae(
692
+ () => ot(i, n),
693
+ [i, n]
694
+ ), S = ae(() => at(h, r), [h, r]), u = S?.playlistText ?? null;
695
+ return x(() => {
696
+ if (u === null) {
697
+ m(null);
698
+ return;
699
+ }
700
+ const o = it(u);
701
+ return m(o), () => {
702
+ URL.revokeObjectURL(o), m(null);
703
+ };
704
+ }, [u]), { chunks: h, chunk: S, url: f, loading: a, error: d };
705
+ }
706
+ function lt(e, r, n) {
707
+ const [t, i] = L(n), [l, a] = L(0), [p, d] = L(0);
708
+ return x(() => {
709
+ const c = e.current;
710
+ if (!c)
711
+ return;
712
+ const f = () => a(c.currentTime || 0), m = () => d(isFinite(c.duration) ? c.duration : 0), y = () => i(!0), h = () => i(!1);
713
+ return c.addEventListener("timeupdate", f), c.addEventListener("loadedmetadata", m), c.addEventListener("playing", y), c.addEventListener("pause", h), a(c.currentTime || 0), d(isFinite(c.duration) ? c.duration : 0), () => {
714
+ c.removeEventListener("timeupdate", f), c.removeEventListener("loadedmetadata", m), c.removeEventListener("playing", y), c.removeEventListener("pause", h);
715
+ };
716
+ }, [r]), { isPlaying: t, currentTime: l, duration: p };
717
+ }
718
+ function gt({
719
+ src: e,
720
+ emptyMessage: r = "No video to display",
721
+ recordingSearch: n,
722
+ recordingAt: t,
723
+ recordingChunkSeconds: i,
724
+ onRecordingChunks: l,
725
+ loadingMessage: a = "Loading recording…",
726
+ autoPlay: p = !0,
727
+ muted: d = !0,
728
+ height: c = 480,
729
+ className: f,
730
+ showControls: m = !0,
731
+ showSpeedMenu: y = !0,
732
+ speedOptions: h,
733
+ initialSpeed: S = 1,
734
+ showGoLive: u = !1,
735
+ goLiveMode: o = "source",
736
+ goLiveLabel: g = "Go Live",
737
+ exitLiveLabel: w = "Back to recording",
738
+ onExitLive: _,
739
+ allowSpeedWhenLive: O = !1,
740
+ liveEdgeToleranceSeconds: P = he,
741
+ onGoLive: s,
742
+ onSpeedChange: N,
743
+ onError: U,
744
+ onLiveChange: W,
745
+ onEngineChange: H,
746
+ debug: $ = !1
747
+ }) {
748
+ const E = k(null), T = k(null), D = k(null), A = typeof e == "string" && e.trim() !== "", {
749
+ chunks: B,
750
+ url: R,
751
+ loading: F
752
+ } = st({
753
+ search: A ? void 0 : n,
754
+ at: t,
755
+ chunkSeconds: i,
756
+ onError: U
757
+ }), v = k(l);
758
+ v.current = l, x(() => {
759
+ v.current?.(B);
760
+ }, [B]);
761
+ const j = A ? e : R ?? void 0, {
762
+ speed: be,
763
+ setSpeed: K,
764
+ showSpeedMenu: ye,
765
+ setShowSpeedMenu: Ee,
766
+ speedMenuRef: Se,
767
+ speedRef: Y,
768
+ applyPlaybackSpeed: ve,
769
+ clearJumpScan: ke
770
+ } = Ze({
771
+ videoRef: E,
772
+ hlsRef: T,
773
+ engineRef: D,
774
+ initialSpeed: S,
775
+ onSpeedChange: N
776
+ }), { url: xe, isLive: I, engine: te } = Je({
777
+ src: j,
778
+ videoRef: E,
779
+ hlsRef: T,
780
+ engineRef: D,
781
+ speedRef: Y,
782
+ applyPlaybackSpeed: ve,
783
+ clearJumpScan: ke,
784
+ autoPlay: p,
785
+ onError: U,
786
+ debug: $
787
+ }), { isPlaying: Le, currentTime: Te, duration: Re } = lt(
788
+ E,
789
+ xe,
790
+ p
791
+ ), { secondsBehind: re, behindLiveEdge: Me, goLive: we } = Ke(
792
+ E,
793
+ I,
794
+ P
795
+ ), ne = k(W);
796
+ ne.current = W, x(() => {
797
+ ne.current?.(I);
798
+ }, [I]);
799
+ const oe = k(H);
800
+ oe.current = H, x(() => {
801
+ oe.current?.(te);
802
+ }, [te]);
803
+ const Z = o === "source" && I === !0;
804
+ let z = !1, G = !1, X = g, ie = g;
805
+ u && (o === "source" ? Z ? (z = !!_, G = !0, ie = w, X = `${w} — leave the live stream`) : (z = !0, G = I === !1, X = I === null ? "Checking the stream…" : `${g} — switch to the live stream`) : (z = I === !0, G = Me, X = re === null ? g : `${g} — ${Math.round(re)}s behind`));
806
+ const _e = () => {
807
+ if (Z) {
808
+ _?.();
809
+ return;
810
+ }
811
+ o === "seek" && we(), s?.();
812
+ }, q = !O && I === !0;
813
+ return x(() => {
814
+ q && Y.current !== 1 && K(1);
815
+ }, [q, K, Y]), typeof j == "string" && j.trim() !== "" ? /* @__PURE__ */ C(
816
+ "div",
817
+ {
818
+ className: f,
819
+ style: { position: "relative", width: "100%" },
820
+ "data-testid": "hls-player",
821
+ children: [
822
+ /* @__PURE__ */ b(
823
+ "video",
824
+ {
825
+ ref: E,
826
+ controls: !1,
827
+ autoPlay: p,
828
+ muted: d,
829
+ playsInline: !0,
830
+ onContextMenu: (Pe) => Pe.preventDefault(),
831
+ controlsList: "nodownload nofullscreen noremoteplayback noplaybackrate",
832
+ disablePictureInPicture: !0,
833
+ disableRemotePlayback: !0,
834
+ style: { width: "100%", height: c, display: "block", background: "#000" },
835
+ children: /* @__PURE__ */ b("track", { kind: "captions" })
836
+ }
837
+ ),
838
+ m && /* @__PURE__ */ b(
839
+ ze,
840
+ {
841
+ videoRef: E,
842
+ isPlaying: Le,
843
+ currentTime: Te,
844
+ duration: Re,
845
+ isLive: I === !0,
846
+ goLive: z ? /* @__PURE__ */ b(
847
+ Ne,
848
+ {
849
+ enabled: G,
850
+ label: ie,
851
+ hint: X,
852
+ live: Z,
853
+ onGoLive: _e
854
+ }
855
+ ) : void 0
856
+ }
857
+ ),
858
+ y && /* @__PURE__ */ b(
859
+ Xe,
860
+ {
861
+ speed: be,
862
+ setSpeed: K,
863
+ showSpeedMenu: ye,
864
+ setShowSpeedMenu: Ee,
865
+ speedMenuRef: Se,
866
+ options: h,
867
+ disabled: q
868
+ }
869
+ )
870
+ ]
871
+ }
872
+ ) : /* @__PURE__ */ b(
873
+ "div",
874
+ {
875
+ className: f,
876
+ style: { position: "relative", width: "100%" },
877
+ "data-testid": "hls-player",
878
+ children: /* @__PURE__ */ b(
879
+ "div",
880
+ {
881
+ "data-testid": F ? "hls-player-loading" : "hls-player-empty",
882
+ style: {
883
+ width: "100%",
884
+ height: c,
885
+ display: "flex",
886
+ alignItems: "center",
887
+ justifyContent: "center",
888
+ background: "#000",
889
+ color: "#94a3b8",
890
+ fontSize: 14,
891
+ textAlign: "center",
892
+ padding: 16,
893
+ boxSizing: "border-box"
894
+ },
895
+ children: F ? a : r
896
+ }
897
+ )
898
+ }
899
+ );
900
+ }
901
+ export {
902
+ ee as CHUNK_DURATION_SECONDS,
903
+ he as DEFAULT_LIVE_EDGE_TOLERANCE_SECONDS,
904
+ Ne as GoLiveButton,
905
+ gt as HlsPlayer,
906
+ se as JUMP_SCAN_BASE_RATE,
907
+ le as JUMP_SCAN_INTERVAL_MS,
908
+ He as JUMP_SCAN_THRESHOLD,
909
+ ze as PlaybackControls,
910
+ Ae as SPEED_OPTIONS,
911
+ Xe as SpeedMenu,
912
+ nt as buildChunkPlaylistText,
913
+ ot as buildRecordingChunks,
914
+ at as chunkAt,
915
+ rt as chunkSegments,
916
+ Q as formatTime,
917
+ Ue as getBufferedAhead,
918
+ me as getLiveEdge,
919
+ pe as getScaledBufferSeconds,
920
+ ge as getScaledBufferSizeBytes,
921
+ ce as getSecondsBehindLive,
922
+ We as isSafariBrowser,
923
+ V as jumpToLiveEdge,
924
+ it as makeBlobUrl,
925
+ tt as parseM3u8,
926
+ et as parseSegmentTimestamp,
927
+ Ye as useClickOutside,
928
+ Je as useHlsEngine,
929
+ Ke as useLiveEdge,
930
+ Ze as usePlaybackSpeed,
931
+ st as useRecordingSearch,
932
+ lt as useVideoPlaybackState,
933
+ ue as withReloadMarker,
934
+ $e as withoutReloadMarker
935
+ };
936
+ //# sourceMappingURL=index.es.js.map