stormcloud-video-player 0.3.62 → 0.3.64

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.
@@ -2430,6 +2430,99 @@ function createHlsAdPlayer(contentVideo, options) {
2430
2430
  }
2431
2431
  };
2432
2432
  }
2433
+ // src/utils/mqttConfig.ts
2434
+ var DEFAULT_MQTT_CONFIG = {
2435
+ enabled: true,
2436
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2437
+ brokerPort: 8883,
2438
+ wsPort: 8084,
2439
+ username: "for-sonifi",
2440
+ password: "sonifi-mqtt",
2441
+ topicPrefix: "adstorm/players",
2442
+ qos: 1
2443
+ };
2444
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2445
+ function isMQTTEnabled() {
2446
+ return mqttConfig.enabled;
2447
+ }
2448
+ function buildMQTTBrokerUrl() {
2449
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2450
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2451
+ }
2452
+ function buildPlayerTopic(licenseKey, channel) {
2453
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2454
+ }
2455
+ // src/utils/mqttClient.ts
2456
+ var import_mqtt = __toESM(require("mqtt"), 1);
2457
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
2458
+ var client = null;
2459
+ var status = "disconnected";
2460
+ function initMQTTClient() {
2461
+ if (client || !isMQTTEnabled()) return;
2462
+ var url = buildMQTTBrokerUrl();
2463
+ status = "connecting";
2464
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2465
+ try {
2466
+ client = import_mqtt.default.connect(url, {
2467
+ clientId: clientId,
2468
+ username: mqttConfig.username,
2469
+ password: mqttConfig.password,
2470
+ keepalive: 60,
2471
+ clean: true,
2472
+ reconnectPeriod: 5e3,
2473
+ connectTimeout: 1e4,
2474
+ queueQoSZero: false
2475
+ });
2476
+ } catch (err) {
2477
+ status = "error";
2478
+ console.warn("".concat(LOG, " connect() threw:"), err);
2479
+ return;
2480
+ }
2481
+ client.on("connect", function() {
2482
+ status = "connected";
2483
+ console.info("".concat(LOG, " connected to ").concat(url));
2484
+ });
2485
+ client.on("reconnect", function() {
2486
+ status = "connecting";
2487
+ console.info("".concat(LOG, " reconnecting…"));
2488
+ });
2489
+ client.on("offline", function() {
2490
+ status = "disconnected";
2491
+ console.warn("".concat(LOG, " offline"));
2492
+ });
2493
+ client.on("error", function(err) {
2494
+ status = "error";
2495
+ console.warn("".concat(LOG, " error:"), err.message);
2496
+ });
2497
+ client.on("close", function() {
2498
+ if (status === "connected") {
2499
+ status = "disconnected";
2500
+ }
2501
+ });
2502
+ }
2503
+ function ensureMQTTClient() {
2504
+ if (isMQTTEnabled() && !client) {
2505
+ initMQTTClient();
2506
+ }
2507
+ }
2508
+ function publishMQTT(topic, payload) {
2509
+ if (!isMQTTEnabled()) {
2510
+ return false;
2511
+ }
2512
+ ensureMQTTClient();
2513
+ if (!client) {
2514
+ return false;
2515
+ }
2516
+ try {
2517
+ client.publish(topic, JSON.stringify(payload), {
2518
+ qos: mqttConfig.qos
2519
+ });
2520
+ return true;
2521
+ } catch (err) {
2522
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2523
+ return false;
2524
+ }
2525
+ }
2433
2526
  // src/utils/tracking.ts
2434
2527
  var cachedBrowserId = null;
