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.
@@ -2482,7 +2482,7 @@ function createHlsAdPlayer(contentVideo, options) {
2482
2482
  img.onload = null;
2483
2483
  img.onerror = null;
2484
2484
  inFlightBeacons.delete(img);
2485
- console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingUrl));
2485
+ console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingHost));
2486
2486
  if (outcome !== "load") {
2487
2487
  logFunnel("tracking_request_settled", {
2488
2488
  reason: outcome,
@@ -2500,7 +2500,7 @@ function createHlsAdPlayer(contentVideo, options) {
2500
2500
  return release("timeout");
2501
2501
  }, BEACON_TIMEOUT_MS);
2502
2502
  img.src = trackingUrl;
2503
- console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
2503
+ console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingHost));
2504
2504
  logFunnel("tracking_request_created", {
2505
2505
  trackingHost: trackingHost,
2506
2506
  errorCode: options2 === null || options2 === void 0 ? void 0 : options2.errorCode
@@ -2954,7 +2954,7 @@ function createHlsAdPlayer(contentVideo, options) {
2954
2954
  });
2955
2955
  }
2956
2956
  if (parsed.type === "wrapper") {
2957
- console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(parsed.vastAdTagUri));
2957
+ console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(redactUrl(parsed.vastAdTagUri)));
2958
2958
  }
2959
2959
  return parsed;
2960
2960
  }
