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
@@ -603,15 +603,147 @@ function logBrowserInfo() {
603
603
  userAgent: navigator.userAgent
604
604
  }));
605
605
  }
606
+ // src/utils/vastTrackingMacros.ts
607
+ var VAST_MACRO_PATTERN = /\[([A-Z][A-Z0-9_]*)\]/g;
608
+ var CACHEBUSTING_ALIASES = /\[CACHEBUSTER\]|\[cachebuster\]|\[timestamp\]|\[random\]|\[INSERT_RANDOM_NUMBER\]|%%CACHEBUSTER%%|%%CACHEBUST%%|\$\{CACHEBUSTER\}|\$\{CACHEBUSTING\}|\{cachebuster\}|\{random\}/g;
609
+ var KNOWN_VAST_MACROS = /* @__PURE__ */ new Set([
610
+ "ADCATEGORIES",
611
+ "ADCOUNT",
612
+ "ADPLAYHEAD",
613
+ "ADSERVINGID",
614
+ "ADTYPE",
615
+ "APIFRAMEWORKS",
616
+ "APPBUNDLE",
617
+ "ASSETURI",
618
+ "BLOCKEDADCATEGORIES",
619
+ "BREAKMAXADLENGTH",
620
+ "BREAKMAXADS",
621
+ "BREAKMAXDURATION",
622
+ "BREAKMINADLENGTH",
623
+ "BREAKPOSITION",
624
+ "CACHEBUSTING",
625
+ "CLICKPOS",
626
+ "CLICKTYPE",
627
+ "CLIENTUA",
628
+ "CONTENTID",
629
+ "CONTENTPLAYHEAD",
630
+ "CONTENTURI",
631
+ "DEVICEIP",
632
+ "DEVICEUA",
633
+ "DOMAIN",
634
+ "ERRORCODE",
635
+ "EXTENSIONS",
636
+ "GDPRCONSENT",
637
+ "IFA",
638
+ "IFATYPE",
639
+ "INVENTORYSTATE",
640
+ "LATLONG",
641
+ "LIMITADTRACKING",
642
+ "MEDIAMIME",
643
+ "MEDIAPLAYHEAD",
644
+ "OMIDPARTNER",
645
+ "PAGEURL",
646
+ "PLACEMENTTYPE",
647
+ "PLAYERCAPABILITIES",
648
+ "PLAYERSIZE",
649
+ "PLAYERSTATE",
650
+ "PODSEQUENCE",
651
+ "REGULATIONS",
652
+ "SERVERSIDE",
653
+ "SERVERUA",
654
+ "TIMESTAMP",
655
+ "TRANSACTIONID",
656
+ "UNIVERSALADID",
657
+ "VASTVERSIONS",
658
+ "VERIFICATIONVENDORS"
659
+ ]);
660
+ function generateCachebuster() {
661
+ return String(Math.floor(Math.random() * 9e7) + 1e7);
662
+ }
663
+ function formatPlayhead(seconds) {
664
+ if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
665
+ return void 0;
666
+ }
667
+ var total = Math.floor(seconds);
668
+ var hh = String(Math.floor(total / 3600)).padStart(2, "0");
669
+ var mm = String(Math.floor(total % 3600 / 60)).padStart(2, "0");
670
+ var ss = String(total % 60).padStart(2, "0");
671
+ var ms = String(Math.floor((seconds - total) * 1e3)).padStart(3, "0");
672
+ return "".concat(hh, ":").concat(mm, ":").concat(ss, ".").concat(ms);
673
+ }
674
+ function resolveMacroValues(ctx) {
675
+ var values = /* @__PURE__ */ new Map();
676
+ var set = function set(key, value) {
677
+ if (value != null && value !== "") values.set(key, value);
678
+ };
679
+ var adPlayhead = formatPlayhead(ctx.adPlayheadSeconds);
680
+ set("ADPLAYHEAD", adPlayhead);
681
+ set("MEDIAPLAYHEAD", adPlayhead);
682
+ set("CONTENTPLAYHEAD", formatPlayhead(ctx.contentPlayheadSeconds));
683
+ set("ASSETURI", ctx.assetUri);
684
+ set("MEDIAMIME", ctx.mediaMime);
685
+ set("TIMESTAMP", (/* @__PURE__ */ new Date()).toISOString());
686
+ set("SERVERSIDE", "0");
687
+ if (ctx.errorCode != null && ctx.errorCode !== "") {
688
+ set("ERRORCODE", String(ctx.errorCode));
689
+ }
690
+ if (ctx.playerWidth != null && ctx.playerHeight != null) {
691
+ set("PLAYERSIZE", "".concat(Math.round(ctx.playerWidth), ",").concat(Math.round(ctx.playerHeight)));
692
+ }
693
+ set("IFA", ctx.deviceId);
694
+ set("IFATYPE", ctx.deviceIdType);
695
+ if (ctx.limitAdTracking != null) {
696
+ set("LIMITADTRACKING", ctx.limitAdTracking ? "1" : "0");
697
+ }
698
+ set("PAGEURL", ctx.pageUrl);
699
+ if (ctx.pageUrl) {
700
+ try {
701
+ set("DOMAIN", new URL(ctx.pageUrl).hostname);
702
+ } catch (unused) {}
703
+ }
704
+ set("CLIENTUA", ctx.clientUa);
705
+ set("DEVICEUA", ctx.clientUa);
706
+ if (ctx.podSequence != null) {
707
+ set("PODSEQUENCE", String(ctx.podSequence));
708
+ }
709
+ return values;
710
+ }
711
+ function substituteVastTrackingMacros(url) {
712
+ var ctx = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
713
+ if (!url) return url;
714
+ var values = resolveMacroValues(ctx);
715
+ return url.replace(CACHEBUSTING_ALIASES, function() {
716
+ return generateCachebuster();
717
+ }).replace(VAST_MACRO_PATTERN, function(match, name) {
718
+ if (name === "CACHEBUSTING") return generateCachebuster();
719
+ var value = values.get(name);
720
+ if (value != null) return encodeURIComponent(value);
721
+ return KNOWN_VAST_MACROS.has(name) ? "" : match;
722
+ });
723
+ }
606
724
  // src/sdk/hlsAdPlayer.ts
