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.
@@ -2532,7 +2532,7 @@ function createHlsAdPlayer(contentVideo, options) {
2532
2532
  img.onload = null;
2533
2533
  img.onerror = null;
2534
2534
  inFlightBeacons.delete(img);
2535
- console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingUrl));
2535
+ console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingHost));
2536
2536
  if (outcome !== "load") {
2537
2537
  logFunnel("tracking_request_settled", {
2538
2538
  reason: outcome,
@@ -2550,7 +2550,7 @@ function createHlsAdPlayer(contentVideo, options) {
2550
2550
  return release("timeout");
2551
2551
  }, BEACON_TIMEOUT_MS);
2552
2552
  img.src = trackingUrl;
2553
- console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
2553
+ console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingHost));
2554
2554
  logFunnel("tracking_request_created", {
2555
2555
  trackingHost: trackingHost,
2556
2556
  errorCode: options2 === null || options2 === void 0 ? void 0 : options2.errorCode
@@ -3004,7 +3004,7 @@ function createHlsAdPlayer(contentVideo, options) {
3004
3004
  });
3005
3005
  }
3006
3006
  if (parsed.type === "wrapper") {
3007
- console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(parsed.vastAdTagUri));
3007
+ console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(redactUrl(parsed.vastAdTagUri)));
3008
3008
  }
3009
3009
  return parsed;
3010
3010
  }
