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.
package/lib/index.js CHANGED
@@ -2429,6 +2429,123 @@ function createHlsAdPlayer(contentVideo, options) {
2429
2429
  }
2430
2430
  };
2431
2431
  }
2432
+ // src/utils/mqttConfig.ts
2433
+ var MQTT_CA_CERT_FILE = "src/certs/emqxsl-ca.crt";
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 applyMQTTConfig(overrides) {
2446
+ Object.assign(mqttConfig, overrides);
2447
+ }
2448
+ function isMQTTEnabled() {
2449
+ return mqttConfig.enabled;
2450
+ }
2451
+ function buildMQTTBrokerUrl() {
2452
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2453
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2454
+ }
2455
+ function buildPlayerTopic(licenseKey, channel) {
2456
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2457
+ }
2458
+ // src/utils/mqttClient.ts
2459
+ import mqtt from "mqtt";
2460
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
2461
+ var client = null;
2462
+ var status = "disconnected";
2463
+ function getMQTTStatus() {
2464
+ return status;
2465
+ }
2466
+ function isMQTTConnected() {
2467
+ return status === "connected" && client !== null && client.connected;
2468
+ }
2469
+ function isMQTTConfigured() {
2470
+ return isMQTTEnabled();
2471
+ }
2472
+ function configureMQTT(overrides) {
2473
+ applyMQTTConfig(overrides);
2474
+ disconnectMQTT();
2475
+ }
2476
+ function initMQTTClient() {
2477
+ if (client || !isMQTTEnabled()) return;
2478
+ var url = buildMQTTBrokerUrl();
2479
+ status = "connecting";
2480
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2481
+ try {
2482
+ client = mqtt.connect(url, {
2483
+ clientId: clientId,
2484
+ username: mqttConfig.username,
2485
+ password: mqttConfig.password,
2486
+ keepalive: 60,
2487
+ clean: true,
2488
+ reconnectPeriod: 5e3,
2489
+ connectTimeout: 1e4,
2490
+ queueQoSZero: false
2491
+ });
2492
+ } catch (err) {
2493
+ status = "error";
2494
+ console.warn("".concat(LOG, " connect() threw:"), err);
2495
+ return;
2496
+ }
2497
+ client.on("connect", function() {
2498
+ status = "connected";
2499
+ console.info("".concat(LOG, " connected to ").concat(url));
2500
+ });
2501
+ client.on("reconnect", function() {
2502
+ status = "connecting";
2503
+ console.info("".concat(LOG, " reconnecting…"));
2504
+ });
2505
+ client.on("offline", function() {
2506
+ status = "disconnected";
2507
+ console.warn("".concat(LOG, " offline"));
2508
+ });
2509
+ client.on("error", function(err) {
2510
+ status = "error";
2511
+ console.warn("".concat(LOG, " error:"), err.message);
2512
+ });
2513
+ client.on("close", function() {
2514
+ if (status === "connected") {
2515
+ status = "disconnected";
2516
+ }
2517
+ });
2518
+ }
2519
+ function ensureMQTTClient() {
2520
+ if (isMQTTEnabled() && !client) {
2521
+ initMQTTClient();
2522
+ }
2523
+ }
2524
+ function publishMQTT(topic, payload) {
2525
+ if (!isMQTTEnabled()) {
2526
+ return false;
2527
+ }
2528
+ ensureMQTTClient();
2529
+ if (!client) {
2530
+ return false;
2531
+ }
2532
+ try {
2533
+ client.publish(topic, JSON.stringify(payload), {
2534
+ qos: mqttConfig.qos
2535
+ });
2536
+ return true;
2537
+ } catch (err) {
2538
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2539
+ return false;
2540
+ }
2541
+ }
2542
+ function disconnectMQTT() {
2543
+ if (client) {
2544
+ client.end(true);
2545
+ client = null;
2546
+ status = "disconnected";
2547
+ }
2548
+ }
2432
2549
  // src/utils/tracking.ts
2433
2550
  var cachedBrowserId = null;
