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
@@ -557,15 +557,147 @@ function logBrowserInfo() {
557
557
  userAgent: navigator.userAgent
558
558
  }));
559
559
  }
560
+ // src/utils/vastTrackingMacros.ts
561
+ var VAST_MACRO_PATTERN = /\[([A-Z][A-Z0-9_]*)\]/g;
562
+ var CACHEBUSTING_ALIASES = /\[CACHEBUSTER\]|\[cachebuster\]|\[timestamp\]|\[random\]|\[INSERT_RANDOM_NUMBER\]|%%CACHEBUSTER%%|%%CACHEBUST%%|\$\{CACHEBUSTER\}|\$\{CACHEBUSTING\}|\{cachebuster\}|\{random\}/g;
563
+ var KNOWN_VAST_MACROS = /* @__PURE__ */ new Set([
564
+ "ADCATEGORIES",
565
+ "ADCOUNT",
566
+ "ADPLAYHEAD",
567
+ "ADSERVINGID",
568
+ "ADTYPE",
569
+ "APIFRAMEWORKS",
570
+ "APPBUNDLE",
571
+ "ASSETURI",
572
+ "BLOCKEDADCATEGORIES",
573
+ "BREAKMAXADLENGTH",
574
+ "BREAKMAXADS",
575
+ "BREAKMAXDURATION",
576
+ "BREAKMINADLENGTH",
577
+ "BREAKPOSITION",
578
+ "CACHEBUSTING",
579
+ "CLICKPOS",
580
+ "CLICKTYPE",
581
+ "CLIENTUA",
582
+ "CONTENTID",
583
+ "CONTENTPLAYHEAD",
584
+ "CONTENTURI",
585
+ "DEVICEIP",
586
+ "DEVICEUA",
587
+ "DOMAIN",
588
+ "ERRORCODE",
589
+ "EXTENSIONS",
590
+ "GDPRCONSENT",
591
+ "IFA",
592
+ "IFATYPE",
593
+ "INVENTORYSTATE",
594
+ "LATLONG",
595
+ "LIMITADTRACKING",
596
+ "MEDIAMIME",
597
+ "MEDIAPLAYHEAD",
598
+ "OMIDPARTNER",
599
+ "PAGEURL",
600
+ "PLACEMENTTYPE",
601
+ "PLAYERCAPABILITIES",
602
+ "PLAYERSIZE",
603
+ "PLAYERSTATE",
604
+ "PODSEQUENCE",
605
+ "REGULATIONS",
606
+ "SERVERSIDE",
607
+ "SERVERUA",
608
+ "TIMESTAMP",
609
+ "TRANSACTIONID",
610
+ "UNIVERSALADID",
611
+ "VASTVERSIONS",
612
+ "VERIFICATIONVENDORS"
613
+ ]);
614
+ function generateCachebuster() {
615
+ return String(Math.floor(Math.random() * 9e7) + 1e7);
616
+ }
617
+ function formatPlayhead(seconds) {
618
+ if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
619
+ return void 0;
620
+ }
621
+ var total = Math.floor(seconds);
622
+ var hh = String(Math.floor(total / 3600)).padStart(2, "0");
623
+ var mm = String(Math.floor(total % 3600 / 60)).padStart(2, "0");
624
+ var ss = String(total % 60).padStart(2, "0");
625
+ var ms = String(Math.floor((seconds - total) * 1e3)).padStart(3, "0");
626
+ return "".concat(hh, ":").concat(mm, ":").concat(ss, ".").concat(ms);
627
+ }
628
+ function resolveMacroValues(ctx) {
629
+ var values = /* @__PURE__ */ new Map();
630
+ var set = function set(key, value) {
631
+ if (value != null && value !== "") values.set(key, value);
632
+ };
633
+ var adPlayhead = formatPlayhead(ctx.adPlayheadSeconds);
634
+ set("ADPLAYHEAD", adPlayhead);
635
+ set("MEDIAPLAYHEAD", adPlayhead);
636
+ set("CONTENTPLAYHEAD", formatPlayhead(ctx.contentPlayheadSeconds));
637
+ set("ASSETURI", ctx.assetUri);
638
+ set("MEDIAMIME", ctx.mediaMime);
639
+ set("TIMESTAMP", (/* @__PURE__ */ new Date()).toISOString());
640
+ set("SERVERSIDE", "0");
641
+ if (ctx.errorCode != null && ctx.errorCode !== "") {
642
+ set("ERRORCODE", String(ctx.errorCode));
643
+ }
644
+ if (ctx.playerWidth != null && ctx.playerHeight != null) {
645
+ set("PLAYERSIZE", "".concat(Math.round(ctx.playerWidth), ",").concat(Math.round(ctx.playerHeight)));
646
+ }
647
+ set("IFA", ctx.deviceId);
648
+ set("IFATYPE", ctx.deviceIdType);
649
+ if (ctx.limitAdTracking != null) {
650
+ set("LIMITADTRACKING", ctx.limitAdTracking ? "1" : "0");
651
+ }
652
+ set("PAGEURL", ctx.pageUrl);
653
+ if (ctx.pageUrl) {
654
+ try {
655
+ set("DOMAIN", new URL(ctx.pageUrl).hostname);
656
+ } catch (unused) {}
657
+ }
658
+ set("CLIENTUA", ctx.clientUa);
659
+ set("DEVICEUA", ctx.clientUa);
660
+ if (ctx.podSequence != null) {
661
+ set("PODSEQUENCE", String(ctx.podSequence));
662
+ }
663
+ return values;
664
+ }
665
+ function substituteVastTrackingMacros(url) {
666
+ var ctx = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
667
+ if (!url) return url;
668
+ var values = resolveMacroValues(ctx);
669
+ return url.replace(CACHEBUSTING_ALIASES, function() {
670
+ return generateCachebuster();
671
+ }).replace(VAST_MACRO_PATTERN, function(match, name) {
672
+ if (name === "CACHEBUSTING") return generateCachebuster();
673
+ var value = values.get(name);
674
+ if (value != null) return encodeURIComponent(value);
675
+ return KNOWN_VAST_MACROS.has(name) ? "" : match;
676
+ });
677
+ }
560
678
  // src/sdk/hlsAdPlayer.ts
