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.
@@ -1333,6 +1333,99 @@ function createPalNonceManager() {
1333
1333
  }
1334
1334
  };
1335
1335
  }
1336
+ // src/utils/mqttConfig.ts
1337
+ var DEFAULT_MQTT_CONFIG = {
1338
+ enabled: true,
1339
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
1340
+ brokerPort: 8883,
1341
+ wsPort: 8084,
1342
+ username: "for-sonifi",
1343
+ password: "sonifi-mqtt",
1344
+ topicPrefix: "adstorm/players",
1345
+ qos: 1
1346
+ };
1347
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
1348
+ function isMQTTEnabled() {
1349
+ return mqttConfig.enabled;
1350
+ }
1351
+ function buildMQTTBrokerUrl() {
1352
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
1353
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
1354
+ }
1355
+ function buildPlayerTopic(licenseKey, channel) {
1356
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
1357
+ }
1358
+ // src/utils/mqttClient.ts
1359
+ var import_mqtt = __toESM(require("mqtt"), 1);
1360
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
1361
+ var client = null;
1362
+ var status = "disconnected";
1363
+ function initMQTTClient() {
1364
+ if (client || !isMQTTEnabled()) return;
1365
+ var url = buildMQTTBrokerUrl();
1366
+ status = "connecting";
1367
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
1368
+ try {
1369
+ client = import_mqtt.default.connect(url, {
1370
+ clientId: clientId,
1371
+ username: mqttConfig.username,
1372
+ password: mqttConfig.password,
1373
+ keepalive: 60,
1374
+ clean: true,
1375
+ reconnectPeriod: 5e3,
1376
+ connectTimeout: 1e4,
1377
+ queueQoSZero: false
1378
+ });
1379
+ } catch (err) {
1380
+ status = "error";
1381
+ console.warn("".concat(LOG, " connect() threw:"), err);
1382
+ return;
1383
+ }
1384
+ client.on("connect", function() {
1385
+ status = "connected";
1386
+ console.info("".concat(LOG, " connected to ").concat(url));
1387
+ });
1388
+ client.on("reconnect", function() {
1389
+ status = "connecting";
1390
+ console.info("".concat(LOG, " reconnecting…"));
1391
+ });
1392
+ client.on("offline", function() {
1393
+ status = "disconnected";
1394
+ console.warn("".concat(LOG, " offline"));
1395
+ });
1396
+ client.on("error", function(err) {
1397
+ status = "error";
1398
+ console.warn("".concat(LOG, " error:"), err.message);
1399
+ });
1400
+ client.on("close", function() {
1401
+ if (status === "connected") {
1402
+ status = "disconnected";
1403
+ }
1404
+ });
1405
+ }
1406
+ function ensureMQTTClient() {
1407
+ if (isMQTTEnabled() && !client) {
1408
+ initMQTTClient();
1409
+ }
1410
+ }
1411
+ function publishMQTT(topic, payload) {
1412
+ if (!isMQTTEnabled()) {
1413
+ return false;
1414
+ }
1415
+ ensureMQTTClient();
1416
+ if (!client) {
1417
+ return false;
1418
+ }
1419
+ try {
1420
+ client.publish(topic, JSON.stringify(payload), {
1421
+ qos: mqttConfig.qos
1422
+ });
1423
+ return true;
1424
+ } catch (err) {
1425
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
1426
+ return false;
1427
+ }
1428
+ }
1336
1429
  // src/utils/tracking.ts
1337
1430
  var cachedBrowserId = null;