@@ -3146,8 +3146,8 @@ function createHlsAdPlayer(contentVideo, options) {
3146
3146
  switch(_state.label){
3147
3147
  case 0:
3148
3148
  attempt++;
3149
- if (kind === "gam") {
3150
- logFunnel("gam_http_request_sent", {
3149
+ if (kind !== "wrapper") {
3150
+ logFunnel("".concat(kind, "_http_request_sent"), {
3151
3151
  attempt: attempt,
3152
3152
  credentials: creds
3153
3153
  });
@@ -3167,8 +3167,8 @@ function createHlsAdPlayer(contentVideo, options) {
3167
3167
  ];
3168
3168
  case 1:
3169
3169
  response = _state.sent();
3170
- if (kind === "gam") {
3171
- logFunnel("gam_response_received", {
3170
+ if (kind !== "wrapper") {
3171
+ logFunnel("".concat(kind, "_response_received"), {
3172
3172
  attempt: attempt,
3173
3173
  httpStatus: response.status,
3174
3174
  ok: response.ok,
@@ -3338,8 +3338,7 @@ function createHlsAdPlayer(contentVideo, options) {
3338
3338
  elapsedMs: Date.now() - fetchStartedAt
3339
3339
  });
3340
3340
  }
3341
- console.log("[HlsAdPlayer] VAST XML received");
3342
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3341
+ console.log("[HlsAdPlayer] VAST XML received (length=".concat(vastXml.length, ")"));
3343
3342
  parsed = parseVastXml(vastXml);
3344
3343
  if (parsed.type === "empty") {
3345
3344
  ;
@@ -3380,16 +3379,35 @@ function createHlsAdPlayer(contentVideo, options) {
3380
3379
  });
3381
3380
  }).apply(this, arguments);
3382
3381
  }
3382
+ function isEdgeEmptySentinelAd(adElement) {
3383
+ var _textOf;
3384
+ if ((adElement.getAttribute("id") || "").trim().toLowerCase() === "empty") {
3385
+ return true;
3386
+ }
3387
+ var inLine = adElement.querySelector(":scope > InLine");
3388
+ if (!inLine) return false;
3389
+ if (/^no ad available$/i.test((_textOf = textOf(inLine.querySelector(":scope > AdTitle"))) !== null && _textOf !== void 0 ? _textOf : "")) {
3390
+ return true;
3391
+ }
3392
+ var durations = Array.from(inLine.querySelectorAll(":scope > Creatives > Creative > Linear > Duration")).map(function(el) {
3393
+ return parseVastTime(textOf(el));
3394
+ });
3395
+ return durations.length > 0 && durations.every(function(seconds) {
3396
+ return seconds === 0;
3397
+ });
3398
+ }
3383
3399
  function fetchAndParsePod(vastTagUrl) {
3400
+ var source = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "gam";
3384
3401
  return _async_to_generator(function() {
3385
- 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;
3402
+ 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;
3386
3403
  return _ts_generator(this, function(_state) {
3387
3404
  switch(_state.label){
3388
3405
  case 0:
3389
3406
  lastPodAdElementCount = 0;
3407
+ isEdge = source === "edge";
3390
3408
  return [
3391
3409
  4,
3392
- fetchVastXml(vastTagUrl)
3410
+ fetchVastXml(vastTagUrl, isEdge ? "omit" : "include", source)
3393
3411
  ];
3394
3412
  case 1:
3395
3413
  vastXml = _state.sent();
@@ -3412,7 +3430,6 @@ function createHlsAdPlayer(contentVideo, options) {
3412
3430
  }
3413
3431
  xmlDoc = parseXml(vastXml);
3414
3432
  if (!xmlDoc) {
3415
- console.error("[HlsAdPlayer] Pod VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3416
3433
  logFunnel("vast_parsed", {
3417
3434
  adElementCount: 0,
3418
3435
  reason: "xml_parse_error"
@@ -3484,12 +3501,24 @@ function createHlsAdPlayer(contentVideo, options) {
3484
3501
  });
3485
3502
  })();
3486
3503
  };
3487
- podElements = adElements.filter(function(el) {
3504
+ candidates = adElements;
3505
+ if (isEdge) {
3506
+ candidates = adElements.filter(function(el) {
3507
+ return !isEdgeEmptySentinelAd(el);
3508
+ });
3509
+ if (candidates.length < adElements.length) {
3510
+ logFunnel("edge_empty_sentinel", {
3511
+ sentinelCount: adElements.length - candidates.length,
3512
+ adElementCount: adElements.length
3513
+ });
3514
+ }
3515
+ }
3516
+ podElements = candidates.filter(function(el) {
3488
3517
  return getAdSequence(el) !== void 0;
3489
3518
  }).sort(function(a, b) {
3490
3519
  return getAdSequence(a) - getAdSequence(b);
3491
3520
  });
3492
- buffet = adElements.filter(function(el) {
3521
+ buffet = candidates.filter(function(el) {
3493
3522
  return getAdSequence(el) === void 0;
3494
3523
  });
3495
3524
  resolveAll = function resolveAll(elements) {
@@ -3646,7 +3675,9 @@ function createHlsAdPlayer(contentVideo, options) {
3646
3675
  lastPodAdElementCount = adElements.length;
3647
3676
  return [
3648
3677
  2,
3649
- ads
3678
+ isEdge ? ads.filter(function(ad) {
3679
+ return ad.duration > 0;
3680
+ }) : ads
3650
3681
  ];
3651
3682
  }
3652
3683
  });
@@ -4647,13 +4678,14 @@ function createHlsAdPlayer(contentVideo, options) {
4647
4678
  contentVideo[AD_CONTAINER_PROP] = adContainerEl;
4648
4679
  attachContainerHandlers();
4649
4680
  },
4650
- requestAds: function requestAds(vastTagUrl) {
4681
+ requestAds: function requestAds(vastTagUrl, requestOptions) {
4651
4682
  return _async_to_generator(function() {
4652
- var ads, preloaded, error;
4683
+ var _ref, source, ads, preloaded, error;
4653
4684
  return _ts_generator(this, function(_state) {
4654
4685
  switch(_state.label){
4655
4686
  case 0:
4656
- console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
4687
+ source = (_ref = requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.source) !== null && _ref !== void 0 ? _ref : "gam";
4688
+ console.log("[HlsAdPlayer] Requesting ads (".concat(source, "):"), redactUrl(vastTagUrl));
4657
4689
  if (adPlaying) {
4658
4690
  console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
4659
4691
  return [
@@ -4698,7 +4730,7 @@ function createHlsAdPlayer(contentVideo, options) {
4698
4730
  case 2:
4699
4731
  return [
4700
4732
  4,
4701
- fetchAndParsePod(vastTagUrl)
4733
+ fetchAndParsePod(vastTagUrl, source)
4702
4734
  ];
4703
4735
  case 3:
4704
4736
  ads = _state.sent();
@@ -5108,6 +5140,75 @@ function createHlsAdPlayer(contentVideo, options) {
5108
5140
  return podAds.reduce(function(total, ad) {
5109
5141
  return total + (ad.duration > 0 ? ad.duration * 1e3 : 0);
5110
5142
  }, 0);
5143
+ },
5144
+ limitPodToBudget: function limitPodToBudget(maxDurationMs, maxAds) {
5145
+ if (adPlaying) return podAds.length;
5146
+ var kept = [];
5147
+ var dropped = [];
5148
+ var totalMs = 0;
5149
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
5150
+ try {
5151
+ for(var _iterator = podAds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
5152
+ var ad = _step.value;
5153
+ var durationMs = Math.round(ad.duration * 1e3);
5154
+ if (kept.length < maxAds && totalMs + durationMs <= maxDurationMs) {
5155
+ kept.push(ad);
5156
+ totalMs += durationMs;
5157
+ } else {
5158
+ dropped.push(ad);
5159
+ }
5160
+ }
5161
+ } catch (err) {
5162
+ _didIteratorError = true;
5163
+ _iteratorError = err;
5164
+ } finally{
5165
+ try {
5166
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
5167
+ _iterator.return();
5168
+ }
5169
+ } finally{
5170
+ if (_didIteratorError) {
5171
+ throw _iteratorError;
5172
+ }
5173
+ }
5174
+ }
5175
+ if (dropped.length > 0) {
5176
+ logFunnel("pod_trimmed_to_budget", {
5177
+ keptCount: kept.length,
5178
+ droppedCount: dropped.length,
5179
+ keptDurationMs: totalMs,
5180
+ maxDurationMs: maxDurationMs,
5181
+ maxAds: maxAds
5182
+ });
5183
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
5184
+ try {
5185
+ for(var _iterator1 = dropped[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
5186
+ var ad1 = _step1.value;
5187
+ resolvedAds.add(ad1);
5188
+ fireTrackingPixels(ad1.trackingUrls.notUsed, {
5189
+ ad: ad1
5190
+ });
5191
+ }
5192
+ } catch (err) {
5193
+ _didIteratorError1 = true;
5194
+ _iteratorError1 = err;
5195
+ } finally{
5196
+ try {
5197
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
5198
+ _iterator1.return();
5199
+ }
5200
+ } finally{
5201
+ if (_didIteratorError1) {
5202
+ throw _iteratorError1;
5203
+ }
5204
+ }
5205
+ }
5206
+ }
5207
+ podAds = kept;
5208
+ podIndex = 0;
5209
+ currentAd = kept[0];
5210
+ currentAdMediaFile = void 0;
5211
+ return kept.length;
5111
5212
  }
5112
5213
  };
5113
5214
  }
@@ -9106,6 +9207,59 @@ var HlsEngine = /*#__PURE__*/ function() {
9106
9207
  ]);
9107
9208
  return HlsEngine;
9108
9209
  }();
9210
+ // src/player/EdgePodRequest.ts
9211
+ var EDGE_POD_MAX_ADS = 10;
9212
+ var EDGE_POD_EXCEEDS_BUDGET = "edge_pod_exceeds_budget";
9213
+ function resolveEdgePodBaseUrl(config) {
9214
+ var _config_edgePodUrl;
9215
+ if (config.enableEdgePodRequest !== true) return void 0;
9216
+ var raw = (_config_edgePodUrl = config.edgePodUrl) === null || _config_edgePodUrl === void 0 ? void 0 : _config_edgePodUrl.trim();
9217
+ if (!raw) {
9218
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but edgePodUrl is missing; using GAM only");
9219
+ return void 0;
9220
+ }
9221
+ try {
9222
+ var url = new URL(raw);
9223
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
9224
+ throw new Error("unsupported protocol ".concat(url.protocol));
9225
+ }
9226
+ return url.toString();
9227
+ } catch (unused) {
9228
+ console.warn("[AdBreak][EDGE] edgePodUrl is not a valid http(s) URL; using GAM only");
9229
+ return void 0;
9230
+ }
9231
+ }
9232
+ function buildEdgePodUrl(baseUrl, remainingMs, remainingAdLimit) {
9233
+ var url = new URL(baseUrl);
9234
+ var maxMs = Math.max(0, Math.floor(remainingMs));
9235
+ var maxAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, Math.floor(remainingAdLimit)));
9236
+ url.searchParams.set("pmxd", String(maxMs));
9237
+ url.searchParams.set("pmad", String(maxAds));
9238
+ url.searchParams.set("duration", String(Math.floor(maxMs / 1e3)));
9239
+ url.searchParams.set("count", String(maxAds));
9240
+ return url.toString();
9241
+ }
9242
+ function classifyEdgeVastResponse(result) {
9243
+ var _ref;
9244
+ var _result_error;
9245
+ if (result.error == null) {
9246
+ return result.podAdCount > 0 ? "fill" : "no_fill";
9247
+ }
9248
+ 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);
9249
+ if (/no ads available|fill rejected|no media file|no ad loaded/i.test(message) || message.includes(EDGE_POD_EXCEEDS_BUDGET)) {
9250
+ return "no_fill";
9251
+ }
9252
+ return "invalid";
9253
+ }
9254
+ function describeEdgeUrl(url) {
9255
+ try {
9256
+ var parsed = new URL(url);
9257
+ return "".concat(parsed.origin).concat(parsed.pathname);
9258
+ } catch (unused) {
9259
+ var _url_split_;
9260
+ return (_url_split_ = url.split("?")[0]) !== null && _url_split_ !== void 0 ? _url_split_ : "";
9261
+ }
9262
+ }
9109
9263
  // src/player/PlayerControls.ts
9110
9264
  function getAdAudioVolume(adPlayer) {
9111
9265
  return adPlayer.getOriginalMutedState() ? 0 : adPlayer.getOriginalVolume();
@@ -9264,6 +9418,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9264
9418
  this.controllerCallInFlight = false;
9265
9419
  this.playbackStartedForCurrentRequest = false;
9266
9420
  this.opportunityEpoch = -1;
9421
+ this.edgeExhaustedForBreak = false;
9422
+ this.edgeRequestsInBreak = 0;
9267
9423
  this.adPodQueue = [];
9268
9424
  this.defaultMinRemainingMsToRequestAd = 1e4;
9269
9425
  this.defaultMaxNoFillsPerBreak = 2;
@@ -9414,7 +9570,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9414
9570
  _this.timing.clearAdRequestWatchdog();
9415
9571
  _this.timing.activeAdRequestToken = null;
9416
9572
  _this.showAds = true;
9417
- _this.timing.resetGamNoFillCounter();
9573
+ if (_this.currentPodSource !== "edge") {
9574
+ _this.timing.resetGamNoFillCounter();
9575
+ }
9418
9576
  if (_this.inAdBreak && _this.timing.expectedAdBreakDurationMs != null && _this.timing.getAdBreakEndWallClockMs() == null) {
9419
9577
  _this.timing.scheduleAdStopAtBreakBoundary();
9420
9578
  if (_this.debug) {
@@ -9546,6 +9704,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9546
9704
  key: "beginVmapBreak",
9547
9705
  value: function beginVmapBreak(breakKey, position) {
9548
9706
  this.breakSource = "vmap";
9707
+ this.resetEdgeStateForBreak();
9549
9708
  this.followUpBaseVastUrl = void 0;
9550
9709
  this.unusableResponsesInBreak = 0;
9551
9710
  this.timing.totalAdRequestsInBreak = 0;
@@ -9557,7 +9716,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9557
9716
  key: "handleAdStart",
9558
9717
  value: function handleAdStart(marker) {
9559
9718
  return _async_to_generator(function() {
9560
- var baseVastUrl, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, podPlayed;
9719
+ var baseVastUrl, edgeConfigured, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, edgeUrl, outcome, podPlayed;
9561
9720
  return _ts_generator(this, function(_state) {
9562
9721
  switch(_state.label){
9563
9722
  case 0:
@@ -9571,7 +9730,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9571
9730
  ];
9572
9731
  }
9573
9732
  baseVastUrl = this.adConfig.apiVastTagUrl;
9574
- if (!baseVastUrl) {
9733
+ edgeConfigured = !!this.host.getEdgePodBaseUrl();
9734
+ if (!baseVastUrl && !edgeConfigured) {
9575
9735
  console.warn("[AdBreak] SCTE-35 break started but no direct VAST tag is configured");
9576
9736
  this.clearPendingAdBreak();
9577
9737
  return [
@@ -9587,6 +9747,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9587
9747
  this.breakSource = "scte35";
9588
9748
  this.followUpBaseVastUrl = baseVastUrl;
9589
9749
  this.unusableResponsesInBreak = 0;
9750
+ this.resetEdgeStateForBreak();
9590
9751
  this.setSlotState("NEEDS_NEXT_AD", "scte35_break_start");
9591
9752
  if (this.adConfig.podAssignedByPrefetch) {
9592
9753
  this.adConfig.podAssignedByPrefetch = false;
@@ -9608,7 +9769,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9608
9769
  this.noteOpportunity("scte35", this.cueManager.activeScte35BreakKey, "midroll");
9609
9770
  epoch = this.timing.adBreakEpoch;
9610
9771
  hasPrefetchedUrl = !!(this.pendingAdBreak && this.pendingAdBreak.vastUrls.length > 0);
9611
- if (!(adBreakDurationMs == null && !hasPrefetchedUrl)) return [
9772
+ if (!(adBreakDurationMs == null && (!hasPrefetchedUrl || edgeConfigured))) return [
9612
9773
  3,
9613
9774
  2
9614
9775
  ];
@@ -9627,11 +9788,46 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9627
9788
  mode = this.host.config.isLiveStream ? "LIVE" : "VOD";
9628
9789
  console.log("[AdBreak] ".concat(mode, " break start: target duration=").concat(adBreakDurationMs != null ? "".concat(adBreakDurationMs, "ms") : "unknown"));
9629
9790
  }
9791
+ if (!edgeConfigured) return [
9792
+ 3,
9793
+ 5
9794
+ ];
9795
+ edgeUrl = this.canRequestEdge() ? this.prepareEdgePodRequest() : void 0;
9796
+ if (!edgeUrl) return [
9797
+ 3,
9798
+ 4
9799
+ ];
9800
+ this.clearPendingAdBreak();
9630
9801
  return [
9631
9802
  4,
9632
- this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9803
+ this.runEdgePod(edgeUrl, "edge_break_start")
9633
9804
  ];
9634
9805
  case 3:
9806
+ outcome = _state.sent();
9807
+ this.continueAfterEdgeOutcome(outcome, epoch);
9808
+ return [
9809
+ 2
9810
+ ];
9811
+ case 4:
9812
+ if (!this.edgeExhaustedForBreak) {
9813
+ this.markEdgeExhausted("not_eligible_at_break_start", {
9814
+ isLive: this.isLiveBreak(),
9815
+ durationMs: this.timing.expectedAdBreakDurationMs
9816
+ });
9817
+ }
9818
+ _state.label = 5;
9819
+ case 5:
9820
+ if (!baseVastUrl) {
9821
+ this.requestNextAd("no_gam_source");
9822
+ return [
9823
+ 2
9824
+ ];
9825
+ }
9826
+ return [
9827
+ 4,
9828
+ this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9829
+ ];
9830
+ case 6:
9635
9831
  podPlayed = _state.sent();
9636
9832
  if (!podPlayed && this.inAdBreak && epoch === this.timing.adBreakEpoch) {
9637
9833
  this.setSlotState("NEEDS_NEXT_AD", "initial_request_failed");
@@ -9723,6 +9919,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9723
9919
  adPlayer = this.host.getAdPlayer();
9724
9920
  requestToken = ++this.timing.adRequestTokenCounter;
9725
9921
  this.timing.activeAdRequestToken = requestToken;
9922
+ this.currentPodSource = "gam";
9726
9923
  this.setSlotState("REQUESTING", "initial_pod");
9727
9924
  this.beginFunnelRequest(adPlayer, requestToken, "initial_pod");
9728
9925
  requestUrl = this.applyOpportunityFloor(podUrl);
@@ -9839,6 +10036,224 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9839
10036
  }).call(this);
9840
10037
  }
9841
10038
  },
10039
+ {
10040
+ // ───────────────────────────────────────────────────────────────
10041
+ // Edge-first pods (live SCTE-35 only, GAM fallback)
10042
+ // ───────────────────────────────────────────────────────────────
10043
+ key: "resetEdgeStateForBreak",
10044
+ value: function resetEdgeStateForBreak() {
10045
+ this.edgeExhaustedForBreak = false;
10046
+ this.edgeRequestsInBreak = 0;
10047
+ this.currentPodSource = void 0;
10048
+ }
10049
+ },
10050
+ {
10051
+ key: "isLiveBreak",
10052
+ value: function isLiveBreak() {
10053
+ return this.host.isLiveStream() || this.host.config.isLiveStream === true;
10054
+ }
10055
+ },
10056
+ {
10057
+ key: "canRequestEdge",
10058
+ value: function canRequestEdge() {
10059
+ return this.breakSource === "scte35" && !this.edgeExhaustedForBreak && !!this.host.getEdgePodBaseUrl() && this.isLiveBreak() && this.timing.expectedAdBreakDurationMs != null && this.edgeRequestsInBreak < this.timing.maxTotalAdRequestsPerBreak;
10060
+ }
10061
+ },
10062
+ {
10063
+ key: "markEdgeExhausted",
10064
+ value: function markEdgeExhausted(reason) {
10065
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
10066
+ this.edgeExhaustedForBreak = true;
10067
+ this.countFunnelStage("edge_fallback:".concat(reason));
10068
+ this.logOrchestratorFunnel("edge_exhausted", _object_spread({
10069
+ reason: reason,
10070
+ edgeRequests: this.edgeRequestsInBreak,
10071
+ remainingMs: this.timing.getRemainingAdMs()
10072
+ }, extra));
10073
+ }
10074
+ },
10075
+ {
10076
+ /**
10077
+ * Builds the next Edge request from the budget left right now, or marks Edge
10078
+ * exhausted for this break. Synchronous on purpose: callers must reach the
10079
+ * REQUESTING state in the same tick so a second completion event cannot race
10080
+ * in a parallel request.
10081
+ */ key: "prepareEdgePodRequest",
10082
+ value: function prepareEdgePodRequest() {
10083
+ var baseUrl = this.host.getEdgePodBaseUrl();
10084
+ if (!baseUrl) return void 0;
10085
+ var remainingMs = this.timing.getRemainingAdMs();
10086
+ var remainingAds = Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak);
10087
+ if (remainingAds < 1) {
10088
+ this.markEdgeExhausted("ad_capacity_reached", {
10089
+ adsInBreak: this.timing.totalAdsInBreak
10090
+ });
10091
+ return void 0;
10092
+ }
10093
+ if (remainingMs < this.getMinRemainingMsToRequestAd()) {
10094
+ this.markEdgeExhausted("insufficient_break_time", {
10095
+ remainingMs: remainingMs
10096
+ });
10097
+ return void 0;
10098
+ }
10099
+ try {
10100
+ return buildEdgePodUrl(baseUrl, remainingMs, remainingAds);
10101
+ } catch (unused) {
10102
+ this.markEdgeExhausted("invalid_edge_url");
10103
+ return void 0;
10104
+ }
10105
+ }
10106
+ },
10107
+ {
10108
+ key: "runEdgePod",
10109
+ value: function runEdgePod(requestUrl, trigger) {
10110
+ return _async_to_generator(function() {
10111
+ var _params_get, _params_get1, epoch, generation, adPlayer, requestToken, params, _ref, _adPlayer_getPodAdCount, _adPlayer_getPodTotalDurationMs, remainingMs, remainingAds, podCount, error, errorMessage, classification, diag;
10112
+ return _ts_generator(this, function(_state) {
10113
+ switch(_state.label){
10114
+ case 0:
10115
+ epoch = this.timing.adBreakEpoch;
10116
+ this.recreateAdController("new_request");
10117
+ generation = this.controllerGeneration;
10118
+ adPlayer = this.host.getAdPlayer();
10119
+ requestToken = ++this.timing.adRequestTokenCounter;
10120
+ this.timing.activeAdRequestToken = requestToken;
10121
+ this.currentPodSource = "edge";
10122
+ this.edgeRequestsInBreak++;
10123
+ this.setSlotState("REQUESTING", trigger);
10124
+ this.beginFunnelRequest(adPlayer, requestToken, trigger, "edge");
10125
+ this.currentVastUrl = requestUrl;
10126
+ params = new URL(requestUrl).searchParams;
10127
+ this.logOrchestratorFunnel("edge_request_sent", {
10128
+ trigger: trigger,
10129
+ attempt: this.edgeRequestsInBreak,
10130
+ endpoint: describeEdgeUrl(requestUrl),
10131
+ pmxd: (_params_get = params.get("pmxd")) !== null && _params_get !== void 0 ? _params_get : void 0,
10132
+ pmad: (_params_get1 = params.get("pmad")) !== null && _params_get1 !== void 0 ? _params_get1 : void 0
10133
+ });
10134
+ _state.label = 1;
10135
+ case 1:
10136
+ _state.trys.push([
10137
+ 1,
10138
+ 4,
10139
+ ,
10140
+ 5
10141
+ ]);
10142
+ this.timing.startAdRequestWatchdog(requestToken);
10143
+ return [
10144
+ 4,
10145
+ this.callController(function() {
10146
+ return adPlayer.requestAds(requestUrl, {
10147
+ source: "edge"
10148
+ });
10149
+ })
10150
+ ];
10151
+ case 2:
10152
+ _state.sent();
10153
+ this.timing.clearAdRequestWatchdog();
10154
+ if (this.isStale(epoch, generation)) {
10155
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10156
+ requestId: requestToken,
10157
+ source: "edge"
10158
+ });
10159
+ return [
10160
+ 2,
10161
+ "stale"
10162
+ ];
10163
+ }
10164
+ remainingMs = this.timing.getRemainingAdMs();
10165
+ remainingAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak));
10166
+ 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;
10167
+ if (classifyEdgeVastResponse({
10168
+ podAdCount: podCount
10169
+ }) !== "fill") {
10170
+ throw new Error("".concat(EDGE_POD_EXCEEDS_BUDGET, ": no Edge ad fits ").concat(remainingMs, "ms"));
10171
+ }
10172
+ this.setSlotState("READY", "edge_vast_ready");
10173
+ this.logOrchestratorFunnel("edge_fill", {
10174
+ podAdCount: podCount,
10175
+ podDurationMs: (_adPlayer_getPodTotalDurationMs = adPlayer.getPodTotalDurationMs) === null || _adPlayer_getPodTotalDurationMs === void 0 ? void 0 : _adPlayer_getPodTotalDurationMs.call(adPlayer),
10176
+ remainingMs: remainingMs
10177
+ });
10178
+ sendAdLoadedTracking(this.host.config.licenseKey, {
10179
+ source: "hls",
10180
+ vastUrl: requestUrl,
10181
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10182
+ }, this.analyticsContext).catch(function() {});
10183
+ this.timing.startAdFailsafeTimer(requestToken);
10184
+ return [
10185
+ 4,
10186
+ this.callController(function() {
10187
+ return adPlayer.play();
10188
+ })
10189
+ ];
10190
+ case 3:
10191
+ _state.sent();
10192
+ if (this.isStale(epoch, generation)) return [
10193
+ 2,
10194
+ "stale"
10195
+ ];
10196
+ if (this.timing.expectedAdBreakDurationMs != null && this.timing.getAdBreakEndWallClockMs() == null) {
10197
+ this.timing.scheduleAdStopAtBreakBoundary();
10198
+ }
10199
+ adPlayer.setAdVolume(getAdAudioVolume(adPlayer));
10200
+ return [
10201
+ 2,
10202
+ "fill"
10203
+ ];
10204
+ case 4:
10205
+ error = _state.sent();
10206
+ this.timing.clearAdRequestWatchdog();
10207
+ this.timing.clearAdFailsafeTimer();
10208
+ if (this.timing.activeAdRequestToken === requestToken) {
10209
+ this.timing.activeAdRequestToken = null;
10210
+ }
10211
+ errorMessage = error === null || error === void 0 ? void 0 : error.message;
10212
+ if (this.isStale(epoch, generation)) {
10213
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10214
+ requestId: requestToken,
10215
+ source: "edge",
10216
+ errorMessage: errorMessage
10217
+ });
10218
+ return [
10219
+ 2,
10220
+ "stale"
10221
+ ];
10222
+ }
10223
+ classification = classifyEdgeVastResponse({
10224
+ podAdCount: 0,
10225
+ error: error
10226
+ });
10227
+ diag = this.requestDiagnostics.get(requestToken);
10228
+ if (diag && !diag.failureReason) diag.failureReason = "edge_".concat(classification, ": ").concat(errorMessage);
10229
+ this.markEdgeExhausted("edge_".concat(classification), {
10230
+ errorMessage: errorMessage
10231
+ });
10232
+ return [
10233
+ 2,
10234
+ "fallback"
10235
+ ];
10236
+ case 5:
10237
+ return [
10238
+ 2
10239
+ ];
10240
+ }
10241
+ });
10242
+ }).call(this);
10243
+ }
10244
+ },
10245
+ {
10246
+ key: "continueAfterEdgeOutcome",
10247
+ value: function continueAfterEdgeOutcome(outcome, epoch) {
10248
+ if (outcome !== "fallback") return;
10249
+ if (!this.inAdBreak || epoch !== this.timing.adBreakEpoch) return;
10250
+ if (this.debug) {
10251
+ console.log("[AdBreak][EDGE] No usable Edge pod; checking with GAM for the remaining break");
10252
+ }
10253
+ this.setSlotState("NEEDS_NEXT_AD", "edge_fallback");
10254
+ this.requestNextAd("edge_fallback");
10255
+ }
10256
+ },
9842
10257
  {
9843
10258
  // ───────────────────────────────────────────────────────────────
9844
10259
  // Next-ad scheduling (single flight)
@@ -9894,7 +10309,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9894
10309
  value: function getStopRequestingReason(remainingMs) {
9895
10310
  if (this.breakSource === "vmap") {
9896
10311
  if (this.adPodQueue.length === 0) return "vmap_queue_empty";
9897
- } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl) {
10312
+ } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl && !this.canRequestEdge()) {
9898
10313
  return "no_follow_up_source";
9899
10314
  }
9900
10315
  if (this.timing.consecutiveFailures >= this.timing.maxConsecutiveFailures) {
@@ -9906,7 +10321,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9906
10321
  if (this.timing.totalAdRequestsInBreak >= this.timing.maxTotalAdRequestsPerBreak) {
9907
10322
  return "request_cap";
9908
10323
  }
9909
- if (this.timing.isGamInCooldown()) {
10324
+ if (!this.canRequestEdge() && this.timing.isGamInCooldown()) {
9910
10325
  return "gam_cooldown";
9911
10326
  }
9912
10327
  if (this.isBreakTimeBounded() && remainingMs < this.getMinRemainingMsToRequestAd()) {
@@ -10000,10 +10415,33 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10000
10415
  key: "requestFollowUpAd",
10001
10416
  value: function requestFollowUpAd(trigger) {
10002
10417
  return _async_to_generator(function() {
10003
- var url, epoch, unused;
10418
+ var edgeUrl, epoch2, outcome, url, epoch, unused;
10004
10419
  return _ts_generator(this, function(_state) {
10005
10420
  switch(_state.label){
10006
10421
  case 0:
10422
+ if (!this.canRequestEdge()) return [
10423
+ 3,
10424
+ 2
10425
+ ];
10426
+ edgeUrl = this.prepareEdgePodRequest();
10427
+ if (!edgeUrl) {
10428
+ this.requestNextAd("".concat(trigger, ":edge_unavailable"));
10429
+ return [
10430
+ 2
10431
+ ];
10432
+ }
10433
+ epoch2 = this.timing.adBreakEpoch;
10434
+ return [
10435
+ 4,
10436
+ this.runEdgePod(edgeUrl, "edge_".concat(trigger))
10437
+ ];
10438
+ case 1:
10439
+ outcome = _state.sent();
10440
+ this.continueAfterEdgeOutcome(outcome, epoch2);
10441
+ return [
10442
+ 2
10443
+ ];
10444
+ case 2:
10007
10445
  if (this.breakSource === "vmap") {
10008
10446
  url = this.adPodQueue.shift();
10009
10447
  } else if (this.followUpBaseVastUrl) {
@@ -10014,32 +10452,32 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10014
10452
  ];
10015
10453
  this.noteVastUrlGenerated(ensureVideoAdType(url), trigger);
10016
10454
  epoch = this.timing.adBreakEpoch;
10017
- _state.label = 1;
10018
- case 1:
10455
+ _state.label = 3;
10456
+ case 3:
10019
10457
  _state.trys.push([
10020
- 1,
10021
10458
  3,
10459
+ 5,
10022
10460
  ,
10023
- 4
10461
+ 6
10024
10462
  ]);
10025
10463
  return [
10026
10464
  4,
10027
10465
  this.playSingleAd(url, trigger)
10028
10466
  ];
10029
- case 2:
10467
+ case 4:
10030
10468
  _state.sent();
10031
10469
  return [
10032
10470
  3,
10033
- 4
10471
+ 6
10034
10472
  ];
10035
- case 3:
10473
+ case 5:
10036
10474
  unused = _state.sent();
10037
10475
  this.continueAfterRequestFailure(epoch);
10038
10476
  return [
10039
10477
  3,
10040
- 4
10478
+ 6
10041
10479
  ];
10042
- case 4:
10480
+ case 6:
10043
10481
  return [
10044
10482
  2
10045
10483
  ];
@@ -10152,7 +10590,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10152
10590
  case "gam_http_request_sent":
10153
10591
  diag.gamRequestSent++;
10154
10592
  break;
10593
+ case "edge_http_request_sent":
10594
+ diag.edgeRequestSent++;
10595
+ break;
10155
10596
  case "gam_response_received":
10597
+ case "edge_response_received":
10156
10598
  diag.vastHttpStatus = payload.httpStatus;
10157
10599
  break;
10158
10600
  case "vast_parsed":
@@ -10339,6 +10781,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10339
10781
  {
10340
10782
  key: "beginFunnelRequest",
10341
10783
  value: function beginFunnelRequest(adPlayer, requestToken, trigger) {
10784
+ var source = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : "gam";
10342
10785
  var _adPlayer_setFunnelContext;
10343
10786
  if (this.controllerCallInFlight) {
10344
10787
  this.logOrchestratorFunnel("duplicate_request_detected", {
@@ -10353,7 +10796,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10353
10796
  requestId: requestToken,
10354
10797
  controllerId: this.controllerGeneration,
10355
10798
  trigger: trigger,
10799
+ source: source,
10356
10800
  gamRequestSent: 0,
10801
+ edgeRequestSent: 0,
10357
10802
  wrapperDepth: 0,
10358
10803
  inlineResolved: false,
10359
10804
  mediaCandidateCount: 0,
@@ -10484,6 +10929,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10484
10929
  this.timing.activeAdRequestToken = requestToken;
10485
10930
  ap = this.host.getAdPlayer();
10486
10931
  budgetedUrl = this.withCurrentPodBudget(vastTagUrl);
10932
+ this.currentPodSource = "gam";
10487
10933
  this.setSlotState("REQUESTING", trigger);
10488
10934
  this.beginFunnelRequest(ap, requestToken, trigger);
10489
10935
  requestUrl = this.applyOpportunityFloor(budgetedUrl);
@@ -10631,6 +11077,10 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10631
11077
  requests: requests,
10632
11078
  requestRetries: requestRetries,
10633
11079
  gamResponses: count("gam_response_received"),
11080
+ edgeRequests: count("edge_request_sent"),
11081
+ edgeFills: count("edge_fill"),
11082
+ edgeEmptySentinels: count("edge_empty_sentinel"),
11083
+ edgeFallbacks: countPrefix("edge_fallback:"),
10634
11084
  requestsSuppressed: count("request_suppressed"),
10635
11085
  duplicates: count("duplicate_request_detected") + count("duplicate_break_start"),
10636
11086
  filled: count("vast_received"),
@@ -10727,6 +11177,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10727
11177
  this.breakSource = void 0;
10728
11178
  this.followUpBaseVastUrl = void 0;
10729
11179
  this.unusableResponsesInBreak = 0;
11180
+ this.resetEdgeStateForBreak();
10730
11181
  adPlayer.stop().catch(function() {});
10731
11182
  if (!alreadyEnded) this.logBreakFunnel(reason);
10732
11183
  this.opportunityFloor = void 0;
@@ -11040,7 +11491,13 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
11040
11491
  });
11041
11492
  return;
11042
11493
  }
11043
- this.timing.consecutiveFailures++;
11494
+ if (this.currentPodSource === "edge") {
11495
+ this.markEdgeExhausted("edge_playback_failed", {
11496
+ reason: reason
11497
+ });
11498
+ } else {
11499
+ this.timing.consecutiveFailures++;
11500
+ }
11044
11501
  var diag = this.currentRequestId != null ? this.requestDiagnostics.get(this.currentRequestId) : void 0;
11045
11502
  if (diag && !diag.failureReason) diag.failureReason = reason;
11046
11503
  if (this.debug) {
@@ -11070,7 +11527,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11070
11527
  if (this.config.adFloorMaxCents != null) floorBounds.maxCents = this.config.adFloorMaxCents;
11071
11528
  if (this.config.adFloorStepCents != null) floorBounds.stepCents = this.config.adFloorStepCents;
11072
11529
  this.floorLadder = new FloorLadderController(this.config.adFloorLadder, floorBounds);
11530
+ this.floorLadderEnabled = this.config.adFloorLadderEnabled === true;
11073
11531
  this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11532
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11074
11533
  this.adPlayer = this.createAdPlayer(false);
11075
11534
  this.palNonce = createPalNonceManager();
11076
11535
  this.cueManager = new Scte35CueManager(!!this.config.debugAdTiming, {
@@ -11170,11 +11629,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11170
11629
  generatePodVastUrl: function generatePodVastUrl(base, breakDurationMs) {
11171
11630
  return _this.generatePodVastUrl(base, breakDurationMs);
11172
11631
  },
11632
+ getEdgePodBaseUrl: function getEdgePodBaseUrl() {
11633
+ return _this.edgePodBaseUrl;
11634
+ },
11173
11635
  getFloorSnapshot: function getFloorSnapshot(position) {
11174
- return _this.floorLadder.snapshotFor({
11636
+ return _this.floorLadderEnabled ? _this.floorLadder.snapshotFor({
11175
11637
  position: position,
11176
11638
  isCtv: _this.isCtvEnvironment()
11177
- });
11639
+ }) : void 0;
11178
11640
  },
11179
11641
  onAdClick: function onAdClick() {
11180
11642
  return _this.palNonce.sendAdClick();
@@ -11225,11 +11687,24 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11225
11687
  return report;
11226
11688
  }
11227
11689
  },
11690
+ {
11691
+ key: "setAdFloorLadderEnabled",
11692
+ value: function setAdFloorLadderEnabled(enabled) {
11693
+ this.floorLadderEnabled = enabled;
11694
+ if (enabled) this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11695
+ }
11696
+ },
11697
+ {
11698
+ key: "isAdFloorLadderEnabled",
11699
+ value: function isAdFloorLadderEnabled() {
11700
+ return this.floorLadderEnabled;
11701
+ }
11702
+ },
11228
11703
  {
11229
11704
  key: "warnOnInvalidFloorLadder",
11230
11705
  value: function warnOnInvalidFloorLadder(report) {
11231
11706
  var config = this.floorLadder.get();
11232
- if (report.ok || !config.enabled) return;
11707
+ if (report.ok || !this.floorLadderEnabled || !config.enabled) return;
11233
11708
  console.warn("[AdBreak][FLOOR] configuration has issues; invalid floors will not be sent", {
11234
11709
  errors: report.errors,
11235
11710
  configurationVersion: config.configurationVersion,
@@ -11962,6 +12437,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11962
12437
  this.timing.destroy();
11963
12438
  this.adBreak.clearPendingAdBreak();
11964
12439
  this.placeholder.destroy();
12440
+ 0;
11965
12441
  if (this.timeUpdateHandler) {
11966
12442
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
11967
12443
  delete this.timeUpdateHandler;