stormcloud-video-player 0.1.13 → 0.2.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.
@@ -0,0 +1,2905 @@
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/ui/StormcloudVideoPlayer.tsx
31
+ var StormcloudVideoPlayer_exports = {};
32
+ __export(StormcloudVideoPlayer_exports, {
33
+ StormcloudVideoPlayerComponent: () => StormcloudVideoPlayerComponent
34
+ });
35
+ module.exports = __toCommonJS(StormcloudVideoPlayer_exports);
36
+ var import_react = __toESM(require("react"), 1);
37
+
38
+ // src/player/StormcloudVideoPlayer.ts
39
+ var import_hls = __toESM(require("hls.js"), 1);
40
+
41
+ // src/sdk/ima.ts
42
+ function createImaController(video) {
43
+ let adPlaying = false;
44
+ let originalMutedState = false;
45
+ const listeners = /* @__PURE__ */ new Map();
46
+ function emit(event, payload) {
47
+ const set = listeners.get(event);
48
+ if (!set) return;
49
+ for (const fn of Array.from(set)) {
50
+ try {
51
+ fn(payload);
52
+ } catch {
53
+ }
54
+ }
55
+ }
56
+ function ensureImaLoaded() {
57
+ try {
58
+ const frameEl = window.frameElement;
59
+ const sandboxAttr = frameEl?.getAttribute?.("sandbox") || "";
60
+ if (sandboxAttr) {
61
+ const tokens = new Set(
62
+ sandboxAttr.split(/\s+/).map((t) => t.trim()).filter((t) => t.length > 0)
63
+ );
64
+ const allowsScripts = tokens.has("allow-scripts");
65
+ if (!allowsScripts) {
66
+ console.error(
67
+ "StormcloudVideoPlayer: The host page is inside a sandboxed iframe without 'allow-scripts'. Google IMA cannot run ads within sandboxed frames. Remove the sandbox attribute or include 'allow-scripts allow-same-origin'."
68
+ );
69
+ }
70
+ }
71
+ } catch {
72
+ }
73
+ if (typeof window !== "undefined" && window.google?.ima)
74
+ return Promise.resolve();
75
+ const existing = document.querySelector(
76
+ 'script[data-ima="true"]'
77
+ );
78
+ if (existing) {
79
+ return new Promise(
80
+ (resolve) => existing.addEventListener("load", () => resolve())
81
+ );
82
+ }
83
+ return new Promise((resolve, reject) => {
84
+ const script = document.createElement("script");
85
+ script.src = "https://imasdk.googleapis.com/js/sdkloader/ima3.js";
86
+ script.async = true;
87
+ script.defer = true;
88
+ script.setAttribute("data-ima", "true");
89
+ script.onload = () => resolve();
90
+ script.onerror = () => reject(new Error("IMA SDK load failed"));
91
+ document.head.appendChild(script);
92
+ });
93
+ }
94
+ let adsManager;
95
+ let adsLoader;
96
+ let adDisplayContainer;
97
+ let adContainerEl;
98
+ let lastAdTagUrl;
99
+ let retryAttempts = 0;
100
+ const maxRetries = 2;
101
+ const backoffBaseMs = 500;
102
+ let adsLoadedPromise;
103
+ let adsLoadedResolve;
104
+ let adsLoadedReject;
105
+ function makeAdsRequest(google, vastTagUrl) {
106
+ const adsRequest = new google.ima.AdsRequest();
107
+ adsRequest.adTagUrl = vastTagUrl;
108
+ adsLoader.requestAds(adsRequest);
109
+ }
110
+ return {
111
+ initialize() {
112
+ ensureImaLoaded().then(() => {
113
+ const google = window.google;
114
+ if (!adDisplayContainer) {
115
+ const container = document.createElement("div");
116
+ container.style.position = "absolute";
117
+ container.style.left = "0";
118
+ container.style.top = "0";
119
+ container.style.right = "0";
120
+ container.style.bottom = "0";
121
+ container.style.display = "flex";
122
+ container.style.alignItems = "center";
123
+ container.style.justifyContent = "center";
124
+ container.style.pointerEvents = "none";
125
+ container.style.zIndex = "2";
126
+ video.parentElement?.appendChild(container);
127
+ adContainerEl = container;
128
+ adDisplayContainer = new google.ima.AdDisplayContainer(
129
+ container,
130
+ video
131
+ );
132
+ try {
133
+ adDisplayContainer.initialize?.();
134
+ } catch {
135
+ }
136
+ }
137
+ }).catch(() => {
138
+ });
139
+ },
140
+ async requestAds(vastTagUrl) {
141
+ console.log("[IMA] Requesting ads:", vastTagUrl);
142
+ adsLoadedPromise = new Promise((resolve, reject) => {
143
+ adsLoadedResolve = resolve;
144
+ adsLoadedReject = reject;
145
+ setTimeout(() => {
146
+ if (adsLoadedReject) {
147
+ adsLoadedReject(new Error("Ad request timeout"));
148
+ adsLoadedReject = void 0;
149
+ adsLoadedResolve = void 0;
150
+ }
151
+ }, 1e4);
152
+ });
153
+ try {
154
+ await ensureImaLoaded();
155
+ const google = window.google;
156
+ lastAdTagUrl = vastTagUrl;
157
+ retryAttempts = 0;
158
+ if (!adDisplayContainer) {
159
+ console.log("[IMA] Creating ad display container");
160
+ const container = document.createElement("div");
161
+ container.style.position = "absolute";
162
+ container.style.left = "0";
163
+ container.style.top = "0";
164
+ container.style.right = "0";
165
+ container.style.bottom = "0";
166
+ container.style.display = "flex";
167
+ container.style.alignItems = "center";
168
+ container.style.justifyContent = "center";
169
+ container.style.pointerEvents = "none";
170
+ container.style.zIndex = "2";
171
+ if (!video.parentElement) {
172
+ throw new Error("Video element has no parent for ad container");
173
+ }
174
+ video.parentElement.appendChild(container);
175
+ adContainerEl = container;
176
+ adDisplayContainer = new google.ima.AdDisplayContainer(
177
+ container,
178
+ video
179
+ );
180
+ try {
181
+ adDisplayContainer.initialize();
182
+ console.log("[IMA] Ad display container initialized");
183
+ } catch (error) {
184
+ console.warn(
185
+ "[IMA] Failed to initialize ad display container:",
186
+ error
187
+ );
188
+ }
189
+ }
190
+ if (!adsLoader) {
191
+ console.log("[IMA] Creating ads loader");
192
+ const adsLoaderCls = new google.ima.AdsLoader(adDisplayContainer);
193
+ adsLoader = adsLoaderCls;
194
+ adsLoader.addEventListener(
195
+ google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,
196
+ (evt) => {
197
+ console.log("[IMA] Ads manager loaded");
198
+ try {
199
+ adsManager = evt.getAdsManager(video);
200
+ const AdEvent = google.ima.AdEvent.Type;
201
+ const AdErrorEvent = google.ima.AdErrorEvent.Type;
202
+ adsManager.addEventListener(
203
+ AdErrorEvent.AD_ERROR,
204
+ (errorEvent) => {
205
+ console.error("[IMA] Ad error:", errorEvent.getError());
206
+ try {
207
+ adsManager?.destroy?.();
208
+ } catch {
209
+ }
210
+ adPlaying = false;
211
+ video.muted = originalMutedState;
212
+ if (adContainerEl)
213
+ adContainerEl.style.pointerEvents = "none";
214
+ if (adsLoadedReject) {
215
+ adsLoadedReject(new Error("Ad playback error"));
216
+ adsLoadedReject = void 0;
217
+ adsLoadedResolve = void 0;
218
+ }
219
+ if (lastAdTagUrl && retryAttempts < maxRetries) {
220
+ const delay = backoffBaseMs * Math.pow(2, retryAttempts++);
221
+ console.log(
222
+ `[IMA] Retrying ad request in ${delay}ms (attempt ${retryAttempts})`
223
+ );
224
+ window.setTimeout(() => {
225
+ try {
226
+ makeAdsRequest(google, lastAdTagUrl);
227
+ } catch {
228
+ }
229
+ }, delay);
230
+ } else {
231
+ console.log(
232
+ "[IMA] Max retries reached, emitting ad_error"
233
+ );
234
+ emit("ad_error");
235
+ video.play().catch(() => {
236
+ });
237
+ }
238
+ }
239
+ );
240
+ adsManager.addEventListener(
241
+ AdEvent.CONTENT_PAUSE_REQUESTED,
242
+ () => {
243
+ console.log("[IMA] Content pause requested");
244
+ originalMutedState = video.muted;
245
+ video.muted = true;
246
+ video.pause();
247
+ adPlaying = true;
248
+ if (adContainerEl)
249
+ adContainerEl.style.pointerEvents = "auto";
250
+ emit("content_pause");
251
+ }
252
+ );
253
+ adsManager.addEventListener(
254
+ AdEvent.CONTENT_RESUME_REQUESTED,
255
+ () => {
256
+ console.log("[IMA] Content resume requested");
257
+ adPlaying = false;
258
+ video.muted = originalMutedState;
259
+ if (adContainerEl)
260
+ adContainerEl.style.pointerEvents = "none";
261
+ video.play().catch(() => {
262
+ });
263
+ emit("content_resume");
264
+ }
265
+ );
266
+ adsManager.addEventListener(AdEvent.ALL_ADS_COMPLETED, () => {
267
+ console.log("[IMA] All ads completed");
268
+ adPlaying = false;
269
+ video.muted = originalMutedState;
270
+ if (adContainerEl) adContainerEl.style.pointerEvents = "none";
271
+ video.play().catch(() => {
272
+ });
273
+ emit("all_ads_completed");
274
+ });
275
+ console.log("[IMA] Ads manager event listeners attached");
276
+ if (adsLoadedResolve) {
277
+ adsLoadedResolve();
278
+ adsLoadedResolve = void 0;
279
+ adsLoadedReject = void 0;
280
+ }
281
+ } catch (e) {
282
+ console.error("[IMA] Error setting up ads manager:", e);
283
+ adPlaying = false;
284
+ video.muted = originalMutedState;
285
+ if (adContainerEl) adContainerEl.style.pointerEvents = "none";
286
+ video.play().catch(() => {
287
+ });
288
+ if (adsLoadedReject) {
289
+ adsLoadedReject(new Error("Failed to setup ads manager"));
290
+ adsLoadedReject = void 0;
291
+ adsLoadedResolve = void 0;
292
+ }
293
+ emit("ad_error");
294
+ }
295
+ },
296
+ false
297
+ );
298
+ adsLoader.addEventListener(
299
+ google.ima.AdErrorEvent.Type.AD_ERROR,
300
+ (adErrorEvent) => {
301
+ console.error("[IMA] Ads loader error:", adErrorEvent.getError());
302
+ if (adsLoadedReject) {
303
+ adsLoadedReject(new Error("Ads loader error"));
304
+ adsLoadedReject = void 0;
305
+ adsLoadedResolve = void 0;
306
+ }
307
+ emit("ad_error");
308
+ },
309
+ false
310
+ );
311
+ }
312
+ console.log("[IMA] Making ads request");
313
+ makeAdsRequest(google, vastTagUrl);
314
+ return adsLoadedPromise;
315
+ } catch (error) {
316
+ console.error("[IMA] Failed to request ads:", error);
317
+ if (adsLoadedReject) {
318
+ adsLoadedReject(error);
319
+ adsLoadedReject = void 0;
320
+ adsLoadedResolve = void 0;
321
+ }
322
+ return Promise.reject(error);
323
+ }
324
+ },
325
+ async play() {
326
+ if (!window.google?.ima || !adDisplayContainer) {
327
+ console.warn(
328
+ "[IMA] Cannot play ad: IMA SDK or ad container not available"
329
+ );
330
+ return Promise.reject(new Error("IMA SDK not available"));
331
+ }
332
+ if (!adsManager) {
333
+ console.warn("[IMA] Cannot play ad: No ads manager available");
334
+ return Promise.reject(new Error("No ads manager"));
335
+ }
336
+ try {
337
+ const width = video.clientWidth || 640;
338
+ const height = video.clientHeight || 360;
339
+ console.log(`[IMA] Initializing ads manager (${width}x${height})`);
340
+ adsManager.init(width, height, window.google.ima.ViewMode.NORMAL);
341
+ console.log("[IMA] Pausing video for ad playback");
342
+ video.pause();
343
+ adPlaying = true;
344
+ console.log("[IMA] Starting ad playback");
345
+ adsManager.start();
346
+ return Promise.resolve();
347
+ } catch (error) {
348
+ console.error("[IMA] Error starting ad playback:", error);
349
+ adPlaying = false;
350
+ video.play().catch(() => {
351
+ });
352
+ return Promise.reject(error);
353
+ }
354
+ },
355
+ async stop() {
356
+ adPlaying = false;
357
+ video.muted = originalMutedState;
358
+ try {
359
+ adsManager?.stop?.();
360
+ } catch {
361
+ }
362
+ video.play().catch(() => {
363
+ });
364
+ },
365
+ destroy() {
366
+ try {
367
+ adsManager?.destroy?.();
368
+ } catch {
369
+ }
370
+ adPlaying = false;
371
+ video.muted = originalMutedState;
372
+ try {
373
+ adsLoader?.destroy?.();
374
+ } catch {
375
+ }
376
+ if (adContainerEl?.parentElement) {
377
+ adContainerEl.parentElement.removeChild(adContainerEl);
378
+ }
379
+ },
380
+ isAdPlaying() {
381
+ return adPlaying;
382
+ },
383
+ resize(width, height) {
384
+ if (!adsManager || !window.google?.ima) {
385
+ console.warn(
386
+ "[IMA] Cannot resize: No ads manager or IMA SDK available"
387
+ );
388
+ return;
389
+ }
390
+ try {
391
+ console.log(`[IMA] Resizing ads manager to ${width}x${height}`);
392
+ adsManager.resize(width, height, window.google.ima.ViewMode.NORMAL);
393
+ } catch (error) {
394
+ console.warn("[IMA] Error resizing ads manager:", error);
395
+ }
396
+ },
397
+ on(event, listener) {
398
+ if (!listeners.has(event)) listeners.set(event, /* @__PURE__ */ new Set());
399
+ listeners.get(event).add(listener);
400
+ },
401
+ off(event, listener) {
402
+ listeners.get(event)?.delete(listener);
403
+ },
404
+ updateOriginalMutedState(muted) {
405
+ originalMutedState = muted;
406
+ },
407
+ getOriginalMutedState() {
408
+ return originalMutedState;
409
+ },
410
+ setAdVolume(volume) {
411
+ if (adsManager && adPlaying) {
412
+ try {
413
+ adsManager.setVolume(Math.max(0, Math.min(1, volume)));
414
+ } catch (error) {
415
+ console.warn("[IMA] Failed to set ad volume:", error);
416
+ }
417
+ }
418
+ },
419
+ getAdVolume() {
420
+ if (adsManager && adPlaying) {
421
+ try {
422
+ return adsManager.getVolume();
423
+ } catch (error) {
424
+ console.warn("[IMA] Failed to get ad volume:", error);
425
+ return 1;
426
+ }
427
+ }
428
+ return 1;
429
+ }
430
+ };
431
+ }
432
+
433
+ // src/utils/tracking.ts
434
+ function getClientInfo() {
435
+ const ua = navigator.userAgent;
436
+ const platform = navigator.platform;
437
+ const vendor = navigator.vendor || "";
438
+ const maxTouchPoints = navigator.maxTouchPoints || 0;
439
+ const memory = navigator.deviceMemory || null;
440
+ const hardwareConcurrency = navigator.hardwareConcurrency || 1;
441
+ const screenInfo = {
442
+ width: screen?.width,
443
+ height: screen?.height,
444
+ availWidth: screen?.availWidth,
445
+ availHeight: screen?.availHeight,
446
+ orientation: screen?.orientation?.type || "",
447
+ pixelDepth: screen?.pixelDepth
448
+ };
449
+ let deviceType = "desktop";
450
+ let brand = "Unknown";
451
+ let os = "Unknown";
452
+ let model = "";
453
+ let isSmartTV = false;
454
+ let isAndroid = false;
455
+ let isWebView = false;
456
+ let isWebApp = false;
457
+ if (ua.includes("Web0S")) {
458
+ brand = "LG";
459
+ os = "webOS";
460
+ isSmartTV = true;
461
+ deviceType = "tv";
462
+ const webosMatch = ua.match(/Web0S\/([^\s]+)/);
463
+ model = webosMatch ? `webOS ${webosMatch[1]}` : "webOS TV";
464
+ } else if (ua.includes("Tizen")) {
465
+ brand = "Samsung";
466
+ os = "Tizen";
467
+ isSmartTV = true;
468
+ deviceType = "tv";
469
+ const tizenMatch = ua.match(/Tizen\/([^\s]+)/);
470
+ const tvMatch = ua.match(/(?:Smart-TV|SMART-TV|TV)/i) ? "Smart TV" : "";
471
+ model = tizenMatch ? `Tizen ${tizenMatch[1]} ${tvMatch}`.trim() : "Tizen TV";
472
+ } else if (ua.includes("Philips")) {
473
+ brand = "Philips";
474
+ os = "Saphi";
475
+ isSmartTV = true;
476
+ deviceType = "tv";
477
+ } else if (ua.includes("Sharp") || ua.includes("AQUOS")) {
478
+ brand = "Sharp";
479
+ os = "Android TV";
480
+ isSmartTV = true;
481
+ deviceType = "tv";
482
+ } else if (ua.includes("Android") && (ua.includes("Sony") || vendor.includes("Sony"))) {
483
+ brand = "Sony";
484
+ os = "Android TV";
485
+ isSmartTV = true;
486
+ deviceType = "tv";
487
+ } else if (ua.includes("Android") && (ua.includes("NetCast") || ua.includes("LG"))) {
488
+ brand = "LG";
489
+ os = "Android TV";
490
+ isSmartTV = true;
491
+ deviceType = "tv";
492
+ } else if (ua.includes(" Roku") || ua.includes("Roku/")) {
493
+ brand = "Roku";
494
+ os = "Roku OS";
495
+ isSmartTV = true;
496
+ deviceType = "tv";
497
+ } else if (ua.includes("AppleTV")) {
498
+ brand = "Apple";
499
+ os = "tvOS";
500
+ isSmartTV = true;
501
+ deviceType = "tv";
502
+ }
503
+ if (ua.includes("Android")) {
504
+ isAndroid = true;
505
+ os = "Android";
506
+ deviceType = /Mobile/.test(ua) ? "mobile" : "tablet";
507
+ if (ua.includes("Android") && (maxTouchPoints === 0 || ua.includes("Google TV") || ua.includes("XiaoMi"))) {
508
+ deviceType = "tv";
509
+ isSmartTV = true;
510
+ brand = brand === "Unknown" ? "Android TV" : brand;
511
+ }
512
+ const androidModelMatch = ua.match(/\(([^)]*Android[^)]*)\)/);
513
+ if (androidModelMatch && androidModelMatch[1]) {
514
+ model = androidModelMatch[1];
515
+ }
516
+ }
517
+ if (/iPad|iPhone|iPod/.test(ua)) {
518
+ os = "iOS";
519
+ deviceType = "mobile";
520
+ brand = "Apple";
521
+ if (navigator.maxTouchPoints > 1 && /iPad/.test(ua)) {
522
+ deviceType = "tablet";
523
+ }
524
+ }
525
+ if (!isAndroid && !isSmartTV && !/Mobile/.test(ua)) {
526
+ if (ua.includes("Windows")) {
527
+ os = "Windows";
528
+ deviceType = "desktop";
529
+ } else if (ua.includes("Mac") && !/iPhone/.test(ua)) {
530
+ os = "macOS";
531
+ deviceType = "desktop";
532
+ if (maxTouchPoints > 1) deviceType = "tablet";
533
+ } else if (ua.includes("Linux")) {
534
+ os = "Linux";
535
+ deviceType = "desktop";
536
+ }
537
+ }
538
+ if (brand === "Unknown") {
539
+ if (vendor.includes("Google") || ua.includes("Chrome")) brand = "Google";
540
+ if (vendor.includes("Apple")) brand = "Apple";
541
+ if (vendor.includes("Samsung") || ua.includes("SM-")) brand = "Samsung";
542
+ }
543
+ isWebView = /wv|WebView|Linux; U;/.test(ua);
544
+ if (window?.outerHeight === 0 && window?.outerWidth === 0) {
545
+ isWebView = true;
546
+ }
547
+ isWebApp = window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true || window.screen?.orientation?.angle !== void 0;
548
+ return {
549
+ brand,
550
+ os,
551
+ model: model || ua.substring(0, 50) + "...",
552
+ deviceType,
553
+ isSmartTV,
554
+ isAndroid,
555
+ isWebView,
556
+ isWebApp,
557
+ domain: window.location.hostname,
558
+ origin: window.location.origin,
559
+ path: window.location.pathname,
560
+ userAgent: ua,
561
+ vendor,
562
+ platform,
563
+ screen: screenInfo,
564
+ hardwareConcurrency,
565
+ deviceMemory: memory,
566
+ maxTouchPoints,
567
+ language: navigator.language,
568
+ languages: navigator.languages?.join(",") || "",
569
+ cookieEnabled: navigator.cookieEnabled,
570
+ doNotTrack: navigator.doNotTrack || "",
571
+ referrer: document.referrer,
572
+ visibilityState: document.visibilityState
573
+ };
574
+ }
575
+ async function getBrowserID(clientInfo) {
576
+ const fingerprintString = JSON.stringify(clientInfo);
577
+ const hashBuffer = await crypto.subtle.digest(
578
+ "SHA-256",
579
+ new TextEncoder().encode(fingerprintString)
580
+ );
581
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
582
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
583
+ return hashHex;
584
+ }
585
+ async function sendInitialTracking(licenseKey) {
586
+ try {
587
+ const clientInfo = getClientInfo();
588
+ const browserId = await getBrowserID(clientInfo);
589
+ const trackingData = {
590
+ browserId,
591
+ ...clientInfo
592
+ };
593
+ const headers = {
594
+ "Content-Type": "application/json"
595
+ };
596
+ if (licenseKey) {
597
+ headers["Authorization"] = `Bearer ${licenseKey}`;
598
+ }
599
+ const response = await fetch(
600
+ "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/track",
601
+ {
602
+ method: "POST",
603
+ headers,
604
+ body: JSON.stringify(trackingData)
605
+ }
606
+ );
607
+ if (!response.ok) {
608
+ throw new Error(`HTTP error! status: ${response.status}`);
609
+ }
610
+ await response.json();
611
+ } catch (error) {
612
+ console.error(
613
+ "[StormcloudVideoPlayer] Error sending initial tracking data:",
614
+ error
615
+ );
616
+ }
617
+ }
618
+ async function sendHeartbeat(licenseKey) {
619
+ try {
620
+ const clientInfo = getClientInfo();
621
+ const browserId = await getBrowserID(clientInfo);
622
+ const heartbeatData = {
623
+ browserId,
624
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
625
+ };
626
+ const headers = {
627
+ "Content-Type": "application/json"
628
+ };
629
+ if (licenseKey) {
630
+ headers["Authorization"] = `Bearer ${licenseKey}`;
631
+ }
632
+ const response = await fetch(
633
+ "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking/heartbeat",
634
+ {
635
+ method: "POST",
636
+ headers,
637
+ body: JSON.stringify(heartbeatData)
638
+ }
639
+ );
640
+ if (!response.ok) {
641
+ throw new Error(`HTTP error! status: ${response.status}`);
642
+ }
643
+ await response.json();
644
+ } catch (error) {
645
+ console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
646
+ }
647
+ }
648
+
649
+ // src/player/StormcloudVideoPlayer.ts
650
+ var StormcloudVideoPlayer = class {
651
+ constructor(config) {
652
+ this.attached = false;
653
+ this.inAdBreak = false;
654
+ this.ptsDriftEmaMs = 0;
655
+ this.adPodQueue = [];
656
+ this.lastHeartbeatTime = 0;
657
+ this.currentAdIndex = 0;
658
+ this.totalAdsInBreak = 0;
659
+ this.showAds = false;
660
+ this.config = config;
661
+ this.video = config.videoElement;
662
+ this.ima = createImaController(this.video);
663
+ }
664
+ async load() {
665
+ if (!this.attached) {
666
+ this.attach();
667
+ }
668
+ try {
669
+ await this.fetchAdConfiguration();
670
+ } catch (error) {
671
+ if (this.config.debugAdTiming) {
672
+ console.warn(
673
+ "[StormcloudVideoPlayer] Failed to fetch ad configuration:",
674
+ error
675
+ );
676
+ }
677
+ }
678
+ this.initializeTracking();
679
+ if (this.shouldUseNativeHls()) {
680
+ this.video.src = this.config.src;
681
+ if (this.config.autoplay) {
682
+ await this.video.play().catch(() => {
683
+ });
684
+ }
685
+ return;
686
+ }
687
+ this.hls = new import_hls.default({
688
+ enableWorker: true,
689
+ backBufferLength: 30,
690
+ liveDurationInfinity: true,
691
+ lowLatencyMode: !!this.config.lowLatencyMode,
692
+ maxLiveSyncPlaybackRate: this.config.lowLatencyMode ? 1.5 : 1,
693
+ ...this.config.lowLatencyMode ? { liveSyncDuration: 2 } : {}
694
+ });
695
+ this.hls.on(import_hls.default.Events.MEDIA_ATTACHED, () => {
696
+ this.hls?.loadSource(this.config.src);
697
+ });
698
+ this.hls.on(import_hls.default.Events.MANIFEST_PARSED, async () => {
699
+ if (this.config.autoplay) {
700
+ await this.video.play().catch(() => {
701
+ });
702
+ }
703
+ });
704
+ this.hls.on(import_hls.default.Events.FRAG_PARSING_METADATA, (_evt, data) => {
705
+ const id3Tags = (data?.samples || []).map((s) => ({
706
+ key: "ID3",
707
+ value: s?.data,
708
+ ptsSeconds: s?.pts
709
+ }));
710
+ id3Tags.forEach((tag) => this.onId3Tag(tag));
711
+ });
712
+ this.hls.on(import_hls.default.Events.FRAG_CHANGED, (_evt, data) => {
713
+ const frag = data?.frag;
714
+ const tagList = frag?.tagList;
715
+ if (!Array.isArray(tagList)) return;
716
+ for (const entry of tagList) {
717
+ let tag = "";
718
+ let value = "";
719
+ if (Array.isArray(entry)) {
720
+ tag = String(entry[0] ?? "");
721
+ value = String(entry[1] ?? "");
722
+ } else if (typeof entry === "string") {
723
+ const idx = entry.indexOf(":");
724
+ if (idx >= 0) {
725
+ tag = entry.substring(0, idx);
726
+ value = entry.substring(idx + 1);
727
+ } else {
728
+ tag = entry;
729
+ value = "";
730
+ }
731
+ }
732
+ if (!tag) continue;
733
+ if (tag.includes("EXT-X-CUE-OUT")) {
734
+ const durationSeconds = this.parseCueOutDuration(value);
735
+ const marker = {
736
+ type: "start",
737
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
738
+ raw: { tag, value }
739
+ };
740
+ this.onScte35Marker(marker);
741
+ } else if (tag.includes("EXT-X-CUE-OUT-CONT")) {
742
+ const prog = this.parseCueOutCont(value);
743
+ const marker = {
744
+ type: "progress",
745
+ ...prog?.duration !== void 0 ? { durationSeconds: prog.duration } : {},
746
+ ...prog?.elapsed !== void 0 ? { ptsSeconds: prog.elapsed } : {},
747
+ raw: { tag, value }
748
+ };
749
+ this.onScte35Marker(marker);
750
+ } else if (tag.includes("EXT-X-CUE-IN")) {
751
+ this.onScte35Marker({ type: "end", raw: { tag, value } });
752
+ } else if (tag.includes("EXT-X-DATERANGE")) {
753
+ const attrs = this.parseAttributeList(value);
754
+ const hasScteOut = "SCTE35-OUT" in attrs || attrs["SCTE35-OUT"] !== void 0;
755
+ const hasScteIn = "SCTE35-IN" in attrs || attrs["SCTE35-IN"] !== void 0;
756
+ const klass = String(attrs["CLASS"] ?? "");
757
+ const duration = this.toNumber(attrs["DURATION"]);
758
+ if (hasScteOut || /com\.apple\.hls\.cue/i.test(klass)) {
759
+ const marker = {
760
+ type: "start",
761
+ ...duration !== void 0 ? { durationSeconds: duration } : {},
762
+ raw: { tag, value, attrs }
763
+ };
764
+ this.onScte35Marker(marker);
765
+ }
766
+ if (hasScteIn) {
767
+ this.onScte35Marker({ type: "end", raw: { tag, value, attrs } });
768
+ }
769
+ }
770
+ }
771
+ });
772
+ this.hls.on(import_hls.default.Events.ERROR, (_evt, data) => {
773
+ if (data?.fatal) {
774
+ switch (data.type) {
775
+ case import_hls.default.ErrorTypes.NETWORK_ERROR:
776
+ this.hls?.startLoad();
777
+ break;
778
+ case import_hls.default.ErrorTypes.MEDIA_ERROR:
779
+ this.hls?.recoverMediaError();
780
+ break;
781
+ default:
782
+ this.destroy();
783
+ break;
784
+ }
785
+ }
786
+ });
787
+ this.hls.attachMedia(this.video);
788
+ }
789
+ attach() {
790
+ if (this.attached) return;
791
+ this.attached = true;
792
+ this.video.autoplay = !!this.config.autoplay;
793
+ this.video.muted = !!this.config.muted;
794
+ this.ima.initialize();
795
+ this.ima.on("all_ads_completed", () => {
796
+ if (!this.inAdBreak) return;
797
+ const remaining = this.getRemainingAdMs();
798
+ if (remaining > 500 && this.adPodQueue.length > 0) {
799
+ const next = this.adPodQueue.shift();
800
+ this.currentAdIndex++;
801
+ this.playSingleAd(next).catch(() => {
802
+ });
803
+ } else {
804
+ this.currentAdIndex = 0;
805
+ this.totalAdsInBreak = 0;
806
+ this.showAds = false;
807
+ }
808
+ });
809
+ this.ima.on("ad_error", () => {
810
+ if (this.config.debugAdTiming) {
811
+ console.log("[StormcloudVideoPlayer] IMA ad_error event received");
812
+ }
813
+ if (!this.inAdBreak) return;
814
+ const remaining = this.getRemainingAdMs();
815
+ if (remaining > 500 && this.adPodQueue.length > 0) {
816
+ const next = this.adPodQueue.shift();
817
+ this.currentAdIndex++;
818
+ this.playSingleAd(next).catch(() => {
819
+ });
820
+ } else {
821
+ this.handleAdFailure();
822
+ }
823
+ });
824
+ this.ima.on("content_pause", () => {
825
+ if (this.config.debugAdTiming) {
826
+ console.log("[StormcloudVideoPlayer] IMA content_pause event received");
827
+ }
828
+ this.clearAdFailsafeTimer();
829
+ });
830
+ this.ima.on("content_resume", () => {
831
+ if (this.config.debugAdTiming) {
832
+ console.log(
833
+ "[StormcloudVideoPlayer] IMA content_resume event received"
834
+ );
835
+ }
836
+ this.clearAdFailsafeTimer();
837
+ });
838
+ this.video.addEventListener("timeupdate", () => {
839
+ this.onTimeUpdate(this.video.currentTime);
840
+ });
841
+ }
842
+ shouldUseNativeHls() {
843
+ const streamType = this.getStreamType();
844
+ if (streamType === "other") {
845
+ return true;
846
+ }
847
+ const canNative = this.video.canPlayType("application/vnd.apple.mpegURL");
848
+ return !!(this.config.allowNativeHls && canNative);
849
+ }
850
+ onId3Tag(tag) {
851
+ if (typeof tag.ptsSeconds === "number") {
852
+ this.updatePtsDrift(tag.ptsSeconds);
853
+ }
854
+ const marker = this.parseScte35FromId3(tag);
855
+ if (marker) {
856
+ this.onScte35Marker(marker);
857
+ }
858
+ }
859
+ parseScte35FromId3(tag) {
860
+ const text = this.decodeId3ValueToText(tag.value);
861
+ if (!text) return void 0;
862
+ const cueOutMatch = text.match(/EXT-X-CUE-OUT(?::([^\r\n]*))?/i) || text.match(/CUE-OUT(?::([^\r\n]*))?/i);
863
+ if (cueOutMatch) {
864
+ const arg = (cueOutMatch[1] ?? "").trim();
865
+ const dur = this.parseCueOutDuration(arg);
866
+ const marker = {
867
+ type: "start",
868
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
869
+ ...dur !== void 0 ? { durationSeconds: dur } : {},
870
+ raw: { id3: text }
871
+ };
872
+ return marker;
873
+ }
874
+ const cueOutContMatch = text.match(/EXT-X-CUE-OUT-CONT:([^\r\n]*)/i);
875
+ if (cueOutContMatch) {
876
+ const arg = (cueOutContMatch[1] ?? "").trim();
877
+ const cont = this.parseCueOutCont(arg);
878
+ const marker = {
879
+ type: "progress",
880
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
881
+ ...cont?.duration !== void 0 ? { durationSeconds: cont.duration } : {},
882
+ raw: { id3: text }
883
+ };
884
+ return marker;
885
+ }
886
+ const cueInMatch = text.match(/EXT-X-CUE-IN\b/i) || text.match(/CUE-IN\b/i);
887
+ if (cueInMatch) {
888
+ const marker = {
889
+ type: "end",
890
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
891
+ raw: { id3: text }
892
+ };
893
+ return marker;
894
+ }
895
+ const daterangeMatch = text.match(/EXT-X-DATERANGE:([^\r\n]*)/i);
896
+ if (daterangeMatch) {
897
+ const attrs = this.parseAttributeList(daterangeMatch[1] ?? "");
898
+ const hasScteOut = "SCTE35-OUT" in attrs || attrs["SCTE35-OUT"] !== void 0;
899
+ const hasScteIn = "SCTE35-IN" in attrs || attrs["SCTE35-IN"] !== void 0;
900
+ const klass = String(attrs["CLASS"] ?? "");
901
+ const duration = this.toNumber(attrs["DURATION"]);
902
+ if (hasScteOut || /com\.apple\.hls\.cue/i.test(klass)) {
903
+ const marker = {
904
+ type: "start",
905
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
906
+ ...duration !== void 0 ? { durationSeconds: duration } : {},
907
+ raw: { id3: text, attrs }
908
+ };
909
+ return marker;
910
+ }
911
+ if (hasScteIn) {
912
+ const marker = {
913
+ type: "end",
914
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
915
+ raw: { id3: text, attrs }
916
+ };
917
+ return marker;
918
+ }
919
+ }
920
+ if (/SCTE35-OUT/i.test(text)) {
921
+ const marker = {
922
+ type: "start",
923
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
924
+ raw: { id3: text }
925
+ };
926
+ return marker;
927
+ }
928
+ if (/SCTE35-IN/i.test(text)) {
929
+ const marker = {
930
+ type: "end",
931
+ ...tag.ptsSeconds !== void 0 ? { ptsSeconds: tag.ptsSeconds } : {},
932
+ raw: { id3: text }
933
+ };
934
+ return marker;
935
+ }
936
+ if (tag.value instanceof Uint8Array) {
937
+ const bin = this.parseScte35Binary(tag.value);
938
+ if (bin) return bin;
939
+ }
940
+ return void 0;
941
+ }
942
+ decodeId3ValueToText(value) {
943
+ try {
944
+ if (typeof value === "string") return value;
945
+ const decoder = new TextDecoder("utf-8", { fatal: false });
946
+ const text = decoder.decode(value);
947
+ if (text && /[\x20-\x7E]/.test(text)) return text;
948
+ let out = "";
949
+ for (let i = 0; i < value.length; i++)
950
+ out += String.fromCharCode(value[i]);
951
+ return out;
952
+ } catch {
953
+ return void 0;
954
+ }
955
+ }
956
+ onScte35Marker(marker) {
957
+ if (this.config.debugAdTiming) {
958
+ console.log("[StormcloudVideoPlayer] SCTE-35 marker detected:", {
959
+ type: marker.type,
960
+ ptsSeconds: marker.ptsSeconds,
961
+ durationSeconds: marker.durationSeconds,
962
+ currentTime: this.video.currentTime,
963
+ raw: marker.raw
964
+ });
965
+ }
966
+ if (marker.type === "start") {
967
+ this.inAdBreak = true;
968
+ const durationMs = marker.durationSeconds != null ? marker.durationSeconds * 1e3 : void 0;
969
+ this.expectedAdBreakDurationMs = durationMs;
970
+ this.currentAdBreakStartWallClockMs = Date.now();
971
+ const isManifestMarker = this.isManifestBasedMarker(marker);
972
+ const forceImmediate = this.config.immediateManifestAds ?? true;
973
+ if (this.config.debugAdTiming) {
974
+ console.log("[StormcloudVideoPlayer] Ad start decision:", {
975
+ isManifestMarker,
976
+ forceImmediate,
977
+ hasPts: typeof marker.ptsSeconds === "number"
978
+ });
979
+ }
980
+ if (isManifestMarker && forceImmediate) {
981
+ if (this.config.debugAdTiming) {
982
+ console.log(
983
+ "[StormcloudVideoPlayer] Starting ad immediately (manifest-based)"
984
+ );
985
+ }
986
+ this.clearAdStartTimer();
987
+ this.handleAdStart(marker);
988
+ } else if (typeof marker.ptsSeconds === "number") {
989
+ const tol = this.config.driftToleranceMs ?? 1e3;
990
+ const nowMs = this.video.currentTime * 1e3;
991
+ const estCurrentPtsMs = nowMs - this.ptsDriftEmaMs;
992
+ const deltaMs = Math.floor(marker.ptsSeconds * 1e3 - estCurrentPtsMs);
993
+ if (this.config.debugAdTiming) {
994
+ console.log("[StormcloudVideoPlayer] PTS-based timing calculation:", {
995
+ nowMs,
996
+ estCurrentPtsMs,
997
+ markerPtsMs: marker.ptsSeconds * 1e3,
998
+ deltaMs,
999
+ tolerance: tol
1000
+ });
1001
+ }
1002
+ if (deltaMs > tol) {
1003
+ if (this.config.debugAdTiming) {
1004
+ console.log(
1005
+ `[StormcloudVideoPlayer] Scheduling ad start in ${deltaMs}ms`
1006
+ );
1007
+ }
1008
+ this.scheduleAdStartIn(deltaMs);
1009
+ } else {
1010
+ if (this.config.debugAdTiming) {
1011
+ console.log(
1012
+ "[StormcloudVideoPlayer] Starting ad immediately (within tolerance)"
1013
+ );
1014
+ }
1015
+ this.clearAdStartTimer();
1016
+ this.handleAdStart(marker);
1017
+ }
1018
+ } else {
1019
+ if (this.config.debugAdTiming) {
1020
+ console.log(
1021
+ "[StormcloudVideoPlayer] Starting ad immediately (fallback)"
1022
+ );
1023
+ }
1024
+ this.clearAdStartTimer();
1025
+ this.handleAdStart(marker);
1026
+ }
1027
+ if (this.expectedAdBreakDurationMs != null) {
1028
+ this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1029
+ }
1030
+ return;
1031
+ }
1032
+ if (marker.type === "progress" && this.inAdBreak) {
1033
+ if (marker.durationSeconds != null) {
1034
+ this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
1035
+ }
1036
+ if (this.expectedAdBreakDurationMs != null && this.currentAdBreakStartWallClockMs != null) {
1037
+ const elapsedMs = Date.now() - this.currentAdBreakStartWallClockMs;
1038
+ const remainingMs = Math.max(
1039
+ 0,
1040
+ this.expectedAdBreakDurationMs - elapsedMs
1041
+ );
1042
+ this.scheduleAdStopCountdown(remainingMs);
1043
+ }
1044
+ if (!this.ima.isAdPlaying()) {
1045
+ const scheduled = this.findCurrentOrNextBreak(
1046
+ this.video.currentTime * 1e3
1047
+ );
1048
+ const tags = this.selectVastTagsForBreak(scheduled) || (this.apiVastTagUrl ? [this.apiVastTagUrl] : void 0);
1049
+ if (tags && tags.length > 0) {
1050
+ const first = tags[0];
1051
+ const rest = tags.slice(1);
1052
+ this.adPodQueue = rest;
1053
+ this.playSingleAd(first).catch(() => {
1054
+ });
1055
+ }
1056
+ }
1057
+ return;
1058
+ }
1059
+ if (marker.type === "end") {
1060
+ this.inAdBreak = false;
1061
+ this.expectedAdBreakDurationMs = void 0;
1062
+ this.currentAdBreakStartWallClockMs = void 0;
1063
+ this.clearAdStartTimer();
1064
+ this.clearAdStopTimer();
1065
+ if (this.ima.isAdPlaying()) {
1066
+ this.ima.stop().catch(() => {
1067
+ });
1068
+ }
1069
+ return;
1070
+ }
1071
+ }
1072
+ parseCueOutDuration(value) {
1073
+ const num = parseFloat(value.trim());
1074
+ if (!Number.isNaN(num)) return num;
1075
+ const match = value.match(/(?:^|[,\s])DURATION\s*=\s*([0-9.]+)/i) || value.match(/Duration\s*=\s*([0-9.]+)/i);
1076
+ if (match && match[1] != null) {
1077
+ const dStr = match[1];
1078
+ const d = parseFloat(dStr);
1079
+ return Number.isNaN(d) ? void 0 : d;
1080
+ }
1081
+ return void 0;
1082
+ }
1083
+ parseCueOutCont(value) {
1084
+ const elapsedMatch = value.match(/Elapsed\s*=\s*([0-9.]+)/i);
1085
+ const durationMatch = value.match(/Duration\s*=\s*([0-9.]+)/i);
1086
+ const res = {};
1087
+ if (elapsedMatch && elapsedMatch[1] != null) {
1088
+ const e = parseFloat(elapsedMatch[1]);
1089
+ if (!Number.isNaN(e)) res.elapsed = e;
1090
+ }
1091
+ if (durationMatch && durationMatch[1] != null) {
1092
+ const d = parseFloat(durationMatch[1]);
1093
+ if (!Number.isNaN(d)) res.duration = d;
1094
+ }
1095
+ if ("elapsed" in res || "duration" in res) return res;
1096
+ return void 0;
1097
+ }
1098
+ parseAttributeList(value) {
1099
+ const attrs = {};
1100
+ const regex = /([A-Z0-9-]+)=(("[^"]*")|([^",]*))(?:,|$)/gi;
1101
+ let match;
1102
+ while ((match = regex.exec(value)) !== null) {
1103
+ const key = match[1] ?? "";
1104
+ let rawVal = match[3] ?? match[4] ?? "";
1105
+ if (rawVal.startsWith('"') && rawVal.endsWith('"')) {
1106
+ rawVal = rawVal.slice(1, -1);
1107
+ }
1108
+ if (key) {
1109
+ attrs[key] = rawVal;
1110
+ }
1111
+ }
1112
+ return attrs;
1113
+ }
1114
+ toNumber(val) {
1115
+ if (val == null) return void 0;
1116
+ const n = typeof val === "string" ? parseFloat(val) : Number(val);
1117
+ return Number.isNaN(n) ? void 0 : n;
1118
+ }
1119
+ isManifestBasedMarker(marker) {
1120
+ const raw = marker.raw;
1121
+ if (!raw) return false;
1122
+ if (raw.tag) {
1123
+ const tag = String(raw.tag);
1124
+ return tag.includes("EXT-X-CUE-OUT") || tag.includes("EXT-X-CUE-IN") || tag.includes("EXT-X-DATERANGE");
1125
+ }
1126
+ if (raw.id3) return false;
1127
+ if (raw.splice_command_type) return false;
1128
+ return false;
1129
+ }
1130
+ parseScte35Binary(data) {
1131
+ class BitReader {
1132
+ constructor(buf) {
1133
+ this.buf = buf;
1134
+ this.bytePos = 0;
1135
+ this.bitPos = 0;
1136
+ }
1137
+ readBits(numBits) {
1138
+ let result = 0;
1139
+ while (numBits > 0) {
1140
+ if (this.bytePos >= this.buf.length) return result;
1141
+ const remainingInByte = 8 - this.bitPos;
1142
+ const toRead = Math.min(numBits, remainingInByte);
1143
+ const currentByte = this.buf[this.bytePos];
1144
+ const shift = remainingInByte - toRead;
1145
+ const mask = (1 << toRead) - 1 & 255;
1146
+ const bits = currentByte >> shift & mask;
1147
+ result = result << toRead | bits;
1148
+ this.bitPos += toRead;
1149
+ if (this.bitPos >= 8) {
1150
+ this.bitPos = 0;
1151
+ this.bytePos += 1;
1152
+ }
1153
+ numBits -= toRead;
1154
+ }
1155
+ return result >>> 0;
1156
+ }
1157
+ skipBits(n) {
1158
+ this.readBits(n);
1159
+ }
1160
+ }
1161
+ const r = new BitReader(data);
1162
+ const tableId = r.readBits(8);
1163
+ if (tableId !== 252) return void 0;
1164
+ r.readBits(1);
1165
+ r.readBits(1);
1166
+ r.readBits(2);
1167
+ const sectionLength = r.readBits(12);
1168
+ r.readBits(8);
1169
+ r.readBits(1);
1170
+ r.readBits(6);
1171
+ const ptsAdjHigh = r.readBits(1);
1172
+ const ptsAdjLow = r.readBits(32);
1173
+ void ptsAdjHigh;
1174
+ void ptsAdjLow;
1175
+ r.readBits(8);
1176
+ r.readBits(12);
1177
+ const spliceCommandLength = r.readBits(12);
1178
+ const spliceCommandType = r.readBits(8);
1179
+ if (spliceCommandType !== 5) {
1180
+ return void 0;
1181
+ }
1182
+ r.readBits(32);
1183
+ const cancel = r.readBits(1) === 1;
1184
+ r.readBits(7);
1185
+ if (cancel) return void 0;
1186
+ const outOfNetwork = r.readBits(1) === 1;
1187
+ const programSpliceFlag = r.readBits(1) === 1;
1188
+ const durationFlag = r.readBits(1) === 1;
1189
+ const spliceImmediateFlag = r.readBits(1) === 1;
1190
+ r.readBits(4);
1191
+ if (programSpliceFlag && !spliceImmediateFlag) {
1192
+ const timeSpecifiedFlag = r.readBits(1) === 1;
1193
+ if (timeSpecifiedFlag) {
1194
+ r.readBits(6);
1195
+ r.readBits(33);
1196
+ } else {
1197
+ r.readBits(7);
1198
+ }
1199
+ } else if (!programSpliceFlag) {
1200
+ const componentCount = r.readBits(8);
1201
+ for (let i = 0; i < componentCount; i++) {
1202
+ r.readBits(8);
1203
+ if (!spliceImmediateFlag) {
1204
+ const timeSpecifiedFlag = r.readBits(1) === 1;
1205
+ if (timeSpecifiedFlag) {
1206
+ r.readBits(6);
1207
+ r.readBits(33);
1208
+ } else {
1209
+ r.readBits(7);
1210
+ }
1211
+ }
1212
+ }
1213
+ }
1214
+ let durationSeconds = void 0;
1215
+ if (durationFlag) {
1216
+ r.readBits(6);
1217
+ r.readBits(1);
1218
+ const high = r.readBits(1);
1219
+ const low = r.readBits(32);
1220
+ const durationTicks = high * 4294967296 + low;
1221
+ durationSeconds = durationTicks / 9e4;
1222
+ }
1223
+ r.readBits(16);
1224
+ r.readBits(8);
1225
+ r.readBits(8);
1226
+ if (outOfNetwork) {
1227
+ const marker = {
1228
+ type: "start",
1229
+ ...durationSeconds !== void 0 ? { durationSeconds } : {},
1230
+ raw: { splice_command_type: 5 }
1231
+ };
1232
+ return marker;
1233
+ }
1234
+ return void 0;
1235
+ }
1236
+ initializeTracking() {
1237
+ sendInitialTracking(this.config.licenseKey).catch((error) => {
1238
+ if (this.config.debugAdTiming) {
1239
+ console.warn(
1240
+ "[StormcloudVideoPlayer] Failed to send initial tracking:",
1241
+ error
1242
+ );
1243
+ }
1244
+ });
1245
+ this.heartbeatInterval = window.setInterval(() => {
1246
+ this.sendHeartbeatIfNeeded();
1247
+ }, 5e3);
1248
+ }
1249
+ sendHeartbeatIfNeeded() {
1250
+ const now = Date.now();
1251
+ if (!this.lastHeartbeatTime || now - this.lastHeartbeatTime > 3e4) {
1252
+ this.lastHeartbeatTime = now;
1253
+ sendHeartbeat(this.config.licenseKey).catch((error) => {
1254
+ if (this.config.debugAdTiming) {
1255
+ console.warn(
1256
+ "[StormcloudVideoPlayer] Failed to send heartbeat:",
1257
+ error
1258
+ );
1259
+ }
1260
+ });
1261
+ }
1262
+ }
1263
+ async fetchAdConfiguration() {
1264
+ const apiUrl = "https://adstorm.co/api-adstorm-dev/adstorm/ads/web";
1265
+ if (this.config.debugAdTiming) {
1266
+ console.log(
1267
+ "[StormcloudVideoPlayer] Fetching ad configuration from:",
1268
+ apiUrl
1269
+ );
1270
+ }
1271
+ const headers = {};
1272
+ if (this.config.licenseKey) {
1273
+ headers["Authorization"] = `Bearer ${this.config.licenseKey}`;
1274
+ }
1275
+ const response = await fetch(apiUrl, { headers });
1276
+ if (!response.ok) {
1277
+ throw new Error(`Failed to fetch ad configuration: ${response.status}`);
1278
+ }
1279
+ const data = await response.json();
1280
+ const imaPayload = data.response?.ima?.["publisherdesk.ima"]?.payload;
1281
+ if (imaPayload) {
1282
+ this.apiVastTagUrl = decodeURIComponent(imaPayload);
1283
+ if (this.config.debugAdTiming) {
1284
+ console.log(
1285
+ "[StormcloudVideoPlayer] Extracted VAST tag URL:",
1286
+ this.apiVastTagUrl
1287
+ );
1288
+ }
1289
+ }
1290
+ this.vastConfig = data.response?.options?.vast;
1291
+ if (this.config.debugAdTiming) {
1292
+ console.log("[StormcloudVideoPlayer] Ad configuration loaded:", {
1293
+ vastTagUrl: this.apiVastTagUrl,
1294
+ vastConfig: this.vastConfig
1295
+ });
1296
+ }
1297
+ }
1298
+ getCurrentAdIndex() {
1299
+ return this.currentAdIndex;
1300
+ }
1301
+ getTotalAdsInBreak() {
1302
+ return this.totalAdsInBreak;
1303
+ }
1304
+ isAdPlaying() {
1305
+ return this.inAdBreak && this.ima.isAdPlaying();
1306
+ }
1307
+ isShowingAds() {
1308
+ return this.showAds;
1309
+ }
1310
+ getStreamType() {
1311
+ const url = this.config.src.toLowerCase();
1312
+ if (url.includes(".m3u8") || url.includes("/hls/") || url.includes("application/vnd.apple.mpegurl")) {
1313
+ return "hls";
1314
+ }
1315
+ return "other";
1316
+ }
1317
+ shouldShowNativeControls() {
1318
+ const streamType = this.getStreamType();
1319
+ if (streamType === "other") {
1320
+ return !(this.config.showCustomControls ?? false);
1321
+ }
1322
+ return !!(this.config.allowNativeHls && !(this.config.showCustomControls ?? false));
1323
+ }
1324
+ async loadDefaultVastFromAdstorm(adstormApiUrl, params) {
1325
+ const usp = new URLSearchParams(params || {});
1326
+ const url = `${adstormApiUrl}?${usp.toString()}`;
1327
+ const res = await fetch(url);
1328
+ if (!res.ok) throw new Error(`Failed to fetch adstorm ads: ${res.status}`);
1329
+ const data = await res.json();
1330
+ const tag = data?.adTagUrl || data?.vastTagUrl || data?.tagUrl;
1331
+ if (typeof tag === "string" && tag.length > 0) {
1332
+ this.apiVastTagUrl = tag;
1333
+ }
1334
+ }
1335
+ async handleAdStart(_marker) {
1336
+ const scheduled = this.findCurrentOrNextBreak(
1337
+ this.video.currentTime * 1e3
1338
+ );
1339
+ const tags = this.selectVastTagsForBreak(scheduled);
1340
+ let vastTagUrl;
1341
+ let adsNumber = 1;
1342
+ if (this.apiVastTagUrl) {
1343
+ vastTagUrl = this.apiVastTagUrl;
1344
+ if (this.vastConfig) {
1345
+ const isHls = this.config.src.includes(".m3u8") || this.config.src.includes("hls");
1346
+ if (isHls && this.vastConfig.cue_tones?.number_ads) {
1347
+ adsNumber = this.vastConfig.cue_tones.number_ads;
1348
+ } else if (!isHls && this.vastConfig.timer_vod?.number_ads) {
1349
+ adsNumber = this.vastConfig.timer_vod.number_ads;
1350
+ }
1351
+ }
1352
+ this.adPodQueue = new Array(adsNumber - 1).fill(vastTagUrl);
1353
+ this.currentAdIndex = 0;
1354
+ this.totalAdsInBreak = adsNumber;
1355
+ if (this.config.debugAdTiming) {
1356
+ console.log(
1357
+ `[StormcloudVideoPlayer] Using API VAST tag with ${adsNumber} ads:`,
1358
+ vastTagUrl
1359
+ );
1360
+ }
1361
+ } else if (tags && tags.length > 0) {
1362
+ vastTagUrl = tags[0];
1363
+ const rest = tags.slice(1);
1364
+ this.adPodQueue = rest;
1365
+ this.currentAdIndex = 0;
1366
+ this.totalAdsInBreak = tags.length;
1367
+ if (this.config.debugAdTiming) {
1368
+ console.log(
1369
+ "[StormcloudVideoPlayer] Using scheduled VAST tag:",
1370
+ vastTagUrl
1371
+ );
1372
+ }
1373
+ } else {
1374
+ if (this.config.debugAdTiming) {
1375
+ console.log("[StormcloudVideoPlayer] No VAST tag available for ad");
1376
+ }
1377
+ return;
1378
+ }
1379
+ if (vastTagUrl) {
1380
+ this.showAds = true;
1381
+ this.currentAdIndex++;
1382
+ await this.playSingleAd(vastTagUrl);
1383
+ }
1384
+ if (this.expectedAdBreakDurationMs == null && scheduled?.durationMs != null) {
1385
+ this.expectedAdBreakDurationMs = scheduled.durationMs;
1386
+ this.currentAdBreakStartWallClockMs = this.currentAdBreakStartWallClockMs ?? Date.now();
1387
+ this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
1388
+ }
1389
+ }
1390
+ findCurrentOrNextBreak(nowMs) {
1391
+ const schedule = [];
1392
+ let candidate;
1393
+ for (const b of schedule) {
1394
+ const tol = this.config.driftToleranceMs ?? 1e3;
1395
+ if (b.startTimeMs <= nowMs + tol && (candidate == null || b.startTimeMs > (candidate.startTimeMs || 0))) {
1396
+ candidate = b;
1397
+ }
1398
+ }
1399
+ return candidate;
1400
+ }
1401
+ onTimeUpdate(currentTimeSec) {
1402
+ if (this.ima.isAdPlaying()) return;
1403
+ const nowMs = currentTimeSec * 1e3;
1404
+ const breakToPlay = this.findBreakForTime(nowMs);
1405
+ if (breakToPlay) {
1406
+ this.handleMidAdJoin(breakToPlay, nowMs);
1407
+ }
1408
+ }
1409
+ async handleMidAdJoin(adBreak, nowMs) {
1410
+ const durationMs = adBreak.durationMs ?? 0;
1411
+ const endMs = adBreak.startTimeMs + durationMs;
1412
+ if (durationMs > 0 && nowMs > adBreak.startTimeMs && nowMs < endMs) {
1413
+ const remainingMs = endMs - nowMs;
1414
+ const tags = this.selectVastTagsForBreak(adBreak) || (this.apiVastTagUrl ? [this.apiVastTagUrl] : void 0);
1415
+ if (tags && tags.length > 0) {
1416
+ const first = tags[0];
1417
+ const rest = tags.slice(1);
1418
+ this.adPodQueue = rest;
1419
+ await this.playSingleAd(first);
1420
+ this.inAdBreak = true;
1421
+ this.expectedAdBreakDurationMs = remainingMs;
1422
+ this.currentAdBreakStartWallClockMs = Date.now();
1423
+ this.scheduleAdStopCountdown(remainingMs);
1424
+ }
1425
+ }
1426
+ }
1427
+ scheduleAdStopCountdown(remainingMs) {
1428
+ this.clearAdStopTimer();
1429
+ const ms = Math.max(0, Math.floor(remainingMs));
1430
+ if (ms === 0) {
1431
+ this.ensureAdStoppedByTimer();
1432
+ return;
1433
+ }
1434
+ this.adStopTimerId = window.setTimeout(() => {
1435
+ this.ensureAdStoppedByTimer();
1436
+ }, ms);
1437
+ }
1438
+ clearAdStopTimer() {
1439
+ if (this.adStopTimerId != null) {
1440
+ clearTimeout(this.adStopTimerId);
1441
+ this.adStopTimerId = void 0;
1442
+ }
1443
+ }
1444
+ ensureAdStoppedByTimer() {
1445
+ if (!this.inAdBreak) return;
1446
+ this.inAdBreak = false;
1447
+ this.expectedAdBreakDurationMs = void 0;
1448
+ this.currentAdBreakStartWallClockMs = void 0;
1449
+ this.adStopTimerId = void 0;
1450
+ if (this.ima.isAdPlaying()) {
1451
+ this.ima.stop().catch(() => {
1452
+ });
1453
+ }
1454
+ }
1455
+ scheduleAdStartIn(delayMs) {
1456
+ this.clearAdStartTimer();
1457
+ const ms = Math.max(0, Math.floor(delayMs));
1458
+ if (ms === 0) {
1459
+ this.handleAdStart({ type: "start" }).catch(() => {
1460
+ });
1461
+ return;
1462
+ }
1463
+ this.adStartTimerId = window.setTimeout(() => {
1464
+ this.handleAdStart({ type: "start" }).catch(() => {
1465
+ });
1466
+ }, ms);
1467
+ }
1468
+ clearAdStartTimer() {
1469
+ if (this.adStartTimerId != null) {
1470
+ clearTimeout(this.adStartTimerId);
1471
+ this.adStartTimerId = void 0;
1472
+ }
1473
+ }
1474
+ updatePtsDrift(ptsSecondsSample) {
1475
+ const sampleMs = (this.video.currentTime - ptsSecondsSample) * 1e3;
1476
+ if (!Number.isFinite(sampleMs) || Math.abs(sampleMs) > 6e4) return;
1477
+ const alpha = 0.1;
1478
+ this.ptsDriftEmaMs = this.ptsDriftEmaMs * (1 - alpha) + sampleMs * alpha;
1479
+ }
1480
+ async playSingleAd(vastTagUrl) {
1481
+ if (this.config.debugAdTiming) {
1482
+ console.log("[StormcloudVideoPlayer] Attempting to play ad:", vastTagUrl);
1483
+ }
1484
+ this.startAdFailsafeTimer();
1485
+ try {
1486
+ await this.ima.requestAds(vastTagUrl);
1487
+ await this.ima.play();
1488
+ if (this.config.debugAdTiming) {
1489
+ console.log("[StormcloudVideoPlayer] Ad playback started successfully");
1490
+ }
1491
+ } catch (error) {
1492
+ if (this.config.debugAdTiming) {
1493
+ console.error("[StormcloudVideoPlayer] Ad playback failed:", error);
1494
+ }
1495
+ this.handleAdFailure();
1496
+ }
1497
+ }
1498
+ handleAdFailure() {
1499
+ if (this.config.debugAdTiming) {
1500
+ console.log(
1501
+ "[StormcloudVideoPlayer] Handling ad failure - resuming content"
1502
+ );
1503
+ }
1504
+ this.inAdBreak = false;
1505
+ this.expectedAdBreakDurationMs = void 0;
1506
+ this.currentAdBreakStartWallClockMs = void 0;
1507
+ this.clearAdStartTimer();
1508
+ this.clearAdStopTimer();
1509
+ this.clearAdFailsafeTimer();
1510
+ this.adPodQueue = [];
1511
+ this.showAds = false;
1512
+ this.currentAdIndex = 0;
1513
+ this.totalAdsInBreak = 0;
1514
+ if (this.video.paused) {
1515
+ this.video.play().catch(() => {
1516
+ if (this.config.debugAdTiming) {
1517
+ console.error(
1518
+ "[StormcloudVideoPlayer] Failed to resume video after ad failure"
1519
+ );
1520
+ }
1521
+ });
1522
+ }
1523
+ }
1524
+ startAdFailsafeTimer() {
1525
+ this.clearAdFailsafeTimer();
1526
+ const failsafeMs = this.config.adFailsafeTimeoutMs ?? 1e4;
1527
+ if (this.config.debugAdTiming) {
1528
+ console.log(
1529
+ `[StormcloudVideoPlayer] Starting failsafe timer (${failsafeMs}ms)`
1530
+ );
1531
+ }
1532
+ this.adFailsafeTimerId = window.setTimeout(() => {
1533
+ if (this.video.paused) {
1534
+ if (this.config.debugAdTiming) {
1535
+ console.warn(
1536
+ "[StormcloudVideoPlayer] Failsafe timer triggered - forcing video resume"
1537
+ );
1538
+ }
1539
+ this.handleAdFailure();
1540
+ }
1541
+ }, failsafeMs);
1542
+ }
1543
+ clearAdFailsafeTimer() {
1544
+ if (this.adFailsafeTimerId != null) {
1545
+ clearTimeout(this.adFailsafeTimerId);
1546
+ this.adFailsafeTimerId = void 0;
1547
+ }
1548
+ }
1549
+ selectVastTagsForBreak(b) {
1550
+ if (!b || !b.vastTagUrl) return void 0;
1551
+ if (b.vastTagUrl.includes(",")) {
1552
+ return b.vastTagUrl.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
1553
+ }
1554
+ return [b.vastTagUrl];
1555
+ }
1556
+ getRemainingAdMs() {
1557
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null)
1558
+ return 0;
1559
+ const elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
1560
+ return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
1561
+ }
1562
+ findBreakForTime(nowMs) {
1563
+ const schedule = [];
1564
+ for (const b of schedule) {
1565
+ const end = (b.startTimeMs || 0) + (b.durationMs || 0);
1566
+ if (nowMs >= (b.startTimeMs || 0) && (b.durationMs ? nowMs < end : true)) {
1567
+ return b;
1568
+ }
1569
+ }
1570
+ return void 0;
1571
+ }
1572
+ toggleMute() {
1573
+ if (this.ima.isAdPlaying()) {
1574
+ const currentPerceptualState = this.isMuted();
1575
+ const newMutedState = !currentPerceptualState;
1576
+ this.ima.updateOriginalMutedState(newMutedState);
1577
+ this.ima.setAdVolume(newMutedState ? 0 : 1);
1578
+ if (this.config.debugAdTiming) {
1579
+ console.log(
1580
+ "[StormcloudVideoPlayer] Mute toggle during ad - immediately applied:",
1581
+ newMutedState
1582
+ );
1583
+ }
1584
+ } else {
1585
+ this.video.muted = !this.video.muted;
1586
+ if (this.config.debugAdTiming) {
1587
+ console.log("[StormcloudVideoPlayer] Muted:", this.video.muted);
1588
+ }
1589
+ }
1590
+ }
1591
+ toggleFullscreen() {
1592
+ return new Promise((resolve, reject) => {
1593
+ if (!document.fullscreenElement) {
1594
+ const container = this.video.parentElement;
1595
+ if (!container) {
1596
+ reject(new Error("No parent container found for fullscreen"));
1597
+ return;
1598
+ }
1599
+ container.requestFullscreen().then(() => {
1600
+ if (this.config.debugAdTiming) {
1601
+ console.log("[StormcloudVideoPlayer] Entered fullscreen");
1602
+ }
1603
+ resolve();
1604
+ }).catch((err) => {
1605
+ if (this.config.debugAdTiming) {
1606
+ console.error("[StormcloudVideoPlayer] Fullscreen error:", err);
1607
+ }
1608
+ reject(err);
1609
+ });
1610
+ } else {
1611
+ document.exitFullscreen().then(() => {
1612
+ if (this.config.debugAdTiming) {
1613
+ console.log("[StormcloudVideoPlayer] Exited fullscreen");
1614
+ }
1615
+ resolve();
1616
+ }).catch((err) => {
1617
+ if (this.config.debugAdTiming) {
1618
+ console.error(
1619
+ "[StormcloudVideoPlayer] Exit fullscreen error:",
1620
+ err
1621
+ );
1622
+ }
1623
+ reject(err);
1624
+ });
1625
+ }
1626
+ });
1627
+ }
1628
+ isMuted() {
1629
+ if (this.ima.isAdPlaying()) {
1630
+ const adVolume = this.ima.getAdVolume();
1631
+ return adVolume === 0;
1632
+ }
1633
+ return this.video.muted;
1634
+ }
1635
+ isFullscreen() {
1636
+ return !!document.fullscreenElement;
1637
+ }
1638
+ get videoElement() {
1639
+ return this.video;
1640
+ }
1641
+ resize() {
1642
+ if (this.config.debugAdTiming) {
1643
+ console.log("[StormcloudVideoPlayer] Resizing player");
1644
+ }
1645
+ if (this.ima && this.ima.isAdPlaying()) {
1646
+ const width = this.video.clientWidth || 640;
1647
+ const height = this.video.clientHeight || 360;
1648
+ if (this.config.debugAdTiming) {
1649
+ console.log(
1650
+ `[StormcloudVideoPlayer] Resizing ads manager to ${width}x${height}`
1651
+ );
1652
+ }
1653
+ this.ima.resize(width, height);
1654
+ }
1655
+ }
1656
+ destroy() {
1657
+ this.clearAdStartTimer();
1658
+ this.clearAdStopTimer();
1659
+ this.clearAdFailsafeTimer();
1660
+ if (this.heartbeatInterval) {
1661
+ clearInterval(this.heartbeatInterval);
1662
+ this.heartbeatInterval = void 0;
1663
+ }
1664
+ this.hls?.destroy();
1665
+ this.ima?.destroy();
1666
+ }
1667
+ };
1668
+
1669
+ // src/ui/StormcloudVideoPlayer.tsx
1670
+ var import_fa = require("react-icons/fa");
1671
+ var import_jsx_runtime = require("react/jsx-runtime");
1672
+ var CRITICAL_PROPS = [
1673
+ "src",
1674
+ "allowNativeHls",
1675
+ "licenseKey",
1676
+ "lowLatencyMode",
1677
+ "driftToleranceMs"
1678
+ ];
1679
+ var StormcloudVideoPlayerComponent = import_react.default.memo(
1680
+ (props) => {
1681
+ const {
1682
+ src,
1683
+ autoplay,
1684
+ muted,
1685
+ lowLatencyMode,
1686
+ allowNativeHls,
1687
+ driftToleranceMs,
1688
+ immediateManifestAds,
1689
+ debugAdTiming,
1690
+ showCustomControls,
1691
+ onVolumeToggle,
1692
+ onFullscreenToggle,
1693
+ onControlClick,
1694
+ onReady,
1695
+ wrapperClassName,
1696
+ wrapperStyle,
1697
+ className,
1698
+ style,
1699
+ controls,
1700
+ playsInline,
1701
+ preload,
1702
+ poster,
1703
+ children,
1704
+ licenseKey,
1705
+ ...restVideoAttrs
1706
+ } = props;
1707
+ const videoRef = (0, import_react.useRef)(null);
1708
+ const playerRef = (0, import_react.useRef)(null);
1709
+ const [adStatus, setAdStatus] = import_react.default.useState({ showAds: false, currentIndex: 0, totalAds: 0 });
1710
+ const [shouldShowNativeControls, setShouldShowNativeControls] = import_react.default.useState(true);
1711
+ const [isMuted, setIsMuted] = import_react.default.useState(false);
1712
+ const [isFullscreen, setIsFullscreen] = import_react.default.useState(false);
1713
+ const [isPlaying, setIsPlaying] = import_react.default.useState(false);
1714
+ const [currentTime, setCurrentTime] = import_react.default.useState(0);
1715
+ const [duration, setDuration] = import_react.default.useState(0);
1716
+ const [volume, setVolume] = import_react.default.useState(1);
1717
+ const [playbackRate, setPlaybackRate] = import_react.default.useState(1);
1718
+ const [showVolumeSlider, setShowVolumeSlider] = import_react.default.useState(false);
1719
+ const [showSpeedMenu, setShowSpeedMenu] = import_react.default.useState(false);
1720
+ const [isLoading, setIsLoading] = import_react.default.useState(true);
1721
+ const [isBuffering, setIsBuffering] = import_react.default.useState(false);
1722
+ const [showCenterPlay, setShowCenterPlay] = import_react.default.useState(false);
1723
+ const formatTime = (seconds) => {
1724
+ if (!isFinite(seconds)) return "0:00:00";
1725
+ const hours = Math.floor(seconds / 3600);
1726
+ const minutes = Math.floor(seconds % 3600 / 60);
1727
+ const remainingSeconds = Math.floor(seconds % 60);
1728
+ return `${hours}:${minutes.toString().padStart(2, "0")}:${remainingSeconds.toString().padStart(2, "0")}`;
1729
+ };
1730
+ const handlePlayPause = () => {
1731
+ if (videoRef.current) {
1732
+ if (videoRef.current.paused) {
1733
+ videoRef.current.play();
1734
+ setShowCenterPlay(false);
1735
+ } else {
1736
+ videoRef.current.pause();
1737
+ setShowCenterPlay(true);
1738
+ }
1739
+ }
1740
+ };
1741
+ const handleCenterPlayClick = () => {
1742
+ if (videoRef.current && videoRef.current.paused) {
1743
+ videoRef.current.play();
1744
+ setShowCenterPlay(false);
1745
+ }
1746
+ };
1747
+ const handleTimelineSeek = (e) => {
1748
+ if (videoRef.current && duration > 0 && isFinite(duration)) {
1749
+ const rect = e.currentTarget.getBoundingClientRect();
1750
+ const clickX = e.clientX - rect.left;
1751
+ const progress = Math.max(0, Math.min(1, clickX / rect.width));
1752
+ const newTime = progress * duration;
1753
+ if (isFinite(newTime) && newTime >= 0 && newTime <= duration) {
1754
+ videoRef.current.currentTime = newTime;
1755
+ }
1756
+ }
1757
+ };
1758
+ const handleVolumeChange = (newVolume) => {
1759
+ if (videoRef.current && isFinite(newVolume)) {
1760
+ const clampedVolume = Math.max(0, Math.min(1, newVolume));
1761
+ videoRef.current.volume = clampedVolume;
1762
+ videoRef.current.muted = clampedVolume === 0;
1763
+ }
1764
+ };
1765
+ const handlePlaybackRateChange = (rate) => {
1766
+ if (videoRef.current && isFinite(rate) && rate > 0) {
1767
+ videoRef.current.playbackRate = rate;
1768
+ }
1769
+ setShowSpeedMenu(false);
1770
+ };
1771
+ const isHlsStream = src?.toLowerCase().includes(".m3u8") || src?.toLowerCase().includes("/hls/");
1772
+ const shouldShowEnhancedControls = showCustomControls && (isHlsStream ? allowNativeHls : true);
1773
+ const criticalPropsKey = (0, import_react.useMemo)(() => {
1774
+ return CRITICAL_PROPS.map((prop) => `${prop}:${props[prop]}`).join("|");
1775
+ }, [src, allowNativeHls, licenseKey, lowLatencyMode, driftToleranceMs]);
1776
+ (0, import_react.useEffect)(() => {
1777
+ if (typeof window === "undefined") return;
1778
+ const el = videoRef.current;
1779
+ if (!el || !src) return;
1780
+ if (playerRef.current) {
1781
+ try {
1782
+ playerRef.current.destroy();
1783
+ } catch {
1784
+ }
1785
+ playerRef.current = null;
1786
+ }
1787
+ const cfg = {
1788
+ src,
1789
+ videoElement: el
1790
+ };
1791
+ if (autoplay !== void 0) cfg.autoplay = autoplay;
1792
+ if (muted !== void 0) cfg.muted = muted;
1793
+ if (lowLatencyMode !== void 0) cfg.lowLatencyMode = lowLatencyMode;
1794
+ if (allowNativeHls !== void 0) cfg.allowNativeHls = allowNativeHls;
1795
+ if (driftToleranceMs !== void 0)
1796
+ cfg.driftToleranceMs = driftToleranceMs;
1797
+ if (immediateManifestAds !== void 0)
1798
+ cfg.immediateManifestAds = immediateManifestAds;
1799
+ if (debugAdTiming !== void 0) cfg.debugAdTiming = debugAdTiming;
1800
+ if (showCustomControls !== void 0)
1801
+ cfg.showCustomControls = showCustomControls;
1802
+ if (onVolumeToggle !== void 0) cfg.onVolumeToggle = onVolumeToggle;
1803
+ if (onFullscreenToggle !== void 0)
1804
+ cfg.onFullscreenToggle = onFullscreenToggle;
1805
+ if (onControlClick !== void 0) cfg.onControlClick = onControlClick;
1806
+ if (licenseKey !== void 0) cfg.licenseKey = licenseKey;
1807
+ const player = new StormcloudVideoPlayer(cfg);
1808
+ playerRef.current = player;
1809
+ player.load().then(() => {
1810
+ const showNative = player.shouldShowNativeControls();
1811
+ setShouldShowNativeControls(showNative);
1812
+ onReady?.(player);
1813
+ }).catch(() => {
1814
+ });
1815
+ return () => {
1816
+ try {
1817
+ player.destroy();
1818
+ } catch {
1819
+ }
1820
+ playerRef.current = null;
1821
+ };
1822
+ }, [criticalPropsKey]);
1823
+ (0, import_react.useEffect)(() => {
1824
+ if (!playerRef.current) return;
1825
+ try {
1826
+ if (autoplay !== void 0 && playerRef.current.videoElement) {
1827
+ playerRef.current.videoElement.autoplay = autoplay;
1828
+ }
1829
+ if (muted !== void 0 && playerRef.current.videoElement) {
1830
+ playerRef.current.videoElement.muted = muted;
1831
+ }
1832
+ } catch (error) {
1833
+ console.warn("Failed to update player properties:", error);
1834
+ }
1835
+ }, [autoplay, muted]);
1836
+ (0, import_react.useEffect)(() => {
1837
+ if (!playerRef.current) return;
1838
+ const checkAdStatus = () => {
1839
+ if (playerRef.current) {
1840
+ const showAds = playerRef.current.isShowingAds();
1841
+ const currentIndex = playerRef.current.getCurrentAdIndex();
1842
+ const totalAds = playerRef.current.getTotalAdsInBreak();
1843
+ setAdStatus((prev) => {
1844
+ if (prev.showAds !== showAds || prev.currentIndex !== currentIndex || prev.totalAds !== totalAds) {
1845
+ return { showAds, currentIndex, totalAds };
1846
+ }
1847
+ return prev;
1848
+ });
1849
+ }
1850
+ };
1851
+ const interval = setInterval(checkAdStatus, 100);
1852
+ return () => clearInterval(interval);
1853
+ }, []);
1854
+ (0, import_react.useEffect)(() => {
1855
+ if (typeof window === "undefined" || !playerRef.current) return;
1856
+ const handleResize = () => {
1857
+ if (playerRef.current && videoRef.current) {
1858
+ if (typeof playerRef.current.resize === "function") {
1859
+ playerRef.current.resize();
1860
+ }
1861
+ }
1862
+ };
1863
+ window.addEventListener("resize", handleResize);
1864
+ return () => window.removeEventListener("resize", handleResize);
1865
+ }, []);
1866
+ (0, import_react.useEffect)(() => {
1867
+ if (!playerRef.current || !videoRef.current) return;
1868
+ const updateStates = () => {
1869
+ if (playerRef.current && videoRef.current) {
1870
+ setIsMuted(playerRef.current.isMuted());
1871
+ setIsPlaying(!videoRef.current.paused);
1872
+ const currentTimeValue = videoRef.current.currentTime;
1873
+ setCurrentTime(isFinite(currentTimeValue) ? currentTimeValue : 0);
1874
+ const durationValue = videoRef.current.duration;
1875
+ setDuration(isFinite(durationValue) ? durationValue : 0);
1876
+ const volumeValue = videoRef.current.volume;
1877
+ setVolume(
1878
+ isFinite(volumeValue) ? Math.max(0, Math.min(1, volumeValue)) : 1
1879
+ );
1880
+ const rateValue = videoRef.current.playbackRate;
1881
+ setPlaybackRate(
1882
+ isFinite(rateValue) && rateValue > 0 ? rateValue : 1
1883
+ );
1884
+ }
1885
+ setIsFullscreen(
1886
+ document.fullscreenElement === videoRef.current?.parentElement
1887
+ );
1888
+ };
1889
+ const interval = setInterval(updateStates, 200);
1890
+ const handleFullscreenChange = () => {
1891
+ setIsFullscreen(
1892
+ document.fullscreenElement === videoRef.current?.parentElement
1893
+ );
1894
+ };
1895
+ document.addEventListener("fullscreenchange", handleFullscreenChange);
1896
+ return () => {
1897
+ clearInterval(interval);
1898
+ document.removeEventListener(
1899
+ "fullscreenchange",
1900
+ handleFullscreenChange
1901
+ );
1902
+ };
1903
+ }, []);
1904
+ (0, import_react.useEffect)(() => {
1905
+ if (!videoRef.current) return;
1906
+ const handleLoadedMetadata = () => {
1907
+ if (videoRef.current) {
1908
+ const video2 = videoRef.current;
1909
+ void video2.offsetHeight;
1910
+ }
1911
+ };
1912
+ const handleLoadStart = () => {
1913
+ setIsLoading(true);
1914
+ setIsBuffering(false);
1915
+ };
1916
+ const handleCanPlay = () => {
1917
+ setIsLoading(false);
1918
+ setIsBuffering(false);
1919
+ };
1920
+ const handleWaiting = () => {
1921
+ setIsBuffering(true);
1922
+ };
1923
+ const handlePlaying = () => {
1924
+ setIsLoading(false);
1925
+ setIsBuffering(false);
1926
+ setShowCenterPlay(false);
1927
+ };
1928
+ const handlePause = () => {
1929
+ if (playerRef.current && !playerRef.current.isShowingAds()) {
1930
+ setShowCenterPlay(true);
1931
+ } else {
1932
+ setShowCenterPlay(false);
1933
+ }
1934
+ };
1935
+ const handleEnded = () => {
1936
+ setShowCenterPlay(true);
1937
+ };
1938
+ const video = videoRef.current;
1939
+ video.addEventListener("loadstart", handleLoadStart);
1940
+ video.addEventListener("loadedmetadata", handleLoadedMetadata);
1941
+ video.addEventListener("loadeddata", handleLoadedMetadata);
1942
+ video.addEventListener("canplay", handleCanPlay);
1943
+ video.addEventListener("waiting", handleWaiting);
1944
+ video.addEventListener("playing", handlePlaying);
1945
+ video.addEventListener("pause", handlePause);
1946
+ video.addEventListener("ended", handleEnded);
1947
+ if (video.paused) {
1948
+ setShowCenterPlay(true);
1949
+ }
1950
+ return () => {
1951
+ video.removeEventListener("loadstart", handleLoadStart);
1952
+ video.removeEventListener("loadedmetadata", handleLoadedMetadata);
1953
+ video.removeEventListener("loadeddata", handleLoadedMetadata);
1954
+ video.removeEventListener("canplay", handleCanPlay);
1955
+ video.removeEventListener("waiting", handleWaiting);
1956
+ video.removeEventListener("playing", handlePlaying);
1957
+ video.removeEventListener("pause", handlePause);
1958
+ video.removeEventListener("ended", handleEnded);
1959
+ };
1960
+ }, []);
1961
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
1962
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("style", { children: `
1963
+ @keyframes spin {
1964
+ from {
1965
+ transform: rotate(0deg);
1966
+ }
1967
+ to {
1968
+ transform: rotate(360deg);
1969
+ }
1970
+ }
1971
+
1972
+ .stormcloud-video-wrapper:fullscreen {
1973
+ border-radius: 0 !important;
1974
+ box-shadow: none !important;
1975
+ width: 100vw !important;
1976
+ height: 100vh !important;
1977
+ max-width: 100vw !important;
1978
+ max-height: 100vh !important;
1979
+ position: fixed !important;
1980
+ top: 0 !important;
1981
+ left: 0 !important;
1982
+ z-index: 999999 !important;
1983
+ background: #000 !important;
1984
+ display: flex !important;
1985
+ align-items: center !important;
1986
+ justify-content: center !important;
1987
+ }
1988
+
1989
+ .stormcloud-video-wrapper:has(*:fullscreen) {
1990
+ border-radius: 0 !important;
1991
+ box-shadow: none !important;
1992
+ width: 100vw !important;
1993
+ height: 100vh !important;
1994
+ max-width: 100vw !important;
1995
+ max-height: 100vh !important;
1996
+ position: fixed !important;
1997
+ top: 0 !important;
1998
+ left: 0 !important;
1999
+ z-index: 999999 !important;
2000
+ background: #000 !important;
2001
+ display: flex !important;
2002
+ align-items: center !important;
2003
+ justify-content: center !important;
2004
+ }
2005
+
2006
+ *:fullscreen {
2007
+ width: 100vw !important;
2008
+ height: 100vh !important;
2009
+ max-width: 100vw !important;
2010
+ max-height: 100vh !important;
2011
+ position: fixed !important;
2012
+ top: 0 !important;
2013
+ left: 0 !important;
2014
+ z-index: 999999 !important;
2015
+ background: #000 !important;
2016
+ }
2017
+ ` }),
2018
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2019
+ "div",
2020
+ {
2021
+ className: `stormcloud-video-wrapper ${wrapperClassName || ""}`,
2022
+ style: {
2023
+ display: "flex",
2024
+ alignItems: "center",
2025
+ justifyContent: "center",
2026
+ position: isFullscreen ? "fixed" : "relative",
2027
+ top: isFullscreen ? 0 : void 0,
2028
+ left: isFullscreen ? 0 : void 0,
2029
+ overflow: "hidden",
2030
+ width: isFullscreen ? "100vw" : "100%",
2031
+ height: isFullscreen ? "100vh" : "auto",
2032
+ minHeight: isFullscreen ? "100vh" : "auto",
2033
+ maxWidth: isFullscreen ? "100vw" : "100%",
2034
+ maxHeight: isFullscreen ? "100vh" : "none",
2035
+ zIndex: isFullscreen ? 999999 : void 0,
2036
+ backgroundColor: isFullscreen ? "#000" : void 0,
2037
+ borderRadius: isFullscreen ? 0 : void 0,
2038
+ boxShadow: isFullscreen ? "none" : void 0,
2039
+ ...wrapperStyle
2040
+ },
2041
+ children: [
2042
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2043
+ "video",
2044
+ {
2045
+ ref: videoRef,
2046
+ className,
2047
+ style: {
2048
+ display: "block",
2049
+ width: "100%",
2050
+ height: isFullscreen ? "100%" : "auto",
2051
+ maxWidth: "100%",
2052
+ maxHeight: isFullscreen ? "100%" : "none",
2053
+ objectFit: isFullscreen ? "cover" : "contain",
2054
+ backgroundColor: "#000",
2055
+ aspectRatio: isFullscreen ? "unset" : void 0,
2056
+ ...style
2057
+ },
2058
+ controls: shouldShowNativeControls && controls && !showCustomControls,
2059
+ playsInline,
2060
+ preload,
2061
+ poster,
2062
+ ...restVideoAttrs,
2063
+ children
2064
+ }
2065
+ ),
2066
+ (isLoading || isBuffering) && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2067
+ "div",
2068
+ {
2069
+ style: {
2070
+ position: "absolute",
2071
+ top: "50%",
2072
+ left: "50%",
2073
+ transform: "translate(-50%, -50%)",
2074
+ zIndex: 20,
2075
+ display: "flex",
2076
+ alignItems: "center",
2077
+ justifyContent: "center",
2078
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.8) 0%, rgba(20, 20, 20, 0.6) 100%)",
2079
+ width: "80px",
2080
+ height: "80px",
2081
+ borderRadius: "50%",
2082
+ backdropFilter: "blur(20px)",
2083
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.6), inset 0 2px 0 rgba(255, 255, 255, 0.1)"
2084
+ },
2085
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2086
+ import_fa.FaSpinner,
2087
+ {
2088
+ size: 28,
2089
+ color: "white",
2090
+ style: {
2091
+ animation: "spin 1s linear infinite",
2092
+ filter: "drop-shadow(0 3px 6px rgba(0, 0, 0, 0.8))"
2093
+ }
2094
+ }
2095
+ )
2096
+ }
2097
+ ),
2098
+ showCenterPlay && !isLoading && !isBuffering && !adStatus.showAds && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2099
+ "div",
2100
+ {
2101
+ onClick: handleCenterPlayClick,
2102
+ style: {
2103
+ position: "absolute",
2104
+ top: "50%",
2105
+ left: "50%",
2106
+ transform: "translate(-50%, -50%)",
2107
+ zIndex: 15,
2108
+ cursor: "pointer",
2109
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(20, 20, 20, 0.8) 100%)",
2110
+ borderRadius: "50%",
2111
+ width: "100px",
2112
+ height: "100px",
2113
+ display: "flex",
2114
+ alignItems: "center",
2115
+ justifyContent: "center",
2116
+ backdropFilter: "blur(20px)",
2117
+ border: "3px solid rgba(255, 255, 255, 0.8)",
2118
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.8), inset 0 2px 0 rgba(255, 255, 255, 0.3)",
2119
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
2120
+ },
2121
+ onMouseEnter: (e) => {
2122
+ const target = e.currentTarget;
2123
+ target.style.transform = "translate(-50%, -50%) scale(1.1)";
2124
+ target.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.95) 0%, rgba(40, 40, 40, 0.9) 100%)";
2125
+ target.style.boxShadow = "0 16px 48px rgba(0, 0, 0, 0.9), inset 0 2px 0 rgba(255, 255, 255, 0.4)";
2126
+ target.style.borderColor = "rgba(255, 255, 255, 0.9)";
2127
+ },
2128
+ onMouseLeave: (e) => {
2129
+ const target = e.currentTarget;
2130
+ target.style.transform = "translate(-50%, -50%) scale(1)";
2131
+ target.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(20, 20, 20, 0.8) 100%)";
2132
+ target.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.8), inset 0 2px 0 rgba(255, 255, 255, 0.3)";
2133
+ target.style.borderColor = "rgba(255, 255, 255, 0.8)";
2134
+ },
2135
+ title: "Play",
2136
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2137
+ import_fa.FaPlay,
2138
+ {
2139
+ size: 36,
2140
+ color: "white",
2141
+ style: {
2142
+ marginLeft: "6px",
2143
+ filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))"
2144
+ }
2145
+ }
2146
+ )
2147
+ }
2148
+ ),
2149
+ shouldShowEnhancedControls ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2150
+ "div",
2151
+ {
2152
+ style: {
2153
+ position: "absolute",
2154
+ bottom: 0,
2155
+ left: 0,
2156
+ right: 0,
2157
+ background: "linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.8) 100%)",
2158
+ padding: "20px 16px 16px",
2159
+ zIndex: 10
2160
+ },
2161
+ children: [
2162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2163
+ "div",
2164
+ {
2165
+ style: {
2166
+ width: "100%",
2167
+ height: "8px",
2168
+ background: "linear-gradient(90deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.1) 100%)",
2169
+ borderRadius: "8px",
2170
+ marginBottom: "16px",
2171
+ cursor: "pointer",
2172
+ position: "relative",
2173
+ backdropFilter: "blur(5px)",
2174
+ border: "1px solid rgba(255, 255, 255, 0.1)",
2175
+ boxShadow: "inset 0 2px 4px rgba(0, 0, 0, 0.2)"
2176
+ },
2177
+ onClick: handleTimelineSeek,
2178
+ children: [
2179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2180
+ "div",
2181
+ {
2182
+ style: {
2183
+ height: "100%",
2184
+ background: "linear-gradient(90deg, rgba(139, 92, 246, 0.9) 0%, rgba(59, 130, 246, 0.8) 50%, rgba(34, 197, 94, 0.9) 100%)",
2185
+ borderRadius: "8px",
2186
+ width: `${duration > 0 ? currentTime / duration * 100 : 0}%`,
2187
+ transition: "width 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
2188
+ boxShadow: "0 2px 8px rgba(139, 92, 246, 0.4)"
2189
+ }
2190
+ }
2191
+ ),
2192
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2193
+ "div",
2194
+ {
2195
+ style: {
2196
+ position: "absolute",
2197
+ top: "-6px",
2198
+ right: `${duration > 0 ? 100 - currentTime / duration * 100 : 100}%`,
2199
+ width: "20px",
2200
+ height: "20px",
2201
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(240, 240, 240, 0.9) 100%)",
2202
+ borderRadius: "50%",
2203
+ border: "3px solid rgba(139, 92, 246, 0.8)",
2204
+ boxShadow: "0 4px 16px rgba(139, 92, 246, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.8)",
2205
+ transform: "translateX(50%)",
2206
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)"
2207
+ }
2208
+ }
2209
+ )
2210
+ ]
2211
+ }
2212
+ ),
2213
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2214
+ "div",
2215
+ {
2216
+ style: {
2217
+ display: "flex",
2218
+ alignItems: "center",
2219
+ justifyContent: "space-between",
2220
+ color: "white"
2221
+ },
2222
+ children: [
2223
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2224
+ "div",
2225
+ {
2226
+ style: {
2227
+ display: "flex",
2228
+ alignItems: "center",
2229
+ gap: "12px"
2230
+ },
2231
+ children: [
2232
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2233
+ "button",
2234
+ {
2235
+ onClick: handlePlayPause,
2236
+ style: {
2237
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.05) 100%)",
2238
+ backdropFilter: "blur(10px)",
2239
+ border: "1px solid rgba(255, 255, 255, 0.2)",
2240
+ color: "#ffffff",
2241
+ cursor: "pointer",
2242
+ padding: "12px",
2243
+ borderRadius: "12px",
2244
+ display: "flex",
2245
+ alignItems: "center",
2246
+ justifyContent: "center",
2247
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
2248
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.2)",
2249
+ minWidth: "48px",
2250
+ minHeight: "48px"
2251
+ },
2252
+ onMouseEnter: (e) => {
2253
+ const target = e.target;
2254
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0.1) 100%)";
2255
+ target.style.transform = "translateY(-2px) scale(1.05)";
2256
+ target.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.3)";
2257
+ },
2258
+ onMouseLeave: (e) => {
2259
+ const target = e.target;
2260
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.05) 100%)";
2261
+ target.style.transform = "translateY(0) scale(1)";
2262
+ target.style.boxShadow = "0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.2)";
2263
+ },
2264
+ title: isPlaying ? "Pause" : "Play",
2265
+ children: isPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2266
+ import_fa.FaPause,
2267
+ {
2268
+ size: 20,
2269
+ style: { filter: "drop-shadow(0 0 0 transparent)" }
2270
+ }
2271
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2272
+ import_fa.FaPlay,
2273
+ {
2274
+ size: 20,
2275
+ style: { filter: "drop-shadow(0 0 0 transparent)" }
2276
+ }
2277
+ )
2278
+ }
2279
+ ),
2280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2281
+ "div",
2282
+ {
2283
+ style: {
2284
+ position: "relative",
2285
+ display: "flex",
2286
+ alignItems: "center",
2287
+ padding: "8px",
2288
+ margin: "-8px"
2289
+ },
2290
+ onMouseEnter: () => setShowVolumeSlider(true),
2291
+ onMouseLeave: () => setShowVolumeSlider(false),
2292
+ children: [
2293
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2294
+ "button",
2295
+ {
2296
+ onClick: () => {
2297
+ if (onVolumeToggle) {
2298
+ onVolumeToggle();
2299
+ } else if (playerRef.current) {
2300
+ playerRef.current.toggleMute();
2301
+ }
2302
+ },
2303
+ style: {
2304
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.04) 100%)",
2305
+ backdropFilter: "blur(8px)",
2306
+ border: "1px solid rgba(255, 255, 255, 0.15)",
2307
+ color: isMuted ? "#ff6b6b" : "#ffffff",
2308
+ cursor: "pointer",
2309
+ padding: "10px",
2310
+ borderRadius: "10px",
2311
+ display: "flex",
2312
+ alignItems: "center",
2313
+ justifyContent: "center",
2314
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
2315
+ boxShadow: "0 6px 24px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1)",
2316
+ minWidth: "40px",
2317
+ minHeight: "40px"
2318
+ },
2319
+ onMouseEnter: (e) => {
2320
+ const target = e.target;
2321
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.08) 100%)";
2322
+ target.style.transform = "translateY(-1px) scale(1.03)";
2323
+ target.style.boxShadow = "0 8px 28px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2)";
2324
+ },
2325
+ onMouseLeave: (e) => {
2326
+ const target = e.target;
2327
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.04) 100%)";
2328
+ target.style.transform = "translateY(0) scale(1)";
2329
+ target.style.boxShadow = "0 6px 24px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1)";
2330
+ },
2331
+ title: isMuted ? "Unmute" : "Mute",
2332
+ children: isMuted || volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2333
+ import_fa.FaVolumeMute,
2334
+ {
2335
+ size: 16,
2336
+ style: {
2337
+ filter: "drop-shadow(0 0 0 transparent)"
2338
+ }
2339
+ }
2340
+ ) : volume < 0.5 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2341
+ import_fa.FaVolumeDown,
2342
+ {
2343
+ size: 16,
2344
+ style: {
2345
+ filter: "drop-shadow(0 0 0 transparent)"
2346
+ }
2347
+ }
2348
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2349
+ import_fa.FaVolumeUp,
2350
+ {
2351
+ size: 16,
2352
+ style: {
2353
+ filter: "drop-shadow(0 0 0 transparent)"
2354
+ }
2355
+ }
2356
+ )
2357
+ }
2358
+ ),
2359
+ showVolumeSlider && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2360
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2361
+ "div",
2362
+ {
2363
+ style: {
2364
+ position: "absolute",
2365
+ bottom: "100%",
2366
+ left: "50%",
2367
+ transform: "translateX(-50%)",
2368
+ width: "60px",
2369
+ height: "20px",
2370
+ marginBottom: "-16px",
2371
+ zIndex: 9
2372
+ },
2373
+ onMouseEnter: () => setShowVolumeSlider(true),
2374
+ onMouseLeave: () => setShowVolumeSlider(false)
2375
+ }
2376
+ ),
2377
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2378
+ "div",
2379
+ {
2380
+ style: {
2381
+ position: "absolute",
2382
+ bottom: "100%",
2383
+ left: "50%",
2384
+ transform: "translateX(-50%)",
2385
+ marginBottom: "4px",
2386
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.85) 0%, rgba(20, 20, 20, 0.9) 100%)",
2387
+ backdropFilter: "blur(15px)",
2388
+ padding: "16px 12px",
2389
+ borderRadius: "12px",
2390
+ border: "1px solid rgba(255, 255, 255, 0.1)",
2391
+ display: "flex",
2392
+ flexDirection: "column",
2393
+ alignItems: "center",
2394
+ height: "130px",
2395
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.1)",
2396
+ zIndex: 10
2397
+ },
2398
+ onMouseEnter: () => setShowVolumeSlider(true),
2399
+ onMouseLeave: () => setShowVolumeSlider(false),
2400
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2401
+ "input",
2402
+ {
2403
+ type: "range",
2404
+ min: "0",
2405
+ max: "1",
2406
+ step: "0.01",
2407
+ value: isMuted ? 0 : volume,
2408
+ onChange: (e) => handleVolumeChange(parseFloat(e.target.value)),
2409
+ style: {
2410
+ writingMode: "bt-lr",
2411
+ WebkitAppearance: "slider-vertical",
2412
+ width: "6px",
2413
+ height: "90px",
2414
+ background: "linear-gradient(180deg, rgba(255, 255, 255, 0.3) 0%, rgba(255, 255, 255, 0.1) 100%)",
2415
+ borderRadius: "3px",
2416
+ outline: "none",
2417
+ cursor: "pointer"
2418
+ }
2419
+ }
2420
+ )
2421
+ }
2422
+ )
2423
+ ] })
2424
+ ]
2425
+ }
2426
+ ),
2427
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2428
+ "div",
2429
+ {
2430
+ style: {
2431
+ fontSize: "14px",
2432
+ fontFamily: "monospace",
2433
+ color: "rgba(255, 255, 255, 0.9)"
2434
+ },
2435
+ children: [
2436
+ formatTime(currentTime),
2437
+ " / ",
2438
+ formatTime(duration)
2439
+ ]
2440
+ }
2441
+ )
2442
+ ]
2443
+ }
2444
+ ),
2445
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2446
+ "div",
2447
+ {
2448
+ style: {
2449
+ display: "flex",
2450
+ alignItems: "center",
2451
+ gap: "12px"
2452
+ },
2453
+ children: [
2454
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { position: "relative" }, children: [
2455
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2456
+ "button",
2457
+ {
2458
+ onClick: () => setShowSpeedMenu(!showSpeedMenu),
2459
+ style: {
2460
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.03) 100%)",
2461
+ backdropFilter: "blur(8px)",
2462
+ border: "1px solid rgba(255, 255, 255, 0.12)",
2463
+ color: "#ffffff",
2464
+ cursor: "pointer",
2465
+ padding: "8px 14px",
2466
+ borderRadius: "8px",
2467
+ fontSize: "13px",
2468
+ fontFamily: "monospace",
2469
+ fontWeight: "600",
2470
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
2471
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08)",
2472
+ minWidth: "50px"
2473
+ },
2474
+ onMouseEnter: (e) => {
2475
+ const target = e.target;
2476
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0.06) 100%)";
2477
+ target.style.transform = "translateY(-1px) scale(1.02)";
2478
+ target.style.boxShadow = "0 6px 20px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.15)";
2479
+ },
2480
+ onMouseLeave: (e) => {
2481
+ const target = e.target;
2482
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, rgba(255, 255, 255, 0.03) 100%)";
2483
+ target.style.transform = "translateY(0) scale(1)";
2484
+ target.style.boxShadow = "0 4px 16px rgba(0, 0, 0, 0.2), inset 0 1px 0 rgba(255, 255, 255, 0.08)";
2485
+ },
2486
+ title: "Playback Speed",
2487
+ children: [
2488
+ playbackRate,
2489
+ "x"
2490
+ ]
2491
+ }
2492
+ ),
2493
+ showSpeedMenu && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2494
+ "div",
2495
+ {
2496
+ style: {
2497
+ position: "absolute",
2498
+ bottom: "100%",
2499
+ right: 0,
2500
+ marginBottom: "12px",
2501
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(20, 20, 20, 0.95) 100%)",
2502
+ backdropFilter: "blur(20px)",
2503
+ borderRadius: "12px",
2504
+ border: "1px solid rgba(255, 255, 255, 0.1)",
2505
+ overflow: "hidden",
2506
+ minWidth: "90px",
2507
+ boxShadow: "0 16px 48px rgba(0, 0, 0, 0.5), inset 0 1px 0 rgba(255, 255, 255, 0.1)"
2508
+ },
2509
+ children: [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2].map(
2510
+ (speed) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2511
+ "button",
2512
+ {
2513
+ onClick: () => handlePlaybackRateChange(speed),
2514
+ style: {
2515
+ display: "block",
2516
+ width: "100%",
2517
+ padding: "10px 16px",
2518
+ background: playbackRate === speed ? "linear-gradient(135deg, rgba(99, 102, 241, 0.8) 0%, rgba(139, 92, 246, 0.6) 100%)" : "transparent",
2519
+ border: "none",
2520
+ color: "white",
2521
+ cursor: "pointer",
2522
+ fontSize: "13px",
2523
+ fontFamily: "monospace",
2524
+ fontWeight: "600",
2525
+ textAlign: "center",
2526
+ transition: "all 0.2s cubic-bezier(0.4, 0, 0.2, 1)",
2527
+ borderBottom: speed !== 2 ? "1px solid rgba(255, 255, 255, 0.05)" : "none"
2528
+ },
2529
+ onMouseEnter: (e) => {
2530
+ if (playbackRate !== speed) {
2531
+ e.target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.15) 0%, rgba(255, 255, 255, 0.05) 100%)";
2532
+ }
2533
+ },
2534
+ onMouseLeave: (e) => {
2535
+ if (playbackRate !== speed) {
2536
+ e.target.style.background = "transparent";
2537
+ }
2538
+ },
2539
+ children: [
2540
+ speed,
2541
+ "x"
2542
+ ]
2543
+ },
2544
+ speed
2545
+ )
2546
+ )
2547
+ }
2548
+ )
2549
+ ] }),
2550
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2551
+ "button",
2552
+ {
2553
+ onClick: () => {
2554
+ if (onFullscreenToggle) {
2555
+ onFullscreenToggle();
2556
+ } else if (playerRef.current) {
2557
+ playerRef.current.toggleFullscreen().catch((err) => {
2558
+ console.error("Fullscreen error:", err);
2559
+ });
2560
+ }
2561
+ },
2562
+ style: {
2563
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.04) 100%)",
2564
+ backdropFilter: "blur(8px)",
2565
+ border: "1px solid rgba(255, 255, 255, 0.15)",
2566
+ color: "#ffffff",
2567
+ cursor: "pointer",
2568
+ padding: "10px",
2569
+ borderRadius: "10px",
2570
+ display: "flex",
2571
+ alignItems: "center",
2572
+ justifyContent: "center",
2573
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
2574
+ boxShadow: "0 6px 24px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1)",
2575
+ minWidth: "40px",
2576
+ minHeight: "40px"
2577
+ },
2578
+ onMouseEnter: (e) => {
2579
+ const target = e.target;
2580
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.2) 0%, rgba(255, 255, 255, 0.08) 100%)";
2581
+ target.style.transform = "translateY(-1px) scale(1.03)";
2582
+ target.style.boxShadow = "0 8px 28px rgba(0, 0, 0, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2)";
2583
+ },
2584
+ onMouseLeave: (e) => {
2585
+ const target = e.target;
2586
+ target.style.background = "linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.04) 100%)";
2587
+ target.style.transform = "translateY(0) scale(1)";
2588
+ target.style.boxShadow = "0 6px 24px rgba(0, 0, 0, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.1)";
2589
+ },
2590
+ title: isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen",
2591
+ children: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2592
+ import_fa.FaCompress,
2593
+ {
2594
+ size: 16,
2595
+ style: { filter: "drop-shadow(0 0 0 transparent)" }
2596
+ }
2597
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2598
+ import_fa.FaExpand,
2599
+ {
2600
+ size: 16,
2601
+ style: { filter: "drop-shadow(0 0 0 transparent)" }
2602
+ }
2603
+ )
2604
+ }
2605
+ )
2606
+ ]
2607
+ }
2608
+ )
2609
+ ]
2610
+ }
2611
+ )
2612
+ ]
2613
+ }
2614
+ ) }) : showCustomControls && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2615
+ "div",
2616
+ {
2617
+ style: {
2618
+ position: "absolute",
2619
+ bottom: "10px",
2620
+ right: "10px",
2621
+ display: "flex",
2622
+ gap: "8px",
2623
+ zIndex: 10
2624
+ },
2625
+ children: [
2626
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
2627
+ "div",
2628
+ {
2629
+ style: {
2630
+ position: "relative",
2631
+ display: "flex",
2632
+ alignItems: "center",
2633
+ padding: "8px",
2634
+ margin: "-8px"
2635
+ },
2636
+ onMouseEnter: () => setShowVolumeSlider(true),
2637
+ onMouseLeave: () => setShowVolumeSlider(false),
2638
+ children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2640
+ "button",
2641
+ {
2642
+ onClick: () => {
2643
+ if (onVolumeToggle) {
2644
+ onVolumeToggle();
2645
+ } else if (playerRef.current) {
2646
+ playerRef.current.toggleMute();
2647
+ }
2648
+ },
2649
+ onMouseEnter: (e) => {
2650
+ const target = e.currentTarget;
2651
+ target.style.transform = "translateY(-3px) scale(1.08)";
2652
+ target.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.8), 0 0 0 2px rgba(255, 255, 255, 0.8), inset 0 2px 0 rgba(255, 255, 255, 0.3)";
2653
+ target.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(40, 40, 40, 0.85) 100%)";
2654
+ },
2655
+ onMouseLeave: (e) => {
2656
+ const target = e.currentTarget;
2657
+ target.style.transform = "translateY(0) scale(1)";
2658
+ target.style.boxShadow = "0 8px 32px rgba(0, 0, 0, 0.7), 0 0 0 2px rgba(255, 255, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2)";
2659
+ target.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.85) 0%, rgba(30, 30, 30, 0.8) 100%)";
2660
+ },
2661
+ style: {
2662
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.85) 0%, rgba(30, 30, 30, 0.8) 100%)",
2663
+ color: isMuted ? "#ff6b6b" : "#ffffff",
2664
+ border: "none",
2665
+ borderRadius: "16px",
2666
+ padding: "10px",
2667
+ cursor: "pointer",
2668
+ display: "flex",
2669
+ alignItems: "center",
2670
+ justifyContent: "center",
2671
+ backdropFilter: "blur(20px)",
2672
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.7), 0 0 0 2px rgba(255, 255, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2)",
2673
+ transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
2674
+ minWidth: "44px",
2675
+ minHeight: "44px"
2676
+ },
2677
+ title: isMuted ? "Unmute" : "Mute",
2678
+ children: isMuted || volume === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2679
+ import_fa.FaVolumeMute,
2680
+ {
2681
+ size: 16,
2682
+ style: {
2683
+ filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))",
2684
+ color: "#ffffff"
2685
+ }
2686
+ }
2687
+ ) : volume < 0.5 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2688
+ import_fa.FaVolumeDown,
2689
+ {
2690
+ size: 16,
2691
+ style: {
2692
+ filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))",
2693
+ color: "#ffffff"
2694
+ }
2695
+ }
2696
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2697
+ import_fa.FaVolumeUp,
2698
+ {
2699
+ size: 16,
2700
+ style: {
2701
+ filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))",
2702
+ color: "#ffffff"
2703
+ }
2704
+ }
2705
+ )
2706
+ }
2707
+ ),
2708
+ showVolumeSlider && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
2709
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2710
+ "div",
2711
+ {
2712
+ style: {
2713
+ position: "absolute",
2714
+ bottom: "100%",
2715
+ left: "50%",
2716
+ transform: "translateX(-50%)",
2717
+ width: "60px",
2718
+ height: "20px",
2719
+ marginBottom: "-16px",
2720
+ zIndex: 9
2721
+ },
2722
+ onMouseEnter: () => setShowVolumeSlider(true),
2723
+ onMouseLeave: () => setShowVolumeSlider(false)
2724
+ }
2725
+ ),
2726
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2727
+ "div",
2728
+ {
2729
+ style: {
2730
+ position: "absolute",
2731
+ bottom: "100%",
2732
+ left: "50%",
2733
+ transform: "translateX(-50%)",
2734
+ marginBottom: "4px",
2735
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.95) 0%, rgba(20, 20, 20, 0.9) 100%)",
2736
+ backdropFilter: "blur(20px)",
2737
+ padding: "16px 12px",
2738
+ borderRadius: "12px",
2739
+ border: "2px solid rgba(255, 255, 255, 0.6)",
2740
+ display: "flex",
2741
+ flexDirection: "column",
2742
+ alignItems: "center",
2743
+ height: "130px",
2744
+ boxShadow: "0 12px 40px rgba(0, 0, 0, 0.8), inset 0 1px 0 rgba(255, 255, 255, 0.3)",
2745
+ zIndex: 10
2746
+ },
2747
+ onMouseEnter: () => setShowVolumeSlider(true),
2748
+ onMouseLeave: () => setShowVolumeSlider(false),
2749
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2750
+ "input",
2751
+ {
2752
+ type: "range",
2753
+ min: "0",
2754
+ max: "1",
2755
+ step: "0.01",
2756
+ value: isMuted ? 0 : volume,
2757
+ onChange: (e) => handleVolumeChange(parseFloat(e.target.value)),
2758
+ style: {
2759
+ writingMode: "bt-lr",
2760
+ WebkitAppearance: "slider-vertical",
2761
+ width: "6px",
2762
+ height: "90px",
2763
+ background: "linear-gradient(180deg, rgba(255, 255, 255, 0.8) 0%, rgba(255, 255, 255, 0.4) 100%)",
2764
+ borderRadius: "3px",
2765
+ outline: "none",
2766
+ cursor: "pointer",
2767
+ border: "1px solid rgba(255, 255, 255, 0.3)"
2768
+ }
2769
+ }
2770
+ )
2771
+ }
2772
+ )
2773
+ ] })
2774
+ ]
2775
+ }
2776
+ ),
2777
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2778
+ "button",
2779
+ {
2780
+ onClick: () => {
2781
+ if (onFullscreenToggle) {
2782
+ onFullscreenToggle();
2783
+ } else if (playerRef.current) {
2784
+ playerRef.current.toggleFullscreen().catch((err) => {
2785
+ console.error("Fullscreen error:", err);
2786
+ });
2787
+ }
2788
+ },
2789
+ onMouseEnter: (e) => {
2790
+ const target = e.currentTarget;
2791
+ target.style.transform = "translateY(-3px) scale(1.08)";
2792
+ target.style.boxShadow = "0 12px 40px rgba(0, 0, 0, 0.8), 0 0 0 2px rgba(255, 255, 255, 0.8), inset 0 2px 0 rgba(255, 255, 255, 0.3)";
2793
+ target.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.9) 0%, rgba(40, 40, 40, 0.85) 100%)";
2794
+ },
2795
+ onMouseLeave: (e) => {
2796
+ const target = e.currentTarget;
2797
+ target.style.transform = "translateY(0) scale(1)";
2798
+ target.style.boxShadow = "0 8px 32px rgba(0, 0, 0, 0.7), 0 0 0 2px rgba(255, 255, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2)";
2799
+ target.style.background = "linear-gradient(135deg, rgba(0, 0, 0, 0.85) 0%, rgba(30, 30, 30, 0.8) 100%)";
2800
+ },
2801
+ style: {
2802
+ background: "linear-gradient(135deg, rgba(0, 0, 0, 0.85) 0%, rgba(30, 30, 30, 0.8) 100%)",
2803
+ color: "#ffffff",
2804
+ border: "none",
2805
+ borderRadius: "16px",
2806
+ padding: "10px",
2807
+ cursor: "pointer",
2808
+ display: "flex",
2809
+ alignItems: "center",
2810
+ justifyContent: "center",
2811
+ backdropFilter: "blur(20px)",
2812
+ boxShadow: "0 8px 32px rgba(0, 0, 0, 0.7), 0 0 0 2px rgba(255, 255, 255, 0.6), inset 0 1px 0 rgba(255, 255, 255, 0.2)",
2813
+ transition: "all 0.4s cubic-bezier(0.4, 0, 0.2, 1)",
2814
+ minWidth: "44px",
2815
+ minHeight: "44px"
2816
+ },
2817
+ title: isFullscreen ? "Exit Fullscreen" : "Enter Fullscreen",
2818
+ children: isFullscreen ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2819
+ import_fa.FaCompress,
2820
+ {
2821
+ size: 16,
2822
+ style: {
2823
+ filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))",
2824
+ color: "#ffffff"
2825
+ }
2826
+ }
2827
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2828
+ import_fa.FaExpand,
2829
+ {
2830
+ size: 16,
2831
+ style: {
2832
+ filter: "drop-shadow(0 2px 4px rgba(0, 0, 0, 0.8))",
2833
+ color: "#ffffff"
2834
+ }
2835
+ }
2836
+ )
2837
+ }
2838
+ )
2839
+ ]
2840
+ }
2841
+ ),
2842
+ onControlClick && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
2843
+ "div",
2844
+ {
2845
+ onClick: onControlClick,
2846
+ style: {
2847
+ position: "absolute",
2848
+ top: 0,
2849
+ left: 0,
2850
+ right: 0,
2851
+ bottom: 0,
2852
+ zIndex: 1,
2853
+ cursor: "pointer"
2854
+ }
2855
+ }
2856
+ )
2857
+ ]
2858
+ }
2859
+ )
2860
+ ] });
2861
+ },
2862
+ (prevProps, nextProps) => {
2863
+ for (const prop of CRITICAL_PROPS) {
2864
+ if (prevProps[prop] !== nextProps[prop]) {
2865
+ return false;
2866
+ }
2867
+ }
2868
+ const uiProps = [
2869
+ "autoplay",
2870
+ "muted",
2871
+ "controls",
2872
+ "showCustomControls",
2873
+ "className",
2874
+ "style",
2875
+ "wrapperClassName",
2876
+ "wrapperStyle",
2877
+ "playsInline",
2878
+ "preload",
2879
+ "poster",
2880
+ "children"
2881
+ ];
2882
+ for (const prop of uiProps) {
2883
+ if (prevProps[prop] !== nextProps[prop]) {
2884
+ return false;
2885
+ }
2886
+ }
2887
+ const callbackProps = [
2888
+ "onReady",
2889
+ "onVolumeToggle",
2890
+ "onFullscreenToggle",
2891
+ "onControlClick"
2892
+ ];
2893
+ for (const prop of callbackProps) {
2894
+ if (prevProps[prop] !== nextProps[prop]) {
2895
+ return false;
2896
+ }
2897
+ }
2898
+ return true;
2899
+ }
2900
+ );
2901
+ // Annotate the CommonJS export names for ESM import in node:
2902
+ 0 && (module.exports = {
2903
+ StormcloudVideoPlayerComponent
2904
+ });
2905
+ //# sourceMappingURL=StormcloudVideoPlayer.cjs.map