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.
@@ -2565,7 +2565,7 @@ function createHlsAdPlayer(contentVideo, options) {
2565
2565
  img.onload = null;
2566
2566
  img.onerror = null;
2567
2567
  inFlightBeacons.delete(img);
2568
- console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingUrl));
2568
+ console.log("[HlsAdPlayer] Tracking pixel settled (".concat(outcome, "): ").concat(trackingHost));
2569
2569
  if (outcome !== "load") {
2570
2570
  logFunnel("tracking_request_settled", {
2571
2571
  reason: outcome,
@@ -2583,7 +2583,7 @@ function createHlsAdPlayer(contentVideo, options) {
2583
2583
  return release("timeout");
2584
2584
  }, BEACON_TIMEOUT_MS);
2585
2585
  img.src = trackingUrl;
2586
- console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingUrl));
2586
+ console.log("[HlsAdPlayer] Fired tracking pixel: ".concat(trackingHost));
2587
2587
  logFunnel("tracking_request_created", {
2588
2588
  trackingHost: trackingHost,
2589
2589
  errorCode: options2 === null || options2 === void 0 ? void 0 : options2.errorCode
@@ -3037,7 +3037,7 @@ function createHlsAdPlayer(contentVideo, options) {
3037
3037
  });
3038
3038
  }
3039
3039
  if (parsed.type === "wrapper") {
3040
- console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(parsed.vastAdTagUri));
3040
+ console.log("[HlsAdPlayer] VAST wrapper detected, following VASTAdTagURI: ".concat(redactUrl(parsed.vastAdTagUri)));
3041
3041
  }
3042
3042
  return parsed;
3043
3043
  }
