stormcloud-video-player 0.8.55 → 0.8.57

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +2 -2
  2. package/dist/stormcloud-vp.min.js +1 -3
  3. package/lib/index.cjs +377 -359
  4. package/lib/index.cjs.map +1 -1
  5. package/lib/index.d.cts +12 -30
  6. package/lib/index.d.ts +12 -30
  7. package/lib/index.js +357 -303
  8. package/lib/index.js.map +1 -1
  9. package/lib/player/AdBreakOrchestrator.cjs +140 -212
  10. package/lib/player/AdBreakOrchestrator.cjs.map +1 -1
  11. package/lib/player/AdBreakOrchestrator.d.cts +1 -1
  12. package/lib/player/AdConfigManager.cjs +103 -211
  13. package/lib/player/AdConfigManager.cjs.map +1 -1
  14. package/lib/player/AdConfigManager.d.cts +1 -2
  15. package/lib/player/AdTimingService.cjs.map +1 -1
  16. package/lib/player/AdTimingService.d.cts +1 -1
  17. package/lib/player/HlsEngine.cjs.map +1 -1
  18. package/lib/player/HlsEngine.d.cts +1 -1
  19. package/lib/player/PlaceholderLayer.cjs.map +1 -1
  20. package/lib/player/PlayerControls.cjs.map +1 -1
  21. package/lib/player/PlayerControls.d.cts +1 -1
  22. package/lib/player/Scte35CueManager.cjs.map +1 -1
  23. package/lib/player/Scte35CueManager.d.cts +1 -1
  24. package/lib/player/Scte35Parser.cjs.map +1 -1
  25. package/lib/player/Scte35Parser.d.cts +1 -1
  26. package/lib/player/StormcloudVideoPlayer.cjs +350 -281
  27. package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
  28. package/lib/player/StormcloudVideoPlayer.d.cts +1 -1
  29. package/lib/player/playerTypes.cjs.map +1 -1
  30. package/lib/player/playerTypes.d.cts +1 -1
  31. package/lib/players/FilePlayer.cjs.map +1 -1
  32. package/lib/players/HlsPlayer.cjs +350 -281
  33. package/lib/players/HlsPlayer.cjs.map +1 -1
  34. package/lib/players/HlsPlayer.d.cts +1 -1
  35. package/lib/players/index.cjs +350 -281
  36. package/lib/players/index.cjs.map +1 -1
  37. package/lib/sdk/hlsAdPlayer.cjs +194 -52
  38. package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
  39. package/lib/sdk/hlsAdPlayer.d.cts +1 -1
  40. package/lib/sdk/pal.cjs.map +1 -1
  41. package/lib/{types-DpbgEvSH.d.cts → types-cTqIKw_D.d.cts} +0 -1
  42. package/lib/ui/StormcloudVideoPlayer.cjs +350 -281
  43. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  44. package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
  45. package/lib/utils/browserCompat.cjs.map +1 -1
  46. package/lib/utils/ctvVastSignals.cjs.map +1 -1
  47. package/lib/utils/devUrl.cjs.map +1 -1
  48. package/lib/utils/polyfills.cjs.map +1 -1
  49. package/lib/utils/tracking.cjs +135 -212
  50. package/lib/utils/tracking.cjs.map +1 -1
  51. package/lib/utils/tracking.d.cts +1 -1
  52. package/lib/utils/trackingIngest.cjs +322 -0
  53. package/lib/utils/trackingIngest.cjs.map +1 -0
  54. package/lib/utils/trackingIngest.d.cts +14 -0
  55. package/lib/utils/vastEnvironmentSignals.cjs +0 -4
  56. package/lib/utils/vastEnvironmentSignals.cjs.map +1 -1
  57. package/lib/utils/vastEnvironmentSignals.d.cts +1 -2
  58. package/lib/utils/vastMacros.cjs.map +1 -1
  59. package/lib/utils/vastTrackingMacros.cjs +185 -0
  60. package/lib/utils/vastTrackingMacros.cjs.map +1 -0
  61. package/lib/utils/vastTrackingMacros.d.cts +19 -0
  62. package/package.json +1 -3
  63. package/lib/utils/mqttClient.cjs +0 -305
  64. package/lib/utils/mqttClient.cjs.map +0 -1
  65. package/lib/utils/mqttClient.d.cts +0 -13
  66. package/lib/utils/mqttConfig.cjs +0 -139
  67. package/lib/utils/mqttConfig.cjs.map +0 -1
  68. package/lib/utils/mqttConfig.d.cts +0 -20
  69. package/src/certs/emqxsl-ca.crt +0 -22
@@ -520,15 +520,147 @@ function logBrowserInfo() {
520
520
  userAgent: navigator.userAgent
521
521
  }));
522
522
  }
