stormcloud-video-player 0.8.3 → 0.8.4

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.
@@ -1421,6 +1421,99 @@ function createPalNonceManager() {
1421
1421
  }
1422
1422
  };
1423
1423
  }
1424
+ // src/utils/mqttConfig.ts
1425
+ var DEFAULT_MQTT_CONFIG = {
1426
+ enabled: true,
1427
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
1428
+ brokerPort: 8883,
1429
+ wsPort: 8084,
1430
+ username: "for-sonifi",
1431
+ password: "sonifi-mqtt",
1432
+ topicPrefix: "adstorm/players",
1433
+ qos: 1
1434
+ };
1435
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
1436
+ function isMQTTEnabled() {
1437
+ return mqttConfig.enabled;
1438
+ }
1439
+ function buildMQTTBrokerUrl() {
1440
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
1441
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
1442
+ }
1443
+ function buildPlayerTopic(licenseKey, channel) {
1444
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
1445
+ }
1446
+ // src/utils/mqttClient.ts
1447
+ var import_mqtt = __toESM(require("mqtt"), 1);
1448
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
1449
+ var client = null;
1450
+ var status = "disconnected";
1451
+ function initMQTTClient() {
1452
+ if (client || !isMQTTEnabled()) return;
1453
+ var url = buildMQTTBrokerUrl();
1454
+ status = "connecting";
1455
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
1456
+ try {
1457
+ client = import_mqtt.default.connect(url, {
1458
+ clientId: clientId,
1459
+ username: mqttConfig.username,
1460
+ password: mqttConfig.password,
1461
+ keepalive: 60,
1462
+ clean: true,
1463
+ reconnectPeriod: 5e3,
1464
+ connectTimeout: 1e4,
1465
+ queueQoSZero: false
1466
+ });
1467
+ } catch (err) {
1468
+ status = "error";
1469
+ console.warn("".concat(LOG, " connect() threw:"), err);
1470
+ return;
1471
+ }
1472
+ client.on("connect", function() {
1473
+ status = "connected";
1474
+ console.info("".concat(LOG, " connected to ").concat(url));
1475
+ });
1476
+ client.on("reconnect", function() {
1477
+ status = "connecting";
1478
+ console.info("".concat(LOG, " reconnecting…"));
1479
+ });
1480
+ client.on("offline", function() {
1481
+ status = "disconnected";
1482
+ console.warn("".concat(LOG, " offline"));
1483
+ });
1484
+ client.on("error", function(err) {
1485
+ status = "error";
1486
+ console.warn("".concat(LOG, " error:"), err.message);
1487
+ });
1488
+ client.on("close", function() {
1489
+ if (status === "connected") {
1490
+ status = "disconnected";
1491
+ }
1492
+ });
1493
+ }
1494
+ function ensureMQTTClient() {
1495
+ if (isMQTTEnabled() && !client) {
1496
+ initMQTTClient();
1497
+ }
1498
+ }
1499
+ function publishMQTT(topic, payload) {
1500
+ if (!isMQTTEnabled()) {
1501
+ return false;
1502
+ }
1503
+ ensureMQTTClient();
1504
+ if (!client) {
1505
+ return false;
1506
+ }
1507
+ try {
1508
+ client.publish(topic, JSON.stringify(payload), {
1509
+ qos: mqttConfig.qos
1510
+ });
1511
+ return true;
1512
+ } catch (err) {
1513
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
1514
+ return false;
1515
+ }
1516
+ }
1424
1517
  // src/utils/tracking.ts
1425
1518
  var cachedBrowserId = null;