607
725
  var MAX_VAST_WRAPPER_DEPTH = 5;
726
+ var BEACON_TIMEOUT_MS = 3e4;
727
+ var inFlightBeacons = /* @__PURE__ */ new Set();
728
+ var VAST_ERROR_MEDIA_DISPLAY = 405;
729
+ function toVastMediaErrorCode(error) {
730
+ switch(error === null || error === void 0 ? void 0 : error.code){
731
+ case 1:
732
+ return 400;
733
+ case 2:
734
+ return 401;
735
+ case 4:
736
+ return 403;
737
+ default:
738
+ return VAST_ERROR_MEDIA_DISPLAY;
739
+ }
740
+ }
608
741
  function createHlsAdPlayer(contentVideo, options) {
609
742
  var _contentVideo_AD_VIDEO_PROP, _contentVideo_AD_CONTAINER_PROP;
610
743
  var adPlaying = false;
611
744
  var originalMutedState = false;
612
745
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
613
746
  var listeners = /* @__PURE__ */ new Map();
614
- var licenseKey = options === null || options === void 0 ? void 0 : options.licenseKey;
615
747
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
616
748
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
617
749
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
@@ -622,10 +754,9 @@ function createHlsAdPlayer(contentVideo, options) {
622
754
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
623
755
  var adEventHandlers = [];
624
756
  var currentAd;
625
- var currentAdMediaUrl;
757
+ var currentAdMediaFile;
626
758
  var podAds = [];
627
759
  var podIndex = 0;
628
- var sessionId;
629
760
  var preloadedAds = /* @__PURE__ */ new Map();
630
761
  var preloadingAds = /* @__PURE__ */ new Map();
631
762
  var destroyed = false;
@@ -675,21 +806,47 @@ function createHlsAdPlayer(contentVideo, options) {
675
806
  }
676
807
  }
677
808
  }
678
- function generateSessionId() {
679
- return "session-".concat(Date.now(), "-").concat(Math.random().toString(36).substr(2, 9));
809
+ function buildTrackingMacroContext(errorCode) {
810
+ var context = {
811
+ adPlayheadSeconds: adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.currentTime,
812
+ contentPlayheadSeconds: contentVideo.currentTime,
813
+ assetUri: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.url,
814
+ mediaMime: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.type,
815
+ playerWidth: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientWidth) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientWidth),
816
+ playerHeight: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientHeight) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientHeight)
817
+ };
818
+ if (errorCode != null) {
819
+ context.errorCode = errorCode;
820
+ }
821
+ if (typeof navigator !== "undefined" && navigator.userAgent) {
822
+ context.clientUa = navigator.userAgent;
823
+ }
824
+ if (typeof location !== "undefined" && location.href) {
825
+ context.pageUrl = location.href;
826
+ }
827
+ if (podAds.length > 0) {
828
+ context.podSequence = podIndex + 1;
829
+ }
830
+ return context;
680
831
  }
