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
@@ -567,15 +567,147 @@ function logBrowserInfo() {
567
567
  userAgent: navigator.userAgent
568
568
  }));
569
569
  }
570
+ // src/utils/vastTrackingMacros.ts
571
+ var VAST_MACRO_PATTERN = /\[([A-Z][A-Z0-9_]*)\]/g;
572
+ var CACHEBUSTING_ALIASES = /\[CACHEBUSTER\]|\[cachebuster\]|\[timestamp\]|\[random\]|\[INSERT_RANDOM_NUMBER\]|%%CACHEBUSTER%%|%%CACHEBUST%%|\$\{CACHEBUSTER\}|\$\{CACHEBUSTING\}|\{cachebuster\}|\{random\}/g;
573
+ var KNOWN_VAST_MACROS = /* @__PURE__ */ new Set([
574
+ "ADCATEGORIES",
575
+ "ADCOUNT",
576
+ "ADPLAYHEAD",
577
+ "ADSERVINGID",
578
+ "ADTYPE",
579
+ "APIFRAMEWORKS",
580
+ "APPBUNDLE",
581
+ "ASSETURI",
582
+ "BLOCKEDADCATEGORIES",
583
+ "BREAKMAXADLENGTH",
584
+ "BREAKMAXADS",
585
+ "BREAKMAXDURATION",
586
+ "BREAKMINADLENGTH",
587
+ "BREAKPOSITION",
588
+ "CACHEBUSTING",
589
+ "CLICKPOS",
590
+ "CLICKTYPE",
591
+ "CLIENTUA",
592
+ "CONTENTID",
593
+ "CONTENTPLAYHEAD",
594
+ "CONTENTURI",
595
+ "DEVICEIP",
596
+ "DEVICEUA",
597
+ "DOMAIN",
598
+ "ERRORCODE",
599
+ "EXTENSIONS",
600
+ "GDPRCONSENT",
601
+ "IFA",
602
+ "IFATYPE",
603
+ "INVENTORYSTATE",
604
+ "LATLONG",
605
+ "LIMITADTRACKING",
606
+ "MEDIAMIME",
607
+ "MEDIAPLAYHEAD",
608
+ "OMIDPARTNER",
609
+ "PAGEURL",
610
+ "PLACEMENTTYPE",
611
+ "PLAYERCAPABILITIES",
612
+ "PLAYERSIZE",
613
+ "PLAYERSTATE",
614
+ "PODSEQUENCE",
615
+ "REGULATIONS",
616
+ "SERVERSIDE",
617
+ "SERVERUA",
618
+ "TIMESTAMP",
619
+ "TRANSACTIONID",
620
+ "UNIVERSALADID",
621
+ "VASTVERSIONS",
622
+ "VERIFICATIONVENDORS"
623
+ ]);
624
+ function generateCachebuster() {
625
+ return String(Math.floor(Math.random() * 9e7) + 1e7);
626
+ }
627
+ function formatPlayhead(seconds) {
628
+ if (seconds == null || !Number.isFinite(seconds) || seconds < 0) {
629
+ return void 0;
630
+ }
631
+ var total = Math.floor(seconds);
632
+ var hh = String(Math.floor(total / 3600)).padStart(2, "0");
633
+ var mm = String(Math.floor(total % 3600 / 60)).padStart(2, "0");
634
+ var ss = String(total % 60).padStart(2, "0");
635
+ var ms = String(Math.floor((seconds - total) * 1e3)).padStart(3, "0");
636
+ return "".concat(hh, ":").concat(mm, ":").concat(ss, ".").concat(ms);
637
+ }
638
+ function resolveMacroValues(ctx) {
639
+ var values = /* @__PURE__ */ new Map();
640
+ var set = function set(key, value) {
641
+ if (value != null && value !== "") values.set(key, value);
642
+ };
643
+ var adPlayhead = formatPlayhead(ctx.adPlayheadSeconds);
644
+ set("ADPLAYHEAD", adPlayhead);
645
+ set("MEDIAPLAYHEAD", adPlayhead);
646
+ set("CONTENTPLAYHEAD", formatPlayhead(ctx.contentPlayheadSeconds));
647
+ set("ASSETURI", ctx.assetUri);
648
+ set("MEDIAMIME", ctx.mediaMime);
649
+ set("TIMESTAMP", (/* @__PURE__ */ new Date()).toISOString());
650
+ set("SERVERSIDE", "0");
651
+ if (ctx.errorCode != null && ctx.errorCode !== "") {
652
+ set("ERRORCODE", String(ctx.errorCode));
653
+ }
654
+ if (ctx.playerWidth != null && ctx.playerHeight != null) {
655
+ set("PLAYERSIZE", "".concat(Math.round(ctx.playerWidth), ",").concat(Math.round(ctx.playerHeight)));
656
+ }
657
+ set("IFA", ctx.deviceId);
658
+ set("IFATYPE", ctx.deviceIdType);
659
+ if (ctx.limitAdTracking != null) {
660
+ set("LIMITADTRACKING", ctx.limitAdTracking ? "1" : "0");
661
+ }
662
+ set("PAGEURL", ctx.pageUrl);
663
+ if (ctx.pageUrl) {
664
+ try {
665
+ set("DOMAIN", new URL(ctx.pageUrl).hostname);
666
+ } catch (unused) {}
667
+ }
668
+ set("CLIENTUA", ctx.clientUa);
669
+ set("DEVICEUA", ctx.clientUa);
670
+ if (ctx.podSequence != null) {
671
+ set("PODSEQUENCE", String(ctx.podSequence));
672
+ }
673
+ return values;
674
+ }
675
+ function substituteVastTrackingMacros(url) {
676
+ var ctx = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
677
+ if (!url) return url;
678
+ var values = resolveMacroValues(ctx);
679
+ return url.replace(CACHEBUSTING_ALIASES, function() {
680
+ return generateCachebuster();
681
+ }).replace(VAST_MACRO_PATTERN, function(match, name) {
682
+ if (name === "CACHEBUSTING") return generateCachebuster();
683
+ var value = values.get(name);
684
+ if (value != null) return encodeURIComponent(value);
685
+ return KNOWN_VAST_MACROS.has(name) ? "" : match;
686
+ });
687
+ }
570
688
  // src/sdk/hlsAdPlayer.ts