1338
1431
  function getClientInfo() {
@@ -1479,7 +1572,7 @@ function getClientInfo() {
1479
1572
  }
1480
1573
  function getBrowserID(clientInfo) {
1481
1574
  return _async_to_generator(function() {
1482
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
1575
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
1483
1576
  return _ts_generator(this, function(_state) {
1484
1577
  switch(_state.label){
1485
1578
  case 0:
@@ -1490,7 +1583,7 @@ function getBrowserID(clientInfo) {
1490
1583
  ];
1491
1584
  }
1492
1585
  fingerprintString = JSON.stringify(clientInfo);
1493
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
1586
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
1494
1587
  3,
1495
1588
  5
1496
1589
  ];
@@ -1528,8 +1621,7 @@ function getBrowserID(clientInfo) {
1528
1621
  ];
1529
1622
  case 3:
1530
1623
  hashBuffer = _state.sent();
1531
- hashArray = Array.from(new Uint8Array(hashBuffer));
1532
- hashHex = hashArray.map(function(b) {
1624
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
1533
1625
  return b.toString(16).padStart(2, "0");
1534
1626
  }).join("");
1535
1627
  cachedBrowserId = hashHex;
@@ -1538,8 +1630,8 @@ function getBrowserID(clientInfo) {
1538
1630
  hashHex
1539
1631
  ];
1540
1632
  case 4:
1541
- error = _state.sent();
1542
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
1633
+ unused = _state.sent();
1634
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
1543
1635
  return [
1544
1636
  3,
1545
1637
  5
@@ -1563,177 +1655,91 @@ function getBrowserID(clientInfo) {
1563
1655
  });
1564
1656
  })();
1565
1657
  }
1566
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
1567
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
1568
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
1569
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
1570
- function buildHeaders(licenseKey) {
1571
- var headers = {
1572
- "Content-Type": "application/json"
1573
- };
1574
- if (licenseKey) {
1575
- headers["Authorization"] = "Bearer ".concat(licenseKey);
1576
- }
1577
- return headers;
1658
+ function canPublish(licenseKey) {
1659
+ return Boolean(isMQTTEnabled() && licenseKey);
1578
1660
  }
1579
- function sendTrackRequest(licenseKey, body) {
1661
+ function buildPlayerMetricEvent() {
1580
1662
  return _async_to_generator(function() {
1581
- var response;
1582
- return _ts_generator(this, function(_state) {
1583
- switch(_state.label){
1584
- case 0:
1585
- return [
1586
- 4,
1587
- fetch(TRACK_URL, {
1588
- method: "POST",
1589
- headers: buildHeaders(licenseKey),
1590
- body: JSON.stringify(body)
1591
- })
1592
- ];
1593
- case 1:
1594
- response = _state.sent();
1595
- if (!response.ok) {
1596
- throw new Error("HTTP error! status: ".concat(response.status));
1597
- }
1598
- return [
1599
- 4,
1600
- response.json()
1601
- ];
1602
- case 2:
1603
- _state.sent();
1604
- return [
1605
- 2
1606
- ];
1607
- }
1608
- });
1609
- })();
1610
- }
1611
- function postJson(url, licenseKey, body) {
1612
- return _async_to_generator(function() {
1613
- var response;
1614
- return _ts_generator(this, function(_state) {
1615
- switch(_state.label){
1616
- case 0:
1617
- return [
1618
- 4,
1619
- fetch(url, {
1620
- method: "POST",
1621
- headers: buildHeaders(licenseKey),
1622
- body: JSON.stringify(body)
1623
- })
1624
- ];
1625
- case 1:
1626
- response = _state.sent();
1627
- if (!response.ok) {
1628
- throw new Error("HTTP error! status: ".concat(response.status));
1629
- }
1630
- return [
1631
- 4,
1632
- response.json()
1633
- ];
1634
- case 2:
1635
- _state.sent();
1636
- return [
1637
- 2
1638
- ];
1639
- }
1640
- });
1641
- })();
1642
- }
1643
- function buildPlayerMetricEvent(_0) {
1644
- return _async_to_generator(function(licenseKey) {
1645
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
1663
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
1646
1664
  var _arguments = arguments;
1647
1665
  return _ts_generator(this, function(_state) {
1648
1666
  switch(_state.label){
1649
1667
  case 0:
1650
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
1668
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
1651
1669
  clientInfo = getClientInfo();
1652
1670
  return [
1653
1671
  4,
1654
1672
  getBrowserID(clientInfo)
1655
1673
  ];
1656
1674
  case 1:
1657
- browserId = _state.sent();
1675
+ playerId = _state.sent();
1658
1676
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
1659
1677
  return [
1660
1678
  2,
1661
- {
1662
- player_id: browserId,
1663
- browserId: browserId,
1679
+ _object_spread({
1680
+ player_id: playerId,
1664
1681
  device_type: clientInfo.deviceType,
1665
- deviceType: clientInfo.deviceType,
1666
- input_stream_type: context.inputStreamType,
1667
- os: clientInfo.os,
1668
- ad_loaded: flags.adLoaded,
1669
- ad_detect: flags.adDetect,
1670
- license_key: licenseKey,
1671
- capture_at: captureAt,
1672
- timestamp: captureAt
1673
- }
1682
+ os: clientInfo.os.toLowerCase(),
1683
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
1684
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
1685
+ capture_at: captureAt
1686
+ }, context.inputStreamType ? {
1687
+ input_stream_type: context.inputStreamType
1688
+ } : {})
1674
1689
  ];
1675
1690
  }
1676
1691
  });
1677
1692
  }).apply(this, arguments);
1678
1693
  }
1694
+ function publishTracking(licenseKey, channel, body) {
1695
+ ensureMQTTClient();
1696
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
1697
+ }
1679
1698
  function sendInitialTracking(_0) {
1680
1699
  return _async_to_generator(function(licenseKey) {
1681
- var context, clientInfo, browserId, trackingData, error;
1700
+ var context, metricEvent, error;
1682
1701
  var _arguments = arguments;
1683
1702
  return _ts_generator(this, function(_state) {
1684
1703
  switch(_state.label){
1685
1704
  case 0:
1686
1705
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
1706
+ if (!canPublish(licenseKey)) return [
1707
+ 2
1708
+ ];
1687
1709
  _state.label = 1;
1688
1710
  case 1:
1689
1711
  _state.trys.push([
1690
1712
  1,
1691
- 4,
1713
+ 3,
1692
1714
  ,
1693
- 5
1715
+ 4
1694
1716
  ]);
1695
- clientInfo = getClientInfo();
1696
- return [
1697
- 4,
1698
- getBrowserID(clientInfo)
1699
- ];
1700
- case 2:
1701
- browserId = _state.sent();
1702
- trackingData = _object_spread({
1703
- browserId: browserId
1704
- }, clientInfo);
1705
1717
  return [
1706
1718
  4,
1707
- sendTrackRequest(licenseKey, {
1708
- events: [
1709
- {
1710
- player_id: browserId,
1711
- device_type: clientInfo.deviceType,
1712
- input_stream_type: context.inputStreamType,
1713
- os: clientInfo.os,
1714
- ad_loaded: false,
1715
- ad_detect: false,
1716
- license_key: licenseKey,
1717
- capture_at: /* @__PURE__ */ new Date().toISOString()
1718
- }
1719
- ],
1720
- trackingData: trackingData
1719
+ buildPlayerMetricEvent(context, {
1720
+ adLoaded: false,
1721
+ adDetect: false
1721
1722
  })
1722
1723
  ];
1723
- case 3:
1724
- _state.sent();
1724
+ case 2:
1725
+ metricEvent = _state.sent();
1726
+ publishTracking(licenseKey, "metrics", {
1727
+ events: [
1728
+ metricEvent
1729
+ ]
1730
+ });
1725
1731
  return [
1726
1732
  3,
1727
- 5
1733
+ 4
1728
1734
  ];
1729
- case 4:
1735
+ case 3:
1730
1736
  error = _state.sent();
1731
1737
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
1732
1738
  return [
1733
1739
  3,
1734
- 5
1740
+ 4
1735
1741
  ];
1736
- case 5:
1742
+ case 4:
1737
1743
  return [
1738
1744
  2
1739
1745
  ];
@@ -1837,53 +1843,48 @@ function sendAdImpressionTracking(_0, _1) {
1837
1843
  switch(_state.label){
1838
1844
  case 0:
1839
1845
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
1846
+ if (!canPublish(licenseKey)) return [
1847
+ 2
1848
+ ];
1840
1849
  _state.label = 1;
1841
1850
  case 1:
1842
1851
  _state.trys.push([
1843
1852
  1,
1844
- 4,
1853
+ 3,
1845
1854
  ,
1846
- 5
1855
+ 4
1847
1856
  ]);
1848
1857
  return [
1849
1858
  4,
1850
- buildPlayerMetricEvent(licenseKey, context, {
1859
+ buildPlayerMetricEvent(context, {
1851
1860
  captureAt: adImpressionInfo.timestamp
1852
1861
  })
1853
1862
  ];
1854
1863
  case 2:
1855
1864
  metricEvent = _state.sent();
1856
- return [
1857
- 4,
1858
- Promise.all([
1859
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
1860
- postJson(IMPRESSIONS_URL, licenseKey, {
1861
- events: [
1862
- {
1863
- player_id: metricEvent.player_id,
1864
- ad_played_count: 1,
1865
- ad_url: adImpressionInfo.adUrl,
1866
- license_key: licenseKey,
1867
- capture_at: adImpressionInfo.timestamp
1868
- }
1869
- ]
1870
- })
1871
- ])
1872
- ];
1873
- case 3:
1874
- _state.sent();
1865
+ publishTracking(licenseKey, "heartbeat", metricEvent);
1866
+ publishTracking(licenseKey, "impressions", {
1867
+ events: [
1868
+ {
1869
+ player_id: metricEvent.player_id,
1870
+ ad_played_count: 1,
1871
+ ad_url: adImpressionInfo.adUrl,
1872
+ capture_at: adImpressionInfo.timestamp
1873
+ }
1874
+ ]
1875
+ });
1875
1876
  return [
1876
1877
  3,
1877
- 5
1878
+ 4
1878
1879
  ];
1879
- case 4:
1880
+ case 3:
1880
1881
  error = _state.sent();
1881
1882
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
1882
1883
  return [
1883
1884
  3,
1884
- 5
1885
+ 4
1885
1886
  ];
1886
- case 5:
1887
+ case 4:
1887
1888
  return [
1888
1889
  2
1889
1890
  ];
@@ -1899,38 +1900,36 @@ function sendHeartbeat(_0) {
1899
1900
  switch(_state.label){
1900
1901
  case 0:
1901
1902
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
1903
+ if (!canPublish(licenseKey)) return [
1904
+ 2
1905
+ ];
1902
1906
  _state.label = 1;
1903
1907
  case 1:
1904
1908
  _state.trys.push([
1905
1909
  1,
1906
- 4,
1910
+ 3,
1907
1911
  ,
1908
- 5
1912
+ 4
1909
1913
  ]);
1910
1914
  return [
1911
1915
  4,
1912
- buildPlayerMetricEvent(licenseKey, context, flags)
1916
+ buildPlayerMetricEvent(context, flags)
1913
1917
  ];
1914
1918
  case 2:
1915
1919
  heartbeatData = _state.sent();
1916
- return [
1917
- 4,
1918
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
1919
- ];
1920
- case 3:
1921
- _state.sent();
1920
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
1922
1921
  return [
1923
1922
  3,
1924
- 5
1923
+ 4
1925
1924
  ];
1926
- case 4:
1925
+ case 3:
1927
1926
  error = _state.sent();
1928
1927
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
1929
1928
  return [
1930
1929
  3,
1931
- 5
1930
+ 4
1932
1931
  ];
1933
- case 5:
1932
+ case 4:
1934
1933
  return [
1935
1934
  2
1936
1935
  ];
@@ -2553,7 +2552,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
2553
2552
  var _level_details, _level_details1;
2554
2553
  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";
2555
2554
  })) !== null && _ref !== void 0 ? _ref : false;
2556
- if (!this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
2555
+ if (!this.isVmapEnabled() && !this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
2557
2556
  prerollKey = "synthetic-vod-preroll";
2558
2557
  if (!this.consumedVmapBreakIds.has(prerollKey)) {
2559
2558
  this.vmapBreaks = [
@@ -3138,12 +3137,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3138
3137
  _this.palNonce.sendPlaybackStart();
3139
3138
  }
3140
3139
  });
3141
- this.video.addEventListener("ended", function() {
3140
+ this.endedHandler = function() {
3142
3141
  if (_this.palPlaybackStarted) {
3143
3142
  _this.palPlaybackStarted = false;
3144
3143
  _this.palNonce.sendPlaybackEnd();
3145
3144
  }
3146
- });
3145
+ _this.onVideoEnded();
3146
+ };
3147
+ this.video.addEventListener("ended", this.endedHandler);
3147
3148
  this.video.addEventListener("mousedown", function(e) {
3148
3149
  _this.palNonce.sendAdTouch(e);
3149
3150
  });
@@ -4465,6 +4466,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4465
4466
  }
4466
4467
  }
4467
4468
  },
4469
+ {
4470
+ key: "isVmapEnabled",
4471
+ value: function isVmapEnabled() {
4472
+ var _this_config_vmapUrl;
4473
+ return !!(this.config.isVmap && ((_this_config_vmapUrl = this.config.vmapUrl) === null || _this_config_vmapUrl === void 0 ? void 0 : _this_config_vmapUrl.trim()));
4474
+ }
4475
+ },
4468
4476
  {
4469
4477
  key: "fetchAdConfiguration",
4470
4478
  value: function fetchAdConfiguration() {
@@ -4473,7 +4481,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4473
4481
  return _ts_generator(this, function(_state) {
4474
4482
  switch(_state.label){
4475
4483
  case 0:
4476
- if (!this.config.vmapUrl) return [
4484
+ if (!this.isVmapEnabled()) return [
4477
4485
  3,
4478
4486
  2
4479
4487
  ];
@@ -4483,7 +4491,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4483
4491
  ];
4484
4492
  case 1:
4485
4493
  _state.sent();
4486
- _state.label = 2;
4494
+ if (this.config.debugAdTiming) {
4495
+ console.log("[StormcloudVideoPlayer] VMAP mode enabled");
4496
+ }
4497
+ return [
4498
+ 2
4499
+ ];
4487
4500
  case 2:
4488
4501
  vastMode = this.config.vastMode || "default";
4489
4502
  if (this.config.debugAdTiming) {
@@ -4651,7 +4664,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4651
4664
  }
4652
4665
  return [];
4653
4666
  }
4654
- var adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
4667
+ var VMAP_NS = "http://www.iab.net/videosuite/vmap";
4668
+ var adBreakNodes = Array.from(doc.getElementsByTagNameNS(VMAP_NS, "AdBreak"));
4669
+ if (adBreakNodes.length === 0) {
4670
+ adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
4671
+ }
4672
+ if (adBreakNodes.length === 0) {
4673
+ adBreakNodes = Array.from(doc.getElementsByTagName("*")).filter(function(el) {
4674
+ return el.localName === "AdBreak";
4675
+ });
4676
+ }
4655
4677
  var parsed = [];
4656
4678
  adBreakNodes.forEach(function(node, index) {
4657
4679
  var timeOffsetRaw = (node.getAttribute("timeOffset") || "").trim();
@@ -4659,7 +4681,11 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4659
4681
  if (startTimeMs == null) {
4660
4682
  return;
4661
4683
  }
4662
- var adTagNode = node.querySelector("AdTagURI, vmap\\:AdTagURI");
4684
+ var adTagNode = node.getElementsByTagNameNS(VMAP_NS, "AdTagURI")[0];
4685
+ if (!adTagNode) {
4686
+ var _node_querySelector;
4687
+ adTagNode = (_node_querySelector = node.querySelector("AdTagURI, vmap\\:AdTagURI")) !== null && _node_querySelector !== void 0 ? _node_querySelector : void 0;
4688
+ }
4663
4689
  var adTagUrl = ((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim();
4664
4690
  if (!adTagUrl) {
4665
4691
  return;
@@ -6216,23 +6242,49 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6216
6242
  key: "onTimeUpdate",
6217
6243
  value: function onTimeUpdate(currentTimeSec) {
6218
6244
  var _this = this;
6245
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
6246
+ return;
6247
+ }
6219
6248
  if (this.adPlayer.isAdPlaying() || this.inAdBreak) return;
6220
6249
  var nowMs = currentTimeSec * 1e3;
6221
6250
  var breakToPlay = this.findBreakForTime(nowMs);
6222
6251
  if (breakToPlay) {
6223
- void this.handleMidAdJoin(breakToPlay, nowMs).catch(function(error) {
6252
+ void this.handleVmapAdBreak(breakToPlay, nowMs).catch(function(error) {
6253
+ if (_this.config.debugAdTiming) {
6254
+ console.warn("[StormcloudVideoPlayer] VMAP ad break failed gracefully:", error);
6255
+ }
6256
+ });
6257
+ }
6258
+ }
6259
+ },
6260
+ {
6261
+ key: "onVideoEnded",
6262
+ value: function onVideoEnded() {
6263
+ var _this = this;
6264
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
6265
+ return;
6266
+ }
6267
+ if (this.adPlayer.isAdPlaying() || this.inAdBreak) {
6268
+ return;
6269
+ }
6270
+ var durationMs = Number.isFinite(this.video.duration) ? Math.floor(this.video.duration * 1e3) : 0;
6271
+ var postroll = this.vmapBreaks.find(function(b) {
6272
+ return b.startTimeMs === -1 && !_this.consumedVmapBreakIds.has(_this.getAdBreakKey(b));
6273
+ });
6274
+ if (postroll) {
6275
+ void this.handleVmapAdBreak(postroll, durationMs).catch(function(error) {
6224
6276
  if (_this.config.debugAdTiming) {
6225
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP join failed gracefully:", error);
6277
+ console.warn("[StormcloudVideoPlayer] VMAP post-roll failed gracefully:", error);
6226
6278
  }
6227
6279
  });
6228
6280
  }
6229
6281
  }
6230
6282
  },
6231
6283
  {
6232
- key: "handleMidAdJoin",
6233
- value: function handleMidAdJoin(adBreak, nowMs) {
6284
+ key: "handleVmapAdBreak",
6285
+ value: function handleVmapAdBreak(adBreak, nowMs) {
6234
6286
  return _async_to_generator(function() {
6235
- var _adBreak_durationMs, _this_config_driftToleranceMs, key, breakStartMs, durationMs, endMs, tol, inWindow, remainingMs, tags, first, rest, error;
6287
+ var _adBreak_durationMs, key, breakStartMs, durationMs, endMs, inWindow, tags, first, rest, error;
6236
6288
  return _ts_generator(this, function(_state) {
6237
6289
  switch(_state.label){
6238
6290
  case 0:
@@ -6250,25 +6302,28 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6250
6302
  }
6251
6303
  durationMs = (_adBreak_durationMs = adBreak.durationMs) !== null && _adBreak_durationMs !== void 0 ? _adBreak_durationMs : 0;
6252
6304
  endMs = breakStartMs + durationMs;
6253
- tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
6254
- inWindow = durationMs > 0 ? nowMs > breakStartMs && nowMs < endMs : nowMs + tol >= breakStartMs;
6305
+ inWindow = durationMs > 0 ? nowMs >= breakStartMs && nowMs < endMs : nowMs >= breakStartMs;
6255
6306
  if (!inWindow) return [
6256
6307
  3,
6257
6308
  4
6258
6309
  ];
6259
6310
  this.consumedVmapBreakIds.add(key);
6260
- remainingMs = durationMs > 0 ? Math.max(0, endMs - nowMs) : 0;
6261
- tags = this.selectVastTagsForBreak(adBreak) || (this.apiVastTagUrl ? [
6262
- this.apiVastTagUrl
6263
- ] : void 0);
6264
- if (!(tags && tags.length > 0)) return [
6265
- 3,
6266
- 4
6267
- ];
6311
+ tags = this.selectVastTagsForBreak(adBreak);
6312
+ if (!tags || tags.length === 0) {
6313
+ return [
6314
+ 2
6315
+ ];
6316
+ }
6268
6317
  first = tags[0];
6269
6318
  rest = tags.slice(1);
6270
6319
  this.adPodQueue = rest;
6271
6320
  this.adPlayer.updateOriginalMutedState(this.video.muted, this.video.volume);
6321
+ this.showAds = true;
6322
+ this.inAdBreak = true;
6323
+ this.currentAdBreakStartWallClockMs = Date.now();
6324
+ if (!this.video.paused) {
6325
+ this.video.pause();
6326
+ }
6272
6327
  _state.label = 1;
6273
6328
  case 1:
6274
6329
  _state.trys.push([
@@ -6283,10 +6338,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6283
6338
  ];
6284
6339
  case 2:
6285
6340
  _state.sent();
6286
- this.inAdBreak = true;
6287
- this.expectedAdBreakDurationMs = remainingMs;
6288
- this.currentAdBreakStartWallClockMs = Date.now();
6289
- this.scheduleAdStopCountdown(remainingMs);
6290
6341
  return [
6291
6342
  3,
6292
6343
  4
@@ -6294,8 +6345,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6294
6345
  case 3:
6295
6346
  error = _state.sent();
6296
6347
  this.adPodQueue = [];
6348
+ this.inAdBreak = false;
6349
+ this.showAds = false;
6297
6350
  if (this.config.debugAdTiming) {
6298
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP ad request failed:", error);
6351
+ console.warn("[StormcloudVideoPlayer] VMAP ad request failed:", error);
6299
6352
  }
6300
6353
  return [
6301
6354
  3,
@@ -7007,9 +7060,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7007
7060
  {
7008
7061
  key: "findBreakForTime",
7009
7062
  value: function findBreakForTime(nowMs) {
7010
- var _this_config_driftToleranceMs;
7011
7063
  var schedule = this.vmapBreaks;
7012
- var tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7013
7064
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7014
7065
  try {
7015
7066
  for(var _iterator = schedule[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -7021,9 +7072,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7021
7072
  if (breakStartMs == null) {
7022
7073
  continue;
7023
7074
  }
7024
- var end = breakStartMs + (b.durationMs || 0);
7025
- var effectiveTol = breakStartMs === 0 ? Math.max(tol, 3e4) : tol;
7026
- if (nowMs >= breakStartMs && (b.durationMs ? nowMs < end : nowMs <= breakStartMs + effectiveTol)) {
7075
+ if (b.durationMs) {
7076
+ var end = breakStartMs + b.durationMs;
7077
+ if (nowMs >= breakStartMs && nowMs < end) {
7078
+ return b;
7079
+ }
7080
+ continue;
7081
+ }
7082
+ if (nowMs >= breakStartMs) {
7027
7083
  return b;
7028
7084
  }
7029
7085
  }
@@ -7231,6 +7287,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7231
7287
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
7232
7288
  delete this.timeUpdateHandler;
7233
7289
  }
7290
+ if (this.endedHandler) {
7291
+ this.video.removeEventListener("ended", this.endedHandler);
7292
+ delete this.endedHandler;
7293
+ }
7234
7294
  if (this.emptiedHandler) {
7235
7295
  this.video.removeEventListener("emptied", this.emptiedHandler);
7236
7296
  delete this.emptiedHandler;