2434
2551
  function getClientInfo() {
@@ -2575,7 +2692,7 @@ function getClientInfo() {
2575
2692
  }
2576
2693
  function getBrowserID(clientInfo) {
2577
2694
  return _async_to_generator(function() {
2578
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
2695
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
2579
2696
  return _ts_generator(this, function(_state) {
2580
2697
  switch(_state.label){
2581
2698
  case 0:
@@ -2586,7 +2703,7 @@ function getBrowserID(clientInfo) {
2586
2703
  ];
2587
2704
  }
2588
2705
  fingerprintString = JSON.stringify(clientInfo);
2589
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
2706
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
2590
2707
  3,
2591
2708
  5
2592
2709
  ];
@@ -2624,8 +2741,7 @@ function getBrowserID(clientInfo) {
2624
2741
  ];
2625
2742
  case 3:
2626
2743
  hashBuffer = _state.sent();
2627
- hashArray = Array.from(new Uint8Array(hashBuffer));
2628
- hashHex = hashArray.map(function(b) {
2744
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
2629
2745
  return b.toString(16).padStart(2, "0");
2630
2746
  }).join("");
2631
2747
  cachedBrowserId = hashHex;
@@ -2634,8 +2750,8 @@ function getBrowserID(clientInfo) {
2634
2750
  hashHex
2635
2751
  ];
2636
2752
  case 4:
2637
- error = _state.sent();
2638
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
2753
+ unused = _state.sent();
2754
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
2639
2755
  return [
2640
2756
  3,
2641
2757
  5
@@ -2659,177 +2775,91 @@ function getBrowserID(clientInfo) {
2659
2775
  });
2660
2776
  })();
2661
2777
  }
2662
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
2663
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
2664
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
2665
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
2666
- function buildHeaders(licenseKey) {
2667
- var headers = {
2668
- "Content-Type": "application/json"
2669
- };
2670
- if (licenseKey) {
2671
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2672
- }
2673
- return headers;
2778
+ function canPublish(licenseKey) {
2779
+ return Boolean(isMQTTEnabled() && licenseKey);
2674
2780
  }
2675
- function sendTrackRequest(licenseKey, body) {
2781
+ function buildPlayerMetricEvent() {
2676
2782
  return _async_to_generator(function() {
2677
- var response;
2678
- return _ts_generator(this, function(_state) {
2679
- switch(_state.label){
2680
- case 0:
2681
- return [
2682
- 4,
2683
- fetch(TRACK_URL, {
2684
- method: "POST",
2685
- headers: buildHeaders(licenseKey),
2686
- body: JSON.stringify(body)
2687
- })
2688
- ];
2689
- case 1:
2690
- response = _state.sent();
2691
- if (!response.ok) {
2692
- throw new Error("HTTP error! status: ".concat(response.status));
2693
- }
2694
- return [
2695
- 4,
2696
- response.json()
2697
- ];
2698
- case 2:
2699
- _state.sent();
2700
- return [
2701
- 2
2702
- ];
2703
- }
2704
- });
2705
- })();
2706
- }
2707
- function postJson(url, licenseKey, body) {
2708
- return _async_to_generator(function() {
2709
- var response;
2710
- return _ts_generator(this, function(_state) {
2711
- switch(_state.label){
2712
- case 0:
2713
- return [
2714
- 4,
2715
- fetch(url, {
2716
- method: "POST",
2717
- headers: buildHeaders(licenseKey),
2718
- body: JSON.stringify(body)
2719
- })
2720
- ];
2721
- case 1:
2722
- response = _state.sent();
2723
- if (!response.ok) {
2724
- throw new Error("HTTP error! status: ".concat(response.status));
2725
- }
2726
- return [
2727
- 4,
2728
- response.json()
2729
- ];
2730
- case 2:
2731
- _state.sent();
2732
- return [
2733
- 2
2734
- ];
2735
- }
2736
- });
2737
- })();
2738
- }
2739
- function buildPlayerMetricEvent(_0) {
2740
- return _async_to_generator(function(licenseKey) {
2741
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
2783
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
2742
2784
  var _arguments = arguments;
2743
2785
  return _ts_generator(this, function(_state) {
2744
2786
  switch(_state.label){
2745
2787
  case 0:
2746
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2788
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2747
2789
  clientInfo = getClientInfo();
2748
2790
  return [
2749
2791
  4,
2750
2792
  getBrowserID(clientInfo)
2751
2793
  ];
2752
2794
  case 1:
2753
- browserId = _state.sent();
2795
+ playerId = _state.sent();
2754
2796
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
2755
2797
  return [
2756
2798
  2,
2757
- {
2758
- player_id: browserId,
2759
- browserId: browserId,
2799
+ _object_spread({
2800
+ player_id: playerId,
2760
2801
  device_type: clientInfo.deviceType,
2761
- deviceType: clientInfo.deviceType,
2762
- input_stream_type: context.inputStreamType,
2763
- os: clientInfo.os,
2764
- ad_loaded: flags.adLoaded,
2765
- ad_detect: flags.adDetect,
2766
- license_key: licenseKey,
2767
- capture_at: captureAt,
2768
- timestamp: captureAt
2769
- }
2802
+ os: clientInfo.os.toLowerCase(),
2803
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
2804
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
2805
+ capture_at: captureAt
2806
+ }, context.inputStreamType ? {
2807
+ input_stream_type: context.inputStreamType
2808
+ } : {})
2770
2809
  ];
2771
2810
  }
2772
2811
  });
2773
2812
  }).apply(this, arguments);
2774
2813
  }
2814
+ function publishTracking(licenseKey, channel, body) {
2815
+ ensureMQTTClient();
2816
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
2817
+ }
2775
2818
  function sendInitialTracking(_0) {
2776
2819
  return _async_to_generator(function(licenseKey) {
2777
- var context, clientInfo, browserId, trackingData, error;
2820
+ var context, metricEvent, error;
2778
2821
  var _arguments = arguments;
2779
2822
  return _ts_generator(this, function(_state) {
2780
2823
  switch(_state.label){
2781
2824
  case 0:
2782
2825
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2826
+ if (!canPublish(licenseKey)) return [
2827
+ 2
2828
+ ];
2783
2829
  _state.label = 1;
2784
2830
  case 1:
2785
2831
  _state.trys.push([
2786
2832
  1,
2787
- 4,
2833
+ 3,
2788
2834
  ,
2789
- 5
2835
+ 4
2790
2836
  ]);
2791
- clientInfo = getClientInfo();
2792
2837
  return [
2793
2838
  4,
2794
- getBrowserID(clientInfo)
2795
- ];
2796
- case 2:
2797
- browserId = _state.sent();
2798
- trackingData = _object_spread({
2799
- browserId: browserId
2800
- }, clientInfo);
2801
- return [
2802
- 4,
2803
- sendTrackRequest(licenseKey, {
2804
- events: [
2805
- {
2806
- player_id: browserId,
2807
- device_type: clientInfo.deviceType,
2808
- input_stream_type: context.inputStreamType,
2809
- os: clientInfo.os,
2810
- ad_loaded: false,
2811
- ad_detect: false,
2812
- license_key: licenseKey,
2813
- capture_at: /* @__PURE__ */ new Date().toISOString()
2814
- }
2815
- ],
2816
- trackingData: trackingData
2839
+ buildPlayerMetricEvent(context, {
2840
+ adLoaded: false,
2841
+ adDetect: false
2817
2842
  })
2818
2843
  ];
2819
- case 3:
2820
- _state.sent();
2844
+ case 2:
2845
+ metricEvent = _state.sent();
2846
+ publishTracking(licenseKey, "metrics", {
2847
+ events: [
2848
+ metricEvent
2849
+ ]
2850
+ });
2821
2851
  return [
2822
2852
  3,
2823
- 5
2853
+ 4
2824
2854
  ];
2825
- case 4:
2855
+ case 3:
2826
2856
  error = _state.sent();
2827
2857
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
2828
2858
  return [
2829
2859
  3,
2830
- 5
2860
+ 4
2831
2861
  ];
2832
- case 5:
2862
+ case 4:
2833
2863
  return [
2834
2864
  2
2835
2865
  ];
@@ -2933,53 +2963,48 @@ function sendAdImpressionTracking(_0, _1) {
2933
2963
  switch(_state.label){
2934
2964
  case 0:
2935
2965
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2966
+ if (!canPublish(licenseKey)) return [
2967
+ 2
2968
+ ];
2936
2969
  _state.label = 1;
2937
2970
  case 1:
2938
2971
  _state.trys.push([
2939
2972
  1,
2940
- 4,
2973
+ 3,
2941
2974
  ,
2942
- 5
2975
+ 4
2943
2976
  ]);
2944
2977
  return [
2945
2978
  4,
2946
- buildPlayerMetricEvent(licenseKey, context, {
2979
+ buildPlayerMetricEvent(context, {
2947
2980
  captureAt: adImpressionInfo.timestamp
2948
2981
  })
2949
2982
  ];
2950
2983
  case 2:
2951
2984
  metricEvent = _state.sent();
2952
- return [
2953
- 4,
2954
- Promise.all([
2955
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
2956
- postJson(IMPRESSIONS_URL, licenseKey, {
2957
- events: [
2958
- {
2959
- player_id: metricEvent.player_id,
2960
- ad_played_count: 1,
2961
- ad_url: adImpressionInfo.adUrl,
2962
- license_key: licenseKey,
2963
- capture_at: adImpressionInfo.timestamp
2964
- }
2965
- ]
2966
- })
2967
- ])
2968
- ];
2969
- case 3:
2970
- _state.sent();
2985
+ publishTracking(licenseKey, "heartbeat", metricEvent);
2986
+ publishTracking(licenseKey, "impressions", {
2987
+ events: [
2988
+ {
2989
+ player_id: metricEvent.player_id,
2990
+ ad_played_count: 1,
2991
+ ad_url: adImpressionInfo.adUrl,
2992
+ capture_at: adImpressionInfo.timestamp
2993
+ }
2994
+ ]
2995
+ });
2971
2996
  return [
2972
2997
  3,
2973
- 5
2998
+ 4
2974
2999
  ];
2975
- case 4:
3000
+ case 3:
2976
3001
  error = _state.sent();
2977
3002
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
2978
3003
  return [
2979
3004
  3,
2980
- 5
3005
+ 4
2981
3006
  ];
2982
- case 5:
3007
+ case 4:
2983
3008
  return [
2984
3009
  2
2985
3010
  ];
@@ -2995,38 +3020,36 @@ function sendHeartbeat(_0) {
2995
3020
  switch(_state.label){
2996
3021
  case 0:
2997
3022
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
3023
+ if (!canPublish(licenseKey)) return [
3024
+ 2
3025
+ ];
2998
3026
  _state.label = 1;
2999
3027
  case 1:
3000
3028
  _state.trys.push([
3001
3029
  1,
3002
- 4,
3030
+ 3,
3003
3031
  ,
3004
- 5
3032
+ 4
3005
3033
  ]);
3006
3034
  return [
3007
3035
  4,
3008
- buildPlayerMetricEvent(licenseKey, context, flags)
3036
+ buildPlayerMetricEvent(context, flags)
3009
3037
  ];
3010
3038
  case 2:
3011
3039
  heartbeatData = _state.sent();
3012
- return [
3013
- 4,
3014
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
3015
- ];
3016
- case 3:
3017
- _state.sent();
3040
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
3018
3041
  return [
3019
3042
  3,
3020
- 5
3043
+ 4
3021
3044
  ];
3022
- case 4:
3045
+ case 3:
3023
3046
  error = _state.sent();
3024
3047
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3025
3048
  return [
3026
3049
  3,
3027
- 5
3050
+ 4
3028
3051
  ];
3029
- case 5:
3052
+ case 4:
3030
3053
  return [
3031
3054
  2
3032
3055
  ];
@@ -3494,7 +3517,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3494
3517
  var _level_details, _level_details1;
3495
3518
  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";
3496
3519
  })) !== null && _ref !== void 0 ? _ref : false;
3497
- if (!this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
3520
+ if (!this.isVmapEnabled() && !this.isLiveStream && this.vmapBreaks.length === 0 && this.apiVastTagUrl) {
3498
3521
  prerollKey = "synthetic-vod-preroll";
3499
3522
  if (!this.consumedVmapBreakIds.has(prerollKey)) {
3500
3523
  this.vmapBreaks = [
@@ -4077,6 +4100,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4077
4100
  _this.onTimeUpdate(_this.video.currentTime);
4078
4101
  };
4079
4102
  this.video.addEventListener("timeupdate", this.timeUpdateHandler);
4103
+ this.endedHandler = function() {
4104
+ _this.onVideoEnded();
4105
+ };
4106
+ this.video.addEventListener("ended", this.endedHandler);
4080
4107
  this.emptiedHandler = function() {
4081
4108
  if (_this.nativeHlsMode && _this.videoSrcProtection && !_this.ima.isAdPlaying()) {
4082
4109
  if (_this.config.debugAdTiming) {
@@ -5388,6 +5415,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5388
5415
  }
5389
5416
  }
5390
5417
  },
5418
+ {
5419
+ key: "isVmapEnabled",
5420
+ value: function isVmapEnabled() {
5421
+ var _this_config_vmapUrl;
5422
+ return !!(this.config.isVmap && ((_this_config_vmapUrl = this.config.vmapUrl) === null || _this_config_vmapUrl === void 0 ? void 0 : _this_config_vmapUrl.trim()));
5423
+ }
5424
+ },
5391
5425
  {
5392
5426
  key: "fetchAdConfiguration",
5393
5427
  value: function fetchAdConfiguration() {
@@ -5396,7 +5430,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5396
5430
  return _ts_generator(this, function(_state) {
5397
5431
  switch(_state.label){
5398
5432
  case 0:
5399
- if (!this.config.vmapUrl) return [
5433
+ if (!this.isVmapEnabled()) return [
5400
5434
  3,
5401
5435
  2
5402
5436
  ];
@@ -5406,7 +5440,12 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5406
5440
  ];
5407
5441
  case 1:
5408
5442
  _state.sent();
5409
- _state.label = 2;
5443
+ if (this.config.debugAdTiming) {
5444
+ console.log("[StormcloudVideoPlayer] VMAP mode enabled");
5445
+ }
5446
+ return [
5447
+ 2
5448
+ ];
5410
5449
  case 2:
5411
5450
  vastMode = this.config.vastMode || "default";
5412
5451
  if (this.config.debugAdTiming) {
@@ -5574,7 +5613,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5574
5613
  }
5575
5614
  return [];
5576
5615
  }
5577
- var adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
5616
+ var VMAP_NS = "http://www.iab.net/videosuite/vmap";
5617
+ var adBreakNodes = Array.from(doc.getElementsByTagNameNS(VMAP_NS, "AdBreak"));
5618
+ if (adBreakNodes.length === 0) {
5619
+ adBreakNodes = Array.from(doc.querySelectorAll("AdBreak, vmap\\:AdBreak"));
5620
+ }
5621
+ if (adBreakNodes.length === 0) {
5622
+ adBreakNodes = Array.from(doc.getElementsByTagName("*")).filter(function(el) {
5623
+ return el.localName === "AdBreak";
5624
+ });
5625
+ }
5578
5626
  var parsed = [];
5579
5627
  adBreakNodes.forEach(function(node, index) {
5580
5628
  var timeOffsetRaw = (node.getAttribute("timeOffset") || "").trim();
@@ -5582,8 +5630,17 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5582
5630
  if (startTimeMs == null) {
5583
5631
  return;
5584
5632
  }
5585
- var adTagNode = node.querySelector("AdTagURI, vmap\\:AdTagURI");
5586
- var adTagUrl = ((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim();
5633
+ var adTagNode = node.getElementsByTagNameNS(VMAP_NS, "AdTagURI")[0];
5634
+ if (!adTagNode) {
5635
+ var _node_querySelector;
5636
+ adTagNode = (_node_querySelector = node.querySelector("AdTagURI, vmap\\:AdTagURI")) !== null && _node_querySelector !== void 0 ? _node_querySelector : void 0;
5637
+ }
5638
+ if (!adTagNode) {
5639
+ adTagNode = Array.from(node.getElementsByTagName("*")).find(function(el) {
5640
+ return el.localName === "AdTagURI";
5641
+ });
5642
+ }
5643
+ var adTagUrl = _this.resolveVmapAdTagUrl(((adTagNode === null || adTagNode === void 0 ? void 0 : adTagNode.textContent) || "").trim());
5587
5644
  if (!adTagUrl) {
5588
5645
  return;
5589
5646
  }
@@ -5602,6 +5659,15 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5602
5659
  return parsed;
5603
5660
  }
5604
5661
  },
5662
+ {
5663
+ key: "resolveVmapAdTagUrl",
5664
+ value: function resolveVmapAdTagUrl(url) {
5665
+ if (!url) {
5666
+ return "";
5667
+ }
5668
+ return url.replace(/\[timestamp\]/gi, String(Date.now())).replace(/\$\{GDPR\}/gi, "0").trim();
5669
+ }
5670
+ },
5605
5671
  {
5606
5672
  key: "parseVmapTimeOffsetToMs",
5607
5673
  value: function parseVmapTimeOffsetToMs(timeOffset) {
@@ -5624,6 +5690,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
5624
5690
  var millis = Number(ms.padEnd(3, "0").slice(0, 3));
5625
5691
  return (hours * 3600 + minutes * 60 + seconds) * 1e3 + millis;
5626
5692
  }
5693
+ var msOnly = timeOffset.match(/^(\d{1,2}):(\d{2})(?:\.(\d{1,3}))?$/);
5694
+ if (msOnly) {
5695
+ var _msOnly = _sliced_to_array(msOnly, 4), mm1 = _msOnly[1], ss1 = _msOnly[2], tmp1 = _msOnly[3], ms1 = tmp1 === void 0 ? "0" : tmp1;
5696
+ var minutes1 = Number(mm1);
5697
+ var seconds1 = Number(ss1);
5698
+ var millis1 = Number(ms1.padEnd(3, "0").slice(0, 3));
5699
+ return (minutes1 * 60 + seconds1) * 1e3 + millis1;
5700
+ }
5627
5701
  var percent = timeOffset.match(/^(\d+(?:\.\d+)?)%$/);
5628
5702
  if (percent) {
5629
5703
  var ratio = Number(percent[1]) / 100;
@@ -7139,23 +7213,49 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7139
7213
  key: "onTimeUpdate",
7140
7214
  value: function onTimeUpdate(currentTimeSec) {
7141
7215
  var _this = this;
7216
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
7217
+ return;
7218
+ }
7142
7219
  if (this.ima.isAdPlaying() || this.inAdBreak) return;
7143
7220
  var nowMs = currentTimeSec * 1e3;
7144
7221
  var breakToPlay = this.findBreakForTime(nowMs);
7145
7222
  if (breakToPlay) {
7146
- void this.handleMidAdJoin(breakToPlay, nowMs).catch(function(error) {
7223
+ void this.handleVmapAdBreak(breakToPlay, nowMs).catch(function(error) {
7147
7224
  if (_this.config.debugAdTiming) {
7148
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP join failed gracefully:", error);
7225
+ console.warn("[StormcloudVideoPlayer] VMAP ad break failed gracefully:", error);
7149
7226
  }
7150
7227
  });
7151
7228
  }
7152
7229
  }
7153
7230
  },
7154
7231
  {
7155
- key: "handleMidAdJoin",
7156
- value: function handleMidAdJoin(adBreak, nowMs) {
7232
+ key: "onVideoEnded",
7233
+ value: function onVideoEnded() {
7234
+ var _this = this;
7235
+ if (!this.isVmapEnabled() || this.vmapBreaks.length === 0) {
7236
+ return;
7237
+ }
7238
+ if (this.ima.isAdPlaying() || this.inAdBreak) {
7239
+ return;
7240
+ }
7241
+ var durationMs = Number.isFinite(this.video.duration) ? Math.floor(this.video.duration * 1e3) : 0;
7242
+ var postroll = this.vmapBreaks.find(function(b) {
7243
+ return b.startTimeMs === -1 && !_this.consumedVmapBreakIds.has(_this.getAdBreakKey(b));
7244
+ });
7245
+ if (postroll) {
7246
+ void this.handleVmapAdBreak(postroll, durationMs).catch(function(error) {
7247
+ if (_this.config.debugAdTiming) {
7248
+ console.warn("[StormcloudVideoPlayer] VMAP post-roll failed gracefully:", error);
7249
+ }
7250
+ });
7251
+ }
7252
+ }
7253
+ },
7254
+ {
7255
+ key: "handleVmapAdBreak",
7256
+ value: function handleVmapAdBreak(adBreak, nowMs) {
7157
7257
  return _async_to_generator(function() {
7158
- var _adBreak_durationMs, _this_config_driftToleranceMs, key, breakStartMs, durationMs, endMs, tol, inWindow, remainingMs, tags, first, rest, error;
7258
+ var _adBreak_durationMs, key, breakStartMs, durationMs, endMs, inWindow, tags, first, rest, error;
7159
7259
  return _ts_generator(this, function(_state) {
7160
7260
  switch(_state.label){
7161
7261
  case 0:
@@ -7173,25 +7273,28 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7173
7273
  }
7174
7274
  durationMs = (_adBreak_durationMs = adBreak.durationMs) !== null && _adBreak_durationMs !== void 0 ? _adBreak_durationMs : 0;
7175
7275
  endMs = breakStartMs + durationMs;
7176
- tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7177
- inWindow = durationMs > 0 ? nowMs > breakStartMs && nowMs < endMs : nowMs + tol >= breakStartMs;
7276
+ inWindow = durationMs > 0 ? nowMs >= breakStartMs && nowMs < endMs : nowMs >= breakStartMs;
7178
7277
  if (!inWindow) return [
7179
7278
  3,
7180
7279
  4
7181
7280
  ];
7182
7281
  this.consumedVmapBreakIds.add(key);
7183
- remainingMs = durationMs > 0 ? Math.max(0, endMs - nowMs) : 0;
7184
- tags = this.selectVastTagsForBreak(adBreak) || (this.apiVastTagUrl ? [
7185
- this.apiVastTagUrl
7186
- ] : void 0);
7187
- if (!(tags && tags.length > 0)) return [
7188
- 3,
7189
- 4
7190
- ];
7282
+ tags = this.selectVastTagsForBreak(adBreak);
7283
+ if (!tags || tags.length === 0) {
7284
+ return [
7285
+ 2
7286
+ ];
7287
+ }
7191
7288
  first = tags[0];
7192
7289
  rest = tags.slice(1);
7193
7290
  this.adPodQueue = rest;
7194
7291
  this.ima.updateOriginalMutedState(this.video.muted, this.video.volume);
7292
+ this.showAds = true;
7293
+ this.inAdBreak = true;
7294
+ this.currentAdBreakStartWallClockMs = Date.now();
7295
+ if (!this.video.paused) {
7296
+ this.video.pause();
7297
+ }
7195
7298
  _state.label = 1;
7196
7299
  case 1:
7197
7300
  _state.trys.push([
@@ -7206,10 +7309,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7206
7309
  ];
7207
7310
  case 2:
7208
7311
  _state.sent();
7209
- this.inAdBreak = true;
7210
- this.expectedAdBreakDurationMs = remainingMs;
7211
- this.currentAdBreakStartWallClockMs = Date.now();
7212
- this.scheduleAdStopCountdown(remainingMs);
7213
7312
  return [
7214
7313
  3,
7215
7314
  4
@@ -7217,8 +7316,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7217
7316
  case 3:
7218
7317
  error = _state.sent();
7219
7318
  this.adPodQueue = [];
7319
+ this.inAdBreak = false;
7320
+ this.showAds = false;
7220
7321
  if (this.config.debugAdTiming) {
7221
- console.warn("[StormcloudVideoPlayer] Mid-roll VMAP ad request failed:", error);
7322
+ console.warn("[StormcloudVideoPlayer] VMAP ad request failed:", error);
7222
7323
  }
7223
7324
  return [
7224
7325
  3,
@@ -7889,16 +7990,18 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7889
7990
  {
7890
7991
  key: "selectVastTagsForBreak",
7891
7992
  value: function selectVastTagsForBreak(b) {
7993
+ var _this = this;
7892
7994
  if (!b || !b.vastTagUrl) return void 0;
7893
- if (b.vastTagUrl.includes(",")) {
7894
- return b.vastTagUrl.split(",").map(function(s) {
7895
- return s.trim();
7995
+ var resolvedUrl = this.resolveVmapAdTagUrl(b.vastTagUrl);
7996
+ if (resolvedUrl.includes(",")) {
7997
+ return resolvedUrl.split(",").map(function(s) {
7998
+ return _this.resolveVmapAdTagUrl(s.trim());
7896
7999
  }).filter(function(s) {
7897
8000
  return s.length > 0;
7898
8001
  });
7899
8002
  }
7900
8003
  return [
7901
- b.vastTagUrl
8004
+ resolvedUrl
7902
8005
  ];
7903
8006
  }
7904
8007
  },
@@ -7930,9 +8033,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7930
8033
  {
7931
8034
  key: "findBreakForTime",
7932
8035
  value: function findBreakForTime(nowMs) {
7933
- var _this_config_driftToleranceMs;
7934
8036
  var schedule = this.vmapBreaks;
7935
- var tol = (_this_config_driftToleranceMs = this.config.driftToleranceMs) !== null && _this_config_driftToleranceMs !== void 0 ? _this_config_driftToleranceMs : 1e3;
7936
8037
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
7937
8038
  try {
7938
8039
  for(var _iterator = schedule[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
@@ -7944,9 +8045,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7944
8045
  if (breakStartMs == null) {
7945
8046
  continue;
7946
8047
  }
7947
- var end = breakStartMs + (b.durationMs || 0);
7948
- var effectiveTol = breakStartMs === 0 ? Math.max(tol, 3e4) : tol;
7949
- if (nowMs >= breakStartMs && (b.durationMs ? nowMs < end : nowMs <= breakStartMs + effectiveTol)) {
8048
+ if (b.durationMs) {
8049
+ var end = breakStartMs + b.durationMs;
8050
+ if (nowMs >= breakStartMs && nowMs < end) {
8051
+ return b;
8052
+ }
8053
+ continue;
8054
+ }
8055
+ if (nowMs >= breakStartMs) {
7950
8056
  return b;
7951
8057
  }
7952
8058
  }
@@ -8154,6 +8260,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8154
8260
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
8155
8261
  delete this.timeUpdateHandler;
8156
8262
  }
8263
+ if (this.endedHandler) {
8264
+ this.video.removeEventListener("ended", this.endedHandler);
8265
+ delete this.endedHandler;
8266
+ }
8157
8267
  if (this.emptiedHandler) {
8158
8268
  this.video.removeEventListener("emptied", this.emptiedHandler);
8159
8269
  delete this.emptiedHandler;
@@ -8179,6 +8289,7 @@ var CRITICAL_PROPS = [
8179
8289
  "allowNativeHls",
8180
8290
  "isLiveStream",
8181
8291
  "licenseKey",
8292
+ "isVmap",
8182
8293
  "vmapUrl",
8183
8294
  "lowLatencyMode",
8184
8295
  "driftToleranceMs",
@@ -8187,7 +8298,7 @@ var CRITICAL_PROPS = [
8187
8298
  var CONTROLS_HIDE_DELAY = 3e3;
8188
8299
  var DEFAULT_PLAYER_ASPECT_RATIO = 16 / 9;
8189
8300
  var StormcloudVideoPlayerComponent = React.memo(function(props) {
8190
- var src = props.src, autoplay = props.autoplay, muted = props.muted, lowLatencyMode = props.lowLatencyMode, allowNativeHls = props.allowNativeHls, isLiveStream = props.isLiveStream, driftToleranceMs = props.driftToleranceMs, immediateManifestAds = props.immediateManifestAds, debugAdTiming = props.debugAdTiming, showCustomControls = props.showCustomControls, hideLoadingIndicator = props.hideLoadingIndicator, onVolumeToggle = props.onVolumeToggle, onFullscreenToggle = props.onFullscreenToggle, onControlClick = props.onControlClick, onReady = props.onReady, wrapperClassName = props.wrapperClassName, wrapperStyle = props.wrapperStyle, className = props.className, style = props.style, controls = props.controls, playsInline = props.playsInline, preload = props.preload, poster = props.poster, children = props.children, licenseKey = props.licenseKey, vastMode = props.vastMode, vastTagUrl = props.vastTagUrl, vmapUrl = props.vmapUrl, adPlayerType = props.adPlayerType, minSegmentsBeforePlay = props.minSegmentsBeforePlay, restVideoAttrs = _object_without_properties(props, [
8301
+ var src = props.src, autoplay = props.autoplay, muted = props.muted, lowLatencyMode = props.lowLatencyMode, allowNativeHls = props.allowNativeHls, isLiveStream = props.isLiveStream, driftToleranceMs = props.driftToleranceMs, immediateManifestAds = props.immediateManifestAds, debugAdTiming = props.debugAdTiming, showCustomControls = props.showCustomControls, hideLoadingIndicator = props.hideLoadingIndicator, onVolumeToggle = props.onVolumeToggle, onFullscreenToggle = props.onFullscreenToggle, onControlClick = props.onControlClick, onReady = props.onReady, wrapperClassName = props.wrapperClassName, wrapperStyle = props.wrapperStyle, className = props.className, style = props.style, controls = props.controls, playsInline = props.playsInline, preload = props.preload, poster = props.poster, children = props.children, licenseKey = props.licenseKey, vastMode = props.vastMode, vastTagUrl = props.vastTagUrl, isVmap = props.isVmap, vmapUrl = props.vmapUrl, adPlayerType = props.adPlayerType, minSegmentsBeforePlay = props.minSegmentsBeforePlay, restVideoAttrs = _object_without_properties(props, [
8191
8302
  "src",
8192
8303
  "autoplay",
8193
8304
  "muted",
@@ -8215,6 +8326,7 @@ var StormcloudVideoPlayerComponent = React.memo(function(props) {
8215
8326
  "licenseKey",
8216
8327
  "vastMode",
8217
8328
  "vastTagUrl",
8329
+ "isVmap",
8218
8330
  "vmapUrl",
8219
8331
  "adPlayerType",
8220
8332
  "minSegmentsBeforePlay"
@@ -8358,6 +8470,7 @@ var StormcloudVideoPlayerComponent = React.memo(function(props) {
8358
8470
  allowNativeHls,
8359
8471
  isLiveStream,
8360
8472
  licenseKey,
8473
+ isVmap,
8361
8474
  vmapUrl,
8362
8475
  lowLatencyMode,
8363
8476
  driftToleranceMs,
@@ -8402,6 +8515,7 @@ var StormcloudVideoPlayerComponent = React.memo(function(props) {
8402
8515
  if (licenseKey !== void 0) cfg.licenseKey = licenseKey;
8403
8516
  if (vastMode !== void 0) cfg.vastMode = vastMode;
8404
8517
  if (vastTagUrl !== void 0) cfg.vastTagUrl = vastTagUrl;
8518
+ if (isVmap !== void 0) cfg.isVmap = isVmap;
8405
8519
  if (vmapUrl !== void 0) cfg.vmapUrl = vmapUrl;
8406
8520
  if (adPlayerType !== void 0) cfg.adPlayerType = adPlayerType;
8407
8521
  if (minSegmentsBeforePlay !== void 0) cfg.minSegmentsBeforePlay = minSegmentsBeforePlay;
@@ -9565,6 +9679,7 @@ var defaultProps = {
9565
9679
  showCustomControls: false,
9566
9680
  hideLoadingIndicator: false,
9567
9681
  licenseKey: "",
9682
+ isVmap: false,
9568
9683
  vmapUrl: "",
9569
9684
  adFailsafeTimeoutMs: 1e4,
9570
9685
  minSegmentsBeforePlay: 2,
@@ -10512,6 +10627,7 @@ var SUPPORTED_PROPS = [
10512
10627
  "debugAdTiming",
10513
10628
  "showCustomControls",
10514
10629
  "licenseKey",
10630
+ "isVmap",
10515
10631
  "vmapUrl",
10516
10632
  "adFailsafeTimeoutMs",
10517
10633
  "minSegmentsBeforePlay",
@@ -10700,5 +10816,5 @@ var createStormcloudPlayer = function createStormcloudPlayer(playerList, fallbac
10700
10816
  };
10701
10817
  var StormcloudPlayer = createStormcloudPlayer(players_default, players_default[players_default.length - 1]);
10702
10818
  var StormcloudPlayer_default = StormcloudPlayer;
10703
- export { IS_BROWSER, IS_GLOBAL, IS_IOS, IS_SAFARI, SUPPORTS_DASH, SUPPORTS_HLS, StormcloudPlayer_default as StormcloudPlayer, StormcloudVideoPlayer, StormcloudVideoPlayerComponent, canPlay, createHlsAdPlayer, createImaController, createStormcloudPlayer, StormcloudVideoPlayerComponent as default, detectBrowser, getBrowserConfigOverrides, getBrowserID, getClientInfo, getRecommendedAdPlayer, initializePolyfills, isMediaStream, lazy, logBrowserInfo, merge, omit, parseQuery, players_default as players, randomString, sendAdDetectTracking, sendAdImpressionTracking, sendAdLoadedTracking, sendHeartbeat, sendInitialTracking, supportsFeature, supportsGoogleIMA, supportsModernJS, supportsWebKitPresentationMode };
10819
+ export { DEFAULT_MQTT_CONFIG, IS_BROWSER, IS_GLOBAL, IS_IOS, IS_SAFARI, MQTT_CA_CERT_FILE, SUPPORTS_DASH, SUPPORTS_HLS, StormcloudPlayer_default as StormcloudPlayer, StormcloudVideoPlayer, StormcloudVideoPlayerComponent, applyMQTTConfig, buildMQTTBrokerUrl, buildPlayerTopic, canPlay, configureMQTT, createHlsAdPlayer, createImaController, createStormcloudPlayer, StormcloudVideoPlayerComponent as default, detectBrowser, disconnectMQTT, ensureMQTTClient, getBrowserConfigOverrides, getBrowserID, getClientInfo, getMQTTStatus, getRecommendedAdPlayer, initMQTTClient, initializePolyfills, isMQTTConfigured, isMQTTConnected, isMQTTEnabled, isMediaStream, lazy, logBrowserInfo, merge, mqttConfig, omit, parseQuery, players_default as players, publishMQTT, randomString, sendAdDetectTracking, sendAdImpressionTracking, sendAdLoadedTracking, sendHeartbeat, sendInitialTracking, supportsFeature, supportsGoogleIMA, supportsModernJS, supportsWebKitPresentationMode };
10704
10820
  //# sourceMappingURL=index.js.map