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