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.
@@ -2378,6 +2378,99 @@ function createHlsAdPlayer(contentVideo, options) {
2378
2378
  }
2379
2379
  };
2380
2380
  }
2381
+ // src/utils/mqttConfig.ts
2382
+ var DEFAULT_MQTT_CONFIG = {
2383
+ enabled: true,
2384
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2385
+ brokerPort: 8883,
2386
+ wsPort: 8084,
2387
+ username: "for-sonifi",
2388
+ password: "sonifi-mqtt",
2389
+ topicPrefix: "adstorm/players",
2390
+ qos: 1
2391
+ };
2392
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2393
+ function isMQTTEnabled() {
2394
+ return mqttConfig.enabled;
2395
+ }
2396
+ function buildMQTTBrokerUrl() {
2397
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2398
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2399
+ }
2400
+ function buildPlayerTopic(licenseKey, channel) {
2401
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2402
+ }
2403
+ // src/utils/mqttClient.ts
2404
+ var import_mqtt = __toESM(require("mqtt"), 1);
2405
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
2406
+ var client = null;
2407
+ var status = "disconnected";
2408
+ function initMQTTClient() {
2409
+ if (client || !isMQTTEnabled()) return;
2410
+ var url = buildMQTTBrokerUrl();
2411
+ status = "connecting";
2412
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2413
+ try {
2414
+ client = import_mqtt.default.connect(url, {
2415
+ clientId: clientId,
2416
+ username: mqttConfig.username,
2417
+ password: mqttConfig.password,
2418
+ keepalive: 60,
2419
+ clean: true,
2420
+ reconnectPeriod: 5e3,
2421
+ connectTimeout: 1e4,
2422
+ queueQoSZero: false
2423
+ });
2424
+ } catch (err) {
2425
+ status = "error";
2426
+ console.warn("".concat(LOG, " connect() threw:"), err);
2427
+ return;
2428
+ }
2429
+ client.on("connect", function() {
2430
+ status = "connected";
2431
+ console.info("".concat(LOG, " connected to ").concat(url));
2432
+ });
2433
+ client.on("reconnect", function() {
2434
+ status = "connecting";
2435
+ console.info("".concat(LOG, " reconnecting…"));
2436
+ });
2437
+ client.on("offline", function() {
2438
+ status = "disconnected";
2439
+ console.warn("".concat(LOG, " offline"));
2440
+ });
2441
+ client.on("error", function(err) {
2442
+ status = "error";
2443
+ console.warn("".concat(LOG, " error:"), err.message);
2444
+ });
2445
+ client.on("close", function() {
2446
+ if (status === "connected") {
2447
+ status = "disconnected";
2448
+ }
2449
+ });
2450
+ }
2451
+ function ensureMQTTClient() {
2452
+ if (isMQTTEnabled() && !client) {
2453
+ initMQTTClient();
2454
+ }
2455
+ }
2456
+ function publishMQTT(topic, payload) {
2457
+ if (!isMQTTEnabled()) {
2458
+ return false;
2459
+ }
2460
+ ensureMQTTClient();
2461
+ if (!client) {
2462
+ return false;
2463
+ }
2464
+ try {
2465
+ client.publish(topic, JSON.stringify(payload), {
2466
+ qos: mqttConfig.qos
2467
+ });
2468
+ return true;
2469
+ } catch (err) {
2470
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2471
+ return false;
2472
+ }
2473
+ }
2381
2474
  // src/utils/tracking.ts
2382
2475
  var cachedBrowserId = null;
