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.
@@ -2565,7 +2565,7 @@ function createHlsAdPlayer(contentVideo, options) {
2565
2565
  img.onload = null;
2566
2566
  img.onerror = null;
2567
2567
  inFlightBeacons.delete(img);
2568
- console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingUrl));
2568
+ console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingHost));
2569
2569
  if (outcome !== "load") {
2570
2570
  logFunnel("tracking_request_settled", {
2571
2571
  reason: outcome,
@@ -2583,7 +2583,7 @@ function createHlsAdPlayer(contentVideo, options) {
2583
2583
  return release("timeout");
2584
2584
  }, BEACON_TIMEOUT_MS);
2585
2585
  img.src = trackingUrl;
2586
- console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
2586
+ console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingHost));
2587
2587
  logFunnel("tracking_request_created", {
2588
2588
  trackingHost: trackingHost,
2589
2589
  errorCode: options2 === null || options2 === void 0 ? void 0 : options2.errorCode
@@ -3037,7 +3037,7 @@ function createHlsAdPlayer(contentVideo, options) {
3037
3037
  });
3038
3038
  }
3039
3039
  if (parsed.type === "wrapper") {
3040
- console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(parsed.vastAdTagUri));
3040
+ console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(redactUrl(parsed.vastAdTagUri)));
3041
3041
  }
3042
3042
  return parsed;
3043
3043
  }
