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.
@@ -2378,6 +2378,99 @@ function createHlsAdPlayer(contentVideo, options) {
2378
2378
  }
2379
2379
  };
2380
2380
  }
2381
+ // src/utils/mqttConfig.ts
2382
+ var DEFAULT_MQTT_CONFIG = {
2383
+ enabled: true,
2384
+ brokerAddress: "vecbae77.ala.us-east-1.emqxsl.com",
2385
+ brokerPort: 8883,
2386
+ wsPort: 8084,
2387
+ username: "for-sonifi",
2388
+ password: "sonifi-mqtt",
2389
+ topicPrefix: "adstorm/players",
2390
+ qos: 1
2391
+ };
2392
+ var mqttConfig = _object_spread({}, DEFAULT_MQTT_CONFIG);
2393
+ function isMQTTEnabled() {
2394
+ return mqttConfig.enabled;
2395
+ }
2396
+ function buildMQTTBrokerUrl() {
2397
+ if (mqttConfig.brokerUrl) return mqttConfig.brokerUrl;
2398
+ return "wss://".concat(mqttConfig.brokerAddress, ":").concat(mqttConfig.wsPort, "/mqtt");
2399
+ }
2400
+ function buildPlayerTopic(licenseKey, channel) {
2401
+ return "".concat(mqttConfig.topicPrefix, "/").concat(licenseKey, "/").concat(channel);
2402
+ }
2403
+ // src/utils/mqttClient.ts
2404
+ var import_mqtt = __toESM(require("mqtt"), 1);
2405
+ var LOG = "[StormcloudVideoPlayer][MQTT]";
2406
+ var client = null;
2407
+ var status = "disconnected";
2408
+ function initMQTTClient() {
2409
+ if (client || !isMQTTEnabled()) return;
2410
+ var url = buildMQTTBrokerUrl();
2411
+ status = "connecting";
2412
+ var clientId = "stormcloud-vp-".concat(Math.random().toString(36).slice(2, 9));
2413
+ try {
2414
+ client = import_mqtt.default.connect(url, {
2415
+ clientId: clientId,
2416
+ username: mqttConfig.username,
2417
+ password: mqttConfig.password,
2418
+ keepalive: 60,
2419
+ clean: true,
2420
+ reconnectPeriod: 5e3,
2421
+ connectTimeout: 1e4,
2422
+ queueQoSZero: false
2423
+ });
2424
+ } catch (err) {
2425
+ status = "error";
2426
+ console.warn("".concat(LOG, " connect() threw:"), err);
2427
+ return;
2428
+ }
2429
+ client.on("connect", function() {
2430
+ status = "connected";
2431
+ console.info("".concat(LOG, " connected to ").concat(url));
2432
+ });
2433
+ client.on("reconnect", function() {
2434
+ status = "connecting";
2435
+ console.info("".concat(LOG, " reconnecting…"));
2436
+ });
2437
+ client.on("offline", function() {
2438
+ status = "disconnected";
2439
+ console.warn("".concat(LOG, " offline"));
2440
+ });
2441
+ client.on("error", function(err) {
2442
+ status = "error";
2443
+ console.warn("".concat(LOG, " error:"), err.message);
2444
+ });
2445
+ client.on("close", function() {
2446
+ if (status === "connected") {
2447
+ status = "disconnected";
2448
+ }
2449
+ });
2450
+ }
2451
+ function ensureMQTTClient() {
2452
+ if (isMQTTEnabled() && !client) {
2453
+ initMQTTClient();
2454
+ }
2455
+ }
2456
+ function publishMQTT(topic, payload) {
2457
+ if (!isMQTTEnabled()) {
2458
+ return false;
2459
+ }
2460
+ ensureMQTTClient();
2461
+ if (!client) {
2462
+ return false;
2463
+ }
2464
+ try {
2465
+ client.publish(topic, JSON.stringify(payload), {
2466
+ qos: mqttConfig.qos
2467
+ });
2468
+ return true;
2469
+ } catch (err) {
2470
+ console.warn("".concat(LOG, " publish failed on ").concat(topic, ":"), err);
2471
+ return false;
2472
+ }
2473
+ }
2381
2474
  // src/utils/tracking.ts