571
689
  var MAX_VAST_WRAPPER_DEPTH = 5;
690
+ var BEACON_TIMEOUT_MS = 3e4;
691
+ var inFlightBeacons = /* @__PURE__ */ new Set();
692
+ var VAST_ERROR_MEDIA_DISPLAY = 405;
693
+ function toVastMediaErrorCode(error) {
694
+ switch(error === null || error === void 0 ? void 0 : error.code){
695
+ case 1:
696
+ return 400;
697
+ case 2:
698
+ return 401;
699
+ case 4:
700
+ return 403;
701
+ default:
702
+ return VAST_ERROR_MEDIA_DISPLAY;
703
+ }
704
+ }
572
705
  function createHlsAdPlayer(contentVideo, options) {
573
706
  var _contentVideo_AD_VIDEO_PROP, _contentVideo_AD_CONTAINER_PROP;
574
707
  var adPlaying = false;
575
708
  var originalMutedState = false;
576
709
  var originalVolume = Math.max(0, Math.min(1, contentVideo.volume || 1));
577
710
  var listeners = /* @__PURE__ */ new Map();
578
- var licenseKey = options === null || options === void 0 ? void 0 : options.licenseKey;
579
711
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
580
712
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
581
713
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
@@ -586,10 +718,9 @@ function createHlsAdPlayer(contentVideo, options) {
586
718
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
587
719
  var adEventHandlers = [];
588
720
  var currentAd;
589
- var currentAdMediaUrl;
721
+ var currentAdMediaFile;
590
722
  var podAds = [];
591
723
  var podIndex = 0;
592
- var sessionId;
593
724
  var preloadedAds = /* @__PURE__ */ new Map();
594
725
  var preloadingAds = /* @__PURE__ */ new Map();
595
726
  var destroyed = false;
@@ -639,21 +770,47 @@ function createHlsAdPlayer(contentVideo, options) {
639
770
  }
640
771
  }
641
772
  }
642
- function generateSessionId() {
643
- return "session-".concat(Date.now(), "-").concat(Math.random().toString(36).substr(2, 9));
773
+ function buildTrackingMacroContext(errorCode) {
774
+ var context = {
775
+ adPlayheadSeconds: adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.currentTime,
776
+ contentPlayheadSeconds: contentVideo.currentTime,
777
+ assetUri: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.url,
778
+ mediaMime: currentAdMediaFile === null || currentAdMediaFile === void 0 ? void 0 : currentAdMediaFile.type,
779
+ playerWidth: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientWidth) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientWidth),
780
+ playerHeight: (adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.clientHeight) || (adContainerEl === null || adContainerEl === void 0 ? void 0 : adContainerEl.clientHeight)
781
+ };
782
+ if (errorCode != null) {
783
+ context.errorCode = errorCode;
784
+ }
785
+ if (typeof navigator !== "undefined" && navigator.userAgent) {
786
+ context.clientUa = navigator.userAgent;
787
+ }
788
+ if (typeof location !== "undefined" && location.href) {
789
+ context.pageUrl = location.href;
790
+ }
791
+ if (podAds.length > 0) {
792
+ context.podSequence = podIndex + 1;
793
+ }
794
+ return context;
644
795
  }