@@ -3196,8 +3196,8 @@ function createHlsAdPlayer(contentVideo, options) {
3196
3196
  switch(_state.label){
3197
3197
  case 0:
3198
3198
  attempt++;
3199
- if (kind === "gam") {
3200
- logFunnel("gam_http_request_sent", {
3199
+ if (kind !== "wrapper") {
3200
+ logFunnel("".concat(kind, "_http_request_sent"), {
3201
3201
  attempt: attempt,
3202
3202
  credentials: creds
3203
3203
  });
@@ -3217,8 +3217,8 @@ function createHlsAdPlayer(contentVideo, options) {
3217
3217
  ];
3218
3218
  case 1:
3219
3219
  response = _state.sent();
3220
- if (kind === "gam") {
3221
- logFunnel("gam_response_received", {
3220
+ if (kind !== "wrapper") {
3221
+ logFunnel("".concat(kind, "_response_received"), {
3222
3222
  attempt: attempt,
3223
3223
  httpStatus: response.status,
3224
3224
  ok: response.ok,
@@ -3388,8 +3388,7 @@ function createHlsAdPlayer(contentVideo, options) {
3388
3388
  elapsedMs: Date.now() - fetchStartedAt
3389
3389
  });
3390
3390
  }
3391
- console.log("[HlsAdPlayer] VAST XML received");
3392
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3391
+ console.log("[HlsAdPlayer] VAST XML received (length=".concat(vastXml.length, ")"));
3393
3392
  parsed = parseVastXml(vastXml);
3394
3393
  if (parsed.type === "empty") {
3395
3394
  ;
@@ -3430,16 +3429,35 @@ function createHlsAdPlayer(contentVideo, options) {
3430
3429
  });
3431
3430
  }).apply(this, arguments);
3432
3431
  }
3432
+ function isEdgeEmptySentinelAd(adElement) {
3433
+ var _textOf;
3434
+ if ((adElement.getAttribute("id") || "").trim().toLowerCase() === "empty") {
3435
+ return true;
3436
+ }
3437
+ var inLine = adElement.querySelector(":scope > InLine");
3438
+ if (!inLine) return false;
3439
+ if (/^no ad available$/i.test((_textOf = textOf(inLine.querySelector(":scope > AdTitle"))) !== null && _textOf !== void 0 ? _textOf : "")) {
3440
+ return true;
3441
+ }
3442
+ var durations = Array.from(inLine.querySelectorAll(":scope > Creatives > Creative > Linear > Duration")).map(function(el) {
3443
+ return parseVastTime(textOf(el));
3444
+ });
3445
+ return durations.length > 0 && durations.every(function(seconds) {
3446
+ return seconds === 0;
3447
+ });
3448
+ }
3433
3449
  function fetchAndParsePod(vastTagUrl) {
3450
+ var source = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "gam";
3434
3451
  return _async_to_generator(function() {
3435
- var _root_getAttribute, _root_getAttribute1, vastXml, trimmedXml, xmlDoc, root, adElements, _root_getAttribute2, _empty_errorUrls, empty, _empty_errorCode, resolveAd, podElements, buffet, resolveAll, ads, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, ad, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ad1, err;
3452
+ var _root_getAttribute, _root_getAttribute1, isEdge, vastXml, trimmedXml, xmlDoc, root, adElements, _root_getAttribute2, _empty_errorUrls, empty, _empty_errorCode, resolveAd, candidates, podElements, buffet, resolveAll, ads, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, ad, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ad1, err;
3436
3453
  return _ts_generator(this, function(_state) {
3437
3454
  switch(_state.label){
3438
3455
  case 0:
3439
3456
  lastPodAdElementCount = 0;
3457
+ isEdge = source === "edge";
3440
3458
  return [
3441
3459
  4,
3442
- fetchVastXml(vastTagUrl)
3460
+ fetchVastXml(vastTagUrl, isEdge ? "omit" : "include", source)
3443
3461
  ];
3444
3462
  case 1:
3445
3463
  vastXml = _state.sent();
@@ -3462,7 +3480,6 @@ function createHlsAdPlayer(contentVideo, options) {
3462
3480
  }
3463
3481
  xmlDoc = parseXml(vastXml);
3464
3482
  if (!xmlDoc) {
3465
- console.error("[HlsAdPlayer] Pod VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3466
3483
  logFunnel("vast_parsed", {
3467
3484
  adElementCount: 0,
3468
3485
  reason: "xml_parse_error"
@@ -3534,12 +3551,24 @@ function createHlsAdPlayer(contentVideo, options) {
3534
3551
  });
3535
3552
  })();
3536
3553
  };
3537
- podElements = adElements.filter(function(el) {
3554
+ candidates = adElements;
3555
+ if (isEdge) {
3556
+ candidates = adElements.filter(function(el) {
3557
+ return !isEdgeEmptySentinelAd(el);
3558
+ });
3559
+ if (candidates.length < adElements.length) {
3560
+ logFunnel("edge_empty_sentinel", {
3561
+ sentinelCount: adElements.length - candidates.length,
3562
+ adElementCount: adElements.length
3563
+ });
3564
+ }
3565
+ }
3566
+ podElements = candidates.filter(function(el) {
3538
3567
  return getAdSequence(el) !== void 0;
3539
3568
  }).sort(function(a, b) {
3540
3569
  return getAdSequence(a) - getAdSequence(b);
3541
3570
  });
3542
- buffet = adElements.filter(function(el) {
3571
+ buffet = candidates.filter(function(el) {
3543
3572
  return getAdSequence(el) === void 0;
3544
3573
  });
3545
3574
  resolveAll = function resolveAll(elements) {
@@ -3696,7 +3725,9 @@ function createHlsAdPlayer(contentVideo, options) {
3696
3725
  lastPodAdElementCount = adElements.length;
3697
3726
  return [
3698
3727
  2,
3699
- ads
3728
+ isEdge ? ads.filter(function(ad) {
3729
+ return ad.duration > 0;
3730
+ }) : ads
3700
3731
  ];
3701
3732
  }
3702
3733
  });
@@ -4697,13 +4728,14 @@ function createHlsAdPlayer(contentVideo, options) {
4697
4728
  contentVideo[AD_CONTAINER_PROP] = adContainerEl;
4698
4729
  attachContainerHandlers();
4699
4730
  },
4700
- requestAds: function requestAds(vastTagUrl) {
4731
+ requestAds: function requestAds(vastTagUrl, requestOptions) {
4701
4732
  return _async_to_generator(function() {
4702
- var ads, preloaded, error;
4733
+ var _ref, source, ads, preloaded, error;
4703
4734
  return _ts_generator(this, function(_state) {
4704
4735
  switch(_state.label){
4705
4736
  case 0:
4706
- console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
4737
+ source = (_ref = requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.source) !== null && _ref !== void 0 ? _ref : "gam";
4738
+ console.log("[HlsAdPlayer] Requesting ads (".concat(source, "):"), redactUrl(vastTagUrl));
4707
4739
  if (adPlaying) {
4708
4740
  console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
4709
4741
  return [
@@ -4748,7 +4780,7 @@ function createHlsAdPlayer(contentVideo, options) {
4748
4780
  case 2:
4749
4781
  return [
4750
4782
  4,
4751
- fetchAndParsePod(vastTagUrl)
4783
+ fetchAndParsePod(vastTagUrl, source)
4752
4784
  ];
4753
4785
  case 3:
4754
4786
  ads = _state.sent();
@@ -5158,6 +5190,75 @@ function createHlsAdPlayer(contentVideo, options) {
5158
5190
  return podAds.reduce(function(total, ad) {
5159
5191
  return total + (ad.duration > 0 ? ad.duration * 1e3 : 0);
5160
5192
  }, 0);
5193
+ },
5194
+ limitPodToBudget: function limitPodToBudget(maxDurationMs, maxAds) {
5195
+ if (adPlaying) return podAds.length;
5196
+ var kept = [];
5197
+ var dropped = [];
5198
+ var totalMs = 0;
5199
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
5200
+ try {
5201
+ for(var _iterator = podAds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
5202
+ var ad = _step.value;
5203
+ var durationMs = Math.round(ad.duration * 1e3);
5204
+ if (kept.length < maxAds && totalMs + durationMs <= maxDurationMs) {
5205
+ kept.push(ad);
5206
+ totalMs += durationMs;
5207
+ } else {
5208
+ dropped.push(ad);
5209
+ }
5210
+ }
5211
+ } catch (err) {
5212
+ _didIteratorError = true;
5213
+ _iteratorError = err;
5214
+ } finally{
5215
+ try {
5216
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
5217
+ _iterator.return();
5218
+ }
5219
+ } finally{
5220
+ if (_didIteratorError) {
5221
+ throw _iteratorError;
5222
+ }
5223
+ }
5224
+ }
5225
+ if (dropped.length > 0) {
5226
+ logFunnel("pod_trimmed_to_budget", {
5227
+ keptCount: kept.length,
5228
+ droppedCount: dropped.length,
5229
+ keptDurationMs: totalMs,
5230
+ maxDurationMs: maxDurationMs,
5231
+ maxAds: maxAds
5232
+ });
5233
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
5234
+ try {
5235
+ for(var _iterator1 = dropped[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
5236
+ var ad1 = _step1.value;
5237
+ resolvedAds.add(ad1);
5238
+ fireTrackingPixels(ad1.trackingUrls.notUsed, {
5239
+ ad: ad1
5240
+ });
5241
+ }
5242
+ } catch (err) {
5243
+ _didIteratorError1 = true;
5244
+ _iteratorError1 = err;
5245
+ } finally{
5246
+ try {
5247
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
5248
+ _iterator1.return();
5249
+ }
5250
+ } finally{
5251
+ if (_didIteratorError1) {
5252
+ throw _iteratorError1;
5253
+ }
5254
+ }
5255
+ }
5256
+ }
5257
+ podAds = kept;
5258
+ podIndex = 0;
5259
+ currentAd = kept[0];
5260
+ currentAdMediaFile = void 0;
5261
+ return kept.length;
5161
5262
  }
5162
5263
  };
5163
5264
  }
@@ -9156,6 +9257,71 @@ var HlsEngine = /*#__PURE__*/ function() {
9156
9257
  ]);
9157
9258
  return HlsEngine;
9158
9259
  }();
9260
+ // src/player/EdgePodRequest.ts
9261
+ var EDGE_POD_MAX_ADS = 10;
9262
+ var EDGE_POD_EXCEEDS_BUDGET = "edge_pod_exceeds_budget";
9263
+ var LICENSE_KEY_PLACEHOLDER = /\{licenseKey\}|%7BlicenseKey%7D/gi;
9264
+ function resolveEdgePodBaseUrl(config) {
9265
+ var _config_edgePodUrl, _config_licenseKey;
9266
+ if (config.enableEdgePodRequest !== true) return void 0;
9267
+ var raw = (_config_edgePodUrl = config.edgePodUrl) === null || _config_edgePodUrl === void 0 ? void 0 : _config_edgePodUrl.trim();
9268
+ if (!raw) {
9269
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but edgePodUrl is missing; using GAM only");
9270
+ return void 0;
9271
+ }
9272
+ var licenseKey = (_config_licenseKey = config.licenseKey) === null || _config_licenseKey === void 0 ? void 0 : _config_licenseKey.trim();
9273
+ if (!licenseKey) {
9274
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but licenseKey is missing; using GAM only");
9275
+ return void 0;
9276
+ }
9277
+ var encodedKey = encodeURIComponent(licenseKey);
9278
+ try {
9279
+ var hasPlaceholder = raw.search(LICENSE_KEY_PLACEHOLDER) !== -1;
9280
+ var url = new URL(hasPlaceholder ? raw.replace(LICENSE_KEY_PLACEHOLDER, encodedKey) : raw);
9281
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
9282
+ throw new Error("unsupported protocol ".concat(url.protocol));
9283
+ }
9284
+ if (!hasPlaceholder) {
9285
+ var path = url.pathname.replace(/\/+$/, "");
9286
+ url.pathname = /\/[^/]+\/pod$/.test(path) ? path.replace(/\/[^/]+\/pod$/, "/".concat(encodedKey, "/pod")) : "".concat(path, "/").concat(encodedKey, "/pod");
9287
+ }
9288
+ return url.toString();
9289
+ } catch (unused) {
9290
+ console.warn("[AdBreak][EDGE] edgePodUrl is not a valid http(s) URL; using GAM only");
9291
+ return void 0;
9292
+ }
9293
+ }
9294
+ function buildEdgePodUrl(baseUrl, remainingMs, remainingAdLimit) {
9295
+ var url = new URL(baseUrl);
9296
+ var maxMs = Math.max(0, Math.floor(remainingMs));
9297
+ var maxAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, Math.floor(remainingAdLimit)));
9298
+ url.searchParams.set("pmxd", String(maxMs));
9299
+ url.searchParams.set("pmad", String(maxAds));
9300
+ url.searchParams.set("duration", String(Math.floor(maxMs / 1e3)));
9301
+ url.searchParams.set("count", String(maxAds));
9302
+ return url.toString();
9303
+ }
9304
+ function classifyEdgeVastResponse(result) {
9305
+ var _ref;
9306
+ var _result_error;
9307
+ if (result.error == null) {
9308
+ return result.podAdCount > 0 ? "fill" : "no_fill";
9309
+ }
9310
+ 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);
9311
+ if (/no ads available|fill rejected|no media file|no ad loaded/i.test(message) || message.includes(EDGE_POD_EXCEEDS_BUDGET)) {
9312
+ return "no_fill";
9313
+ }
9314
+ return "invalid";
9315
+ }
9316
+ function describeEdgeUrl(url) {
9317
+ try {
9318
+ var parsed = new URL(url);
9319
+ return "".concat(parsed.origin).concat(parsed.pathname);
9320
+ } catch (unused) {
9321
+ var _url_split_;
9322
+ return (_url_split_ = url.split("?")[0]) !== null && _url_split_ !== void 0 ? _url_split_ : "";
9323
+ }
9324
+ }
9159
9325
  // src/player/PlayerControls.ts
9160
9326
  function getAdAudioVolume(adPlayer) {
9161
9327
  return adPlayer.getOriginalMutedState() ? 0 : adPlayer.getOriginalVolume();
@@ -9314,6 +9480,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9314
9480
  this.controllerCallInFlight = false;
9315
9481
  this.playbackStartedForCurrentRequest = false;
9316
9482
  this.opportunityEpoch = -1;
9483
+ this.edgeExhaustedForBreak = false;
9484
+ this.edgeRequestsInBreak = 0;
9317
9485
  this.adPodQueue = [];
9318
9486
  this.defaultMinRemainingMsToRequestAd = 1e4;
9319
9487
  this.defaultMaxNoFillsPerBreak = 2;
@@ -9464,7 +9632,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9464
9632
  _this.timing.clearAdRequestWatchdog();
9465
9633
  _this.timing.activeAdRequestToken = null;
9466
9634
  _this.showAds = true;
9467
- _this.timing.resetGamNoFillCounter();
9635
+ if (_this.currentPodSource !== "edge") {
9636
+ _this.timing.resetGamNoFillCounter();
9637
+ }
9468
9638
  if (_this.inAdBreak && _this.timing.expectedAdBreakDurationMs != null && _this.timing.getAdBreakEndWallClockMs() == null) {
9469
9639
  _this.timing.scheduleAdStopAtBreakBoundary();
9470
9640
  if (_this.debug) {
@@ -9596,6 +9766,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9596
9766
  key: "beginVmapBreak",
9597
9767
  value: function beginVmapBreak(breakKey, position) {
9598
9768
  this.breakSource = "vmap";
9769
+ this.resetEdgeStateForBreak();
9599
9770
  this.followUpBaseVastUrl = void 0;
9600
9771
  this.unusableResponsesInBreak = 0;
9601
9772
  this.timing.totalAdRequestsInBreak = 0;
@@ -9607,7 +9778,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9607
9778
  key: "handleAdStart",
9608
9779
  value: function handleAdStart(marker) {
9609
9780
  return _async_to_generator(function() {
9610
- var baseVastUrl, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, podPlayed;
9781
+ var baseVastUrl, edgeConfigured, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, edgeUrl, outcome, podPlayed;
9611
9782
  return _ts_generator(this, function(_state) {
9612
9783
  switch(_state.label){
9613
9784
  case 0:
@@ -9621,7 +9792,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9621
9792
  ];
9622
9793
  }
9623
9794
  baseVastUrl = this.adConfig.apiVastTagUrl;
9624
- if (!baseVastUrl) {
9795
+ edgeConfigured = !!this.host.getEdgePodBaseUrl();
9796
+ if (!baseVastUrl && !edgeConfigured) {
9625
9797
  console.warn("[AdBreak] SCTE-35 break started but no direct VAST tag is configured");
9626
9798
  this.clearPendingAdBreak();
9627
9799
  return [
@@ -9637,6 +9809,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9637
9809
  this.breakSource = "scte35";
9638
9810
  this.followUpBaseVastUrl = baseVastUrl;
9639
9811
  this.unusableResponsesInBreak = 0;
9812
+ this.resetEdgeStateForBreak();
9640
9813
  this.setSlotState("NEEDS_NEXT_AD", "scte35_break_start");
9641
9814
  if (this.adConfig.podAssignedByPrefetch) {
9642
9815
  this.adConfig.podAssignedByPrefetch = false;
@@ -9658,7 +9831,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9658
9831
  this.noteOpportunity("scte35", this.cueManager.activeScte35BreakKey, "midroll");
9659
9832
  epoch = this.timing.adBreakEpoch;
9660
9833
  hasPrefetchedUrl = !!(this.pendingAdBreak && this.pendingAdBreak.vastUrls.length > 0);
9661
- if (!(adBreakDurationMs == null && !hasPrefetchedUrl)) return [
9834
+ if (!(adBreakDurationMs == null && (!hasPrefetchedUrl || edgeConfigured))) return [
9662
9835
  3,
9663
9836
  2
9664
9837
  ];
@@ -9677,11 +9850,46 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9677
9850
  mode = this.host.config.isLiveStream ? "LIVE" : "VOD";
9678
9851
  console.log("[AdBreak] ".concat(mode, " break start: target duration=").concat(adBreakDurationMs != null ? "".concat(adBreakDurationMs, "ms") : "unknown"));
9679
9852
  }
9853
+ if (!edgeConfigured) return [
9854
+ 3,
9855
+ 5
9856
+ ];
9857
+ edgeUrl = this.canRequestEdge() ? this.prepareEdgePodRequest() : void 0;
9858
+ if (!edgeUrl) return [
9859
+ 3,
9860
+ 4
9861
+ ];
9862
+ this.clearPendingAdBreak();
9680
9863
  return [
9681
9864
  4,
9682
- this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9865
+ this.runEdgePod(edgeUrl, "edge_break_start")
9683
9866
  ];
9684
9867
  case 3:
9868
+ outcome = _state.sent();
9869
+ this.continueAfterEdgeOutcome(outcome, epoch);
9870
+ return [
9871
+ 2
9872
+ ];
9873
+ case 4:
9874
+ if (!this.edgeExhaustedForBreak) {
9875
+ this.markEdgeExhausted("not_eligible_at_break_start", {
9876
+ isLive: this.isLiveBreak(),
9877
+ durationMs: this.timing.expectedAdBreakDurationMs
9878
+ });
9879
+ }
9880
+ _state.label = 5;
9881
+ case 5:
9882
+ if (!baseVastUrl) {
9883
+ this.requestNextAd("no_gam_source");
9884
+ return [
9885
+ 2
9886
+ ];
9887
+ }
9888
+ return [
9889
+ 4,
9890
+ this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9891
+ ];
9892
+ case 6:
9685
9893
  podPlayed = _state.sent();
9686
9894
  if (!podPlayed && this.inAdBreak && epoch === this.timing.adBreakEpoch) {
9687
9895
  this.setSlotState("NEEDS_NEXT_AD", "initial_request_failed");
@@ -9773,6 +9981,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9773
9981
  adPlayer = this.host.getAdPlayer();
9774
9982
  requestToken = ++this.timing.adRequestTokenCounter;
9775
9983
  this.timing.activeAdRequestToken = requestToken;
9984
+ this.currentPodSource = "gam";
9776
9985
  this.setSlotState("REQUESTING", "initial_pod");
9777
9986
  this.beginFunnelRequest(adPlayer, requestToken, "initial_pod");
9778
9987
  requestUrl = this.applyOpportunityFloor(podUrl);
@@ -9889,6 +10098,224 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9889
10098
  }).call(this);
9890
10099
  }
9891
10100
  },
10101
+ {
10102
+ // ───────────────────────────────────────────────────────────────
10103
+ // Edge-first pods (live SCTE-35 only, GAM fallback)
10104
+ // ───────────────────────────────────────────────────────────────
10105
+ key: "resetEdgeStateForBreak",
10106
+ value: function resetEdgeStateForBreak() {
10107
+ this.edgeExhaustedForBreak = false;
10108
+ this.edgeRequestsInBreak = 0;
10109
+ this.currentPodSource = void 0;
10110
+ }
10111
+ },
10112
+ {
10113
+ key: "isLiveBreak",
10114
+ value: function isLiveBreak() {
10115
+ return this.host.isLiveStream() || this.host.config.isLiveStream === true;
10116
+ }
10117
+ },
10118
+ {
10119
+ key: "canRequestEdge",
10120
+ value: function canRequestEdge() {
10121
+ return this.breakSource === "scte35" && !this.edgeExhaustedForBreak && !!this.host.getEdgePodBaseUrl() && this.isLiveBreak() && this.timing.expectedAdBreakDurationMs != null && this.edgeRequestsInBreak < this.timing.maxTotalAdRequestsPerBreak;
10122
+ }
10123
+ },
10124
+ {
10125
+ key: "markEdgeExhausted",
10126
+ value: function markEdgeExhausted(reason) {
10127
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
10128
+ this.edgeExhaustedForBreak = true;
10129
+ this.countFunnelStage("edge_fallback:".concat(reason));
10130
+ this.logOrchestratorFunnel("edge_exhausted", _object_spread({
10131
+ reason: reason,
10132
+ edgeRequests: this.edgeRequestsInBreak,
10133
+ remainingMs: this.timing.getRemainingAdMs()
10134
+ }, extra));
10135
+ }
10136
+ },
10137
+ {
10138
+ /**
10139
+ * Builds the next Edge request from the budget left right now, or marks Edge
10140
+ * exhausted for this break. Synchronous on purpose: callers must reach the
10141
+ * REQUESTING state in the same tick so a second completion event cannot race
10142
+ * in a parallel request.
10143
+ */ key: "prepareEdgePodRequest",
10144
+ value: function prepareEdgePodRequest() {
10145
+ var baseUrl = this.host.getEdgePodBaseUrl();
10146
+ if (!baseUrl) return void 0;
10147
+ var remainingMs = this.timing.getRemainingAdMs();
10148
+ var remainingAds = Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak);
10149
+ if (remainingAds < 1) {
10150
+ this.markEdgeExhausted("ad_capacity_reached", {
10151
+ adsInBreak: this.timing.totalAdsInBreak
10152
+ });
10153
+ return void 0;
10154
+ }
10155
+ if (remainingMs < this.getMinRemainingMsToRequestAd()) {
10156
+ this.markEdgeExhausted("insufficient_break_time", {
10157
+ remainingMs: remainingMs
10158
+ });
10159
+ return void 0;
10160
+ }
10161
+ try {
10162
+ return buildEdgePodUrl(baseUrl, remainingMs, remainingAds);
10163
+ } catch (unused) {
10164
+ this.markEdgeExhausted("invalid_edge_url");
10165
+ return void 0;
10166
+ }
10167
+ }
10168
+ },
10169
+ {
10170
+ key: "runEdgePod",
10171
+ value: function runEdgePod(requestUrl, trigger) {
10172
+ return _async_to_generator(function() {
10173
+ var _params_get, _params_get1, epoch, generation, adPlayer, requestToken, params, _ref, _adPlayer_getPodAdCount, _adPlayer_getPodTotalDurationMs, remainingMs, remainingAds, podCount, error, errorMessage, classification, diag;
10174
+ return _ts_generator(this, function(_state) {
10175
+ switch(_state.label){
10176
+ case 0:
10177
+ epoch = this.timing.adBreakEpoch;
10178
+ this.recreateAdController("new_request");
10179
+ generation = this.controllerGeneration;
10180
+ adPlayer = this.host.getAdPlayer();
10181
+ requestToken = ++this.timing.adRequestTokenCounter;
10182
+ this.timing.activeAdRequestToken = requestToken;
10183
+ this.currentPodSource = "edge";
10184
+ this.edgeRequestsInBreak++;
10185
+ this.setSlotState("REQUESTING", trigger);
10186
+ this.beginFunnelRequest(adPlayer, requestToken, trigger, "edge");
10187
+ this.currentVastUrl = requestUrl;
10188
+ params = new URL(requestUrl).searchParams;
10189
+ this.logOrchestratorFunnel("edge_request_sent", {
10190
+ trigger: trigger,
10191
+ attempt: this.edgeRequestsInBreak,
10192
+ endpoint: describeEdgeUrl(requestUrl),
10193
+ pmxd: (_params_get = params.get("pmxd")) !== null && _params_get !== void 0 ? _params_get : void 0,
10194
+ pmad: (_params_get1 = params.get("pmad")) !== null && _params_get1 !== void 0 ? _params_get1 : void 0
10195
+ });
10196
+ _state.label = 1;
10197
+ case 1:
10198
+ _state.trys.push([
10199
+ 1,
10200
+ 4,
10201
+ ,
10202
+ 5
10203
+ ]);
10204
+ this.timing.startAdRequestWatchdog(requestToken);
10205
+ return [
10206
+ 4,
10207
+ this.callController(function() {
10208
+ return adPlayer.requestAds(requestUrl, {
10209
+ source: "edge"
10210
+ });
10211
+ })
10212
+ ];
10213
+ case 2:
10214
+ _state.sent();
10215
+ this.timing.clearAdRequestWatchdog();
10216
+ if (this.isStale(epoch, generation)) {
10217
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10218
+ requestId: requestToken,
10219
+ source: "edge"
10220
+ });
10221
+ return [
10222
+ 2,
10223
+ "stale"
10224
+ ];
10225
+ }
10226
+ remainingMs = this.timing.getRemainingAdMs();
10227
+ remainingAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak));
10228
+ 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;
10229
+ if (classifyEdgeVastResponse({
10230
+ podAdCount: podCount
10231
+ }) !== "fill") {
10232
+ throw new Error("".concat(EDGE_POD_EXCEEDS_BUDGET, ": no Edge ad fits ").concat(remainingMs, "ms"));
10233
+ }
10234
+ this.setSlotState("READY", "edge_vast_ready");
10235
+ this.logOrchestratorFunnel("edge_fill", {
10236
+ podAdCount: podCount,
10237
+ podDurationMs: (_adPlayer_getPodTotalDurationMs = adPlayer.getPodTotalDurationMs) === null || _adPlayer_getPodTotalDurationMs === void 0 ? void 0 : _adPlayer_getPodTotalDurationMs.call(adPlayer),
10238
+ remainingMs: remainingMs
10239
+ });
10240
+ sendAdLoadedTracking(this.host.config.licenseKey, {
10241
+ source: "hls",
10242
+ vastUrl: requestUrl,
10243
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10244
+ }, this.analyticsContext).catch(function() {});
10245
+ this.timing.startAdFailsafeTimer(requestToken);
10246
+ return [
10247
+ 4,
10248
+ this.callController(function() {
10249
+ return adPlayer.play();
10250
+ })
10251
+ ];
10252
+ case 3:
10253
+ _state.sent();
10254
+ if (this.isStale(epoch, generation)) return [
10255
+ 2,
10256
+ "stale"
10257
+ ];
10258
+ if (this.timing.expectedAdBreakDurationMs != null && this.timing.getAdBreakEndWallClockMs() == null) {
10259
+ this.timing.scheduleAdStopAtBreakBoundary();
10260
+ }
10261
+ adPlayer.setAdVolume(getAdAudioVolume(adPlayer));
10262
+ return [
10263
+ 2,
10264
+ "fill"
10265
+ ];
10266
+ case 4:
10267
+ error = _state.sent();
10268
+ this.timing.clearAdRequestWatchdog();
10269
+ this.timing.clearAdFailsafeTimer();
10270
+ if (this.timing.activeAdRequestToken === requestToken) {
10271
+ this.timing.activeAdRequestToken = null;
10272
+ }
10273
+ errorMessage = error === null || error === void 0 ? void 0 : error.message;
10274
+ if (this.isStale(epoch, generation)) {
10275
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10276
+ requestId: requestToken,
10277
+ source: "edge",
10278
+ errorMessage: errorMessage
10279
+ });
10280
+ return [
10281
+ 2,
10282
+ "stale"
10283
+ ];
10284
+ }
10285
+ classification = classifyEdgeVastResponse({
10286
+ podAdCount: 0,
10287
+ error: error
10288
+ });
10289
+ diag = this.requestDiagnostics.get(requestToken);
10290
+ if (diag && !diag.failureReason) diag.failureReason = "edge_".concat(classification, ": ").concat(errorMessage);
10291
+ this.markEdgeExhausted("edge_".concat(classification), {
10292
+ errorMessage: errorMessage
10293
+ });
10294
+ return [
10295
+ 2,
10296
+ "fallback"
10297
+ ];
10298
+ case 5:
10299
+ return [
10300
+ 2
10301
+ ];
10302
+ }
10303
+ });
10304
+ }).call(this);
10305
+ }
10306
+ },
10307
+ {
10308
+ key: "continueAfterEdgeOutcome",
10309
+ value: function continueAfterEdgeOutcome(outcome, epoch) {
10310
+ if (outcome !== "fallback") return;
10311
+ if (!this.inAdBreak || epoch !== this.timing.adBreakEpoch) return;
10312
+ if (this.debug) {
10313
+ console.log("[AdBreak][EDGE] No usable Edge pod; checking with GAM for the remaining break");
10314
+ }
10315
+ this.setSlotState("NEEDS_NEXT_AD", "edge_fallback");
10316
+ this.requestNextAd("edge_fallback");
10317
+ }
10318
+ },
9892
10319
  {
9893
10320
  // ───────────────────────────────────────────────────────────────
9894
10321
  // Next-ad scheduling (single flight)
@@ -9944,7 +10371,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9944
10371
  value: function getStopRequestingReason(remainingMs) {
9945
10372
  if (this.breakSource === "vmap") {
9946
10373
  if (this.adPodQueue.length === 0) return "vmap_queue_empty";
9947
- } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl) {
10374
+ } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl && !this.canRequestEdge()) {
9948
10375
  return "no_follow_up_source";
9949
10376
  }
9950
10377
  if (this.timing.consecutiveFailures >= this.timing.maxConsecutiveFailures) {
@@ -9956,7 +10383,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9956
10383
  if (this.timing.totalAdRequestsInBreak >= this.timing.maxTotalAdRequestsPerBreak) {
9957
10384
  return "request_cap";
9958
10385
  }
9959
- if (this.timing.isGamInCooldown()) {
10386
+ if (!this.canRequestEdge() && this.timing.isGamInCooldown()) {
9960
10387
  return "gam_cooldown";
9961
10388
  }
9962
10389
  if (this.isBreakTimeBounded() && remainingMs < this.getMinRemainingMsToRequestAd()) {
@@ -10050,10 +10477,33 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10050
10477
  key: "requestFollowUpAd",
10051
10478
  value: function requestFollowUpAd(trigger) {
10052
10479
  return _async_to_generator(function() {
10053
- var url, epoch, unused;
10480
+ var edgeUrl, epoch2, outcome, url, epoch, unused;
10054
10481
  return _ts_generator(this, function(_state) {
10055
10482
  switch(_state.label){
10056
10483
  case 0:
10484
+ if (!this.canRequestEdge()) return [
10485
+ 3,
10486
+ 2
10487
+ ];
10488
+ edgeUrl = this.prepareEdgePodRequest();
10489
+ if (!edgeUrl) {
10490
+ this.requestNextAd("".concat(trigger, ":edge_unavailable"));
10491
+ return [
10492
+ 2
10493
+ ];
10494
+ }
10495
+ epoch2 = this.timing.adBreakEpoch;
10496
+ return [
10497
+ 4,
10498
+ this.runEdgePod(edgeUrl, "edge_".concat(trigger))
10499
+ ];
10500
+ case 1:
10501
+ outcome = _state.sent();
10502
+ this.continueAfterEdgeOutcome(outcome, epoch2);
10503
+ return [
10504
+ 2
10505
+ ];
10506
+ case 2:
10057
10507
  if (this.breakSource === "vmap") {
10058
10508
  url = this.adPodQueue.shift();
10059
10509
  } else if (this.followUpBaseVastUrl) {
@@ -10064,32 +10514,32 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10064
10514
  ];
10065
10515
  this.noteVastUrlGenerated(ensureVideoAdType(url), trigger);
10066
10516
  epoch = this.timing.adBreakEpoch;
10067
- _state.label = 1;
10068
- case 1:
10517
+ _state.label = 3;
10518
+ case 3:
10069
10519
  _state.trys.push([
10070
- 1,
10071
10520
  3,
10521
+ 5,
10072
10522
  ,
10073
- 4
10523
+ 6
10074
10524
  ]);
10075
10525
  return [
10076
10526
  4,
10077
10527
  this.playSingleAd(url, trigger)
10078
10528
  ];
10079
- case 2:
10529
+ case 4:
10080
10530
  _state.sent();
10081
10531
  return [
10082
10532
  3,
10083
- 4
10533
+ 6
10084
10534
  ];
10085
- case 3:
10535
+ case 5:
10086
10536
  unused = _state.sent();
10087
10537
  this.continueAfterRequestFailure(epoch);
10088
10538
  return [
10089
10539
  3,
10090
- 4
10540
+ 6
10091
10541
  ];
10092
- case 4:
10542
+ case 6:
10093
10543
  return [
10094
10544
  2
10095
10545
  ];
@@ -10202,7 +10652,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10202
10652
  case "gam_http_request_sent":
10203
10653
  diag.gamRequestSent++;
10204
10654
  break;
10655
+ case "edge_http_request_sent":
10656
+ diag.edgeRequestSent++;
10657
+ break;
10205
10658
  case "gam_response_received":
10659
+ case "edge_response_received":
10206
10660
  diag.vastHttpStatus = payload.httpStatus;
10207
10661
  break;
10208
10662
  case "vast_parsed":
@@ -10389,6 +10843,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10389
10843
  {
10390
10844
  key: "beginFunnelRequest",
10391
10845
  value: function beginFunnelRequest(adPlayer, requestToken, trigger) {
10846
+ var source = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : "gam";
10392
10847
  var _adPlayer_setFunnelContext;
10393
10848
  if (this.controllerCallInFlight) {
10394
10849
  this.logOrchestratorFunnel("duplicate_request_detected", {
@@ -10403,7 +10858,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10403
10858
  requestId: requestToken,
10404
10859
  controllerId: this.controllerGeneration,
10405
10860
  trigger: trigger,
10861
+ source: source,
10406
10862
  gamRequestSent: 0,
10863
+ edgeRequestSent: 0,
10407
10864
  wrapperDepth: 0,
10408
10865
  inlineResolved: false,
10409
10866
  mediaCandidateCount: 0,
@@ -10534,6 +10991,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10534
10991
  this.timing.activeAdRequestToken = requestToken;
10535
10992
  ap = this.host.getAdPlayer();
10536
10993
  budgetedUrl = this.withCurrentPodBudget(vastTagUrl);
10994
+ this.currentPodSource = "gam";
10537
10995
  this.setSlotState("REQUESTING", trigger);
10538
10996
  this.beginFunnelRequest(ap, requestToken, trigger);
10539
10997
  requestUrl = this.applyOpportunityFloor(budgetedUrl);
@@ -10681,6 +11139,10 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10681
11139
  requests: requests,
10682
11140
  requestRetries: requestRetries,
10683
11141
  gamResponses: count("gam_response_received"),
11142
+ edgeRequests: count("edge_request_sent"),
11143
+ edgeFills: count("edge_fill"),
11144
+ edgeEmptySentinels: count("edge_empty_sentinel"),
11145
+ edgeFallbacks: countPrefix("edge_fallback:"),
10684
11146
  requestsSuppressed: count("request_suppressed"),
10685
11147
  duplicates: count("duplicate_request_detected") + count("duplicate_break_start"),
10686
11148
  filled: count("vast_received"),
@@ -10777,6 +11239,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10777
11239
  this.breakSource = void 0;
10778
11240
  this.followUpBaseVastUrl = void 0;
10779
11241
  this.unusableResponsesInBreak = 0;
11242
+ this.resetEdgeStateForBreak();
10780
11243
  adPlayer.stop().catch(function() {});
10781
11244
  if (!alreadyEnded) this.logBreakFunnel(reason);
10782
11245
  this.opportunityFloor = void 0;
@@ -11090,7 +11553,13 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
11090
11553
  });
11091
11554
  return;
11092
11555
  }
11093
- this.timing.consecutiveFailures++;
11556
+ if (this.currentPodSource === "edge") {
11557
+ this.markEdgeExhausted("edge_playback_failed", {
11558
+ reason: reason
11559
+ });
11560
+ } else {
11561
+ this.timing.consecutiveFailures++;
11562
+ }
11094
11563
  var diag = this.currentRequestId != null ? this.requestDiagnostics.get(this.currentRequestId) : void 0;
11095
11564
  if (diag && !diag.failureReason) diag.failureReason = reason;
11096
11565
  if (this.debug) {
@@ -11120,7 +11589,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11120
11589
  if (this.config.adFloorMaxCents != null) floorBounds.maxCents = this.config.adFloorMaxCents;
11121
11590
  if (this.config.adFloorStepCents != null) floorBounds.stepCents = this.config.adFloorStepCents;
11122
11591
  this.floorLadder = new FloorLadderController(this.config.adFloorLadder, floorBounds);
11592
+ this.floorLadderEnabled = this.config.adFloorLadderEnabled === true;
11123
11593
  this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11594
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11124
11595
  this.adPlayer = this.createAdPlayer(false);
11125
11596
  this.palNonce = createPalNonceManager();
11126
11597
  this.cueManager = new Scte35CueManager(!!this.config.debugAdTiming, {
@@ -11220,11 +11691,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11220
11691
  generatePodVastUrl: function generatePodVastUrl(base, breakDurationMs) {
11221
11692
  return _this.generatePodVastUrl(base, breakDurationMs);
11222
11693
  },
11694
+ getEdgePodBaseUrl: function getEdgePodBaseUrl() {
11695
+ return _this.edgePodBaseUrl;
11696
+ },
11223
11697
  getFloorSnapshot: function getFloorSnapshot(position) {
11224
- return _this.floorLadder.snapshotFor({
11698
+ return _this.floorLadderEnabled ? _this.floorLadder.snapshotFor({
11225
11699
  position: position,
11226
11700
  isCtv: _this.isCtvEnvironment()
11227
- });
11701
+ }) : void 0;
11228
11702
  },
11229
11703
  onAdClick: function onAdClick() {
11230
11704
  return _this.palNonce.sendAdClick();
@@ -11275,11 +11749,43 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11275
11749
  return report;
11276
11750
  }
11277
11751
  },
11752
+ {
11753
+ key: "setAdFloorLadderEnabled",
11754
+ value: function setAdFloorLadderEnabled(enabled) {
11755
+ this.floorLadderEnabled = enabled;
11756
+ if (enabled) this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11757
+ }
11758
+ },
11759
+ {
11760
+ key: "isAdFloorLadderEnabled",
11761
+ value: function isAdFloorLadderEnabled() {
11762
+ return this.floorLadderEnabled;
11763
+ }
11764
+ },
11765
+ {
11766
+ key: "setEdgePodRequest",
11767
+ value: function setEdgePodRequest(update) {
11768
+ if (update.enableEdgePodRequest === this.config.enableEdgePodRequest && update.edgePodUrl === this.config.edgePodUrl) {
11769
+ return;
11770
+ }
11771
+ if (update.enableEdgePodRequest === void 0) delete this.config.enableEdgePodRequest;
11772
+ else this.config.enableEdgePodRequest = update.enableEdgePodRequest;
11773
+ if (update.edgePodUrl === void 0) delete this.config.edgePodUrl;
11774
+ else this.config.edgePodUrl = update.edgePodUrl;
11775
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11776
+ }
11777
+ },
11778
+ {
11779
+ key: "getEdgePodUrl",
11780
+ value: function getEdgePodUrl() {
11781
+ return this.edgePodBaseUrl;
11782
+ }
11783
+ },
11278
11784
  {
11279
11785
  key: "warnOnInvalidFloorLadder",
11280
11786
  value: function warnOnInvalidFloorLadder(report) {
11281
11787
  var config = this.floorLadder.get();
11282
- if (report.ok || !config.enabled) return;
11788
+ if (report.ok || !this.floorLadderEnabled || !config.enabled) return;
11283
11789
  console.warn("[AdBreak][FLOOR] configuration has issues; invalid floors will not be sent", {
11284
11790
  errors: report.errors,
11285
11791
  configurationVersion: config.configurationVersion,
@@ -12012,6 +12518,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12012
12518
  this.timing.destroy();
12013
12519
  this.adBreak.clearPendingAdBreak();
12014
12520
  this.placeholder.destroy();
12521
+ 0;
12015
12522
  if (this.timeUpdateHandler) {
12016
12523
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
12017
12524
  delete this.timeUpdateHandler;