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.
@@ -2430,6 +2430,99 @@ function createHlsAdPlayer(contentVideo, options) {
2430
2430
  }
2431
2431
  };
2432
2432
  }
2433
+ // src/utils/mqttConfig.ts
2434
+ var DEFAULT_MQTT_CONFIG = {
2435
+ enabled: true,
2436
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2437
+ brokerPort: 8883,
2438
+ wsPort: 8084,
2439
+ username: "for-sonifi",
2440
+ password: "sonifi-mqtt",
2441
+ topicPrefix: "adstorm/players",
2442
+ qos: 1
2443
+ };
2444
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2445
+ function isMQTTEnabled() {
2446
+ return mqttConfig.enabled;
2447
+ }
2448
+ function buildMQTTBrokerUrl() {
2449
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2450
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2451
+ }
2452
+ function buildPlayerTopic(licenseKey, channel) {
2453
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2454
+ }
2455
+ // src/utils/mqttClient.ts
2456
+ var import_mqtt = __toESM(require("mqtt"), 1);
2457
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
2458
+ var client = null;
2459
+ var status = "disconnected";
2460
+ function initMQTTClient() {
2461
+ if (client || !isMQTTEnabled()) return;
2462
+ var url = buildMQTTBrokerUrl();
2463
+ status = "connecting";
2464
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2465
+ try {
2466
+ client = import_mqtt.default.connect(url, {
2467
+ clientId: clientId,
2468
+ username: mqttConfig.username,
2469
+ password: mqttConfig.password,
2470
+ keepalive: 60,
2471
+ clean: true,
2472
+ reconnectPeriod: 5e3,
2473
+ connectTimeout: 1e4,
2474
+ queueQoSZero: false
2475
+ });
2476
+ } catch (err) {
2477
+ status = "error";
2478
+ console.warn("".concat(LOG, " connect() threw:"), err);
2479
+ return;
2480
+ }
2481
+ client.on("connect", function() {
2482
+ status = "connected";
2483
+ console.info("".concat(LOG, " connected to ").concat(url));
2484
+ });
2485
+ client.on("reconnect", function() {
2486
+ status = "connecting";
2487
+ console.info("".concat(LOG, " reconnecting…"));
2488
+ });
2489
+ client.on("offline", function() {
2490
+ status = "disconnected";
2491
+ console.warn("".concat(LOG, " offline"));
2492
+ });
2493
+ client.on("error", function(err) {
2494
+ status = "error";
2495
+ console.warn("".concat(LOG, " error:"), err.message);
2496
+ });
2497
+ client.on("close", function() {
2498
+ if (status === "connected") {
2499
+ status = "disconnected";
2500
+ }
2501
+ });
2502
+ }
2503
+ function ensureMQTTClient() {
2504
+ if (isMQTTEnabled() && !client) {
2505
+ initMQTTClient();
2506
+ }
2507
+ }
2508
+ function publishMQTT(topic, payload) {
2509
+ if (!isMQTTEnabled()) {
2510
+ return false;
2511
+ }
2512
+ ensureMQTTClient();
2513
+ if (!client) {
2514
+ return false;
2515
+ }
2516
+ try {
2517
+ client.publish(topic, JSON.stringify(payload), {
2518
+ qos: mqttConfig.qos
2519
+ });
2520
+ return true;
2521
+ } catch (err) {
2522
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2523
+ return false;
2524
+ }
2525
+ }
2433
2526
  // src/utils/tracking.ts
2434
2527
  var cachedBrowserId = null;