523
+ // src/utils/vastTrackingMacros.ts
524
+ var VAST_MACRO_PATTERN = /\[([A-Z][A-Z0-9_]*)\]/g;
525
+ var CACHEBUSTING_ALIASES = /\[CACHEBUSTER\]|\[cachebuster\]|\[timestamp\]|\[random\]|\[INSERT_RANDOM_NUMBER\]|%%CACHEBUSTER%%|%%CACHEBUST%%|\$\{CACHEBUSTER\}|\$\{CACHEBUSTING\}|\{cachebuster\}|\{random\}/g;
526
+ var KNOWN_VAST_MACROS = /* @__PURE__ */ new Set([
527
+ "ADCATEGORIES",
528
+ "ADCOUNT",
529
+ "ADPLAYHEAD",
530
+ "ADSERVINGID",
531
+ "ADTYPE",
532
+ "APIFRAMEWORKS",
533
+ "APPBUNDLE",
534
+ "ASSETURI",
535
+ "BLOCKEDADCATEGORIES",
536
+ "BREAKMAXADLENGTH",
537
+ "BREAKMAXADS",
538
+ "BREAKMAXDURATION",
539
+ "BREAKMINADLENGTH",
540
+ "BREAKPOSITION",
541
+ "CACHEBUSTING",
542
+ "CLICKPOS",
543
+ "CLICKTYPE",
544
+ "CLIENTUA",
545
+ "CONTENTID",
546
+ "CONTENTPLAYHEAD",
547
+ "CONTENTURI",
548
+ "DEVICEIP",
549
+ "DEVICEUA",
550
+ "DOMAIN",
551
+ "ERRORCODE",
552
+ "EXTENSIONS",
553
+ "GDPRCONSENT",
554
+ "IFA",
555
+ "IFATYPE",
556
+ "INVENTORYSTATE",
557
+ "LATLONG",
558
+ "LIMITADTRACKING",
559
+ "MEDIAMIME",
560
+ "MEDIAPLAYHEAD",
561
+ "OMIDPARTNER",
562
+ "PAGEURL",
563
+ "PLACEMENTTYPE",
564
+ "PLAYERCAPABILITIES",
565
+ "PLAYERSIZE",
566
+ "PLAYERSTATE",
567
+ "PODSEQUENCE",
568
+ "REGULATIONS",
569
+ "SERVERSIDE",
570
+ "SERVERUA",
571
+ "TIMESTAMP",
572
+ "TRANSACTIONID",
573
+ "UNIVERSALADID",
574
+ "VASTVERSIONS",
575
+ "VERIFICATIONVENDORS"
576
+ ]);
577
+ function generateCachebuster() {
578
+ return String(Math.floor(Math.random() * 9e7) + 1e7);
579
+ }
580
+ function formatPlayhead(seconds) {
581
+ if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
582
+ return void 0;
583
+ }
584
+ var total = Math.floor(seconds);
585
+ var hh = String(Math.floor(total / 3600)).padStart(2, "0");
586
+ var mm = String(Math.floor(total % 3600 / 60)).padStart(2, "0");
587
+ var ss = String(total % 60).padStart(2, "0");
588
+ var ms = String(Math.floor((seconds - total) * 1e3)).padStart(3, "0");
589
+ return "".concat(hh, ":").concat(mm, ":").concat(ss, ".").concat(ms);
590
+ }
591
+ function resolveMacroValues(ctx) {
592
+ var values = /* @__PURE__ */ new Map();
593
+ var set = function set(key, value) {
594
+ if (value != null && value !== "") values.set(key, value);
595
+ };
596
+ var adPlayhead = formatPlayhead(ctx.adPlayheadSeconds);
597
+ set("ADPLAYHEAD", adPlayhead);
598
+ set("MEDIAPLAYHEAD", adPlayhead);
599
+ set("CONTENTPLAYHEAD", formatPlayhead(ctx.contentPlayheadSeconds));
600
+ set("ASSETURI", ctx.assetUri);
601
+ set("MEDIAMIME", ctx.mediaMime);
602
+ set("TIMESTAMP", (/* @__PURE__ */ new Date()).toISOString());
603
+ set("SERVERSIDE", "0");
604
+ if (ctx.errorCode != null && ctx.errorCode !== "") {
605
+ set("ERRORCODE", String(ctx.errorCode));
606
+ }
607
+ if (ctx.playerWidth != null && ctx.playerHeight != null) {
608
+ set("PLAYERSIZE", "".concat(Math.round(ctx.playerWidth), ",").concat(Math.round(ctx.playerHeight)));
609
+ }
610
+ set("IFA", ctx.deviceId);
611
+ set("IFATYPE", ctx.deviceIdType);
612
+ if (ctx.limitAdTracking != null) {
613
+ set("LIMITADTRACKING", ctx.limitAdTracking ? "1" : "0");
614
+ }
615
+ set("PAGEURL", ctx.pageUrl);
616
+ if (ctx.pageUrl) {
617
+ try {
618
+ set("DOMAIN", new URL(ctx.pageUrl).hostname);
619
+ } catch (unused) {}
620
+ }
621
+ set("CLIENTUA", ctx.clientUa);
622
+ set("DEVICEUA", ctx.clientUa);
623
+ if (ctx.podSequence != null) {
624
+ set("PODSEQUENCE", String(ctx.podSequence));
625
+ }
626
+ return values;
627
+ }
628
+ function substituteVastTrackingMacros(url) {
629
+ var ctx = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
630
+ if (!url) return url;
631
+ var values = resolveMacroValues(ctx);
632
+ return url.replace(CACHEBUSTING_ALIASES, function() {
633
+ return generateCachebuster();
634
+ }).replace(VAST_MACRO_PATTERN, function(match, name) {
635
+ if (name === "CACHEBUSTING") return generateCachebuster();
636
+ var value = values.get(name);
637
+ if (value != null) return encodeURIComponent(value);
638
+ return KNOWN_VAST_MACROS.has(name) ? "" : match;
639
+ });
640
+ }
523
641
  // src/sdk/hlsAdPlayer.ts
524
642
  var MAX_VAST_WRAPPER_DEPTH = 5;