2435
2528
  function getClientInfo() {
@@ -2576,7 +2669,7 @@ function getClientInfo() {
2576
2669
  }
2577
2670
  function getBrowserID(clientInfo) {
2578
2671
  return _async_to_generator(function() {
2579
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
2672
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
2580
2673
  return _ts_generator(this, function(_state) {
2581
2674
  switch(_state.label){
2582
2675
  case 0:
@@ -2587,7 +2680,7 @@ function getBrowserID(clientInfo) {
2587
2680
  ];
2588
2681
  }
2589
2682
  fingerprintString = JSON.stringify(clientInfo);
2590
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
2683
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
2591
2684
  3,
2592
2685
  5
2593
2686
  ];
@@ -2625,8 +2718,7 @@ function getBrowserID(clientInfo) {
2625
2718
  ];
2626
2719
  case 3:
2627
2720
  hashBuffer = _state.sent();
2628
- hashArray = Array.from(new Uint8Array(hashBuffer));
2629
- hashHex = hashArray.map(function(b) {
2721
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
2630
2722
  return b.toString(16).padStart(2, "0");
2631
2723
  }).join("");
2632
2724
  cachedBrowserId = hashHex;
@@ -2635,8 +2727,8 @@ function getBrowserID(clientInfo) {
2635
2727
  hashHex
2636
2728
  ];
2637
2729
  case 4:
2638
- error = _state.sent();
2639
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
2730
+ unused = _state.sent();
2731
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
2640
2732
  return [
2641
2733
  3,
2642
2734
  5
@@ -2660,177 +2752,91 @@ function getBrowserID(clientInfo) {
2660
2752
  });
2661
2753
  })();
2662
2754
  }
2663
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
2664
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
2665
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
2666
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
2667
- function buildHeaders(licenseKey) {
2668
- var headers = {
2669
- "Content-Type": "application/json"
2670
- };
2671
- if (licenseKey) {
2672
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2673
- }
2674
- return headers;
2675
- }
2676
- function sendTrackRequest(licenseKey, body) {
2677
- return _async_to_generator(function() {
2678
- var response;
2679
- return _ts_generator(this, function(_state) {
2680
- switch(_state.label){
2681
- case 0:
2682
- return [
2683
- 4,
2684
- fetch(TRACK_URL, {
2685
- method: "POST",
2686
- headers: buildHeaders(licenseKey),
2687
- body: JSON.stringify(body)
2688
- })
2689
- ];
2690
- case 1:
2691
- response = _state.sent();
2692
- if (!response.ok) {
2693
- throw new Error("HTTP error! status: ".concat(response.status));
2694
- }
2695
- return [
2696
- 4,
2697
- response.json()
2698
- ];
2699
- case 2:
2700
- _state.sent();
2701
- return [
2702
- 2
2703
- ];
2704
- }
2705
- });
2706
- })();
2755
+ function canPublish(licenseKey) {
2756
+ return Boolean(isMQTTEnabled() && licenseKey);
2707
2757
  }
2708
- function postJson(url, licenseKey, body) {
2758
+ function buildPlayerMetricEvent() {
2709
2759
  return _async_to_generator(function() {
2710
- var response;
2711
- return _ts_generator(this, function(_state) {
2712
- switch(_state.label){
2713
- case 0:
2714
- return [
2715
- 4,
2716
- fetch(url, {
2717
- method: "POST",
2718
- headers: buildHeaders(licenseKey),
2719
- body: JSON.stringify(body)
2720
- })
2721
- ];
2722
- case 1:
2723
- response = _state.sent();
2724
- if (!response.ok) {
2725
- throw new Error("HTTP error! status: ".concat(response.status));
2726
- }
2727
- return [
2728
- 4,
2729
- response.json()
2730
- ];
2731
- case 2:
2732
- _state.sent();
2733
- return [
2734
- 2
2735
- ];
2736
- }
2737
- });
2738
- })();
2739
- }
2740
- function buildPlayerMetricEvent(_0) {
2741
- return _async_to_generator(function(licenseKey) {
2742
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
2760
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
2743
2761
  var _arguments = arguments;
2744
2762
  return _ts_generator(this, function(_state) {
2745
2763
  switch(_state.label){
2746
2764
  case 0:
2747
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2765
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2748
2766
  clientInfo = getClientInfo();
2749
2767
  return [
2750
2768
  4,
2751
2769
  getBrowserID(clientInfo)
2752
2770
  ];
2753
2771
  case 1:
2754
- browserId = _state.sent();
2772
+ playerId = _state.sent();
2755
2773
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
2756
2774
  return [
2757
2775
  2,
2758
- {
2759
- player_id: browserId,
2760
- browserId: browserId,
2776
+ _object_spread({
2777
+ player_id: playerId,
2761
2778
  device_type: clientInfo.deviceType,
2762
- deviceType: clientInfo.deviceType,
2763
- input_stream_type: context.inputStreamType,
2764
- os: clientInfo.os,
2765
- ad_loaded: flags.adLoaded,
2766
- ad_detect: flags.adDetect,
2767
- license_key: licenseKey,
2768
- capture_at: captureAt,
2769
- timestamp: captureAt
2770
- }
2779
+ os: clientInfo.os.toLowerCase(),
2780
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
2781
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
2782
+ capture_at: captureAt
2783
+ }, context.inputStreamType ? {
2784
+ input_stream_type: context.inputStreamType
2785
+ } : {})
2771
2786
  ];
2772
2787
  }
2773
2788
  });
2774
2789
  }).apply(this, arguments);
2775
2790
  }
2791
+ function publishTracking(licenseKey, channel, body) {
2792
+ ensureMQTTClient();
2793
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
2794
+ }
2776
2795
  function sendInitialTracking(_0) {
2777
2796
  return _async_to_generator(function(licenseKey) {
2778
- var context, clientInfo, browserId, trackingData, error;
2797
+ var context, metricEvent, error;
2779
2798
  var _arguments = arguments;
2780
2799
  return _ts_generator(this, function(_state) {
2781
2800
  switch(_state.label){
2782
2801
  case 0:
2783
2802
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2803
+ if (!canPublish(licenseKey)) return [
2804
+ 2
2805
+ ];
2784
2806
  _state.label = 1;
2785
2807
  case 1:
2786
2808
  _state.trys.push([
2787
2809
  1,
2788
- 4,
2810
+ 3,
2789
2811
  ,
2790
- 5
2812
+ 4
2791
2813
  ]);
2792
- clientInfo = getClientInfo();
2793
2814
  return [
2794
2815
  4,
2795
- getBrowserID(clientInfo)
2796
- ];
2797
- case 2:
2798
- browserId = _state.sent();
2799
- trackingData = _object_spread({
2800
- browserId: browserId
2801
- }, clientInfo);
2802
- return [
2803
- 4,
2804
- sendTrackRequest(licenseKey, {
2805
- events: [
2806
- {
2807
- player_id: browserId,
2808
- device_type: clientInfo.deviceType,
2809
- input_stream_type: context.inputStreamType,
2810
- os: clientInfo.os,
2811
- ad_loaded: false,
2812
- ad_detect: false,
2813
- license_key: licenseKey,
2814
- capture_at: /* @__PURE__ */ new Date().toISOString()
2815
- }
2816
- ],
2817
- trackingData: trackingData
2816
+ buildPlayerMetricEvent(context, {
2817
+ adLoaded: false,
2818
+ adDetect: false
2818
2819
  })
2819
2820
  ];
2820
- case 3:
2821
- _state.sent();
2821
+ case 2:
2822
+ metricEvent = _state.sent();
2823
+ publishTracking(licenseKey, "metrics", {
2824
+ events: [
2825
+ metricEvent
2826
+ ]
2827
+ });
2822
2828
  return [
2823
2829
  3,
2824
- 5
2830
+ 4
2825
2831
  ];
2826
- case 4:
2832
+ case 3:
2827
2833
  error = _state.sent();
2828
2834
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
2829
2835
  return [
2830
2836
  3,
2831
- 5
2837
+ 4
2832
2838
  ];
2833
- case 5:
2839
+ case 4:
2834
2840
  return [
2835
2841
  2
2836
2842
  ];
@@ -2934,53 +2940,48 @@ function sendAdImpressionTracking(_0, _1) {
2934
2940
  switch(_state.label){
2935
2941
  case 0:
2936
2942
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2943
+ if (!canPublish(licenseKey)) return [
2944
+ 2
2945
+ ];
2937
2946
  _state.label = 1;
2938
2947
  case 1:
2939
2948
  _state.trys.push([
2940
2949
  1,
2941
- 4,
2950
+ 3,
2942
2951
  ,
2943
- 5
2952
+ 4
2944
2953
  ]);
2945
2954
  return [
2946
2955
  4,
2947
- buildPlayerMetricEvent(licenseKey, context, {
2956
+ buildPlayerMetricEvent(context, {
2948
2957
  captureAt: adImpressionInfo.timestamp
2949
2958
  })
2950
2959
  ];
2951
2960
  case 2:
2952
2961
  metricEvent = _state.sent();
2953
- return [
2954
- 4,
2955
- Promise.all([
2956
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
2957
- postJson(IMPRESSIONS_URL, licenseKey, {
2958
- events: [
2959
- {
2960
- player_id: metricEvent.player_id,
2961
- ad_played_count: 1,
2962
- ad_url: adImpressionInfo.adUrl,
2963
- license_key: licenseKey,
2964
- capture_at: adImpressionInfo.timestamp
2965
- }
2966
- ]
2967
- })
2968
- ])
2969
- ];
2970
- case 3:
2971
- _state.sent();
2962
+ publishTracking(licenseKey, "heartbeat", metricEvent);
2963
+ publishTracking(licenseKey, "impressions", {
2964
+ events: [
2965
+ {
2966
+ player_id: metricEvent.player_id,
2967
+ ad_played_count: 1,
2968
+ ad_url: adImpressionInfo.adUrl,
2969
+ capture_at: adImpressionInfo.timestamp
2970
+ }
2971
+ ]
2972
+ });
2972
2973
  return [
2973
2974
  3,
2974
- 5
2975
+ 4
2975
2976
  ];
2976
- case 4:
2977
+ case 3:
2977
2978
  error = _state.sent();
2978
2979
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
2979
2980
  return [
2980
2981
  3,
2981
- 5
2982
+ 4
2982
2983
  ];
2983
- case 5:
2984
+ case 4:
2984
2985
  return [
2985
2986
  2
2986
2987
  ];
@@ -2996,38 +2997,36 @@ function sendHeartbeat(_0) {
2996
2997
  switch(_state.label){
2997
2998
  case 0:
2998
2999
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
3000
+ if (!canPublish(licenseKey)) return [
3001
+ 2
3002
+ ];
2999
3003
  _state.label = 1;
3000
3004
  case 1:
3001
3005
  _state.trys.push([
3002
3006
  1,
3003
- 4,
3007
+ 3,
3004
3008
  ,
3005
- 5
3009
+ 4
3006
3010
  ]);
3007
3011
  return [
3008
3012
  4,
3009
- buildPlayerMetricEvent(licenseKey, context, flags)
3013
+ buildPlayerMetricEvent(context, flags)
3010
3014
  ];
3011
3015
  case 2:
3012
3016
  heartbeatData = _state.sent();
3013
- return [
3014
- 4,
3015
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
3016
- ];
3017
- case 3:
3018
- _state.sent();
3017
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
3019
3018
  return [
3020
3019
  3,
3021
- 5
3020
+ 4
3022
3021
  ];
3023
- case 4:
3022
+ case 3:
3024
3023
  error = _state.sent();
3025
3024
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3026
3025
  return [
3027
3026
  3,
3028
- 5
3027
+ 4
3029
3028
  ];
3030
- case 5:
3029
+ case 4:
3031
3030
  return [
3032
3031
  2
3033
3032
  ];
@@ -3492,7 +3491,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3492
3491
  var _level_details, _level_details1;
3493
3492
  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";
3494
3493
  })) !== null && _ref !== void 0 ? _ref : false;
3495
- if (!this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
3494
+ if (!this.isVmapEnabled() && !this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
3496
3495
  prerollKey = "synthetic-vod-preroll";
3497
3496
  if (!this.consumedVmapBreakIds.has(prerollKey)) {
3498
3497
  this.vmapBreaks = [
@@ -4075,6 +4074,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4075
4074
  _this.onTimeUpdate(_this.video.currentTime);
4076
4075
  };
4077
4076
  this.video.addEventListener("timeupdate", this.timeUpdateHandler);
4077
+ this.endedHandler = function() {
4078
+ _this.onVideoEnded();
4079
+ };
4080
+ this.video.addEventListener("ended", this.endedHandler);
4078
4081
  this.emptiedHandler = function() {
4079
4082
  if (_this.nativeHlsMode && _this.videoSrcProtection && !_this.ima.isAdPlaying()) {
4080
4083
  if (_this.config.debugAdTiming) {
@@ -5386,6 +5389,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5386
5389
  }
5387
5390
  }
5388
5391
  },
5392
+ {
5393
+ key: "isVmapEnabled",
5394
+ value: function isVmapEnabled() {
5395
+ var _this_config_vmapUrl;
5396
+ return !!(this.config.isVmap && ((_this_config_vmapUrl = this.config.vmapUrl) === null || _this_config_vmapUrl === void 0 ? void 0 : _this_config_vmapUrl.trim()));
5397
+ }
5398
+ },
5389
5399
  {
5390
5400
  key: "fetchAdConfiguration",
5391
5401
  value: function fetchAdConfiguration() {
@@ -5394,7 +5404,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5394
5404
  return _ts_generator(this, function(_state) {
5395
5405
  switch(_state.label){
5396
5406
  case 0:
5397
- if (!this.config.vmapUrl) return [
5407
+ if (!this.isVmapEnabled()) return [
5398
5408
  3,
5399
5409
  2
5400
5410
  ];
@@ -5404,7 +5414,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5404
5414
  ];
5405
5415
  case 1:
5406
5416
  _state.sent();
5407
- _state.label = 2;
5417
+ if (this.config.debugAdTiming) {
5418
+ console.log("[StormcloudVideoPlayer] VMAP mode enabled");
5419
+ }
5420
+ return [
5421
+ 2
5422
+ ];
5408
5423
  case 2:
5409
5424
  vastMode = this.config.vastMode || "default";
5410
5425
  if (this.config.debugAdTiming) {
@@ -5572,7 +5587,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5572
5587
  }
5573
5588
  return [];
5574
5589
  }
5575
- var adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
5590
+ var VMAP_NS = "http://www.iab.net/videosuite/vmap";
5591
+ var adBreakNodes = Array.from(doc.getElementsByTagNameNS(VMAP_NS, "AdBreak"));
5592
+ if (adBreakNodes.length === 0) {
5593
+ adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
5594
+ }
5595
+ if (adBreakNodes.length === 0) {
5596
+ adBreakNodes = Array.from(doc.getElementsByTagName("*")).filter(function(el) {
5597
+ return el.localName === "AdBreak";
5598
+ });
5599
+ }
5576
5600
  var parsed = [];
5577
5601
  adBreakNodes.forEach(function(node, index) {
5578
5602
  var timeOffsetRaw = (node.getAttribute("timeOffset") || "").trim();
@@ -5580,8 +5604,17 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5580
5604
  if (startTimeMs == null) {
5581
5605
  return;
5582
5606
  }
5583
- var adTagNode = node.querySelector("AdTagURI, vmap\\:AdTagURI");
5584
- var adTagUrl = ((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim();
5607
+ var adTagNode = node.getElementsByTagNameNS(VMAP_NS, "AdTagURI")[0];
5608
+ if (!adTagNode) {
5609
+ var _node_querySelector;
5610
+ adTagNode = (_node_querySelector = node.querySelector("AdTagURI, vmap\\:AdTagURI")) !== null && _node_querySelector !== void 0 ? _node_querySelector : void 0;
5611
+ }
5612
+ if (!adTagNode) {
5613
+ adTagNode = Array.from(node.getElementsByTagName("*")).find(function(el) {
5614
+ return el.localName === "AdTagURI";
5615
+ });
5616
+ }
5617
+ var adTagUrl = _this.resolveVmapAdTagUrl(((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim());
5585
5618
  if (!adTagUrl) {
5586
5619
  return;
5587
5620
  }
@@ -5600,6 +5633,15 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5600
5633
  return parsed;
5601
5634
  }
5602
5635
  },
5636
+ {
5637
+ key: "resolveVmapAdTagUrl",
5638
+ value: function resolveVmapAdTagUrl(url) {
5639
+ if (!url) {
5640
+ return "";
5641
+ }
5642
+ return url.replace(/\[timestamp\]/gi, String(Date.now())).replace(/\$\{GDPR\}/gi, "0").trim();
5643
+ }
5644
+ },
5603
5645
  {
5604
5646
  key: "parseVmapTimeOffsetToMs",
5605
5647
  value: function parseVmapTimeOffsetToMs(timeOffset) {
@@ -5622,6 +5664,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5622
5664
  var millis = Number(ms.padEnd(3, "0").slice(0, 3));
5623
5665
  return (hours * 3600 + minutes * 60 + seconds) * 1e3 + millis;
5624
5666
  }
5667
+ var msOnly = timeOffset.match(/^(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
5668
+ if (msOnly) {
5669
+ var _msOnly = _sliced_to_array(msOnly, 4), mm1 = _msOnly[1], ss1 = _msOnly[2], tmp1 = _msOnly[3], ms1 = tmp1 === void 0 ? "0" : tmp1;
5670
+ var minutes1 = Number(mm1);
5671
+ var seconds1 = Number(ss1);
5672
+ var millis1 = Number(ms1.padEnd(3, "0").slice(0, 3));
5673
+ return (minutes1 * 60 + seconds1) * 1e3 + millis1;
5674
+ }
5625
5675
  var percent = timeOffset.match(/^(\d+(?:\.\d+)?)%$/);
5626
5676
  if (percent) {
5627
5677
  var ratio = Number(percent[1]) / 100;
@@ -7137,23 +7187,49 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7137
7187
  key: "onTimeUpdate",
7138
7188
  value: function onTimeUpdate(currentTimeSec) {
7139
7189
  var _this = this;
7190
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
7191
+ return;
7192
+ }
7140
7193
  if (this.ima.isAdPlaying() || this.inAdBreak) return;
7141
7194
  var nowMs = currentTimeSec * 1e3;
7142
7195
  var breakToPlay = this.findBreakForTime(nowMs);
7143
7196
  if (breakToPlay) {
7144
- void this.handleMidAdJoin(breakToPlay, nowMs).catch(function(error) {
7197
+ void this.handleVmapAdBreak(breakToPlay, nowMs).catch(function(error) {
7145
7198
  if (_this.config.debugAdTiming) {
7146
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP join failed gracefully:", error);
7199
+ console.warn("[StormcloudVideoPlayer] VMAP ad break failed gracefully:", error);
7147
7200
  }
7148
7201
  });
7149
7202
  }
7150
7203
  }
7151
7204
  },
7152
7205
  {
7153
- key: "handleMidAdJoin",
7154
- value: function handleMidAdJoin(adBreak, nowMs) {
7206
+ key: "onVideoEnded",
7207
+ value: function onVideoEnded() {
7208
+ var _this = this;
7209
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
7210
+ return;
7211
+ }
7212
+ if (this.ima.isAdPlaying() || this.inAdBreak) {
7213
+ return;
7214
+ }
7215
+ var durationMs = Number.isFinite(this.video.duration) ? Math.floor(this.video.duration * 1e3) : 0;
7216
+ var postroll = this.vmapBreaks.find(function(b) {
7217
+ return b.startTimeMs === -1 && !_this.consumedVmapBreakIds.has(_this.getAdBreakKey(b));
7218
+ });
7219
+ if (postroll) {
7220
+ void this.handleVmapAdBreak(postroll, durationMs).catch(function(error) {
7221
+ if (_this.config.debugAdTiming) {
7222
+ console.warn("[StormcloudVideoPlayer] VMAP post-roll failed gracefully:", error);
7223
+ }
7224
+ });
7225
+ }
7226
+ }
7227
+ },
7228
+ {
7229
+ key: "handleVmapAdBreak",
7230
+ value: function handleVmapAdBreak(adBreak, nowMs) {
7155
7231
  return _async_to_generator(function() {
7156
- var _adBreak_durationMs, _this_config_driftToleranceMs, key, breakStartMs, durationMs, endMs, tol, inWindow, remainingMs, tags, first, rest, error;
7232
+ var _adBreak_durationMs, key, breakStartMs, durationMs, endMs, inWindow, tags, first, rest, error;
7157
7233
  return _ts_generator(this, function(_state) {
7158
7234
  switch(_state.label){
7159
7235
  case 0:
@@ -7171,25 +7247,28 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7171
7247
  }
7172
7248
  durationMs = (_adBreak_durationMs = adBreak.durationMs) !== null && _adBreak_durationMs !== void 0 ? _adBreak_durationMs : 0;
7173
7249
  endMs = breakStartMs + durationMs;
7174
- tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7175
- inWindow = durationMs > 0 ? nowMs > breakStartMs && nowMs < endMs : nowMs + tol >= breakStartMs;
7250
+ inWindow = durationMs > 0 ? nowMs >= breakStartMs && nowMs < endMs : nowMs >= breakStartMs;
7176
7251
  if (!inWindow) return [
7177
7252
  3,
7178
7253
  4
7179
7254
  ];
7180
7255
  this.consumedVmapBreakIds.add(key);
7181
- remainingMs = durationMs > 0 ? Math.max(0, endMs - nowMs) : 0;
7182
- tags = this.selectVastTagsForBreak(adBreak) || (this.apiVastTagUrl ? [
7183
- this.apiVastTagUrl
7184
- ] : void 0);
7185
- if (!(tags && tags.length > 0)) return [
7186
- 3,
7187
- 4
7188
- ];
7256
+ tags = this.selectVastTagsForBreak(adBreak);
7257
+ if (!tags || tags.length === 0) {
7258
+ return [
7259
+ 2
7260
+ ];
7261
+ }
7189
7262
  first = tags[0];
7190
7263
  rest = tags.slice(1);
7191
7264
  this.adPodQueue = rest;
7192
7265
  this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
7266
+ this.showAds = true;
7267
+ this.inAdBreak = true;
7268
+ this.currentAdBreakStartWallClockMs = Date.now();
7269
+ if (!this.video.paused) {
7270
+ this.video.pause();
7271
+ }
7193
7272
  _state.label = 1;
7194
7273
  case 1:
7195
7274
  _state.trys.push([
@@ -7204,10 +7283,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7204
7283
  ];
7205
7284
  case 2:
7206
7285
  _state.sent();
7207
- this.inAdBreak = true;
7208
- this.expectedAdBreakDurationMs = remainingMs;
7209
- this.currentAdBreakStartWallClockMs = Date.now();
7210
- this.scheduleAdStopCountdown(remainingMs);
7211
7286
  return [
7212
7287
  3,
7213
7288
  4
@@ -7215,8 +7290,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7215
7290
  case 3:
7216
7291
  error = _state.sent();
7217
7292
  this.adPodQueue = [];
7293
+ this.inAdBreak = false;
7294
+ this.showAds = false;
7218
7295
  if (this.config.debugAdTiming) {
7219
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP ad request failed:", error);
7296
+ console.warn("[StormcloudVideoPlayer] VMAP ad request failed:", error);
7220
7297
  }
7221
7298
  return [
7222
7299
  3,
@@ -7887,16 +7964,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7887
7964
  {
7888
7965
  key: "selectVastTagsForBreak",
7889
7966
  value: function selectVastTagsForBreak(b) {
7967
+ var _this = this;
7890
7968
  if (!b || !b.vastTagUrl) return void 0;
7891
- if (b.vastTagUrl.includes(",")) {
7892
- return b.vastTagUrl.split(",").map(function(s) {
7893
- return s.trim();
7969
+ var resolvedUrl = this.resolveVmapAdTagUrl(b.vastTagUrl);
7970
+ if (resolvedUrl.includes(",")) {
7971
+ return resolvedUrl.split(",").map(function(s) {
7972
+ return _this.resolveVmapAdTagUrl(s.trim());
7894
7973
  }).filter(function(s) {
7895
7974
  return s.length > 0;
7896
7975
  });
7897
7976
  }
7898
7977
  return [
7899
- b.vastTagUrl
7978
+ resolvedUrl
7900
7979
  ];
7901
7980
  }
7902
7981
  },
@@ -7928,9 +8007,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7928
8007
  {
7929
8008
  key: "findBreakForTime",
7930
8009
  value: function findBreakForTime(nowMs) {
7931
- var _this_config_driftToleranceMs;
7932
8010
  var schedule = this.vmapBreaks;
7933
- var tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7934
8011
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7935
8012
  try {
7936
8013
  for(var _iterator = schedule[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -7942,9 +8019,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7942
8019
  if (breakStartMs == null) {
7943
8020
  continue;
7944
8021
  }
7945
- var end = breakStartMs + (b.durationMs || 0);
7946
- var effectiveTol = breakStartMs === 0 ? Math.max(tol, 3e4) : tol;
7947
- if (nowMs >= breakStartMs && (b.durationMs ? nowMs < end : nowMs <= breakStartMs + effectiveTol)) {
8022
+ if (b.durationMs) {
8023
+ var end = breakStartMs + b.durationMs;
8024
+ if (nowMs >= breakStartMs && nowMs < end) {
8025
+ return b;
8026
+ }
8027
+ continue;
8028
+ }
8029
+ if (nowMs >= breakStartMs) {
7948
8030
  return b;
7949
8031
  }
7950
8032
  }
@@ -8152,6 +8234,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8152
8234
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
8153
8235
  delete this.timeUpdateHandler;
8154
8236
  }
8237
+ if (this.endedHandler) {
8238
+ this.video.removeEventListener("ended", this.endedHandler);
8239
+ delete this.endedHandler;
8240
+ }
8155
8241
  if (this.emptiedHandler) {
8156
8242
  this.video.removeEventListener("emptied", this.emptiedHandler);
8157
8243
  delete this.emptiedHandler;