@nikelroot/video-player 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,1361 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ VideoPlayer: () => VideoPlayer,
34
+ default: () => VideoPlayer_default,
35
+ hasNativeHlsSupport: () => hasNativeHlsSupport
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // src/VideoPlayer.tsx
40
+ var import_react2 = require("react");
41
+
42
+ // src/controls.tsx
43
+ var import_antd = require("antd");
44
+ var import_icons = require("@ant-design/icons");
45
+
46
+ // src/styles.ts
47
+ var import_styled_components = __toESM(require("styled-components"), 1);
48
+ var VideoPlayerControlStyles = import_styled_components.default.div`
49
+ position: absolute;
50
+ left: 0;
51
+ right: 0;
52
+ bottom: 0;
53
+ padding: 5px;
54
+ color: #fff;
55
+ z-index: 1000;
56
+
57
+ ${(props) => props.$showControl && import_styled_components.css`
58
+ background: rgba(0, 0, 0, 0.5);
59
+ `};
60
+
61
+ .full {
62
+ flex: 1 auto;
63
+ }
64
+
65
+ .video-player-control-title {
66
+ color: #fff;
67
+ max-width: 40%;
68
+ }
69
+
70
+ ${(props) => !props.$showControl && import_styled_components.css`
71
+ .ant-flex {
72
+ > * {
73
+ opacity: 0;
74
+ display: none;
75
+ }
76
+ > .seek-slider {
77
+ opacity: 1;
78
+ display: block;
79
+ top: 2px;
80
+ }
81
+ }
82
+ `};
83
+
84
+ ${(props) => props.$tiny && import_styled_components.css`
85
+ padding: 0;
86
+ `};
87
+ `;
88
+ var VideoPlayerStyles = import_styled_components.default.div`
89
+ position: relative;
90
+ aspect-ratio: 16 / 9;
91
+ max-height: 100%;
92
+ max-width: 100vw;
93
+ background: #000;
94
+
95
+ ${(props) => props.$color && import_styled_components.css`
96
+ box-shadow: 0 0 5px 1px ${props.$color};
97
+ `};
98
+
99
+ &,
100
+ * {
101
+ user-select: none;
102
+ }
103
+
104
+ ${(props) => props.$isMobile && import_styled_components.css`
105
+ max-width: 100vw;
106
+ min-height: auto !important;
107
+ `};
108
+
109
+ video {
110
+ display: block;
111
+ max-width: 100%;
112
+ width: 100%;
113
+ max-height: 100%;
114
+ }
115
+
116
+ .video-wrapper {
117
+ position: relative;
118
+ z-index: 10;
119
+ transition: all 0.25s ease-in-out;
120
+ max-height: 100%;
121
+ overflow: hidden;
122
+ display: flex;
123
+ justify-content: center;
124
+ align-items: center;
125
+ }
126
+
127
+ .video-error-overlay {
128
+ position: absolute;
129
+ inset: 0;
130
+ z-index: 13;
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ padding: 16px;
135
+ background: rgba(0, 0, 0, 0.72);
136
+ }
137
+
138
+ .video-error-content {
139
+ display: flex;
140
+ flex-direction: column;
141
+ align-items: center;
142
+ gap: 12px;
143
+ max-width: 320px;
144
+ text-align: center;
145
+ color: #fff;
146
+ }
147
+
148
+ .video-error-title {
149
+ font-size: 14px;
150
+ line-height: 1.4;
151
+ }
152
+
153
+ .video-error-details {
154
+ display: flex;
155
+ flex-direction: column;
156
+ gap: 6px;
157
+ width: 100%;
158
+ padding: 10px 12px;
159
+ border-radius: 10px;
160
+ background: rgba(255, 255, 255, 0.08);
161
+ font-size: 12px;
162
+ line-height: 1.45;
163
+ text-align: left;
164
+ word-break: break-word;
165
+ }
166
+
167
+ .video-error-detail {
168
+ opacity: 0.92;
169
+ }
170
+
171
+ .video-error-retry {
172
+ border: 0;
173
+ border-radius: 999px;
174
+ padding: 8px 14px;
175
+ background: #fff;
176
+ color: #000;
177
+ font-size: 13px;
178
+ cursor: pointer;
179
+ }
180
+
181
+ ${(props) => props.type === "torrent" && import_styled_components.css`
182
+ width: 100%;
183
+ video {
184
+ width: 100%;
185
+ }
186
+ `};
187
+
188
+ .video-top-bar {
189
+ padding: 5px;
190
+ position: absolute;
191
+ left: 0;
192
+ right: 0;
193
+ top: 0;
194
+ z-index: 11;
195
+ }
196
+
197
+ .video-right-bar {
198
+ position: absolute;
199
+ right: 0;
200
+ top: 0;
201
+ bottom: 0;
202
+ z-index: 12;
203
+ display: flex;
204
+ align-items: stretch;
205
+ pointer-events: auto;
206
+ margin-top: 32px;
207
+ overflow-y: auto;
208
+ background: rgba(0, 0, 0, 0.01);
209
+ max-width: 380px;
210
+ color: #fff;
211
+ transition: all ease-in-out 0.25s;
212
+ opacity: 0.01;
213
+ border-radius: 3px;
214
+
215
+ &:hover {
216
+ background: rgba(0, 0, 0, 0.1);
217
+ opacity: 1;
218
+ }
219
+
220
+ .video-right-bar-toggle {
221
+ flex-direction: column;
222
+ justify-content: center;
223
+ align-items: center;
224
+ color: #fff;
225
+ font-size: 12px;
226
+ width: 20px;
227
+ cursor: pointer;
228
+ display: none !important;
229
+ }
230
+
231
+ .video-right-bar-content {
232
+ display: block;
233
+ }
234
+ }
235
+
236
+ ${(props) => (props.$showControl || props.$alwaysShowSegments) && import_styled_components.css`
237
+ .video-right-bar {
238
+ background: rgba(0, 0, 0, 0.1);
239
+ opacity: 1;
240
+ }
241
+ `};
242
+ `;
243
+ var SeekSliderStyled = import_styled_components.default.div`
244
+ position: absolute;
245
+ left: 0;
246
+ right: 0;
247
+ top: -12px;
248
+
249
+ .ant-slider-rail {
250
+ height: 20px;
251
+ top: auto;
252
+ bottom: 0;
253
+ background: transparent !important;
254
+ }
255
+
256
+ .ant-slider {
257
+ &:hover {
258
+ .ant-slider-rail {
259
+ background: transparent !important;
260
+ }
261
+ }
262
+ }
263
+
264
+ .ant-slider-track {
265
+ top: 6px;
266
+ }
267
+
268
+ .ant-slider-handle {
269
+ top: 3px;
270
+ }
271
+
272
+ .ant-slider-step {
273
+ height: 8px;
274
+ background: rgba(255, 255, 255, 0.1);
275
+ }
276
+
277
+ .ant-slider {
278
+ margin: 0;
279
+ }
280
+ `;
281
+
282
+ // src/utils.ts
283
+ var import_react = require("react");
284
+ var formatTime = (seconds) => {
285
+ const totalSeconds = Number.parseInt(String(seconds), 10);
286
+ const safeSeconds = Number.isFinite(totalSeconds) ? totalSeconds : 0;
287
+ const hours = Math.floor(safeSeconds / 3600);
288
+ const minutes = Math.floor(safeSeconds % 3600 / 60);
289
+ const secs = safeSeconds % 60;
290
+ return [
291
+ hours.toString().padStart(2, "0"),
292
+ minutes.toString().padStart(2, "0"),
293
+ secs.toString().padStart(2, "0")
294
+ ].join(":");
295
+ };
296
+ var widthSnapshot = 0;
297
+ var resizeRafId = null;
298
+ var isListening = false;
299
+ var listeners = /* @__PURE__ */ new Set();
300
+ var notifyAll = () => {
301
+ listeners.forEach((listener) => listener());
302
+ };
303
+ var resizeHandler = () => {
304
+ if (typeof window === "undefined") return;
305
+ const nextWidth = window.innerWidth;
306
+ if (resizeRafId != null) cancelAnimationFrame(resizeRafId);
307
+ resizeRafId = requestAnimationFrame(() => {
308
+ if (widthSnapshot === nextWidth) return;
309
+ widthSnapshot = nextWidth;
310
+ notifyAll();
311
+ });
312
+ };
313
+ var startListening = () => {
314
+ if (typeof window === "undefined" || isListening) return;
315
+ isListening = true;
316
+ widthSnapshot = window.innerWidth;
317
+ window.addEventListener("resize", resizeHandler, { passive: true });
318
+ };
319
+ var stopListening = () => {
320
+ if (typeof window === "undefined" || !isListening || listeners.size > 0) return;
321
+ window.removeEventListener("resize", resizeHandler);
322
+ if (resizeRafId != null) {
323
+ cancelAnimationFrame(resizeRafId);
324
+ resizeRafId = null;
325
+ }
326
+ isListening = false;
327
+ };
328
+ var subscribe = (listener) => {
329
+ listeners.add(listener);
330
+ startListening();
331
+ return () => {
332
+ listeners.delete(listener);
333
+ stopListening();
334
+ };
335
+ };
336
+ var getSnapshot = () => {
337
+ if (typeof window !== "undefined" && !isListening) {
338
+ widthSnapshot = window.innerWidth;
339
+ }
340
+ return widthSnapshot;
341
+ };
342
+ var getServerSnapshot = () => 0;
343
+ function useWindowWidth(mobileLimit = 768) {
344
+ const width = (0, import_react.useSyncExternalStore)(subscribe, getSnapshot, getServerSnapshot);
345
+ const isMobile = width <= mobileLimit;
346
+ const mobileType = (0, import_react.useMemo)(() => {
347
+ if (width <= mobileLimit) return "mobile";
348
+ if (width <= 1024) return "laptop";
349
+ return "default";
350
+ }, [width, mobileLimit]);
351
+ return [width, isMobile, mobileType];
352
+ }
353
+ var createThrottledNumberFn = (fn, waitMs) => {
354
+ let timer = null;
355
+ let lastCall = 0;
356
+ let pendingValue = null;
357
+ const throttled = ((value) => {
358
+ const now = Date.now();
359
+ const elapsed = now - lastCall;
360
+ if (elapsed >= waitMs) {
361
+ lastCall = now;
362
+ fn(value);
363
+ return;
364
+ }
365
+ pendingValue = value;
366
+ if (timer) return;
367
+ timer = setTimeout(() => {
368
+ timer = null;
369
+ lastCall = Date.now();
370
+ if (pendingValue != null) {
371
+ fn(pendingValue);
372
+ pendingValue = null;
373
+ }
374
+ }, waitMs - elapsed);
375
+ });
376
+ throttled.cancel = () => {
377
+ if (timer) clearTimeout(timer);
378
+ timer = null;
379
+ pendingValue = null;
380
+ };
381
+ return throttled;
382
+ };
383
+
384
+ // src/controls.tsx
385
+ var import_jsx_runtime = require("react/jsx-runtime");
386
+ var SeekSlider = ({ playAction, duration, currentTime, seekSetTime }) => {
387
+ const formatter = (value) => {
388
+ return formatTime(Number(value != null ? value : 0));
389
+ };
390
+ const clickHandler = (e) => {
391
+ e.preventDefault();
392
+ e.stopPropagation();
393
+ };
394
+ const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
395
+ const safeCurrentTime = Math.max(0, Math.min(Number.isFinite(currentTime) ? currentTime : 0, safeDuration || Infinity));
396
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeekSliderStyled, { onClick: clickHandler, className: "seek-slider", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
397
+ import_antd.Slider,
398
+ {
399
+ onChange: seekSetTime,
400
+ onChangeComplete: () => {
401
+ void playAction();
402
+ },
403
+ value: safeCurrentTime,
404
+ tooltip: { formatter },
405
+ max: safeDuration,
406
+ keyboard: false
407
+ }
408
+ ) });
409
+ };
410
+ var TimeBlock = ({ duration, currentTime }) => {
411
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_antd.Typography.Text, { style: { color: "#fff", whiteSpace: "nowrap" }, children: [
412
+ formatTime(currentTime),
413
+ " / ",
414
+ formatTime(duration)
415
+ ] });
416
+ };
417
+ var stopControlClick = (e) => {
418
+ e.preventDefault();
419
+ e.stopPropagation();
420
+ };
421
+ var VideoPlayerControls = (props) => {
422
+ const {
423
+ variant,
424
+ fullScreenAction,
425
+ showControl,
426
+ videoRef,
427
+ playAction,
428
+ pauseAction,
429
+ seekSetTime,
430
+ seekPrevAction,
431
+ seekNextAction,
432
+ playing,
433
+ muted,
434
+ onMutedChange,
435
+ duration,
436
+ currentTime,
437
+ title
438
+ } = props;
439
+ const [, isMobile] = useWindowWidth();
440
+ if (variant === "none") return null;
441
+ if (!videoRef.current) return null;
442
+ if (variant === "tiny") {
443
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VideoPlayerControlStyles, { $tiny: true, $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_antd.Flex, { gap: "small", align: "center", justify: "space-between", children: [
444
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "full" }),
445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeekSlider, { playAction, duration, currentTime, seekSetTime })
446
+ ] }) });
447
+ }
448
+ if (variant === "fullscreen-only") {
449
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VideoPlayerControlStyles, { $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_antd.Flex, { gap: "small", align: "center", justify: "space-between", children: [
450
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "full" }),
451
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: () => onMutedChange(!muted), icon: muted ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.MutedFilled, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.SoundFilled, {}) }),
452
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: fullScreenAction, icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.FullscreenOutlined, {}) })
453
+ ] }) });
454
+ }
455
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(VideoPlayerControlStyles, { $isMobile: isMobile, onClick: stopControlClick, $showControl: showControl, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_antd.Flex, { gap: "small", align: "center", justify: "space-between", children: [
456
+ playing ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: () => pauseAction(), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.PauseOutlined, {}) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: () => playAction(), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.PlaySquareFilled, {}) }),
457
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: () => seekPrevAction(), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.BackwardFilled, {}) }),
458
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: () => seekNextAction(), icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.FastForwardFilled, {}) }),
459
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Typography.Text, { ellipsis: true, className: "video-player-control-title", children: title }),
460
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "full" }),
461
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SeekSlider, { playAction, duration, currentTime, seekSetTime }),
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TimeBlock, { duration, currentTime }),
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: () => onMutedChange(!muted), icon: muted ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.MutedFilled, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.SoundFilled, {}) }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Button, { size: "small", onClick: fullScreenAction, icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.FullscreenOutlined, {}) })
465
+ ] }) });
466
+ };
467
+
468
+ // src/configs.ts
469
+ var confVod = {
470
+ autoStartLoad: true,
471
+ enableWorker: true,
472
+ capLevelToPlayerSize: true,
473
+ appendErrorMaxRetry: 5,
474
+ maxBufferLength: 45,
475
+ maxMaxBufferLength: 90,
476
+ maxBufferHole: 0.3,
477
+ backBufferLength: 20,
478
+ fragLoadPolicy: {
479
+ default: {
480
+ maxTimeToFirstByteMs: 12e3,
481
+ maxLoadTimeMs: 2e4,
482
+ timeoutRetry: { maxNumRetry: 2, retryDelayMs: 500, maxRetryDelayMs: 2e3 },
483
+ errorRetry: { maxNumRetry: 2, retryDelayMs: 500, maxRetryDelayMs: 2e3 }
484
+ }
485
+ }
486
+ };
487
+ var confLiveStability = {
488
+ autoStartLoad: true,
489
+ enableWorker: true,
490
+ capLevelToPlayerSize: true,
491
+ lowLatencyMode: false,
492
+ liveSyncDuration: 7,
493
+ liveMaxLatencyDuration: 14,
494
+ maxBufferLength: 30,
495
+ backBufferLength: 20,
496
+ fragLoadPolicy: {
497
+ default: {
498
+ maxTimeToFirstByteMs: 15e3,
499
+ maxLoadTimeMs: 25e3,
500
+ timeoutRetry: { maxNumRetry: 3, retryDelayMs: 500, maxRetryDelayMs: 2500 },
501
+ errorRetry: { maxNumRetry: 3, retryDelayMs: 500, maxRetryDelayMs: 2500 }
502
+ }
503
+ },
504
+ debug: false
505
+ };
506
+ var withCredentialsConfig = (config, credentials) => {
507
+ if (!credentials || credentials === "same-origin") return config;
508
+ return {
509
+ ...config,
510
+ xhrSetup: (xhr, url) => {
511
+ var _a;
512
+ xhr.withCredentials = credentials === "include";
513
+ (_a = config.xhrSetup) == null ? void 0 : _a.call(config, xhr, url);
514
+ },
515
+ fetchSetup: (context, initParams) => {
516
+ var _a;
517
+ const request = new Request(context.url, {
518
+ ...initParams,
519
+ credentials
520
+ });
521
+ return ((_a = config.fetchSetup) == null ? void 0 : _a.call(config, context, initParams)) || request;
522
+ }
523
+ };
524
+ };
525
+
526
+ // src/errorDetails.ts
527
+ var isRecord = (value) => typeof value === "object" && value !== null;
528
+ var asText = (value) => {
529
+ if (typeof value === "string") {
530
+ const trimmed = value.trim();
531
+ return trimmed ? trimmed : null;
532
+ }
533
+ if (typeof value === "number" || typeof value === "boolean") {
534
+ return String(value);
535
+ }
536
+ return null;
537
+ };
538
+ var getMediaErrorCodeLabel = (code) => {
539
+ switch (code) {
540
+ case 1:
541
+ return "MEDIA_ERR_ABORTED";
542
+ case 2:
543
+ return "MEDIA_ERR_NETWORK";
544
+ case 3:
545
+ return "MEDIA_ERR_DECODE";
546
+ case 4:
547
+ return "MEDIA_ERR_SRC_NOT_SUPPORTED";
548
+ default:
549
+ return "MEDIA_ERR_UNKNOWN";
550
+ }
551
+ };
552
+ var getPlaybackErrorCodeText = (error) => {
553
+ if (!isRecord(error) || typeof error.code !== "number") return null;
554
+ return `${getMediaErrorCodeLabel(error.code)} (${error.code})`;
555
+ };
556
+ var getResponseSummary = (response) => {
557
+ if (!isRecord(response)) return null;
558
+ const code = asText(response.code);
559
+ const text = asText(response.text);
560
+ if (code && text) return `${code} ${text}`;
561
+ return code || text;
562
+ };
563
+ var getSource = (data) => {
564
+ if (!isRecord(data)) return null;
565
+ const directSource = asText(data.src) || asText(data.url);
566
+ if (directSource) return directSource;
567
+ const contextUrl = isRecord(data.context) ? asText(data.context.url) : null;
568
+ if (contextUrl) return contextUrl;
569
+ const fragUrl = isRecord(data.frag) ? asText(data.frag.url) : null;
570
+ if (fragUrl) return fragUrl;
571
+ const responseUrl = isRecord(data.response) ? asText(data.response.url) : null;
572
+ return responseUrl;
573
+ };
574
+ var addDetail = (details, label, value) => {
575
+ const text = asText(value);
576
+ if (!text) return;
577
+ const line = `${label}: ${text}`;
578
+ if (!details.includes(line)) {
579
+ details.push(line);
580
+ }
581
+ };
582
+ var formatPlaybackErrorDetails = (error, data) => {
583
+ const details = [];
584
+ addDetail(details, "\u041A\u043E\u0434", getPlaybackErrorCodeText(error));
585
+ if (error instanceof Error) {
586
+ addDetail(details, "\u041E\u0448\u0438\u0431\u043A\u0430", error.message || error.name);
587
+ } else if (typeof error === "string") {
588
+ addDetail(details, "\u041E\u0448\u0438\u0431\u043A\u0430", error);
589
+ }
590
+ if (isRecord(data)) {
591
+ addDetail(details, "\u0422\u0438\u043F", data.type);
592
+ addDetail(details, "\u0414\u0435\u0442\u0430\u043B\u0438", data.details);
593
+ addDetail(details, "\u041F\u0440\u0438\u0447\u0438\u043D\u0430", data.reason);
594
+ addDetail(details, "HTTP", getResponseSummary(data.response));
595
+ addDetail(details, "\u0418\u0441\u0442\u043E\u0447\u043D\u0438\u043A", getSource(data));
596
+ }
597
+ return details;
598
+ };
599
+
600
+ // src/VideoPlayer.tsx
601
+ var import_jsx_runtime2 = require("react/jsx-runtime");
602
+ var defaultMessages = {
603
+ defaultError: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0437\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044C \u0432\u0438\u0434\u0435\u043E",
604
+ aborted: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0432\u0438\u0434\u0435\u043E \u0431\u044B\u043B\u0430 \u043F\u0440\u0435\u0440\u0432\u0430\u043D\u0430",
605
+ network: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0442\u0438 \u043F\u0440\u0438 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0435 \u0432\u0438\u0434\u0435\u043E",
606
+ decode: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0434\u0435\u043A\u043E\u0434\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0438\u0434\u0435\u043E",
607
+ unsupported: "\u0424\u043E\u0440\u043C\u0430\u0442 \u0432\u0438\u0434\u0435\u043E \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F",
608
+ playFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043D\u0430\u0447\u0430\u0442\u044C \u0432\u043E\u0441\u043F\u0440\u043E\u0438\u0437\u0432\u0435\u0434\u0435\u043D\u0438\u0435",
609
+ streamLoadFailed: "\u041E\u0448\u0438\u0431\u043A\u0430 \u0437\u0430\u0433\u0440\u0443\u0437\u043A\u0438 \u0432\u0438\u0434\u0435\u043E\u043F\u043E\u0442\u043E\u043A\u0430",
610
+ streamDecodeFailed: "\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0434\u0435\u043A\u043E\u0434\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0438\u0434\u0435\u043E\u043F\u043E\u0442\u043E\u043A",
611
+ retry: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C"
612
+ };
613
+ var MAX_NET_RETRIES = 3;
614
+ var MAX_FATAL_RELOADS = 3;
615
+ var LIVE_STALL_WINDOW_MS = 2e4;
616
+ var LIVE_STALLS_BEFORE_STABILITY = 3;
617
+ var hlsCtorPromise = null;
618
+ var activeExclusiveVideo = null;
619
+ var loadHlsCtor = () => {
620
+ if (!hlsCtorPromise) {
621
+ hlsCtorPromise = import("hls.js").then((mod) => mod.default);
622
+ }
623
+ return hlsCtorPromise;
624
+ };
625
+ var isInterruptedPlayRequestError = (error) => {
626
+ if (!(error instanceof Error)) return false;
627
+ return error.name === "AbortError" || error.message.includes("The play() request was interrupted by a new load request") || error.message.includes("The play() request was interrupted");
628
+ };
629
+ var canAttemptPlayback = (video) => {
630
+ return !!video && video.readyState >= HTMLMediaElement.HAVE_METADATA;
631
+ };
632
+ function hasNativeHlsSupport() {
633
+ if (typeof navigator === "undefined" || typeof document === "undefined") return false;
634
+ const isSafari = /^((?!chrome|android|firefox|opera|edg).)*safari/i.test(navigator.userAgent);
635
+ if (!isSafari) return false;
636
+ const video = document.createElement("video");
637
+ const mimes = ["application/vnd.apple.mpegurl", "application/x-mpegURL"];
638
+ return mimes.some((t) => {
639
+ const r = video.canPlayType(t);
640
+ return r === "probably" || r === "maybe";
641
+ });
642
+ }
643
+ var getNativeVideoErrorMessage = (error, messages) => {
644
+ if (!error) return messages.defaultError;
645
+ switch (error.code) {
646
+ case MediaError.MEDIA_ERR_ABORTED:
647
+ return messages.aborted;
648
+ case MediaError.MEDIA_ERR_NETWORK:
649
+ return messages.network;
650
+ case MediaError.MEDIA_ERR_DECODE:
651
+ return messages.decode;
652
+ case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
653
+ return messages.unsupported;
654
+ default:
655
+ return messages.defaultError;
656
+ }
657
+ };
658
+ var clampTime = (time, duration) => {
659
+ const safeTime = Number.isFinite(time) ? time : 0;
660
+ const safeDuration = Number.isFinite(duration) && duration > 0 ? duration : 0;
661
+ return safeDuration > 0 ? Math.max(0, Math.min(safeTime, safeDuration)) : Math.max(0, safeTime);
662
+ };
663
+ var VideoPlayerBase = (props, ref) => {
664
+ const {
665
+ controlsVariant = "none",
666
+ autoPlay = false,
667
+ preload = "metadata",
668
+ loop = false,
669
+ type,
670
+ sourceType = "auto",
671
+ onVideoClick,
672
+ onVideoDoubleClick,
673
+ playOnClick = true,
674
+ live = false,
675
+ videoTopBar: VideoTopBar,
676
+ videoRightBar: VideoRightBar,
677
+ defaultPlaying = false,
678
+ playing: playingProp,
679
+ poster,
680
+ alwaysShowSegments = false,
681
+ initialTime = 0,
682
+ reloadKey,
683
+ onVideoRefChange,
684
+ handleClick = true,
685
+ onPointerDown,
686
+ onPointerMove,
687
+ onPointerUp,
688
+ onPointerCancel,
689
+ exclusivePlayback = true,
690
+ onMutedChange,
691
+ onPlayingChange,
692
+ onTimeChange,
693
+ onDurationChange,
694
+ onActiveChange,
695
+ onPlaybackError,
696
+ title,
697
+ scrollTo = true,
698
+ muted: mutedProp,
699
+ defaultMuted = false,
700
+ currentTime: currentTimeProp,
701
+ duration: durationProp,
702
+ hlsConfig,
703
+ vodHlsConfig,
704
+ liveHlsConfig,
705
+ hlsCredentials = "include",
706
+ crossOrigin = "use-credentials",
707
+ messages: messagesProp,
708
+ accentColor,
709
+ ...videoProps
710
+ } = props;
711
+ let { videoSrc = "" } = props;
712
+ videoSrc = videoSrc.replaceAll(" ", "%20").replaceAll("#", "%23");
713
+ const messages = (0, import_react2.useMemo)(() => ({ ...defaultMessages, ...messagesProp }), [messagesProp]);
714
+ const [, isMobile, mobileType] = useWindowWidth();
715
+ const videoRef = (0, import_react2.useRef)(null);
716
+ const hlsRef = (0, import_react2.useRef)(null);
717
+ const containerRef = (0, import_react2.useRef)(null);
718
+ const netRetryCount = (0, import_react2.useRef)(0);
719
+ const mediaRecoverCount = (0, import_react2.useRef)(0);
720
+ const fatalReloadCount = (0, import_react2.useRef)(0);
721
+ const reloadTimer = (0, import_react2.useRef)(null);
722
+ const stalledNudgeTimer = (0, import_react2.useRef)(null);
723
+ const liveStabilityMode = (0, import_react2.useRef)(false);
724
+ const liveStallEvents = (0, import_react2.useRef)([]);
725
+ const controlTime = (0, import_react2.useRef)(null);
726
+ const [playingState, setPlayingState] = (0, import_react2.useState)(!!defaultPlaying);
727
+ const [mutedState, setMutedState] = (0, import_react2.useState)(!!defaultMuted);
728
+ const [durationState, setDurationState] = (0, import_react2.useState)(0);
729
+ const [currentTimeState, setCurrentTimeState] = (0, import_react2.useState)(0);
730
+ const [isFullscreen, setIsFullscreen] = (0, import_react2.useState)(false);
731
+ const [showControl, setControlVisible] = (0, import_react2.useState)(true);
732
+ const [errorState, setErrorState] = (0, import_react2.useState)(null);
733
+ const [reloadToken, setReloadToken] = (0, import_react2.useState)(0);
734
+ const playing = playingProp != null ? playingProp : playingState;
735
+ const muted = mutedProp != null ? mutedProp : mutedState;
736
+ const duration = durationProp != null ? durationProp : durationState;
737
+ const currentTime = currentTimeProp != null ? currentTimeProp : currentTimeState;
738
+ const setTimeD = (0, import_react2.useRef)(
739
+ createThrottledNumberFn((value) => {
740
+ const v = videoRef.current;
741
+ if (!v) return;
742
+ v.currentTime = value;
743
+ }, 30)
744
+ );
745
+ const clearTimers = (0, import_react2.useCallback)(() => {
746
+ if (reloadTimer.current) {
747
+ clearTimeout(reloadTimer.current);
748
+ reloadTimer.current = null;
749
+ }
750
+ if (stalledNudgeTimer.current) {
751
+ clearTimeout(stalledNudgeTimer.current);
752
+ stalledNudgeTimer.current = null;
753
+ }
754
+ if (controlTime.current) {
755
+ clearTimeout(controlTime.current);
756
+ controlTime.current = null;
757
+ }
758
+ }, []);
759
+ const setNextPlaying = (0, import_react2.useCallback)(
760
+ (value) => {
761
+ if (playingProp === void 0) setPlayingState(value);
762
+ onPlayingChange == null ? void 0 : onPlayingChange(value);
763
+ },
764
+ [onPlayingChange, playingProp]
765
+ );
766
+ const setNextMuted = (0, import_react2.useCallback)(
767
+ (value) => {
768
+ if (mutedProp === void 0) setMutedState(value);
769
+ const el = videoRef.current;
770
+ if (el) el.muted = value;
771
+ onMutedChange == null ? void 0 : onMutedChange(value);
772
+ },
773
+ [mutedProp, onMutedChange]
774
+ );
775
+ const clearErrorState = (0, import_react2.useCallback)(() => {
776
+ setErrorState((prev) => prev ? null : prev);
777
+ }, []);
778
+ const reportError = (0, import_react2.useCallback)(
779
+ (error, data, message = messages.defaultError) => {
780
+ const details = formatPlaybackErrorDetails(error, data);
781
+ const code = getPlaybackErrorCodeText(error);
782
+ setNextPlaying(false);
783
+ setErrorState({ message, error, data, details });
784
+ console.error("[VideoPlayer] playback error", {
785
+ message,
786
+ code,
787
+ details,
788
+ error,
789
+ data
790
+ });
791
+ onPlaybackError == null ? void 0 : onPlaybackError({ error, data, message, details });
792
+ },
793
+ [messages.defaultError, onPlaybackError, setNextPlaying]
794
+ );
795
+ const notifyActiveChange = (0, import_react2.useCallback)(
796
+ (video, reason) => {
797
+ onActiveChange == null ? void 0 : onActiveChange({ video, reason });
798
+ },
799
+ [onActiveChange]
800
+ );
801
+ const onTime = (0, import_react2.useCallback)(() => {
802
+ const el = videoRef.current;
803
+ if (!el) return;
804
+ const nextTime = Number.isFinite(el.currentTime) ? el.currentTime : 0;
805
+ if (currentTimeProp === void 0) setCurrentTimeState(nextTime);
806
+ onTimeChange == null ? void 0 : onTimeChange(nextTime, el);
807
+ }, [currentTimeProp, onTimeChange]);
808
+ const onDur = (0, import_react2.useCallback)(() => {
809
+ const el = videoRef.current;
810
+ if (!el) return;
811
+ const nextDuration = Number.isFinite(el.duration) ? el.duration : 0;
812
+ if (durationProp === void 0) setDurationState(nextDuration);
813
+ onDurationChange == null ? void 0 : onDurationChange(nextDuration, el);
814
+ }, [durationProp, onDurationChange]);
815
+ const setVideoEl = (0, import_react2.useCallback)(
816
+ (el) => {
817
+ const prev = videoRef.current;
818
+ if (prev === el) return;
819
+ if (prev) {
820
+ prev.removeEventListener("timeupdate", onTime);
821
+ prev.removeEventListener("durationchange", onDur);
822
+ }
823
+ videoRef.current = el;
824
+ onVideoRefChange == null ? void 0 : onVideoRefChange(el);
825
+ notifyActiveChange(el, "ref");
826
+ if (!el) return;
827
+ el.muted = muted;
828
+ el.addEventListener("timeupdate", onTime);
829
+ el.addEventListener("durationchange", onDur);
830
+ },
831
+ [muted, notifyActiveChange, onDur, onTime, onVideoRefChange]
832
+ );
833
+ const pauseOtherVideos = (0, import_react2.useCallback)(
834
+ (el) => {
835
+ if (!exclusivePlayback) return;
836
+ if (typeof exclusivePlayback === "object") {
837
+ exclusivePlayback.pauseOthers(el);
838
+ return;
839
+ }
840
+ if (activeExclusiveVideo && activeExclusiveVideo !== el && !activeExclusiveVideo.paused) {
841
+ activeExclusiveVideo.pause();
842
+ }
843
+ activeExclusiveVideo = el;
844
+ },
845
+ [exclusivePlayback]
846
+ );
847
+ const playAction = (0, import_react2.useCallback)(async () => {
848
+ const el = videoRef.current;
849
+ if (!el) return;
850
+ pauseOtherVideos(el);
851
+ try {
852
+ await el.play();
853
+ clearErrorState();
854
+ setNextPlaying(true);
855
+ notifyActiveChange(el, "play");
856
+ } catch (e) {
857
+ if (isInterruptedPlayRequestError(e)) return;
858
+ console.debug(e);
859
+ reportError(e, { type: "play" }, messages.playFailed);
860
+ }
861
+ if (scrollTo === true) {
862
+ const rect = el.getBoundingClientRect();
863
+ const inViewport = rect.top >= 0 && rect.left >= 0 && rect.bottom <= window.innerHeight && rect.right <= window.innerWidth;
864
+ if (!inViewport) {
865
+ el.scrollIntoView({
866
+ behavior: "smooth",
867
+ block: "center",
868
+ inline: "nearest"
869
+ });
870
+ }
871
+ }
872
+ }, [clearErrorState, messages.playFailed, notifyActiveChange, pauseOtherVideos, reportError, scrollTo, setNextPlaying]);
873
+ const pauseAction = (0, import_react2.useCallback)(() => {
874
+ const el = videoRef.current;
875
+ if (!el) return;
876
+ el.pause();
877
+ setNextPlaying(false);
878
+ notifyActiveChange(el, "pause");
879
+ }, [notifyActiveChange, setNextPlaying]);
880
+ const seekSetTime = (0, import_react2.useCallback)(
881
+ (value) => {
882
+ const el = videoRef.current;
883
+ if (!el) return;
884
+ const next = clampTime(value, duration);
885
+ setTimeD.current(next);
886
+ if (currentTimeProp === void 0) setCurrentTimeState(next);
887
+ notifyActiveChange(el, "seek");
888
+ },
889
+ [currentTimeProp, duration, notifyActiveChange]
890
+ );
891
+ const seekPrevAction = (0, import_react2.useCallback)(
892
+ (sec = 15) => {
893
+ const el = videoRef.current;
894
+ if (!el) return;
895
+ seekSetTime(el.currentTime - sec);
896
+ },
897
+ [seekSetTime]
898
+ );
899
+ const seekNextAction = (0, import_react2.useCallback)(
900
+ (sec = 15) => {
901
+ const el = videoRef.current;
902
+ if (!el) return;
903
+ seekSetTime(el.currentTime + sec);
904
+ },
905
+ [seekSetTime]
906
+ );
907
+ const toggleAction = (0, import_react2.useCallback)(
908
+ (force) => {
909
+ const el = videoRef.current;
910
+ if (!el) return;
911
+ if (force || el.paused) {
912
+ void playAction();
913
+ } else {
914
+ pauseAction();
915
+ }
916
+ },
917
+ [pauseAction, playAction]
918
+ );
919
+ const nudgeOnStall = (0, import_react2.useCallback)(() => {
920
+ const v = videoRef.current;
921
+ if (!v) return;
922
+ v.currentTime = Math.max(0, v.currentTime + 0.05);
923
+ }, []);
924
+ const shouldSwitchLiveToStability = (0, import_react2.useCallback)(() => {
925
+ if (!live || liveStabilityMode.current) return false;
926
+ const now = Date.now();
927
+ liveStallEvents.current = [...liveStallEvents.current.filter((ts) => now - ts <= LIVE_STALL_WINDOW_MS), now];
928
+ if (liveStallEvents.current.length < LIVE_STALLS_BEFORE_STABILITY) return false;
929
+ liveStabilityMode.current = true;
930
+ liveStallEvents.current = [];
931
+ return true;
932
+ }, [live]);
933
+ const resolvedHlsConfig = (0, import_react2.useCallback)(() => {
934
+ const modeConfig = live ? liveHlsConfig : vodHlsConfig;
935
+ const base = live ? confLiveStability : confVod;
936
+ return withCredentialsConfig(
937
+ {
938
+ ...base,
939
+ ...hlsConfig,
940
+ ...modeConfig
941
+ },
942
+ hlsCredentials
943
+ );
944
+ }, [hlsConfig, hlsCredentials, live, liveHlsConfig, vodHlsConfig]);
945
+ const loadVideo = (0, import_react2.useCallback)(
946
+ async (startAt = 0) => {
947
+ const startVideoRef = videoRef.current;
948
+ if (!startVideoRef) return;
949
+ const HlsCtor = await loadHlsCtor();
950
+ if (!videoRef.current || videoRef.current !== startVideoRef) return;
951
+ const video = videoRef.current;
952
+ if (!video) return;
953
+ const h = new HlsCtor(resolvedHlsConfig());
954
+ hlsRef.current = h;
955
+ h.on(HlsCtor.Events.ERROR, (_evt, data) => {
956
+ var _a, _b;
957
+ const { type: errorType, details, fatal } = data;
958
+ if (details === HlsCtor.ErrorDetails.BUFFER_APPEND_ERROR || details === "bufferAppendingError") return;
959
+ if (!fatal) {
960
+ switch (details) {
961
+ case HlsCtor.ErrorDetails.BUFFER_STALLED_ERROR:
962
+ case HlsCtor.ErrorDetails.BUFFER_NUDGE_ON_STALL:
963
+ if (!stalledNudgeTimer.current) {
964
+ stalledNudgeTimer.current = setTimeout(() => {
965
+ stalledNudgeTimer.current = null;
966
+ nudgeOnStall();
967
+ }, 200);
968
+ }
969
+ if (shouldSwitchLiveToStability()) {
970
+ const resumeTime2 = ((_a = videoRef.current) == null ? void 0 : _a.currentTime) || 0;
971
+ clearTimers();
972
+ reloadTimer.current = setTimeout(() => {
973
+ if (hlsRef.current) {
974
+ hlsRef.current.destroy();
975
+ hlsRef.current = null;
976
+ }
977
+ void loadVideo(resumeTime2);
978
+ }, 250);
979
+ }
980
+ return;
981
+ default:
982
+ return;
983
+ }
984
+ }
985
+ const resumeTime = ((_b = videoRef.current) == null ? void 0 : _b.currentTime) || 0;
986
+ if (errorType === HlsCtor.ErrorTypes.NETWORK_ERROR) {
987
+ if (netRetryCount.current < MAX_NET_RETRIES) {
988
+ netRetryCount.current += 1;
989
+ const backoffMs = 400 * Math.pow(2, netRetryCount.current - 1);
990
+ clearErrorState();
991
+ clearTimers();
992
+ reloadTimer.current = setTimeout(() => {
993
+ if (hlsRef.current) {
994
+ hlsRef.current.destroy();
995
+ hlsRef.current = null;
996
+ }
997
+ void loadVideo(resumeTime);
998
+ }, backoffMs);
999
+ return;
1000
+ }
1001
+ h.destroy();
1002
+ if (hlsRef.current === h) {
1003
+ hlsRef.current = null;
1004
+ }
1005
+ reportError(details, data, messages.streamLoadFailed);
1006
+ return;
1007
+ }
1008
+ if (errorType === HlsCtor.ErrorTypes.MEDIA_ERROR) {
1009
+ if (mediaRecoverCount.current === 0) {
1010
+ mediaRecoverCount.current = 1;
1011
+ clearErrorState();
1012
+ h.recoverMediaError();
1013
+ return;
1014
+ }
1015
+ if (mediaRecoverCount.current === 1) {
1016
+ mediaRecoverCount.current = 2;
1017
+ clearErrorState();
1018
+ h.swapAudioCodec();
1019
+ h.recoverMediaError();
1020
+ return;
1021
+ }
1022
+ }
1023
+ if (fatalReloadCount.current >= MAX_FATAL_RELOADS) {
1024
+ if (hlsRef.current) {
1025
+ hlsRef.current.destroy();
1026
+ hlsRef.current = null;
1027
+ }
1028
+ reportError(
1029
+ details,
1030
+ data,
1031
+ errorType === HlsCtor.ErrorTypes.MEDIA_ERROR ? messages.streamDecodeFailed : messages.streamLoadFailed
1032
+ );
1033
+ return;
1034
+ }
1035
+ fatalReloadCount.current += 1;
1036
+ clearErrorState();
1037
+ if (hlsRef.current) {
1038
+ hlsRef.current.destroy();
1039
+ hlsRef.current = null;
1040
+ }
1041
+ void loadVideo(resumeTime);
1042
+ });
1043
+ h.attachMedia(video);
1044
+ h.on(HlsCtor.Events.MEDIA_ATTACHED, () => {
1045
+ h.loadSource(videoSrc);
1046
+ });
1047
+ h.on(HlsCtor.Events.MANIFEST_PARSED, () => {
1048
+ fatalReloadCount.current = 0;
1049
+ netRetryCount.current = 0;
1050
+ mediaRecoverCount.current = 0;
1051
+ if (!live && startAt > 0) {
1052
+ h.startLoad(startAt);
1053
+ video.currentTime = startAt;
1054
+ } else {
1055
+ h.startLoad();
1056
+ }
1057
+ if (autoPlay) void playAction();
1058
+ });
1059
+ },
1060
+ [
1061
+ autoPlay,
1062
+ clearErrorState,
1063
+ clearTimers,
1064
+ live,
1065
+ messages.streamDecodeFailed,
1066
+ messages.streamLoadFailed,
1067
+ nudgeOnStall,
1068
+ playAction,
1069
+ reportError,
1070
+ resolvedHlsConfig,
1071
+ shouldSwitchLiveToStability,
1072
+ videoSrc
1073
+ ]
1074
+ );
1075
+ const loadVideoNative = (0, import_react2.useCallback)(() => {
1076
+ const video = videoRef.current;
1077
+ if (!video) return;
1078
+ video.src = videoSrc;
1079
+ video.currentTime = initialTime;
1080
+ if (autoPlay) void playAction();
1081
+ }, [autoPlay, initialTime, playAction, videoSrc]);
1082
+ const loadVideoHls = (0, import_react2.useCallback)(async () => {
1083
+ const video = videoRef.current;
1084
+ const canNativeHls = !!(video == null ? void 0 : video.canPlayType) && video.canPlayType("application/vnd.apple.mpegurl") !== "";
1085
+ const HlsCtor = await loadHlsCtor();
1086
+ if (!videoRef.current || videoRef.current !== video) return;
1087
+ if (HlsCtor.isSupported()) {
1088
+ void loadVideo();
1089
+ return;
1090
+ }
1091
+ if (canNativeHls) {
1092
+ loadVideoNative();
1093
+ return;
1094
+ }
1095
+ void loadVideo();
1096
+ }, [loadVideo, loadVideoNative]);
1097
+ const destroy = (0, import_react2.useCallback)(() => {
1098
+ clearTimers();
1099
+ if (hlsRef.current) {
1100
+ hlsRef.current.destroy();
1101
+ hlsRef.current = null;
1102
+ }
1103
+ const v = videoRef.current;
1104
+ if (v) {
1105
+ v.removeEventListener("timeupdate", onTime);
1106
+ v.removeEventListener("durationchange", onDur);
1107
+ v.pause();
1108
+ v.removeAttribute("src");
1109
+ v.load();
1110
+ if (activeExclusiveVideo === v) activeExclusiveVideo = null;
1111
+ }
1112
+ videoRef.current = null;
1113
+ onVideoRefChange == null ? void 0 : onVideoRefChange(null);
1114
+ notifyActiveChange(null, "destroy");
1115
+ setNextPlaying(false);
1116
+ if (durationProp === void 0) setDurationState(0);
1117
+ if (currentTimeProp === void 0) setCurrentTimeState(0);
1118
+ }, [
1119
+ clearTimers,
1120
+ currentTimeProp,
1121
+ durationProp,
1122
+ notifyActiveChange,
1123
+ onDur,
1124
+ onTime,
1125
+ onVideoRefChange,
1126
+ setNextPlaying
1127
+ ]);
1128
+ const videoClickHandler = (0, import_react2.useCallback)(() => {
1129
+ if (!handleClick) return;
1130
+ if (playOnClick) toggleAction();
1131
+ onVideoClick == null ? void 0 : onVideoClick();
1132
+ }, [handleClick, onVideoClick, playOnClick, toggleAction]);
1133
+ const fullScreenAction = (0, import_react2.useCallback)(() => {
1134
+ if (typeof document === "undefined") return;
1135
+ const doc = document;
1136
+ const videoEl = videoRef.current;
1137
+ const containerEl = containerRef.current;
1138
+ const isFs = !!document.fullscreenElement || !!doc.webkitFullscreenElement || !!doc.mozFullScreenElement || !!doc.msFullscreenElement;
1139
+ if (isFs) {
1140
+ if (document.exitFullscreen) void document.exitFullscreen();
1141
+ else if (doc.webkitExitFullscreen) void doc.webkitExitFullscreen();
1142
+ else if (doc.msExitFullscreen) void doc.msExitFullscreen();
1143
+ return;
1144
+ }
1145
+ if (containerEl) {
1146
+ if (containerEl.requestFullscreen) void containerEl.requestFullscreen();
1147
+ else if (containerEl.webkitRequestFullscreen) void containerEl.webkitRequestFullscreen();
1148
+ else if (containerEl.msRequestFullscreen) void containerEl.msRequestFullscreen();
1149
+ else if (videoEl && videoEl.webkitEnterFullscreen) void videoEl.webkitEnterFullscreen();
1150
+ return;
1151
+ }
1152
+ if (videoEl) {
1153
+ if (videoEl.requestFullscreen) void videoEl.requestFullscreen();
1154
+ else if (videoEl.webkitEnterFullscreen) void videoEl.webkitEnterFullscreen();
1155
+ }
1156
+ }, []);
1157
+ const videoDbClickHandler = (0, import_react2.useCallback)(() => {
1158
+ if (!handleClick) return;
1159
+ fullScreenAction();
1160
+ onVideoDoubleClick == null ? void 0 : onVideoDoubleClick();
1161
+ }, [fullScreenAction, handleClick, onVideoDoubleClick]);
1162
+ const showClickHandler = (0, import_react2.useCallback)(() => {
1163
+ setControlVisible((st) => !st);
1164
+ }, []);
1165
+ const retryLoadHandler = (0, import_react2.useCallback)(() => {
1166
+ clearErrorState();
1167
+ setReloadToken((value) => value + 1);
1168
+ }, [clearErrorState]);
1169
+ const videoErrorHandler = (0, import_react2.useCallback)(() => {
1170
+ const video = videoRef.current;
1171
+ const error = video == null ? void 0 : video.error;
1172
+ const src = (video == null ? void 0 : video.currentSrc) || videoSrc;
1173
+ if (hlsRef.current && src.startsWith("blob:")) return;
1174
+ reportError(error, { type: "native", src }, getNativeVideoErrorMessage(error, messages));
1175
+ }, [messages, reportError, videoSrc]);
1176
+ const videoLoadedHandler = (0, import_react2.useCallback)(() => {
1177
+ clearErrorState();
1178
+ }, [clearErrorState]);
1179
+ const mouseMoveHandler = (0, import_react2.useCallback)(() => {
1180
+ if (controlTime.current) clearTimeout(controlTime.current);
1181
+ setControlVisible(true);
1182
+ controlTime.current = setTimeout(() => {
1183
+ setControlVisible(false);
1184
+ }, 1e3 * 10);
1185
+ }, []);
1186
+ (0, import_react2.useImperativeHandle)(
1187
+ ref,
1188
+ () => ({
1189
+ play: playAction,
1190
+ pause: pauseAction,
1191
+ seek: seekSetTime,
1192
+ fullscreen: fullScreenAction,
1193
+ getVideoElement: () => videoRef.current
1194
+ }),
1195
+ [fullScreenAction, pauseAction, playAction, seekSetTime]
1196
+ );
1197
+ (0, import_react2.useEffect)(() => {
1198
+ const el = videoRef.current;
1199
+ if (!el) return;
1200
+ el.muted = muted;
1201
+ }, [muted]);
1202
+ (0, import_react2.useEffect)(() => {
1203
+ if (playingProp === void 0) return;
1204
+ if (!canAttemptPlayback(videoRef.current)) return;
1205
+ if (playingProp) void playAction();
1206
+ else pauseAction();
1207
+ }, [pauseAction, playAction, playingProp]);
1208
+ (0, import_react2.useEffect)(() => {
1209
+ const el = videoRef.current;
1210
+ if (!el || currentTimeProp === void 0) return;
1211
+ if (Math.abs(el.currentTime - currentTimeProp) < 0.25) return;
1212
+ el.currentTime = clampTime(currentTimeProp, duration);
1213
+ }, [currentTimeProp, duration]);
1214
+ (0, import_react2.useEffect)(() => {
1215
+ if (!videoRef.current) return;
1216
+ clearErrorState();
1217
+ fatalReloadCount.current = 0;
1218
+ netRetryCount.current = 0;
1219
+ mediaRecoverCount.current = 0;
1220
+ liveStabilityMode.current = false;
1221
+ liveStallEvents.current = [];
1222
+ if (hlsRef.current) {
1223
+ hlsRef.current.destroy();
1224
+ hlsRef.current = null;
1225
+ }
1226
+ if (sourceType === "native" || type === "video") loadVideoNative();
1227
+ else if (sourceType === "hls") void loadVideo();
1228
+ else void loadVideoHls();
1229
+ return () => {
1230
+ clearTimers();
1231
+ if (hlsRef.current) {
1232
+ hlsRef.current.destroy();
1233
+ hlsRef.current = null;
1234
+ }
1235
+ };
1236
+ }, [
1237
+ clearErrorState,
1238
+ clearTimers,
1239
+ loadVideo,
1240
+ loadVideoHls,
1241
+ loadVideoNative,
1242
+ reloadKey,
1243
+ reloadToken,
1244
+ sourceType,
1245
+ type,
1246
+ videoSrc
1247
+ ]);
1248
+ (0, import_react2.useEffect)(() => {
1249
+ if (!autoPlay) return;
1250
+ if (!canAttemptPlayback(videoRef.current)) return;
1251
+ void playAction();
1252
+ }, [autoPlay, playAction]);
1253
+ (0, import_react2.useEffect)(() => {
1254
+ if (typeof document === "undefined") return;
1255
+ const doc = document;
1256
+ const handleFsChange = () => {
1257
+ const fs = !!document.fullscreenElement || !!doc.webkitFullscreenElement || !!doc.mozFullScreenElement || !!doc.msFullscreenElement;
1258
+ setIsFullscreen(fs);
1259
+ };
1260
+ document.addEventListener("fullscreenchange", handleFsChange);
1261
+ document.addEventListener("webkitfullscreenchange", handleFsChange);
1262
+ document.addEventListener("mozfullscreenchange", handleFsChange);
1263
+ document.addEventListener("MSFullscreenChange", handleFsChange);
1264
+ return () => {
1265
+ document.removeEventListener("fullscreenchange", handleFsChange);
1266
+ document.removeEventListener("webkitfullscreenchange", handleFsChange);
1267
+ document.removeEventListener("mozfullscreenchange", handleFsChange);
1268
+ document.removeEventListener("MSFullscreenChange", handleFsChange);
1269
+ };
1270
+ }, []);
1271
+ (0, import_react2.useEffect)(() => {
1272
+ return () => {
1273
+ setTimeD.current.cancel();
1274
+ destroy();
1275
+ };
1276
+ }, [destroy]);
1277
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
1278
+ VideoPlayerStyles,
1279
+ {
1280
+ ref: containerRef,
1281
+ onMouseMove: mouseMoveHandler,
1282
+ className: "VideoPlayer",
1283
+ type,
1284
+ $isMobile: isMobile,
1285
+ $mobileType: mobileType,
1286
+ $showControl: showControl,
1287
+ $alwaysShowSegments: alwaysShowSegments,
1288
+ onPointerDown,
1289
+ onPointerMove,
1290
+ onPointerUp,
1291
+ onPointerCancel: onPointerCancel || onPointerUp,
1292
+ $color: accentColor,
1293
+ children: [
1294
+ VideoTopBar && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-top-bar", children: VideoTopBar }),
1295
+ isFullscreen && VideoRightBar && !isMobile && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "video-right-bar", children: [
1296
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { onClick: showClickHandler, className: "video-right-bar-toggle", children: "<" }),
1297
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-right-bar-content", children: VideoRightBar })
1298
+ ] }),
1299
+ !isFullscreen && VideoRightBar && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "video-right-bar", children: [
1300
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { onClick: showClickHandler, className: "video-right-bar-toggle", children: "<" }),
1301
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-right-bar-content", children: VideoRightBar })
1302
+ ] }),
1303
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "video-wrapper", children: [
1304
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1305
+ "video",
1306
+ {
1307
+ ...videoProps,
1308
+ ref: setVideoEl,
1309
+ crossOrigin,
1310
+ playsInline: true,
1311
+ muted,
1312
+ preload,
1313
+ loop,
1314
+ onPlay: () => setNextPlaying(true),
1315
+ onPause: () => setNextPlaying(false),
1316
+ onEnded: () => setNextPlaying(false),
1317
+ onLoadedData: videoLoadedHandler,
1318
+ onCanPlay: videoLoadedHandler,
1319
+ onError: videoErrorHandler,
1320
+ onClick: videoClickHandler,
1321
+ onDoubleClick: videoDbClickHandler,
1322
+ poster
1323
+ }
1324
+ ),
1325
+ errorState && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-error-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "video-error-content", children: [
1326
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-error-title", children: errorState.message }),
1327
+ errorState.details.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-error-details", children: errorState.details.map((detail) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "video-error-detail", children: detail }, detail)) }),
1328
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", onClick: retryLoadHandler, className: "video-error-retry", children: messages.retry })
1329
+ ] }) })
1330
+ ] }),
1331
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1332
+ VideoPlayerControls,
1333
+ {
1334
+ variant: controlsVariant,
1335
+ fullScreenAction,
1336
+ showControl,
1337
+ videoRef,
1338
+ playAction,
1339
+ pauseAction,
1340
+ seekSetTime,
1341
+ seekPrevAction,
1342
+ seekNextAction,
1343
+ playing,
1344
+ muted,
1345
+ onMutedChange: setNextMuted,
1346
+ duration,
1347
+ currentTime,
1348
+ title
1349
+ }
1350
+ )
1351
+ ]
1352
+ }
1353
+ );
1354
+ };
1355
+ var VideoPlayer = (0, import_react2.memo)((0, import_react2.forwardRef)(VideoPlayerBase));
1356
+ var VideoPlayer_default = VideoPlayer;
1357
+ // Annotate the CommonJS export names for ESM import in node:
1358
+ 0 && (module.exports = {
1359
+ VideoPlayer,
1360
+ hasNativeHlsSupport
1361
+ });