561
679
  var MAX_VAST_WRAPPER_DEPTH = 5;
680
+ var BEACON_TIMEOUT_MS = 3e4;
681
+ var inFlightBeacons = /* @__PURE__ */ new Set();
682
+ var VAST_ERROR_MEDIA_DISPLAY = 405;
683
+ function toVastMediaErrorCode(error) {
684
+ switch(error === null || error === void 0 ? void 0 : error.code){
685
+ case 1:
686
+ return 400;
687
+ case 2:
688
+ return 401;
689
+ case 4:
690
+ return 403;
691
+ default:
692
+ return VAST_ERROR_MEDIA_DISPLAY;
693
+ }
694
+ }
562
695
  function createHlsAdPlayer(contentVideo, options) {
563
696
  var _contentVideo_AD_VIDEO_PROP, _contentVideo_AD_CONTAINER_PROP;
564
697
  var adPlaying = false;
565
698
  var originalMutedState = false;
566
699
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
567
700
  var listeners = /* @__PURE__ */ new Map();
568
- var licenseKey = options === null || options === void 0 ? void 0 : options.licenseKey;
569
701
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
570
702
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
571
703
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
@@ -576,10 +708,9 @@ function createHlsAdPlayer(contentVideo, options) {
576
708
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
577
709
  var adEventHandlers = [];
578
710
  var currentAd;
579
- var currentAdMediaUrl;
711
+ var currentAdMediaFile;
580
712
  var podAds = [];
581
713
  var podIndex = 0;
582
- var sessionId;
583
714
  var preloadedAds = /* @__PURE__ */ new Map();
584
715
  var preloadingAds = /* @__PURE__ */ new Map();
585
716
  var destroyed = false;
@@ -629,21 +760,47 @@ function createHlsAdPlayer(contentVideo, options) {
629
760
  }
630
761
  }
631
762
  }
632
- function generateSessionId() {
633
- return "session-".concat(Date.now(), "-").concat(Math.random().toString(36).substr(2, 9));
763
+ function buildTrackingMacroContext(errorCode) {
764
+ var context = {
765
+ adPlayheadSeconds: adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.currentTime,
766
+ contentPlayheadSeconds: contentVideo.currentTime,
767
+ assetUri: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.url,
768
+ mediaMime: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.type,
769
+ playerWidth: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientWidth) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientWidth),
770
+ playerHeight: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientHeight) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientHeight)
771
+ };
772
+ if (errorCode != null) {
773
+ context.errorCode = errorCode;
774
+ }
775
+ if (typeof navigator !== "undefined" && navigator.userAgent) {
776
+ context.clientUa = navigator.userAgent;
777
+ }
778
+ if (typeof location !== "undefined" && location.href) {
779
+ context.pageUrl = location.href;
780
+ }
781
+ if (podAds.length > 0) {
782
+ context.podSequence = podIndex + 1;
783
+ }
784
+ return context;
634
785
  }
