stormcloud-video-player 0.8.69 → 0.8.71

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,71 @@ 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
+ var LICENSE_KEY_PLACEHOLDER = /\{licenseKey\}|%7BlicenseKey%7D/gi;
9214
+ function resolveEdgePodBaseUrl(config) {
9215
+ var _config_edgePodUrl, _config_licenseKey;
9216
+ if (config.enableEdgePodRequest !== true) return void 0;
9217
+ var raw = (_config_edgePodUrl = config.edgePodUrl) === null || _config_edgePodUrl === void 0 ? void 0 : _config_edgePodUrl.trim();
9218
+ if (!raw) {
9219
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but edgePodUrl is missing; using GAM only");
9220
+ return void 0;
9221
+ }
9222
+ var licenseKey = (_config_licenseKey = config.licenseKey) === null || _config_licenseKey === void 0 ? void 0 : _config_licenseKey.trim();
9223
+ if (!licenseKey) {
9224
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but licenseKey is missing; using GAM only");
9225
+ return void 0;
9226
+ }
9227
+ var encodedKey = encodeURIComponent(licenseKey);
9228
+ try {
9229
+ var hasPlaceholder = raw.search(LICENSE_KEY_PLACEHOLDER) !== -1;
9230
+ var url = new URL(hasPlaceholder ? raw.replace(LICENSE_KEY_PLACEHOLDER, encodedKey) : raw);
9231
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
9232
+ throw new Error("unsupported protocol ".concat(url.protocol));
9233
+ }
9234
+ if (!hasPlaceholder) {
9235
+ var path = url.pathname.replace(/\/+$/, "");
9236
+ url.pathname = /\/[^/]+\/pod$/.test(path) ? path.replace(/\/[^/]+\/pod$/, "/".concat(encodedKey, "/pod")) : "".concat(path, "/").concat(encodedKey, "/pod");
9237
+ }
9238
+ return url.toString();
9239
+ } catch (unused) {
9240
+ console.warn("[AdBreak][EDGE] edgePodUrl is not a valid http(s) URL; using GAM only");
9241
+ return void 0;
9242
+ }
9243
+ }
9244
+ function buildEdgePodUrl(baseUrl, remainingMs, remainingAdLimit) {
9245
+ var url = new URL(baseUrl);
9246
+ var maxMs = Math.max(0, Math.floor(remainingMs));
9247
+ var maxAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, Math.floor(remainingAdLimit)));
9248
+ url.searchParams.set("pmxd", String(maxMs));
9249
+ url.searchParams.set("pmad", String(maxAds));
9250
+ url.searchParams.set("duration", String(Math.floor(maxMs / 1e3)));
9251
+ url.searchParams.set("count", String(maxAds));
9252
+ return url.toString();
9253
+ }
9254
+ function classifyEdgeVastResponse(result) {
9255
+ var _ref;
9256
+ var _result_error;
9257
+ if (result.error == null) {
9258
+ return result.podAdCount > 0 ? "fill" : "no_fill";
9259
+ }
9260
+ 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);
9261
+ if (/no ads available|fill rejected|no media file|no ad loaded/i.test(message) || message.includes(EDGE_POD_EXCEEDS_BUDGET)) {
9262
+ return "no_fill";
9263
+ }
9264
+ return "invalid";
9265
+ }
9266
+ function describeEdgeUrl(url) {
9267
+ try {
9268
+ var parsed = new URL(url);
9269
+ return "".concat(parsed.origin).concat(parsed.pathname);
9270
+ } catch (unused) {
9271
+ var _url_split_;
9272
+ return (_url_split_ = url.split("?")[0]) !== null && _url_split_ !== void 0 ? _url_split_ : "";
9273
+ }
9274
+ }
9109
9275
  // src/player/PlayerControls.ts