@@ -3229,8 +3229,8 @@ function createHlsAdPlayer(contentVideo, options) {
3229
3229
  switch(_state.label){
3230
3230
  case 0:
3231
3231
  attempt++;
3232
- if (kind === "gam") {
3233
- logFunnel("gam_http_request_sent", {
3232
+ if (kind !== "wrapper") {
3233
+ logFunnel("".concat(kind, "_http_request_sent"), {
3234
3234
  attempt: attempt,
3235
3235
  credentials: creds
3236
3236
  });
@@ -3250,8 +3250,8 @@ function createHlsAdPlayer(contentVideo, options) {
3250
3250
  ];
3251
3251
  case 1:
3252
3252
  response = _state.sent();
3253
- if (kind === "gam") {
3254
- logFunnel("gam_response_received", {
3253
+ if (kind !== "wrapper") {
3254
+ logFunnel("".concat(kind, "_response_received"), {
3255
3255
  attempt: attempt,
3256
3256
  httpStatus: response.status,
3257
3257
  ok: response.ok,
@@ -3421,8 +3421,7 @@ function createHlsAdPlayer(contentVideo, options) {
3421
3421
  elapsedMs: Date.now() - fetchStartedAt
3422
3422
  });
3423
3423
  }
3424
- console.log("[HlsAdPlayer] VAST XML received");
3425
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3424
+ console.log("[HlsAdPlayer] VAST XML received (length=".concat(vastXml.length, ")"));
3426
3425
  parsed = parseVastXml(vastXml);
3427
3426
  if (parsed.type === "empty") {
3428
3427
  ;
@@ -3463,16 +3462,35 @@ function createHlsAdPlayer(contentVideo, options) {
3463
3462
  });
3464
3463
  }).apply(this, arguments);
3465
3464
  }
3465
+ function isEdgeEmptySentinelAd(adElement) {
3466
+ var _textOf;
3467
+ if ((adElement.getAttribute("id") || "").trim().toLowerCase() === "empty") {
3468
+ return true;
3469
+ }
3470
+ var inLine = adElement.querySelector(":scope > InLine");
3471
+ if (!inLine) return false;
3472
+ if (/^no ad available$/i.test((_textOf = textOf(inLine.querySelector(":scope > AdTitle"))) !== null && _textOf !== void 0 ? _textOf : "")) {
3473
+ return true;
3474
+ }
3475
+ var durations = Array.from(inLine.querySelectorAll(":scope > Creatives > Creative > Linear > Duration")).map(function(el) {
3476
+ return parseVastTime(textOf(el));
3477
+ });
3478
+ return durations.length > 0 && durations.every(function(seconds) {
3479
+ return seconds === 0;
3480
+ });
3481
+ }
3466
3482
  function fetchAndParsePod(vastTagUrl) {
3483
+ var source = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "gam";
3467
3484
  return _async_to_generator(function() {
3468
- 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;
3485
+ 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;
3469
3486
  return _ts_generator(this, function(_state) {
3470
3487
  switch(_state.label){
3471
3488
  case 0:
3472
3489
  lastPodAdElementCount = 0;
3490
+ isEdge = source === "edge";
3473
3491
  return [
3474
3492
  4,
3475
- fetchVastXml(vastTagUrl)
3493
+ fetchVastXml(vastTagUrl, isEdge ? "omit" : "include", source)
3476
3494
  ];
3477
3495
  case 1:
3478
3496
  vastXml = _state.sent();
@@ -3495,7 +3513,6 @@ function createHlsAdPlayer(contentVideo, options) {
3495
3513
  }
3496
3514
  xmlDoc = parseXml(vastXml);
3497
3515
  if (!xmlDoc) {
3498
- console.error("[HlsAdPlayer] Pod VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3499
3516
  logFunnel("vast_parsed", {
3500
3517
  adElementCount: 0,
3501
3518
  reason: "xml_parse_error"
@@ -3567,12 +3584,24 @@ function createHlsAdPlayer(contentVideo, options) {
3567
3584
  });
3568
3585
  })();
3569
3586
  };
3570
- podElements = adElements.filter(function(el) {
3587
+ candidates = adElements;
3588
+ if (isEdge) {
3589
+ candidates = adElements.filter(function(el) {
3590
+ return !isEdgeEmptySentinelAd(el);
3591
+ });
3592
+ if (candidates.length < adElements.length) {
3593
+ logFunnel("edge_empty_sentinel", {
3594
+ sentinelCount: adElements.length - candidates.length,
3595
+ adElementCount: adElements.length
3596
+ });
3597
+ }
3598
+ }
3599
+ podElements = candidates.filter(function(el) {
3571
3600
  return getAdSequence(el) !== void 0;
3572
3601
  }).sort(function(a, b) {
3573
3602
  return getAdSequence(a) - getAdSequence(b);
3574
3603
  });
3575
- buffet = adElements.filter(function(el) {
3604
+ buffet = candidates.filter(function(el) {
3576
3605
  return getAdSequence(el) === void 0;
3577
3606
  });
3578
3607
  resolveAll = function resolveAll(elements) {
@@ -3729,7 +3758,9 @@ function createHlsAdPlayer(contentVideo, options) {
3729
3758
  lastPodAdElementCount = adElements.length;
3730
3759
  return [
3731
3760
  2,
3732
- ads
3761
+ isEdge ? ads.filter(function(ad) {
3762
+ return ad.duration > 0;
3763
+ }) : ads
3733
3764
  ];
3734
3765
  }
3735
3766
  });
@@ -4730,13 +4761,14 @@ function createHlsAdPlayer(contentVideo, options) {
4730
4761
  contentVideo[AD_CONTAINER_PROP] = adContainerEl;
4731
4762
  attachContainerHandlers();
4732
4763
  },
4733
- requestAds: function requestAds(vastTagUrl) {
4764
+ requestAds: function requestAds(vastTagUrl, requestOptions) {
4734
4765
  return _async_to_generator(function() {
4735
- var ads, preloaded, error;
4766
+ var _ref, source, ads, preloaded, error;
4736
4767
  return _ts_generator(this, function(_state) {
4737
4768
  switch(_state.label){
4738
4769
  case 0:
4739
- console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
4770
+ source = (_ref = requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.source) !== null && _ref !== void 0 ? _ref : "gam";
4771
+ console.log("[HlsAdPlayer] Requesting ads (".concat(source, "):"), redactUrl(vastTagUrl));
4740
4772
  if (adPlaying) {
4741
4773
  console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
4742
4774
  return [
@@ -4781,7 +4813,7 @@ function createHlsAdPlayer(contentVideo, options) {
4781
4813
  case 2:
4782
4814
  return [
4783
4815
  4,
4784
- fetchAndParsePod(vastTagUrl)
4816
+ fetchAndParsePod(vastTagUrl, source)
4785
4817
  ];
4786
4818
  case 3:
4787
4819
  ads = _state.sent();
@@ -5191,6 +5223,75 @@ function createHlsAdPlayer(contentVideo, options) {
5191
5223
  return podAds.reduce(function(total, ad) {
5192
5224
  return total + (ad.duration > 0 ? ad.duration * 1e3 : 0);
5193
5225
  }, 0);
5226
+ },
5227
+ limitPodToBudget: function limitPodToBudget(maxDurationMs, maxAds) {
5228
+ if (adPlaying) return podAds.length;
5229
+ var kept = [];
5230
+ var dropped = [];
5231
+ var totalMs = 0;
5232
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
5233
+ try {
5234
+ for(var _iterator = podAds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
5235
+ var ad = _step.value;
5236
+ var durationMs = Math.round(ad.duration * 1e3);
5237
+ if (kept.length < maxAds && totalMs + durationMs <= maxDurationMs) {
5238
+ kept.push(ad);
5239
+ totalMs += durationMs;
5240
+ } else {
5241
+ dropped.push(ad);
5242
+ }
5243
+ }
5244
+ } catch (err) {
5245
+ _didIteratorError = true;
5246
+ _iteratorError = err;
5247
+ } finally{
5248
+ try {
5249
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
5250
+ _iterator.return();
5251
+ }
5252
+ } finally{
5253
+ if (_didIteratorError) {
5254
+ throw _iteratorError;
5255
+ }
5256
+ }
5257
+ }
5258
+ if (dropped.length > 0) {
5259
+ logFunnel("pod_trimmed_to_budget", {
5260
+ keptCount: kept.length,
5261
+ droppedCount: dropped.length,
5262
+ keptDurationMs: totalMs,
5263
+ maxDurationMs: maxDurationMs,
5264
+ maxAds: maxAds
5265
+ });
5266
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
5267
+ try {
5268
+ for(var _iterator1 = dropped[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
5269
+ var ad1 = _step1.value;
5270
+ resolvedAds.add(ad1);
5271
+ fireTrackingPixels(ad1.trackingUrls.notUsed, {
5272
+ ad: ad1
5273
+ });
5274
+ }
5275
+ } catch (err) {
5276
+ _didIteratorError1 = true;
5277
+ _iteratorError1 = err;
5278
+ } finally{
5279
+ try {
5280
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
5281
+ _iterator1.return();
5282
+ }
5283
+ } finally{
5284
+ if (_didIteratorError1) {
5285
+ throw _iteratorError1;
5286
+ }
5287
+ }
5288
+ }
5289
+ }
5290
+ podAds = kept;
5291
+ podIndex = 0;
5292
+ currentAd = kept[0];
5293
+ currentAdMediaFile = void 0;
5294
+ return kept.length;
5194
5295
  }
5195
5296
  };
5196
5297
  }
@@ -9189,6 +9290,59 @@ var HlsEngine = /*#__PURE__*/ function() {
9189
9290
  ]);
9190
9291
  return HlsEngine;
9191
9292
  }();
9293
+ // src/player/EdgePodRequest.ts
9294
+ var EDGE_POD_MAX_ADS = 10;
9295
+ var EDGE_POD_EXCEEDS_BUDGET = "edge_pod_exceeds_budget";
9296
+ function resolveEdgePodBaseUrl(config) {
9297
+ var _config_edgePodUrl;
9298
+ if (config.enableEdgePodRequest !== true) return void 0;
9299
+ var raw = (_config_edgePodUrl = config.edgePodUrl) === null || _config_edgePodUrl === void 0 ? void 0 : _config_edgePodUrl.trim();
9300
+ if (!raw) {
9301
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but edgePodUrl is missing; using GAM only");
9302
+ return void 0;
9303
+ }
9304
+ try {
9305
+ var url = new URL(raw);
9306
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
9307
+ throw new Error("unsupported protocol ".concat(url.protocol));
9308
+ }
9309
+ return url.toString();
9310
+ } catch (unused) {
9311
+ console.warn("[AdBreak][EDGE] edgePodUrl is not a valid http(s) URL; using GAM only");
9312
+ return void 0;
9313
+ }
9314
+ }
9315
+ function buildEdgePodUrl(baseUrl, remainingMs, remainingAdLimit) {
9316
+ var url = new URL(baseUrl);
9317
+ var maxMs = Math.max(0, Math.floor(remainingMs));
9318
+ var maxAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, Math.floor(remainingAdLimit)));
9319
+ url.searchParams.set("pmxd", String(maxMs));
9320
+ url.searchParams.set("pmad", String(maxAds));
9321
+ url.searchParams.set("duration", String(Math.floor(maxMs / 1e3)));
9322
+ url.searchParams.set("count", String(maxAds));
9323
+ return url.toString();
9324
+ }
9325
+ function classifyEdgeVastResponse(result) {
9326
+ var _ref;
9327
+ var _result_error;
9328
+ if (result.error == null) {
9329
+ return result.podAdCount > 0 ? "fill" : "no_fill";
9330
+ }
9331
+ 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);
9332
+ if (/no ads available|fill rejected|no media file|no ad loaded/i.test(message) || message.includes(EDGE_POD_EXCEEDS_BUDGET)) {
9333
+ return "no_fill";
9334
+ }
9335
+ return "invalid";
9336
+ }
9337
+ function describeEdgeUrl(url) {
9338
+ try {
9339
+ var parsed = new URL(url);
9340
+ return "".concat(parsed.origin).concat(parsed.pathname);
9341
+ } catch (unused) {
9342
+ var _url_split_;
9343
+ return (_url_split_ = url.split("?")[0]) !== null && _url_split_ !== void 0 ? _url_split_ : "";
9344
+ }
9345
+ }
9192
9346
  // src/player/PlayerControls.ts
9193
9347
  function getAdAudioVolume(adPlayer) {
9194
9348
  return adPlayer.getOriginalMutedState() ? 0 : adPlayer.getOriginalVolume();
@@ -9347,6 +9501,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9347
9501
  this.controllerCallInFlight = false;
9348
9502
  this.playbackStartedForCurrentRequest = false;
9349
9503
  this.opportunityEpoch = -1;
9504
+ this.edgeExhaustedForBreak = false;
9505
+ this.edgeRequestsInBreak = 0;
9350
9506
  this.adPodQueue = [];
9351
9507
  this.defaultMinRemainingMsToRequestAd = 1e4;
9352
9508
  this.defaultMaxNoFillsPerBreak = 2;
@@ -9497,7 +9653,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9497
9653
  _this.timing.clearAdRequestWatchdog();
9498
9654
  _this.timing.activeAdRequestToken = null;
9499
9655
  _this.showAds = true;
9500
- _this.timing.resetGamNoFillCounter();
9656
+ if (_this.currentPodSource !== "edge") {
9657
+ _this.timing.resetGamNoFillCounter();
9658
+ }
9501
9659
  if (_this.inAdBreak && _this.timing.expectedAdBreakDurationMs != null && _this.timing.getAdBreakEndWallClockMs() == null) {
9502
9660
  _this.timing.scheduleAdStopAtBreakBoundary();
9503
9661
  if (_this.debug) {
@@ -9629,6 +9787,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9629
9787
  key: "beginVmapBreak",
9630
9788
  value: function beginVmapBreak(breakKey, position) {
9631
9789
  this.breakSource = "vmap";
9790
+ this.resetEdgeStateForBreak();
9632
9791
  this.followUpBaseVastUrl = void 0;
9633
9792
  this.unusableResponsesInBreak = 0;
9634
9793
  this.timing.totalAdRequestsInBreak = 0;
@@ -9640,7 +9799,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9640
9799
  key: "handleAdStart",
9641
9800
  value: function handleAdStart(marker) {
9642
9801
  return _async_to_generator(function() {
9643
- var baseVastUrl, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, podPlayed;
9802
+ var baseVastUrl, edgeConfigured, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, edgeUrl, outcome, podPlayed;
9644
9803
  return _ts_generator(this, function(_state) {
9645
9804
  switch(_state.label){
9646
9805
  case 0:
@@ -9654,7 +9813,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9654
9813
  ];
9655
9814
  }
9656
9815
  baseVastUrl = this.adConfig.apiVastTagUrl;
9657
- if (!baseVastUrl) {
9816
+ edgeConfigured = !!this.host.getEdgePodBaseUrl();
9817
+ if (!baseVastUrl && !edgeConfigured) {
9658
9818
  console.warn("[AdBreak] SCTE-35 break started but no direct VAST tag is configured");
9659
9819
  this.clearPendingAdBreak();
9660
9820
  return [
@@ -9670,6 +9830,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9670
9830
  this.breakSource = "scte35";
9671
9831
  this.followUpBaseVastUrl = baseVastUrl;
9672
9832
  this.unusableResponsesInBreak = 0;
9833
+ this.resetEdgeStateForBreak();
9673
9834
  this.setSlotState("NEEDS_NEXT_AD", "scte35_break_start");
9674
9835
  if (this.adConfig.podAssignedByPrefetch) {
9675
9836
  this.adConfig.podAssignedByPrefetch = false;
@@ -9691,7 +9852,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9691
9852
  this.noteOpportunity("scte35", this.cueManager.activeScte35BreakKey, "midroll");
9692
9853
  epoch = this.timing.adBreakEpoch;
9693
9854
  hasPrefetchedUrl = !!(this.pendingAdBreak && this.pendingAdBreak.vastUrls.length > 0);
9694
- if (!(adBreakDurationMs == null && !hasPrefetchedUrl)) return [
9855
+ if (!(adBreakDurationMs == null && (!hasPrefetchedUrl || edgeConfigured))) return [
9695
9856
  3,
9696
9857
  2
9697
9858
  ];
@@ -9710,11 +9871,46 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9710
9871
  mode = this.host.config.isLiveStream ? "LIVE" : "VOD";
9711
9872
  console.log("[AdBreak] ".concat(mode, " break start: target duration=").concat(adBreakDurationMs != null ? "".concat(adBreakDurationMs, "ms") : "unknown"));
9712
9873
  }
9874
+ if (!edgeConfigured) return [
9875
+ 3,
9876
+ 5
9877
+ ];
9878
+ edgeUrl = this.canRequestEdge() ? this.prepareEdgePodRequest() : void 0;
9879
+ if (!edgeUrl) return [
9880
+ 3,
9881
+ 4
9882
+ ];
9883
+ this.clearPendingAdBreak();
9713
9884
  return [
9714
9885
  4,
9715
- this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9886
+ this.runEdgePod(edgeUrl, "edge_break_start")
9716
9887
  ];
9717
9888
  case 3:
9889
+ outcome = _state.sent();
9890
+ this.continueAfterEdgeOutcome(outcome, epoch);
9891
+ return [
9892
+ 2
9893
+ ];
9894
+ case 4:
9895
+ if (!this.edgeExhaustedForBreak) {
9896
+ this.markEdgeExhausted("not_eligible_at_break_start", {
9897
+ isLive: this.isLiveBreak(),
9898
+ durationMs: this.timing.expectedAdBreakDurationMs
9899
+ });
9900
+ }
9901
+ _state.label = 5;
9902
+ case 5:
9903
+ if (!baseVastUrl) {
9904
+ this.requestNextAd("no_gam_source");
9905
+ return [
9906
+ 2
9907
+ ];
9908
+ }
9909
+ return [
9910
+ 4,
9911
+ this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9912
+ ];
9913
+ case 6:
9718
9914
  podPlayed = _state.sent();
9719
9915
  if (!podPlayed && this.inAdBreak && epoch === this.timing.adBreakEpoch) {
9720
9916
  this.setSlotState("NEEDS_NEXT_AD", "initial_request_failed");
@@ -9806,6 +10002,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9806
10002
  adPlayer = this.host.getAdPlayer();
9807
10003
  requestToken = ++this.timing.adRequestTokenCounter;
9808
10004
  this.timing.activeAdRequestToken = requestToken;
10005
+ this.currentPodSource = "gam";
9809
10006
  this.setSlotState("REQUESTING", "initial_pod");
9810
10007
  this.beginFunnelRequest(adPlayer, requestToken, "initial_pod");
9811
10008
  requestUrl = this.applyOpportunityFloor(podUrl);
@@ -9922,6 +10119,224 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9922
10119
  }).call(this);
9923
10120
  }
9924
10121
  },
10122
+ {
10123
+ // ───────────────────────────────────────────────────────────────
10124
+ // Edge-first pods (live SCTE-35 only, GAM fallback)
10125
+ // ───────────────────────────────────────────────────────────────
10126
+ key: "resetEdgeStateForBreak",
10127
+ value: function resetEdgeStateForBreak() {
10128
+ this.edgeExhaustedForBreak = false;
10129
+ this.edgeRequestsInBreak = 0;
10130
+ this.currentPodSource = void 0;
10131
+ }
10132
+ },
10133
+ {
10134
+ key: "isLiveBreak",
10135
+ value: function isLiveBreak() {
10136
+ return this.host.isLiveStream() || this.host.config.isLiveStream === true;
10137
+ }
10138
+ },
10139
+ {
10140
+ key: "canRequestEdge",
10141
+ value: function canRequestEdge() {
10142
+ return this.breakSource === "scte35" && !this.edgeExhaustedForBreak && !!this.host.getEdgePodBaseUrl() && this.isLiveBreak() && this.timing.expectedAdBreakDurationMs != null && this.edgeRequestsInBreak < this.timing.maxTotalAdRequestsPerBreak;
10143
+ }
10144
+ },
10145
+ {
10146
+ key: "markEdgeExhausted",
10147
+ value: function markEdgeExhausted(reason) {
10148
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
10149
+ this.edgeExhaustedForBreak = true;
10150
+ this.countFunnelStage("edge_fallback:".concat(reason));
10151
+ this.logOrchestratorFunnel("edge_exhausted", _object_spread({
10152
+ reason: reason,
10153
+ edgeRequests: this.edgeRequestsInBreak,
10154
+ remainingMs: this.timing.getRemainingAdMs()
10155
+ }, extra));
10156
+ }
10157
+ },
10158
+ {
10159
+ /**
10160
+ * Builds the next Edge request from the budget left right now, or marks Edge
10161
+ * exhausted for this break. Synchronous on purpose: callers must reach the
10162
+ * REQUESTING state in the same tick so a second completion event cannot race
10163
+ * in a parallel request.
10164
+ */ key: "prepareEdgePodRequest",
10165
+ value: function prepareEdgePodRequest() {
10166
+ var baseUrl = this.host.getEdgePodBaseUrl();
10167
+ if (!baseUrl) return void 0;
10168
+ var remainingMs = this.timing.getRemainingAdMs();
10169
+ var remainingAds = Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak);
10170
+ if (remainingAds < 1) {
10171
+ this.markEdgeExhausted("ad_capacity_reached", {
10172
+ adsInBreak: this.timing.totalAdsInBreak
10173
+ });
10174
+ return void 0;
10175
+ }
10176
+ if (remainingMs < this.getMinRemainingMsToRequestAd()) {
10177
+ this.markEdgeExhausted("insufficient_break_time", {
10178
+ remainingMs: remainingMs
10179
+ });
10180
+ return void 0;
10181
+ }
10182
+ try {
10183
+ return buildEdgePodUrl(baseUrl, remainingMs, remainingAds);
10184
+ } catch (unused) {
10185
+ this.markEdgeExhausted("invalid_edge_url");
10186
+ return void 0;
10187
+ }
10188
+ }
10189
+ },
10190
+ {
10191
+ key: "runEdgePod",
10192
+ value: function runEdgePod(requestUrl, trigger) {
10193
+ return _async_to_generator(function() {
10194
+ var _params_get, _params_get1, epoch, generation, adPlayer, requestToken, params, _ref, _adPlayer_getPodAdCount, _adPlayer_getPodTotalDurationMs, remainingMs, remainingAds, podCount, error, errorMessage, classification, diag;
10195
+ return _ts_generator(this, function(_state) {
10196
+ switch(_state.label){
10197
+ case 0:
10198
+ epoch = this.timing.adBreakEpoch;
10199
+ this.recreateAdController("new_request");
10200
+ generation = this.controllerGeneration;
10201
+ adPlayer = this.host.getAdPlayer();
10202
+ requestToken = ++this.timing.adRequestTokenCounter;
10203
+ this.timing.activeAdRequestToken = requestToken;
10204
+ this.currentPodSource = "edge";
10205
+ this.edgeRequestsInBreak++;
10206
+ this.setSlotState("REQUESTING", trigger);
10207
+ this.beginFunnelRequest(adPlayer, requestToken, trigger, "edge");
10208
+ this.currentVastUrl = requestUrl;
10209
+ params = new URL(requestUrl).searchParams;
10210
+ this.logOrchestratorFunnel("edge_request_sent", {
10211
+ trigger: trigger,
10212
+ attempt: this.edgeRequestsInBreak,
10213
+ endpoint: describeEdgeUrl(requestUrl),
10214
+ pmxd: (_params_get = params.get("pmxd")) !== null && _params_get !== void 0 ? _params_get : void 0,
10215
+ pmad: (_params_get1 = params.get("pmad")) !== null && _params_get1 !== void 0 ? _params_get1 : void 0
10216
+ });
10217
+ _state.label = 1;
10218
+ case 1:
10219
+ _state.trys.push([
10220
+ 1,
10221
+ 4,
10222
+ ,
10223
+ 5
10224
+ ]);
10225
+ this.timing.startAdRequestWatchdog(requestToken);
10226
+ return [
10227
+ 4,
10228
+ this.callController(function() {
10229
+ return adPlayer.requestAds(requestUrl, {
10230
+ source: "edge"
10231
+ });
10232
+ })
10233
+ ];
10234
+ case 2:
10235
+ _state.sent();
10236
+ this.timing.clearAdRequestWatchdog();
10237
+ if (this.isStale(epoch, generation)) {
10238
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10239
+ requestId: requestToken,
10240
+ source: "edge"
10241
+ });
10242
+ return [
10243
+ 2,
10244
+ "stale"
10245
+ ];
10246
+ }
10247
+ remainingMs = this.timing.getRemainingAdMs();
10248
+ remainingAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak));
10249
+ 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;
10250
+ if (classifyEdgeVastResponse({
10251
+ podAdCount: podCount
10252
+ }) !== "fill") {
10253
+ throw new Error("".concat(EDGE_POD_EXCEEDS_BUDGET, ": no Edge ad fits ").concat(remainingMs, "ms"));
10254
+ }
10255
+ this.setSlotState("READY", "edge_vast_ready");
10256
+ this.logOrchestratorFunnel("edge_fill", {
10257
+ podAdCount: podCount,
10258
+ podDurationMs: (_adPlayer_getPodTotalDurationMs = adPlayer.getPodTotalDurationMs) === null || _adPlayer_getPodTotalDurationMs === void 0 ? void 0 : _adPlayer_getPodTotalDurationMs.call(adPlayer),
10259
+ remainingMs: remainingMs
10260
+ });
10261
+ sendAdLoadedTracking(this.host.config.licenseKey, {
10262
+ source: "hls",
10263
+ vastUrl: requestUrl,
10264
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10265
+ }, this.analyticsContext).catch(function() {});
10266
+ this.timing.startAdFailsafeTimer(requestToken);
10267
+ return [
10268
+ 4,
10269
+ this.callController(function() {
10270
+ return adPlayer.play();
10271
+ })
10272
+ ];
10273
+ case 3:
10274
+ _state.sent();
10275
+ if (this.isStale(epoch, generation)) return [
10276
+ 2,
10277
+ "stale"
10278
+ ];
10279
+ if (this.timing.expectedAdBreakDurationMs != null && this.timing.getAdBreakEndWallClockMs() == null) {
10280
+ this.timing.scheduleAdStopAtBreakBoundary();
10281
+ }
10282
+ adPlayer.setAdVolume(getAdAudioVolume(adPlayer));
10283
+ return [
10284
+ 2,
10285
+ "fill"
10286
+ ];
10287
+ case 4:
10288
+ error = _state.sent();
10289
+ this.timing.clearAdRequestWatchdog();
10290
+ this.timing.clearAdFailsafeTimer();
10291
+ if (this.timing.activeAdRequestToken === requestToken) {
10292
+ this.timing.activeAdRequestToken = null;
10293
+ }
10294
+ errorMessage = error === null || error === void 0 ? void 0 : error.message;
10295
+ if (this.isStale(epoch, generation)) {
10296
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10297
+ requestId: requestToken,
10298
+ source: "edge",
10299
+ errorMessage: errorMessage
10300
+ });
10301
+ return [
10302
+ 2,
10303
+ "stale"
10304
+ ];
10305
+ }
10306
+ classification = classifyEdgeVastResponse({
10307
+ podAdCount: 0,
10308
+ error: error
10309
+ });
10310
+ diag = this.requestDiagnostics.get(requestToken);
10311
+ if (diag && !diag.failureReason) diag.failureReason = "edge_".concat(classification, ": ").concat(errorMessage);
10312
+ this.markEdgeExhausted("edge_".concat(classification), {
10313
+ errorMessage: errorMessage
10314
+ });
10315
+ return [
10316
+ 2,
10317
+ "fallback"
10318
+ ];
10319
+ case 5:
10320
+ return [
10321
+ 2
10322
+ ];
10323
+ }
10324
+ });
10325
+ }).call(this);
10326
+ }
10327
+ },
10328
+ {
10329
+ key: "continueAfterEdgeOutcome",
10330
+ value: function continueAfterEdgeOutcome(outcome, epoch) {
10331
+ if (outcome !== "fallback") return;
10332
+ if (!this.inAdBreak || epoch !== this.timing.adBreakEpoch) return;
10333
+ if (this.debug) {
10334
+ console.log("[AdBreak][EDGE] No usable Edge pod; checking with GAM for the remaining break");
10335
+ }
10336
+ this.setSlotState("NEEDS_NEXT_AD", "edge_fallback");
10337
+ this.requestNextAd("edge_fallback");
10338
+ }
10339
+ },
9925
10340
  {
9926
10341
  // ───────────────────────────────────────────────────────────────
9927
10342
  // Next-ad scheduling (single flight)
@@ -9977,7 +10392,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9977
10392
  value: function getStopRequestingReason(remainingMs) {
9978
10393
  if (this.breakSource === "vmap") {
9979
10394
  if (this.adPodQueue.length === 0) return "vmap_queue_empty";
9980
- } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl) {
10395
+ } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl && !this.canRequestEdge()) {
9981
10396
  return "no_follow_up_source";
9982
10397
  }
9983
10398
  if (this.timing.consecutiveFailures >= this.timing.maxConsecutiveFailures) {
@@ -9989,7 +10404,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9989
10404
  if (this.timing.totalAdRequestsInBreak >= this.timing.maxTotalAdRequestsPerBreak) {
9990
10405
  return "request_cap";
9991
10406
  }
9992
- if (this.timing.isGamInCooldown()) {
10407
+ if (!this.canRequestEdge() && this.timing.isGamInCooldown()) {
9993
10408
  return "gam_cooldown";
9994
10409
  }
9995
10410
  if (this.isBreakTimeBounded() && remainingMs < this.getMinRemainingMsToRequestAd()) {
@@ -10083,10 +10498,33 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10083
10498
  key: "requestFollowUpAd",
10084
10499
  value: function requestFollowUpAd(trigger) {
10085
10500
  return _async_to_generator(function() {
10086
- var url, epoch, unused;
10501
+ var edgeUrl, epoch2, outcome, url, epoch, unused;
10087
10502
  return _ts_generator(this, function(_state) {
10088
10503
  switch(_state.label){
10089
10504
  case 0:
10505
+ if (!this.canRequestEdge()) return [
10506
+ 3,
10507
+ 2
10508
+ ];
10509
+ edgeUrl = this.prepareEdgePodRequest();
10510
+ if (!edgeUrl) {
10511
+ this.requestNextAd("".concat(trigger, ":edge_unavailable"));
10512
+ return [
10513
+ 2
10514
+ ];
10515
+ }
10516
+ epoch2 = this.timing.adBreakEpoch;
10517
+ return [
10518
+ 4,
10519
+ this.runEdgePod(edgeUrl, "edge_".concat(trigger))
10520
+ ];
10521
+ case 1:
10522
+ outcome = _state.sent();
10523
+ this.continueAfterEdgeOutcome(outcome, epoch2);
10524
+ return [
10525
+ 2
10526
+ ];
10527
+ case 2:
10090
10528
  if (this.breakSource === "vmap") {
10091
10529
  url = this.adPodQueue.shift();
10092
10530
  } else if (this.followUpBaseVastUrl) {
@@ -10097,32 +10535,32 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10097
10535
  ];
10098
10536
  this.noteVastUrlGenerated(ensureVideoAdType(url), trigger);
10099
10537
  epoch = this.timing.adBreakEpoch;
10100
- _state.label = 1;
10101
- case 1:
10538
+ _state.label = 3;
10539
+ case 3:
10102
10540
  _state.trys.push([
10103
- 1,
10104
10541
  3,
10542
+ 5,
10105
10543
  ,
10106
- 4
10544
+ 6
10107
10545
  ]);
10108
10546
  return [
10109
10547
  4,
10110
10548
  this.playSingleAd(url, trigger)
10111
10549
  ];
10112
- case 2:
10550
+ case 4:
10113
10551
  _state.sent();
10114
10552
  return [
10115
10553
  3,
10116
- 4
10554
+ 6
10117
10555
  ];
10118
- case 3:
10556
+ case 5:
10119
10557
  unused = _state.sent();
10120
10558
  this.continueAfterRequestFailure(epoch);
10121
10559
  return [
10122
10560
  3,
10123
- 4
10561
+ 6
10124
10562
  ];
10125
- case 4:
10563
+ case 6:
10126
10564
  return [
10127
10565
  2
10128
10566
  ];
@@ -10235,7 +10673,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10235
10673
  case "gam_http_request_sent":
10236
10674
  diag.gamRequestSent++;
10237
10675
  break;
10676
+ case "edge_http_request_sent":
10677
+ diag.edgeRequestSent++;
10678
+ break;
10238
10679
  case "gam_response_received":
10680
+ case "edge_response_received":
10239
10681
  diag.vastHttpStatus = payload.httpStatus;
10240
10682
  break;
10241
10683
  case "vast_parsed":
@@ -10422,6 +10864,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10422
10864
  {
10423
10865
  key: "beginFunnelRequest",
10424
10866
  value: function beginFunnelRequest(adPlayer, requestToken, trigger) {
10867
+ var source = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : "gam";
10425
10868
  var _adPlayer_setFunnelContext;
10426
10869
  if (this.controllerCallInFlight) {
10427
10870
  this.logOrchestratorFunnel("duplicate_request_detected", {
@@ -10436,7 +10879,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10436
10879
  requestId: requestToken,
10437
10880
  controllerId: this.controllerGeneration,
10438
10881
  trigger: trigger,
10882
+ source: source,
10439
10883
  gamRequestSent: 0,
10884
+ edgeRequestSent: 0,
10440
10885
  wrapperDepth: 0,
10441
10886
  inlineResolved: false,
10442
10887
  mediaCandidateCount: 0,
@@ -10567,6 +11012,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10567
11012
  this.timing.activeAdRequestToken = requestToken;
10568
11013
  ap = this.host.getAdPlayer();
10569
11014
  budgetedUrl = this.withCurrentPodBudget(vastTagUrl);
11015
+ this.currentPodSource = "gam";
10570
11016
  this.setSlotState("REQUESTING", trigger);
10571
11017
  this.beginFunnelRequest(ap, requestToken, trigger);
10572
11018
  requestUrl = this.applyOpportunityFloor(budgetedUrl);
@@ -10714,6 +11160,10 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10714
11160
  requests: requests,
10715
11161
  requestRetries: requestRetries,
10716
11162
  gamResponses: count("gam_response_received"),
11163
+ edgeRequests: count("edge_request_sent"),
11164
+ edgeFills: count("edge_fill"),
11165
+ edgeEmptySentinels: count("edge_empty_sentinel"),
11166
+ edgeFallbacks: countPrefix("edge_fallback:"),
10717
11167
  requestsSuppressed: count("request_suppressed"),
10718
11168
  duplicates: count("duplicate_request_detected") + count("duplicate_break_start"),
10719
11169
  filled: count("vast_received"),
@@ -10810,6 +11260,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10810
11260
  this.breakSource = void 0;
10811
11261
  this.followUpBaseVastUrl = void 0;
10812
11262
  this.unusableResponsesInBreak = 0;
11263
+ this.resetEdgeStateForBreak();
10813
11264
  adPlayer.stop().catch(function() {});
10814
11265
  if (!alreadyEnded) this.logBreakFunnel(reason);
10815
11266
  this.opportunityFloor = void 0;
@@ -11123,7 +11574,13 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
11123
11574
  });
11124
11575
  return;
11125
11576
  }
11126
- this.timing.consecutiveFailures++;
11577
+ if (this.currentPodSource === "edge") {
11578
+ this.markEdgeExhausted("edge_playback_failed", {
11579
+ reason: reason
11580
+ });
11581
+ } else {
11582
+ this.timing.consecutiveFailures++;
11583
+ }
11127
11584
  var diag = this.currentRequestId != null ? this.requestDiagnostics.get(this.currentRequestId) : void 0;
11128
11585
  if (diag && !diag.failureReason) diag.failureReason = reason;
11129
11586
  if (this.debug) {
@@ -11153,7 +11610,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11153
11610
  if (this.config.adFloorMaxCents != null) floorBounds.maxCents = this.config.adFloorMaxCents;
11154
11611
  if (this.config.adFloorStepCents != null) floorBounds.stepCents = this.config.adFloorStepCents;
11155
11612
  this.floorLadder = new FloorLadderController(this.config.adFloorLadder, floorBounds);
11613
+ this.floorLadderEnabled = this.config.adFloorLadderEnabled === true;
11156
11614
  this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11615
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11157
11616
  this.adPlayer = this.createAdPlayer(false);
11158
11617
  this.palNonce = createPalNonceManager();
11159
11618
  this.cueManager = new Scte35CueManager(!!this.config.debugAdTiming, {
@@ -11253,11 +11712,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11253
11712
  generatePodVastUrl: function generatePodVastUrl(base, breakDurationMs) {
11254
11713
  return _this.generatePodVastUrl(base, breakDurationMs);
11255
11714
  },
11715
+ getEdgePodBaseUrl: function getEdgePodBaseUrl() {
11716
+ return _this.edgePodBaseUrl;
11717
+ },
11256
11718
  getFloorSnapshot: function getFloorSnapshot(position) {
11257
- return _this.floorLadder.snapshotFor({
11719
+ return _this.floorLadderEnabled ? _this.floorLadder.snapshotFor({
11258
11720
  position: position,
11259
11721
  isCtv: _this.isCtvEnvironment()
11260
- });
11722
+ }) : void 0;
11261
11723
  },
11262
11724
  onAdClick: function onAdClick() {
11263
11725
  return _this.palNonce.sendAdClick();
@@ -11308,11 +11770,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11308
11770
  return report;
11309
11771
  }
11310
11772
  },
11773
+ {
11774
+ key: "setAdFloorLadderEnabled",
11775
+ value: function setAdFloorLadderEnabled(enabled) {
11776
+ this.floorLadderEnabled = enabled;
11777
+ if (enabled) this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11778
+ }
11779
+ },
11780
+ {
11781
+ key: "isAdFloorLadderEnabled",
11782
+ value: function isAdFloorLadderEnabled() {
11783
+ return this.floorLadderEnabled;
11784
+ }
11785
+ },
11311
11786
  {
11312
11787
  key: "warnOnInvalidFloorLadder",
11313
11788
  value: function warnOnInvalidFloorLadder(report) {
11314
11789
  var config = this.floorLadder.get();
11315
- if (report.ok || !config.enabled) return;
11790
+ if (report.ok || !this.floorLadderEnabled || !config.enabled) return;
11316
11791
  console.warn("[AdBreak][FLOOR] configuration has issues; invalid floors will not be sent", {
11317
11792
  errors: report.errors,
11318
11793
  configurationVersion: config.configurationVersion,
@@ -12045,6 +12520,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12045
12520
  this.timing.destroy();
12046
12521
  this.adBreak.clearPendingAdBreak();
12047
12522
  this.placeholder.destroy();
12523
+ 0;
12048
12524
  if (this.timeUpdateHandler) {
12049
12525
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
12050
12526
  delete this.timeUpdateHandler;