stormcloud-video-player 0.8.69 → 0.8.70

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.
@@ -2529,7 +2529,7 @@ function createHlsAdPlayer(contentVideo, options) {
2529
2529
  img.onload = null;
2530
2530
  img.onerror = null;
2531
2531
  inFlightBeacons.delete(img);
2532
- console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingUrl));
2532
+ console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingHost));
2533
2533
  if (outcome !== "load") {
2534
2534
  logFunnel("tracking_request_settled", {
2535
2535
  reason: outcome,
@@ -2547,7 +2547,7 @@ function createHlsAdPlayer(contentVideo, options) {
2547
2547
  return release("timeout");
2548
2548
  }, BEACON_TIMEOUT_MS);
2549
2549
  img.src = trackingUrl;
2550
- console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
2550
+ console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingHost));
2551
2551
  logFunnel("tracking_request_created", {
2552
2552
  trackingHost: trackingHost,
2553
2553
  errorCode: options2 === null || options2 === void 0 ? void 0 : options2.errorCode
@@ -3001,7 +3001,7 @@ function createHlsAdPlayer(contentVideo, options) {
3001
3001
  });
3002
3002
  }
3003
3003
  if (parsed.type === "wrapper") {
3004
- console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(parsed.vastAdTagUri));
3004
+ console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(redactUrl(parsed.vastAdTagUri)));
3005
3005
  }
3006
3006
  return parsed;
3007
3007
  }