@@ -3229,8 +3229,8 @@ function createHlsAdPlayer(contentVideo, options) {
3229
3229
  switch(_state.label){
3230
3230
  case 0:
3231
3231
  attempt++;
3232
- if (kind === "gam") {
3233
- logFunnel("gam_http_request_sent", {
3232
+ if (kind !== "wrapper") {
3233
+ logFunnel("".concat(kind, "_http_request_sent"), {
3234
3234
  attempt: attempt,
3235
3235
  credentials: creds
3236
3236
  });
@@ -3250,8 +3250,8 @@ function createHlsAdPlayer(contentVideo, options) {
3250
3250
  ];
3251
3251
  case 1:
3252
3252
  response = _state.sent();
3253
- if (kind === "gam") {
3254
- logFunnel("gam_response_received", {
3253
+ if (kind !== "wrapper") {
3254
+ logFunnel("".concat(kind, "_response_received"), {
3255
3255
  attempt: attempt,
3256
3256
  httpStatus: response.status,
3257
3257
  ok: response.ok,
@@ -3421,8 +3421,7 @@ function createHlsAdPlayer(contentVideo, options) {
3421
3421
  elapsedMs: Date.now() - fetchStartedAt
3422
3422
  });
3423
3423
  }
3424
- console.log("[HlsAdPlayer] VAST XML received");
3425
- console.log("[HlsAdPlayer] VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3424
+ console.log("[HlsAdPlayer] VAST XML received (length=".concat(vastXml.length, ")"));
3426
3425
  parsed = parseVastXml(vastXml);
3427
3426
  if (parsed.type === "empty") {
3428
3427
  ;
@@ -3463,16 +3462,35 @@ function createHlsAdPlayer(contentVideo, options) {
3463
3462
  });
3464
3463
  }).apply(this, arguments);
3465
3464
  }
3465
+ function isEdgeEmptySentinelAd(adElement) {
3466
+ var _textOf;
3467
+ if ((adElement.getAttribute("id") || "").trim().toLowerCase() === "empty") {
3468
+ return true;
3469
+ }
3470
+ var inLine = adElement.querySelector(":scope > InLine");
3471
+ if (!inLine) return false;
3472
+ if (/^no ad available$/i.test((_textOf = textOf(inLine.querySelector(":scope > AdTitle"))) !== null && _textOf !== void 0 ? _textOf : "")) {
3473
+ return true;
3474
+ }
3475
+ var durations = Array.from(inLine.querySelectorAll(":scope > Creatives > Creative > Linear > Duration")).map(function(el) {
3476
+ return parseVastTime(textOf(el));
3477
+ });
3478
+ return durations.length > 0 && durations.every(function(seconds) {
3479
+ return seconds === 0;
3480
+ });
3481
+ }
3466
3482
  function fetchAndParsePod(vastTagUrl) {
3483
+ var source = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : "gam";
3467
3484
  return _async_to_generator(function() {
3468
- var _root_getAttribute, _root_getAttribute1, vastXml, trimmedXml, xmlDoc, root, adElements, _root_getAttribute2, _empty_errorUrls, empty, _empty_errorCode, resolveAd, podElements, buffet, resolveAll, ads, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, ad, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ad1, err;
3485
+ var _root_getAttribute, _root_getAttribute1, isEdge, vastXml, trimmedXml, xmlDoc, root, adElements, _root_getAttribute2, _empty_errorUrls, empty, _empty_errorCode, resolveAd, candidates, podElements, buffet, resolveAll, ads, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, ad, err, _iteratorNormalCompletion1, _didIteratorError1, _iteratorError1, _iterator1, _step1, ad1, err;
3469
3486
  return _ts_generator(this, function(_state) {
3470
3487
  switch(_state.label){
3471
3488
  case 0:
3472
3489
  lastPodAdElementCount = 0;
3490
+ isEdge = source === "edge";
3473
3491
  return [
3474
3492
  4,
3475
- fetchVastXml(vastTagUrl)
3493
+ fetchVastXml(vastTagUrl, isEdge ? "omit" : "include", source)
3476
3494
  ];
3477
3495
  case 1:
3478
3496
  vastXml = _state.sent();
@@ -3495,7 +3513,6 @@ function createHlsAdPlayer(contentVideo, options) {
3495
3513
  }
3496
3514
  xmlDoc = parseXml(vastXml);
3497
3515
  if (!xmlDoc) {
3498
- console.error("[HlsAdPlayer] Pod VAST XML content (first 2000 chars):", vastXml.substring(0, 2e3));
3499
3516
  logFunnel("vast_parsed", {
3500
3517
  adElementCount: 0,
3501
3518
  reason: "xml_parse_error"
@@ -3567,12 +3584,24 @@ function createHlsAdPlayer(contentVideo, options) {
3567
3584
  });
3568
3585
  })();
3569
3586
  };
3570
- podElements = adElements.filter(function(el) {
3587
+ candidates = adElements;
3588
+ if (isEdge) {
3589
+ candidates = adElements.filter(function(el) {
3590
+ return !isEdgeEmptySentinelAd(el);
3591
+ });
3592
+ if (candidates.length < adElements.length) {
3593
+ logFunnel("edge_empty_sentinel", {
3594
+ sentinelCount: adElements.length - candidates.length,
3595
+ adElementCount: adElements.length
3596
+ });
3597
+ }
3598
+ }
3599
+ podElements = candidates.filter(function(el) {
3571
3600
  return getAdSequence(el) !== void 0;
3572
3601
  }).sort(function(a, b) {
3573
3602
  return getAdSequence(a) - getAdSequence(b);
3574
3603
  });
3575
- buffet = adElements.filter(function(el) {
3604
+ buffet = candidates.filter(function(el) {
3576
3605
  return getAdSequence(el) === void 0;
3577
3606
  });
3578
3607
  resolveAll = function resolveAll(elements) {
@@ -3729,7 +3758,9 @@ function createHlsAdPlayer(contentVideo, options) {
3729
3758
  lastPodAdElementCount = adElements.length;
3730
3759
  return [
3731
3760
  2,
3732
- ads
3761
+ isEdge ? ads.filter(function(ad) {
3762
+ return ad.duration > 0;
3763
+ }) : ads
3733
3764
  ];
3734
3765
  }
3735
3766
  });
@@ -4730,13 +4761,14 @@ function createHlsAdPlayer(contentVideo, options) {
4730
4761
  contentVideo[AD_CONTAINER_PROP] = adContainerEl;
4731
4762
  attachContainerHandlers();
4732
4763
  },
4733
- requestAds: function requestAds(vastTagUrl) {
4764
+ requestAds: function requestAds(vastTagUrl, requestOptions) {
4734
4765
  return _async_to_generator(function() {
4735
- var ads, preloaded, error;
4766
+ var _ref, source, ads, preloaded, error;
4736
4767
  return _ts_generator(this, function(_state) {
4737
4768
  switch(_state.label){
4738
4769
  case 0:
4739
- console.log("[HlsAdPlayer] Requesting ads:", vastTagUrl);
4770
+ source = (_ref = requestOptions === null || requestOptions === void 0 ? void 0 : requestOptions.source) !== null && _ref !== void 0 ? _ref : "gam";
4771
+ console.log("[HlsAdPlayer] Requesting ads (".concat(source, "):"), redactUrl(vastTagUrl));
4740
4772
  if (adPlaying) {
4741
4773
  console.warn("[HlsAdPlayer] Cannot request new ads while an ad is playing");
4742
4774
  return [
@@ -4781,7 +4813,7 @@ function createHlsAdPlayer(contentVideo, options) {
4781
4813
  case 2:
4782
4814
  return [
4783
4815
  4,
4784
- fetchAndParsePod(vastTagUrl)
4816
+ fetchAndParsePod(vastTagUrl, source)
4785
4817
  ];
4786
4818
  case 3:
4787
4819
  ads = _state.sent();
@@ -5191,6 +5223,75 @@ function createHlsAdPlayer(contentVideo, options) {
5191
5223
  return podAds.reduce(function(total, ad) {
5192
5224
  return total + (ad.duration > 0 ? ad.duration * 1e3 : 0);
5193
5225
  }, 0);
5226
+ },
5227
+ limitPodToBudget: function limitPodToBudget(maxDurationMs, maxAds) {
5228
+ if (adPlaying) return podAds.length;
5229
+ var kept = [];
5230
+ var dropped = [];
5231
+ var totalMs = 0;
5232
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
5233
+ try {
5234
+ for(var _iterator = podAds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
5235
+ var ad = _step.value;
5236
+ var durationMs = Math.round(ad.duration * 1e3);
5237
+ if (kept.length < maxAds && totalMs + durationMs <= maxDurationMs) {
5238
+ kept.push(ad);
5239
+ totalMs += durationMs;
5240
+ } else {
5241
+ dropped.push(ad);
5242
+ }
5243
+ }
5244
+ } catch (err) {
5245
+ _didIteratorError = true;
5246
+ _iteratorError = err;
5247
+ } finally{
5248
+ try {
5249
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
5250
+ _iterator.return();
5251
+ }
5252
+ } finally{
5253
+ if (_didIteratorError) {
5254
+ throw _iteratorError;
5255
+ }
5256
+ }
5257
+ }
5258
+ if (dropped.length > 0) {
5259
+ logFunnel("pod_trimmed_to_budget", {
5260
+ keptCount: kept.length,
5261
+ droppedCount: dropped.length,
5262
+ keptDurationMs: totalMs,
5263
+ maxDurationMs: maxDurationMs,
5264
+ maxAds: maxAds
5265
+ });
5266
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
5267
+ try {
5268
+ for(var _iterator1 = dropped[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
5269
+ var ad1 = _step1.value;
5270
+ resolvedAds.add(ad1);
5271
+ fireTrackingPixels(ad1.trackingUrls.notUsed, {
5272
+ ad: ad1
5273
+ });
5274
+ }
5275
+ } catch (err) {
5276
+ _didIteratorError1 = true;
5277
+ _iteratorError1 = err;
5278
+ } finally{
5279
+ try {
5280
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
5281
+ _iterator1.return();
5282
+ }
5283
+ } finally{
5284
+ if (_didIteratorError1) {
5285
+ throw _iteratorError1;
5286
+ }
5287
+ }
5288
+ }
5289
+ }
5290
+ podAds = kept;
5291
+ podIndex = 0;
5292
+ currentAd = kept[0];
5293
+ currentAdMediaFile = void 0;
5294
+ return kept.length;
5194
5295
  }
5195
5296
  };
5196
5297
  }
@@ -9189,6 +9290,71 @@ var HlsEngine = /*#__PURE__*/ function() {
9189
9290
  ]);
9190
9291
  return HlsEngine;
9191
9292
  }();
9293
+ // src/player/EdgePodRequest.ts
9294
+ var EDGE_POD_MAX_ADS = 10;
9295
+ var EDGE_POD_EXCEEDS_BUDGET = "edge_pod_exceeds_budget";
9296
+ var LICENSE_KEY_PLACEHOLDER = /\{licenseKey\}|%7BlicenseKey%7D/gi;
9297
+ function resolveEdgePodBaseUrl(config) {
9298
+ var _config_edgePodUrl, _config_licenseKey;
9299
+ if (config.enableEdgePodRequest !== true) return void 0;
9300
+ var raw = (_config_edgePodUrl = config.edgePodUrl) === null || _config_edgePodUrl === void 0 ? void 0 : _config_edgePodUrl.trim();
9301
+ if (!raw) {
9302
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but edgePodUrl is missing; using GAM only");
9303
+ return void 0;
9304
+ }
9305
+ var licenseKey = (_config_licenseKey = config.licenseKey) === null || _config_licenseKey === void 0 ? void 0 : _config_licenseKey.trim();
9306
+ if (!licenseKey) {
9307
+ console.warn("[AdBreak][EDGE] enableEdgePodRequest is true but licenseKey is missing; using GAM only");
9308
+ return void 0;
9309
+ }
9310
+ var encodedKey = encodeURIComponent(licenseKey);
9311
+ try {
9312
+ var hasPlaceholder = raw.search(LICENSE_KEY_PLACEHOLDER) !== -1;
9313
+ var url = new URL(hasPlaceholder ? raw.replace(LICENSE_KEY_PLACEHOLDER, encodedKey) : raw);
9314
+ if (url.protocol !== "https:" && url.protocol !== "http:") {
9315
+ throw new Error("unsupported protocol ".concat(url.protocol));
9316
+ }
9317
+ if (!hasPlaceholder) {
9318
+ var path = url.pathname.replace(/\/+$/, "");
9319
+ url.pathname = /\/[^/]+\/pod$/.test(path) ? path.replace(/\/[^/]+\/pod$/, "/".concat(encodedKey, "/pod")) : "".concat(path, "/").concat(encodedKey, "/pod");
9320
+ }
9321
+ return url.toString();
9322
+ } catch (unused) {
9323
+ console.warn("[AdBreak][EDGE] edgePodUrl is not a valid http(s) URL; using GAM only");
9324
+ return void 0;
9325
+ }
9326
+ }
9327
+ function buildEdgePodUrl(baseUrl, remainingMs, remainingAdLimit) {
9328
+ var url = new URL(baseUrl);
9329
+ var maxMs = Math.max(0, Math.floor(remainingMs));
9330
+ var maxAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, Math.floor(remainingAdLimit)));
9331
+ url.searchParams.set("pmxd", String(maxMs));
9332
+ url.searchParams.set("pmad", String(maxAds));
9333
+ url.searchParams.set("duration", String(Math.floor(maxMs / 1e3)));
9334
+ url.searchParams.set("count", String(maxAds));
9335
+ return url.toString();
9336
+ }
9337
+ function classifyEdgeVastResponse(result) {
9338
+ var _ref;
9339
+ var _result_error;
9340
+ if (result.error == null) {
9341
+ return result.podAdCount > 0 ? "fill" : "no_fill";
9342
+ }
9343
+ 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);
9344
+ if (/no ads available|fill rejected|no media file|no ad loaded/i.test(message) || message.includes(EDGE_POD_EXCEEDS_BUDGET)) {
9345
+ return "no_fill";
9346
+ }
9347
+ return "invalid";
9348
+ }
9349
+ function describeEdgeUrl(url) {
9350
+ try {
9351
+ var parsed = new URL(url);
9352
+ return "".concat(parsed.origin).concat(parsed.pathname);
9353
+ } catch (unused) {
9354
+ var _url_split_;
9355
+ return (_url_split_ = url.split("?")[0]) !== null && _url_split_ !== void 0 ? _url_split_ : "";
9356
+ }
9357
+ }
9192
9358
  // src/player/PlayerControls.ts
9193
9359
  function getAdAudioVolume(adPlayer) {
9194
9360
  return adPlayer.getOriginalMutedState() ? 0 : adPlayer.getOriginalVolume();
@@ -9347,6 +9513,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9347
9513
  this.controllerCallInFlight = false;
9348
9514
  this.playbackStartedForCurrentRequest = false;
9349
9515
  this.opportunityEpoch = -1;
9516
+ this.edgeExhaustedForBreak = false;
9517
+ this.edgeRequestsInBreak = 0;
9350
9518
  this.adPodQueue = [];
9351
9519
  this.defaultMinRemainingMsToRequestAd = 1e4;
9352
9520
  this.defaultMaxNoFillsPerBreak = 2;
@@ -9497,7 +9665,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9497
9665
  _this.timing.clearAdRequestWatchdog();
9498
9666
  _this.timing.activeAdRequestToken = null;
9499
9667
  _this.showAds = true;
9500
- _this.timing.resetGamNoFillCounter();
9668
+ if (_this.currentPodSource !== "edge") {
9669
+ _this.timing.resetGamNoFillCounter();
9670
+ }
9501
9671
  if (_this.inAdBreak && _this.timing.expectedAdBreakDurationMs != null && _this.timing.getAdBreakEndWallClockMs() == null) {
9502
9672
  _this.timing.scheduleAdStopAtBreakBoundary();
9503
9673
  if (_this.debug) {
@@ -9629,6 +9799,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9629
9799
  key: "beginVmapBreak",
9630
9800
  value: function beginVmapBreak(breakKey, position) {
9631
9801
  this.breakSource = "vmap";
9802
+ this.resetEdgeStateForBreak();
9632
9803
  this.followUpBaseVastUrl = void 0;
9633
9804
  this.unusableResponsesInBreak = 0;
9634
9805
  this.timing.totalAdRequestsInBreak = 0;
@@ -9640,7 +9811,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9640
9811
  key: "handleAdStart",
9641
9812
  value: function handleAdStart(marker) {
9642
9813
  return _async_to_generator(function() {
9643
- var baseVastUrl, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, podPlayed;
9814
+ var baseVastUrl, edgeConfigured, adBreakDurationMs, currentMuted, currentVolume, epoch, hasPrefetchedUrl, mode, edgeUrl, outcome, podPlayed;
9644
9815
  return _ts_generator(this, function(_state) {
9645
9816
  switch(_state.label){
9646
9817
  case 0:
@@ -9654,7 +9825,8 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9654
9825
  ];
9655
9826
  }
9656
9827
  baseVastUrl = this.adConfig.apiVastTagUrl;
9657
- if (!baseVastUrl) {
9828
+ edgeConfigured = !!this.host.getEdgePodBaseUrl();
9829
+ if (!baseVastUrl && !edgeConfigured) {
9658
9830
  console.warn("[AdBreak] SCTE-35 break started but no direct VAST tag is configured");
9659
9831
  this.clearPendingAdBreak();
9660
9832
  return [
@@ -9670,6 +9842,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9670
9842
  this.breakSource = "scte35";
9671
9843
  this.followUpBaseVastUrl = baseVastUrl;
9672
9844
  this.unusableResponsesInBreak = 0;
9845
+ this.resetEdgeStateForBreak();
9673
9846
  this.setSlotState("NEEDS_NEXT_AD", "scte35_break_start");
9674
9847
  if (this.adConfig.podAssignedByPrefetch) {
9675
9848
  this.adConfig.podAssignedByPrefetch = false;
@@ -9691,7 +9864,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9691
9864
  this.noteOpportunity("scte35", this.cueManager.activeScte35BreakKey, "midroll");
9692
9865
  epoch = this.timing.adBreakEpoch;
9693
9866
  hasPrefetchedUrl = !!(this.pendingAdBreak && this.pendingAdBreak.vastUrls.length > 0);
9694
- if (!(adBreakDurationMs == null && !hasPrefetchedUrl)) return [
9867
+ if (!(adBreakDurationMs == null && (!hasPrefetchedUrl || edgeConfigured))) return [
9695
9868
  3,
9696
9869
  2
9697
9870
  ];
@@ -9710,11 +9883,46 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9710
9883
  mode = this.host.config.isLiveStream ? "LIVE" : "VOD";
9711
9884
  console.log("[AdBreak] ".concat(mode, " break start: target duration=").concat(adBreakDurationMs != null ? "".concat(adBreakDurationMs, "ms") : "unknown"));
9712
9885
  }
9886
+ if (!edgeConfigured) return [
9887
+ 3,
9888
+ 5
9889
+ ];
9890
+ edgeUrl = this.canRequestEdge() ? this.prepareEdgePodRequest() : void 0;
9891
+ if (!edgeUrl) return [
9892
+ 3,
9893
+ 4
9894
+ ];
9895
+ this.clearPendingAdBreak();
9713
9896
  return [
9714
9897
  4,
9715
- this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9898
+ this.runEdgePod(edgeUrl, "edge_break_start")
9716
9899
  ];
9717
9900
  case 3:
9901
+ outcome = _state.sent();
9902
+ this.continueAfterEdgeOutcome(outcome, epoch);
9903
+ return [
9904
+ 2
9905
+ ];
9906
+ case 4:
9907
+ if (!this.edgeExhaustedForBreak) {
9908
+ this.markEdgeExhausted("not_eligible_at_break_start", {
9909
+ isLive: this.isLiveBreak(),
9910
+ durationMs: this.timing.expectedAdBreakDurationMs
9911
+ });
9912
+ }
9913
+ _state.label = 5;
9914
+ case 5:
9915
+ if (!baseVastUrl) {
9916
+ this.requestNextAd("no_gam_source");
9917
+ return [
9918
+ 2
9919
+ ];
9920
+ }
9921
+ return [
9922
+ 4,
9923
+ this.startOptimizedPod(baseVastUrl, adBreakDurationMs)
9924
+ ];
9925
+ case 6:
9718
9926
  podPlayed = _state.sent();
9719
9927
  if (!podPlayed && this.inAdBreak && epoch === this.timing.adBreakEpoch) {
9720
9928
  this.setSlotState("NEEDS_NEXT_AD", "initial_request_failed");
@@ -9806,6 +10014,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9806
10014
  adPlayer = this.host.getAdPlayer();
9807
10015
  requestToken = ++this.timing.adRequestTokenCounter;
9808
10016
  this.timing.activeAdRequestToken = requestToken;
10017
+ this.currentPodSource = "gam";
9809
10018
  this.setSlotState("REQUESTING", "initial_pod");
9810
10019
  this.beginFunnelRequest(adPlayer, requestToken, "initial_pod");
9811
10020
  requestUrl = this.applyOpportunityFloor(podUrl);
@@ -9922,6 +10131,224 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9922
10131
  }).call(this);
9923
10132
  }
9924
10133
  },
10134
+ {
10135
+ // ───────────────────────────────────────────────────────────────
10136
+ // Edge-first pods (live SCTE-35 only, GAM fallback)
10137
+ // ───────────────────────────────────────────────────────────────
10138
+ key: "resetEdgeStateForBreak",
10139
+ value: function resetEdgeStateForBreak() {
10140
+ this.edgeExhaustedForBreak = false;
10141
+ this.edgeRequestsInBreak = 0;
10142
+ this.currentPodSource = void 0;
10143
+ }
10144
+ },
10145
+ {
10146
+ key: "isLiveBreak",
10147
+ value: function isLiveBreak() {
10148
+ return this.host.isLiveStream() || this.host.config.isLiveStream === true;
10149
+ }
10150
+ },
10151
+ {
10152
+ key: "canRequestEdge",
10153
+ value: function canRequestEdge() {
10154
+ return this.breakSource === "scte35" && !this.edgeExhaustedForBreak && !!this.host.getEdgePodBaseUrl() && this.isLiveBreak() && this.timing.expectedAdBreakDurationMs != null && this.edgeRequestsInBreak < this.timing.maxTotalAdRequestsPerBreak;
10155
+ }
10156
+ },
10157
+ {
10158
+ key: "markEdgeExhausted",
10159
+ value: function markEdgeExhausted(reason) {
10160
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
10161
+ this.edgeExhaustedForBreak = true;
10162
+ this.countFunnelStage("edge_fallback:".concat(reason));
10163
+ this.logOrchestratorFunnel("edge_exhausted", _object_spread({
10164
+ reason: reason,
10165
+ edgeRequests: this.edgeRequestsInBreak,
10166
+ remainingMs: this.timing.getRemainingAdMs()
10167
+ }, extra));
10168
+ }
10169
+ },
10170
+ {
10171
+ /**
10172
+ * Builds the next Edge request from the budget left right now, or marks Edge
10173
+ * exhausted for this break. Synchronous on purpose: callers must reach the
10174
+ * REQUESTING state in the same tick so a second completion event cannot race
10175
+ * in a parallel request.
10176
+ */ key: "prepareEdgePodRequest",
10177
+ value: function prepareEdgePodRequest() {
10178
+ var baseUrl = this.host.getEdgePodBaseUrl();
10179
+ if (!baseUrl) return void 0;
10180
+ var remainingMs = this.timing.getRemainingAdMs();
10181
+ var remainingAds = Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak);
10182
+ if (remainingAds < 1) {
10183
+ this.markEdgeExhausted("ad_capacity_reached", {
10184
+ adsInBreak: this.timing.totalAdsInBreak
10185
+ });
10186
+ return void 0;
10187
+ }
10188
+ if (remainingMs < this.getMinRemainingMsToRequestAd()) {
10189
+ this.markEdgeExhausted("insufficient_break_time", {
10190
+ remainingMs: remainingMs
10191
+ });
10192
+ return void 0;
10193
+ }
10194
+ try {
10195
+ return buildEdgePodUrl(baseUrl, remainingMs, remainingAds);
10196
+ } catch (unused) {
10197
+ this.markEdgeExhausted("invalid_edge_url");
10198
+ return void 0;
10199
+ }
10200
+ }
10201
+ },
10202
+ {
10203
+ key: "runEdgePod",
10204
+ value: function runEdgePod(requestUrl, trigger) {
10205
+ return _async_to_generator(function() {
10206
+ var _params_get, _params_get1, epoch, generation, adPlayer, requestToken, params, _ref, _adPlayer_getPodAdCount, _adPlayer_getPodTotalDurationMs, remainingMs, remainingAds, podCount, error, errorMessage, classification, diag;
10207
+ return _ts_generator(this, function(_state) {
10208
+ switch(_state.label){
10209
+ case 0:
10210
+ epoch = this.timing.adBreakEpoch;
10211
+ this.recreateAdController("new_request");
10212
+ generation = this.controllerGeneration;
10213
+ adPlayer = this.host.getAdPlayer();
10214
+ requestToken = ++this.timing.adRequestTokenCounter;
10215
+ this.timing.activeAdRequestToken = requestToken;
10216
+ this.currentPodSource = "edge";
10217
+ this.edgeRequestsInBreak++;
10218
+ this.setSlotState("REQUESTING", trigger);
10219
+ this.beginFunnelRequest(adPlayer, requestToken, trigger, "edge");
10220
+ this.currentVastUrl = requestUrl;
10221
+ params = new URL(requestUrl).searchParams;
10222
+ this.logOrchestratorFunnel("edge_request_sent", {
10223
+ trigger: trigger,
10224
+ attempt: this.edgeRequestsInBreak,
10225
+ endpoint: describeEdgeUrl(requestUrl),
10226
+ pmxd: (_params_get = params.get("pmxd")) !== null && _params_get !== void 0 ? _params_get : void 0,
10227
+ pmad: (_params_get1 = params.get("pmad")) !== null && _params_get1 !== void 0 ? _params_get1 : void 0
10228
+ });
10229
+ _state.label = 1;
10230
+ case 1:
10231
+ _state.trys.push([
10232
+ 1,
10233
+ 4,
10234
+ ,
10235
+ 5
10236
+ ]);
10237
+ this.timing.startAdRequestWatchdog(requestToken);
10238
+ return [
10239
+ 4,
10240
+ this.callController(function() {
10241
+ return adPlayer.requestAds(requestUrl, {
10242
+ source: "edge"
10243
+ });
10244
+ })
10245
+ ];
10246
+ case 2:
10247
+ _state.sent();
10248
+ this.timing.clearAdRequestWatchdog();
10249
+ if (this.isStale(epoch, generation)) {
10250
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10251
+ requestId: requestToken,
10252
+ source: "edge"
10253
+ });
10254
+ return [
10255
+ 2,
10256
+ "stale"
10257
+ ];
10258
+ }
10259
+ remainingMs = this.timing.getRemainingAdMs();
10260
+ remainingAds = Math.max(1, Math.min(EDGE_POD_MAX_ADS, this.adConfig.resolvePodParams().maxAds - this.timing.totalAdsInBreak));
10261
+ 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;
10262
+ if (classifyEdgeVastResponse({
10263
+ podAdCount: podCount
10264
+ }) !== "fill") {
10265
+ throw new Error("".concat(EDGE_POD_EXCEEDS_BUDGET, ": no Edge ad fits ").concat(remainingMs, "ms"));
10266
+ }
10267
+ this.setSlotState("READY", "edge_vast_ready");
10268
+ this.logOrchestratorFunnel("edge_fill", {
10269
+ podAdCount: podCount,
10270
+ podDurationMs: (_adPlayer_getPodTotalDurationMs = adPlayer.getPodTotalDurationMs) === null || _adPlayer_getPodTotalDurationMs === void 0 ? void 0 : _adPlayer_getPodTotalDurationMs.call(adPlayer),
10271
+ remainingMs: remainingMs
10272
+ });
10273
+ sendAdLoadedTracking(this.host.config.licenseKey, {
10274
+ source: "hls",
10275
+ vastUrl: requestUrl,
10276
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
10277
+ }, this.analyticsContext).catch(function() {});
10278
+ this.timing.startAdFailsafeTimer(requestToken);
10279
+ return [
10280
+ 4,
10281
+ this.callController(function() {
10282
+ return adPlayer.play();
10283
+ })
10284
+ ];
10285
+ case 3:
10286
+ _state.sent();
10287
+ if (this.isStale(epoch, generation)) return [
10288
+ 2,
10289
+ "stale"
10290
+ ];
10291
+ if (this.timing.expectedAdBreakDurationMs != null && this.timing.getAdBreakEndWallClockMs() == null) {
10292
+ this.timing.scheduleAdStopAtBreakBoundary();
10293
+ }
10294
+ adPlayer.setAdVolume(getAdAudioVolume(adPlayer));
10295
+ return [
10296
+ 2,
10297
+ "fill"
10298
+ ];
10299
+ case 4:
10300
+ error = _state.sent();
10301
+ this.timing.clearAdRequestWatchdog();
10302
+ this.timing.clearAdFailsafeTimer();
10303
+ if (this.timing.activeAdRequestToken === requestToken) {
10304
+ this.timing.activeAdRequestToken = null;
10305
+ }
10306
+ errorMessage = error === null || error === void 0 ? void 0 : error.message;
10307
+ if (this.isStale(epoch, generation)) {
10308
+ this.logOrchestratorFunnel("stale_request_result_ignored", {
10309
+ requestId: requestToken,
10310
+ source: "edge",
10311
+ errorMessage: errorMessage
10312
+ });
10313
+ return [
10314
+ 2,
10315
+ "stale"
10316
+ ];
10317
+ }
10318
+ classification = classifyEdgeVastResponse({
10319
+ podAdCount: 0,
10320
+ error: error
10321
+ });
10322
+ diag = this.requestDiagnostics.get(requestToken);
10323
+ if (diag && !diag.failureReason) diag.failureReason = "edge_".concat(classification, ": ").concat(errorMessage);
10324
+ this.markEdgeExhausted("edge_".concat(classification), {
10325
+ errorMessage: errorMessage
10326
+ });
10327
+ return [
10328
+ 2,
10329
+ "fallback"
10330
+ ];
10331
+ case 5:
10332
+ return [
10333
+ 2
10334
+ ];
10335
+ }
10336
+ });
10337
+ }).call(this);
10338
+ }
10339
+ },
10340
+ {
10341
+ key: "continueAfterEdgeOutcome",
10342
+ value: function continueAfterEdgeOutcome(outcome, epoch) {
10343
+ if (outcome !== "fallback") return;
10344
+ if (!this.inAdBreak || epoch !== this.timing.adBreakEpoch) return;
10345
+ if (this.debug) {
10346
+ console.log("[AdBreak][EDGE] No usable Edge pod; checking with GAM for the remaining break");
10347
+ }
10348
+ this.setSlotState("NEEDS_NEXT_AD", "edge_fallback");
10349
+ this.requestNextAd("edge_fallback");
10350
+ }
10351
+ },
9925
10352
  {
9926
10353
  // ───────────────────────────────────────────────────────────────
9927
10354
  // Next-ad scheduling (single flight)
@@ -9977,7 +10404,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9977
10404
  value: function getStopRequestingReason(remainingMs) {
9978
10405
  if (this.breakSource === "vmap") {
9979
10406
  if (this.adPodQueue.length === 0) return "vmap_queue_empty";
9980
- } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl) {
10407
+ } else if (this.breakSource !== "scte35" || !this.followUpBaseVastUrl && !this.canRequestEdge()) {
9981
10408
  return "no_follow_up_source";
9982
10409
  }
9983
10410
  if (this.timing.consecutiveFailures >= this.timing.maxConsecutiveFailures) {
@@ -9989,7 +10416,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
9989
10416
  if (this.timing.totalAdRequestsInBreak >= this.timing.maxTotalAdRequestsPerBreak) {
9990
10417
  return "request_cap";
9991
10418
  }
9992
- if (this.timing.isGamInCooldown()) {
10419
+ if (!this.canRequestEdge() && this.timing.isGamInCooldown()) {
9993
10420
  return "gam_cooldown";
9994
10421
  }
9995
10422
  if (this.isBreakTimeBounded() && remainingMs < this.getMinRemainingMsToRequestAd()) {
@@ -10083,10 +10510,33 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10083
10510
  key: "requestFollowUpAd",
10084
10511
  value: function requestFollowUpAd(trigger) {
10085
10512
  return _async_to_generator(function() {
10086
- var url, epoch, unused;
10513
+ var edgeUrl, epoch2, outcome, url, epoch, unused;
10087
10514
  return _ts_generator(this, function(_state) {
10088
10515
  switch(_state.label){
10089
10516
  case 0:
10517
+ if (!this.canRequestEdge()) return [
10518
+ 3,
10519
+ 2
10520
+ ];
10521
+ edgeUrl = this.prepareEdgePodRequest();
10522
+ if (!edgeUrl) {
10523
+ this.requestNextAd("".concat(trigger, ":edge_unavailable"));
10524
+ return [
10525
+ 2
10526
+ ];
10527
+ }
10528
+ epoch2 = this.timing.adBreakEpoch;
10529
+ return [
10530
+ 4,
10531
+ this.runEdgePod(edgeUrl, "edge_".concat(trigger))
10532
+ ];
10533
+ case 1:
10534
+ outcome = _state.sent();
10535
+ this.continueAfterEdgeOutcome(outcome, epoch2);
10536
+ return [
10537
+ 2
10538
+ ];
10539
+ case 2:
10090
10540
  if (this.breakSource === "vmap") {
10091
10541
  url = this.adPodQueue.shift();
10092
10542
  } else if (this.followUpBaseVastUrl) {
@@ -10097,32 +10547,32 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10097
10547
  ];
10098
10548
  this.noteVastUrlGenerated(ensureVideoAdType(url), trigger);
10099
10549
  epoch = this.timing.adBreakEpoch;
10100
- _state.label = 1;
10101
- case 1:
10550
+ _state.label = 3;
10551
+ case 3:
10102
10552
  _state.trys.push([
10103
- 1,
10104
10553
  3,
10554
+ 5,
10105
10555
  ,
10106
- 4
10556
+ 6
10107
10557
  ]);
10108
10558
  return [
10109
10559
  4,
10110
10560
  this.playSingleAd(url, trigger)
10111
10561
  ];
10112
- case 2:
10562
+ case 4:
10113
10563
  _state.sent();
10114
10564
  return [
10115
10565
  3,
10116
- 4
10566
+ 6
10117
10567
  ];
10118
- case 3:
10568
+ case 5:
10119
10569
  unused = _state.sent();
10120
10570
  this.continueAfterRequestFailure(epoch);
10121
10571
  return [
10122
10572
  3,
10123
- 4
10573
+ 6
10124
10574
  ];
10125
- case 4:
10575
+ case 6:
10126
10576
  return [
10127
10577
  2
10128
10578
  ];
@@ -10235,7 +10685,11 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10235
10685
  case "gam_http_request_sent":
10236
10686
  diag.gamRequestSent++;
10237
10687
  break;
10688
+ case "edge_http_request_sent":
10689
+ diag.edgeRequestSent++;
10690
+ break;
10238
10691
  case "gam_response_received":
10692
+ case "edge_response_received":
10239
10693
  diag.vastHttpStatus = payload.httpStatus;
10240
10694
  break;
10241
10695
  case "vast_parsed":
@@ -10422,6 +10876,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10422
10876
  {
10423
10877
  key: "beginFunnelRequest",
10424
10878
  value: function beginFunnelRequest(adPlayer, requestToken, trigger) {
10879
+ var source = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : "gam";
10425
10880
  var _adPlayer_setFunnelContext;
10426
10881
  if (this.controllerCallInFlight) {
10427
10882
  this.logOrchestratorFunnel("duplicate_request_detected", {
@@ -10436,7 +10891,9 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10436
10891
  requestId: requestToken,
10437
10892
  controllerId: this.controllerGeneration,
10438
10893
  trigger: trigger,
10894
+ source: source,
10439
10895
  gamRequestSent: 0,
10896
+ edgeRequestSent: 0,
10440
10897
  wrapperDepth: 0,
10441
10898
  inlineResolved: false,
10442
10899
  mediaCandidateCount: 0,
@@ -10567,6 +11024,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10567
11024
  this.timing.activeAdRequestToken = requestToken;
10568
11025
  ap = this.host.getAdPlayer();
10569
11026
  budgetedUrl = this.withCurrentPodBudget(vastTagUrl);
11027
+ this.currentPodSource = "gam";
10570
11028
  this.setSlotState("REQUESTING", trigger);
10571
11029
  this.beginFunnelRequest(ap, requestToken, trigger);
10572
11030
  requestUrl = this.applyOpportunityFloor(budgetedUrl);
@@ -10714,6 +11172,10 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10714
11172
  requests: requests,
10715
11173
  requestRetries: requestRetries,
10716
11174
  gamResponses: count("gam_response_received"),
11175
+ edgeRequests: count("edge_request_sent"),
11176
+ edgeFills: count("edge_fill"),
11177
+ edgeEmptySentinels: count("edge_empty_sentinel"),
11178
+ edgeFallbacks: countPrefix("edge_fallback:"),
10717
11179
  requestsSuppressed: count("request_suppressed"),
10718
11180
  duplicates: count("duplicate_request_detected") + count("duplicate_break_start"),
10719
11181
  filled: count("vast_received"),
@@ -10810,6 +11272,7 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
10810
11272
  this.breakSource = void 0;
10811
11273
  this.followUpBaseVastUrl = void 0;
10812
11274
  this.unusableResponsesInBreak = 0;
11275
+ this.resetEdgeStateForBreak();
10813
11276
  adPlayer.stop().catch(function() {});
10814
11277
  if (!alreadyEnded) this.logBreakFunnel(reason);
10815
11278
  this.opportunityFloor = void 0;
@@ -11123,7 +11586,13 @@ var AdBreakOrchestrator = /*#__PURE__*/ function() {
11123
11586
  });
11124
11587
  return;
11125
11588
  }
11126
- this.timing.consecutiveFailures++;
11589
+ if (this.currentPodSource === "edge") {
11590
+ this.markEdgeExhausted("edge_playback_failed", {
11591
+ reason: reason
11592
+ });
11593
+ } else {
11594
+ this.timing.consecutiveFailures++;
11595
+ }
11127
11596
  var diag = this.currentRequestId != null ? this.requestDiagnostics.get(this.currentRequestId) : void 0;
11128
11597
  if (diag && !diag.failureReason) diag.failureReason = reason;
11129
11598
  if (this.debug) {
@@ -11153,7 +11622,9 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11153
11622
  if (this.config.adFloorMaxCents != null) floorBounds.maxCents = this.config.adFloorMaxCents;
11154
11623
  if (this.config.adFloorStepCents != null) floorBounds.stepCents = this.config.adFloorStepCents;
11155
11624
  this.floorLadder = new FloorLadderController(this.config.adFloorLadder, floorBounds);
11625
+ this.floorLadderEnabled = this.config.adFloorLadderEnabled === true;
11156
11626
  this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11627
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11157
11628
  this.adPlayer = this.createAdPlayer(false);
11158
11629
  this.palNonce = createPalNonceManager();
11159
11630
  this.cueManager = new Scte35CueManager(!!this.config.debugAdTiming, {
@@ -11253,11 +11724,14 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11253
11724
  generatePodVastUrl: function generatePodVastUrl(base, breakDurationMs) {
11254
11725
  return _this.generatePodVastUrl(base, breakDurationMs);
11255
11726
  },
11727
+ getEdgePodBaseUrl: function getEdgePodBaseUrl() {
11728
+ return _this.edgePodBaseUrl;
11729
+ },
11256
11730
  getFloorSnapshot: function getFloorSnapshot(position) {
11257
- return _this.floorLadder.snapshotFor({
11731
+ return _this.floorLadderEnabled ? _this.floorLadder.snapshotFor({
11258
11732
  position: position,
11259
11733
  isCtv: _this.isCtvEnvironment()
11260
- });
11734
+ }) : void 0;
11261
11735
  },
11262
11736
  onAdClick: function onAdClick() {
11263
11737
  return _this.palNonce.sendAdClick();
@@ -11308,11 +11782,43 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
11308
11782
  return report;
11309
11783
  }
11310
11784
  },
11785
+ {
11786
+ key: "setAdFloorLadderEnabled",
11787
+ value: function setAdFloorLadderEnabled(enabled) {
11788
+ this.floorLadderEnabled = enabled;
11789
+ if (enabled) this.warnOnInvalidFloorLadder(this.floorLadder.validate());
11790
+ }
11791
+ },
11792
+ {
11793
+ key: "isAdFloorLadderEnabled",
11794
+ value: function isAdFloorLadderEnabled() {
11795
+ return this.floorLadderEnabled;
11796
+ }
11797
+ },
11798
+ {
11799
+ key: "setEdgePodRequest",
11800
+ value: function setEdgePodRequest(update) {
11801
+ if (update.enableEdgePodRequest === this.config.enableEdgePodRequest && update.edgePodUrl === this.config.edgePodUrl) {
11802
+ return;
11803
+ }
11804
+ if (update.enableEdgePodRequest === void 0) delete this.config.enableEdgePodRequest;
11805
+ else this.config.enableEdgePodRequest = update.enableEdgePodRequest;
11806
+ if (update.edgePodUrl === void 0) delete this.config.edgePodUrl;
11807
+ else this.config.edgePodUrl = update.edgePodUrl;
11808
+ this.edgePodBaseUrl = resolveEdgePodBaseUrl(this.config);
11809
+ }
11810
+ },
11811
+ {
11812
+ key: "getEdgePodUrl",
11813
+ value: function getEdgePodUrl() {
11814
+ return this.edgePodBaseUrl;
11815
+ }
11816
+ },
11311
11817
  {
11312
11818
  key: "warnOnInvalidFloorLadder",
11313
11819
  value: function warnOnInvalidFloorLadder(report) {
11314
11820
  var config = this.floorLadder.get();
11315
- if (report.ok || !config.enabled) return;
11821
+ if (report.ok || !this.floorLadderEnabled || !config.enabled) return;
11316
11822
  console.warn("[AdBreak][FLOOR] configuration has issues; invalid floors will not be sent", {
11317
11823
  errors: report.errors,
11318
11824
  configurationVersion: config.configurationVersion,
@@ -12045,6 +12551,7 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
12045
12551
  this.timing.destroy();
12046
12552
  this.adBreak.clearPendingAdBreak();
12047
12553
  this.placeholder.destroy();
12554
+ 0;
12048
12555
  if (this.timeUpdateHandler) {
12049
12556
  this.video.removeEventListener("timeupdate", this.timeUpdateHandler);
12050
12557
  delete this.timeUpdateHandler;