2435
2528
  function getClientInfo() {
@@ -2576,7 +2669,7 @@ function getClientInfo() {
2576
2669
  }
2577
2670
  function getBrowserID(clientInfo) {
2578
2671
  return _async_to_generator(function() {
2579
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
2672
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
2580
2673
  return _ts_generator(this, function(_state) {
2581
2674
  switch(_state.label){
2582
2675
  case 0:
@@ -2587,7 +2680,7 @@ function getBrowserID(clientInfo) {
2587
2680
  ];
2588
2681
  }
2589
2682
  fingerprintString = JSON.stringify(clientInfo);
2590
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
2683
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
2591
2684
  3,
2592
2685
  5
2593
2686
  ];
@@ -2625,8 +2718,7 @@ function getBrowserID(clientInfo) {
2625
2718
  ];
2626
2719
  case 3:
2627
2720
  hashBuffer = _state.sent();
2628
- hashArray = Array.from(new Uint8Array(hashBuffer));
2629
- hashHex = hashArray.map(function(b) {
2721
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
2630
2722
  return b.toString(16).padStart(2, "0");
2631
2723
  }).join("");
2632
2724
  cachedBrowserId = hashHex;
@@ -2635,8 +2727,8 @@ function getBrowserID(clientInfo) {
2635
2727
  hashHex
2636
2728
  ];
2637
2729
  case 4:
2638
- error = _state.sent();
2639
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
2730
+ unused = _state.sent();
2731
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
2640
2732
  return [
2641
2733
  3,
2642
2734
  5
@@ -2660,177 +2752,91 @@ function getBrowserID(clientInfo) {
2660
2752
  });
2661
2753
  })();
2662
2754
  }
2663
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
2664
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
2665
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
2666
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
2667
- function buildHeaders(licenseKey) {
2668
- var headers = {
2669
- "Content-Type": "application/json"
2670
- };
2671
- if (licenseKey) {
2672
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2673
- }
2674
- return headers;
2675
- }
2676
- function sendTrackRequest(licenseKey, body) {
2677
- return _async_to_generator(function() {
2678
- var response;
2679
- return _ts_generator(this, function(_state) {
2680
- switch(_state.label){
2681
- case 0:
2682
- return [
2683
- 4,
2684
- fetch(TRACK_URL, {
2685
- method: "POST",
2686
- headers: buildHeaders(licenseKey),
2687
- body: JSON.stringify(body)
2688
- })
2689
- ];
2690
- case 1:
2691
- response = _state.sent();
2692
- if (!response.ok) {
2693
- throw new Error("HTTP error! status: ".concat(response.status));
2694
- }
2695
- return [
2696
- 4,
2697
- response.json()
2698
- ];
2699
- case 2:
2700
- _state.sent();
2701
- return [
2702
- 2
2703
- ];
2704
- }
2705
- });
2706
- })();
2755
+ function canPublish(licenseKey) {
2756
+ return Boolean(isMQTTEnabled() && licenseKey);
2707
2757
  }
2708
- function postJson(url, licenseKey, body) {
2758
+ function buildPlayerMetricEvent() {
2709
2759
  return _async_to_generator(function() {
2710
- var response;
2711
- return _ts_generator(this, function(_state) {
2712
- switch(_state.label){
2713
- case 0:
2714
- return [
2715
- 4,
2716
- fetch(url, {
2717
- method: "POST",
2718
- headers: buildHeaders(licenseKey),
2719
- body: JSON.stringify(body)
2720
- })
2721
- ];
2722
- case 1:
2723
- response = _state.sent();
2724
- if (!response.ok) {
2725
- throw new Error("HTTP error! status: ".concat(response.status));
2726
- }
2727
- return [
2728
- 4,
2729
- response.json()
2730
- ];
2731
- case 2:
2732
- _state.sent();
2733
- return [
2734
- 2
2735
- ];
2736
- }
2737
- });
2738
- })();
2739
- }
2740
- function buildPlayerMetricEvent(_0) {
2741
- return _async_to_generator(function(licenseKey) {
2742
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
2760
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
2743
2761
  var _arguments = arguments;
2744
2762
  return _ts_generator(this, function(_state) {
2745
2763
  switch(_state.label){
2746
2764
  case 0:
2747
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2765
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2748
2766
  clientInfo = getClientInfo();
2749
2767
  return [
2750
2768
  4,
2751
2769
  getBrowserID(clientInfo)
2752
2770
  ];
2753
2771
  case 1:
2754
- browserId = _state.sent();
2772
+ playerId = _state.sent();
2755
2773
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
2756
2774
  return [
2757
2775
  2,
2758
- {
2759
- player_id: browserId,
2760
- browserId: browserId,
2776
+ _object_spread({
2777
+ player_id: playerId,
2761
2778
  device_type: clientInfo.deviceType,
2762
- deviceType: clientInfo.deviceType,
2763
- input_stream_type: context.inputStreamType,
2764
- os: clientInfo.os,
2765
- ad_loaded: flags.adLoaded,
2766
- ad_detect: flags.adDetect,
2767
- license_key: licenseKey,
2768
- capture_at: captureAt,
2769
- timestamp: captureAt
2770
- }
2779
+ os: clientInfo.os.toLowerCase(),
2780
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
2781
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
2782
+ capture_at: captureAt
2783
+ }, context.inputStreamType ? {
2784
+ input_stream_type: context.inputStreamType
2785
+ } : {})
2771
2786
  ];
2772
2787
  }
2773
2788
  });
2774
2789
  }).apply(this, arguments);
2775
2790
  }
2791
+ function publishTracking(licenseKey, channel, body) {
2792
+ ensureMQTTClient();
2793
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
2794
+ }
2776
2795
  function sendInitialTracking(_0) {
2777
2796
  return _async_to_generator(function(licenseKey) {
2778
- var context, clientInfo, browserId, trackingData, error;
2797
+ var context, metricEvent, error;
2779
2798
  var _arguments = arguments;
2780
2799
  return _ts_generator(this, function(_state) {
2781
2800
  switch(_state.label){
2782
2801
  case 0:
2783
2802
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2803
+ if (!canPublish(licenseKey)) return [
2804
+ 2
2805
+ ];
2784
2806
  _state.label = 1;
2785
2807
  case 1:
2786
2808
  _state.trys.push([
2787
2809
  1,
2788
- 4,
2810
+ 3,
2789
2811
  ,
2790
- 5
2812
+ 4
2791
2813
  ]);
2792
- clientInfo = getClientInfo();
2793
2814
  return [
2794
2815
  4,
2795
- getBrowserID(clientInfo)
2796
- ];
2797
- case 2:
2798
- browserId = _state.sent();
2799
- trackingData = _object_spread({
2800
- browserId: browserId
2801
- }, clientInfo);
2802
- return [
2803
- 4,
2804
- sendTrackRequest(licenseKey, {
2805
- events: [
2806
- {
2807
- player_id: browserId,
2808
- device_type: clientInfo.deviceType,
2809
- input_stream_type: context.inputStreamType,
2810
- os: clientInfo.os,
2811
- ad_loaded: false,
2812
- ad_detect: false,
2813
- license_key: licenseKey,
2814
- capture_at: /* @__PURE__ */ new Date().toISOString()
2815
- }
2816
- ],
2817
- trackingData: trackingData
2816
+ buildPlayerMetricEvent(context, {
2817
+ adLoaded: false,
2818
+ adDetect: false
2818
2819
  })
2819
2820
  ];
2820
- case 3:
2821
- _state.sent();
2821
+ case 2:
2822
+ metricEvent = _state.sent();
2823
+ publishTracking(licenseKey, "metrics", {
2824
+ events: [
2825
+ metricEvent
2826
+ ]
2827
+ });
2822
2828
  return [
2823
2829
  3,
2824
- 5
2830
+ 4
2825
2831
  ];
2826
- case 4:
2832
+ case 3:
2827
2833
  error = _state.sent();
2828
2834
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
2829
2835
  return [
2830
2836
  3,
2831
- 5
2837
+ 4
2832
2838
  ];
2833
- case 5:
2839
+ case 4:
2834
2840
  return [
2835
2841
  2
2836
2842
  ];
@@ -2934,53 +2940,48 @@ function sendAdImpressionTracking(_0, _1) {
2934
2940
  switch(_state.label){
2935
2941
  case 0:
2936
2942
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2943
+ if (!canPublish(licenseKey)) return [
2944
+ 2
2945
+ ];
2937
2946
  _state.label = 1;
2938
2947
  case 1:
2939
2948
  _state.trys.push([
2940
2949
  1,
2941
- 4,
2950
+ 3,
2942
2951
  ,
2943
- 5
2952
+ 4
2944
2953
  ]);
2945
2954
  return [
2946
2955
  4,
2947
- buildPlayerMetricEvent(licenseKey, context, {
2956
+ buildPlayerMetricEvent(context, {
2948
2957
  captureAt: adImpressionInfo.timestamp
2949
2958
  })
2950
2959
  ];
2951
2960
  case 2:
2952
2961
  metricEvent = _state.sent();
2953
- return [
2954
- 4,
2955
- Promise.all([
2956
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
2957
- postJson(IMPRESSIONS_URL, licenseKey, {
2958
- events: [
2959
- {
2960
- player_id: metricEvent.player_id,
2961
- ad_played_count: 1,
2962
- ad_url: adImpressionInfo.adUrl,
2963
- license_key: licenseKey,
2964
- capture_at: adImpressionInfo.timestamp
2965
- }
2966
- ]
2967
- })
2968
- ])
2969
- ];
2970
- case 3:
2971
- _state.sent();
2962
+ publishTracking(licenseKey, "heartbeat", metricEvent);
2963
+ publishTracking(licenseKey, "impressions", {
2964
+ events: [
2965
+ {
2966
+ player_id: metricEvent.player_id,
2967
+ ad_played_count: 1,
2968
+ ad_url: adImpressionInfo.adUrl,
2969
+ capture_at: adImpressionInfo.timestamp
2970
+ }
2971
+ ]
2972
+ });
2972
2973
  return [
2973
2974
  3,
2974
- 5
2975
+ 4
2975
2976
  ];
2976
- case 4:
2977
+ case 3:
2977
2978
  error = _state.sent();
2978
2979
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
2979
2980
  return [
2980
2981
  3,
2981
- 5
2982
+ 4
2982
2983
  ];
2983
- case 5:
2984
+ case 4:
2984
2985
  return [
2985
2986
  2
2986
2987
  ];
@@ -2996,38 +2997,36 @@ function sendHeartbeat(_0) {
2996
2997
  switch(_state.label){
2997
2998
  case 0:
2998
2999
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
3000
+ if (!canPublish(licenseKey)) return [
3001
+ 2
3002
+ ];
2999
3003
  _state.label = 1;
3000
3004
  case 1:
3001
3005
  _state.trys.push([
3002
3006
  1,
3003
- 4,
3007
+ 3,
3004
3008
  ,
3005
- 5
3009
+ 4
3006
3010
  ]);
3007
3011
  return [
3008
3012
  4,
3009
- buildPlayerMetricEvent(licenseKey, context, flags)
3013
+ buildPlayerMetricEvent(context, flags)
3010
3014
  ];
3011
3015
  case 2:
3012
3016
  heartbeatData = _state.sent();
3013
- return [
3014
- 4,
3015
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
3016
- ];
3017
- case 3:
3018
- _state.sent();
3017
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
3019
3018
  return [
3020
3019
  3,
3021
- 5
3020
+ 4
3022
3021
  ];
3023
- case 4:
3022
+ case 3:
3024
3023
  error = _state.sent();
3025
3024
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
3026
3025
  return [
3027
3026
  3,
3028
- 5
3027
+ 4
3029
3028
  ];
3030
- case 5:
3029
+ case 4:
3031
3030
  return [
3032
3031
  2
3033
3032
  ];
@@ -3880,7 +3879,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3880
3879
  _this.showAds = true;
3881
3880
  _this.resetGamNoFillCounter();
3882
3881
  if (_this.inAdBreak && _this.expectedAdBreakDurationMs != null && _this.adStopTimerId == null) {
3883
- _this.scheduleAdStopCountdown(_this.getRemainingAdMs());
3882
+ _this.scheduleAdStopAtBreakBoundary();
3884
3883
  if (_this.config.debugAdTiming) {
3885
3884
  console.log("[StormcloudVideoPlayer] Starting ad break timer on content_pause (first ad starting)");
3886
3885
  }
@@ -4524,12 +4523,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4524
4523
  if (marker.durationSeconds != null) {
4525
4524
  var newDurationMs = marker.durationSeconds * 1e3;
4526
4525
  if (this.expectedAdBreakDurationMs == null || newDurationMs > this.expectedAdBreakDurationMs) {
4527
- this.expectedAdBreakDurationMs = newDurationMs;
4528
- var elapsedMs = this.currentAdBreakStartWallClockMs != null ? Date.now() - this.currentAdBreakStartWallClockMs : 0;
4529
- var remainingMs = Math.max(0, newDurationMs - elapsedMs);
4530
- this.scheduleAdStopCountdown(remainingMs);
4526
+ this.setAdBreakDurationBoundary(newDurationMs);
4527
+ this.scheduleAdStopAtBreakBoundary();
4531
4528
  if (this.config.debugAdTiming) {
4532
- console.log("[StormcloudVideoPlayer] Updated ad break duration from subsequent marker: ".concat(newDurationMs, "ms, remaining: ").concat(remainingMs, "ms"));
4529
+ console.log("[StormcloudVideoPlayer] Updated ad break duration from subsequent marker: ".concat(newDurationMs, "ms, remaining: ").concat(this.getRemainingAdMs(), "ms"));
4533
4530
  }
4534
4531
  }
4535
4532
  }
@@ -4603,13 +4600,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4603
4600
  }
4604
4601
  if (marker.type === "progress" && this.inAdBreak) {
4605
4602
  if (marker.durationSeconds != null) {
4606
- this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
4607
- }
4608
- if (this.expectedAdBreakDurationMs != null && this.currentAdBreakStartWallClockMs != null) {
4609
- var elapsedMs1 = Date.now() - this.currentAdBreakStartWallClockMs;
4610
- var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
4611
- this.scheduleAdStopCountdown(remainingMs1);
4603
+ this.setAdBreakDurationBoundary(marker.durationSeconds * 1e3);
4612
4604
  }
4605
+ this.scheduleAdStopAtBreakBoundary();
4613
4606
  if (!this.ima.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
4614
4607
  this.tryNextAvailableAdWithRateLimit();
4615
4608
  }
@@ -4634,15 +4627,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4634
4627
  activeAdRequest: this.activeAdRequestToken !== null
4635
4628
  });
4636
4629
  }
4637
- if (adPlaying || remaining > 1e3 && hasQueuedAds) {
4638
- if (this.config.debugAdTiming) {
4639
- console.log("[StormcloudVideoPlayer] Ignoring premature SCTE-35 end marker - ads still active");
4640
- }
4641
- return;
4642
- }
4643
- this.inAdBreak = false;
4644
- this.expectedAdBreakDurationMs = void 0;
4645
- this.currentAdBreakStartWallClockMs = void 0;
4630
+ this.scteAdBreakEndWallClockMs = Date.now();
4646
4631
  this.clearAdStartTimer();
4647
4632
  this.clearAdStopTimer();
4648
4633
  if (adPlaying) {
@@ -4666,12 +4651,46 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4666
4651
  this.inAdBreak = true;
4667
4652
  this.activeScte35BreakKey = cueKey !== null && cueKey !== void 0 ? cueKey : this.pendingScte35CueKey;
4668
4653
  this.scheduledScte35BreakKey = void 0;
4669
- this.expectedAdBreakDurationMs = durationMs;
4670
- this.currentAdBreakStartWallClockMs = Date.now();
4654
+ this.currentAdBreakStartWallClockMs = this.resolveScteBreakStartWallClockMs(marker);
4655
+ this.setAdBreakDurationBoundary(durationMs);
4671
4656
  this.handleAdStart(marker);
4672
- if (this.expectedAdBreakDurationMs != null) {
4673
- this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
4657
+ this.scheduleAdStopAtBreakBoundary();
4658
+ }
4659
+ },
4660
+ {
4661
+ key: "resolveScteBreakStartWallClockMs",
4662
+ value: function resolveScteBreakStartWallClockMs(marker) {
4663
+ var nowWallClockMs = Date.now();
4664
+ if (typeof marker.ptsSeconds !== "number") {
4665
+ return nowWallClockMs;
4674
4666
  }
4667
+ var nowMs = this.video.currentTime * 1e3;
4668
+ var estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;
4669
+ var deltaMs = marker.ptsSeconds * 1e3 - estimatedCurrentPtsMs;
4670
+ if (!Number.isFinite(deltaMs)) {
4671
+ return nowWallClockMs;
4672
+ }
4673
+ return nowWallClockMs + Math.floor(deltaMs);
4674
+ }
4675
+ },
4676
+ {
4677
+ key: "setAdBreakDurationBoundary",
4678
+ value: function setAdBreakDurationBoundary(durationMs) {
4679
+ this.expectedAdBreakDurationMs = durationMs;
4680
+ if (durationMs != null && this.currentAdBreakStartWallClockMs != null) {
4681
+ this.scteAdBreakEndWallClockMs = this.currentAdBreakStartWallClockMs + durationMs;
4682
+ } else {
4683
+ this.scteAdBreakEndWallClockMs = void 0;
4684
+ }
4685
+ }
4686
+ },
4687
+ {
4688
+ key: "scheduleAdStopAtBreakBoundary",
4689
+ value: function scheduleAdStopAtBreakBoundary() {
4690
+ if (this.expectedAdBreakDurationMs == null) {
4691
+ return;
4692
+ }
4693
+ this.scheduleAdStopCountdown(this.getRemainingAdMs());
4675
4694
  }
4676
4695
  },
4677
4696
  {
@@ -6299,7 +6318,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6299
6318
  }
6300
6319
  }
6301
6320
  this.inAdBreak = true;
6302
- this.currentAdBreakStartWallClockMs = Date.now();
6303
6321
  this.currentAdIndex = 0;
6304
6322
  this.totalAdsInBreak = 1;
6305
6323
  this.adPodQueue = [];
@@ -6386,7 +6404,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6386
6404
  case 2:
6387
6405
  _state.sent();
6388
6406
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6389
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6407
+ this.scheduleAdStopAtBreakBoundary();
6390
6408
  }
6391
6409
  adVolume = currentMuted ? 0 : currentVolume;
6392
6410
  this.ima.setAdVolume(adVolume);
@@ -6429,7 +6447,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6429
6447
  case 6:
6430
6448
  _state.sent();
6431
6449
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6432
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6450
+ this.scheduleAdStopAtBreakBoundary();
6433
6451
  }
6434
6452
  adVolume1 = currentMuted ? 0 : currentVolume;
6435
6453
  this.ima.setAdVolume(adVolume1);
@@ -6484,7 +6502,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6484
6502
  case 10:
6485
6503
  _state.sent();
6486
6504
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6487
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6505
+ this.scheduleAdStopAtBreakBoundary();
6488
6506
  }
6489
6507
  adVolume2 = currentMuted ? 0 : currentVolume;
6490
6508
  this.ima.setAdVolume(adVolume2);
@@ -6836,7 +6854,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6836
6854
  case 2:
6837
6855
  _state.sent();
6838
6856
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6839
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6857
+ this.scheduleAdStopAtBreakBoundary();
6840
6858
  }
6841
6859
  currentMuted = this.video.muted;
6842
6860
  currentVolume = this.video.volume;
@@ -7336,21 +7354,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7336
7354
  {
7337
7355
  key: "ensureAdStoppedByTimer",
7338
7356
  value: function ensureAdStoppedByTimer() {
7339
- var _this_config_adBreakCheckIntervalMs, _this_expectedAdBreakDurationMs;
7357
+ var _this_config_adBreakCheckIntervalMs, _this_getAdBreakEndWallClockMs;
7340
7358
  if (!this.inAdBreak) return;
7341
7359
  this.adStopTimerId = void 0;
7342
7360
  var adPlaying = this.ima.isAdPlaying();
7343
- var pendingAds = this.adPodQueue.length > 0;
7344
7361
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
7345
7362
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
7346
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
7347
- var elapsedSinceStartMs = 0;
7348
- if (this.currentAdBreakStartWallClockMs != null) {
7349
- elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
7350
- }
7351
- var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
7352
- var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
7353
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
7363
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
7364
+ var endWallClockMs = (_this_getAdBreakEndWallClockMs = this.getAdBreakEndWallClockMs()) !== null && _this_getAdBreakEndWallClockMs !== void 0 ? _this_getAdBreakEndWallClockMs : Date.now();
7365
+ var overrunMs = Math.max(0, Date.now() - endWallClockMs);
7366
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
7354
7367
  if (shouldExtendAdBreak) {
7355
7368
  this.scheduleAdStopCountdown(checkIntervalMs);
7356
7369
  return;
@@ -7616,7 +7629,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7616
7629
  case 5:
7617
7630
  _state.sent();
7618
7631
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7619
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
7632
+ this.scheduleAdStopAtBreakBoundary();
7620
7633
  }
7621
7634
  currentMuted = this.video.muted;
7622
7635
  currentVolume = this.video.volume;
@@ -7666,7 +7679,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7666
7679
  case 8:
7667
7680
  _state.sent();
7668
7681
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7669
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
7682
+ this.scheduleAdStopAtBreakBoundary();
7670
7683
  }
7671
7684
  currentMuted1 = this.video.muted;
7672
7685
  currentVolume1 = this.video.volume;
@@ -7811,6 +7824,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7811
7824
  this.scheduledScte35BreakKey = void 0;
7812
7825
  this.expectedAdBreakDurationMs = void 0;
7813
7826
  this.currentAdBreakStartWallClockMs = void 0;
7827
+ this.scteAdBreakEndWallClockMs = void 0;
7814
7828
  this.clearAdStartTimer();
7815
7829
  this.clearAdStopTimer();
7816
7830
  this.adPodQueue = [];
@@ -8000,9 +8014,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8000
8014
  {
8001
8015
  key: "getRemainingAdMs",
8002
8016
  value: function getRemainingAdMs() {
8003
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
8004
- var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
8005
- return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
8017
+ var endWallClockMs = this.getAdBreakEndWallClockMs();
8018
+ if (endWallClockMs != null) {
8019
+ return Math.max(0, endWallClockMs - Date.now());
8020
+ }
8021
+ return 0;
8022
+ }
8023
+ },
8024
+ {
8025
+ key: "getAdBreakEndWallClockMs",
8026
+ value: function getAdBreakEndWallClockMs() {
8027
+ if (this.scteAdBreakEndWallClockMs != null) {
8028
+ return this.scteAdBreakEndWallClockMs;
8029
+ }
8030
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return void 0;
8031
+ return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;
8006
8032
  }
8007
8033
  },
8008
8034
  {