@@ -3193,8 +3193,8 @@ function createHlsAdPlayer(contentVideo, options) {
3193
3193
  switch(_state.label){
3194
3194
  case 0:
3195
3195
  attempt++;
3196
- if (kind === "gam") {
3197
- logFunnel("gam_http_request_sent", {
3196
+ if (kind !== "wrapper") {
3197
+ logFunnel("".concat(kind, "_http_request_sent"), {
3198
3198
  attempt: attempt,
3199
3199
  credentials: creds
3200
3200
  });
@@ -3214,8 +3214,8 @@ function createHlsAdPlayer(contentVideo, options) {
3214
3214
  ];
3215
3215
  case 1:
3216
3216
  response = _state.sent();
3217
- if (kind === "gam") {
3218
- logFunnel("gam_response_received", {
3217
+ if (kind !== "wrapper") {
3218
+ logFunnel("".concat(kind, "_response_received"), {
3219
3219
  attempt: attempt,
3220
3220
  httpStatus: response.status,
3221
3221
  ok: response.ok,
@@ -3385,8 +3385,7 @@ function createHlsAdPlayer(contentVideo, options) {
3385
3385
  elapsedMs: Date.now() - fetchStartedAt
3386
3386
  });
3387
3387
  }
3388
- console.log("[HlsAdPlayer] VAST XML received");
3389
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3388
+ console.log("[HlsAdPlayer] VAST XML received (length=".concat(vastXml.length, ")"));
3390
3389
  parsed = parseVastXml(vastXml);
3391
3390
  if (parsed.type === "empty") {
3392
3391
  ;
@@ -3427,16 +3426,35 @@ function createHlsAdPlayer(contentVideo, options) {
3427
3426
  });
3428
3427
  }).apply(this, arguments);
3429
3428
  }
3429
+ function isEdgeEmptySentinelAd(adElement) {
3430
+ var _textOf;
3431
+ if ((adElement.getAttribute("id") || "").trim().toLowerCase() === "empty") {
3432
+ return true;
3433
+ }
3434
+ var inLine = adElement.querySelector(":scope > InLine");
3435
+ if (!inLine) return false;
3436
+ if (/^no ad available$/i.test((_textOf = textOf(inLine.querySelector(":scope > AdTitle"))) !== null && _textOf !== void 0 ? _textOf : "")) {
3437
+ return true;
3438
+ }
3439
+ var durations = Array.from(inLine.querySelectorAll(":scope > Creatives > Creative > Linear > Duration")).map(function(el) {
3440
+ return parseVastTime(textOf(el));
3441
+ });
3442
+ return durations.length > 0 && durations.every(function(seconds) {
3443
+ return seconds === 0;
3444
+ });
3445
+ }
3430
3446
  function fetchAndParsePod(vastTagUrl) {
3447
+ var source = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "gam";
3431
3448
  return _async_to_generator(function() {
3432
- var _root_getAttribute, _root_getAttribute1, vastXml, trimmedXml, xmlDoc, root, adElements, _root_getAttribute2, _empty_errorUrls, empty, _empty_errorCode, resolveAd, podElements, buffet, resolveAll, ads, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, ad, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ad1, err;
3449
+ var _root_getAttribute, _root_getAttribute1, isEdge, vastXml, trimmedXml, xmlDoc, root, adElements, _root_getAttribute2, _empty_errorUrls, empty, _empty_errorCode, resolveAd, candidates, podElements, buffet, resolveAll, ads, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, ad, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ad1, err;
3433
3450
  return _ts_generator(this, function(_state) {
3434
3451
  switch(_state.label){
3435
3452
  case 0:
3436
3453
  lastPodAdElementCount = 0;
3454
+ isEdge = source === "edge";
3437
3455
  return [
3438
3456
  4,
3439
- fetchVastXml(vastTagUrl)
3457
+ fetchVastXml(vastTagUrl, isEdge ? "omit" : "include", source)
3440
3458
  ];
3441
3459
  case 1:
3442
3460
  vastXml = _state.sent();
@@ -3459,7 +3477,6 @@ function createHlsAdPlayer(contentVideo, options) {
3459
3477
  }
3460
3478
  xmlDoc = parseXml(vastXml);
3461
3479
  if (!xmlDoc) {
3462
- console.error("[HlsAdPlayer] Pod VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3463
3480
  logFunnel("vast_parsed", {
3464
3481
  adElementCount: 0,
3465
3482
  reason: "xml_parse_error"
@@ -3531,12 +3548,24 @@ function createHlsAdPlayer(contentVideo, options) {
3531
3548
  });
3532
3549
  })();
3533
3550
  };
3534
- podElements = adElements.filter(function(el) {
3551
+ candidates = adElements;
3552
+ if (isEdge) {
3553
+ candidates = adElements.filter(function(el) {
3554
+ return !isEdgeEmptySentinelAd(el);
3555
+ });
3556
+ if (candidates.length < adElements.length) {
3557
+ logFunnel("edge_empty_sentinel", {
3558
+ sentinelCount: adElements.length - candidates.length,
3559
+ adElementCount: adElements.length
3560
+ });
3561
+ }
3562
+ }
3563
+ podElements = candidates.filter(function(el) {
3535
3564
  return getAdSequence(el) !== void 0;
3536
3565
  }).sort(function(a, b) {
3537
3566
  return getAdSequence(a) - getAdSequence(b);
3538
3567
  });
3539
- buffet = adElements.filter(function(el) {
3568
+ buffet = candidates.filter(function(el) {
3540
3569
  return getAdSequence(el) === void 0;
3541
3570
  });
3542
3571
  resolveAll = function resolveAll(elements) {
@@ -3693,7 +3722,9 @@ function createHlsAdPlayer(contentVideo, options) {
3693
3722
  lastPodAdElementCount = adElements.length;
3694
3723
  return [
3695
3724
  2,
3696
- ads
3725
+ isEdge ? ads.filter(function(ad) {
3726
+ return ad.duration > 0;
3727
+ }) : ads
3697
3728
  ];
3698
3729
  }
3699
3730
  });
@@ -4694,13 +4725,14 @@ function createHlsAdPlayer(contentVideo, options) {
4694
4725
  contentVideo[AD_CONTAINER_PROP] = adContainerEl;
4695
4726
  attachContainerHandlers();
4696
4727
  },
4697
- requestAds: function requestAds(vastTagUrl) {
4728
+ requestAds: function requestAds(vastTagUrl, requestOptions) {
4698
4729
  return _async_to_generator(function() {
4699
- var ads, preloaded, error;
4730
+ var _ref, source, ads, preloaded, error;
4700
4731
  return _ts_generator(this, function(_state) {
4701
4732
  switch(_state.label){
4702
4733
  case 0:
4703
- console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
4734
+ source = (_ref = requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.source) !== null && _ref !== void 0 ? _ref : "gam";
4735
+ console.log("[HlsAdPlayer] Requesting ads (".concat(source, "):"), redactUrl(vastTagUrl));
4704
4736
  if (adPlaying) {
4705
4737
  console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
4706
4738
  return [
@@ -4745,7 +4777,7 @@ function createHlsAdPlayer(contentVideo, options) {
4745
4777
  case 2:
4746
4778
  return [
4747
4779
  4,
4748
- fetchAndParsePod(vastTagUrl)
4780
+ fetchAndParsePod(vastTagUrl, source)
4749
4781
  ];
4750
4782
  case 3:
4751
4783
  ads = _state.sent();
@@ -5155,6 +5187,75 @@ function createHlsAdPlayer(contentVideo, options) {
5155
5187
  return podAds.reduce(function(total, ad) {
5156
5188
  return total + (ad.duration > 0 ? ad.duration * 1e3 : 0);
5157
5189
  }, 0);
5190
+ },
5191
+ limitPodToBudget: function limitPodToBudget(maxDurationMs, maxAds) {
5192
+ if (adPlaying) return podAds.length;
5193
+ var kept = [];
5194
+ var dropped = [];
5195
+ var totalMs = 0;
5196
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
5197
+ try {
5198
+ for(var _iterator = podAds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
5199
+ var ad = _step.value;
5200
+ var durationMs = Math.round(ad.duration * 1e3);
5201
+ if (kept.length < maxAds && totalMs + durationMs <= maxDurationMs) {
5202
+ kept.push(ad);
5203
+ totalMs += durationMs;
5204
+ } else {
5205
+ dropped.push(ad);
5206
+ }
5207
+ }
5208
+ } catch (err) {
5209
+ _didIteratorError = true;
5210
+ _iteratorError = err;
5211
+ } finally{
5212
+ try {
5213
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
5214
+ _iterator.return();
5215
+ }
5216
+ } finally{
5217
+ if (_didIteratorError) {
5218
+ throw _iteratorError;
5219
+ }
5220
+ }
5221
+ }
5222
+ if (dropped.length > 0) {
5223
+ logFunnel("pod_trimmed_to_budget", {
5224
+ keptCount: kept.length,
5225
+ droppedCount: dropped.length,
5226
+ keptDurationMs: totalMs,
5227
+ maxDurationMs: maxDurationMs,
5228
+ maxAds: maxAds
5229
+ });
5230
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
5231
+ try {
5232
+ for(var _iterator1 = dropped[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
5233
+ var ad1 = _step1.value;
5234
+ resolvedAds.add(ad1);
5235
+ fireTrackingPixels(ad1.trackingUrls.notUsed, {
5236
+ ad: ad1
5237
+ });
5238
+ }
5239
+ } catch (err) {
5240
+ _didIteratorError1 = true;
5241
+ _iteratorError1 = err;
5242
+ } finally{
5243
+ try {
5244
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
5245
+ _iterator1.return();
5246
+ }
5247
+ } finally{
5248
+ if (_didIteratorError1) {
5249
+ throw _iteratorError1;
5250
+ }
5251
+ }
5252
+ }
5253
+ }
5254
+ podAds = kept;
5255
+ podIndex = 0;
5256
+ currentAd = kept[0];
5257
+ currentAdMediaFile = void 0;
5258
+ return kept.length;
5158
5259
  }
5159
5260
  };
5160
5261
  }
@@ -9153,6 +9254,59 @@ var HlsEngine = /*#__PURE__*/ function() {
9153
9254
  ]);
9154
9255
  return HlsEngine;
9155
9256
  }();
9257
+ // src/player/EdgePodRequest.ts
9258
+ var EDGE_POD_MAX_ADS = 10;
9259
+ var EDGE_POD_EXCEEDS_BUDGET = "edge_pod_exceeds_budget";
9260
+ function resolveEdgePodBaseUrl(config) {
9261
+ var _config_edgePodUrl;
9262
+ if (config.enableEdgePodRequest !== true) return void 0;
9263
+ var raw = (_config_edgePodUrl = config.edgePodUrl) === null || _config_edgePodUrl === void 0 ? void 0 : _config_edgePodUrl.trim();
9264
+ if (!raw) {
9265
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but edgePodUrl is missing; using GAM only");
9266
+ return void 0;
9267
+ }
9268
+ try {
9269
+ var url = new URL(raw);
9270
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
9271
+ throw new Error("unsupported protocol ".concat(url.protocol));
9272
+ }
9273
+ return url.toString();
9274
+ } catch (unused) {
9275
+ console.warn("[AdBreak][EDGE] edgePodUrl is not a valid http(s) URL; using GAM only");
9276
+ return void 0;
9277
+ }
9278
+ }
9279
+ function buildEdgePodUrl(baseUrl, remainingMs, remainingAdLimit) {
9280
+ var url = new URL(baseUrl);
9281
+ var maxMs = Math.max(0, Math.floor(remainingMs));
9282
+ var maxAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, Math.floor(remainingAdLimit)));
9283
+ url.searchParams.set("pmxd", String(maxMs));
9284
+ url.searchParams.set("pmad", String(maxAds));
9285
+ url.searchParams.set("duration", String(Math.floor(maxMs / 1e3)));
9286
+ url.searchParams.set("count", String(maxAds));
9287
+ return url.toString();
9288
+ }
9289
+ function classifyEdgeVastResponse(result) {
9290
+ var _ref;
9291
+ var _result_error;
9292
+ if (result.error == null) {
9293
+ return result.podAdCount > 0 ? "fill" : "no_fill";
9294
+ }
9295
+ var message = (_ref = (_result_error = result.error) === null || _result_error === void 0 ? void 0 : _result_error.message) !== null && _ref !== void 0 ? _ref : String(result.error);
9296
+ if (/no ads available|fill rejected|no media file|no ad loaded/i.test(message) || message.includes(EDGE_POD_EXCEEDS_BUDGET)) {
9297
+ return "no_fill";
9298
+ }
9299
+ return "invalid";
9300
+ }
9301
+ function describeEdgeUrl(url) {
9302
+ try {
9303
+ var parsed = new URL(url);
9304
+ return "".concat(parsed.origin).concat(parsed.pathname);
9305
+ } catch (unused) {
9306
+ var _url_split_;
9307
+ return (_url_split_ = url.split("?")[0]) !== null && _url_split_ !== void 0 ? _url_split_ : "";
9308
+ }
9309
+ }
9156
9310
  // src/player/PlayerControls.ts
9157
9311
  function getAdAudioVolume(adPlayer) {
9158
9312
  return adPlayer.getOriginalMutedState() ? 0 : adPlayer.getOriginalVolume();
@@ -9311,6 +9465,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9311
9465
  this.controllerCallInFlight = false;
9312
9466
  this.playbackStartedForCurrentRequest = false;
9313
9467
  this.opportunityEpoch = -1;
9468
+ this.edgeExhaustedForBreak = false;
9469
+ this.edgeRequestsInBreak = 0;
9314
9470
  this.adPodQueue = [];
9315
9471
  this.defaultMinRemainingMsToRequestAd = 1e4;
9316
9472
  this.defaultMaxNoFillsPerBreak = 2;
@@ -9461,7 +9617,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9461
9617
  _this.timing.clearAdRequestWatchdog();
9462
9618
  _this.timing.activeAdRequestToken = null;
9463
9619
  _this.showAds = true;
9464
- _this.timing.resetGamNoFillCounter();
9620
+ if (_this.currentPodSource !== "edge") {
9621
+ _this.timing.resetGamNoFillCounter();
9622
+ }
9465
9623
  if (_this.inAdBreak && _this.timing.expectedAdBreakDurationMs != null && _this.timing.getAdBreakEndWallClockMs() == null) {
9466
9624
  _this.timing.scheduleAdStopAtBreakBoundary();
9467
9625
  if (_this.debug) {
@@ -9593,6 +9751,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9593
9751
  key: "beginVmapBreak",
9594
9752
  value: function beginVmapBreak(breakKey, position) {
9595
9753
  this.breakSource = "vmap";
9754
+ this.resetEdgeStateForBreak();
9596
9755
  this.followUpBaseVastUrl = void 0;
9597
9756
  this.unusableResponsesInBreak = 0;
9598
9757
  this.timing.totalAdRequestsInBreak = 0;
@@ -9604,7 +9763,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9604
9763
  key: "handleAdStart",
9605
9764
  value: function handleAdStart(marker) {
9606
9765
  return _async_to_generator(function() {
9607
- var baseVastUrl, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, podPlayed;
9766
+ var baseVastUrl, edgeConfigured, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, edgeUrl, outcome, podPlayed;
9608
9767
  return _ts_generator(this, function(_state) {
9609
9768
  switch(_state.label){
9610
9769
  case 0:
@@ -9618,7 +9777,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9618
9777
  ];
9619
9778
  }
9620
9779
  baseVastUrl = this.adConfig.apiVastTagUrl;
9621
- if (!baseVastUrl) {
9780
+ edgeConfigured = !!this.host.getEdgePodBaseUrl();
9781
+ if (!baseVastUrl && !edgeConfigured) {
9622
9782
  console.warn("[AdBreak] SCTE-35 break started but no direct VAST tag is configured");
9623
9783
  this.clearPendingAdBreak();
9624
9784
  return [
@@ -9634,6 +9794,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9634
9794
  this.breakSource = "scte35";
9635
9795
  this.followUpBaseVastUrl = baseVastUrl;
9636
9796
  this.unusableResponsesInBreak = 0;
9797
+ this.resetEdgeStateForBreak();
9637
9798
  this.setSlotState("NEEDS_NEXT_AD", "scte35_break_start");
9638
9799
  if (this.adConfig.podAssignedByPrefetch) {
9639
9800
  this.adConfig.podAssignedByPrefetch = false;
@@ -9655,7 +9816,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9655
9816
  this.noteOpportunity("scte35", this.cueManager.activeScte35BreakKey, "midroll");
9656
9817
  epoch = this.timing.adBreakEpoch;
9657
9818
  hasPrefetchedUrl = !!(this.pendingAdBreak && this.pendingAdBreak.vastUrls.length > 0);
9658
- if (!(adBreakDurationMs == null && !hasPrefetchedUrl)) return [
9819
+ if (!(adBreakDurationMs == null && (!hasPrefetchedUrl || edgeConfigured))) return [
9659
9820
  3,
9660
9821
  2
9661
9822
  ];
@@ -9674,11 +9835,46 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9674
9835
  mode = this.host.config.isLiveStream ? "LIVE" : "VOD";
9675
9836
  console.log("[AdBreak] ".concat(mode, " break start: target duration=").concat(adBreakDurationMs != null ? "".concat(adBreakDurationMs, "ms") : "unknown"));
9676
9837
  }
9838
+ if (!edgeConfigured) return [
9839
+ 3,
9840
+ 5
9841
+ ];
9842
+ edgeUrl = this.canRequestEdge() ? this.prepareEdgePodRequest() : void 0;
9843
+ if (!edgeUrl) return [
9844
+ 3,
9845
+ 4
9846
+ ];
9847
+ this.clearPendingAdBreak();
9677
9848
  return [
9678
9849
  4,
9679
- this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9850
+ this.runEdgePod(edgeUrl, "edge_break_start")
9680
9851
  ];
9681
9852
  case 3:
9853
+ outcome = _state.sent();
9854
+ this.continueAfterEdgeOutcome(outcome, epoch);
9855
+ return [
9856
+ 2
9857
+ ];
9858
+ case 4:
9859
+ if (!this.edgeExhaustedForBreak) {
9860
+ this.markEdgeExhausted("not_eligible_at_break_start", {
9861
+ isLive: this.isLiveBreak(),
9862
+ durationMs: this.timing.expectedAdBreakDurationMs
9863
+ });
9864
+ }
9865
+ _state.label = 5;
9866
+ case 5:
9867
+ if (!baseVastUrl) {
9868
+ this.requestNextAd("no_gam_source");
9869
+ return [
9870
+ 2
9871
+ ];
9872
+ }
9873
+ return [
9874
+ 4,
9875
+ this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9876
+ ];
9877
+ case 6:
9682
9878
  podPlayed = _state.sent();
9683
9879
  if (!podPlayed && this.inAdBreak && epoch === this.timing.adBreakEpoch) {
9684
9880
  this.setSlotState("NEEDS_NEXT_AD", "initial_request_failed");
@@ -9770,6 +9966,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9770
9966
  adPlayer = this.host.getAdPlayer();
9771
9967
  requestToken = ++this.timing.adRequestTokenCounter;
9772
9968
  this.timing.activeAdRequestToken = requestToken;
9969
+ this.currentPodSource = "gam";
9773
9970
  this.setSlotState("REQUESTING", "initial_pod");
9774
9971
  this.beginFunnelRequest(adPlayer, requestToken, "initial_pod");
9775
9972
  requestUrl = this.applyOpportunityFloor(podUrl);
@@ -9886,6 +10083,224 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9886
10083
  }).call(this);
9887
10084
  }
9888
10085
  },
10086
+ {
10087
+ // ───────────────────────────────────────────────────────────────
10088
+ // Edge-first pods (live SCTE-35 only, GAM fallback)
10089
+ // ───────────────────────────────────────────────────────────────
10090
+ key: "resetEdgeStateForBreak",
10091
+ value: function resetEdgeStateForBreak() {
10092
+ this.edgeExhaustedForBreak = false;
10093
+ this.edgeRequestsInBreak = 0;
10094
+ this.currentPodSource = void 0;
10095
+ }
10096
+ },
10097
+ {
10098
+ key: "isLiveBreak",
10099
+ value: function isLiveBreak() {
10100
+ return this.host.isLiveStream() || this.host.config.isLiveStream === true;
10101
+ }
10102
+ },
10103
+ {
10104
+ key: "canRequestEdge",
10105
+ value: function canRequestEdge() {
10106
+ return this.breakSource === "scte35" && !this.edgeExhaustedForBreak && !!this.host.getEdgePodBaseUrl() && this.isLiveBreak() && this.timing.expectedAdBreakDurationMs != null && this.edgeRequestsInBreak < this.timing.maxTotalAdRequestsPerBreak;
10107
+ }
10108
+ },
10109
+ {
10110
+ key: "markEdgeExhausted",
10111
+ value: function markEdgeExhausted(reason) {
10112
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
10113
+ this.edgeExhaustedForBreak = true;
10114
+ this.countFunnelStage("edge_fallback:".concat(reason));
10115
+ this.logOrchestratorFunnel("edge_exhausted", _object_spread({
10116
+ reason: reason,
10117
+ edgeRequests: this.edgeRequestsInBreak,
10118
+ remainingMs: this.timing.getRemainingAdMs()
10119
+ }, extra));
10120
+ }
10121
+ },
10122
+ {
10123
+ /**
10124
+ * Builds the next Edge request from the budget left right now, or marks Edge
10125
+ * exhausted for this break. Synchronous on purpose: callers must reach the
10126
+ * REQUESTING state in the same tick so a second completion event cannot race
10127
+ * in a parallel request.
10128
+ */ key: "prepareEdgePodRequest",
10129
+ value: function prepareEdgePodRequest() {
10130
+ var baseUrl = this.host.getEdgePodBaseUrl();
10131
+ if (!baseUrl) return void 0;
10132
+ var remainingMs = this.timing.getRemainingAdMs();
10133
+ var remainingAds = Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak);
10134
+ if (remainingAds < 1) {
10135
+ this.markEdgeExhausted("ad_capacity_reached", {
10136
+ adsInBreak: this.timing.totalAdsInBreak
10137
+ });
10138
+ return void 0;
10139
+ }
10140
+ if (remainingMs < this.getMinRemainingMsToRequestAd()) {
10141
+ this.markEdgeExhausted("insufficient_break_time", {
10142
+ remainingMs: remainingMs
10143
+ });
10144
+ return void 0;
10145
+ }
10146
+ try {
10147
+ return buildEdgePodUrl(baseUrl, remainingMs, remainingAds);
10148
+ } catch (unused) {
10149
+ this.markEdgeExhausted("invalid_edge_url");
10150
+ return void 0;
10151
+ }
10152
+ }
10153
+ },
10154
+ {
10155
+ key: "runEdgePod",
10156
+ value: function runEdgePod(requestUrl, trigger) {
10157
+ return _async_to_generator(function() {
10158
+ var _params_get, _params_get1, epoch, generation, adPlayer, requestToken, params, _ref, _adPlayer_getPodAdCount, _adPlayer_getPodTotalDurationMs, remainingMs, remainingAds, podCount, error, errorMessage, classification, diag;
10159
+ return _ts_generator(this, function(_state) {
10160
+ switch(_state.label){
10161
+ case 0:
10162
+ epoch = this.timing.adBreakEpoch;
10163
+ this.recreateAdController("new_request");
10164
+ generation = this.controllerGeneration;
10165
+ adPlayer = this.host.getAdPlayer();
10166
+ requestToken = ++this.timing.adRequestTokenCounter;
10167
+ this.timing.activeAdRequestToken = requestToken;
10168
+ this.currentPodSource = "edge";
10169
+ this.edgeRequestsInBreak++;
10170
+ this.setSlotState("REQUESTING", trigger);
10171
+ this.beginFunnelRequest(adPlayer, requestToken, trigger, "edge");
10172
+ this.currentVastUrl = requestUrl;
10173
+ params = new URL(requestUrl).searchParams;
10174
+ this.logOrchestratorFunnel("edge_request_sent", {
10175
+ trigger: trigger,
10176
+ attempt: this.edgeRequestsInBreak,
10177
+ endpoint: describeEdgeUrl(requestUrl),
10178
+ pmxd: (_params_get = params.get("pmxd")) !== null && _params_get !== void 0 ? _params_get : void 0,
10179
+ pmad: (_params_get1 = params.get("pmad")) !== null && _params_get1 !== void 0 ? _params_get1 : void 0
10180
+ });
10181
+ _state.label = 1;
10182
+ case 1:
10183
+ _state.trys.push([
10184
+ 1,
10185
+ 4,
10186
+ ,
10187
+ 5
10188
+ ]);
10189
+ this.timing.startAdRequestWatchdog(requestToken);
10190
+ return [
10191
+ 4,
10192
+ this.callController(function() {
10193
+ return adPlayer.requestAds(requestUrl, {
10194
+ source: "edge"
10195
+ });
10196
+ })
10197
+ ];
10198
+ case 2:
10199
+ _state.sent();
10200
+ this.timing.clearAdRequestWatchdog();
10201
+ if (this.isStale(epoch, generation)) {
10202
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10203
+ requestId: requestToken,
10204
+ source: "edge"
10205
+ });
10206
+ return [
10207
+ 2,
10208
+ "stale"
10209
+ ];
10210
+ }
10211
+ remainingMs = this.timing.getRemainingAdMs();
10212
+ remainingAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak));
10213
+ podCount = adPlayer.limitPodToBudget ? adPlayer.limitPodToBudget(remainingMs, remainingAds) : (_ref = (_adPlayer_getPodAdCount = adPlayer.getPodAdCount) === null || _adPlayer_getPodAdCount === void 0 ? void 0 : _adPlayer_getPodAdCount.call(adPlayer)) !== null && _ref !== void 0 ? _ref : 0;
10214
+ if (classifyEdgeVastResponse({
10215
+ podAdCount: podCount
10216
+ }) !== "fill") {
10217
+ throw new Error("".concat(EDGE_POD_EXCEEDS_BUDGET, ": no Edge ad fits ").concat(remainingMs, "ms"));
10218
+ }
10219
+ this.setSlotState("READY", "edge_vast_ready");
10220
+ this.logOrchestratorFunnel("edge_fill", {
10221
+ podAdCount: podCount,
10222
+ podDurationMs: (_adPlayer_getPodTotalDurationMs = adPlayer.getPodTotalDurationMs) === null || _adPlayer_getPodTotalDurationMs === void 0 ? void 0 : _adPlayer_getPodTotalDurationMs.call(adPlayer),
10223
+ remainingMs: remainingMs
10224
+ });
10225
+ sendAdLoadedTracking(this.host.config.licenseKey, {
10226
+ source: "hls",
10227
+ vastUrl: requestUrl,
10228
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10229
+ }, this.analyticsContext).catch(function() {});
10230
+ this.timing.startAdFailsafeTimer(requestToken);
10231
+ return [
10232
+ 4,
10233
+ this.callController(function() {
10234
+ return adPlayer.play();
10235
+ })
10236
+ ];
10237
+ case 3:
10238
+ _state.sent();
10239
+ if (this.isStale(epoch, generation)) return [
10240
+ 2,
10241
+ "stale"
10242
+ ];
10243
+ if (this.timing.expectedAdBreakDurationMs != null && this.timing.getAdBreakEndWallClockMs() == null) {
10244
+ this.timing.scheduleAdStopAtBreakBoundary();
10245
+ }
10246
+ adPlayer.setAdVolume(getAdAudioVolume(adPlayer));
10247
+ return [
10248
+ 2,
10249
+ "fill"
10250
+ ];
10251
+ case 4:
10252
+ error = _state.sent();
10253
+ this.timing.clearAdRequestWatchdog();
10254
+ this.timing.clearAdFailsafeTimer();
10255
+ if (this.timing.activeAdRequestToken === requestToken) {
10256
+ this.timing.activeAdRequestToken = null;
10257
+ }
10258
+ errorMessage = error === null || error === void 0 ? void 0 : error.message;
10259
+ if (this.isStale(epoch, generation)) {
10260
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10261
+ requestId: requestToken,
10262
+ source: "edge",
10263
+ errorMessage: errorMessage
10264
+ });
10265
+ return [
10266
+ 2,
10267
+ "stale"
10268
+ ];
10269
+ }
10270
+ classification = classifyEdgeVastResponse({
10271
+ podAdCount: 0,
10272
+ error: error
10273
+ });
10274
+ diag = this.requestDiagnostics.get(requestToken);
10275
+ if (diag && !diag.failureReason) diag.failureReason = "edge_".concat(classification, ": ").concat(errorMessage);
10276
+ this.markEdgeExhausted("edge_".concat(classification), {
10277
+ errorMessage: errorMessage
10278
+ });
10279
+ return [
10280
+ 2,
10281
+ "fallback"
10282
+ ];
10283
+ case 5:
10284
+ return [
10285
+ 2
10286
+ ];
10287
+ }
10288
+ });
10289
+ }).call(this);
10290
+ }
10291
+ },
10292
+ {
10293
+ key: "continueAfterEdgeOutcome",
10294
+ value: function continueAfterEdgeOutcome(outcome, epoch) {
10295
+ if (outcome !== "fallback") return;
10296
+ if (!this.inAdBreak || epoch !== this.timing.adBreakEpoch) return;
10297
+ if (this.debug) {
10298
+ console.log("[AdBreak][EDGE] No usable Edge pod; checking with GAM for the remaining break");
10299
+ }
10300
+ this.setSlotState("NEEDS_NEXT_AD", "edge_fallback");
10301
+ this.requestNextAd("edge_fallback");
10302
+ }
10303
+ },
9889
10304
  {
9890
10305
  // ───────────────────────────────────────────────────────────────
9891
10306
  // Next-ad scheduling (single flight)
@@ -9941,7 +10356,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9941
10356
  value: function getStopRequestingReason(remainingMs) {
9942
10357
  if (this.breakSource === "vmap") {
9943
10358
  if (this.adPodQueue.length === 0) return "vmap_queue_empty";
9944
- } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl) {
10359
+ } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl && !this.canRequestEdge()) {
9945
10360
  return "no_follow_up_source";
9946
10361
  }
9947
10362
  if (this.timing.consecutiveFailures >= this.timing.maxConsecutiveFailures) {
@@ -9953,7 +10368,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9953
10368
  if (this.timing.totalAdRequestsInBreak >= this.timing.maxTotalAdRequestsPerBreak) {
9954
10369
  return "request_cap";
9955
10370
  }
9956
- if (this.timing.isGamInCooldown()) {
10371
+ if (!this.canRequestEdge() && this.timing.isGamInCooldown()) {
9957
10372
  return "gam_cooldown";
9958
10373
  }
9959
10374
  if (this.isBreakTimeBounded() && remainingMs < this.getMinRemainingMsToRequestAd()) {
@@ -10047,10 +10462,33 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10047
10462
  key: "requestFollowUpAd",
10048
10463
  value: function requestFollowUpAd(trigger) {
10049
10464
  return _async_to_generator(function() {
10050
- var url, epoch, unused;
10465
+ var edgeUrl, epoch2, outcome, url, epoch, unused;
10051
10466
  return _ts_generator(this, function(_state) {
10052
10467
  switch(_state.label){
10053
10468
  case 0:
10469
+ if (!this.canRequestEdge()) return [
10470
+ 3,
10471
+ 2
10472
+ ];
10473
+ edgeUrl = this.prepareEdgePodRequest();
10474
+ if (!edgeUrl) {
10475
+ this.requestNextAd("".concat(trigger, ":edge_unavailable"));
10476
+ return [
10477
+ 2
10478
+ ];
10479
+ }
10480
+ epoch2 = this.timing.adBreakEpoch;
10481
+ return [
10482
+ 4,
10483
+ this.runEdgePod(edgeUrl, "edge_".concat(trigger))
10484
+ ];
10485
+ case 1:
10486
+ outcome = _state.sent();
10487
+ this.continueAfterEdgeOutcome(outcome, epoch2);
10488
+ return [
10489
+ 2
10490
+ ];
10491
+ case 2:
10054
10492
  if (this.breakSource === "vmap") {
10055
10493
  url = this.adPodQueue.shift();
10056
10494
  } else if (this.followUpBaseVastUrl) {
@@ -10061,32 +10499,32 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10061
10499
  ];
10062
10500
  this.noteVastUrlGenerated(ensureVideoAdType(url), trigger);
10063
10501
  epoch = this.timing.adBreakEpoch;
10064
- _state.label = 1;
10065
- case 1:
10502
+ _state.label = 3;
10503
+ case 3:
10066
10504
  _state.trys.push([
10067
- 1,
10068
10505
  3,
10506
+ 5,
10069
10507
  ,
10070
- 4
10508
+ 6
10071
10509
  ]);
10072
10510
  return [
10073
10511
  4,
10074
10512
  this.playSingleAd(url, trigger)
10075
10513
  ];
10076
- case 2:
10514
+ case 4:
10077
10515
  _state.sent();
10078
10516
  return [
10079
10517
  3,
10080
- 4
10518
+ 6
10081
10519
  ];
10082
- case 3:
10520
+ case 5:
10083
10521
  unused = _state.sent();
10084
10522
  this.continueAfterRequestFailure(epoch);
10085
10523
  return [
10086
10524
  3,
10087
- 4
10525
+ 6
10088
10526
  ];
10089
- case 4:
10527
+ case 6:
10090
10528
  return [
10091
10529
  2
10092
10530
  ];
@@ -10199,7 +10637,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10199
10637
  case "gam_http_request_sent":
10200
10638
  diag.gamRequestSent++;
10201
10639
  break;
10640
+ case "edge_http_request_sent":
10641
+ diag.edgeRequestSent++;
10642
+ break;
10202
10643
  case "gam_response_received":
10644
+ case "edge_response_received":
10203
10645
  diag.vastHttpStatus = payload.httpStatus;
10204
10646
  break;
10205
10647
  case "vast_parsed":
@@ -10386,6 +10828,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10386
10828
  {
10387
10829
  key: "beginFunnelRequest",
10388
10830
  value: function beginFunnelRequest(adPlayer, requestToken, trigger) {
10831
+ var source = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : "gam";
10389
10832
  var _adPlayer_setFunnelContext;
10390
10833
  if (this.controllerCallInFlight) {
10391
10834
  this.logOrchestratorFunnel("duplicate_request_detected", {
@@ -10400,7 +10843,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10400
10843
  requestId: requestToken,
10401
10844
  controllerId: this.controllerGeneration,
10402
10845
  trigger: trigger,
10846
+ source: source,
10403
10847
  gamRequestSent: 0,
10848
+ edgeRequestSent: 0,
10404
10849
  wrapperDepth: 0,
10405
10850
  inlineResolved: false,
10406
10851
  mediaCandidateCount: 0,
@@ -10531,6 +10976,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10531
10976
  this.timing.activeAdRequestToken = requestToken;
10532
10977
  ap = this.host.getAdPlayer();
10533
10978
  budgetedUrl = this.withCurrentPodBudget(vastTagUrl);
10979
+ this.currentPodSource = "gam";
10534
10980
  this.setSlotState("REQUESTING", trigger);
10535
10981
  this.beginFunnelRequest(ap, requestToken, trigger);
10536
10982
  requestUrl = this.applyOpportunityFloor(budgetedUrl);
@@ -10678,6 +11124,10 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10678
11124
  requests: requests,
10679
11125
  requestRetries: requestRetries,
10680
11126
  gamResponses: count("gam_response_received"),
11127
+ edgeRequests: count("edge_request_sent"),
11128
+ edgeFills: count("edge_fill"),
11129
+ edgeEmptySentinels: count("edge_empty_sentinel"),
11130
+ edgeFallbacks: countPrefix("edge_fallback:"),
10681
11131
  requestsSuppressed: count("request_suppressed"),
10682
11132
  duplicates: count("duplicate_request_detected") + count("duplicate_break_start"),
10683
11133
  filled: count("vast_received"),
@@ -10774,6 +11224,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10774
11224
  this.breakSource = void 0;
10775
11225
  this.followUpBaseVastUrl = void 0;
10776
11226
  this.unusableResponsesInBreak = 0;
11227
+ this.resetEdgeStateForBreak();
10777
11228
  adPlayer.stop().catch(function() {});
10778
11229
  if (!alreadyEnded) this.logBreakFunnel(reason);
10779
11230
  this.opportunityFloor = void 0;
@@ -11087,7 +11538,13 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
11087
11538
  });
11088
11539
  return;
11089
11540
  }
11090
- this.timing.consecutiveFailures++;
11541
+ if (this.currentPodSource === "edge") {
11542
+ this.markEdgeExhausted("edge_playback_failed", {
11543
+ reason: reason
11544
+ });
11545
+ } else {
11546
+ this.timing.consecutiveFailures++;
11547
+ }
11091
11548
  var diag = this.currentRequestId != null ? this.requestDiagnostics.get(this.currentRequestId) : void 0;
11092
11549
  if (diag && !diag.failureReason) diag.failureReason = reason;
11093
11550
  if (this.debug) {
@@ -11117,7 +11574,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11117
11574
  if (this.config.adFloorMaxCents != null) floorBounds.maxCents = this.config.adFloorMaxCents;
11118
11575
  if (this.config.adFloorStepCents != null) floorBounds.stepCents = this.config.adFloorStepCents;
11119
11576
  this.floorLadder = new FloorLadderController(this.config.adFloorLadder, floorBounds);
11577
+ this.floorLadderEnabled = this.config.adFloorLadderEnabled === true;
11120
11578
  this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11579
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11121
11580
  this.adPlayer = this.createAdPlayer(false);
11122
11581
  this.palNonce = createPalNonceManager();
11123
11582
  this.cueManager = new Scte35CueManager(!!this.config.debugAdTiming, {
@@ -11217,11 +11676,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11217
11676
  generatePodVastUrl: function generatePodVastUrl(base, breakDurationMs) {
11218
11677
  return _this.generatePodVastUrl(base, breakDurationMs);
11219
11678
  },
11679
+ getEdgePodBaseUrl: function getEdgePodBaseUrl() {
11680
+ return _this.edgePodBaseUrl;
11681
+ },
11220
11682
  getFloorSnapshot: function getFloorSnapshot(position) {
11221
- return _this.floorLadder.snapshotFor({
11683
+ return _this.floorLadderEnabled ? _this.floorLadder.snapshotFor({
11222
11684
  position: position,
11223
11685
  isCtv: _this.isCtvEnvironment()
11224
- });
11686
+ }) : void 0;
11225
11687
  },
11226
11688
  onAdClick: function onAdClick() {
11227
11689
  return _this.palNonce.sendAdClick();
@@ -11272,11 +11734,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11272
11734
  return report;
11273
11735
  }
11274
11736
  },
11737
+ {
11738
+ key: "setAdFloorLadderEnabled",
11739
+ value: function setAdFloorLadderEnabled(enabled) {
11740
+ this.floorLadderEnabled = enabled;
11741
+ if (enabled) this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11742
+ }
11743
+ },
11744
+ {
11745
+ key: "isAdFloorLadderEnabled",
11746
+ value: function isAdFloorLadderEnabled() {
11747
+ return this.floorLadderEnabled;
11748
+ }
11749
+ },
11275
11750
  {
11276
11751
  key: "warnOnInvalidFloorLadder",
11277
11752
  value: function warnOnInvalidFloorLadder(report) {
11278
11753
  var config = this.floorLadder.get();
11279
- if (report.ok || !config.enabled) return;
11754
+ if (report.ok || !this.floorLadderEnabled || !config.enabled) return;
11280
11755
  console.warn("[AdBreak][FLOOR] configuration has issues; invalid floors will not be sent", {
11281
11756
  errors: report.errors,
11282
11757
  configurationVersion: config.configurationVersion,
@@ -12009,6 +12484,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12009
12484
  this.timing.destroy();
12010
12485
  this.adBreak.clearPendingAdBreak();
12011
12486
  this.placeholder.destroy();
12487
+ 0;
12012
12488
  if (this.timeUpdateHandler) {
12013
12489
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
12014
12490
  delete this.timeUpdateHandler;