stormcloud-video-player 0.3.63 → 0.3.65

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
- return [
2793
- 4,
2794
- getBrowserID(clientInfo)
2795
- ];
2796
- case 2:
2797
- browserId = _state.sent();
2798
- trackingData = _object_spread({
2799
- browserId: browserId
2800
- }, clientInfo);
2801
2837
  return [
2802
2838
  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
  ];
@@ -3882,7 +3905,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3882
3905
  _this.showAds = true;
3883
3906
  _this.resetGamNoFillCounter();
3884
3907
  if (_this.inAdBreak && _this.expectedAdBreakDurationMs != null && _this.adStopTimerId == null) {
3885
- _this.scheduleAdStopCountdown(_this.getRemainingAdMs());
3908
+ _this.scheduleAdStopAtBreakBoundary();
3886
3909
  if (_this.config.debugAdTiming) {
3887
3910
  console.log("[StormcloudVideoPlayer] Starting ad break timer on content_pause (first ad starting)");
3888
3911
  }
@@ -4526,12 +4549,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4526
4549
  if (marker.durationSeconds != null) {
4527
4550
  var newDurationMs = marker.durationSeconds * 1e3;
4528
4551
  if (this.expectedAdBreakDurationMs == null || newDurationMs > this.expectedAdBreakDurationMs) {
4529
- this.expectedAdBreakDurationMs = newDurationMs;
4530
- var elapsedMs = this.currentAdBreakStartWallClockMs != null ? Date.now() - this.currentAdBreakStartWallClockMs : 0;
4531
- var remainingMs = Math.max(0, newDurationMs - elapsedMs);
4532
- this.scheduleAdStopCountdown(remainingMs);
4552
+ this.setAdBreakDurationBoundary(newDurationMs);
4553
+ this.scheduleAdStopAtBreakBoundary();
4533
4554
  if (this.config.debugAdTiming) {
4534
- console.log("[StormcloudVideoPlayer] Updated ad break duration from subsequent marker: ".concat(newDurationMs, "ms, remaining: ").concat(remainingMs, "ms"));
4555
+ console.log("[StormcloudVideoPlayer] Updated ad break duration from subsequent marker: ".concat(newDurationMs, "ms, remaining: ").concat(this.getRemainingAdMs(), "ms"));
4535
4556
  }
4536
4557
  }
4537
4558
  }
@@ -4605,13 +4626,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4605
4626
  }
4606
4627
  if (marker.type === "progress" && this.inAdBreak) {
4607
4628
  if (marker.durationSeconds != null) {
4608
- this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
4609
- }
4610
- if (this.expectedAdBreakDurationMs != null && this.currentAdBreakStartWallClockMs != null) {
4611
- var elapsedMs1 = Date.now() - this.currentAdBreakStartWallClockMs;
4612
- var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
4613
- this.scheduleAdStopCountdown(remainingMs1);
4629
+ this.setAdBreakDurationBoundary(marker.durationSeconds * 1e3);
4614
4630
  }
4631
+ this.scheduleAdStopAtBreakBoundary();
4615
4632
  if (!this.ima.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
4616
4633
  this.tryNextAvailableAdWithRateLimit();
4617
4634
  }
@@ -4636,15 +4653,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4636
4653
  activeAdRequest: this.activeAdRequestToken !== null
4637
4654
  });
4638
4655
  }
4639
- if (adPlaying || remaining > 1e3 && hasQueuedAds) {
4640
- if (this.config.debugAdTiming) {
4641
- console.log("[StormcloudVideoPlayer] Ignoring premature SCTE-35 end marker - ads still active");
4642
- }
4643
- return;
4644
- }
4645
- this.inAdBreak = false;
4646
- this.expectedAdBreakDurationMs = void 0;
4647
- this.currentAdBreakStartWallClockMs = void 0;
4656
+ this.scteAdBreakEndWallClockMs = Date.now();
4648
4657
  this.clearAdStartTimer();
4649
4658
  this.clearAdStopTimer();