645
- function fireTrackingPixels(urls) {
796
+ function fireTrackingPixels(urls, options2) {
646
797
  if (!urls || urls.length === 0) return;
798
+ var macroContext = buildTrackingMacroContext(options2 === null || options2 === void 0 ? void 0 : options2.errorCode);
647
799
  urls.forEach(function(url) {
648
800
  try {
649
- var trackingUrl = url;
650
- if (sessionId) {
651
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "session_id=").concat(sessionId);
652
- }
653
- if (licenseKey) {
654
- trackingUrl = "".concat(trackingUrl).concat(trackingUrl.includes("?") ? "&" : "?", "license_key=").concat(licenseKey);
655
- }
801
+ var trackingUrl = substituteVastTrackingMacros(url, macroContext);
656
802
  var img = new Image(1, 1);
803
+ inFlightBeacons.add(img);
804
+ var timeoutId;
805
+ var release = function release() {
806
+ if (timeoutId !== void 0) clearTimeout(timeoutId);
807
+ img.onload = null;
808
+ img.onerror = null;
809
+ inFlightBeacons.delete(img);
810
+ };
811
+ img.onload = release;
812
+ img.onerror = release;
813
+ timeoutId = setTimeout(release, BEACON_TIMEOUT_MS);
657
814
  img.src = trackingUrl;
658
815
  console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
659
816
  } catch (error) {
@@ -1292,7 +1449,9 @@ function createHlsAdPlayer(contentVideo, options) {
1292
1449
  if (!adPlaying) return;
1293
1450
  console.error("[HlsAdPlayer] Ad video error:", e);
1294
1451
  if (currentAd) {
1295
- fireTrackingPixels(currentAd.trackingUrls.error);
1452
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1453
+ errorCode: toVastMediaErrorCode(adVideoElement === null || adVideoElement === void 0 ? void 0 : adVideoElement.error)
1454
+ });
1296
1455
  }
1297
1456
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1298
1457
  return;
@@ -1404,9 +1563,9 @@ function createHlsAdPlayer(contentVideo, options) {
1404
1563
  delete contentVideo.dataset.stormcloudAdPlaying;
1405
1564
  }
1406
1565
  }
1407
- function resetQuartileTracking() {
1566
+ function resetQuartileTracking(preserveImpression) {
1408
1567
  trackingFired = {
1409
- impression: false,
1568
+ impression: preserveImpression ? trackingFired.impression : false,
1410
1569
  start: false,
1411
1570
  firstQuartile: false,
1412
1571
  midpoint: false,
@@ -1414,27 +1573,6 @@ function createHlsAdPlayer(contentVideo, options) {
1414
1573
  complete: false
1415
1574
  };
1416
1575
  }
1417
- function markCreativeRendered(reason) {
1418
- if (!currentAd || trackingFired.impression) return;
1419
- trackingFired.impression = true;
1420
- fireTrackingPixels(currentAd.trackingUrls.impression);
1421
- console.log("[HlsAdPlayer] Impression (pod ".concat(podIndex + 1, "/").concat(podAds.length || 1, ", reason: ").concat(reason, "): ").concat(currentAd.title));
1422
- emit("ad_impression", {
1423
- adId: currentAd.id,
1424
- title: currentAd.title,
1425
- durationSeconds: currentAd.duration,
1426
- mediaUrl: currentAdMediaUrl,
1427
- podIndex: podIndex,
1428
- podSize: podAds.length || 1,
1429
- reason: reason
1430
- });
1431
- }
1432
- function canDetectBlackFrames() {
1433
- if (!adVideoElement) return false;
1434
- if (requiresMediaPipelineResetAfterAds()) return false;
1435
- if (isRvfcSupported(adVideoElement)) return true;
1436
- return getDecodedVideoFrameCount(adVideoElement) !== void 0;
1437
- }
1438
1576
  function isRvfcSupported(video) {
1439
1577
  return typeof video.requestVideoFrameCallback === "function";
1440
1578
  }
@@ -1497,7 +1635,7 @@ function createHlsAdPlayer(contentVideo, options) {
1497
1635
  if (!mediaFile) {
1498
1636
  throw new Error("No media file available for ad");
1499
1637
  }
1500
- currentAdMediaUrl = mediaFile.url;
1638
+ currentAdMediaFile = mediaFile;
1501
1639
  var isHlsAd = isHlsMediaFile(mediaFile);
1502
1640
  console.log("[HlsAdPlayer] Loading ".concat(isHlsAd ? "HLS" : "progressive", " ad (pod ").concat(podIndex + 1, "/").concat(podAds.length || 1, ") from: ").concat(mediaFile.url));
1503
1641
  if (isHlsAd && import_hls.default.isSupported()) {
@@ -1541,9 +1679,11 @@ function createHlsAdPlayer(contentVideo, options) {
1541
1679
  }
1542
1680
  podIndex++;
1543
1681
  currentAd = podAds[podIndex];
1544
- currentAdMediaUrl = void 0;
1545
- resetQuartileTracking();
1682
+ currentAdMediaFile = void 0;
1683
+ resetQuartileTracking(false);
1546
1684
  if (currentAd) {
1685
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1686
+ trackingFired.impression = true;
1547
1687
  console.log("[HlsAdPlayer] Advancing to next pod ad (".concat(podIndex + 1, "/").concat(podAds.length, "): ").concat(currentAd.title, ", duration: ").concat(currentAd.duration, "s"));
1548
1688
  }
1549
1689
  clearStallWatchdog();
@@ -1582,7 +1722,9 @@ function createHlsAdPlayer(contentVideo, options) {
1582
1722
  console.warn("[HlsAdPlayer] Ad playback stalled (no progress) - recovering to avoid a blank frame");
1583
1723
  clearStallWatchdog();
1584
1724
  if (currentAd) {
1585
- fireTrackingPixels(currentAd.trackingUrls.error);
1725
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1726
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1727
+ });
1586
1728
  }
1587
1729
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1588
1730
  return;
@@ -1638,7 +1780,6 @@ function createHlsAdPlayer(contentVideo, options) {
1638
1780
  if (!adPlaying) return;
1639
1781
  sawDecodedVideoFrame = true;
1640
1782
  markRvfcReliable();
1641
- markCreativeRendered("first_presented_frame");
1642
1783
  };
1643
1784
  try {
1644
1785
  videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
@@ -1651,7 +1792,9 @@ function createHlsAdPlayer(contentVideo, options) {
1651
1792
  console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1652
1793
  clearStallWatchdog();
1653
1794
  if (currentAd) {
1654
- fireTrackingPixels(currentAd.trackingUrls.error);
1795
+ fireTrackingPixels(currentAd.trackingUrls.error, {
1796
+ errorCode: VAST_ERROR_MEDIA_DISPLAY
1797
+ });
1655
1798
  }
1656
1799
  if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
1657
1800
  return;
@@ -1673,7 +1816,6 @@ function createHlsAdPlayer(contentVideo, options) {
1673
1816
  if (decodedFrames !== void 0 && decodedFrames > 0) {
1674
1817
  sawDecodedVideoFrame = true;
1675
1818
  markFrameCounterReliable();
1676
- markCreativeRendered("decoded_frames");
1677
1819
  } else if (adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1678
1820
  var timeAdvancing = adVideoElement.currentTime > lastAdProgressPosition + 0.05;
1679
1821
  if (isRvfcSupported(adVideoElement) && timeAdvancing && isRvfcReliable()) {
@@ -1686,9 +1828,6 @@ function createHlsAdPlayer(contentVideo, options) {
1686
1828
  }
1687
1829
  }
1688
1830
  }
1689
- if (!trackingFired.impression && !adVideoElement.paused && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && (sawDecodedVideoFrame || !canDetectBlackFrames())) {
1690
- markCreativeRendered(sawDecodedVideoFrame ? "decoded_frames" : "sustained_playback");
1691
- }
1692
1831
  if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
1693
1832
  noteAdProgress();
1694
1833
  return;
@@ -1789,7 +1928,6 @@ function createHlsAdPlayer(contentVideo, options) {
1789
1928
  ,
1790
1929
  6
1791
1930
  ]);
1792
- sessionId = generateSessionId();
1793
1931
  ads = [];
1794
1932
  if (!preloadedAds.has(vastTagUrl)) return [
1795
1933
  3,
@@ -1827,9 +1965,12 @@ function createHlsAdPlayer(contentVideo, options) {
1827
1965
  podAds = ads;
1828
1966
  podIndex = 0;
1829
1967
  currentAd = ads[0];
1830
- currentAdMediaUrl = void 0;
1831
- resetQuartileTracking();
1968
+ currentAdMediaFile = void 0;
1832
1969
  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"));
1970
+ if (currentAd) {
1971
+ fireTrackingPixels(currentAd.trackingUrls.impression);
1972
+ trackingFired.impression = true;
1973
+ }
1833
1974
  return [
1834
1975
  2,
1835
1976
  Promise.resolve()
@@ -1869,7 +2010,7 @@ function createHlsAdPlayer(contentVideo, options) {
1869
2010
  contentVideo[AD_VIDEO_PROP] = adVideoElement;
1870
2011
  }
1871
2012
  setupAdEventListeners();
1872
- resetQuartileTracking();
2013
+ resetQuartileTracking(true);
1873
2014
  contentVolume = contentVideo.volume;
1874
2015
  originalVolume = Math.max(0, Math.min(1, contentVolume || originalVolume));
1875
2016
  if (!(options === null || options === void 0 ? void 0 : options.continueLiveStreamDuringAds)) {
@@ -1959,7 +2100,7 @@ function createHlsAdPlayer(contentVideo, options) {
1959
2100
  }
1960
2101
  }
1961
2102
  currentAd = void 0;
1962
- currentAdMediaUrl = void 0;
2103
+ currentAdMediaFile = void 0;
1963
2104
  podAds = [];
1964
2105
  podIndex = 0;
1965
2106
  return [
@@ -2000,6 +2141,7 @@ function createHlsAdPlayer(contentVideo, options) {
2000
2141
  teardownAdEventListeners();
2001
2142
  releaseAdDecoder();
2002
2143
  currentAd = void 0;
2144
+ currentAdMediaFile = void 0;
2003
2145
  podAds = [];
2004
2146
  podIndex = 0;
2005
2147
  listeners.clear();
@@ -2323,163 +2465,83 @@ function createPalNonceManager() {
2323
2465
  }
2324
2466
  };
2325
2467
  }
2326
- // src/utils/mqttConfig.ts
2327
- var DEFAULT_MQTT_CONFIG = {
2468
+ // src/utils/trackingIngest.ts
2469
+ var DEFAULT_TRACKING_INGEST_CONFIG = {
2328
2470
  enabled: true,
2329
- brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2330
- brokerPort: 8883,
2331
- wsPort: 8084,
2332
- username: "for-sonifi",
2333
- password: "sonifi-mqtt",
2334
- topicPrefix: "adstorm/players",
2335
- qos: 1
2471
+ baseUrl: "https://player-analytics-ingest-api-760490581852.us-central1.run.app/api/adstorm/player-tracking"
2336
2472
  };
2337
- var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2338
- function applyMQTTConfig(overrides) {
2339
- Object.assign(mqttConfig, overrides);
2340
- }
2341
- function isMQTTEnabled() {
2342
- return mqttConfig.enabled;
2343
- }
2344
- function buildMQTTBrokerUrl() {
2345
- if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2346
- return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2347
- }
2348
- function buildPlayerTopic(licenseKey, channel) {
2349
- return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2350
- }
2351
- // src/utils/mqttClient.ts
2352
- var import_mqtt = __toESM(require("mqtt"), 1);
2353
- var LOG = "[StormcloudVideoPlayer][MQTT]";
2354
- var KEEPALIVE_SECONDS = 30;
2355
- var CONNECT_TIMEOUT_MS = 1e4;
2356
- var RECONNECT_BASE_MS = 5e3;
2357
- var RECONNECT_MAX_MS = 6e4;
2358
- var MAX_RECONNECT_ATTEMPTS = 8;
2359
- var COOLDOWN_MS = 10 * 6e4;
2360
- var client = null;
2361
- var status = "disconnected";
2362
- var reconnectAttempts = 0;
2363
- var reconnectTimer = null;
2364
- var stopped = false;
2365
- function clearReconnectTimer() {
2366
- if (reconnectTimer) {
2367
- clearTimeout(reconnectTimer);
2368
- reconnectTimer = null;
2369
- }
2370
- }
2371
- function teardownClient() {
2372
- if (client) {
2373
- try {
2374
- client.removeAllListeners();
2375
- client.end(true);
2376
- } catch (unused) {}
2377
- client = null;
2378
- }
2379
- }
2380
- function scheduleReconnect() {
2381
- if (stopped || !isMQTTEnabled() || reconnectTimer) return;
2382
- if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
2383
- console.warn("".concat(LOG, " giving up after ").concat(reconnectAttempts, " attempts; cooling down for ").concat(COOLDOWN_MS / 6e4, "m"));
2384
- teardownClient();
2385
- status = "disconnected";
2386
- reconnectTimer = setTimeout(function() {
2387
- reconnectTimer = null;
2388
- reconnectAttempts = 0;
2389
- openConnection();
2390
- }, COOLDOWN_MS);
2391
- return;
2392
- }
2393
- var delay = Math.min(RECONNECT_MAX_MS, RECONNECT_BASE_MS * Math.pow(2, reconnectAttempts));
2394
- var jitter = Math.floor(Math.random() * 1e3);
2395
- reconnectAttempts += 1;
2396
- reconnectTimer = setTimeout(function() {
2397
- reconnectTimer = null;
2398
- openConnection();
2399
- }, delay + jitter);
2400
- }
2401
- function openConnection() {
2402
- if (stopped || !isMQTTEnabled()) return;
2403
- teardownClient();
2404
- var url = buildMQTTBrokerUrl();
2405
- status = "connecting";
2406
- var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2407
- try {
2408
- client = import_mqtt.default.connect(url, {
2409
- clientId: clientId,
2410
- username: mqttConfig.username,
2411
- password: mqttConfig.password,
2412
- keepalive: KEEPALIVE_SECONDS,
2413
- clean: true,
2414
- // Disable MQTT.js internal reconnect; we manage reconnection ourselves.
2415
- reconnectPeriod: 0,
2416
- connectTimeout: CONNECT_TIMEOUT_MS,
2417
- queueQoSZero: false,
2418
- reschedulePings: true,
2419
- // Root-cause fix for the Tizen 4.0 "Keepalive timeout" loop: MQTT.js's
2420
- // default timerVariant "auto" runs the keepalive interval inside a
2421
- // Blob-URL Web Worker (via worker-timers). On Samsung Tizen 4.0 (old
2422
- // WebKit) served from a file:// origin that worker can't tick reliably,
2423
- // so the keepalive counter trips onKeepaliveTimeout almost immediately
2424
- // after connect. Force the native setInterval timer instead; the main
2425
- // thread is free because video is decoded by the native AVPlay pipeline.
2426
- timerVariant: "native"
2427
- });
2428
- } catch (err) {
2429
- status = "error";
2430
- console.warn("".concat(LOG, " connect() threw:"), err);
2431
- scheduleReconnect();
2432
- return;
2433
- }
2434
- client.on("connect", function() {
2435
- status = "connected";
2436
- reconnectAttempts = 0;
2437
- clearReconnectTimer();
2438
- console.info("".concat(LOG, " connected to ").concat(url));
2439
- });
2440
- client.on("offline", function() {
2441
- if (status === "connected") status = "disconnected";
2442
- console.warn("".concat(LOG, " offline"));
2443
- scheduleReconnect();
2444
- });
2445
- client.on("error", function(err) {
2446
- status = "error";
2447
- console.warn("".concat(LOG, " error:"), err.message);
2448
- scheduleReconnect();
2449
- });
2450
- client.on("close", function() {
2451
- if (status === "connected") status = "disconnected";
2452
- scheduleReconnect();
2453
- });
2473
+ var trackingIngestConfig = _object_spread({}, DEFAULT_TRACKING_INGEST_CONFIG);
2474
+ function isTrackingIngestEnabled() {
2475
+ return trackingIngestConfig.enabled === true;
2454
2476
  }
2455
- function initMQTTClient() {
2456
- if (client || !isMQTTEnabled()) return;
2457
- stopped = false;
2458
- reconnectAttempts = 0;
2459
- openConnection();
2477
+ function normalizeIngestBaseUrl(baseUrl) {
2478
+ return baseUrl.replace(/\/+$/, "");
2460
2479
  }
2461
- function ensureMQTTClient() {
2462
- if (isMQTTEnabled() && !client && !reconnectTimer) {
2463
- initMQTTClient();
2480
+ function buildTrackingIngestUrl(channel) {
2481
+ var base = normalizeIngestBaseUrl(trackingIngestConfig.baseUrl);
2482
+ if (channel === "impressions") {
2483
+ return "".concat(base, "/impressions/ingest");
2464
2484
  }
2485
+ return "".concat(base, "/metrics/ingest");
2465
2486
  }
2466
- function publishMQTT(topic, payload) {
2467
- if (!isMQTTEnabled()) {
2468
- return false;
2469
- }
2470
- ensureMQTTClient();
2471
- if (!client || !client.connected) {
2472
- return false;
2473
- }
2474
- try {
2475
- client.publish(topic, JSON.stringify(payload), {
2476
- qos: mqttConfig.qos
2487
+ function ingestPlayerTracking(licenseKey, channel, body) {
2488
+ return _async_to_generator(function() {
2489
+ var response, error;
2490
+ return _ts_generator(this, function(_state) {
2491
+ switch(_state.label){
2492
+ case 0:
2493
+ if (!isTrackingIngestEnabled() || !licenseKey) {
2494
+ return [
2495
+ 2,
2496
+ false
2497
+ ];
2498
+ }
2499
+ _state.label = 1;
2500
+ case 1:
2501
+ _state.trys.push([
2502
+ 1,
2503
+ 3,
2504
+ ,
2505
+ 4
2506
+ ]);
2507
+ return [
2508
+ 4,
2509
+ fetch(buildTrackingIngestUrl(channel), {
2510
+ method: "POST",
2511
+ headers: {
2512
+ "Content-Type": "application/json",
2513
+ Authorization: "License-Key ".concat(licenseKey)
2514
+ },
2515
+ body: JSON.stringify(body)
2516
+ })
2517
+ ];
2518
+ case 2:
2519
+ response = _state.sent();
2520
+ if (!response.ok) {
2521
+ console.warn("[StormcloudVideoPlayer] Tracking ingest failed (".concat(response.status, ") for ").concat(channel));
2522
+ return [
2523
+ 2,
2524
+ false
2525
+ ];
2526
+ }
2527
+ return [
2528
+ 2,
2529
+ true
2530
+ ];
2531
+ case 3:
2532
+ error = _state.sent();
2533
+ console.warn("[StormcloudVideoPlayer] Tracking ingest error for ".concat(channel, ":"), error);
2534
+ return [
2535
+ 2,
2536
+ false
2537
+ ];
2538
+ case 4:
2539
+ return [
2540
+ 2
2541
+ ];
2542
+ }
2477
2543
  });
2478
- return true;
2479
- } catch (err) {
2480
- console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2481
- return false;
2482
- }
2544
+ })();
2483
2545
  }
2484
2546
  // src/utils/vastEnvironmentSignals.ts
2485
2547
  var AIRY_ANDROID_APP_ID = "com.freeairytv.android";
@@ -3250,7 +3312,7 @@ function getBrowserID(clientInfo) {
3250
3312
  })();
3251
3313
  }
3252
3314
  function canPublish(licenseKey) {
3253
- return Boolean(isMQTTEnabled() && licenseKey);
3315
+ return Boolean(isTrackingIngestEnabled() && licenseKey);
3254
3316
  }
3255
3317
  function buildPlayerMetricEvent() {
3256
3318
  return _async_to_generator(function() {
@@ -3292,21 +3354,6 @@ function buildPlayerMetricEvent() {
3292
3354
  });
3293
3355
  }).apply(this, arguments);
3294
3356
  }
3295
- function publishTracking(licenseKey, channel, body) {
3296
- var debug = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : false;
3297
- ensureMQTTClient();
3298
- var topic = buildPlayerTopic(licenseKey, channel);
3299
- var published = publishMQTT(topic, body);
3300
- if (debug) {
3301
- console.log("[StormcloudVideoPlayer] publishTracking:", JSON.stringify({
3302
- licenseKey: licenseKey,
3303
- channel: channel,
3304
- topic: topic,
3305
- body: body,
3306
- published: published
3307
- }, null, 2));
3308
- }
3309
- }
3310
3357
  function sendInitialTracking(_0) {
3311
3358
  return _async_to_generator(function(licenseKey) {
3312
3359
  var context, metricEvent, error;
@@ -3322,9 +3369,9 @@ function sendInitialTracking(_0) {
3322
3369
  case 1:
3323
3370
  _state.trys.push([
3324
3371
  1,
3325
- 3,
3372
+ 4,
3326
3373
  ,
3327
- 4
3374
+ 5
3328
3375
  ]);
3329
3376
  return [
3330
3377
  4,
@@ -3335,23 +3382,28 @@ function sendInitialTracking(_0) {
3335
3382
  ];
3336
3383
  case 2:
3337
3384
  metricEvent = _state.sent();
3338
- publishTracking(licenseKey, "metrics", {
3339
- events: [
3340
- metricEvent
3341
- ]
3342
- }, !!context.debugAdTiming);
3343
3385
  return [
3344
- 3,
3345
- 4
3386
+ 4,
3387
+ ingestPlayerTracking(licenseKey, "metrics", {
3388
+ events: [
3389
+ metricEvent
3390
+ ]
3391
+ })
3346
3392
  ];
3347
3393
  case 3:
3394
+ _state.sent();
3395
+ return [
3396
+ 3,
3397
+ 5
3398
+ ];
3399
+ case 4:
3348
3400
  error = _state.sent();
3349
3401
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
3350
3402
  return [
3351
3403
  3,
3352
- 4
3404
+ 5
3353
3405
  ];
3354
- case 4:
3406
+ case 5:
3355
3407
  return [
3356
3408
  2
3357
3409
  ];
@@ -3449,7 +3501,7 @@ function sendAdLoadedTracking(_0, _1) {
3449
3501
  }
3450
3502
  function sendAdImpressionTracking(_0, _1) {
3451
3503
  return _async_to_generator(function(licenseKey, adImpressionInfo) {
3452
- var context, _adImpressionInfo_adUrl, metricEvent, error;
3504
+ var context, metricEvent, error;
3453
3505
  var _arguments = arguments;
3454
3506
  return _ts_generator(this, function(_state) {
3455
3507
  switch(_state.label){
@@ -3462,9 +3514,9 @@ function sendAdImpressionTracking(_0, _1) {
3462
3514
  case 1:
3463
3515
  _state.trys.push([
3464
3516
  1,
3465
- 3,
3517
+ 5,
3466
3518
  ,
3467
- 4
3519
+ 6
3468
3520
  ]);
3469
3521
  return [
3470
3522
  4,
@@ -3474,30 +3526,43 @@ function sendAdImpressionTracking(_0, _1) {
3474
3526
  ];
3475
3527
  case 2:
3476
3528
  metricEvent = _state.sent();
3477
- publishTracking(licenseKey, "heartbeat", metricEvent, !!context.debugAdTiming);
3478
- publishTracking(licenseKey, "impressions", {
3479
- events: [
3480
- {
3481
- event_id: createUuid(),
3482
- player_id: metricEvent.player_id,
3483
- ad_played_count: 1,
3484
- ad_url: (_adImpressionInfo_adUrl = adImpressionInfo.adUrl) !== null && _adImpressionInfo_adUrl !== void 0 ? _adImpressionInfo_adUrl : adImpressionInfo.adId,
3485
- capture_at: adImpressionInfo.timestamp
3486
- }
3487
- ]
3488
- }, !!context.debugAdTiming);
3489
3529
  return [
3490
- 3,
3491
- 4
3530
+ 4,
3531
+ ingestPlayerTracking(licenseKey, "metrics", {
3532
+ events: [
3533
+ metricEvent
3534
+ ]
3535
+ })
3492
3536
  ];
3493
3537
  case 3:
3538
+ _state.sent();
3539
+ return [
3540
+ 4,
3541
+ ingestPlayerTracking(licenseKey, "impressions", {
3542
+ events: [
3543
+ {
3544
+ player_id: metricEvent.player_id,
3545
+ ad_played_count: 1,
3546
+ ad_url: adImpressionInfo.adUrl,
3547
+ capture_at: adImpressionInfo.timestamp
3548
+ }
3549
+ ]
3550
+ })
3551
+ ];
3552
+ case 4:
3553
+ _state.sent();
3554
+ return [
3555
+ 3,
3556
+ 6
3557
+ ];
3558
+ case 5:
3494
3559
  error = _state.sent();
3495
3560
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
3496
3561
  return [
3497
3562
  3,
3498
- 4
3563
+ 6
3499
3564
  ];
3500
- case 4:
3565
+ case 6:
3501
3566
  return [
3502
3567
  2
3503
3568
  ];
@@ -3520,9 +3585,9 @@ function sendHeartbeat(_0) {
3520
3585
  case 1:
3521
3586
  _state.trys.push([
3522
3587
  1,
3523
- 3,
3588
+ 4,
3524
3589
  ,
3525
- 4
3590
+ 5
3526
3591
  ]);
3527
3592
  return [
3528
3593
  4,
@@ -3530,19 +3595,28 @@ function sendHeartbeat(_0) {
3530
3595
  ];
3531
3596
  case 2:
3532
3597
  heartbeatData = _state.sent();
3533
- publishTracking(licenseKey, "heartbeat", heartbeatData, !!context.debugAdTiming);
3534
3598
  return [
3535
- 3,
3536
- 4
3599
+ 4,
3600
+ ingestPlayerTracking(licenseKey, "metrics", {
3601
+ events: [
3602
+ heartbeatData
3603
+ ]
3604
+ })
3537
3605
  ];
3538
3606
  case 3:
3607
+ _state.sent();
3608
+ return [
3609
+ 3,
3610
+ 5
3611
+ ];
3612
+ case 4:
3539
3613
  error = _state.sent();
3540
3614
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3541
3615
  return [
3542
3616
  3,
3543
- 4
3617
+ 5
3544
3618
  ];
3545
- case 4:
3619
+ case 5:
3546
3620
  return [
3547
3621
  2
3548
3622
  ];
@@ -5416,7 +5490,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5416
5490
  ];
5417
5491
  case 4:
5418
5492
  data = _state.sent();
5419
- this.applyMqttConfigFromResponse(data);
5420
5493
  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;
5421
5494
  if (imaPayload) {
5422
5495
  decodedPayload = imaPayload;
@@ -5450,20 +5523,6 @@ var AdConfigManager = /*#__PURE__*/ function() {
5450
5523
  }).call(this);
5451
5524
  }
5452
5525
  },
5453
- {
5454
- key: "applyMqttConfigFromResponse",
5455
- value: function applyMqttConfigFromResponse(data) {
5456
- var _data_response;
5457
- var mqttConfig2 = (_data_response = data.response) === null || _data_response === void 0 ? void 0 : _data_response.mqtt_config;
5458
- if (!mqttConfig2 || (typeof mqttConfig2 === "undefined" ? "undefined" : _type_of(mqttConfig2)) !== "object") {
5459
- return;
5460
- }
5461
- applyMQTTConfig(mqttConfig2);
5462
- if (this.debug) {
5463
- console.log("[StormcloudVideoPlayer] Applied MQTT config from /ads/web:", mqttConfig2);
5464
- }
5465
- }
5466
- },
5467
5526
  {
5468
5527
  key: "fetchAndParseVmap",
5469
5528
  value: function fetchAndParseVmap(vmapUrl) {
@@ -7324,22 +7383,27 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7324
7383
  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 : "?"));
7325
7384
  }
7326
7385
  });
7327
- adPlayer.on("ad_impression", function(payload) {
7328
- var impressionInfo = {
7386
+ adPlayer.on("all_ads_completed", function() {
7387
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7329
7388
  source: "hls",
7330
7389
  adIndex: _this.timing.currentAdIndex,
7331
7390
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
7332
- };
7333
- if (payload === null || payload === void 0 ? void 0 : payload.adId) impressionInfo.adId = payload.adId;
7334
- if (payload === null || payload === void 0 ? void 0 : payload.mediaUrl) impressionInfo.adUrl = payload.mediaUrl;
7335
- if ((payload === null || payload === void 0 ? void 0 : payload.durationSeconds) != null) {
7336
- impressionInfo.durationSeconds = payload.durationSeconds;
7391
+ }, _this.analyticsContext).catch(function() {});
7392
+ _this.timing.recordPlayedAdDuration(_this.host.getAdPlayer());
7393
+ var remaining = _this.timing.getRemainingAdMs();
7394
+ _this.timing.consecutiveFailures = 0;
7395
+ if (_this.debug) {
7396
+ console.log("[StormcloudVideoPlayer] all_ads_completed event received - remaining=".concat(remaining, "ms, queued ads=").concat(_this.adRequestQueue.length), _this.adRequestQueue.length > 0 ? {
7397
+ queuedUrls: _to_consumable_array(_this.adRequestQueue)
7398
+ } : {});
7337
7399
  }
7338
- sendAdImpressionTracking(_this.host.config.licenseKey, impressionInfo, _this.analyticsContext).catch(function(error) {
7339
- if (_this.debug) {
7340
- console.warn("[AdBreakOrchestrator] Failed to send ad impression tracking:", error);
7400
+ if (_this.inAdBreak) {
7401
+ if (_this.timing.hasTimeToStartAnotherAd()) {
7402
+ _this.tryNextAvailableAdWithRateLimit();
7403
+ } else {
7404
+ _this.handleAdPodComplete();
7341
7405
  }
7342
- });
7406
+ }
7343
7407
  });
7344
7408
  adPlayer.on("ad_error", function(errorPayload) {
7345
7409
  var errorMessage = "Ad playback failed";
@@ -7382,6 +7446,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
7382
7446
  }
7383
7447
  });
7384
7448
  adPlayer.on("content_resume", function() {
7449
+ sendAdImpressionTracking(_this.host.config.licenseKey, {
7450
+ source: "hls",
7451
+ adIndex: _this.timing.currentAdIndex,
7452
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
7453
+ }, _this.analyticsContext).catch(function() {});
7385
7454
  if (!_this.host.video.muted) {
7386
7455
  _this.host.video.muted = true;
7387
7456
  if (_this.debug) {