635
- function fireTrackingPixels(urls) {
786
+ function fireTrackingPixels(urls, options2) {
636
787
  if (!urls || urls.length === 0) return;
788
+ var macroContext = buildTrackingMacroContext(options2 === null || options2 === void 0 ? void 0 : options2.errorCode);
637
789
  urls.forEach(function(url) {
638
790
  try {
639
- var trackingUrl = url;
640
- if (sessionId) {
641
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "session_id=").concat(sessionId);
642
- }
643
- if (licenseKey) {
644
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "license_key=").concat(licenseKey);
645
- }
791
+ var trackingUrl = substituteVastTrackingMacros(url, macroContext);
646
792
  var img = new Image(1, 1);
793
+ inFlightBeacons.add(img);
794
+ var timeoutId;
795
+ var release = function release() {
796
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
797
+ img.onload = null;
798
+ img.onerror = null;
799
+ inFlightBeacons.delete(img);
800
+ };
801
+ img.onload = release;
802
+ img.onerror = release;
803
+ timeoutId = setTimeout(release, BEACON_TIMEOUT_MS);
647
804
  img.src = trackingUrl;
648
805
  console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
649
806
  } catch (error) {
@@ -1282,7 +1439,9 @@ function createHlsAdPlayer(contentVideo, options) {
1282
1439
  if (!adPlaying) return;
1283
1440
  console.error("[HlsAdPlayer] Ad video error:", e);
1284
1441
  if (currentAd) {
1285
- fireTrackingPixels(currentAd.trackingUrls.error);
1442
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1443
+ errorCode: toVastMediaErrorCode(adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.error)
1444
+ });
1286
1445
  }
1287
1446
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1288
1447
  return;
@@ -1394,9 +1553,9 @@ function createHlsAdPlayer(contentVideo, options) {
1394
1553
  delete contentVideo.dataset.stormcloudAdPlaying;
1395
1554
  }
1396
1555
  }
1397
- function resetQuartileTracking() {
1556
+ function resetQuartileTracking(preserveImpression) {
1398
1557
  trackingFired = {
1399
- impression: false,
1558
+ impression: preserveImpression ? trackingFired.impression : false,
1400
1559
  start: false,
1401
1560
  firstQuartile: false,
1402
1561
  midpoint: false,
@@ -1404,27 +1563,6 @@ function createHlsAdPlayer(contentVideo, options) {
1404
1563
  complete: false
1405
1564
  };
1406
1565
  }
1407
- function markCreativeRendered(reason) {
1408
- if (!currentAd || trackingFired.impression) return;
1409
- trackingFired.impression = true;
1410
- fireTrackingPixels(currentAd.trackingUrls.impression);
1411
- console.log("[HlsAdPlayer] Impression (pod ".concat(podIndex + 1, "/").concat(podAds.length || 1, ", reason: ").concat(reason, "): ").concat(currentAd.title));
1412
- emit("ad_impression", {
1413
- adId: currentAd.id,
1414
- title: currentAd.title,
1415
- durationSeconds: currentAd.duration,
1416
- mediaUrl: currentAdMediaUrl,
1417
- podIndex: podIndex,
1418
- podSize: podAds.length || 1,
1419
- reason: reason
1420
- });
1421
- }
1422
- function canDetectBlackFrames() {
1423
- if (!adVideoElement) return false;
1424
- if (requiresMediaPipelineResetAfterAds()) return false;
1425
- if (isRvfcSupported(adVideoElement)) return true;
1426
- return getDecodedVideoFrameCount(adVideoElement) !== void 0;
1427
- }
1428
1566
  function isRvfcSupported(video) {
1429
1567
  return typeof video.requestVideoFrameCallback === "function";
1430
1568
  }
@@ -1487,7 +1625,7 @@ function createHlsAdPlayer(contentVideo, options) {
1487
1625
  if (!mediaFile) {
1488
1626
  throw new Error("No media file available for ad");
1489
1627
  }
1490
- currentAdMediaUrl = mediaFile.url;
1628
+ currentAdMediaFile = mediaFile;
1491
1629
  var isHlsAd = isHlsMediaFile(mediaFile);
1492
1630
  console.log("[HlsAdPlayer] Loading ".concat(isHlsAd ? "HLS" : "progressive", " ad (pod ").concat(podIndex + 1, "/").concat(podAds.length || 1, ") from: ").concat(mediaFile.url));
1493
1631
  if (isHlsAd && import_hls.default.isSupported()) {
@@ -1531,9 +1669,11 @@ function createHlsAdPlayer(contentVideo, options) {
1531
1669
  }
1532
1670
  podIndex++;
1533
1671
  currentAd = podAds[podIndex];
1534
- currentAdMediaUrl = void 0;
1535
- resetQuartileTracking();
1672
+ currentAdMediaFile = void 0;
1673
+ resetQuartileTracking(false);
1536
1674
  if (currentAd) {
1675
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1676
+ trackingFired.impression = true;
1537
1677
  console.log("[HlsAdPlayer] Advancing to next pod ad (".concat(podIndex + 1, "/").concat(podAds.length, "): ").concat(currentAd.title, ", duration: ").concat(currentAd.duration, "s"));
1538
1678
  }
1539
1679
  clearStallWatchdog();
@@ -1572,7 +1712,9 @@ function createHlsAdPlayer(contentVideo, options) {
1572
1712
  console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1573
1713
  clearStallWatchdog();
1574
1714
  if (currentAd) {
1575
- fireTrackingPixels(currentAd.trackingUrls.error);
1715
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1716
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1717
+ });
1576
1718
  }
1577
1719
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1578
1720
  return;
@@ -1628,7 +1770,6 @@ function createHlsAdPlayer(contentVideo, options) {
1628
1770
  if (!adPlaying) return;
1629
1771
  sawDecodedVideoFrame = true;
1630
1772
  markRvfcReliable();
1631
- markCreativeRendered("first_presented_frame");
1632
1773
  };
1633
1774
  try {
1634
1775
  videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
@@ -1641,7 +1782,9 @@ function createHlsAdPlayer(contentVideo, options) {
1641
1782
  console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1642
1783
  clearStallWatchdog();
1643
1784
  if (currentAd) {
1644
- fireTrackingPixels(currentAd.trackingUrls.error);
1785
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1786
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1787
+ });
1645
1788
  }
1646
1789
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1647
1790
  return;
@@ -1663,7 +1806,6 @@ function createHlsAdPlayer(contentVideo, options) {
1663
1806
  if (decodedFrames !== void 0 && decodedFrames > 0) {
1664
1807
  sawDecodedVideoFrame = true;
1665
1808
  markFrameCounterReliable();
1666
- markCreativeRendered("decoded_frames");
1667
1809
  } else if (adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1668
1810
  var timeAdvancing = adVideoElement.currentTime > lastAdProgressPosition + 0.05;
1669
1811
  if (isRvfcSupported(adVideoElement) && timeAdvancing && isRvfcReliable()) {
@@ -1676,9 +1818,6 @@ function createHlsAdPlayer(contentVideo, options) {
1676
1818
  }
1677
1819
  }
1678
1820
  }
1679
- if (!trackingFired.impression && !adVideoElement.paused && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && (sawDecodedVideoFrame || !canDetectBlackFrames())) {
1680
- markCreativeRendered(sawDecodedVideoFrame ? "decoded_frames" : "sustained_playback");
1681
- }
1682
1821
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1683
1822
  noteAdProgress();
1684
1823
  return;
@@ -1779,7 +1918,6 @@ function createHlsAdPlayer(contentVideo, options) {
1779
1918
  ,
1780
1919
  6
1781
1920
  ]);
1782
- sessionId = generateSessionId();
1783
1921
  ads = [];
1784
1922
  if (!preloadedAds.has(vastTagUrl)) return [
1785
1923
  3,
@@ -1817,9 +1955,12 @@ function createHlsAdPlayer(contentVideo, options) {
1817
1955
  podAds = ads;
1818
1956
  podIndex = 0;
1819
1957
  currentAd = ads[0];
1820
- currentAdMediaUrl = void 0;
1821
- resetQuartileTracking();
1958
+ currentAdMediaFile = void 0;
1822
1959
  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"));
1960
+ if (currentAd) {
1961
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1962
+ trackingFired.impression = true;
1963
+ }
1823
1964
  return [
1824
1965
  2,
1825
1966
  Promise.resolve()
@@ -1859,7 +2000,7 @@ function createHlsAdPlayer(contentVideo, options) {
1859
2000
  contentVideo[AD_VIDEO_PROP] = adVideoElement;
1860
2001
  }
1861
2002
  setupAdEventListeners();
1862
- resetQuartileTracking();
2003
+ resetQuartileTracking(true);
1863
2004
  contentVolume = contentVideo.volume;
1864
2005
  originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1865
2006
  if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
@@ -1949,7 +2090,7 @@ function createHlsAdPlayer(contentVideo, options) {
1949
2090
  }
1950
2091
  }
1951
2092
  currentAd = void 0;
1952
- currentAdMediaUrl = void 0;
2093
+ currentAdMediaFile = void 0;
1953
2094
  podAds = [];
1954
2095
  podIndex = 0;
1955
2096
  return [
@@ -1990,6 +2131,7 @@ function createHlsAdPlayer(contentVideo, options) {
1990
2131
  teardownAdEventListeners();
1991
2132
  releaseAdDecoder();
1992
2133
  currentAd = void 0;
2134
+ currentAdMediaFile = void 0;
1993
2135
  podAds = [];
1994
2136
  podIndex = 0;
1995
2137
  listeners.clear();
@@ -2313,163 +2455,83 @@ function createPalNonceManager() {
2313
2455
  }
2314
2456
  };
2315
2457
  }
2316
- // src/utils/mqttConfig.ts
2317
- var DEFAULT_MQTT_CONFIG = {
2458
+ // src/utils/trackingIngest.ts
2459
+ var DEFAULT_TRACKING_INGEST_CONFIG = {
2318
2460
  enabled: true,
2319
- brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2320
- brokerPort: 8883,
2321
- wsPort: 8084,
2322
- username: "for-sonifi",
2323
- password: "sonifi-mqtt",
2324
- topicPrefix: "adstorm/players",
2325
- qos: 1
2461
+ baseUrl: "https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking"
2326
2462
  };
2327
- var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2328
- function applyMQTTConfig(overrides) {
2329
- Object.assign(mqttConfig, overrides);
2330
- }
2331
- function isMQTTEnabled() {
2332
- return mqttConfig.enabled;
2333
- }
2334
- function buildMQTTBrokerUrl() {
2335
- if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2336
- return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2337
- }
2338
- function buildPlayerTopic(licenseKey, channel) {
2339
- return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2340
- }
2341
- // src/utils/mqttClient.ts
2342
- var import_mqtt = __toESM(require("mqtt"), 1);
2343
- var LOG = "[StormcloudVideoPlayer][MQTT]";
2344
- var KEEPALIVE_SECONDS = 30;
2345
- var CONNECT_TIMEOUT_MS = 1e4;
2346
- var RECONNECT_BASE_MS = 5e3;
2347
- var RECONNECT_MAX_MS = 6e4;
2348
- var MAX_RECONNECT_ATTEMPTS = 8;
2349
- var COOLDOWN_MS = 10 * 6e4;
2350
- var client = null;
2351
- var status = "disconnected";
2352
- var reconnectAttempts = 0;
2353
- var reconnectTimer = null;
2354
- var stopped = false;
2355
- function clearReconnectTimer() {
2356
- if (reconnectTimer) {
2357
- clearTimeout(reconnectTimer);
2358
- reconnectTimer = null;
2359
- }
2360
- }
2361
- function teardownClient() {
2362
- if (client) {
2363
- try {
2364
- client.removeAllListeners();
2365
- client.end(true);
2366
- } catch (unused) {}
2367
- client = null;
2368
- }
2369
- }
2370
- function scheduleReconnect() {
2371
- if (stopped || !isMQTTEnabled() || reconnectTimer) return;
2372
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
2373
- console.warn("".concat(LOG, " giving up after ").concat(reconnectAttempts, " attempts; cooling down for ").concat(COOLDOWN_MS / 6e4, "m"));
2374
- teardownClient();
2375
- status = "disconnected";
2376
- reconnectTimer = setTimeout(function() {
2377
- reconnectTimer = null;
2378
- reconnectAttempts = 0;
2379
- openConnection();
2380
- }, COOLDOWN_MS);
2381
- return;
2382
- }
2383
- var delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts));
2384
- var jitter = Math.floor(Math.random() * 1e3);
2385
- reconnectAttempts += 1;
2386
- reconnectTimer = setTimeout(function() {
2387
- reconnectTimer = null;
2388
- openConnection();
2389
- }, delay + jitter);
2390
- }
2391
- function openConnection() {
2392
- if (stopped || !isMQTTEnabled()) return;
2393
- teardownClient();
2394
- var url = buildMQTTBrokerUrl();
2395
- status = "connecting";
2396
- var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2397
- try {
2398
- client = import_mqtt.default.connect(url, {
2399
- clientId: clientId,
2400
- username: mqttConfig.username,
2401
- password: mqttConfig.password,
2402
- keepalive: KEEPALIVE_SECONDS,
2403
- clean: true,
2404
- // Disable MQTT.js internal reconnect; we manage reconnection ourselves.
2405
- reconnectPeriod: 0,
2406
- connectTimeout: CONNECT_TIMEOUT_MS,
2407
- queueQoSZero: false,
2408
- reschedulePings: true,
2409
- // Root-cause fix for the Tizen 4.0 "Keepalive timeout" loop: MQTT.js's
2410
- // default timerVariant "auto" runs the keepalive interval inside a
2411
- // Blob-URL Web Worker (via worker-timers). On Samsung Tizen 4.0 (old
2412
- // WebKit) served from a file:// origin that worker can't tick reliably,
2413
- // so the keepalive counter trips onKeepaliveTimeout almost immediately
2414
- // after connect. Force the native setInterval timer instead; the main
2415
- // thread is free because video is decoded by the native AVPlay pipeline.
2416
- timerVariant: "native"
2417
- });
2418
- } catch (err) {
2419
- status = "error";
2420
- console.warn("".concat(LOG, " connect() threw:"), err);
2421
- scheduleReconnect();
2422
- return;
2423
- }
2424
- client.on("connect", function() {
2425
- status = "connected";
2426
- reconnectAttempts = 0;
2427
- clearReconnectTimer();
2428
- console.info("".concat(LOG, " connected to ").concat(url));
2429
- });
2430
- client.on("offline", function() {
2431
- if (status === "connected") status = "disconnected";
2432
- console.warn("".concat(LOG, " offline"));
2433
- scheduleReconnect();
2434
- });
2435
- client.on("error", function(err) {
2436
- status = "error";
2437
- console.warn("".concat(LOG, " error:"), err.message);
2438
- scheduleReconnect();
2439
- });
2440
- client.on("close", function() {
2441
- if (status === "connected") status = "disconnected";
2442
- scheduleReconnect();
2443
- });
2463
+ var trackingIngestConfig = _object_spread({}, DEFAULT_TRACKING_INGEST_CONFIG);
2464
+ function isTrackingIngestEnabled() {
2465
+ return trackingIngestConfig.enabled === true;
2444
2466
  }
2445
- function initMQTTClient() {
2446
- if (client || !isMQTTEnabled()) return;
2447
- stopped = false;
2448
- reconnectAttempts = 0;
2449
- openConnection();
2467
+ function normalizeIngestBaseUrl(baseUrl) {
2468
+ return baseUrl.replace(/\/+$/, "");
2450
2469
  }
2451
- function ensureMQTTClient() {
2452
- if (isMQTTEnabled() && !client && !reconnectTimer) {
2453
- initMQTTClient();
2470
+ function buildTrackingIngestUrl(channel) {
2471
+ var base = normalizeIngestBaseUrl(trackingIngestConfig.baseUrl);
2472
+ if (channel === "impressions") {
2473
+ return "".concat(base, "/impressions/ingest");
2454
2474
  }
2475
+ return "".concat(base, "/metrics/ingest");
2455
2476
  }
2456
- function publishMQTT(topic, payload) {
2457
- if (!isMQTTEnabled()) {
2458
- return false;
2459
- }
2460
- ensureMQTTClient();
2461
- if (!client || !client.connected) {
2462
- return false;
2463
- }
2464
- try {
2465
- client.publish(topic, JSON.stringify(payload), {
2466
- qos: mqttConfig.qos
2477
+ function ingestPlayerTracking(licenseKey, channel, body) {
2478
+ return _async_to_generator(function() {
2479
+ var response, error;
2480
+ return _ts_generator(this, function(_state) {
2481
+ switch(_state.label){
2482
+ case 0:
2483
+ if (!isTrackingIngestEnabled() || !licenseKey) {
2484
+ return [
2485
+ 2,
2486
+ false
2487
+ ];
2488
+ }
2489
+ _state.label = 1;
2490
+ case 1:
2491
+ _state.trys.push([
2492
+ 1,
2493
+ 3,
2494
+ ,
2495
+ 4
2496
+ ]);
2497
+ return [
2498
+ 4,
2499
+ fetch(buildTrackingIngestUrl(channel), {
2500
+ method: "POST",
2501
+ headers: {
2502
+ "Content-Type": "application/json",
2503
+ Authorization: "License-Key ".concat(licenseKey)
2504
+ },
2505
+ body: JSON.stringify(body)
2506
+ })
2507
+ ];
2508
+ case 2:
2509
+ response = _state.sent();
2510
+ if (!response.ok) {
2511
+ console.warn("[StormcloudVideoPlayer] Tracking ingest failed (".concat(response.status, ") for ").concat(channel));
2512
+ return [
2513
+ 2,
2514
+ false
2515
+ ];
2516
+ }
2517
+ return [
2518
+ 2,
2519
+ true
2520
+ ];
2521
+ case 3:
2522
+ error = _state.sent();
2523
+ console.warn("[StormcloudVideoPlayer] Tracking ingest error for ".concat(channel, ":"), error);
2524
+ return [
2525
+ 2,
2526
+ false
2527
+ ];
2528
+ case 4:
2529
+ return [
2530
+ 2
2531
+ ];
2532
+ }
2467
2533
  });
2468
- return true;
2469
- } catch (err) {
2470
- console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2471
- return false;
2472
- }
2534
+ })();
2473
2535
  }
2474
2536
  // src/utils/vastEnvironmentSignals.ts
2475
2537
  var AIRY_ANDROID_APP_ID = "com.freeairytv.android";
@@ -3240,7 +3302,7 @@ function getBrowserID(clientInfo) {
3240
3302
  })();
3241
3303
  }
3242
3304
  function canPublish(licenseKey) {
3243
- return Boolean(isMQTTEnabled() && licenseKey);
3305
+ return Boolean(isTrackingIngestEnabled() && licenseKey);
3244
3306
  }
3245
3307
  function buildPlayerMetricEvent() {
3246
3308
  return _async_to_generator(function() {
@@ -3282,21 +3344,6 @@ function buildPlayerMetricEvent() {
3282
3344
  });
3283
3345
  }).apply(this, arguments);
3284
3346
  }
3285
- function publishTracking(licenseKey, channel, body) {
3286
- var debug = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
3287
- ensureMQTTClient();
3288
- var topic = buildPlayerTopic(licenseKey, channel);
3289
- var published = publishMQTT(topic, body);
3290
- if (debug) {
3291
- console.log("[StormcloudVideoPlayer] publishTracking:", JSON.stringify({
3292
- licenseKey: licenseKey,
3293
- channel: channel,
3294
- topic: topic,
3295
- body: body,
3296
- published: published
3297
- }, null, 2));
3298
- }
3299
- }
3300
3347
  function sendInitialTracking(_0) {
3301
3348
  return _async_to_generator(function(licenseKey) {
3302
3349
  var context, metricEvent, error;
@@ -3312,9 +3359,9 @@ function sendInitialTracking(_0) {
3312
3359
  case 1:
3313
3360
  _state.trys.push([
3314
3361
  1,
3315
- 3,
3362
+ 4,
3316
3363
  ,
3317
- 4
3364
+ 5
3318
3365
  ]);
3319
3366
  return [
3320
3367
  4,
@@ -3325,23 +3372,28 @@ function sendInitialTracking(_0) {
3325
3372
  ];
3326
3373
  case 2:
3327
3374
  metricEvent = _state.sent();
3328
- publishTracking(licenseKey, "metrics", {
3329
- events: [
3330
- metricEvent
3331
- ]
3332
- }, !!context.debugAdTiming);
3333
3375
  return [
3334
- 3,
3335
- 4
3376
+ 4,
3377
+ ingestPlayerTracking(licenseKey, "metrics", {
3378
+ events: [
3379
+ metricEvent
3380
+ ]
3381
+ })
3336
3382
  ];
3337
3383
  case 3:
3384
+ _state.sent();
3385
+ return [
3386
+ 3,
3387
+ 5
3388
+ ];
3389
+ case 4:
3338
3390
  error = _state.sent();
3339
3391
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
3340
3392
  return [
3341
3393
  3,
3342
- 4
3394
+ 5
3343
3395
  ];
3344
- case 4:
3396
+ case 5:
3345
3397
  return [
3346
3398
  2
3347
3399
  ];
@@ -3439,7 +3491,7 @@ function sendAdLoadedTracking(_0, _1) {
3439
3491
  }
3440
3492
  function sendAdImpressionTracking(_0, _1) {
3441
3493
  return _async_to_generator(function(licenseKey, adImpressionInfo) {
3442
- var context, _adImpressionInfo_adUrl, metricEvent, error;
3494
+ var context, metricEvent, error;
3443
3495
  var _arguments = arguments;
3444
3496
  return _ts_generator(this, function(_state) {
3445
3497
  switch(_state.label){
@@ -3452,9 +3504,9 @@ function sendAdImpressionTracking(_0, _1) {
3452
3504
  case 1:
3453
3505
  _state.trys.push([
3454
3506
  1,
3455
- 3,
3507
+ 5,
3456
3508
  ,
3457
- 4
3509
+ 6
3458
3510
  ]);
3459
3511
  return [
3460
3512
  4,
@@ -3464,30 +3516,43 @@ function sendAdImpressionTracking(_0, _1) {
3464
3516
  ];
3465
3517
  case 2:
3466
3518
  metricEvent = _state.sent();
3467
- publishTracking(licenseKey, "heartbeat", metricEvent, !!context.debugAdTiming);
3468
- publishTracking(licenseKey, "impressions", {
3469
- events: [
3470
- {
3471
- event_id: createUuid(),
3472
- player_id: metricEvent.player_id,
3473
- ad_played_count: 1,
3474
- ad_url: (_adImpressionInfo_adUrl = adImpressionInfo.adUrl) !== null && _adImpressionInfo_adUrl !== void 0 ? _adImpressionInfo_adUrl : adImpressionInfo.adId,
3475
- capture_at: adImpressionInfo.timestamp
3476
- }
3477
- ]
3478
- }, !!context.debugAdTiming);
3479
3519
  return [
3480
- 3,
3481
- 4
3520
+ 4,
3521
+ ingestPlayerTracking(licenseKey, "metrics", {
3522
+ events: [
3523
+ metricEvent
3524
+ ]
3525
+ })
3482
3526
  ];
3483
3527
  case 3:
3528
+ _state.sent();
3529
+ return [
3530
+ 4,
3531
+ ingestPlayerTracking(licenseKey, "impressions", {
3532
+ events: [
3533
+ {
3534
+ player_id: metricEvent.player_id,
3535
+ ad_played_count: 1,
3536
+ ad_url: adImpressionInfo.adUrl,
3537
+ capture_at: adImpressionInfo.timestamp
3538
+ }
3539
+ ]
3540
+ })
3541
+ ];
3542
+ case 4:
3543
+ _state.sent();
3544
+ return [
3545
+ 3,
3546
+ 6
3547
+ ];
3548
+ case 5:
3484
3549
  error = _state.sent();
3485
3550
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
3486
3551
  return [
3487
3552
  3,
3488
- 4
3553
+ 6
3489
3554
  ];
3490
- case 4:
3555
+ case 6:
3491
3556
  return [
3492
3557
  2
3493
3558
  ];
@@ -3510,9 +3575,9 @@ function sendHeartbeat(_0) {
3510
3575
  case 1:
3511
3576
  _state.trys.push([
3512
3577
  1,
3513
- 3,
3578
+ 4,
3514
3579
  ,
3515
- 4
3580
+ 5
3516
3581
  ]);
3517
3582
  return [
3518
3583
  4,
@@ -3520,19 +3585,28 @@ function sendHeartbeat(_0) {
3520
3585
  ];
3521
3586
  case 2:
3522
3587
  heartbeatData = _state.sent();
3523
- publishTracking(licenseKey, "heartbeat", heartbeatData, !!context.debugAdTiming);
3524
3588
  return [
3525
- 3,
3526
- 4
3589
+ 4,
3590
+ ingestPlayerTracking(licenseKey, "metrics", {
3591
+ events: [
3592
+ heartbeatData
3593
+ ]
3594
+ })
3527
3595
  ];
3528
3596
  case 3:
3597
+ _state.sent();
3598
+ return [
3599
+ 3,
3600
+ 5
3601
+ ];
3602
+ case 4:
3529
3603
  error = _state.sent();
3530
3604
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3531
3605
  return [
3532
3606
  3,
3533
- 4
3607
+ 5
3534
3608
  ];
3535
- case 4:
3609
+ case 5:
3536
3610
  return [
3537
3611
  2
3538
3612
  ];
@@ -5406,7 +5480,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5406
5480
  ];
5407
5481
  case 4:
5408
5482
  data = _state.sent();
5409
- this.applyMqttConfigFromResponse(data);
5410
5483
  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;
5411
5484
  if (imaPayload) {
5412
5485
  decodedPayload = imaPayload;
@@ -5440,20 +5513,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5440
5513
  }).call(this);
5441
5514
  }
5442
5515
  },
5443
- {
5444
- key: "applyMqttConfigFromResponse",
5445
- value: function applyMqttConfigFromResponse(data) {
5446
- var _data_response;
5447
- var mqttConfig2 = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : _data_response.mqtt_config;
5448
- if (!mqttConfig2 || (typeof mqttConfig2 === "undefined" ? "undefined" : _type_of(mqttConfig2)) !== "object") {
5449
- return;
5450
- }
5451
- applyMQTTConfig(mqttConfig2);
5452
- if (this.debug) {
5453
- console.log("[StormcloudVideoPlayer] Applied MQTT config from /ads/web:", mqttConfig2);
5454
- }
5455
- }
5456
- },
5457
5516
  {
5458
5517
  key: "fetchAndParseVmap",
5459
5518
  value: function fetchAndParseVmap(vmapUrl) {
@@ -7314,22 +7373,27 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7314
7373
  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 : "?"));
7315
7374
  }
7316
7375
  });
7317
- adPlayer.on("ad_impression", function(payload) {
7318
- var impressionInfo = {
7376
+ adPlayer.on("all_ads_completed", function() {
7377
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7319
7378
  source: "hls",
7320
7379
  adIndex: _this.timing.currentAdIndex,
7321
7380
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7322
- };
7323
- if (payload === null || payload === void 0 ? void 0 : payload.adId) impressionInfo.adId = payload.adId;
7324
- if (payload === null || payload === void 0 ? void 0 : payload.mediaUrl) impressionInfo.adUrl = payload.mediaUrl;
7325
- if ((payload === null || payload === void 0 ? void 0 : payload.durationSeconds) != null) {
7326
- impressionInfo.durationSeconds = payload.durationSeconds;
7381
+ }, _this.analyticsContext).catch(function() {});
7382
+ _this.timing.recordPlayedAdDuration(_this.host.getAdPlayer());
7383
+ var remaining = _this.timing.getRemainingAdMs();
7384
+ _this.timing.consecutiveFailures = 0;
7385
+ if (_this.debug) {
7386
+ console.log("[StormcloudVideoPlayer] all_ads_completed event received - remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
7387
+ queuedUrls: _to_consumable_array(_this.adRequestQueue)
7388
+ } : {});
7327
7389
  }
7328
- sendAdImpressionTracking(_this.host.config.licenseKey, impressionInfo, _this.analyticsContext).catch(function(error) {
7329
- if (_this.debug) {
7330
- console.warn("[AdBreakOrchestrator] Failed to send ad impression tracking:", error);
7390
+ if (_this.inAdBreak) {
7391
+ if (_this.timing.hasTimeToStartAnotherAd()) {
7392
+ _this.tryNextAvailableAdWithRateLimit();
7393
+ } else {
7394
+ _this.handleAdPodComplete();
7331
7395
  }
7332
- });
7396
+ }
7333
7397
  });
7334
7398
  adPlayer.on("ad_error", function(errorPayload) {
7335
7399
  var errorMessage = "Ad playback failed";
@@ -7372,6 +7436,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7372
7436
  }
7373
7437
  });
7374
7438
  adPlayer.on("content_resume", function() {
7439
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7440
+ source: "hls",
7441
+ adIndex: _this.timing.currentAdIndex,
7442
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7443
+ }, _this.analyticsContext).catch(function() {});
7375
7444
  if (!_this.host.video.muted) {
7376
7445
  _this.host.video.muted = true;
7377
7446
  if (_this.debug) {