2382
2475
  var cachedBrowserId = null;
2383
2476
  function getClientInfo() {
@@ -2524,7 +2617,7 @@ function getClientInfo() {
2524
2617
  }
2525
2618
  function getBrowserID(clientInfo) {
2526
2619
  return _async_to_generator(function() {
2527
- var fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashArray, hashHex, error, hash, i1, char, fallbackHash, timestamp, random;
2620
+ var _crypto_subtle, fingerprintString, encodedData, utf8, buffer, i, hashBuffer, hashHex, unused, hash, i1, char, fallbackHash, timestamp, random;
2528
2621
  return _ts_generator(this, function(_state) {
2529
2622
  switch(_state.label){
2530
2623
  case 0:
@@ -2535,7 +2628,7 @@ function getBrowserID(clientInfo) {
2535
2628
  ];
2536
2629
  }
2537
2630
  fingerprintString = JSON.stringify(clientInfo);
2538
- if (!(typeof crypto !== "undefined" && crypto.subtle && crypto.subtle.digest)) return [
2631
+ if (!(typeof crypto !== "undefined" && ((_crypto_subtle = crypto.subtle) === null || _crypto_subtle === void 0 ? void 0 : _crypto_subtle.digest))) return [
2539
2632
  3,
2540
2633
  5
2541
2634
  ];
@@ -2573,8 +2666,7 @@ function getBrowserID(clientInfo) {
2573
2666
  ];
2574
2667
  case 3:
2575
2668
  hashBuffer = _state.sent();
2576
- hashArray = Array.from(new Uint8Array(hashBuffer));
2577
- hashHex = hashArray.map(function(b) {
2669
+ hashHex = Array.from(new Uint8Array(hashBuffer)).map(function(b) {
2578
2670
  return b.toString(16).padStart(2, "0");
2579
2671
  }).join("");
2580
2672
  cachedBrowserId = hashHex;
@@ -2583,8 +2675,8 @@ function getBrowserID(clientInfo) {
2583
2675
  hashHex
2584
2676
  ];
2585
2677
  case 4:
2586
- error = _state.sent();
2587
- console.warn("[StormcloudVideoPlayer] crypto.subtle.digest not supported, using fallback hash");
2678
+ unused = _state.sent();
2679
+ console.warn("[StormcloudVideoPlayer] crypto.subtle not supported, using fallback hash");
2588
2680
  return [
2589
2681
  3,
2590
2682
  5
@@ -2608,177 +2700,91 @@ function getBrowserID(clientInfo) {
2608
2700
  });
2609
2701
  })();
2610
2702
  }
2611
- var PLAYER_TRACKING_BASE_URL = "https://adstorm.co/api-adstorm-dev/adstorm/player-tracking";
2612
- var TRACK_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/metrics/ingest");
2613
- var HEARTBEAT_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/heartbeat");
2614
- var IMPRESSIONS_URL = "".concat(PLAYER_TRACKING_BASE_URL, "/impressions/ingest");
2615
- function buildHeaders(licenseKey) {
2616
- var headers = {
2617
- "Content-Type": "application/json"
2618
- };
2619
- if (licenseKey) {
2620
- headers["Authorization"] = "Bearer ".concat(licenseKey);
2621
- }
2622
- return headers;
2623
- }
2624
- function sendTrackRequest(licenseKey, body) {
2625
- return _async_to_generator(function() {
2626
- var response;
2627
- return _ts_generator(this, function(_state) {
2628
- switch(_state.label){
2629
- case 0:
2630
- return [
2631
- 4,
2632
- fetch(TRACK_URL, {
2633
- method: "POST",
2634
- headers: buildHeaders(licenseKey),
2635
- body: JSON.stringify(body)
2636
- })
2637
- ];
2638
- case 1:
2639
- response = _state.sent();
2640
- if (!response.ok) {
2641
- throw new Error("HTTP error! status: ".concat(response.status));
2642
- }
2643
- return [
2644
- 4,
2645
- response.json()
2646
- ];
2647
- case 2:
2648
- _state.sent();
2649
- return [
2650
- 2
2651
- ];
2652
- }
2653
- });
2654
- })();
2703
+ function canPublish(licenseKey) {
2704
+ return Boolean(isMQTTEnabled() && licenseKey);
2655
2705
  }
2656
- function postJson(url, licenseKey, body) {
2706
+ function buildPlayerMetricEvent() {
2657
2707
  return _async_to_generator(function() {
2658
- var response;
2659
- return _ts_generator(this, function(_state) {
2660
- switch(_state.label){
2661
- case 0:
2662
- return [
2663
- 4,
2664
- fetch(url, {
2665
- method: "POST",
2666
- headers: buildHeaders(licenseKey),
2667
- body: JSON.stringify(body)
2668
- })
2669
- ];
2670
- case 1:
2671
- response = _state.sent();
2672
- if (!response.ok) {
2673
- throw new Error("HTTP error! status: ".concat(response.status));
2674
- }
2675
- return [
2676
- 4,
2677
- response.json()
2678
- ];
2679
- case 2:
2680
- _state.sent();
2681
- return [
2682
- 2
2683
- ];
2684
- }
2685
- });
2686
- })();
2687
- }
2688
- function buildPlayerMetricEvent(_0) {
2689
- return _async_to_generator(function(licenseKey) {
2690
- var context, flags, _flags_captureAt, clientInfo, browserId, captureAt;
2708
+ var context, flags, _flags_captureAt, _flags_adLoaded, _flags_adDetect, clientInfo, playerId, captureAt;
2691
2709
  var _arguments = arguments;
2692
2710
  return _ts_generator(this, function(_state) {
2693
2711
  switch(_state.label){
2694
2712
  case 0:
2695
- context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2713
+ context = _arguments.length > 0 && _arguments[0] !== void 0 ? _arguments[0] : {}, flags = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2696
2714
  clientInfo = getClientInfo();
2697
2715
  return [
2698
2716
  4,
2699
2717
  getBrowserID(clientInfo)
2700
2718
  ];
2701
2719
  case 1:
2702
- browserId = _state.sent();
2720
+ playerId = _state.sent();
2703
2721
  captureAt = (_flags_captureAt = flags.captureAt) !== null && _flags_captureAt !== void 0 ? _flags_captureAt : /* @__PURE__ */ new Date().toISOString();
2704
2722
  return [
2705
2723
  2,
2706
- {
2707
- player_id: browserId,
2708
- browserId: browserId,
2724
+ _object_spread({
2725
+ player_id: playerId,
2709
2726
  device_type: clientInfo.deviceType,
2710
- deviceType: clientInfo.deviceType,
2711
- input_stream_type: context.inputStreamType,
2712
- os: clientInfo.os,
2713
- ad_loaded: flags.adLoaded,
2714
- ad_detect: flags.adDetect,
2715
- license_key: licenseKey,
2716
- capture_at: captureAt,
2717
- timestamp: captureAt
2718
- }
2727
+ os: clientInfo.os.toLowerCase(),
2728
+ ad_loaded: (_flags_adLoaded = flags.adLoaded) !== null && _flags_adLoaded !== void 0 ? _flags_adLoaded : false,
2729
+ ad_detect: (_flags_adDetect = flags.adDetect) !== null && _flags_adDetect !== void 0 ? _flags_adDetect : false,
2730
+ capture_at: captureAt
2731
+ }, context.inputStreamType ? {
2732
+ input_stream_type: context.inputStreamType
2733
+ } : {})
2719
2734
  ];
2720
2735
  }
2721
2736
  });
2722
2737
  }).apply(this, arguments);
2723
2738
  }
2739
+ function publishTracking(licenseKey, channel, body) {
2740
+ ensureMQTTClient();
2741
+ publishMQTT(buildPlayerTopic(licenseKey, channel), body);
2742
+ }
2724
2743
  function sendInitialTracking(_0) {
2725
2744
  return _async_to_generator(function(licenseKey) {
2726
- var context, clientInfo, browserId, trackingData, error;
2745
+ var context, metricEvent, error;
2727
2746
  var _arguments = arguments;
2728
2747
  return _ts_generator(this, function(_state) {
2729
2748
  switch(_state.label){
2730
2749
  case 0:
2731
2750
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {};
2751
+ if (!canPublish(licenseKey)) return [
2752
+ 2
2753
+ ];
2732
2754
  _state.label = 1;
2733
2755
  case 1:
2734
2756
  _state.trys.push([
2735
2757
  1,
2736
- 4,
2758
+ 3,
2737
2759
  ,
2738
- 5
2760
+ 4
2739
2761
  ]);
2740
- clientInfo = getClientInfo();
2741
2762
  return [
2742
2763
  4,
2743
- getBrowserID(clientInfo)
2744
- ];
2745
- case 2:
2746
- browserId = _state.sent();
2747
- trackingData = _object_spread({
2748
- browserId: browserId
2749
- }, clientInfo);
2750
- return [
2751
- 4,
2752
- sendTrackRequest(licenseKey, {
2753
- events: [
2754
- {
2755
- player_id: browserId,
2756
- device_type: clientInfo.deviceType,
2757
- input_stream_type: context.inputStreamType,
2758
- os: clientInfo.os,
2759
- ad_loaded: false,
2760
- ad_detect: false,
2761
- license_key: licenseKey,
2762
- capture_at: /* @__PURE__ */ new Date().toISOString()
2763
- }
2764
- ],
2765
- trackingData: trackingData
2764
+ buildPlayerMetricEvent(context, {
2765
+ adLoaded: false,
2766
+ adDetect: false
2766
2767
  })
2767
2768
  ];
2768
- case 3:
2769
- _state.sent();
2769
+ case 2:
2770
+ metricEvent = _state.sent();
2771
+ publishTracking(licenseKey, "metrics", {
2772
+ events: [
2773
+ metricEvent
2774
+ ]
2775
+ });
2770
2776
  return [
2771
2777
  3,
2772
- 5
2778
+ 4
2773
2779
  ];
2774
- case 4:
2780
+ case 3:
2775
2781
  error = _state.sent();
2776
2782
  console.error("[StormcloudVideoPlayer] Error sending initial tracking data:", error);
2777
2783
  return [
2778
2784
  3,
2779
- 5
2785
+ 4
2780
2786
  ];
2781
- case 5:
2787
+ case 4:
2782
2788
  return [
2783
2789
  2
2784
2790
  ];
@@ -2882,53 +2888,48 @@ function sendAdImpressionTracking(_0, _1) {
2882
2888
  switch(_state.label){
2883
2889
  case 0:
2884
2890
  context = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2891
+ if (!canPublish(licenseKey)) return [
2892
+ 2
2893
+ ];
2885
2894
  _state.label = 1;
2886
2895
  case 1:
2887
2896
  _state.trys.push([
2888
2897
  1,
2889
- 4,
2898
+ 3,
2890
2899
  ,
2891
- 5
2900
+ 4
2892
2901
  ]);
2893
2902
  return [
2894
2903
  4,
2895
- buildPlayerMetricEvent(licenseKey, context, {
2904
+ buildPlayerMetricEvent(context, {
2896
2905
  captureAt: adImpressionInfo.timestamp
2897
2906
  })
2898
2907
  ];
2899
2908
  case 2:
2900
2909
  metricEvent = _state.sent();
2901
- return [
2902
- 4,
2903
- Promise.all([
2904
- postJson(HEARTBEAT_URL, licenseKey, metricEvent),
2905
- postJson(IMPRESSIONS_URL, licenseKey, {
2906
- events: [
2907
- {
2908
- player_id: metricEvent.player_id,
2909
- ad_played_count: 1,
2910
- ad_url: adImpressionInfo.adUrl,
2911
- license_key: licenseKey,
2912
- capture_at: adImpressionInfo.timestamp
2913
- }
2914
- ]
2915
- })
2916
- ])
2917
- ];
2918
- case 3:
2919
- _state.sent();
2910
+ publishTracking(licenseKey, "heartbeat", metricEvent);
2911
+ publishTracking(licenseKey, "impressions", {
2912
+ events: [
2913
+ {
2914
+ player_id: metricEvent.player_id,
2915
+ ad_played_count: 1,
2916
+ ad_url: adImpressionInfo.adUrl,
2917
+ capture_at: adImpressionInfo.timestamp
2918
+ }
2919
+ ]
2920
+ });
2920
2921
  return [
2921
2922
  3,
2922
- 5
2923
+ 4
2923
2924
  ];
2924
- case 4:
2925
+ case 3:
2925
2926
  error = _state.sent();
2926
2927
  console.error("[StormcloudVideoPlayer] Error sending ad impression tracking:", error);
2927
2928
  return [
2928
2929
  3,
2929
- 5
2930
+ 4
2930
2931
  ];
2931
- case 5:
2932
+ case 4:
2932
2933
  return [
2933
2934
  2
2934
2935
  ];
@@ -2944,38 +2945,36 @@ function sendHeartbeat(_0) {
2944
2945
  switch(_state.label){
2945
2946
  case 0:
2946
2947
  context = _arguments.length > 1 && _arguments[1] !== void 0 ? _arguments[1] : {}, flags = _arguments.length > 2 && _arguments[2] !== void 0 ? _arguments[2] : {};
2948
+ if (!canPublish(licenseKey)) return [
2949
+ 2
2950
+ ];
2947
2951
  _state.label = 1;
2948
2952
  case 1:
2949
2953
  _state.trys.push([
2950
2954
  1,
2951
- 4,
2955
+ 3,
2952
2956
  ,
2953
- 5
2957
+ 4
2954
2958
  ]);
2955
2959
  return [
2956
2960
  4,
2957
- buildPlayerMetricEvent(licenseKey, context, flags)
2961
+ buildPlayerMetricEvent(context, flags)
2958
2962
  ];
2959
2963
  case 2:
2960
2964
  heartbeatData = _state.sent();
2961
- return [
2962
- 4,
2963
- postJson(HEARTBEAT_URL, licenseKey, heartbeatData)
2964
- ];
2965
- case 3:
2966
- _state.sent();
2965
+ publishTracking(licenseKey, "heartbeat", heartbeatData);
2967
2966
  return [
2968
2967
  3,
2969
- 5
2968
+ 4
2970
2969
  ];
2971
- case 4:
2970
+ case 3:
2972
2971
  error = _state.sent();
2973
2972
  console.error("[StormcloudVideoPlayer] Error sending heartbeat:", error);
2974
2973
  return [
2975
2974
  3,
2976
- 5
2975
+ 4
2977
2976
  ];
2978
- case 5:
2977
+ case 4:
2979
2978
  return [
2980
2979
  2
2981
2980
  ];
@@ -3828,7 +3827,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
3828
3827
  _this.showAds = true;
3829
3828
  _this.resetGamNoFillCounter();
3830
3829
  if (_this.inAdBreak && _this.expectedAdBreakDurationMs != null && _this.adStopTimerId == null) {
3831
- _this.scheduleAdStopCountdown(_this.getRemainingAdMs());
3830
+ _this.scheduleAdStopAtBreakBoundary();
3832
3831
  if (_this.config.debugAdTiming) {
3833
3832
  console.log("[StormcloudVideoPlayer] Starting ad break timer on content_pause (first ad starting)");
3834
3833
  }
@@ -4472,12 +4471,10 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4472
4471
  if (marker.durationSeconds != null) {
4473
4472
  var newDurationMs = marker.durationSeconds * 1e3;
4474
4473
  if (this.expectedAdBreakDurationMs == null || newDurationMs > this.expectedAdBreakDurationMs) {
4475
- this.expectedAdBreakDurationMs = newDurationMs;
4476
- var elapsedMs = this.currentAdBreakStartWallClockMs != null ? Date.now() - this.currentAdBreakStartWallClockMs : 0;
4477
- var remainingMs = Math.max(0, newDurationMs - elapsedMs);
4478
- this.scheduleAdStopCountdown(remainingMs);
4474
+ this.setAdBreakDurationBoundary(newDurationMs);
4475
+ this.scheduleAdStopAtBreakBoundary();
4479
4476
  if (this.config.debugAdTiming) {
4480
- console.log("[StormcloudVideoPlayer] Updated ad break duration from subsequent marker: ".concat(newDurationMs, "ms, remaining: ").concat(remainingMs, "ms"));
4477
+ console.log("[StormcloudVideoPlayer] Updated ad break duration from subsequent marker: ".concat(newDurationMs, "ms, remaining: ").concat(this.getRemainingAdMs(), "ms"));
4481
4478
  }
4482
4479
  }
4483
4480
  }
@@ -4551,13 +4548,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4551
4548
  }
4552
4549
  if (marker.type === "progress" && this.inAdBreak) {
4553
4550
  if (marker.durationSeconds != null) {
4554
- this.expectedAdBreakDurationMs = marker.durationSeconds * 1e3;
4555
- }
4556
- if (this.expectedAdBreakDurationMs != null && this.currentAdBreakStartWallClockMs != null) {
4557
- var elapsedMs1 = Date.now() - this.currentAdBreakStartWallClockMs;
4558
- var remainingMs1 = Math.max(0, this.expectedAdBreakDurationMs - elapsedMs1);
4559
- this.scheduleAdStopCountdown(remainingMs1);
4551
+ this.setAdBreakDurationBoundary(marker.durationSeconds * 1e3);
4560
4552
  }
4553
+ this.scheduleAdStopAtBreakBoundary();
4561
4554
  if (!this.ima.isAdPlaying() && this.activeAdRequestToken === null && this.adRequestQueue.length > 0) {
4562
4555
  this.tryNextAvailableAdWithRateLimit();
4563
4556
  }
@@ -4582,15 +4575,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4582
4575
  activeAdRequest: this.activeAdRequestToken !== null
4583
4576
  });
4584
4577
  }
4585
- if (adPlaying || remaining > 1e3 && hasQueuedAds) {
4586
- if (this.config.debugAdTiming) {
4587
- console.log("[StormcloudVideoPlayer] Ignoring premature SCTE-35 end marker - ads still active");
4588
- }
4589
- return;
4590
- }
4591
- this.inAdBreak = false;
4592
- this.expectedAdBreakDurationMs = void 0;
4593
- this.currentAdBreakStartWallClockMs = void 0;
4578
+ this.scteAdBreakEndWallClockMs = Date.now();
4594
4579
  this.clearAdStartTimer();
4595
4580
  this.clearAdStopTimer();
4596
4581
  if (adPlaying) {
@@ -4614,12 +4599,46 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
4614
4599
  this.inAdBreak = true;
4615
4600
  this.activeScte35BreakKey = cueKey !== null && cueKey !== void 0 ? cueKey : this.pendingScte35CueKey;
4616
4601
  this.scheduledScte35BreakKey = void 0;
4617
- this.expectedAdBreakDurationMs = durationMs;
4618
- this.currentAdBreakStartWallClockMs = Date.now();
4602
+ this.currentAdBreakStartWallClockMs = this.resolveScteBreakStartWallClockMs(marker);
4603
+ this.setAdBreakDurationBoundary(durationMs);
4619
4604
  this.handleAdStart(marker);
4620
- if (this.expectedAdBreakDurationMs != null) {
4621
- this.scheduleAdStopCountdown(this.expectedAdBreakDurationMs);
4605
+ this.scheduleAdStopAtBreakBoundary();
4606
+ }
4607
+ },
4608
+ {
4609
+ key: "resolveScteBreakStartWallClockMs",
4610
+ value: function resolveScteBreakStartWallClockMs(marker) {
4611
+ var nowWallClockMs = Date.now();
4612
+ if (typeof marker.ptsSeconds !== "number") {
4613
+ return nowWallClockMs;
4622
4614
  }
4615
+ var nowMs = this.video.currentTime * 1e3;
4616
+ var estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;
4617
+ var deltaMs = marker.ptsSeconds * 1e3 - estimatedCurrentPtsMs;
4618
+ if (!Number.isFinite(deltaMs)) {
4619
+ return nowWallClockMs;
4620
+ }
4621
+ return nowWallClockMs + Math.floor(deltaMs);
4622
+ }
4623
+ },
4624
+ {
4625
+ key: "setAdBreakDurationBoundary",
4626
+ value: function setAdBreakDurationBoundary(durationMs) {
4627
+ this.expectedAdBreakDurationMs = durationMs;
4628
+ if (durationMs != null && this.currentAdBreakStartWallClockMs != null) {
4629
+ this.scteAdBreakEndWallClockMs = this.currentAdBreakStartWallClockMs + durationMs;
4630
+ } else {
4631
+ this.scteAdBreakEndWallClockMs = void 0;
4632
+ }
4633
+ }
4634
+ },
4635
+ {
4636
+ key: "scheduleAdStopAtBreakBoundary",
4637
+ value: function scheduleAdStopAtBreakBoundary() {
4638
+ if (this.expectedAdBreakDurationMs == null) {
4639
+ return;
4640
+ }
4641
+ this.scheduleAdStopCountdown(this.getRemainingAdMs());
4623
4642
  }
4624
4643
  },
4625
4644
  {
@@ -6247,7 +6266,6 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6247
6266
  }
6248
6267
  }
6249
6268
  this.inAdBreak = true;
6250
- this.currentAdBreakStartWallClockMs = Date.now();
6251
6269
  this.currentAdIndex = 0;
6252
6270
  this.totalAdsInBreak = 1;
6253
6271
  this.adPodQueue = [];
@@ -6334,7 +6352,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6334
6352
  case 2:
6335
6353
  _state.sent();
6336
6354
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6337
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6355
+ this.scheduleAdStopAtBreakBoundary();
6338
6356
  }
6339
6357
  adVolume = currentMuted ? 0 : currentVolume;
6340
6358
  this.ima.setAdVolume(adVolume);
@@ -6377,7 +6395,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6377
6395
  case 6:
6378
6396
  _state.sent();
6379
6397
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6380
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6398
+ this.scheduleAdStopAtBreakBoundary();
6381
6399
  }
6382
6400
  adVolume1 = currentMuted ? 0 : currentVolume;
6383
6401
  this.ima.setAdVolume(adVolume1);
@@ -6432,7 +6450,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6432
6450
  case 10:
6433
6451
  _state.sent();
6434
6452
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6435
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6453
+ this.scheduleAdStopAtBreakBoundary();
6436
6454
  }
6437
6455
  adVolume2 = currentMuted ? 0 : currentVolume;
6438
6456
  this.ima.setAdVolume(adVolume2);
@@ -6784,7 +6802,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
6784
6802
  case 2:
6785
6803
  _state.sent();
6786
6804
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
6787
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
6805
+ this.scheduleAdStopAtBreakBoundary();
6788
6806
  }
6789
6807
  currentMuted = this.video.muted;
6790
6808
  currentVolume = this.video.volume;
@@ -7284,21 +7302,16 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7284
7302
  {
7285
7303
  key: "ensureAdStoppedByTimer",
7286
7304
  value: function ensureAdStoppedByTimer() {
7287
- var _this_config_adBreakCheckIntervalMs, _this_expectedAdBreakDurationMs;
7305
+ var _this_config_adBreakCheckIntervalMs, _this_getAdBreakEndWallClockMs;
7288
7306
  if (!this.inAdBreak) return;
7289
7307
  this.adStopTimerId = void 0;
7290
7308
  var adPlaying = this.ima.isAdPlaying();
7291
- var pendingAds = this.adPodQueue.length > 0;
7292
7309
  var checkIntervalMs = Math.max(250, Math.floor((_this_config_adBreakCheckIntervalMs = this.config.adBreakCheckIntervalMs) !== null && _this_config_adBreakCheckIntervalMs !== void 0 ? _this_config_adBreakCheckIntervalMs : 1e3));
7293
7310
  var maxExtensionMsConfig = this.config.maxAdBreakExtensionMs;
7294
- var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 6e4;
7295
- var elapsedSinceStartMs = 0;
7296
- if (this.currentAdBreakStartWallClockMs != null) {
7297
- elapsedSinceStartMs = Date.now() - this.currentAdBreakStartWallClockMs;
7298
- }
7299
- var expectedDurationMs = (_this_expectedAdBreakDurationMs = this.expectedAdBreakDurationMs) !== null && _this_expectedAdBreakDurationMs !== void 0 ? _this_expectedAdBreakDurationMs : 0;
7300
- var overrunMs = Math.max(0, elapsedSinceStartMs - expectedDurationMs);
7301
- var shouldExtendAdBreak = (adPlaying || pendingAds || this.showAds) && overrunMs < maxExtensionMs;
7311
+ var maxExtensionMs = typeof maxExtensionMsConfig === "number" && maxExtensionMsConfig > 0 ? maxExtensionMsConfig : 0;
7312
+ var endWallClockMs = (_this_getAdBreakEndWallClockMs = this.getAdBreakEndWallClockMs()) !== null && _this_getAdBreakEndWallClockMs !== void 0 ? _this_getAdBreakEndWallClockMs : Date.now();
7313
+ var overrunMs = Math.max(0, Date.now() - endWallClockMs);
7314
+ var shouldExtendAdBreak = adPlaying && overrunMs < maxExtensionMs;
7302
7315
  if (shouldExtendAdBreak) {
7303
7316
  this.scheduleAdStopCountdown(checkIntervalMs);
7304
7317
  return;
@@ -7564,7 +7577,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7564
7577
  case 5:
7565
7578
  _state.sent();
7566
7579
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7567
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
7580
+ this.scheduleAdStopAtBreakBoundary();
7568
7581
  }
7569
7582
  currentMuted = this.video.muted;
7570
7583
  currentVolume = this.video.volume;
@@ -7614,7 +7627,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7614
7627
  case 8:
7615
7628
  _state.sent();
7616
7629
  if (this.expectedAdBreakDurationMs != null && this.adStopTimerId == null) {
7617
- this.scheduleAdStopCountdown(this.getRemainingAdMs());
7630
+ this.scheduleAdStopAtBreakBoundary();
7618
7631
  }
7619
7632
  currentMuted1 = this.video.muted;
7620
7633
  currentVolume1 = this.video.volume;
@@ -7759,6 +7772,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7759
7772
  this.scheduledScte35BreakKey = void 0;
7760
7773
  this.expectedAdBreakDurationMs = void 0;
7761
7774
  this.currentAdBreakStartWallClockMs = void 0;
7775
+ this.scteAdBreakEndWallClockMs = void 0;
7762
7776
  this.clearAdStartTimer();
7763
7777
  this.clearAdStopTimer();
7764
7778
  this.adPodQueue = [];
@@ -7948,9 +7962,21 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
7948
7962
  {
7949
7963
  key: "getRemainingAdMs",
7950
7964
  value: function getRemainingAdMs() {
7951
- if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return 0;
7952
- var elapsed = Date.now() - this.currentAdBreakStartWallClockMs;
7953
- return Math.max(0, this.expectedAdBreakDurationMs - elapsed);
7965
+ var endWallClockMs = this.getAdBreakEndWallClockMs();
7966
+ if (endWallClockMs != null) {
7967
+ return Math.max(0, endWallClockMs - Date.now());
7968
+ }
7969
+ return 0;
7970
+ }
7971
+ },
7972
+ {
7973
+ key: "getAdBreakEndWallClockMs",
7974
+ value: function getAdBreakEndWallClockMs() {
7975
+ if (this.scteAdBreakEndWallClockMs != null) {
7976
+ return this.scteAdBreakEndWallClockMs;
7977
+ }
7978
+ if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) return void 0;
7979
+ return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;
7954
7980
  }
7955
7981
  },
7956
7982
  {