2383
2476
  function getClientInfo() {
@@ -2524,7 +2617,7 @@ function getClientInfo() {
2524
2617
  }
2525
2618
  function getBrowserID(clientInfo) {
2526
2619
  return _async_to_generator(function() {
2527
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
2620
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
2528
2621
  return _ts_generator(this, function(_state) {
2529
2622
  switch(_state.label){
2530
2623
  case 0:
@@ -2535,7 +2628,7 @@ function getBrowserID(clientInfo) {
2535
2628
  ];
2536
2629
  }
2537
2630
  fingerprintString = JSON.stringify(clientInfo);
2538
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
2631
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
2539
2632
  3,
2540
2633
  5
2541
2634
  ];
@@ -2573,8 +2666,7 @@ function getBrowserID(clientInfo) {
2573
2666
  ];
2574
2667
  case 3:
2575
2668
  hashBuffer = _state.sent();
2576
- hashArray = Array.from(new Uint8Array(hashBuffer));
2577
- hashHex = hashArray.map(function(b) {
2669
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
2578
2670
  return b.toString(16).padStart(2, "0");
2579
2671
  }).join("");
2580
2672
  cachedBrowserId = hashHex;
@@ -2583,8 +2675,8 @@ function getBrowserID(clientInfo) {
2583
2675
  hashHex
2584
2676
  ];
2585
2677
  case 4:
2586
- error = _state.sent();
2587
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
2678
+ unused = _state.sent();
2679
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
2588
2680
  return [
2589
2681
  3,
2590
2682
  5
@@ -2608,177 +2700,91 @@ function getBrowserID(clientInfo) {
2608
2700
  });
2609
2701
  })();
2610
2702
  }
2611
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
2612
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
2613
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
2614
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
2615
- function buildHeaders(licenseKey) {
2616
- var headers = {
2617
- "Content-Type": "application/json"
2618
- };
2619
- if (licenseKey) {
2620
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2621
- }
2622
- return headers;
2623
- }
2624
- function sendTrackRequest(licenseKey, body) {
2625
- return _async_to_generator(function() {
2626
- var response;
2627
- return _ts_generator(this, function(_state) {
2628
- switch(_state.label){
2629
- case 0:
2630
- return [
2631
- 4,
2632
- fetch(TRACK_URL, {
2633
- method: "POST",
2634
- headers: buildHeaders(licenseKey),
2635
- body: JSON.stringify(body)
2636
- })
2637
- ];
2638
- case 1:
2639
- response = _state.sent();
2640
- if (!response.ok) {
2641
- throw new Error("HTTP error! status: ".concat(response.status));
2642
- }
2643
- return [
2644
- 4,
2645
- response.json()
2646
- ];
2647
- case 2:
2648
- _state.sent();
2649
- return [
2650
- 2
2651
- ];
2652
- }
2653
- });
2654
- })();
2703
+ function canPublish(licenseKey) {
2704
+ return Boolean(isMQTTEnabled() && licenseKey);
2655
2705
  }
2656
- function postJson(url, licenseKey, body) {
2706
+ function buildPlayerMetricEvent() {
2657
2707
  return _async_to_generator(function() {
2658
- var response;
2659
- return _ts_generator(this, function(_state) {
2660
- switch(_state.label){
2661
- case 0:
2662
- return [
2663
- 4,
2664
- fetch(url, {
2665
- method: "POST",
2666
- headers: buildHeaders(licenseKey),
2667
- body: JSON.stringify(body)
2668
- })
2669
- ];
2670
- case 1:
2671
- response = _state.sent();
2672
- if (!response.ok) {
2673
- throw new Error("HTTP error! status: ".concat(response.status));
2674
- }
2675
- return [
2676
- 4,
2677
- response.json()
2678
- ];
2679
- case 2:
2680
- _state.sent();
2681
- return [
2682
- 2
2683
- ];
2684
- }
2685
- });
2686
- })();
2687
- }
2688
- function buildPlayerMetricEvent(_0) {
2689
- return _async_to_generator(function(licenseKey) {
2690
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
2708
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
2691
2709
  var _arguments = arguments;
2692
2710
  return _ts_generator(this, function(_state) {
2693
2711
  switch(_state.label){
2694
2712
  case 0:
2695
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2713
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2696
2714
  clientInfo = getClientInfo();
2697
2715
  return [
2698
2716
  4,
2699
2717
  getBrowserID(clientInfo)
2700
2718
  ];
2701
2719
  case 1:
2702
- browserId = _state.sent();
2720
+ playerId = _state.sent();
2703
2721
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
2704
2722
  return [
2705
2723
  2,
2706
- {
2707
- player_id: browserId,
2708
- browserId: browserId,
2724
+ _object_spread({
2725
+ player_id: playerId,
2709
2726
  device_type: clientInfo.deviceType,
2710
- deviceType: clientInfo.deviceType,
2711
- input_stream_type: context.inputStreamType,
2712
- os: clientInfo.os,
2713
- ad_loaded: flags.adLoaded,
2714
- ad_detect: flags.adDetect,
2715
- license_key: licenseKey,
2716
- capture_at: captureAt,
2717
- timestamp: captureAt
2718
- }
2727
+ os: clientInfo.os.toLowerCase(),
2728
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
2729
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
2730
+ capture_at: captureAt
2731
+ }, context.inputStreamType ? {
2732
+ input_stream_type: context.inputStreamType
2733
+ } : {})
2719
2734
  ];
2720
2735
  }
2721
2736
  });
2722
2737
  }).apply(this, arguments);
2723
2738
  }
2739
+ function publishTracking(licenseKey, channel, body) {
2740
+ ensureMQTTClient();
2741
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
2742
+ }
2724
2743
  function sendInitialTracking(_0) {
2725
2744
  return _async_to_generator(function(licenseKey) {
2726
- var context, clientInfo, browserId, trackingData, error;
2745
+ var context, metricEvent, error;
2727
2746
  var _arguments = arguments;
2728
2747
  return _ts_generator(this, function(_state) {
2729
2748
  switch(_state.label){
2730
2749
  case 0:
2731
2750
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2751
+ if (!canPublish(licenseKey)) return [
2752
+ 2
2753
+ ];
2732
2754
  _state.label = 1;
2733
2755
  case 1:
2734
2756
  _state.trys.push([
2735
2757
  1,
2736
- 4,
2758
+ 3,
2737
2759
  ,
2738
- 5
2760
+ 4
2739
2761
  ]);
2740
- clientInfo = getClientInfo();
2741
2762
  return [
2742
2763
  4,
2743
- getBrowserID(clientInfo)
2744
- ];
2745
- case 2:
2746
- browserId = _state.sent();
2747
- trackingData = _object_spread({
2748
- browserId: browserId
2749
- }, clientInfo);
2750
- return [
2751
- 4,
2752
- sendTrackRequest(licenseKey, {
2753
- events: [
2754
- {
2755
- player_id: browserId,
2756
- device_type: clientInfo.deviceType,
2757
- input_stream_type: context.inputStreamType,
2758
- os: clientInfo.os,
2759
- ad_loaded: false,
2760
- ad_detect: false,
2761
- license_key: licenseKey,
2762
- capture_at: /* @__PURE__ */ new Date().toISOString()
2763
- }
2764
- ],
2765
- trackingData: trackingData
2764
+ buildPlayerMetricEvent(context, {
2765
+ adLoaded: false,
2766
+ adDetect: false
2766
2767
  })
2767
2768
  ];
2768
- case 3:
2769
- _state.sent();
2769
+ case 2:
2770
+ metricEvent = _state.sent();
2771
+ publishTracking(licenseKey, "metrics", {
2772
+ events: [
2773
+ metricEvent
2774
+ ]
2775
+ });
2770
2776
  return [
2771
2777
  3,
2772
- 5
2778
+ 4
2773
2779
  ];
2774
- case 4:
2780
+ case 3:
2775
2781
  error = _state.sent();
2776
2782
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
2777
2783
  return [
2778
2784
  3,
2779
- 5
2785
+ 4
2780
2786
  ];
2781
- case 5:
2787
+ case 4:
2782
2788
  return [
2783
2789
  2
2784
2790
  ];
@@ -2882,53 +2888,48 @@ function sendAdImpressionTracking(_0, _1) {
2882
2888
  switch(_state.label){
2883
2889
  case 0:
2884
2890
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2891
+ if (!canPublish(licenseKey)) return [
2892
+ 2
2893
+ ];
2885
2894
  _state.label = 1;
2886
2895
  case 1:
2887
2896
  _state.trys.push([
2888
2897
  1,
2889
- 4,
2898
+ 3,
2890
2899
  ,
2891
- 5
2900
+ 4
2892
2901
  ]);
2893
2902
  return [
2894
2903
  4,
2895
- buildPlayerMetricEvent(licenseKey, context, {
2904
+ buildPlayerMetricEvent(context, {
2896
2905
  captureAt: adImpressionInfo.timestamp
2897
2906
  })
2898
2907
  ];
2899
2908
  case 2:
2900
2909
  metricEvent = _state.sent();
2901
- return [
2902
- 4,
2903
- Promise.all([
2904
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
2905
- postJson(IMPRESSIONS_URL, licenseKey, {
2906
- events: [
2907
- {
2908
- player_id: metricEvent.player_id,
2909
- ad_played_count: 1,
2910
- ad_url: adImpressionInfo.adUrl,
2911
- license_key: licenseKey,
2912
- capture_at: adImpressionInfo.timestamp
2913
- }
2914
- ]
2915
- })
2916
- ])
2917
- ];
2918
- case 3:
2919
- _state.sent();
2910
+ publishTracking(licenseKey, "heartbeat", metricEvent);
2911
+ publishTracking(licenseKey, "impressions", {
2912
+ events: [
2913
+ {
2914
+ player_id: metricEvent.player_id,
2915
+ ad_played_count: 1,
2916
+ ad_url: adImpressionInfo.adUrl,
2917
+ capture_at: adImpressionInfo.timestamp
2918
+ }
2919
+ ]
2920
+ });
2920
2921
  return [
2921
2922
  3,
2922
- 5
2923
+ 4
2923
2924
  ];
2924
- case 4:
2925
+ case 3:
2925
2926
  error = _state.sent();
2926
2927
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
2927
2928
  return [
2928
2929
  3,
2929
- 5
2930
+ 4
2930
2931
  ];
2931
- case 5:
2932
+ case 4:
2932
2933
  return [
2933
2934
  2
2934
2935
  ];
@@ -2944,38 +2945,36 @@ function sendHeartbeat(_0) {
2944
2945
  switch(_state.label){
2945
2946
  case 0:
2946
2947
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2948
+ if (!canPublish(licenseKey)) return [
2949
+ 2
2950
+ ];
2947
2951
  _state.label = 1;
2948
2952
  case 1:
2949
2953
  _state.trys.push([
2950
2954
  1,
2951
- 4,
2955
+ 3,
2952
2956
  ,
2953
- 5
2957
+ 4
2954
2958
  ]);
2955
2959
  return [
2956
2960
  4,
2957
- buildPlayerMetricEvent(licenseKey, context, flags)
2961
+ buildPlayerMetricEvent(context, flags)
2958
2962
  ];
2959
2963
  case 2:
2960
2964
  heartbeatData = _state.sent();
2961
- return [
2962
- 4,
2963
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
2964
- ];
2965
- case 3:
2966
- _state.sent();
2965
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
2967
2966
  return [
2968
2967
  3,
2969
- 5
2968
+ 4
2970
2969
  ];
2971
- case 4:
2970
+ case 3:
2972
2971
  error = _state.sent();
2973
2972
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
2974
2973
  return [
2975
2974
  3,
2976
- 5
2975
+ 4
2977
2976
  ];
2978
- case 5:
2977
+ case 4:
2979
2978
  return [
2980
2979
  2
2981
2980
  ];
@@ -3440,7 +3439,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3440
3439
  var _level_details, _level_details1;
3441
3440
  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";
3442
3441
  })) !== null && _ref !== void 0 ? _ref : false;
3443
- if (!this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
3442
+ if (!this.isVmapEnabled() && !this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
3444
3443
  prerollKey = "synthetic-vod-preroll";
3445
3444
  if (!this.consumedVmapBreakIds.has(prerollKey)) {
3446
3445
  this.vmapBreaks = [
@@ -4023,6 +4022,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4023
4022
  _this.onTimeUpdate(_this.video.currentTime);
4024
4023
  };
4025
4024
  this.video.addEventListener("timeupdate", this.timeUpdateHandler);
4025
+ this.endedHandler = function() {
4026
+ _this.onVideoEnded();
4027
+ };
4028
+ this.video.addEventListener("ended", this.endedHandler);
4026
4029
  this.emptiedHandler = function() {
4027
4030
  if (_this.nativeHlsMode && _this.videoSrcProtection && !_this.ima.isAdPlaying()) {
4028
4031
  if (_this.config.debugAdTiming) {
@@ -5334,6 +5337,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5334
5337
  }
5335
5338
  }
5336
5339
  },
5340
+ {
5341
+ key: "isVmapEnabled",
5342
+ value: function isVmapEnabled() {
5343
+ var _this_config_vmapUrl;
5344
+ return !!(this.config.isVmap && ((_this_config_vmapUrl = this.config.vmapUrl) === null || _this_config_vmapUrl === void 0 ? void 0 : _this_config_vmapUrl.trim()));
5345
+ }
5346
+ },
5337
5347
  {
5338
5348
  key: "fetchAdConfiguration",
5339
5349
  value: function fetchAdConfiguration() {
@@ -5342,7 +5352,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5342
5352
  return _ts_generator(this, function(_state) {
5343
5353
  switch(_state.label){
5344
5354
  case 0:
5345
- if (!this.config.vmapUrl) return [
5355
+ if (!this.isVmapEnabled()) return [
5346
5356
  3,
5347
5357
  2
5348
5358
  ];
@@ -5352,7 +5362,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5352
5362
  ];
5353
5363
  case 1:
5354
5364
  _state.sent();
5355
- _state.label = 2;
5365
+ if (this.config.debugAdTiming) {
5366
+ console.log("[StormcloudVideoPlayer] VMAP mode enabled");
5367
+ }
5368
+ return [
5369
+ 2
5370
+ ];
5356
5371
  case 2:
5357
5372
  vastMode = this.config.vastMode || "default";
5358
5373
  if (this.config.debugAdTiming) {
@@ -5520,7 +5535,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5520
5535
  }
5521
5536
  return [];
5522
5537
  }
5523
- var adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
5538
+ var VMAP_NS = "http://www.iab.net/videosuite/vmap";
5539
+ var adBreakNodes = Array.from(doc.getElementsByTagNameNS(VMAP_NS, "AdBreak"));
5540
+ if (adBreakNodes.length === 0) {
5541
+ adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
5542
+ }
5543
+ if (adBreakNodes.length === 0) {
5544
+ adBreakNodes = Array.from(doc.getElementsByTagName("*")).filter(function(el) {
5545
+ return el.localName === "AdBreak";
5546
+ });
5547
+ }
5524
5548
  var parsed = [];
5525
5549
  adBreakNodes.forEach(function(node, index) {
5526
5550
  var timeOffsetRaw = (node.getAttribute("timeOffset") || "").trim();
@@ -5528,8 +5552,17 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5528
5552
  if (startTimeMs == null) {
5529
5553
  return;
5530
5554
  }
5531
- var adTagNode = node.querySelector("AdTagURI, vmap\\:AdTagURI");
5532
- var adTagUrl = ((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim();
5555
+ var adTagNode = node.getElementsByTagNameNS(VMAP_NS, "AdTagURI")[0];
5556
+ if (!adTagNode) {
5557
+ var _node_querySelector;
5558
+ adTagNode = (_node_querySelector = node.querySelector("AdTagURI, vmap\\:AdTagURI")) !== null && _node_querySelector !== void 0 ? _node_querySelector : void 0;
5559
+ }
5560
+ if (!adTagNode) {
5561
+ adTagNode = Array.from(node.getElementsByTagName("*")).find(function(el) {
5562
+ return el.localName === "AdTagURI";
5563
+ });
5564
+ }
5565
+ var adTagUrl = _this.resolveVmapAdTagUrl(((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim());
5533
5566
  if (!adTagUrl) {
5534
5567
  return;
5535
5568
  }
@@ -5548,6 +5581,15 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5548
5581
  return parsed;
5549
5582
  }
5550
5583
  },
5584
+ {
5585
+ key: "resolveVmapAdTagUrl",
5586
+ value: function resolveVmapAdTagUrl(url) {
5587
+ if (!url) {
5588
+ return "";
5589
+ }
5590
+ return url.replace(/\[timestamp\]/gi, String(Date.now())).replace(/\$\{GDPR\}/gi, "0").trim();
5591
+ }
5592
+ },
5551
5593
  {
5552
5594
  key: "parseVmapTimeOffsetToMs",
5553
5595
  value: function parseVmapTimeOffsetToMs(timeOffset) {
@@ -5570,6 +5612,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5570
5612
  var millis = Number(ms.padEnd(3, "0").slice(0, 3));
5571
5613
  return (hours * 3600 + minutes * 60 + seconds) * 1e3 + millis;
5572
5614
  }
5615
+ var msOnly = timeOffset.match(/^(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
5616
+ if (msOnly) {
5617
+ var _msOnly = _sliced_to_array(msOnly, 4), mm1 = _msOnly[1], ss1 = _msOnly[2], tmp1 = _msOnly[3], ms1 = tmp1 === void 0 ? "0" : tmp1;
5618
+ var minutes1 = Number(mm1);
5619
+ var seconds1 = Number(ss1);
5620
+ var millis1 = Number(ms1.padEnd(3, "0").slice(0, 3));
5621
+ return (minutes1 * 60 + seconds1) * 1e3 + millis1;
5622
+ }
5573
5623
  var percent = timeOffset.match(/^(\d+(?:\.\d+)?)%$/);
5574
5624
  if (percent) {
5575
5625
  var ratio = Number(percent[1]) / 100;
@@ -7085,23 +7135,49 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7085
7135
  key: "onTimeUpdate",
7086
7136
  value: function onTimeUpdate(currentTimeSec) {
7087
7137
  var _this = this;
7138
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
7139
+ return;
7140
+ }
7088
7141
  if (this.ima.isAdPlaying() || this.inAdBreak) return;
7089
7142
  var nowMs = currentTimeSec * 1e3;
7090
7143
  var breakToPlay = this.findBreakForTime(nowMs);
7091
7144
  if (breakToPlay) {
7092
- void this.handleMidAdJoin(breakToPlay, nowMs).catch(function(error) {
7145
+ void this.handleVmapAdBreak(breakToPlay, nowMs).catch(function(error) {
7093
7146
  if (_this.config.debugAdTiming) {
7094
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP join failed gracefully:", error);
7147
+ console.warn("[StormcloudVideoPlayer] VMAP ad break failed gracefully:", error);
7095
7148
  }
7096
7149
  });
7097
7150
  }
7098
7151
  }
7099
7152
  },
7100
7153
  {
7101
- key: "handleMidAdJoin",
7102
- value: function handleMidAdJoin(adBreak, nowMs) {
7154
+ key: "onVideoEnded",
7155
+ value: function onVideoEnded() {
7156
+ var _this = this;
7157
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
7158
+ return;
7159
+ }
7160
+ if (this.ima.isAdPlaying() || this.inAdBreak) {
7161
+ return;
7162
+ }
7163
+ var durationMs = Number.isFinite(this.video.duration) ? Math.floor(this.video.duration * 1e3) : 0;
7164
+ var postroll = this.vmapBreaks.find(function(b) {
7165
+ return b.startTimeMs === -1 && !_this.consumedVmapBreakIds.has(_this.getAdBreakKey(b));
7166
+ });
7167
+ if (postroll) {
7168
+ void this.handleVmapAdBreak(postroll, durationMs).catch(function(error) {
7169
+ if (_this.config.debugAdTiming) {
7170
+ console.warn("[StormcloudVideoPlayer] VMAP post-roll failed gracefully:", error);
7171
+ }
7172
+ });
7173
+ }
7174
+ }
7175
+ },
7176
+ {
7177
+ key: "handleVmapAdBreak",
7178
+ value: function handleVmapAdBreak(adBreak, nowMs) {
7103
7179
  return _async_to_generator(function() {
7104
- var _adBreak_durationMs, _this_config_driftToleranceMs, key, breakStartMs, durationMs, endMs, tol, inWindow, remainingMs, tags, first, rest, error;
7180
+ var _adBreak_durationMs, key, breakStartMs, durationMs, endMs, inWindow, tags, first, rest, error;
7105
7181
  return _ts_generator(this, function(_state) {
7106
7182
  switch(_state.label){
7107
7183
  case 0:
@@ -7119,25 +7195,28 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7119
7195
  }
7120
7196
  durationMs = (_adBreak_durationMs = adBreak.durationMs) !== null && _adBreak_durationMs !== void 0 ? _adBreak_durationMs : 0;
7121
7197
  endMs = breakStartMs + durationMs;
7122
- tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7123
- inWindow = durationMs > 0 ? nowMs > breakStartMs && nowMs < endMs : nowMs + tol >= breakStartMs;
7198
+ inWindow = durationMs > 0 ? nowMs >= breakStartMs && nowMs < endMs : nowMs >= breakStartMs;
7124
7199
  if (!inWindow) return [
7125
7200
  3,
7126
7201
  4
7127
7202
  ];
7128
7203
  this.consumedVmapBreakIds.add(key);
7129
- remainingMs = durationMs > 0 ? Math.max(0, endMs - nowMs) : 0;
7130
- tags = this.selectVastTagsForBreak(adBreak) || (this.apiVastTagUrl ? [
7131
- this.apiVastTagUrl
7132
- ] : void 0);
7133
- if (!(tags && tags.length > 0)) return [
7134
- 3,
7135
- 4
7136
- ];
7204
+ tags = this.selectVastTagsForBreak(adBreak);
7205
+ if (!tags || tags.length === 0) {
7206
+ return [
7207
+ 2
7208
+ ];
7209
+ }
7137
7210
  first = tags[0];
7138
7211
  rest = tags.slice(1);
7139
7212
  this.adPodQueue = rest;
7140
7213
  this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
7214
+ this.showAds = true;
7215
+ this.inAdBreak = true;
7216
+ this.currentAdBreakStartWallClockMs = Date.now();
7217
+ if (!this.video.paused) {
7218
+ this.video.pause();
7219
+ }
7141
7220
  _state.label = 1;
7142
7221
  case 1:
7143
7222
  _state.trys.push([
@@ -7152,10 +7231,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7152
7231
  ];
7153
7232
  case 2:
7154
7233
  _state.sent();
7155
- this.inAdBreak = true;
7156
- this.expectedAdBreakDurationMs = remainingMs;
7157
- this.currentAdBreakStartWallClockMs = Date.now();
7158
- this.scheduleAdStopCountdown(remainingMs);
7159
7234
  return [
7160
7235
  3,
7161
7236
  4
@@ -7163,8 +7238,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7163
7238
  case 3:
7164
7239
  error = _state.sent();
7165
7240
  this.adPodQueue = [];
7241
+ this.inAdBreak = false;
7242
+ this.showAds = false;
7166
7243
  if (this.config.debugAdTiming) {
7167
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP ad request failed:", error);
7244
+ console.warn("[StormcloudVideoPlayer] VMAP ad request failed:", error);
7168
7245
  }
7169
7246
  return [
7170
7247
  3,
@@ -7835,16 +7912,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7835
7912
  {
7836
7913
  key: "selectVastTagsForBreak",
7837
7914
  value: function selectVastTagsForBreak(b) {
7915
+ var _this = this;
7838
7916
  if (!b || !b.vastTagUrl) return void 0;
7839
- if (b.vastTagUrl.includes(",")) {
7840
- return b.vastTagUrl.split(",").map(function(s) {
7841
- return s.trim();
7917
+ var resolvedUrl = this.resolveVmapAdTagUrl(b.vastTagUrl);
7918
+ if (resolvedUrl.includes(",")) {
7919
+ return resolvedUrl.split(",").map(function(s) {
7920
+ return _this.resolveVmapAdTagUrl(s.trim());
7842
7921
  }).filter(function(s) {
7843
7922
  return s.length > 0;
7844
7923
  });
7845
7924
  }
7846
7925
  return [
7847
- b.vastTagUrl
7926
+ resolvedUrl
7848
7927
  ];
7849
7928
  }
7850
7929
  },
@@ -7876,9 +7955,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7876
7955
  {
7877
7956
  key: "findBreakForTime",
7878
7957
  value: function findBreakForTime(nowMs) {
7879
- var _this_config_driftToleranceMs;
7880
7958
  var schedule = this.vmapBreaks;
7881
- var tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7882
7959
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7883
7960
  try {
7884
7961
  for(var _iterator = schedule[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -7890,9 +7967,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7890
7967
  if (breakStartMs == null) {
7891
7968
  continue;
7892
7969
  }
7893
- var end = breakStartMs + (b.durationMs || 0);
7894
- var effectiveTol = breakStartMs === 0 ? Math.max(tol, 3e4) : tol;
7895
- if (nowMs >= breakStartMs && (b.durationMs ? nowMs < end : nowMs <= breakStartMs + effectiveTol)) {
7970
+ if (b.durationMs) {
7971
+ var end = breakStartMs + b.durationMs;
7972
+ if (nowMs >= breakStartMs && nowMs < end) {
7973
+ return b;
7974
+ }
7975
+ continue;
7976
+ }
7977
+ if (nowMs >= breakStartMs) {
7896
7978
  return b;
7897
7979
  }
7898
7980
  }
@@ -8100,6 +8182,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8100
8182
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
8101
8183
  delete this.timeUpdateHandler;
8102
8184
  }
8185
+ if (this.endedHandler) {
8186
+ this.video.removeEventListener("ended", this.endedHandler);
8187
+ delete this.endedHandler;
8188
+ }
8103
8189
  if (this.emptiedHandler) {
8104
8190
  this.video.removeEventListener("emptied", this.emptiedHandler);
8105
8191
  delete this.emptiedHandler;