643
+ var BEACON_TIMEOUT_MS = 3e4;
644
+ var inFlightBeacons = /* @__PURE__ */ new Set();
645
+ var VAST_ERROR_MEDIA_DISPLAY = 405;
646
+ function toVastMediaErrorCode(error) {
647
+ switch(error === null || error === void 0 ? void 0 : error.code){
648
+ case 1:
649
+ return 400;
650
+ case 2:
651
+ return 401;
652
+ case 4:
653
+ return 403;
654
+ default:
655
+ return VAST_ERROR_MEDIA_DISPLAY;
656
+ }
657
+ }
525
658
  function createHlsAdPlayer(contentVideo, options) {
526
659
  var _contentVideo_AD_VIDEO_PROP, _contentVideo_AD_CONTAINER_PROP;
527
660
  var adPlaying = false;
528
661
  var originalMutedState = false;
529
662
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
530
663
  var listeners = /* @__PURE__ */ new Map();
531
- var licenseKey = options === null || options === void 0 ? void 0 : options.licenseKey;
532
664
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
533
665
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
534
666
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
@@ -539,10 +671,9 @@ function createHlsAdPlayer(contentVideo, options) {
539
671
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
540
672
  var adEventHandlers = [];
541
673
  var currentAd;
542
- var currentAdMediaUrl;
674
+ var currentAdMediaFile;
543
675
  var podAds = [];
544
676
  var podIndex = 0;
545
- var sessionId;
546
677
  var preloadedAds = /* @__PURE__ */ new Map();
547
678
  var preloadingAds = /* @__PURE__ */ new Map();
548
679
  var destroyed = false;
@@ -592,21 +723,47 @@ function createHlsAdPlayer(contentVideo, options) {
592
723
  }
593
724
  }
594
725
  }
595
- function generateSessionId() {
596
- return "session-".concat(Date.now(), "-").concat(Math.random().toString(36).substr(2, 9));
726
+ function buildTrackingMacroContext(errorCode) {
727
+ var context = {
728
+ adPlayheadSeconds: adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.currentTime,
729
+ contentPlayheadSeconds: contentVideo.currentTime,
730
+ assetUri: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.url,
731
+ mediaMime: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.type,
732
+ playerWidth: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientWidth) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientWidth),
733
+ playerHeight: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientHeight) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientHeight)
734
+ };
735
+ if (errorCode != null) {
736
+ context.errorCode = errorCode;
737
+ }
738
+ if (typeof navigator !== "undefined" && navigator.userAgent) {
739
+ context.clientUa = navigator.userAgent;
740
+ }
741
+ if (typeof location !== "undefined" && location.href) {
742
+ context.pageUrl = location.href;
743
+ }
744
+ if (podAds.length > 0) {
745
+ context.podSequence = podIndex + 1;
746
+ }
747
+ return context;
597
748
  }