4650
4659
  if (adPlaying) {
@@ -4668,14 +4677,48 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4668
4677
  this.inAdBreak = true;
4669
4678
  this.activeScte35BreakKey = cueKey !== null && cueKey !== void 0 ? cueKey : this.pendingScte35CueKey;
4670
4679
  this.scheduledScte35BreakKey = void 0;
4671
- this.expectedAdBreakDurationMs = durationMs;
4672
- this.currentAdBreakStartWallClockMs = Date.now();
4680
+ this.currentAdBreakStartWallClockMs = this.resolveScteBreakStartWallClockMs(marker);
4681
+ this.setAdBreakDurationBoundary(durationMs);
4673
4682
  this.handleAdStart(marker);
4674
- if (this.expectedAdBreakDurationMs != null) {
4675
- this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
4683
+ this.scheduleAdStopAtBreakBoundary();
4684
+ }
4685
+ },
4686
+ {
4687
+ key: "resolveScteBreakStartWallClockMs",
4688
+ value: function resolveScteBreakStartWallClockMs(marker) {
4689
+ var nowWallClockMs = Date.now();
4690
+ if (typeof marker.ptsSeconds !== "number") {
4691
+ return nowWallClockMs;
4692
+ }
4693
+ var nowMs = this.video.currentTime * 1e3;
4694
+ var estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;
4695
+ var deltaMs = marker.ptsSeconds * 1e3 - estimatedCurrentPtsMs;
4696
+ if (!Number.isFinite(deltaMs)) {
4697
+ return nowWallClockMs;
4698
+ }
4699
+ return nowWallClockMs + Math.floor(deltaMs);
4700
+ }
4701
+ },
4702
+ {
4703
+ key: "setAdBreakDurationBoundary",
4704
+ value: function setAdBreakDurationBoundary(durationMs) {
4705
+ this.expectedAdBreakDurationMs = durationMs;
4706
+ if (durationMs != null && this.currentAdBreakStartWallClockMs != null) {
4707
+ this.scteAdBreakEndWallClockMs = this.currentAdBreakStartWallClockMs + durationMs;
4708
+ } else {
4709
+ this.scteAdBreakEndWallClockMs = void 0;
4676
4710
  }
4677
4711
  }
4678
4712
  },
4713
+ {
4714
+ key: "scheduleAdStopAtBreakBoundary",
4715
+ value: function scheduleAdStopAtBreakBoundary() {
4716
+ if (this.expectedAdBreakDurationMs == null) {
4717
+ return;
4718
+ }
4719
+ this.scheduleAdStopCountdown(this.getRemainingAdMs());
4720
+ }
4721
+ },
4679
4722
  {
4680
4723
  key: "parseCueOutDuration",
4681
4724
  value: function parseCueOutDuration(value) {
@@ -6301,7 +6344,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6301
6344
  }
6302
6345
  }
6303
6346
  this.inAdBreak = true;
6304
- this.currentAdBreakStartWallClockMs = Date.now();
6305
6347
  this.currentAdIndex = 0;
6306
6348
  this.totalAdsInBreak = 1;
6307
6349
  this.adPodQueue = [];
@@ -6388,7 +6430,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6388
6430
  case 2:
6389
6431
  _state.sent();
6390
6432
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6391
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6433
+ this.scheduleAdStopAtBreakBoundary();
6392
6434
  }
6393
6435
  adVolume = currentMuted ? 0 : currentVolume;
6394
6436
  this.ima.setAdVolume(adVolume);
@@ -6431,7 +6473,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6431
6473
  case 6:
6432
6474
  _state.sent();
6433
6475
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6434
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6476
+ this.scheduleAdStopAtBreakBoundary();
6435
6477
  }
6436
6478
  adVolume1 = currentMuted ? 0 : currentVolume;
6437
6479
  this.ima.setAdVolume(adVolume1);
@@ -6486,7 +6528,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6486
6528
  case 10:
6487
6529
  _state.sent();
6488
6530
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6489
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6531
+ this.scheduleAdStopAtBreakBoundary();
6490
6532
  }
6491
6533
  adVolume2 = currentMuted ? 0 : currentVolume;
6492
6534
  this.ima.setAdVolume(adVolume2);
@@ -6838,7 +6880,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6838
6880
  case 2:
6839
6881
  _state.sent();
