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