598
- function fireTrackingPixels(urls) {
749
+ function fireTrackingPixels(urls, options2) {
599
750
  if (!urls || urls.length === 0) return;
751
+ var macroContext = buildTrackingMacroContext(options2 === null || options2 === void 0 ? void 0 : options2.errorCode);
600
752
  urls.forEach(function(url) {
601
753
  try {
602
- var trackingUrl = url;
603
- if (sessionId) {
604
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "session_id=").concat(sessionId);
605
- }
606
- if (licenseKey) {
607
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "license_key=").concat(licenseKey);
608
- }
754
+ var trackingUrl = substituteVastTrackingMacros(url, macroContext);
609
755
  var img = new Image(1, 1);
756
+ inFlightBeacons.add(img);
757
+ var timeoutId;
758
+ var release = function release() {
759
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
760
+ img.onload = null;
761
+ img.onerror = null;
762
+ inFlightBeacons.delete(img);
763
+ };
764
+ img.onload = release;
765
+ img.onerror = release;
766
+ timeoutId = setTimeout(release, BEACON_TIMEOUT_MS);
610
767
  img.src = trackingUrl;
611
768
  console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
612
769
  } catch (error) {
@@ -1245,7 +1402,9 @@ function createHlsAdPlayer(contentVideo, options) {
1245
1402
  if (!adPlaying) return;
1246
1403
  console.error("[HlsAdPlayer] Ad video error:", e);
1247
1404
  if (currentAd) {
1248
- fireTrackingPixels(currentAd.trackingUrls.error);
1405
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1406
+ errorCode: toVastMediaErrorCode(adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.error)
1407
+ });
1249
1408
  }
1250
1409
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1251
1410
  return;
@@ -1357,9 +1516,9 @@ function createHlsAdPlayer(contentVideo, options) {
1357
1516
  delete contentVideo.dataset.stormcloudAdPlaying;
1358
1517
  }
1359
1518
  }
1360
- function resetQuartileTracking() {
1519
+ function resetQuartileTracking(preserveImpression) {
1361
1520
  trackingFired = {
1362
- impression: false,
1521
+ impression: preserveImpression ? trackingFired.impression : false,
1363
1522
  start: false,
1364
1523
  firstQuartile: false,
1365
1524
  midpoint: false,
@@ -1367,27 +1526,6 @@ function createHlsAdPlayer(contentVideo, options) {
1367
1526
  complete: false
1368
1527
  };
1369
1528
  }
1370
- function markCreativeRendered(reason) {
1371
- if (!currentAd || trackingFired.impression) return;
1372
- trackingFired.impression = true;
1373
- fireTrackingPixels(currentAd.trackingUrls.impression);
1374
- console.log("[HlsAdPlayer] Impression (pod ".concat(podIndex + 1, "/").concat(podAds.length || 1, ", reason: ").concat(reason, "): ").concat(currentAd.title));
1375
- emit("ad_impression", {
1376
- adId: currentAd.id,
1377
- title: currentAd.title,
1378
- durationSeconds: currentAd.duration,
1379
- mediaUrl: currentAdMediaUrl,
1380
- podIndex: podIndex,
1381
- podSize: podAds.length || 1,
1382
- reason: reason
1383
- });
1384
- }
1385
- function canDetectBlackFrames() {
1386
- if (!adVideoElement) return false;
1387
- if (requiresMediaPipelineResetAfterAds()) return false;
1388
- if (isRvfcSupported(adVideoElement)) return true;
1389
- return getDecodedVideoFrameCount(adVideoElement) !== void 0;
1390
- }
1391
1529
  function isRvfcSupported(video) {
1392
1530
  return typeof video.requestVideoFrameCallback === "function";
1393
1531
  }
@@ -1450,7 +1588,7 @@ function createHlsAdPlayer(contentVideo, options) {
1450
1588
  if (!mediaFile) {
1451
1589
  throw new Error("No media file available for ad");
1452
1590
  }
1453
- currentAdMediaUrl = mediaFile.url;
1591
+ currentAdMediaFile = mediaFile;
1454
1592
  var isHlsAd = isHlsMediaFile(mediaFile);
1455
1593
  console.log("[HlsAdPlayer] Loading ".concat(isHlsAd ? "HLS" : "progressive", " ad (pod ").concat(podIndex + 1, "/").concat(podAds.length || 1, ") from: ").concat(mediaFile.url));
1456
1594
  if (isHlsAd && import_hls.default.isSupported()) {
@@ -1494,9 +1632,11 @@ function createHlsAdPlayer(contentVideo, options) {
1494
1632
  }
1495
1633
  podIndex++;
1496
1634
  currentAd = podAds[podIndex];
1497
- currentAdMediaUrl = void 0;
1498
- resetQuartileTracking();
1635
+ currentAdMediaFile = void 0;
1636
+ resetQuartileTracking(false);
1499
1637
  if (currentAd) {
1638
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1639
+ trackingFired.impression = true;
1500
1640
  console.log("[HlsAdPlayer] Advancing to next pod ad (".concat(podIndex + 1, "/").concat(podAds.length, "): ").concat(currentAd.title, ", duration: ").concat(currentAd.duration, "s"));
1501
1641
  }
1502
1642
  clearStallWatchdog();
@@ -1535,7 +1675,9 @@ function createHlsAdPlayer(contentVideo, options) {
1535
1675
  console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1536
1676
  clearStallWatchdog();
1537
1677
  if (currentAd) {
1538
- fireTrackingPixels(currentAd.trackingUrls.error);
1678
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1679
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1680
+ });
1539
1681
  }
1540
1682
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1541
1683
  return;
@@ -1591,7 +1733,6 @@ function createHlsAdPlayer(contentVideo, options) {
1591
1733
  if (!adPlaying) return;
1592
1734
  sawDecodedVideoFrame = true;
1593
1735
  markRvfcReliable();
1594
- markCreativeRendered("first_presented_frame");
1595
1736
  };
1596
1737
  try {
1597
1738
  videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
@@ -1604,7 +1745,9 @@ function createHlsAdPlayer(contentVideo, options) {
1604
1745
  console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1605
1746
  clearStallWatchdog();
1606
1747
  if (currentAd) {
1607
- fireTrackingPixels(currentAd.trackingUrls.error);
1748
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1749
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1750
+ });
1608
1751
  }
1609
1752
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1610
1753
  return;
@@ -1626,7 +1769,6 @@ function createHlsAdPlayer(contentVideo, options) {
1626
1769
  if (decodedFrames !== void 0 && decodedFrames > 0) {
1627
1770
  sawDecodedVideoFrame = true;
1628
1771
  markFrameCounterReliable();
1629
- markCreativeRendered("decoded_frames");
1630
1772
  } else if (adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1631
1773
  var timeAdvancing = adVideoElement.currentTime > lastAdProgressPosition + 0.05;
1632
1774
  if (isRvfcSupported(adVideoElement) && timeAdvancing && isRvfcReliable()) {
@@ -1639,9 +1781,6 @@ function createHlsAdPlayer(contentVideo, options) {
1639
1781
  }
1640
1782
  }
1641
1783
  }
1642
- if (!trackingFired.impression && !adVideoElement.paused && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && (sawDecodedVideoFrame || !canDetectBlackFrames())) {
1643
- markCreativeRendered(sawDecodedVideoFrame ? "decoded_frames" : "sustained_playback");
1644
- }
1645
1784
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1646
1785
  noteAdProgress();
1647
1786
  return;
@@ -1742,7 +1881,6 @@ function createHlsAdPlayer(contentVideo, options) {
1742
1881
  ,
1743
1882
  6
1744
1883
  ]);
1745
- sessionId = generateSessionId();
1746
1884
  ads = [];
1747
1885
  if (!preloadedAds.has(vastTagUrl)) return [
1748
1886
  3,
@@ -1780,9 +1918,12 @@ function createHlsAdPlayer(contentVideo, options) {
1780
1918
  podAds = ads;
1781
1919
  podIndex = 0;
1782
1920
  currentAd = ads[0];
1783
- currentAdMediaUrl = void 0;
1784
- resetQuartileTracking();
1921
+ currentAdMediaFile = void 0;
1785
1922
  console.log("[HlsAdPlayer] Pod parsed: ".concat(ads.length, ' ad(s), first="').concat(currentAd === null || currentAd === void 0 ? void 0 : currentAd.title, '", duration=').concat(currentAd === null || currentAd === void 0 ? void 0 : currentAd.duration, "s"));
1923
+ if (currentAd) {
1924
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1925
+ trackingFired.impression = true;
1926
+ }
1786
1927
  return [
1787
1928
  2,
1788
1929
  Promise.resolve()
@@ -1822,7 +1963,7 @@ function createHlsAdPlayer(contentVideo, options) {
1822
1963
  contentVideo[AD_VIDEO_PROP] = adVideoElement;
1823
1964
  }
1824
1965
  setupAdEventListeners();
1825
- resetQuartileTracking();
1966
+ resetQuartileTracking(true);
1826
1967
  contentVolume = contentVideo.volume;
1827
1968
  originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1828
1969
  if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
@@ -1912,7 +2053,7 @@ function createHlsAdPlayer(contentVideo, options) {
1912
2053
  }
1913
2054
  }
1914
2055
  currentAd = void 0;
1915
- currentAdMediaUrl = void 0;
2056
+ currentAdMediaFile = void 0;
1916
2057
  podAds = [];
1917
2058
  podIndex = 0;
1918
2059
  return [
@@ -1953,6 +2094,7 @@ function createHlsAdPlayer(contentVideo, options) {
1953
2094
  teardownAdEventListeners();
1954
2095
  releaseAdDecoder();
1955
2096
  currentAd = void 0;
2097
+ currentAdMediaFile = void 0;
1956
2098
  podAds = [];
1957
2099
  podIndex = 0;
1958
2100
  listeners.clear();
@@ -2276,163 +2418,83 @@ function createPalNonceManager() {
2276
2418
  }
2277
2419
  };
2278
2420
  }
2279
- // src/utils/mqttConfig.ts
2280
- var DEFAULT_MQTT_CONFIG = {
2421
+ // src/utils/trackingIngest.ts
2422
+ var DEFAULT_TRACKING_INGEST_CONFIG = {
2281
2423
  enabled: true,
2282
- brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2283
- brokerPort: 8883,
2284
- wsPort: 8084,
2285
- username: "for-sonifi",
2286
- password: "sonifi-mqtt",
2287
- topicPrefix: "adstorm/players",
2288
- qos: 1
2424
+ baseUrl: "https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking"
2289
2425
  };
2290
- var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2291
- function applyMQTTConfig(overrides) {
2292
- Object.assign(mqttConfig, overrides);
2293
- }
2294
- function isMQTTEnabled() {
2295
- return mqttConfig.enabled;
2296
- }
2297
- function buildMQTTBrokerUrl() {
2298
- if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2299
- return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2300
- }
2301
- function buildPlayerTopic(licenseKey, channel) {
2302
- return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2303
- }
2304
- // src/utils/mqttClient.ts
2305
- var import_mqtt = __toESM(require("mqtt"), 1);
2306
- var LOG = "[StormcloudVideoPlayer][MQTT]";
2307
- var KEEPALIVE_SECONDS = 30;
2308
- var CONNECT_TIMEOUT_MS = 1e4;
2309
- var RECONNECT_BASE_MS = 5e3;
2310
- var RECONNECT_MAX_MS = 6e4;
2311
- var MAX_RECONNECT_ATTEMPTS = 8;
2312
- var COOLDOWN_MS = 10 * 6e4;
2313
- var client = null;
2314
- var status = "disconnected";
2315
- var reconnectAttempts = 0;
2316
- var reconnectTimer = null;
2317
- var stopped = false;
2318
- function clearReconnectTimer() {
2319
- if (reconnectTimer) {
2320
- clearTimeout(reconnectTimer);
2321
- reconnectTimer = null;
2322
- }
2323
- }
2324
- function teardownClient() {
2325
- if (client) {
2326
- try {
2327
- client.removeAllListeners();
2328
- client.end(true);
2329
- } catch (unused) {}
2330
- client = null;
2331
- }
2332
- }
2333
- function scheduleReconnect() {
2334
- if (stopped || !isMQTTEnabled() || reconnectTimer) return;
2335
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
2336
- console.warn("".concat(LOG, " giving up after ").concat(reconnectAttempts, " attempts; cooling down for ").concat(COOLDOWN_MS / 6e4, "m"));
2337
- teardownClient();
2338
- status = "disconnected";
2339
- reconnectTimer = setTimeout(function() {
2340
- reconnectTimer = null;
2341
- reconnectAttempts = 0;
2342
- openConnection();
2343
- }, COOLDOWN_MS);
2344
- return;
2345
- }
2346
- var delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts));
2347
- var jitter = Math.floor(Math.random() * 1e3);
2348
- reconnectAttempts += 1;
2349
- reconnectTimer = setTimeout(function() {
2350
- reconnectTimer = null;
2351
- openConnection();
2352
- }, delay + jitter);
2353
- }
2354
- function openConnection() {
2355
- if (stopped || !isMQTTEnabled()) return;
2356
- teardownClient();
2357
- var url = buildMQTTBrokerUrl();
2358
- status = "connecting";
2359
- var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2360
- try {
2361
- client = import_mqtt.default.connect(url, {
2362
- clientId: clientId,
2363
- username: mqttConfig.username,
2364
- password: mqttConfig.password,
2365
- keepalive: KEEPALIVE_SECONDS,
2366
- clean: true,
2367
- // Disable MQTT.js internal reconnect; we manage reconnection ourselves.
2368
- reconnectPeriod: 0,
2369
- connectTimeout: CONNECT_TIMEOUT_MS,
2370
- queueQoSZero: false,
2371
- reschedulePings: true,
2372
- // Root-cause fix for the Tizen 4.0 "Keepalive timeout" loop: MQTT.js's
2373
- // default timerVariant "auto" runs the keepalive interval inside a
2374
- // Blob-URL Web Worker (via worker-timers). On Samsung Tizen 4.0 (old
2375
- // WebKit) served from a file:// origin that worker can't tick reliably,
2376
- // so the keepalive counter trips onKeepaliveTimeout almost immediately
2377
- // after connect. Force the native setInterval timer instead; the main
2378
- // thread is free because video is decoded by the native AVPlay pipeline.
2379
- timerVariant: "native"
2380
- });
2381
- } catch (err) {
2382
- status = "error";
2383
- console.warn("".concat(LOG, " connect() threw:"), err);
2384
- scheduleReconnect();
2385
- return;
2386
- }
2387
- client.on("connect", function() {
2388
- status = "connected";
2389
- reconnectAttempts = 0;
2390
- clearReconnectTimer();
2391
- console.info("".concat(LOG, " connected to ").concat(url));
2392
- });
2393
- client.on("offline", function() {
2394
- if (status === "connected") status = "disconnected";
2395
- console.warn("".concat(LOG, " offline"));
2396
- scheduleReconnect();
2397
- });
2398
- client.on("error", function(err) {
2399
- status = "error";
2400
- console.warn("".concat(LOG, " error:"), err.message);
2401
- scheduleReconnect();
2402
- });
2403
- client.on("close", function() {
2404
- if (status === "connected") status = "disconnected";
2405
- scheduleReconnect();
2406
- });
2426
+ var trackingIngestConfig = _object_spread({}, DEFAULT_TRACKING_INGEST_CONFIG);
2427
+ function isTrackingIngestEnabled() {
2428
+ return trackingIngestConfig.enabled === true;
2407
2429
  }
2408
- function initMQTTClient() {
2409
- if (client || !isMQTTEnabled()) return;
2410
- stopped = false;
2411
- reconnectAttempts = 0;
2412
- openConnection();
2430
+ function normalizeIngestBaseUrl(baseUrl) {
2431
+ return baseUrl.replace(/\/+$/, "");
2413
2432
  }
2414
- function ensureMQTTClient() {
2415
- if (isMQTTEnabled() && !client && !reconnectTimer) {
2416
- initMQTTClient();
2433
+ function buildTrackingIngestUrl(channel) {
2434
+ var base = normalizeIngestBaseUrl(trackingIngestConfig.baseUrl);
2435
+ if (channel === "impressions") {
2436
+ return "".concat(base, "/impressions/ingest");
2417
2437
  }
2438
+ return "".concat(base, "/metrics/ingest");
2418
2439
  }
2419
- function publishMQTT(topic, payload) {
2420
- if (!isMQTTEnabled()) {
2421
- return false;
2422
- }
2423
- ensureMQTTClient();
2424
- if (!client || !client.connected) {
2425
- return false;
2426
- }
2427
- try {
2428
- client.publish(topic, JSON.stringify(payload), {
2429
- qos: mqttConfig.qos
2440
+ function ingestPlayerTracking(licenseKey, channel, body) {
2441
+ return _async_to_generator(function() {
2442
+ var response, error;
2443
+ return _ts_generator(this, function(_state) {
2444
+ switch(_state.label){
2445
+ case 0:
2446
+ if (!isTrackingIngestEnabled() || !licenseKey) {
2447
+ return [
2448
+ 2,
2449
+ false
2450
+ ];
2451
+ }
2452
+ _state.label = 1;
2453
+ case 1:
2454
+ _state.trys.push([
2455
+ 1,
2456
+ 3,
2457
+ ,
2458
+ 4
2459
+ ]);
2460
+ return [
2461
+ 4,
2462
+ fetch(buildTrackingIngestUrl(channel), {
2463
+ method: "POST",
2464
+ headers: {
2465
+ "Content-Type": "application/json",
2466
+ Authorization: "License-Key ".concat(licenseKey)
2467
+ },
2468
+ body: JSON.stringify(body)
2469
+ })
2470
+ ];
2471
+ case 2:
2472
+ response = _state.sent();
2473
+ if (!response.ok) {
2474
+ console.warn("[StormcloudVideoPlayer] Tracking ingest failed (".concat(response.status, ") for ").concat(channel));
2475
+ return [
2476
+ 2,
2477
+ false
2478
+ ];
2479
+ }
2480
+ return [
2481
+ 2,
2482
+ true
2483
+ ];
2484
+ case 3:
2485
+ error = _state.sent();
2486
+ console.warn("[StormcloudVideoPlayer] Tracking ingest error for ".concat(channel, ":"), error);
2487
+ return [
2488
+ 2,
2489
+ false
2490
+ ];
2491
+ case 4:
2492
+ return [
2493
+ 2
2494
+ ];
2495
+ }
2430
2496
  });
2431
- return true;
2432
- } catch (err) {
2433
- console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2434
- return false;
2435
- }
2497
+ })();
2436
2498
  }
2437
2499
  // src/utils/vastEnvironmentSignals.ts
2438
2500
  var AIRY_ANDROID_APP_ID = "com.freeairytv.android";
@@ -3203,7 +3265,7 @@ function getBrowserID(clientInfo) {
3203
3265
  })();
3204
3266
  }
3205
3267
  function canPublish(licenseKey) {
3206
- return Boolean(isMQTTEnabled() && licenseKey);
3268
+ return Boolean(isTrackingIngestEnabled() && licenseKey);
3207
3269
  }
3208
3270
  function buildPlayerMetricEvent() {
3209
3271
  return _async_to_generator(function() {
@@ -3245,21 +3307,6 @@ function buildPlayerMetricEvent() {
3245
3307
  });
3246
3308
  }).apply(this, arguments);
3247
3309
  }
3248
- function publishTracking(licenseKey, channel, body) {
3249
- var debug = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
3250
- ensureMQTTClient();
3251
- var topic = buildPlayerTopic(licenseKey, channel);
3252
- var published = publishMQTT(topic, body);
3253
- if (debug) {
3254
- console.log("[StormcloudVideoPlayer] publishTracking:", JSON.stringify({
3255
- licenseKey: licenseKey,
3256
- channel: channel,
3257
- topic: topic,
3258
- body: body,
3259
- published: published
3260
- }, null, 2));
3261
- }
3262
- }
3263
3310
  function sendInitialTracking(_0) {
3264
3311
  return _async_to_generator(function(licenseKey) {
3265
3312
  var context, metricEvent, error;
@@ -3275,9 +3322,9 @@ function sendInitialTracking(_0) {
3275
3322
  case 1:
3276
3323
  _state.trys.push([
3277
3324
  1,
3278
- 3,
3325
+ 4,
3279
3326
  ,
3280
- 4
3327
+ 5
3281
3328
  ]);
3282
3329
  return [
3283
3330
  4,
@@ -3288,23 +3335,28 @@ function sendInitialTracking(_0) {
3288
3335
  ];
3289
3336
  case 2:
3290
3337
  metricEvent = _state.sent();
3291
- publishTracking(licenseKey, "metrics", {
3292
- events: [
3293
- metricEvent
3294
- ]
3295
- }, !!context.debugAdTiming);
3296
3338
  return [
3297
- 3,
3298
- 4
3339
+ 4,
3340
+ ingestPlayerTracking(licenseKey, "metrics", {
3341
+ events: [
3342
+ metricEvent
3343
+ ]
3344
+ })
3299
3345
  ];
3300
3346
  case 3:
3347
+ _state.sent();
3348
+ return [
3349
+ 3,
3350
+ 5
3351
+ ];
3352
+ case 4:
3301
3353
  error = _state.sent();
3302
3354
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
3303
3355
  return [
3304
3356
  3,
3305
- 4
3357
+ 5
3306
3358
  ];
3307
- case 4:
3359
+ case 5:
3308
3360
  return [
3309
3361
  2
3310
3362
  ];
@@ -3402,7 +3454,7 @@ function sendAdLoadedTracking(_0, _1) {
3402
3454
  }
3403
3455
  function sendAdImpressionTracking(_0, _1) {
3404
3456
  return _async_to_generator(function(licenseKey, adImpressionInfo) {
3405
- var context, _adImpressionInfo_adUrl, metricEvent, error;
3457
+ var context, metricEvent, error;
3406
3458
  var _arguments = arguments;
3407
3459
  return _ts_generator(this, function(_state) {
3408
3460
  switch(_state.label){
@@ -3415,9 +3467,9 @@ function sendAdImpressionTracking(_0, _1) {
3415
3467
  case 1:
3416
3468
  _state.trys.push([
3417
3469
  1,
3418
- 3,
3470
+ 5,
3419
3471
  ,
3420
- 4
3472
+ 6
3421
3473
  ]);
3422
3474
  return [
3423
3475
  4,
@@ -3427,30 +3479,43 @@ function sendAdImpressionTracking(_0, _1) {
3427
3479
  ];
3428
3480
  case 2:
3429
3481
  metricEvent = _state.sent();
3430
- publishTracking(licenseKey, "heartbeat", metricEvent, !!context.debugAdTiming);
3431
- publishTracking(licenseKey, "impressions", {
3432
- events: [
3433
- {
3434
- event_id: createUuid(),
3435
- player_id: metricEvent.player_id,
3436
- ad_played_count: 1,
3437
- ad_url: (_adImpressionInfo_adUrl = adImpressionInfo.adUrl) !== null && _adImpressionInfo_adUrl !== void 0 ? _adImpressionInfo_adUrl : adImpressionInfo.adId,
3438
- capture_at: adImpressionInfo.timestamp
3439
- }
3440
- ]
3441
- }, !!context.debugAdTiming);
3442
3482
  return [
3443
- 3,
3444
- 4
3483
+ 4,
3484
+ ingestPlayerTracking(licenseKey, "metrics", {
3485
+ events: [
3486
+ metricEvent
3487
+ ]
3488
+ })
3445
3489
  ];
3446
3490
  case 3:
3491
+ _state.sent();
3492
+ return [
3493
+ 4,
3494
+ ingestPlayerTracking(licenseKey, "impressions", {
3495
+ events: [
3496
+ {
3497
+ player_id: metricEvent.player_id,
3498
+ ad_played_count: 1,
3499
+ ad_url: adImpressionInfo.adUrl,
3500
+ capture_at: adImpressionInfo.timestamp
3501
+ }
3502
+ ]
3503
+ })
3504
+ ];
3505
+ case 4:
3506
+ _state.sent();
3507
+ return [
3508
+ 3,
3509
+ 6
3510
+ ];
3511
+ case 5:
3447
3512
  error = _state.sent();
3448
3513
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
3449
3514
  return [
3450
3515
  3,
3451
- 4
3516
+ 6
3452
3517
  ];
3453
- case 4:
3518
+ case 6:
3454
3519
  return [
3455
3520
  2
3456
3521
  ];
@@ -3473,9 +3538,9 @@ function sendHeartbeat(_0) {
3473
3538
  case 1:
3474
3539
  _state.trys.push([
3475
3540
  1,
3476
- 3,
3541
+ 4,
3477
3542
  ,
3478
- 4
3543
+ 5
3479
3544
  ]);
3480
3545
  return [
3481
3546
  4,
@@ -3483,19 +3548,28 @@ function sendHeartbeat(_0) {
3483
3548
  ];
3484
3549
  case 2:
3485
3550
  heartbeatData = _state.sent();
3486
- publishTracking(licenseKey, "heartbeat", heartbeatData, !!context.debugAdTiming);
3487
3551
  return [
3488
- 3,
3489
- 4
3552
+ 4,
3553
+ ingestPlayerTracking(licenseKey, "metrics", {
3554
+ events: [
3555
+ heartbeatData
3556
+ ]
3557
+ })
3490
3558
  ];
3491
3559
  case 3:
3560
+ _state.sent();
3561
+ return [
3562
+ 3,
3563
+ 5
3564
+ ];
3565
+ case 4:
3492
3566
  error = _state.sent();
3493
3567
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3494
3568
  return [
3495
3569
  3,
3496
- 4
3570
+ 5
3497
3571
  ];
3498
- case 4:
3572
+ case 5:
3499
3573
  return [
3500
3574
  2
3501
3575
  ];
@@ -5369,7 +5443,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5369
5443
  ];
5370
5444
  case 4:
5371
5445
  data = _state.sent();
5372
- this.applyMqttConfigFromResponse(data);
5373
5446
  imaPayload = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : (_data_response_ima = _data_response.ima) === null || _data_response_ima === void 0 ? void 0 : (_data_response_ima_publisherdeskima = _data_response_ima["publisherdesk.ima"]) === null || _data_response_ima_publisherdeskima === void 0 ? void 0 : _data_response_ima_publisherdeskima.payload;
5374
5447
  if (imaPayload) {
5375
5448
  decodedPayload = imaPayload;
@@ -5403,20 +5476,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5403
5476
  }).call(this);
5404
5477
  }
5405
5478
  },
5406
- {
5407
- key: "applyMqttConfigFromResponse",
5408
- value: function applyMqttConfigFromResponse(data) {
5409
- var _data_response;
5410
- var mqttConfig2 = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : _data_response.mqtt_config;
5411
- if (!mqttConfig2 || (typeof mqttConfig2 === "undefined" ? "undefined" : _type_of(mqttConfig2)) !== "object") {
5412
- return;
5413
- }
5414
- applyMQTTConfig(mqttConfig2);
5415
- if (this.debug) {
5416
- console.log("[StormcloudVideoPlayer] Applied MQTT config from /ads/web:", mqttConfig2);
5417
- }
5418
- }
5419
- },
5420
5479
  {
5421
5480
  key: "fetchAndParseVmap",
5422
5481
  value: function fetchAndParseVmap(vmapUrl) {
@@ -7277,22 +7336,27 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7277
7336
  console.log("[POD] Playing pod ad ".concat((payload === null || payload === void 0 ? void 0 : payload.index) != null ? payload.index + 1 : "?", "/").concat((_ref = payload === null || payload === void 0 ? void 0 : payload.total) !== null && _ref !== void 0 ? _ref : "?"));
7278
7337
  }
7279
7338
  });
7280
- adPlayer.on("ad_impression", function(payload) {
7281
- var impressionInfo = {
7339
+ adPlayer.on("all_ads_completed", function() {
7340
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7282
7341
  source: "hls",
7283
7342
  adIndex: _this.timing.currentAdIndex,
7284
7343
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7285
- };
7286
- if (payload === null || payload === void 0 ? void 0 : payload.adId) impressionInfo.adId = payload.adId;
7287
- if (payload === null || payload === void 0 ? void 0 : payload.mediaUrl) impressionInfo.adUrl = payload.mediaUrl;
7288
- if ((payload === null || payload === void 0 ? void 0 : payload.durationSeconds) != null) {
7289
- impressionInfo.durationSeconds = payload.durationSeconds;
7344
+ }, _this.analyticsContext).catch(function() {});
7345
+ _this.timing.recordPlayedAdDuration(_this.host.getAdPlayer());
7346
+ var remaining = _this.timing.getRemainingAdMs();
7347
+ _this.timing.consecutiveFailures = 0;
7348
+ if (_this.debug) {
7349
+ console.log("[StormcloudVideoPlayer] all_ads_completed event received - remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
7350
+ queuedUrls: _to_consumable_array(_this.adRequestQueue)
7351
+ } : {});
7290
7352
  }
7291
- sendAdImpressionTracking(_this.host.config.licenseKey, impressionInfo, _this.analyticsContext).catch(function(error) {
7292
- if (_this.debug) {
7293
- console.warn("[AdBreakOrchestrator] Failed to send ad impression tracking:", error);
7353
+ if (_this.inAdBreak) {
7354
+ if (_this.timing.hasTimeToStartAnotherAd()) {
7355
+ _this.tryNextAvailableAdWithRateLimit();
7356
+ } else {
7357
+ _this.handleAdPodComplete();
7294
7358
  }
7295
- });
7359
+ }
7296
7360
  });
7297
7361
  adPlayer.on("ad_error", function(errorPayload) {
7298
7362
  var errorMessage = "Ad playback failed";
@@ -7335,6 +7399,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7335
7399
  }
7336
7400
  });
7337
7401
  adPlayer.on("content_resume", function() {
7402
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7403
+ source: "hls",
7404
+ adIndex: _this.timing.currentAdIndex,
7405
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7406
+ }, _this.analyticsContext).catch(function() {});
7338
7407
  if (!_this.host.video.muted) {
7339
7408
  _this.host.video.muted = true;
7340
7409
  if (_this.debug) {