9110
9276
  function getAdAudioVolume(adPlayer) {
9111
9277
  return adPlayer.getOriginalMutedState() ? 0 : adPlayer.getOriginalVolume();
@@ -9264,6 +9430,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9264
9430
  this.controllerCallInFlight = false;
9265
9431
  this.playbackStartedForCurrentRequest = false;
9266
9432
  this.opportunityEpoch = -1;
9433
+ this.edgeExhaustedForBreak = false;
9434
+ this.edgeRequestsInBreak = 0;
9267
9435
  this.adPodQueue = [];
9268
9436
  this.defaultMinRemainingMsToRequestAd = 1e4;
9269
9437
  this.defaultMaxNoFillsPerBreak = 2;
@@ -9414,7 +9582,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9414
9582
  _this.timing.clearAdRequestWatchdog();
9415
9583
  _this.timing.activeAdRequestToken = null;
9416
9584
  _this.showAds = true;
9417
- _this.timing.resetGamNoFillCounter();
9585
+ if (_this.currentPodSource !== "edge") {
9586
+ _this.timing.resetGamNoFillCounter();
9587
+ }
9418
9588
  if (_this.inAdBreak && _this.timing.expectedAdBreakDurationMs != null && _this.timing.getAdBreakEndWallClockMs() == null) {
9419
9589
  _this.timing.scheduleAdStopAtBreakBoundary();
9420
9590
  if (_this.debug) {
@@ -9546,6 +9716,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9546
9716
  key: "beginVmapBreak",
9547
9717
  value: function beginVmapBreak(breakKey, position) {
9548
9718
  this.breakSource = "vmap";
9719
+ this.resetEdgeStateForBreak();
9549
9720
  this.followUpBaseVastUrl = void 0;
9550
9721
  this.unusableResponsesInBreak = 0;
9551
9722
  this.timing.totalAdRequestsInBreak = 0;
@@ -9557,7 +9728,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9557
9728
  key: "handleAdStart",
9558
9729
  value: function handleAdStart(marker) {
9559
9730
  return _async_to_generator(function() {
9560
- var baseVastUrl, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, podPlayed;
9731
+ var baseVastUrl, edgeConfigured, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, edgeUrl, outcome, podPlayed;
9561
9732
  return _ts_generator(this, function(_state) {
9562
9733
  switch(_state.label){
9563
9734
  case 0:
@@ -9571,7 +9742,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9571
9742
  ];
9572
9743
  }
9573
9744
  baseVastUrl = this.adConfig.apiVastTagUrl;
9574
- if (!baseVastUrl) {
9745
+ edgeConfigured = !!this.host.getEdgePodBaseUrl();
9746
+ if (!baseVastUrl && !edgeConfigured) {
9575
9747
  console.warn("[AdBreak] SCTE-35 break started but no direct VAST tag is configured");
9576
9748
  this.clearPendingAdBreak();
9577
9749
  return [
@@ -9587,6 +9759,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9587
9759
  this.breakSource = "scte35";
9588
9760
  this.followUpBaseVastUrl = baseVastUrl;
9589
9761
  this.unusableResponsesInBreak = 0;
9762
+ this.resetEdgeStateForBreak();
9590
9763
  this.setSlotState("NEEDS_NEXT_AD", "scte35_break_start");
9591
9764
  if (this.adConfig.podAssignedByPrefetch) {
9592
9765
  this.adConfig.podAssignedByPrefetch = false;
@@ -9608,7 +9781,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9608
9781
  this.noteOpportunity("scte35", this.cueManager.activeScte35BreakKey, "midroll");
9609
9782
  epoch = this.timing.adBreakEpoch;
9610
9783
  hasPrefetchedUrl = !!(this.pendingAdBreak && this.pendingAdBreak.vastUrls.length > 0);
9611
- if (!(adBreakDurationMs == null && !hasPrefetchedUrl)) return [
9784
+ if (!(adBreakDurationMs == null && (!hasPrefetchedUrl || edgeConfigured))) return [
9612
9785
  3,
9613
9786
  2
9614
9787
  ];
@@ -9627,11 +9800,46 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9627
9800
  mode = this.host.config.isLiveStream ? "LIVE" : "VOD";
9628
9801
  console.log("[AdBreak] ".concat(mode, " break start: target duration=").concat(adBreakDurationMs != null ? "".concat(adBreakDurationMs, "ms") : "unknown"));
9629
9802
  }
9803
+ if (!edgeConfigured) return [
9804
+ 3,
9805
+ 5
9806
+ ];
9807
+ edgeUrl = this.canRequestEdge() ? this.prepareEdgePodRequest() : void 0;
9808
+ if (!edgeUrl) return [
9809
+ 3,
9810
+ 4
9811
+ ];
9812
+ this.clearPendingAdBreak();
9630
9813
  return [
9631
9814
  4,
9632
- this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9815
+ this.runEdgePod(edgeUrl, "edge_break_start")
9633
9816
  ];
9634
9817
  case 3:
9818
+ outcome = _state.sent();
9819
+ this.continueAfterEdgeOutcome(outcome, epoch);
9820
+ return [
9821
+ 2
9822
+ ];
9823
+ case 4:
9824
+ if (!this.edgeExhaustedForBreak) {
9825
+ this.markEdgeExhausted("not_eligible_at_break_start", {
9826
+ isLive: this.isLiveBreak(),
9827
+ durationMs: this.timing.expectedAdBreakDurationMs
9828
+ });
9829
+ }
9830
+ _state.label = 5;
9831
+ case 5:
9832
+ if (!baseVastUrl) {
9833
+ this.requestNextAd("no_gam_source");
9834
+ return [
9835
+ 2
9836
+ ];
9837
+ }
9838
+ return [
9839
+ 4,
9840
+ this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9841
+ ];
9842
+ case 6:
9635
9843
  podPlayed = _state.sent();
9636
9844
  if (!podPlayed && this.inAdBreak && epoch === this.timing.adBreakEpoch) {
9637
9845
  this.setSlotState("NEEDS_NEXT_AD", "initial_request_failed");
@@ -9723,6 +9931,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9723
9931
  adPlayer = this.host.getAdPlayer();
9724
9932
  requestToken = ++this.timing.adRequestTokenCounter;
9725
9933
  this.timing.activeAdRequestToken = requestToken;
9934
+ this.currentPodSource = "gam";
9726
9935
  this.setSlotState("REQUESTING", "initial_pod");
9727
9936
  this.beginFunnelRequest(adPlayer, requestToken, "initial_pod");
9728
9937
  requestUrl = this.applyOpportunityFloor(podUrl);
@@ -9839,6 +10048,224 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9839
10048
  }).call(this);
9840
10049
  }
9841
10050
  },
10051
+ {
10052
+ // ───────────────────────────────────────────────────────────────
10053
+ // Edge-first pods (live SCTE-35 only, GAM fallback)
10054
+ // ───────────────────────────────────────────────────────────────
10055
+ key: "resetEdgeStateForBreak",
10056
+ value: function resetEdgeStateForBreak() {
10057
+ this.edgeExhaustedForBreak = false;
10058
+ this.edgeRequestsInBreak = 0;
10059
+ this.currentPodSource = void 0;
10060
+ }
10061
+ },
10062
+ {
10063
+ key: "isLiveBreak",
10064
+ value: function isLiveBreak() {
10065
+ return this.host.isLiveStream() || this.host.config.isLiveStream === true;
10066
+ }
10067
+ },
10068
+ {
10069
+ key: "canRequestEdge",
10070
+ value: function canRequestEdge() {
10071
+ return this.breakSource === "scte35" && !this.edgeExhaustedForBreak && !!this.host.getEdgePodBaseUrl() && this.isLiveBreak() && this.timing.expectedAdBreakDurationMs != null && this.edgeRequestsInBreak < this.timing.maxTotalAdRequestsPerBreak;
10072
+ }
10073
+ },
10074
+ {
10075
+ key: "markEdgeExhausted",
10076
+ value: function markEdgeExhausted(reason) {
10077
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
10078
+ this.edgeExhaustedForBreak = true;
10079
+ this.countFunnelStage("edge_fallback:".concat(reason));
10080
+ this.logOrchestratorFunnel("edge_exhausted", _object_spread({
10081
+ reason: reason,
10082
+ edgeRequests: this.edgeRequestsInBreak,
10083
+ remainingMs: this.timing.getRemainingAdMs()
10084
+ }, extra));
10085
+ }
10086
+ },
10087
+ {
10088
+ /**
10089
+ * Builds the next Edge request from the budget left right now, or marks Edge
10090
+ * exhausted for this break. Synchronous on purpose: callers must reach the
10091
+ * REQUESTING state in the same tick so a second completion event cannot race
10092
+ * in a parallel request.
10093
+ */ key: "prepareEdgePodRequest",
10094
+ value: function prepareEdgePodRequest() {
10095
+ var baseUrl = this.host.getEdgePodBaseUrl();
10096
+ if (!baseUrl) return void 0;
10097
+ var remainingMs = this.timing.getRemainingAdMs();
10098
+ var remainingAds = Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak);
10099
+ if (remainingAds < 1) {
10100
+ this.markEdgeExhausted("ad_capacity_reached", {
10101
+ adsInBreak: this.timing.totalAdsInBreak
10102
+ });
10103
+ return void 0;
10104
+ }
10105
+ if (remainingMs < this.getMinRemainingMsToRequestAd()) {
10106
+ this.markEdgeExhausted("insufficient_break_time", {
10107
+ remainingMs: remainingMs
10108
+ });
10109
+ return void 0;
10110
+ }
10111
+ try {
10112
+ return buildEdgePodUrl(baseUrl, remainingMs, remainingAds);
10113
+ } catch (unused) {
10114
+ this.markEdgeExhausted("invalid_edge_url");
10115
+ return void 0;
10116
+ }
10117
+ }
10118
+ },
10119
+ {
10120
+ key: "runEdgePod",
10121
+ value: function runEdgePod(requestUrl, trigger) {
10122
+ return _async_to_generator(function() {
10123
+ var _params_get, _params_get1, epoch, generation, adPlayer, requestToken, params, _ref, _adPlayer_getPodAdCount, _adPlayer_getPodTotalDurationMs, remainingMs, remainingAds, podCount, error, errorMessage, classification, diag;
10124
+ return _ts_generator(this, function(_state) {
10125
+ switch(_state.label){
10126
+ case 0:
10127
+ epoch = this.timing.adBreakEpoch;
10128
+ this.recreateAdController("new_request");
10129
+ generation = this.controllerGeneration;
10130
+ adPlayer = this.host.getAdPlayer();
10131
+ requestToken = ++this.timing.adRequestTokenCounter;
10132
+ this.timing.activeAdRequestToken = requestToken;
10133
+ this.currentPodSource = "edge";
10134
+ this.edgeRequestsInBreak++;
10135
+ this.setSlotState("REQUESTING", trigger);
10136
+ this.beginFunnelRequest(adPlayer, requestToken, trigger, "edge");
10137
+ this.currentVastUrl = requestUrl;
10138
+ params = new URL(requestUrl).searchParams;
10139
+ this.logOrchestratorFunnel("edge_request_sent", {
10140
+ trigger: trigger,
10141
+ attempt: this.edgeRequestsInBreak,
10142
+ endpoint: describeEdgeUrl(requestUrl),
10143
+ pmxd: (_params_get = params.get("pmxd")) !== null && _params_get !== void 0 ? _params_get : void 0,
10144
+ pmad: (_params_get1 = params.get("pmad")) !== null && _params_get1 !== void 0 ? _params_get1 : void 0
10145
+ });
10146
+ _state.label = 1;
10147
+ case 1:
10148
+ _state.trys.push([
10149
+ 1,
10150
+ 4,
10151
+ ,
10152
+ 5
10153
+ ]);
10154
+ this.timing.startAdRequestWatchdog(requestToken);
10155
+ return [
10156
+ 4,
10157
+ this.callController(function() {
10158
+ return adPlayer.requestAds(requestUrl, {
10159
+ source: "edge"
10160
+ });
10161
+ })
10162
+ ];
10163
+ case 2:
10164
+ _state.sent();
10165
+ this.timing.clearAdRequestWatchdog();
10166
+ if (this.isStale(epoch, generation)) {
10167
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10168
+ requestId: requestToken,
10169
+ source: "edge"
10170
+ });
10171
+ return [
10172
+ 2,
10173
+ "stale"
10174
+ ];
10175
+ }
10176
+ remainingMs = this.timing.getRemainingAdMs();
10177
+ remainingAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak));
10178
+ 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;
10179
+ if (classifyEdgeVastResponse({
10180
+ podAdCount: podCount
10181
+ }) !== "fill") {
10182
+ throw new Error("".concat(EDGE_POD_EXCEEDS_BUDGET, ": no Edge ad fits ").concat(remainingMs, "ms"));
10183
+ }
10184
+ this.setSlotState("READY", "edge_vast_ready");
10185
+ this.logOrchestratorFunnel("edge_fill", {
10186
+ podAdCount: podCount,
10187
+ podDurationMs: (_adPlayer_getPodTotalDurationMs = adPlayer.getPodTotalDurationMs) === null || _adPlayer_getPodTotalDurationMs === void 0 ? void 0 : _adPlayer_getPodTotalDurationMs.call(adPlayer),
10188
+ remainingMs: remainingMs
10189
+ });
10190
+ sendAdLoadedTracking(this.host.config.licenseKey, {
10191
+ source: "hls",
10192
+ vastUrl: requestUrl,
10193
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10194
+ }, this.analyticsContext).catch(function() {});
10195
+ this.timing.startAdFailsafeTimer(requestToken);
10196
+ return [
10197
+ 4,
10198
+ this.callController(function() {
10199
+ return adPlayer.play();
10200
+ })
10201
+ ];
10202
+ case 3:
10203
+ _state.sent();
10204
+ if (this.isStale(epoch, generation)) return [
10205
+ 2,
10206
+ "stale"
10207
+ ];
10208
+ if (this.timing.expectedAdBreakDurationMs != null && this.timing.getAdBreakEndWallClockMs() == null) {
10209
+ this.timing.scheduleAdStopAtBreakBoundary();
10210
+ }
10211
+ adPlayer.setAdVolume(getAdAudioVolume(adPlayer));
10212
+ return [
10213
+ 2,
10214
+ "fill"
10215
+ ];
10216
+ case 4:
10217
+ error = _state.sent();
10218
+ this.timing.clearAdRequestWatchdog();
10219
+ this.timing.clearAdFailsafeTimer();
10220
+ if (this.timing.activeAdRequestToken === requestToken) {
10221
+ this.timing.activeAdRequestToken = null;
10222
+ }
10223
+ errorMessage = error === null || error === void 0 ? void 0 : error.message;
10224
+ if (this.isStale(epoch, generation)) {
10225
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10226
+ requestId: requestToken,
10227
+ source: "edge",
10228
+ errorMessage: errorMessage
10229
+ });
10230
+ return [
10231
+ 2,
10232
+ "stale"
10233
+ ];
10234
+ }
10235
+ classification = classifyEdgeVastResponse({
10236
+ podAdCount: 0,
10237
+ error: error
10238
+ });
10239
+ diag = this.requestDiagnostics.get(requestToken);
10240
+ if (diag && !diag.failureReason) diag.failureReason = "edge_".concat(classification, ": ").concat(errorMessage);
10241
+ this.markEdgeExhausted("edge_".concat(classification), {
10242
+ errorMessage: errorMessage
10243
+ });
10244
+ return [
10245
+ 2,
10246
+ "fallback"
10247
+ ];
10248
+ case 5:
10249
+ return [
10250
+ 2
10251
+ ];
10252
+ }
10253
+ });
10254
+ }).call(this);
10255
+ }
10256
+ },
10257
+ {
10258
+ key: "continueAfterEdgeOutcome",
10259
+ value: function continueAfterEdgeOutcome(outcome, epoch) {
10260
+ if (outcome !== "fallback") return;
10261
+ if (!this.inAdBreak || epoch !== this.timing.adBreakEpoch) return;
10262
+ if (this.debug) {
10263
+ console.log("[AdBreak][EDGE] No usable Edge pod; checking with GAM for the remaining break");
10264
+ }
10265
+ this.setSlotState("NEEDS_NEXT_AD", "edge_fallback");
10266
+ this.requestNextAd("edge_fallback");
10267
+ }
10268
+ },
9842
10269
  {
9843
10270
  // ───────────────────────────────────────────────────────────────
9844
10271
  // Next-ad scheduling (single flight)
@@ -9894,7 +10321,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9894
10321
  value: function getStopRequestingReason(remainingMs) {
9895
10322
  if (this.breakSource === "vmap") {
9896
10323
  if (this.adPodQueue.length === 0) return "vmap_queue_empty";
9897
- } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl) {
10324
+ } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl && !this.canRequestEdge()) {
9898
10325
  return "no_follow_up_source";
9899
10326
  }
9900
10327
  if (this.timing.consecutiveFailures >= this.timing.maxConsecutiveFailures) {
@@ -9906,7 +10333,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9906
10333
  if (this.timing.totalAdRequestsInBreak >= this.timing.maxTotalAdRequestsPerBreak) {
9907
10334
  return "request_cap";
9908
10335
  }
9909
- if (this.timing.isGamInCooldown()) {
10336
+ if (!this.canRequestEdge() && this.timing.isGamInCooldown()) {
9910
10337
  return "gam_cooldown";
9911
10338
  }
9912
10339
  if (this.isBreakTimeBounded() && remainingMs < this.getMinRemainingMsToRequestAd()) {
@@ -10000,10 +10427,33 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10000
10427
  key: "requestFollowUpAd",
10001
10428
  value: function requestFollowUpAd(trigger) {
10002
10429
  return _async_to_generator(function() {
10003
- var url, epoch, unused;
10430
+ var edgeUrl, epoch2, outcome, url, epoch, unused;
10004
10431
  return _ts_generator(this, function(_state) {
10005
10432
  switch(_state.label){
10006
10433
  case 0:
10434
+ if (!this.canRequestEdge()) return [
10435
+ 3,
10436
+ 2
10437
+ ];
10438
+ edgeUrl = this.prepareEdgePodRequest();
10439
+ if (!edgeUrl) {
10440
+ this.requestNextAd("".concat(trigger, ":edge_unavailable"));
10441
+ return [
10442
+ 2
10443
+ ];
10444
+ }
10445
+ epoch2 = this.timing.adBreakEpoch;
10446
+ return [
10447
+ 4,
10448
+ this.runEdgePod(edgeUrl, "edge_".concat(trigger))
10449
+ ];
10450
+ case 1:
10451
+ outcome = _state.sent();
10452
+ this.continueAfterEdgeOutcome(outcome, epoch2);
10453
+ return [
10454
+ 2
10455
+ ];
10456
+ case 2:
10007
10457
  if (this.breakSource === "vmap") {
10008
10458
  url = this.adPodQueue.shift();
10009
10459
  } else if (this.followUpBaseVastUrl) {
@@ -10014,32 +10464,32 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10014
10464
  ];
10015
10465
  this.noteVastUrlGenerated(ensureVideoAdType(url), trigger);
10016
10466
  epoch = this.timing.adBreakEpoch;
10017
- _state.label = 1;
10018
- case 1:
10467
+ _state.label = 3;
10468
+ case 3:
10019
10469
  _state.trys.push([
10020
- 1,
10021
10470
  3,
10471
+ 5,
10022
10472
  ,
10023
- 4
10473
+ 6
10024
10474
  ]);
10025
10475
  return [
10026
10476
  4,
10027
10477
  this.playSingleAd(url, trigger)
10028
10478
  ];
10029
- case 2:
10479
+ case 4:
10030
10480
  _state.sent();
10031
10481
  return [
10032
10482
  3,
10033
- 4
10483
+ 6
10034
10484
  ];
10035
- case 3:
10485
+ case 5:
10036
10486
  unused = _state.sent();
10037
10487
  this.continueAfterRequestFailure(epoch);
10038
10488
  return [
10039
10489
  3,
10040
- 4
10490
+ 6
10041
10491
  ];
10042
- case 4:
10492
+ case 6:
10043
10493
  return [
10044
10494
  2
10045
10495
  ];
@@ -10152,7 +10602,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10152
10602
  case "gam_http_request_sent":
10153
10603
  diag.gamRequestSent++;
10154
10604
  break;
10605
+ case "edge_http_request_sent":
10606
+ diag.edgeRequestSent++;
10607
+ break;
10155
10608
  case "gam_response_received":
10609
+ case "edge_response_received":
10156
10610
  diag.vastHttpStatus = payload.httpStatus;
10157
10611
  break;
10158
10612
  case "vast_parsed":
@@ -10339,6 +10793,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10339
10793
  {
10340
10794
  key: "beginFunnelRequest",
10341
10795
  value: function beginFunnelRequest(adPlayer, requestToken, trigger) {
10796
+ var source = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : "gam";
10342
10797
  var _adPlayer_setFunnelContext;
10343
10798
  if (this.controllerCallInFlight) {
10344
10799
  this.logOrchestratorFunnel("duplicate_request_detected", {
@@ -10353,7 +10808,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10353
10808
  requestId: requestToken,
10354
10809
  controllerId: this.controllerGeneration,
10355
10810
  trigger: trigger,
10811
+ source: source,
10356
10812
  gamRequestSent: 0,
10813
+ edgeRequestSent: 0,
10357
10814
  wrapperDepth: 0,
10358
10815
  inlineResolved: false,
10359
10816
  mediaCandidateCount: 0,
@@ -10484,6 +10941,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10484
10941
  this.timing.activeAdRequestToken = requestToken;
10485
10942
  ap = this.host.getAdPlayer();
10486
10943
  budgetedUrl = this.withCurrentPodBudget(vastTagUrl);
10944
+ this.currentPodSource = "gam";
10487
10945
  this.setSlotState("REQUESTING", trigger);
10488
10946
  this.beginFunnelRequest(ap, requestToken, trigger);
10489
10947
  requestUrl = this.applyOpportunityFloor(budgetedUrl);
@@ -10631,6 +11089,10 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10631
11089
  requests: requests,
10632
11090
  requestRetries: requestRetries,
10633
11091
  gamResponses: count("gam_response_received"),
11092
+ edgeRequests: count("edge_request_sent"),
11093
+ edgeFills: count("edge_fill"),
11094
+ edgeEmptySentinels: count("edge_empty_sentinel"),
11095
+ edgeFallbacks: countPrefix("edge_fallback:"),
10634
11096
  requestsSuppressed: count("request_suppressed"),
10635
11097
  duplicates: count("duplicate_request_detected") + count("duplicate_break_start"),
10636
11098
  filled: count("vast_received"),
@@ -10727,6 +11189,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10727
11189
  this.breakSource = void 0;
10728
11190
  this.followUpBaseVastUrl = void 0;
10729
11191
  this.unusableResponsesInBreak = 0;
11192
+ this.resetEdgeStateForBreak();
10730
11193
  adPlayer.stop().catch(function() {});
10731
11194
  if (!alreadyEnded) this.logBreakFunnel(reason);
10732
11195
  this.opportunityFloor = void 0;
@@ -11040,7 +11503,13 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
11040
11503
  });
11041
11504
  return;
11042
11505
  }
11043
- this.timing.consecutiveFailures++;
11506
+ if (this.currentPodSource === "edge") {
11507
+ this.markEdgeExhausted("edge_playback_failed", {
11508
+ reason: reason
11509
+ });
11510
+ } else {
11511
+ this.timing.consecutiveFailures++;
11512
+ }
11044
11513
  var diag = this.currentRequestId != null ? this.requestDiagnostics.get(this.currentRequestId) : void 0;
11045
11514
  if (diag && !diag.failureReason) diag.failureReason = reason;
11046
11515
  if (this.debug) {
@@ -11070,7 +11539,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11070
11539
  if (this.config.adFloorMaxCents != null) floorBounds.maxCents = this.config.adFloorMaxCents;
11071
11540
  if (this.config.adFloorStepCents != null) floorBounds.stepCents = this.config.adFloorStepCents;
11072
11541
  this.floorLadder = new FloorLadderController(this.config.adFloorLadder, floorBounds);
11542
+ this.floorLadderEnabled = this.config.adFloorLadderEnabled === true;
11073
11543
  this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11544
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11074
11545
  this.adPlayer = this.createAdPlayer(false);
11075
11546
  this.palNonce = createPalNonceManager();
11076
11547
  this.cueManager = new Scte35CueManager(!!this.config.debugAdTiming, {
@@ -11170,11 +11641,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11170
11641
  generatePodVastUrl: function generatePodVastUrl(base, breakDurationMs) {
11171
11642
  return _this.generatePodVastUrl(base, breakDurationMs);
11172
11643
  },
11644
+ getEdgePodBaseUrl: function getEdgePodBaseUrl() {
11645
+ return _this.edgePodBaseUrl;
11646
+ },
11173
11647
  getFloorSnapshot: function getFloorSnapshot(position) {
11174
- return _this.floorLadder.snapshotFor({
11648
+ return _this.floorLadderEnabled ? _this.floorLadder.snapshotFor({
11175
11649
  position: position,
11176
11650
  isCtv: _this.isCtvEnvironment()
11177
- });
11651
+ }) : void 0;
11178
11652
  },
11179
11653
  onAdClick: function onAdClick() {
11180
11654
  return _this.palNonce.sendAdClick();
@@ -11225,11 +11699,43 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11225
11699
  return report;
11226
11700
  }
11227
11701
  },
11702
+ {
11703
+ key: "setAdFloorLadderEnabled",
11704
+ value: function setAdFloorLadderEnabled(enabled) {
11705
+ this.floorLadderEnabled = enabled;
11706
+ if (enabled) this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11707
+ }
11708
+ },
11709
+ {
11710
+ key: "isAdFloorLadderEnabled",
11711
+ value: function isAdFloorLadderEnabled() {
11712
+ return this.floorLadderEnabled;
11713
+ }
11714
+ },
11715
+ {
11716
+ key: "setEdgePodRequest",
11717
+ value: function setEdgePodRequest(update) {
11718
+ if (update.enableEdgePodRequest === this.config.enableEdgePodRequest && update.edgePodUrl === this.config.edgePodUrl) {
11719
+ return;
11720
+ }
11721
+ if (update.enableEdgePodRequest === void 0) delete this.config.enableEdgePodRequest;
11722
+ else this.config.enableEdgePodRequest = update.enableEdgePodRequest;
11723
+ if (update.edgePodUrl === void 0) delete this.config.edgePodUrl;
11724
+ else this.config.edgePodUrl = update.edgePodUrl;
11725
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11726
+ }
11727
+ },
11728
+ {
11729
+ key: "getEdgePodUrl",
11730
+ value: function getEdgePodUrl() {
11731
+ return this.edgePodBaseUrl;
11732
+ }
11733
+ },
11228
11734
  {
11229
11735
  key: "warnOnInvalidFloorLadder",
11230
11736
  value: function warnOnInvalidFloorLadder(report) {
11231
11737
  var config = this.floorLadder.get();
11232
- if (report.ok || !config.enabled) return;
11738
+ if (report.ok || !this.floorLadderEnabled || !config.enabled) return;
11233
11739
  console.warn("[AdBreak][FLOOR] configuration has issues; invalid floors will not be sent", {
11234
11740
  errors: report.errors,
11235
11741
  configurationVersion: config.configurationVersion,
@@ -11962,6 +12468,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11962
12468
  this.timing.destroy();
11963
12469
  this.adBreak.clearPendingAdBreak();
11964
12470
  this.placeholder.destroy();
12471
+ 0;
11965
12472
  if (this.timeUpdateHandler) {
11966
12473
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
11967
12474
  delete this.timeUpdateHandler;