6840
6882
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6841
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6883
+ this.scheduleAdStopAtBreakBoundary();
6842
6884
  }
6843
6885
  currentMuted = this.video.muted;
6844
6886
  currentVolume = this.video.volume;
@@ -7338,21 +7380,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7338
7380
  {
7339
7381
  key: "ensureAdStoppedByTimer",
7340
7382
  value: function ensureAdStoppedByTimer() {
7341
- var _this_config_adBreakCheckIntervalMs, _this_expectedAdBreakDurationMs;
7383
+ var _this_config_adBreakCheckIntervalMs, _this_getAdBreakEndWallClockMs;
7342
7384
  if (!this.inAdBreak) return;
7343
7385
  this.adStopTimerId = void 0;
7344
7386
  var adPlaying = this.ima.isAdPlaying();
7345
- var pendingAds = this.adPodQueue.length > 0;
7346
7387
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
7347
7388
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
7348
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
7349
- var elapsedSinceStartMs = 0;
7350
- if (this.currentAdBreakStartWallClockMs != null) {
7351
- elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
7352
- }
7353
- var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
7354
- var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
7355
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
7389
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
7390
+ var endWallClockMs = (_this_getAdBreakEndWallClockMs = this.getAdBreakEndWallClockMs()) !== null && _this_getAdBreakEndWallClockMs !== void 0 ? _this_getAdBreakEndWallClockMs : Date.now();
7391
+ var overrunMs = Math.max(0, Date.now() - endWallClockMs);
7392
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
7356
7393
  if (shouldExtendAdBreak) {
7357
7394
  this.scheduleAdStopCountdown(checkIntervalMs);
7358
7395
  return;
@@ -7618,7 +7655,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7618
7655
  case 5:
7619
7656
  _state.sent();
7620
7657
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7621
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
7658
+ this.scheduleAdStopAtBreakBoundary();
7622
7659
  }
7623
7660
  currentMuted = this.video.muted;
7624
7661
  currentVolume = this.video.volume;
@@ -7668,7 +7705,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7668
7705
  case 8:
7669
7706
  _state.sent();
7670
7707
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7671
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
7708
+ this.scheduleAdStopAtBreakBoundary();
7672
7709
  }
7673
7710
  currentMuted1 = this.video.muted;
7674
7711
  currentVolume1 = this.video.volume;
@@ -7813,6 +7850,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7813
7850
  this.scheduledScte35BreakKey = void 0;
7814
7851
  this.expectedAdBreakDurationMs = void 0;
7815
7852
  this.currentAdBreakStartWallClockMs = void 0;
7853
+ this.scteAdBreakEndWallClockMs = void 0;
7816
7854
  this.clearAdStartTimer();
7817
7855
  this.clearAdStopTimer();
7818
7856
  this.adPodQueue = [];
@@ -8002,9 +8040,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8002
8040
  {
8003
8041
  key: "getRemainingAdMs",
8004
8042
  value: function getRemainingAdMs() {
8005
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
8006
- var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
8007
- return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
8043
+ var endWallClockMs = this.getAdBreakEndWallClockMs();
8044
+ if (endWallClockMs != null) {
8045
+ return Math.max(0, endWallClockMs - Date.now());
8046
+ }
8047
+ return 0;
8048
+ }
8049
+ },
8050
+ {
8051
+ key: "getAdBreakEndWallClockMs",
8052
+ value: function getAdBreakEndWallClockMs() {
8053
+ if (this.scteAdBreakEndWallClockMs != null) {
8054
+ return this.scteAdBreakEndWallClockMs;
8055
+ }
8056
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return void 0;
8057
+ return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;
8008
8058
  }
8009
8059
  },
8010
8060
  {
@@ -10793,5 +10843,5 @@ var createStormcloudPlayer = function createStormcloudPlayer(playerList, fallbac
10793
10843
  };
10794
10844
  var StormcloudPlayer = createStormcloudPlayer(players_default, players_default[players_default.length - 1]);
10795
10845
  var StormcloudPlayer_default = StormcloudPlayer;
10796
- 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 };
10846
+ 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 };
10797
10847
  //# sourceMappingURL=index.js.map