681
- function fireTrackingPixels(urls) {
832
+ function fireTrackingPixels(urls, options2) {
682
833
  if (!urls || urls.length === 0) return;
834
+ var macroContext = buildTrackingMacroContext(options2 === null || options2 === void 0 ? void 0 : options2.errorCode);
683
835
  urls.forEach(function(url) {
684
836
  try {
685
- var trackingUrl = url;
686
- if (sessionId) {
687
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "session_id=").concat(sessionId);
688
- }
689
- if (licenseKey) {
690
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "license_key=").concat(licenseKey);
691
- }
837
+ var trackingUrl = substituteVastTrackingMacros(url, macroContext);
692
838
  var img = new Image(1, 1);
839
+ inFlightBeacons.add(img);
840
+ var timeoutId;
841
+ var release = function release() {
842
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
843
+ img.onload = null;
844
+ img.onerror = null;
845
+ inFlightBeacons.delete(img);
846
+ };
847
+ img.onload = release;
848
+ img.onerror = release;
849
+ timeoutId = setTimeout(release, BEACON_TIMEOUT_MS);
693
850
  img.src = trackingUrl;
694
851
  console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
695
852
  } catch (error) {
@@ -1328,7 +1485,9 @@ function createHlsAdPlayer(contentVideo, options) {
1328
1485
  if (!adPlaying) return;
1329
1486
  console.error("[HlsAdPlayer] Ad video error:", e);
1330
1487
  if (currentAd) {
1331
- fireTrackingPixels(currentAd.trackingUrls.error);
1488
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1489
+ errorCode: toVastMediaErrorCode(adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.error)
1490
+ });
1332
1491
  }
1333
1492
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1334
1493
  return;
@@ -1440,9 +1599,9 @@ function createHlsAdPlayer(contentVideo, options) {
1440
1599
  delete contentVideo.dataset.stormcloudAdPlaying;
1441
1600
  }
1442
1601
  }
1443
- function resetQuartileTracking() {
1602
+ function resetQuartileTracking(preserveImpression) {
1444
1603
  trackingFired = {
1445
- impression: false,
1604
+ impression: preserveImpression ? trackingFired.impression : false,
1446
1605
  start: false,
1447
1606
  firstQuartile: false,
1448
1607
  midpoint: false,
@@ -1450,27 +1609,6 @@ function createHlsAdPlayer(contentVideo, options) {
1450
1609
  complete: false
1451
1610
  };
1452
1611
  }
1453
- function markCreativeRendered(reason) {
1454
- if (!currentAd || trackingFired.impression) return;
1455
- trackingFired.impression = true;
1456
- fireTrackingPixels(currentAd.trackingUrls.impression);
1457
- console.log("[HlsAdPlayer] Impression (pod ".concat(podIndex + 1, "/").concat(podAds.length || 1, ", reason: ").concat(reason, "): ").concat(currentAd.title));
1458
- emit("ad_impression", {
1459
- adId: currentAd.id,
1460
- title: currentAd.title,
1461
- durationSeconds: currentAd.duration,
1462
- mediaUrl: currentAdMediaUrl,
1463
- podIndex: podIndex,
1464
- podSize: podAds.length || 1,
1465
- reason: reason
1466
- });
1467
- }
1468
- function canDetectBlackFrames() {
1469
- if (!adVideoElement) return false;
1470
- if (requiresMediaPipelineResetAfterAds()) return false;
1471
- if (isRvfcSupported(adVideoElement)) return true;
1472
- return getDecodedVideoFrameCount(adVideoElement) !== void 0;
1473
- }
1474
1612
  function isRvfcSupported(video) {
1475
1613
  return typeof video.requestVideoFrameCallback === "function";
1476
1614
  }
@@ -1533,7 +1671,7 @@ function createHlsAdPlayer(contentVideo, options) {
1533
1671
  if (!mediaFile) {
1534
1672
  throw new Error("No media file available for ad");
1535
1673
  }
1536
- currentAdMediaUrl = mediaFile.url;
1674
+ currentAdMediaFile = mediaFile;
1537
1675
  var isHlsAd = isHlsMediaFile(mediaFile);
1538
1676
  console.log("[HlsAdPlayer] Loading ".concat(isHlsAd ? "HLS" : "progressive", " ad (pod ").concat(podIndex + 1, "/").concat(podAds.length || 1, ") from: ").concat(mediaFile.url));
1539
1677
  if (isHlsAd && import_hls.default.isSupported()) {
@@ -1577,9 +1715,11 @@ function createHlsAdPlayer(contentVideo, options) {
1577
1715
  }
1578
1716
  podIndex++;
1579
1717
  currentAd = podAds[podIndex];
1580
- currentAdMediaUrl = void 0;
1581
- resetQuartileTracking();
1718
+ currentAdMediaFile = void 0;
1719
+ resetQuartileTracking(false);
1582
1720
  if (currentAd) {
1721
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1722
+ trackingFired.impression = true;
1583
1723
  console.log("[HlsAdPlayer] Advancing to next pod ad (".concat(podIndex + 1, "/").concat(podAds.length, "): ").concat(currentAd.title, ", duration: ").concat(currentAd.duration, "s"));
1584
1724
  }
1585
1725
  clearStallWatchdog();
@@ -1618,7 +1758,9 @@ function createHlsAdPlayer(contentVideo, options) {
1618
1758
  console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1619
1759
  clearStallWatchdog();
1620
1760
  if (currentAd) {
1621
- fireTrackingPixels(currentAd.trackingUrls.error);
1761
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1762
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1763
+ });
1622
1764
  }
1623
1765
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1624
1766
  return;
@@ -1674,7 +1816,6 @@ function createHlsAdPlayer(contentVideo, options) {
1674
1816
  if (!adPlaying) return;
1675
1817
  sawDecodedVideoFrame = true;
1676
1818
  markRvfcReliable();
1677
- markCreativeRendered("first_presented_frame");
1678
1819
  };
1679
1820
  try {
1680
1821
  videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
@@ -1687,7 +1828,9 @@ function createHlsAdPlayer(contentVideo, options) {
1687
1828
  console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1688
1829
  clearStallWatchdog();
1689
1830
  if (currentAd) {
1690
- fireTrackingPixels(currentAd.trackingUrls.error);
1831
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1832
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1833
+ });
1691
1834
  }
1692
1835
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1693
1836
  return;
@@ -1709,7 +1852,6 @@ function createHlsAdPlayer(contentVideo, options) {
1709
1852
  if (decodedFrames !== void 0 && decodedFrames > 0) {
1710
1853
  sawDecodedVideoFrame = true;
1711
1854
  markFrameCounterReliable();
1712
- markCreativeRendered("decoded_frames");
1713
1855
  } else if (adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1714
1856
  var timeAdvancing = adVideoElement.currentTime > lastAdProgressPosition + 0.05;
1715
1857
  if (isRvfcSupported(adVideoElement) && timeAdvancing && isRvfcReliable()) {
@@ -1722,9 +1864,6 @@ function createHlsAdPlayer(contentVideo, options) {
1722
1864
  }
1723
1865
  }
1724
1866
  }
1725
- if (!trackingFired.impression && !adVideoElement.paused && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && (sawDecodedVideoFrame || !canDetectBlackFrames())) {
1726
- markCreativeRendered(sawDecodedVideoFrame ? "decoded_frames" : "sustained_playback");
1727
- }
1728
1867
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1729
1868
  noteAdProgress();
1730
1869
  return;
@@ -1825,7 +1964,6 @@ function createHlsAdPlayer(contentVideo, options) {
1825
1964
  ,
1826
1965
  6
1827
1966
  ]);
1828
- sessionId = generateSessionId();
1829
1967
  ads = [];
1830
1968
  if (!preloadedAds.has(vastTagUrl)) return [
1831
1969
  3,
@@ -1863,9 +2001,12 @@ function createHlsAdPlayer(contentVideo, options) {
1863
2001
  podAds = ads;
1864
2002
  podIndex = 0;
1865
2003
  currentAd = ads[0];
1866
- currentAdMediaUrl = void 0;
1867
- resetQuartileTracking();
2004
+ currentAdMediaFile = void 0;
1868
2005
  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"));
2006
+ if (currentAd) {
2007
+ fireTrackingPixels(currentAd.trackingUrls.impression);
2008
+ trackingFired.impression = true;
2009
+ }
1869
2010
  return [
1870
2011
  2,
1871
2012
  Promise.resolve()
@@ -1905,7 +2046,7 @@ function createHlsAdPlayer(contentVideo, options) {
1905
2046
  contentVideo[AD_VIDEO_PROP] = adVideoElement;
1906
2047
  }
1907
2048
  setupAdEventListeners();
1908
- resetQuartileTracking();
2049
+ resetQuartileTracking(true);
1909
2050
  contentVolume = contentVideo.volume;
1910
2051
  originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1911
2052
  if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
@@ -1995,7 +2136,7 @@ function createHlsAdPlayer(contentVideo, options) {
1995
2136
  }
1996
2137
  }
1997
2138
  currentAd = void 0;
1998
- currentAdMediaUrl = void 0;
2139
+ currentAdMediaFile = void 0;
1999
2140
  podAds = [];
2000
2141
  podIndex = 0;
2001
2142
  return [
@@ -2036,6 +2177,7 @@ function createHlsAdPlayer(contentVideo, options) {
2036
2177
  teardownAdEventListeners();
2037
2178
  releaseAdDecoder();
2038
2179
  currentAd = void 0;
2180
+ currentAdMediaFile = void 0;
2039
2181
  podAds = [];
2040
2182
  podIndex = 0;
2041
2183
  listeners.clear();
@@ -2359,163 +2501,83 @@ function createPalNonceManager() {
2359
2501
  }
2360
2502
  };
2361
2503
  }
2362
- // src/utils/mqttConfig.ts
2363
- var DEFAULT_MQTT_CONFIG = {
2504
+ // src/utils/trackingIngest.ts
2505
+ var DEFAULT_TRACKING_INGEST_CONFIG = {
2364
2506
  enabled: true,
2365
- brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2366
- brokerPort: 8883,
2367
- wsPort: 8084,
2368
- username: "for-sonifi",
2369
- password: "sonifi-mqtt",
2370
- topicPrefix: "adstorm/players",
2371
- qos: 1
2507
+ baseUrl: "https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking"
2372
2508
  };
2373
- var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2374
- function applyMQTTConfig(overrides) {
2375
- Object.assign(mqttConfig, overrides);
2376
- }
2377
- function isMQTTEnabled() {
2378
- return mqttConfig.enabled;
2379
- }
2380
- function buildMQTTBrokerUrl() {
2381
- if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2382
- return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2383
- }
2384
- function buildPlayerTopic(licenseKey, channel) {
2385
- return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2386
- }
2387
- // src/utils/mqttClient.ts
2388
- var import_mqtt = __toESM(require("mqtt"), 1);
2389
- var LOG = "[StormcloudVideoPlayer][MQTT]";
2390
- var KEEPALIVE_SECONDS = 30;
2391
- var CONNECT_TIMEOUT_MS = 1e4;
2392
- var RECONNECT_BASE_MS = 5e3;
2393
- var RECONNECT_MAX_MS = 6e4;
2394
- var MAX_RECONNECT_ATTEMPTS = 8;
2395
- var COOLDOWN_MS = 10 * 6e4;
2396
- var client = null;
2397
- var status = "disconnected";
2398
- var reconnectAttempts = 0;
2399
- var reconnectTimer = null;
2400
- var stopped = false;
2401
- function clearReconnectTimer() {
2402
- if (reconnectTimer) {
2403
- clearTimeout(reconnectTimer);
2404
- reconnectTimer = null;
2405
- }
2406
- }
2407
- function teardownClient() {
2408
- if (client) {
2409
- try {
2410
- client.removeAllListeners();
2411
- client.end(true);
2412
- } catch (unused) {}
2413
- client = null;
2414
- }
2415
- }
2416
- function scheduleReconnect() {
2417
- if (stopped || !isMQTTEnabled() || reconnectTimer) return;
2418
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
2419
- console.warn("".concat(LOG, " giving up after ").concat(reconnectAttempts, " attempts; cooling down for ").concat(COOLDOWN_MS / 6e4, "m"));
2420
- teardownClient();
2421
- status = "disconnected";
2422
- reconnectTimer = setTimeout(function() {
2423
- reconnectTimer = null;
2424
- reconnectAttempts = 0;
2425
- openConnection();
2426
- }, COOLDOWN_MS);
2427
- return;
2428
- }
2429
- var delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts));
2430
- var jitter = Math.floor(Math.random() * 1e3);
2431
- reconnectAttempts += 1;
2432
- reconnectTimer = setTimeout(function() {
2433
- reconnectTimer = null;
2434
- openConnection();
2435
- }, delay + jitter);
2436
- }
2437
- function openConnection() {
2438
- if (stopped || !isMQTTEnabled()) return;
2439
- teardownClient();
2440
- var url = buildMQTTBrokerUrl();
2441
- status = "connecting";
2442
- var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2443
- try {
2444
- client = import_mqtt.default.connect(url, {
2445
- clientId: clientId,
2446
- username: mqttConfig.username,
2447
- password: mqttConfig.password,
2448
- keepalive: KEEPALIVE_SECONDS,
2449
- clean: true,
2450
- // Disable MQTT.js internal reconnect; we manage reconnection ourselves.
2451
- reconnectPeriod: 0,
2452
- connectTimeout: CONNECT_TIMEOUT_MS,
2453
- queueQoSZero: false,
2454
- reschedulePings: true,
2455
- // Root-cause fix for the Tizen 4.0 "Keepalive timeout" loop: MQTT.js's
2456
- // default timerVariant "auto" runs the keepalive interval inside a
2457
- // Blob-URL Web Worker (via worker-timers). On Samsung Tizen 4.0 (old
2458
- // WebKit) served from a file:// origin that worker can't tick reliably,
2459
- // so the keepalive counter trips onKeepaliveTimeout almost immediately
2460
- // after connect. Force the native setInterval timer instead; the main
2461
- // thread is free because video is decoded by the native AVPlay pipeline.
2462
- timerVariant: "native"
2463
- });
2464
- } catch (err) {
2465
- status = "error";
2466
- console.warn("".concat(LOG, " connect() threw:"), err);
2467
- scheduleReconnect();
2468
- return;
2469
- }
2470
- client.on("connect", function() {
2471
- status = "connected";
2472
- reconnectAttempts = 0;
2473
- clearReconnectTimer();
2474
- console.info("".concat(LOG, " connected to ").concat(url));
2475
- });
2476
- client.on("offline", function() {
2477
- if (status === "connected") status = "disconnected";
2478
- console.warn("".concat(LOG, " offline"));
2479
- scheduleReconnect();
2480
- });
2481
- client.on("error", function(err) {
2482
- status = "error";
2483
- console.warn("".concat(LOG, " error:"), err.message);
2484
- scheduleReconnect();
2485
- });
2486
- client.on("close", function() {
2487
- if (status === "connected") status = "disconnected";
2488
- scheduleReconnect();
2489
- });
2509
+ var trackingIngestConfig = _object_spread({}, DEFAULT_TRACKING_INGEST_CONFIG);
2510
+ function isTrackingIngestEnabled() {
2511
+ return trackingIngestConfig.enabled === true;
2490
2512
  }
2491
- function initMQTTClient() {
2492
- if (client || !isMQTTEnabled()) return;
2493
- stopped = false;
2494
- reconnectAttempts = 0;
2495
- openConnection();
2513
+ function normalizeIngestBaseUrl(baseUrl) {
2514
+ return baseUrl.replace(/\/+$/, "");
2496
2515
  }
2497
- function ensureMQTTClient() {
2498
- if (isMQTTEnabled() && !client && !reconnectTimer) {
2499
- initMQTTClient();
2516
+ function buildTrackingIngestUrl(channel) {
2517
+ var base = normalizeIngestBaseUrl(trackingIngestConfig.baseUrl);
2518
+ if (channel === "impressions") {
2519
+ return "".concat(base, "/impressions/ingest");
2500
2520
  }
2521
+ return "".concat(base, "/metrics/ingest");
2501
2522
  }
2502
- function publishMQTT(topic, payload) {
2503
- if (!isMQTTEnabled()) {
2504
- return false;
2505
- }
2506
- ensureMQTTClient();
2507
- if (!client || !client.connected) {
2508
- return false;
2509
- }
2510
- try {
2511
- client.publish(topic, JSON.stringify(payload), {
2512
- qos: mqttConfig.qos
2523
+ function ingestPlayerTracking(licenseKey, channel, body) {
2524
+ return _async_to_generator(function() {
2525
+ var response, error;
2526
+ return _ts_generator(this, function(_state) {
2527
+ switch(_state.label){
2528
+ case 0:
2529
+ if (!isTrackingIngestEnabled() || !licenseKey) {
2530
+ return [
2531
+ 2,
2532
+ false
2533
+ ];
2534
+ }
2535
+ _state.label = 1;
2536
+ case 1:
2537
+ _state.trys.push([
2538
+ 1,
2539
+ 3,
2540
+ ,
2541
+ 4
2542
+ ]);
2543
+ return [
2544
+ 4,
2545
+ fetch(buildTrackingIngestUrl(channel), {
2546
+ method: "POST",
2547
+ headers: {
2548
+ "Content-Type": "application/json",
2549
+ Authorization: "License-Key ".concat(licenseKey)
2550
+ },
2551
+ body: JSON.stringify(body)
2552
+ })
2553
+ ];
2554
+ case 2:
2555
+ response = _state.sent();
2556
+ if (!response.ok) {
2557
+ console.warn("[StormcloudVideoPlayer] Tracking ingest failed (".concat(response.status, ") for ").concat(channel));
2558
+ return [
2559
+ 2,
2560
+ false
2561
+ ];
2562
+ }
2563
+ return [
2564
+ 2,
2565
+ true
2566
+ ];
2567
+ case 3:
2568
+ error = _state.sent();
2569
+ console.warn("[StormcloudVideoPlayer] Tracking ingest error for ".concat(channel, ":"), error);
2570
+ return [
2571
+ 2,
2572
+ false
2573
+ ];
2574
+ case 4:
2575
+ return [
2576
+ 2
2577
+ ];
2578
+ }
2513
2579
  });
2514
- return true;
2515
- } catch (err) {
2516
- console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2517
- return false;
2518
- }
2580
+ })();
2519
2581
  }
2520
2582
  // src/utils/vastEnvironmentSignals.ts
2521
2583
  var AIRY_ANDROID_APP_ID = "com.freeairytv.android";
@@ -3286,7 +3348,7 @@ function getBrowserID(clientInfo) {
3286
3348
  })();
3287
3349
  }
3288
3350
  function canPublish(licenseKey) {
3289
- return Boolean(isMQTTEnabled() && licenseKey);
3351
+ return Boolean(isTrackingIngestEnabled() && licenseKey);
3290
3352
  }
3291
3353
  function buildPlayerMetricEvent() {
3292
3354
  return _async_to_generator(function() {
@@ -3328,21 +3390,6 @@ function buildPlayerMetricEvent() {
3328
3390
  });
3329
3391
  }).apply(this, arguments);
3330
3392
  }
3331
- function publishTracking(licenseKey, channel, body) {
3332
- var debug = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
3333
- ensureMQTTClient();
3334
- var topic = buildPlayerTopic(licenseKey, channel);
3335
- var published = publishMQTT(topic, body);
3336
- if (debug) {
3337
- console.log("[StormcloudVideoPlayer] publishTracking:", JSON.stringify({
3338
- licenseKey: licenseKey,
3339
- channel: channel,
3340
- topic: topic,
3341
- body: body,
3342
- published: published
3343
- }, null, 2));
3344
- }
3345
- }
3346
3393
  function sendInitialTracking(_0) {
3347
3394
  return _async_to_generator(function(licenseKey) {
3348
3395
  var context, metricEvent, error;
@@ -3358,9 +3405,9 @@ function sendInitialTracking(_0) {
3358
3405
  case 1:
3359
3406
  _state.trys.push([
3360
3407
  1,
3361
- 3,
3408
+ 4,
3362
3409
  ,
3363
- 4
3410
+ 5
3364
3411
  ]);
3365
3412
  return [
3366
3413
  4,
@@ -3371,23 +3418,28 @@ function sendInitialTracking(_0) {
3371
3418
  ];
3372
3419
  case 2:
3373
3420
  metricEvent = _state.sent();
3374
- publishTracking(licenseKey, "metrics", {
3375
- events: [
3376
- metricEvent
3377
- ]
3378
- }, !!context.debugAdTiming);
3379
3421
  return [
3380
- 3,
3381
- 4
3422
+ 4,
3423
+ ingestPlayerTracking(licenseKey, "metrics", {
3424
+ events: [
3425
+ metricEvent
3426
+ ]
3427
+ })
3382
3428
  ];
3383
3429
  case 3:
3430
+ _state.sent();
3431
+ return [
3432
+ 3,
3433
+ 5
3434
+ ];
3435
+ case 4:
3384
3436
  error = _state.sent();
3385
3437
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
3386
3438
  return [
3387
3439
  3,
3388
- 4
3440
+ 5
3389
3441
  ];
3390
- case 4:
3442
+ case 5:
3391
3443
  return [
3392
3444
  2
3393
3445
  ];
@@ -3485,7 +3537,7 @@ function sendAdLoadedTracking(_0, _1) {
3485
3537
  }
3486
3538
  function sendAdImpressionTracking(_0, _1) {
3487
3539
  return _async_to_generator(function(licenseKey, adImpressionInfo) {
3488
- var context, _adImpressionInfo_adUrl, metricEvent, error;
3540
+ var context, metricEvent, error;
3489
3541
  var _arguments = arguments;
3490
3542
  return _ts_generator(this, function(_state) {
3491
3543
  switch(_state.label){
@@ -3498,9 +3550,9 @@ function sendAdImpressionTracking(_0, _1) {
3498
3550
  case 1:
3499
3551
  _state.trys.push([
3500
3552
  1,
3501
- 3,
3553
+ 5,
3502
3554
  ,
3503
- 4
3555
+ 6
3504
3556
  ]);
3505
3557
  return [
3506
3558
  4,
@@ -3510,30 +3562,43 @@ function sendAdImpressionTracking(_0, _1) {
3510
3562
  ];
3511
3563
  case 2:
3512
3564
  metricEvent = _state.sent();
3513
- publishTracking(licenseKey, "heartbeat", metricEvent, !!context.debugAdTiming);
3514
- publishTracking(licenseKey, "impressions", {
3515
- events: [
3516
- {
3517
- event_id: createUuid(),
3518
- player_id: metricEvent.player_id,
3519
- ad_played_count: 1,
3520
- ad_url: (_adImpressionInfo_adUrl = adImpressionInfo.adUrl) !== null && _adImpressionInfo_adUrl !== void 0 ? _adImpressionInfo_adUrl : adImpressionInfo.adId,
3521
- capture_at: adImpressionInfo.timestamp
3522
- }
3523
- ]
3524
- }, !!context.debugAdTiming);
3525
3565
  return [
3526
- 3,
3527
- 4
3566
+ 4,
3567
+ ingestPlayerTracking(licenseKey, "metrics", {
3568
+ events: [
3569
+ metricEvent
3570
+ ]
3571
+ })
3528
3572
  ];
3529
3573
  case 3:
3574
+ _state.sent();
3575
+ return [
3576
+ 4,
3577
+ ingestPlayerTracking(licenseKey, "impressions", {
3578
+ events: [
3579
+ {
3580
+ player_id: metricEvent.player_id,
3581
+ ad_played_count: 1,
3582
+ ad_url: adImpressionInfo.adUrl,
3583
+ capture_at: adImpressionInfo.timestamp
3584
+ }
3585
+ ]
3586
+ })
3587
+ ];
3588
+ case 4:
3589
+ _state.sent();
3590
+ return [
3591
+ 3,
3592
+ 6
3593
+ ];
3594
+ case 5:
3530
3595
  error = _state.sent();
3531
3596
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
3532
3597
  return [
3533
3598
  3,
3534
- 4
3599
+ 6
3535
3600
  ];
3536
- case 4:
3601
+ case 6:
3537
3602
  return [
3538
3603
  2
3539
3604
  ];
@@ -3556,9 +3621,9 @@ function sendHeartbeat(_0) {
3556
3621
  case 1:
3557
3622
  _state.trys.push([
3558
3623
  1,
3559
- 3,
3624
+ 4,
3560
3625
  ,
3561
- 4
3626
+ 5
3562
3627
  ]);
3563
3628
  return [
3564
3629
  4,
@@ -3566,19 +3631,28 @@ function sendHeartbeat(_0) {
3566
3631
  ];
3567
3632
  case 2:
3568
3633
  heartbeatData = _state.sent();
3569
- publishTracking(licenseKey, "heartbeat", heartbeatData, !!context.debugAdTiming);
3570
3634
  return [
3571
- 3,
3572
- 4
3635
+ 4,
3636
+ ingestPlayerTracking(licenseKey, "metrics", {
3637
+ events: [
3638
+ heartbeatData
3639
+ ]
3640
+ })
3573
3641
  ];
3574
3642
  case 3:
3643
+ _state.sent();
3644
+ return [
3645
+ 3,
3646
+ 5
3647
+ ];
3648
+ case 4:
3575
3649
  error = _state.sent();
3576
3650
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3577
3651
  return [
3578
3652
  3,
3579
- 4
3653
+ 5
3580
3654
  ];
3581
- case 4:
3655
+ case 5:
3582
3656
  return [
3583
3657
  2
3584
3658
  ];
@@ -5452,7 +5526,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5452
5526
  ];
5453
5527
  case 4:
5454
5528
  data = _state.sent();
5455
- this.applyMqttConfigFromResponse(data);
5456
5529
  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;
5457
5530
  if (imaPayload) {
5458
5531
  decodedPayload = imaPayload;
@@ -5486,20 +5559,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5486
5559
  }).call(this);
5487
5560
  }
5488
5561
  },
5489
- {
5490
- key: "applyMqttConfigFromResponse",
5491
- value: function applyMqttConfigFromResponse(data) {
5492
- var _data_response;
5493
- var mqttConfig2 = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : _data_response.mqtt_config;
5494
- if (!mqttConfig2 || (typeof mqttConfig2 === "undefined" ? "undefined" : _type_of(mqttConfig2)) !== "object") {
5495
- return;
5496
- }
5497
- applyMQTTConfig(mqttConfig2);
5498
- if (this.debug) {
5499
- console.log("[StormcloudVideoPlayer] Applied MQTT config from /ads/web:", mqttConfig2);
5500
- }
5501
- }
5502
- },
5503
5562
  {
5504
5563
  key: "fetchAndParseVmap",
5505
5564
  value: function fetchAndParseVmap(vmapUrl) {
@@ -7360,22 +7419,27 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7360
7419
  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 : "?"));
7361
7420
  }
7362
7421
  });
7363
- adPlayer.on("ad_impression", function(payload) {
7364
- var impressionInfo = {
7422
+ adPlayer.on("all_ads_completed", function() {
7423
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7365
7424
  source: "hls",
7366
7425
  adIndex: _this.timing.currentAdIndex,
7367
7426
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7368
- };
7369
- if (payload === null || payload === void 0 ? void 0 : payload.adId) impressionInfo.adId = payload.adId;
7370
- if (payload === null || payload === void 0 ? void 0 : payload.mediaUrl) impressionInfo.adUrl = payload.mediaUrl;
7371
- if ((payload === null || payload === void 0 ? void 0 : payload.durationSeconds) != null) {
7372
- impressionInfo.durationSeconds = payload.durationSeconds;
7427
+ }, _this.analyticsContext).catch(function() {});
7428
+ _this.timing.recordPlayedAdDuration(_this.host.getAdPlayer());
7429
+ var remaining = _this.timing.getRemainingAdMs();
7430
+ _this.timing.consecutiveFailures = 0;
7431
+ if (_this.debug) {
7432
+ console.log("[StormcloudVideoPlayer] all_ads_completed event received - remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
7433
+ queuedUrls: _to_consumable_array(_this.adRequestQueue)
7434
+ } : {});
7373
7435
  }
7374
- sendAdImpressionTracking(_this.host.config.licenseKey, impressionInfo, _this.analyticsContext).catch(function(error) {
7375
- if (_this.debug) {
7376
- console.warn("[AdBreakOrchestrator] Failed to send ad impression tracking:", error);
7436
+ if (_this.inAdBreak) {
7437
+ if (_this.timing.hasTimeToStartAnotherAd()) {
7438
+ _this.tryNextAvailableAdWithRateLimit();
7439
+ } else {
7440
+ _this.handleAdPodComplete();
7377
7441
  }
7378
- });
7442
+ }
7379
7443
  });
7380
7444
  adPlayer.on("ad_error", function(errorPayload) {
7381
7445
  var errorMessage = "Ad playback failed";
@@ -7418,6 +7482,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7418
7482
  }
7419
7483
  });
7420
7484
  adPlayer.on("content_resume", function() {
7485
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7486
+ source: "hls",
7487
+ adIndex: _this.timing.currentAdIndex,
7488
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7489
+ }, _this.analyticsContext).catch(function() {});
7421
7490
  if (!_this.host.video.muted) {
7422
7491
  _this.host.video.muted = true;
7423
7492
  if (_this.debug) {