1426
1519
  function getClientInfo() {
@@ -1567,7 +1660,7 @@ function getClientInfo() {
1567
1660
  }
1568
1661
  function getBrowserID(clientInfo) {
1569
1662
  return _async_to_generator(function() {
1570
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
1663
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
1571
1664
  return _ts_generator(this, function(_state) {
1572
1665
  switch(_state.label){
1573
1666
  case 0:
@@ -1578,7 +1671,7 @@ function getBrowserID(clientInfo) {
1578
1671
  ];
1579
1672
  }
1580
1673
  fingerprintString = JSON.stringify(clientInfo);
1581
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
1674
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
1582
1675
  3,
1583
1676
  5
1584
1677
  ];
@@ -1616,8 +1709,7 @@ function getBrowserID(clientInfo) {
1616
1709
  ];
1617
1710
  case 3:
1618
1711
  hashBuffer = _state.sent();
1619
- hashArray = Array.from(new Uint8Array(hashBuffer));
1620
- hashHex = hashArray.map(function(b) {
1712
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
1621
1713
  return b.toString(16).padStart(2, "0");
1622
1714
  }).join("");
1623
1715
  cachedBrowserId = hashHex;
@@ -1626,8 +1718,8 @@ function getBrowserID(clientInfo) {
1626
1718
  hashHex
1627
1719
  ];
1628
1720
  case 4:
1629
- error = _state.sent();
1630
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
1721
+ unused = _state.sent();
1722
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
1631
1723
  return [
1632
1724
  3,
1633
1725
  5
@@ -1651,177 +1743,91 @@ function getBrowserID(clientInfo) {
1651
1743
  });
1652
1744
  })();
1653
1745
  }
1654
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
1655
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
1656
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
1657
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
1658
- function buildHeaders(licenseKey) {
1659
- var headers = {
1660
- "Content-Type": "application/json"
1661
- };
1662
- if (licenseKey) {
1663
- headers["Authorization"] = "Bearer ".concat(licenseKey);
1664
- }
1665
- return headers;
1746
+ function canPublish(licenseKey) {
1747
+ return Boolean(isMQTTEnabled() && licenseKey);
1666
1748
  }
1667
- function sendTrackRequest(licenseKey, body) {
1749
+ function buildPlayerMetricEvent() {
1668
1750
  return _async_to_generator(function() {
1669
- var response;
1670
- return _ts_generator(this, function(_state) {
1671
- switch(_state.label){
1672
- case 0:
1673
- return [
1674
- 4,
1675
- fetch(TRACK_URL, {
1676
- method: "POST",
1677
- headers: buildHeaders(licenseKey),
1678
- body: JSON.stringify(body)
1679
- })
1680
- ];
1681
- case 1:
1682
- response = _state.sent();
1683
- if (!response.ok) {
1684
- throw new Error("HTTP error! status: ".concat(response.status));
1685
- }
1686
- return [
1687
- 4,
1688
- response.json()
1689
- ];
1690
- case 2:
1691
- _state.sent();
1692
- return [
1693
- 2
1694
- ];
1695
- }
1696
- });
1697
- })();
1698
- }
1699
- function postJson(url, licenseKey, body) {
1700
- return _async_to_generator(function() {
1701
- var response;
1702
- return _ts_generator(this, function(_state) {
1703
- switch(_state.label){
1704
- case 0:
1705
- return [
1706
- 4,
1707
- fetch(url, {
1708
- method: "POST",
1709
- headers: buildHeaders(licenseKey),
1710
- body: JSON.stringify(body)
1711
- })
1712
- ];
1713
- case 1:
1714
- response = _state.sent();
1715
- if (!response.ok) {
1716
- throw new Error("HTTP error! status: ".concat(response.status));
1717
- }
1718
- return [
1719
- 4,
1720
- response.json()
1721
- ];
1722
- case 2:
1723
- _state.sent();
1724
- return [
1725
- 2
1726
- ];
1727
- }
1728
- });
1729
- })();
1730
- }
1731
- function buildPlayerMetricEvent(_0) {
1732
- return _async_to_generator(function(licenseKey) {
1733
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
1751
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
1734
1752
  var _arguments = arguments;
1735
1753
  return _ts_generator(this, function(_state) {
1736
1754
  switch(_state.label){
1737
1755
  case 0:
1738
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
1756
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
1739
1757
  clientInfo = getClientInfo();
1740
1758
  return [
1741
1759
  4,
1742
1760
  getBrowserID(clientInfo)
1743
1761
  ];
1744
1762
  case 1:
1745
- browserId = _state.sent();
1763
+ playerId = _state.sent();
1746
1764
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
1747
1765
  return [
1748
1766
  2,
1749
- {
1750
- player_id: browserId,
1751
- browserId: browserId,
1767
+ _object_spread({
1768
+ player_id: playerId,
1752
1769
  device_type: clientInfo.deviceType,
1753
- deviceType: clientInfo.deviceType,
1754
- input_stream_type: context.inputStreamType,
1755
- os: clientInfo.os,
1756
- ad_loaded: flags.adLoaded,
1757
- ad_detect: flags.adDetect,
1758
- license_key: licenseKey,
1759
- capture_at: captureAt,
1760
- timestamp: captureAt
1761
- }
1770
+ os: clientInfo.os.toLowerCase(),
1771
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
1772
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
1773
+ capture_at: captureAt
1774
+ }, context.inputStreamType ? {
1775
+ input_stream_type: context.inputStreamType
1776
+ } : {})
1762
1777
  ];
1763
1778
  }
1764
1779
  });
1765
1780
  }).apply(this, arguments);
1766
1781
  }
1782
+ function publishTracking(licenseKey, channel, body) {
1783
+ ensureMQTTClient();
1784
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
1785
+ }
1767
1786
  function sendInitialTracking(_0) {
1768
1787
  return _async_to_generator(function(licenseKey) {
1769
- var context, clientInfo, browserId, trackingData, error;
1788
+ var context, metricEvent, error;
1770
1789
  var _arguments = arguments;
1771
1790
  return _ts_generator(this, function(_state) {
1772
1791
  switch(_state.label){
1773
1792
  case 0:
1774
1793
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
1794
+ if (!canPublish(licenseKey)) return [
1795
+ 2
1796
+ ];
1775
1797
  _state.label = 1;
1776
1798
  case 1:
1777
1799
  _state.trys.push([
1778
1800
  1,
1779
- 4,
1801
+ 3,
1780
1802
  ,
1781
- 5
1803
+ 4
1782
1804
  ]);
1783
- clientInfo = getClientInfo();
1784
- return [
1785
- 4,
1786
- getBrowserID(clientInfo)
1787
- ];
1788
- case 2:
1789
- browserId = _state.sent();
1790
- trackingData = _object_spread({
1791
- browserId: browserId
1792
- }, clientInfo);
1793
1805
  return [
1794
1806
  4,
1795
- sendTrackRequest(licenseKey, {
1796
- events: [
1797
- {
1798
- player_id: browserId,
1799
- device_type: clientInfo.deviceType,
1800
- input_stream_type: context.inputStreamType,
1801
- os: clientInfo.os,
1802
- ad_loaded: false,
1803
- ad_detect: false,
1804
- license_key: licenseKey,
1805
- capture_at: /* @__PURE__ */ new Date().toISOString()
1806
- }
1807
- ],
1808
- trackingData: trackingData
1807
+ buildPlayerMetricEvent(context, {
1808
+ adLoaded: false,
1809
+ adDetect: false
1809
1810
  })
1810
1811
  ];
1811
- case 3:
1812
- _state.sent();
1812
+ case 2:
1813
+ metricEvent = _state.sent();
1814
+ publishTracking(licenseKey, "metrics", {
1815
+ events: [
1816
+ metricEvent
1817
+ ]
1818
+ });
1813
1819
  return [
1814
1820
  3,
1815
- 5
1821
+ 4
1816
1822
  ];
1817
- case 4:
1823
+ case 3:
1818
1824
  error = _state.sent();
1819
1825
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
1820
1826
  return [
1821
1827
  3,
1822
- 5
1828
+ 4
1823
1829
  ];
1824
- case 5:
1830
+ case 4:
1825
1831
  return [
1826
1832
  2
1827
1833
  ];
@@ -1925,53 +1931,48 @@ function sendAdImpressionTracking(_0, _1) {
1925
1931
  switch(_state.label){
1926
1932
  case 0:
1927
1933
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
1934
+ if (!canPublish(licenseKey)) return [
1935
+ 2
1936
+ ];
1928
1937
  _state.label = 1;
1929
1938
  case 1:
1930
1939
  _state.trys.push([
1931
1940
  1,
1932
- 4,
1941
+ 3,
1933
1942
  ,
1934
- 5
1943
+ 4
1935
1944
  ]);
1936
1945
  return [
1937
1946
  4,
1938
- buildPlayerMetricEvent(licenseKey, context, {
1947
+ buildPlayerMetricEvent(context, {
1939
1948
  captureAt: adImpressionInfo.timestamp
1940
1949
  })
1941
1950
  ];
1942
1951
  case 2:
1943
1952
  metricEvent = _state.sent();
1944
- return [
1945
- 4,
1946
- Promise.all([
1947
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
1948
- postJson(IMPRESSIONS_URL, licenseKey, {
1949
- events: [
1950
- {
1951
- player_id: metricEvent.player_id,
1952
- ad_played_count: 1,
1953
- ad_url: adImpressionInfo.adUrl,
1954
- license_key: licenseKey,
1955
- capture_at: adImpressionInfo.timestamp
1956
- }
1957
- ]
1958
- })
1959
- ])
1960
- ];
1961
- case 3:
1962
- _state.sent();
1953
+ publishTracking(licenseKey, "heartbeat", metricEvent);
1954
+ publishTracking(licenseKey, "impressions", {
1955
+ events: [
1956
+ {
1957
+ player_id: metricEvent.player_id,
1958
+ ad_played_count: 1,
1959
+ ad_url: adImpressionInfo.adUrl,
1960
+ capture_at: adImpressionInfo.timestamp
1961
+ }
1962
+ ]
1963
+ });
1963
1964
  return [
1964
1965
  3,
1965
- 5
1966
+ 4
1966
1967
  ];
1967
- case 4:
1968
+ case 3:
1968
1969
  error = _state.sent();
1969
1970
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
1970
1971
  return [
1971
1972
  3,
1972
- 5
1973
+ 4
1973
1974
  ];
1974
- case 5:
1975
+ case 4:
1975
1976
  return [
1976
1977
  2
1977
1978
  ];
@@ -1987,38 +1988,36 @@ function sendHeartbeat(_0) {
1987
1988
  switch(_state.label){
1988
1989
  case 0:
1989
1990
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
1991
+ if (!canPublish(licenseKey)) return [
1992
+ 2
1993
+ ];
1990
1994
  _state.label = 1;
1991
1995
  case 1:
1992
1996
  _state.trys.push([
1993
1997
  1,
1994
- 4,
1998
+ 3,
1995
1999
  ,
1996
- 5
2000
+ 4
1997
2001
  ]);
1998
2002
  return [
1999
2003
  4,
2000
- buildPlayerMetricEvent(licenseKey, context, flags)
2004
+ buildPlayerMetricEvent(context, flags)
2001
2005
  ];
2002
2006
  case 2:
2003
2007
  heartbeatData = _state.sent();
2004
- return [
2005
- 4,
2006
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
2007
- ];
2008
- case 3:
2009
- _state.sent();
2008
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
2010
2009
  return [
2011
2010
  3,
2012
- 5
2011
+ 4
2013
2012
  ];
2014
- case 4:
2013
+ case 3:
2015
2014
  error = _state.sent();
2016
2015
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
2017
2016
  return [
2018
2017
  3,
2019
- 5
2018
+ 4
2020
2019
  ];
2021
- case 5:
2020
+ case 4:
2022
2021
  return [
2023
2022
  2
2024
2023
  ];
@@ -2641,7 +2640,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2641
2640
  var _level_details, _level_details1;
2642
2641
  return (level === null || level === void 0 ? void 0 : (_level_details = level.details) === null || _level_details === void 0 ? void 0 : _level_details.live) === true || (level === null || level === void 0 ? void 0 : (_level_details1 = level.details) === null || _level_details1 === void 0 ? void 0 : _level_details1.type) === "LIVE";
2643
2642
  })) !== null && _ref !== void 0 ? _ref : false;
2644
- if (!this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
2643
+ if (!this.isVmapEnabled() && !this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
2645
2644
  prerollKey = "synthetic-vod-preroll";
2646
2645
  if (!this.consumedVmapBreakIds.has(prerollKey)) {
2647
2646
  this.vmapBreaks = [
@@ -3226,12 +3225,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3226
3225
  _this.palNonce.sendPlaybackStart();
3227
3226
  }
3228
3227
  });
3229
- this.video.addEventListener("ended", function() {
3228
+ this.endedHandler = function() {
3230
3229
  if (_this.palPlaybackStarted) {
3231
3230
  _this.palPlaybackStarted = false;
3232
3231
  _this.palNonce.sendPlaybackEnd();
3233
3232
  }
3234
- });
3233
+ _this.onVideoEnded();
3234
+ };
3235
+ this.video.addEventListener("ended", this.endedHandler);
3235
3236
  this.video.addEventListener("mousedown", function(e) {
3236
3237
  _this.palNonce.sendAdTouch(e);
3237
3238
  });
@@ -4553,6 +4554,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4553
4554
  }
4554
4555
  }
4555
4556
  },
4557
+ {
4558
+ key: "isVmapEnabled",
4559
+ value: function isVmapEnabled() {
4560
+ var _this_config_vmapUrl;
4561
+ return !!(this.config.isVmap && ((_this_config_vmapUrl = this.config.vmapUrl) === null || _this_config_vmapUrl === void 0 ? void 0 : _this_config_vmapUrl.trim()));
4562
+ }
4563
+ },
4556
4564
  {
4557
4565
  key: "fetchAdConfiguration",
4558
4566
  value: function fetchAdConfiguration() {
@@ -4561,7 +4569,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4561
4569
  return _ts_generator(this, function(_state) {
4562
4570
  switch(_state.label){
4563
4571
  case 0:
4564
- if (!this.config.vmapUrl) return [
4572
+ if (!this.isVmapEnabled()) return [
4565
4573
  3,
4566
4574
  2
4567
4575
  ];
@@ -4571,7 +4579,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4571
4579
  ];
4572
4580
  case 1:
4573
4581
  _state.sent();
4574
- _state.label = 2;
4582
+ if (this.config.debugAdTiming) {
4583
+ console.log("[StormcloudVideoPlayer] VMAP mode enabled");
4584
+ }
4585
+ return [
4586
+ 2
4587
+ ];
4575
4588
  case 2:
4576
4589
  vastMode = this.config.vastMode || "default";
4577
4590
  if (this.config.debugAdTiming) {
@@ -4739,7 +4752,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4739
4752
  }
4740
4753
  return [];
4741
4754
  }
4742
- var adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
4755
+ var VMAP_NS = "http://www.iab.net/videosuite/vmap";
4756
+ var adBreakNodes = Array.from(doc.getElementsByTagNameNS(VMAP_NS, "AdBreak"));
4757
+ if (adBreakNodes.length === 0) {
4758
+ adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
4759
+ }
4760
+ if (adBreakNodes.length === 0) {
4761
+ adBreakNodes = Array.from(doc.getElementsByTagName("*")).filter(function(el) {
4762
+ return el.localName === "AdBreak";
4763
+ });
4764
+ }
4743
4765
  var parsed = [];
4744
4766
  adBreakNodes.forEach(function(node, index) {
4745
4767
  var timeOffsetRaw = (node.getAttribute("timeOffset") || "").trim();
@@ -4747,7 +4769,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4747
4769
  if (startTimeMs == null) {
4748
4770
  return;
4749
4771
  }
4750
- var adTagNode = node.querySelector("AdTagURI, vmap\\:AdTagURI");
4772
+ var adTagNode = node.getElementsByTagNameNS(VMAP_NS, "AdTagURI")[0];
4773
+ if (!adTagNode) {
4774
+ var _node_querySelector;
4775
+ adTagNode = (_node_querySelector = node.querySelector("AdTagURI, vmap\\:AdTagURI")) !== null && _node_querySelector !== void 0 ? _node_querySelector : void 0;
4776
+ }
4751
4777
  var adTagUrl = ((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim();
4752
4778
  if (!adTagUrl) {
4753
4779
  return;
@@ -6304,23 +6330,49 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6304
6330
  key: "onTimeUpdate",
6305
6331
  value: function onTimeUpdate(currentTimeSec) {
6306
6332
  var _this = this;
6333
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
6334
+ return;
6335
+ }
6307
6336
  if (this.adPlayer.isAdPlaying() || this.inAdBreak) return;
6308
6337
  var nowMs = currentTimeSec * 1e3;
6309
6338
  var breakToPlay = this.findBreakForTime(nowMs);
6310
6339
  if (breakToPlay) {
6311
- void this.handleMidAdJoin(breakToPlay, nowMs).catch(function(error) {
6340
+ void this.handleVmapAdBreak(breakToPlay, nowMs).catch(function(error) {
6341
+ if (_this.config.debugAdTiming) {
6342
+ console.warn("[StormcloudVideoPlayer] VMAP ad break failed gracefully:", error);
6343
+ }
6344
+ });
6345
+ }
6346
+ }
6347
+ },
6348
+ {
6349
+ key: "onVideoEnded",
6350
+ value: function onVideoEnded() {
6351
+ var _this = this;
6352
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
6353
+ return;
6354
+ }
6355
+ if (this.adPlayer.isAdPlaying() || this.inAdBreak) {
6356
+ return;
6357
+ }
6358
+ var durationMs = Number.isFinite(this.video.duration) ? Math.floor(this.video.duration * 1e3) : 0;
6359
+ var postroll = this.vmapBreaks.find(function(b) {
6360
+ return b.startTimeMs === -1 && !_this.consumedVmapBreakIds.has(_this.getAdBreakKey(b));
6361
+ });
6362
+ if (postroll) {
6363
+ void this.handleVmapAdBreak(postroll, durationMs).catch(function(error) {
6312
6364
  if (_this.config.debugAdTiming) {
6313
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP join failed gracefully:", error);
6365
+ console.warn("[StormcloudVideoPlayer] VMAP post-roll failed gracefully:", error);
6314
6366
  }
6315
6367
  });
6316
6368
  }
6317
6369
  }
6318
6370
  },
6319
6371
  {
6320
- key: "handleMidAdJoin",
6321
- value: function handleMidAdJoin(adBreak, nowMs) {
6372
+ key: "handleVmapAdBreak",
6373
+ value: function handleVmapAdBreak(adBreak, nowMs) {
6322
6374
  return _async_to_generator(function() {
6323
- var _adBreak_durationMs, _this_config_driftToleranceMs, key, breakStartMs, durationMs, endMs, tol, inWindow, remainingMs, tags, first, rest, error;
6375
+ var _adBreak_durationMs, key, breakStartMs, durationMs, endMs, inWindow, tags, first, rest, error;
6324
6376
  return _ts_generator(this, function(_state) {
6325
6377
  switch(_state.label){
6326
6378
  case 0:
@@ -6338,25 +6390,28 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6338
6390
  }
6339
6391
  durationMs = (_adBreak_durationMs = adBreak.durationMs) !== null && _adBreak_durationMs !== void 0 ? _adBreak_durationMs : 0;
6340
6392
  endMs = breakStartMs + durationMs;
6341
- tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
6342
- inWindow = durationMs > 0 ? nowMs > breakStartMs && nowMs < endMs : nowMs + tol >= breakStartMs;
6393
+ inWindow = durationMs > 0 ? nowMs >= breakStartMs && nowMs < endMs : nowMs >= breakStartMs;
6343
6394
  if (!inWindow) return [
6344
6395
  3,
6345
6396
  4
6346
6397
  ];
6347
6398
  this.consumedVmapBreakIds.add(key);
6348
- remainingMs = durationMs > 0 ? Math.max(0, endMs - nowMs) : 0;
6349
- tags = this.selectVastTagsForBreak(adBreak) || (this.apiVastTagUrl ? [
6350
- this.apiVastTagUrl
6351
- ] : void 0);
6352
- if (!(tags && tags.length > 0)) return [
6353
- 3,
6354
- 4
6355
- ];
6399
+ tags = this.selectVastTagsForBreak(adBreak);
6400
+ if (!tags || tags.length === 0) {
6401
+ return [
6402
+ 2
6403
+ ];
6404
+ }
6356
6405
  first = tags[0];
6357
6406
  rest = tags.slice(1);
6358
6407
  this.adPodQueue = rest;
6359
6408
  this.adPlayer.updateOriginalMutedState(this.video.muted, this.video.volume);
6409
+ this.showAds = true;
6410
+ this.inAdBreak = true;
6411
+ this.currentAdBreakStartWallClockMs = Date.now();
6412
+ if (!this.video.paused) {
6413
+ this.video.pause();
6414
+ }
6360
6415
  _state.label = 1;
6361
6416
  case 1:
6362
6417
  _state.trys.push([
@@ -6371,10 +6426,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6371
6426
  ];
6372
6427
  case 2:
6373
6428
  _state.sent();
6374
- this.inAdBreak = true;
6375
- this.expectedAdBreakDurationMs = remainingMs;
6376
- this.currentAdBreakStartWallClockMs = Date.now();
6377
- this.scheduleAdStopCountdown(remainingMs);
6378
6429
  return [
6379
6430
  3,
6380
6431
  4
@@ -6382,8 +6433,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6382
6433
  case 3:
6383
6434
  error = _state.sent();
6384
6435
  this.adPodQueue = [];
6436
+ this.inAdBreak = false;
6437
+ this.showAds = false;
6385
6438
  if (this.config.debugAdTiming) {
6386
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP ad request failed:", error);
6439
+ console.warn("[StormcloudVideoPlayer] VMAP ad request failed:", error);
6387
6440
  }
6388
6441
  return [
6389
6442
  3,
@@ -7095,9 +7148,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7095
7148
  {
7096
7149
  key: "findBreakForTime",
7097
7150
  value: function findBreakForTime(nowMs) {
7098
- var _this_config_driftToleranceMs;
7099
7151
  var schedule = this.vmapBreaks;
7100
- var tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7101
7152
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7102
7153
  try {
7103
7154
  for(var _iterator = schedule[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -7109,9 +7160,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7109
7160
  if (breakStartMs == null) {
7110
7161
  continue;
7111
7162
  }
7112
- var end = breakStartMs + (b.durationMs || 0);
7113
- var effectiveTol = breakStartMs === 0 ? Math.max(tol, 3e4) : tol;
7114
- if (nowMs >= breakStartMs && (b.durationMs ? nowMs < end : nowMs <= breakStartMs + effectiveTol)) {
7163
+ if (b.durationMs) {
7164
+ var end = breakStartMs + b.durationMs;
7165
+ if (nowMs >= breakStartMs && nowMs < end) {
7166
+ return b;
7167
+ }
7168
+ continue;
7169
+ }
7170
+ if (nowMs >= breakStartMs) {
7115
7171
  return b;
7116
7172
  }
7117
7173
  }
@@ -7319,6 +7375,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7319
7375
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
7320
7376
  delete this.timeUpdateHandler;
7321
7377
  }
7378
+ if (this.endedHandler) {
7379
+ this.video.removeEventListener("ended", this.endedHandler);
7380
+ delete this.endedHandler;
7381
+ }
7322
7382
  if (this.emptiedHandler) {
7323
7383
  this.video.removeEventListener("emptied", this.emptiedHandler);
7324
7384
  delete this.emptiedHandler;