stormcloud-video-player 0.8.25 → 0.8.27

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.
Files changed (48) hide show
  1. package/dist/stormcloud-vp.min.js +1 -1
  2. package/lib/index.cjs +714 -128
  3. package/lib/index.cjs.map +1 -1
  4. package/lib/index.d.cts +7 -0
  5. package/lib/index.d.ts +7 -0
  6. package/lib/index.js +714 -128
  7. package/lib/index.js.map +1 -1
  8. package/lib/player/AdBreakOrchestrator.cjs +278 -49
  9. package/lib/player/AdBreakOrchestrator.cjs.map +1 -1
  10. package/lib/player/AdBreakOrchestrator.d.cts +4 -1
  11. package/lib/player/AdConfigManager.cjs +144 -14
  12. package/lib/player/AdConfigManager.cjs.map +1 -1
  13. package/lib/player/AdConfigManager.d.cts +12 -1
  14. package/lib/player/AdPreloadPool.d.cts +1 -1
  15. package/lib/player/AdTimingService.cjs +7 -0
  16. package/lib/player/AdTimingService.cjs.map +1 -1
  17. package/lib/player/AdTimingService.d.cts +2 -1
  18. package/lib/player/HlsEngine.d.cts +1 -1
  19. package/lib/player/PlayerControls.d.cts +1 -1
  20. package/lib/player/Scte35CueManager.d.cts +1 -1
  21. package/lib/player/Scte35Parser.d.cts +1 -1
  22. package/lib/player/StormcloudVideoPlayer.cjs +714 -128
  23. package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
  24. package/lib/player/StormcloudVideoPlayer.d.cts +2 -1
  25. package/lib/player/playerTypes.d.cts +1 -1
  26. package/lib/players/HlsPlayer.cjs +714 -128
  27. package/lib/players/HlsPlayer.cjs.map +1 -1
  28. package/lib/players/HlsPlayer.d.cts +1 -1
  29. package/lib/players/index.cjs +714 -128
  30. package/lib/players/index.cjs.map +1 -1
  31. package/lib/sdk/hlsAdPlayer.cjs +343 -71
  32. package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
  33. package/lib/sdk/hlsAdPlayer.d.cts +1 -1
  34. package/lib/{types-Bz4aRmzc.d.cts → types-Xgz2_W1C.d.cts} +6 -0
  35. package/lib/ui/StormcloudVideoPlayer.cjs +714 -128
  36. package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
  37. package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
  38. package/lib/utils/ctvVastSignals.cjs +80 -8
  39. package/lib/utils/ctvVastSignals.cjs.map +1 -1
  40. package/lib/utils/tracking.cjs +80 -8
  41. package/lib/utils/tracking.cjs.map +1 -1
  42. package/lib/utils/tracking.d.cts +1 -1
  43. package/lib/utils/vastEnvironmentSignals.cjs +83 -13
  44. package/lib/utils/vastEnvironmentSignals.cjs.map +1 -1
  45. package/lib/utils/vastMacros.cjs +15 -1
  46. package/lib/utils/vastMacros.cjs.map +1 -1
  47. package/lib/utils/vastMacros.d.cts +3 -0
  48. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { b as AdBreak, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-Bz4aRmzc.cjs';
1
+ import { b as AdBreak, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-Xgz2_W1C.cjs';
2
2
  import { PalNonceManager } from '../sdk/pal.cjs';
3
3
  import { VastConsentSignals } from '../utils/vastMacros.cjs';
4
4
 
@@ -30,6 +30,17 @@ declare class AdConfigManager {
30
30
  selectVastTagsForBreak(b?: AdBreak): string[] | undefined;
31
31
  beginNewAdPod(): void;
32
32
  generateVastUrlsWithCorrelators(baseUrl: string, count: number, adPlayer: AdController, palNonce: PalNonceManager, inAdBreak: boolean): string[];
33
+ private readonly defaultPodMaxAds;
34
+ resolvePodParams(breakDurationMs?: number): {
35
+ maxAds: number;
36
+ minDurationMs: number;
37
+ maxDurationMs: number | undefined;
38
+ };
39
+ generatePodVastUrl(baseUrl: string, podParams: {
40
+ maxAds: number;
41
+ minDurationMs: number;
42
+ maxDurationMs: number | undefined;
43
+ }, adPlayer: AdController, palNonce: PalNonceManager, inAdBreak: boolean): string;
33
44
  initializeTracking(): void;
34
45
  private sendHeartbeatIfNeeded;
35
46
  destroyTracking(): void;
@@ -1,4 +1,4 @@
1
- import { A as AdController, S as Scte35Marker } from '../types-Bz4aRmzc.cjs';
1
+ import { A as AdController, S as Scte35Marker } from '../types-Xgz2_W1C.cjs';
2
2
  import { AdTimingService } from './AdTimingService.cjs';
3
3
 
4
4
  interface AdPreloadPoolDeps {
@@ -536,6 +536,13 @@ var AdTimingService = /*#__PURE__*/ function() {
536
536
  this.lastAdRequestTime = Date.now();
537
537
  }
538
538
  },
539
+ {
540
+ key: "notePodAdStarted",
541
+ value: function notePodAdStarted() {
542
+ this.currentAdIndex++;
543
+ this.totalAdsInBreak = this.currentAdIndex;
544
+ }
545
+ },
539
546
  {
540
547
  key: "getRemainingAdMs",
541
548
  value: function getRemainingAdMs() {
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/ubuntu24-new/Dev/stormcloud-vp/lib/player/AdTimingService.cjs","../../src/player/AdTimingService.ts"],"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","AdTimingService_exports","AdTimingService","module","exports","config","video","callbacks","adRequestWatchdogToken","adFailsafeToken","adBreakPlayedDurationMs","adBreakEpoch","adRequestTokenCounter","activeAdRequestToken","totalAdsInBreak","totalAdRequestsInBreak","maxTotalAdRequestsPerBreak","lastAdRequestTime","consecutiveFailures","maxConsecutiveFailures","minAdRequestIntervalMs","maxBackoffMs","defaultMinRemainingMsToStartAd","failedVastUrls","Set","temporaryFailureUrls","Map","temporaryFailureCooldownMs","globalConsecutiveNoFills","globalNoFillThreshold","globalNoFillBackoffBaseMs","globalNoFillCooldownUntil","globalNoFillBackoffMaxMs","globalLastGamRequestTime","globalMinGamIntervalMs","debug","debugAdTiming","scheduleAdStopCountdown","remainingMs","clearAdStopTimer","ms","Math","max","floor","ensureAdStoppedByTimer","adStopTimerId","window","setTimeout","clearTimeout","onAdStopTimerFired","scheduleAdStartIn","delayMs","marker","type","cueKey","clearAdStartTimer","onAdStartTimerFired","adStartTimerId","updatePtsDrift","ptsSecondsSample","sampleMs","currentTime","Number","isFinite","abs","alpha","resolveScteBreakStartWallClockMs","nowWallClockMs","Date","now","ptsSeconds","nowMs","estimatedCurrentPtsMs","ptsDriftEmaMs","deltaMs","setAdBreakDurationBoundary","durationMs","expectedAdBreakDurationMs","currentAdBreakStartWallClockMs","scteAdBreakEndWallClockMs"],"mappings":"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","sourcesContent":["\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/player/AdTimingService.ts\nvar AdTimingService_exports = {};\n__export(AdTimingService_exports, {\n AdTimingService: () => AdTimingService\n});\nmodule.exports = __toCommonJS(AdTimingService_exports);\nvar AdTimingService = class {\n constructor(config, video, callbacks) {\n this.adRequestWatchdogToken = null;\n this.adFailsafeToken = null;\n this.ptsDriftEmaMs = 0;\n this.adBreakPlayedDurationMs = 0;\n this.adBreakEpoch = 0;\n this.adRequestTokenCounter = 0;\n this.activeAdRequestToken = null;\n this.currentAdIndex = 0;\n this.totalAdsInBreak = 0;\n this.totalAdRequestsInBreak = 0;\n this.maxTotalAdRequestsPerBreak = 10;\n this.lastAdRequestTime = 0;\n this.consecutiveFailures = 0;\n this.maxConsecutiveFailures = 5;\n this.minAdRequestIntervalMs = 3e3;\n this.backoffBaseMs = 1e3;\n this.maxBackoffMs = 15e3;\n this.defaultMinRemainingMsToStartAd = 4e3;\n this.failedVastUrls = /* @__PURE__ */ new Set();\n this.temporaryFailureUrls = /* @__PURE__ */ new Map();\n this.temporaryFailureCooldownMs = 3e4;\n this.globalConsecutiveNoFills = 0;\n this.globalNoFillThreshold = 3;\n this.globalNoFillCooldownUntil = 0;\n this.globalNoFillBackoffBaseMs = 5e3;\n this.globalNoFillBackoffMaxMs = 6e4;\n this.globalLastGamRequestTime = 0;\n this.globalMinGamIntervalMs = 3e3;\n this.config = config;\n this.video = video;\n this.callbacks = callbacks;\n }\n get debug() {\n return !!this.config.debugAdTiming;\n }\n // --- Ad stop timer ---\n scheduleAdStopCountdown(remainingMs) {\n this.clearAdStopTimer();\n const ms = Math.max(0, Math.floor(remainingMs));\n if (ms === 0) {\n this.ensureAdStoppedByTimer();\n return;\n }\n this.adStopTimerId = window.setTimeout(() => {\n this.ensureAdStoppedByTimer();\n }, ms);\n }\n clearAdStopTimer() {\n if (this.adStopTimerId != null) {\n clearTimeout(this.adStopTimerId);\n this.adStopTimerId = void 0;\n }\n }\n ensureAdStoppedByTimer() {\n this.adStopTimerId = void 0;\n this.callbacks.onAdStopTimerFired();\n }\n // --- Ad start timer ---\n scheduleAdStartIn(delayMs, marker = { type: \"start\" }, cueKey) {\n this.clearAdStartTimer();\n const ms = Math.max(0, Math.floor(delayMs));\n if (ms === 0) {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n return;\n }\n this.adStartTimerId = window.setTimeout(() => {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n }, ms);\n }\n clearAdStartTimer() {\n if (this.adStartTimerId != null) {\n clearTimeout(this.adStartTimerId);\n this.adStartTimerId = void 0;\n }\n }\n // --- PTS drift ---\n updatePtsDrift(ptsSecondsSample) {\n const sampleMs = (this.video.currentTime - ptsSecondsSample) * 1e3;\n if (!Number.isFinite(sampleMs) || Math.abs(sampleMs) > 6e4) return;\n const alpha = 0.1;\n this.ptsDriftEmaMs = this.ptsDriftEmaMs * (1 - alpha) + sampleMs * alpha;\n }\n resolveScteBreakStartWallClockMs(marker) {\n const nowWallClockMs = Date.now();\n if (typeof marker.ptsSeconds !== \"number\") {\n return nowWallClockMs;\n }\n const nowMs = this.video.currentTime * 1e3;\n const estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;\n const deltaMs = marker.ptsSeconds * 1e3 - estimatedCurrentPtsMs;\n if (!Number.isFinite(deltaMs)) {\n return nowWallClockMs;\n }\n return nowWallClockMs + Math.floor(deltaMs);\n }\n // --- Duration boundary ---\n setAdBreakDurationBoundary(durationMs) {\n this.expectedAdBreakDurationMs = durationMs;\n if (durationMs != null && this.currentAdBreakStartWallClockMs != null) {\n this.scteAdBreakEndWallClockMs = this.currentAdBreakStartWallClockMs + durationMs;\n } else {\n this.scteAdBreakEndWallClockMs = void 0;\n }\n }\n scheduleAdStopAtBreakBoundary() {\n if (this.expectedAdBreakDurationMs == null) {\n return;\n }\n this.scheduleAdStopCountdown(this.getWallClockRemainingAdMs());\n }\n // --- Duration budget ---\n getWallClockRemainingAdMs() {\n const endWallClockMs = this.getAdBreakEndWallClockMs();\n if (endWallClockMs != null) {\n return Math.max(0, endWallClockMs - Date.now());\n }\n return 0;\n }\n getAdBreakEndWallClockMs() {\n if (this.scteAdBreakEndWallClockMs != null) {\n return this.scteAdBreakEndWallClockMs;\n }\n if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {\n return void 0;\n }\n return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;\n }\n getDurationBudgetRemainingMs() {\n if (this.expectedAdBreakDurationMs == null) {\n return 0;\n }\n return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);\n }\n getLoadedAdDurationMs(adController) {\n const durationSec = adController.getLoadedAdDuration?.();\n if (durationSec == null || durationSec <= 0) {\n return 0;\n }\n return Math.round(durationSec * 1e3);\n }\n adFitsRemainingBudget(durationMs) {\n if (durationMs <= 0) {\n return true;\n }\n return durationMs <= this.getDurationBudgetRemainingMs();\n }\n logAdDurationDecision(accepted, durationMs, adController) {\n if (!this.debug) {\n return;\n }\n const remainingBudget = this.getDurationBudgetRemainingMs();\n const adId = adController.getLoadedAdId?.() ?? \"unknown\";\n if (accepted) {\n console.log(\n `[AD-DURATION] Accepted ad id=${adId}, duration=${durationMs}ms, budget remaining after play=${Math.max(0, remainingBudget - durationMs)}ms`\n );\n } else {\n console.log(\n `[AD-DURATION] Rejected ad id=${adId}, duration=${durationMs}ms exceeds remaining budget=${remainingBudget}ms \\u2014 requesting another ad`\n );\n }\n }\n recordPlayedAdDuration(adController) {\n const durationMs = this.getLoadedAdDurationMs(adController);\n if (durationMs <= 0) {\n return;\n }\n this.adBreakPlayedDurationMs += durationMs;\n if (this.debug) {\n console.log(\n `[AD-DURATION] Played ${durationMs}ms \\u2014 total played=${this.adBreakPlayedDurationMs}ms / ${this.expectedAdBreakDurationMs ?? \"?\"}ms`\n );\n }\n }\n ensureLoadedAdFitsBudget(adController) {\n const durationMs = this.getLoadedAdDurationMs(adController);\n const fits = this.adFitsRemainingBudget(durationMs);\n this.logAdDurationDecision(fits, durationMs, adController);\n return fits;\n }\n rejectLoadedAdForDuration(adController) {\n try {\n adController.destroy();\n } catch {\n }\n }\n markAdStarted() {\n this.currentAdIndex++;\n this.totalAdsInBreak = this.currentAdIndex;\n this.totalAdRequestsInBreak++;\n this.lastAdRequestTime = Date.now();\n }\n getRemainingAdMs() {\n const wallClockRemaining = this.getWallClockRemainingAdMs();\n const budgetRemaining = this.getDurationBudgetRemainingMs();\n if (this.expectedAdBreakDurationMs == null) {\n return wallClockRemaining;\n }\n return Math.min(wallClockRemaining, budgetRemaining);\n }\n getMinRemainingMsToStartAd() {\n const configured = this.config.minRemainingMsToStartAd;\n if (typeof configured === \"number\" && configured >= 0) {\n return configured;\n }\n return this.defaultMinRemainingMsToStartAd;\n }\n hasTimeToStartAnotherAd() {\n const minRemaining = this.getMinRemainingMsToStartAd();\n return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;\n }\n // --- Circuit breaker ---\n isTemporaryAdError(error) {\n const message = error?.message?.toLowerCase() || \"\";\n if (message.includes(\"no ads available\") || message.includes(\"no fill\") || message.includes(\"nofill\")) {\n return true;\n }\n if (message.includes(\"timeout\")) {\n return true;\n }\n if (message.includes(\"rate limit\") || message.includes(\"too many requests\")) {\n return true;\n }\n if (message.includes(\"ad playback error\")) {\n return true;\n }\n return false;\n }\n isUrlInCooldown(vastTagUrl) {\n const failureTime = this.temporaryFailureUrls.get(vastTagUrl);\n if (failureTime == null) {\n return false;\n }\n const elapsed = Date.now() - failureTime;\n if (elapsed >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(vastTagUrl);\n return false;\n }\n return true;\n }\n cleanupTemporaryFailures() {\n const now = Date.now();\n for (const [url, timestamp] of this.temporaryFailureUrls) {\n if (now - timestamp >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(url);\n }\n }\n }\n isGamInCooldown() {\n return Date.now() < this.globalNoFillCooldownUntil;\n }\n recordGamNoFill() {\n this.globalConsecutiveNoFills++;\n if (this.globalConsecutiveNoFills >= this.globalNoFillThreshold) {\n const exponent = this.globalConsecutiveNoFills - this.globalNoFillThreshold;\n const backoff = Math.min(\n this.globalNoFillBackoffBaseMs * Math.pow(2, exponent),\n this.globalNoFillBackoffMaxMs\n );\n this.globalNoFillCooldownUntil = Date.now() + backoff;\n if (this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] GAM cooldown activated: ${backoff}ms (${this.globalConsecutiveNoFills} consecutive no-fills)`\n );\n }\n }\n }\n resetGamNoFillCounter() {\n if (this.globalConsecutiveNoFills > 0 && this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] Resetting no-fill counter (was ${this.globalConsecutiveNoFills})`\n );\n }\n this.globalConsecutiveNoFills = 0;\n this.globalNoFillCooldownUntil = 0;\n }\n async enforceGlobalRateLimit() {\n const now = Date.now();\n const elapsed = now - this.globalLastGamRequestTime;\n if (elapsed < this.globalMinGamIntervalMs) {\n const waitMs = this.globalMinGamIntervalMs - elapsed;\n if (this.debug) {\n console.log(`[RATE-LIMIT] Waiting ${waitMs}ms before next GAM request`);\n }\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n this.globalLastGamRequestTime = Date.now();\n }\n // --- Watchdog ---\n startAdRequestWatchdog(token) {\n this.clearAdRequestWatchdog();\n const timeoutMs = this.config.adFailsafeTimeoutMs ?? 1e4;\n this.adRequestWatchdogToken = token;\n this.adRequestWatchdogId = window.setTimeout(() => {\n if (this.adRequestWatchdogToken !== token) {\n return;\n }\n this.adRequestWatchdogId = void 0;\n this.adRequestWatchdogToken = null;\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n this.logAdState(\"ad_request_timeout\", { token, timeoutMs });\n this.callbacks.onWatchdogTimeout();\n }, timeoutMs);\n this.logAdState(\"ad_request_watchdog_started\", { token, timeoutMs });\n }\n clearAdRequestWatchdog() {\n if (this.adRequestWatchdogId != null) {\n clearTimeout(this.adRequestWatchdogId);\n this.adRequestWatchdogId = void 0;\n }\n if (this.adRequestWatchdogToken != null) {\n this.logAdState(\"ad_request_watchdog_cleared\", {\n token: this.adRequestWatchdogToken\n });\n this.adRequestWatchdogToken = null;\n }\n }\n // --- Failsafe ---\n startAdFailsafeTimer(token) {\n this.clearAdFailsafeTimer();\n const failsafeMs = this.config.adFailsafeTimeoutMs ?? 1e4;\n this.adFailsafeToken = token;\n this.adFailsafeTimerId = window.setTimeout(() => {\n if (this.adFailsafeToken !== token) {\n return;\n }\n this.adFailsafeTimerId = void 0;\n this.adFailsafeToken = null;\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n this.logAdState(\"ad_failsafe_triggered\", {\n token,\n failsafeMs,\n videoPaused: this.video.paused\n });\n this.callbacks.onFailsafeTimeout();\n }, failsafeMs);\n this.logAdState(\"ad_failsafe_started\", { token, failsafeMs });\n }\n clearAdFailsafeTimer() {\n if (this.adFailsafeTimerId != null) {\n clearTimeout(this.adFailsafeTimerId);\n this.logAdState(\"ad_failsafe_cleared\", { token: this.adFailsafeToken });\n this.adFailsafeTimerId = void 0;\n }\n this.adFailsafeToken = null;\n }\n logAdState(event, extra = {}, adPlayer, inAdBreak, showAds) {\n if (!this.debug) {\n return;\n }\n console.log(\"[StormcloudVideoPlayer][AdState]\", {\n event,\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n showAds: showAds ?? false,\n adPlaying: adPlayer?.isAdPlaying() ?? false,\n inAdBreak: inAdBreak ?? false,\n activeAdRequestToken: this.activeAdRequestToken,\n ...extra\n });\n }\n // --- Reset for ad break end ---\n resetForBreakEnd() {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdRequestWatchdog();\n this.clearAdFailsafeTimer();\n this.activeAdRequestToken = null;\n this.expectedAdBreakDurationMs = void 0;\n this.currentAdBreakStartWallClockMs = void 0;\n this.scteAdBreakEndWallClockMs = void 0;\n this.adBreakPlayedDurationMs = 0;\n this.currentAdIndex = 0;\n this.totalAdsInBreak = 0;\n this.consecutiveFailures = 0;\n this.globalConsecutiveNoFills = 0;\n this.adBreakEpoch++;\n }\n destroy() {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdFailsafeTimer();\n this.clearAdRequestWatchdog();\n }\n};\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n AdTimingService\n});\n","import type { AdController, StormcloudVideoPlayerConfig, Scte35Marker } from \"../types\";\n\nexport interface AdTimingCallbacks {\n onAdStopTimerFired(): void;\n onAdStartTimerFired(marker: Scte35Marker, cueKey: string | undefined): void;\n onWatchdogTimeout(): void;\n onFailsafeTimeout(): void;\n}\n\nexport class AdTimingService {\n private adStopTimerId: number | undefined;\n private adStartTimerId: number | undefined;\n private adFailsafeTimerId: number | undefined;\n private adRequestWatchdogId: number | undefined;\n private adRequestWatchdogToken: number | null = null;\n private adFailsafeToken: number | null = null;\n\n ptsDriftEmaMs = 0;\n expectedAdBreakDurationMs: number | undefined;\n currentAdBreakStartWallClockMs: number | undefined;\n scteAdBreakEndWallClockMs: number | undefined;\n adBreakPlayedDurationMs: number = 0;\n adBreakEpoch: number = 0;\n\n adRequestTokenCounter = 0;\n activeAdRequestToken: number | null = null;\n currentAdIndex: number = 0;\n totalAdsInBreak: number = 0;\n totalAdRequestsInBreak: number = 0;\n readonly maxTotalAdRequestsPerBreak: number = 10;\n lastAdRequestTime: number = 0;\n consecutiveFailures: number = 0;\n readonly maxConsecutiveFailures: number = 5;\n readonly minAdRequestIntervalMs: number = 3000;\n readonly backoffBaseMs: number = 1000;\n readonly maxBackoffMs: number = 15000;\n private readonly defaultMinRemainingMsToStartAd: number = 4000;\n\n failedVastUrls: Set<string> = new Set();\n temporaryFailureUrls: Map<string, number> = new Map();\n private readonly temporaryFailureCooldownMs: number = 30000;\n\n private globalConsecutiveNoFills: number = 0;\n private readonly globalNoFillThreshold: number = 3;\n private globalNoFillCooldownUntil: number = 0;\n private readonly globalNoFillBackoffBaseMs: number = 5000;\n private readonly globalNoFillBackoffMaxMs: number = 60000;\n private globalLastGamRequestTime: number = 0;\n private readonly globalMinGamIntervalMs: number = 3000;\n\n private config: StormcloudVideoPlayerConfig;\n private video: HTMLVideoElement;\n private callbacks: AdTimingCallbacks;\n\n constructor(\n config: StormcloudVideoPlayerConfig,\n video: HTMLVideoElement,\n callbacks: AdTimingCallbacks\n ) {\n this.config = config;\n this.video = video;\n this.callbacks = callbacks;\n }\n\n get debug(): boolean {\n return !!this.config.debugAdTiming;\n }\n\n // --- Ad stop timer ---\n\n scheduleAdStopCountdown(remainingMs: number): void {\n this.clearAdStopTimer();\n const ms = Math.max(0, Math.floor(remainingMs));\n if (ms === 0) {\n this.ensureAdStoppedByTimer();\n return;\n }\n this.adStopTimerId = window.setTimeout(() => {\n this.ensureAdStoppedByTimer();\n }, ms) as unknown as number;\n }\n\n clearAdStopTimer(): void {\n if (this.adStopTimerId != null) {\n clearTimeout(this.adStopTimerId);\n this.adStopTimerId = undefined;\n }\n }\n\n private ensureAdStoppedByTimer(): void {\n this.adStopTimerId = undefined;\n this.callbacks.onAdStopTimerFired();\n }\n\n // --- Ad start timer ---\n\n scheduleAdStartIn(\n delayMs: number,\n marker: Scte35Marker = { type: \"start\" } as Scte35Marker,\n cueKey?: string\n ): void {\n this.clearAdStartTimer();\n const ms = Math.max(0, Math.floor(delayMs));\n if (ms === 0) {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n return;\n }\n this.adStartTimerId = window.setTimeout(() => {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n }, ms) as unknown as number;\n }\n\n clearAdStartTimer(): void {\n if (this.adStartTimerId != null) {\n clearTimeout(this.adStartTimerId);\n this.adStartTimerId = undefined;\n }\n }\n\n // --- PTS drift ---\n\n updatePtsDrift(ptsSecondsSample: number): void {\n const sampleMs = (this.video.currentTime - ptsSecondsSample) * 1000;\n if (!Number.isFinite(sampleMs) || Math.abs(sampleMs) > 60000) return;\n const alpha = 0.1;\n this.ptsDriftEmaMs = this.ptsDriftEmaMs * (1 - alpha) + sampleMs * alpha;\n }\n\n resolveScteBreakStartWallClockMs(marker: Scte35Marker): number {\n const nowWallClockMs = Date.now();\n if (typeof marker.ptsSeconds !== \"number\") {\n return nowWallClockMs;\n }\n\n const nowMs = this.video.currentTime * 1000;\n const estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;\n const deltaMs = marker.ptsSeconds * 1000 - estimatedCurrentPtsMs;\n if (!Number.isFinite(deltaMs)) {\n return nowWallClockMs;\n }\n\n return nowWallClockMs + Math.floor(deltaMs);\n }\n\n // --- Duration boundary ---\n\n setAdBreakDurationBoundary(durationMs: number | undefined): void {\n this.expectedAdBreakDurationMs = durationMs;\n if (\n durationMs != null &&\n this.currentAdBreakStartWallClockMs != null\n ) {\n this.scteAdBreakEndWallClockMs =\n this.currentAdBreakStartWallClockMs + durationMs;\n } else {\n this.scteAdBreakEndWallClockMs = undefined;\n }\n }\n\n scheduleAdStopAtBreakBoundary(): void {\n if (this.expectedAdBreakDurationMs == null) {\n return;\n }\n this.scheduleAdStopCountdown(this.getWallClockRemainingAdMs());\n }\n\n // --- Duration budget ---\n\n getWallClockRemainingAdMs(): number {\n const endWallClockMs = this.getAdBreakEndWallClockMs();\n if (endWallClockMs != null) {\n return Math.max(0, endWallClockMs - Date.now());\n }\n return 0;\n }\n\n getAdBreakEndWallClockMs(): number | undefined {\n if (this.scteAdBreakEndWallClockMs != null) {\n return this.scteAdBreakEndWallClockMs;\n }\n if (\n this.expectedAdBreakDurationMs == null ||\n this.currentAdBreakStartWallClockMs == null\n ) {\n return undefined;\n }\n return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;\n }\n\n getDurationBudgetRemainingMs(): number {\n if (this.expectedAdBreakDurationMs == null) {\n return 0;\n }\n return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);\n }\n\n getLoadedAdDurationMs(adController: AdController): number {\n const durationSec = adController.getLoadedAdDuration?.();\n if (durationSec == null || durationSec <= 0) {\n return 0;\n }\n return Math.round(durationSec * 1000);\n }\n\n adFitsRemainingBudget(durationMs: number): boolean {\n if (durationMs <= 0) {\n return true;\n }\n return durationMs <= this.getDurationBudgetRemainingMs();\n }\n\n logAdDurationDecision(\n accepted: boolean,\n durationMs: number,\n adController: AdController\n ): void {\n if (!this.debug) {\n return;\n }\n const remainingBudget = this.getDurationBudgetRemainingMs();\n const adId = adController.getLoadedAdId?.() ?? \"unknown\";\n if (accepted) {\n console.log(\n `[AD-DURATION] Accepted ad id=${adId}, duration=${durationMs}ms, budget remaining after play=${Math.max(0, remainingBudget - durationMs)}ms`\n );\n } else {\n console.log(\n `[AD-DURATION] Rejected ad id=${adId}, duration=${durationMs}ms exceeds remaining budget=${remainingBudget}ms — requesting another ad`\n );\n }\n }\n\n recordPlayedAdDuration(adController: AdController): void {\n const durationMs = this.getLoadedAdDurationMs(adController);\n if (durationMs <= 0) {\n return;\n }\n this.adBreakPlayedDurationMs += durationMs;\n if (this.debug) {\n console.log(\n `[AD-DURATION] Played ${durationMs}ms — total played=${this.adBreakPlayedDurationMs}ms / ${this.expectedAdBreakDurationMs ?? \"?\"}ms`\n );\n }\n }\n\n ensureLoadedAdFitsBudget(adController: AdController): boolean {\n const durationMs = this.getLoadedAdDurationMs(adController);\n const fits = this.adFitsRemainingBudget(durationMs);\n this.logAdDurationDecision(fits, durationMs, adController);\n return fits;\n }\n\n rejectLoadedAdForDuration(adController: AdController): void {\n try {\n adController.destroy();\n } catch {}\n }\n\n markAdStarted(): void {\n this.currentAdIndex++;\n this.totalAdsInBreak = this.currentAdIndex;\n this.totalAdRequestsInBreak++;\n this.lastAdRequestTime = Date.now();\n }\n\n getRemainingAdMs(): number {\n const wallClockRemaining = this.getWallClockRemainingAdMs();\n const budgetRemaining = this.getDurationBudgetRemainingMs();\n if (this.expectedAdBreakDurationMs == null) {\n return wallClockRemaining;\n }\n return Math.min(wallClockRemaining, budgetRemaining);\n }\n\n getMinRemainingMsToStartAd(): number {\n const configured = this.config.minRemainingMsToStartAd;\n if (typeof configured === \"number\" && configured >= 0) {\n return configured;\n }\n return this.defaultMinRemainingMsToStartAd;\n }\n\n hasTimeToStartAnotherAd(): boolean {\n const minRemaining = this.getMinRemainingMsToStartAd();\n return (\n this.getWallClockRemainingAdMs() > minRemaining &&\n this.getDurationBudgetRemainingMs() > minRemaining\n );\n }\n\n // --- Circuit breaker ---\n\n isTemporaryAdError(error: Error): boolean {\n const message = error?.message?.toLowerCase() || \"\";\n\n if (message.includes(\"no ads available\") || message.includes(\"no fill\") || message.includes(\"nofill\")) {\n return true;\n }\n if (message.includes(\"timeout\")) {\n return true;\n }\n if (message.includes(\"rate limit\") || message.includes(\"too many requests\")) {\n return true;\n }\n if (message.includes(\"ad playback error\")) {\n return true;\n }\n return false;\n }\n\n isUrlInCooldown(vastTagUrl: string): boolean {\n const failureTime = this.temporaryFailureUrls.get(vastTagUrl);\n if (failureTime == null) {\n return false;\n }\n\n const elapsed = Date.now() - failureTime;\n if (elapsed >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(vastTagUrl);\n return false;\n }\n\n return true;\n }\n\n cleanupTemporaryFailures(): void {\n const now = Date.now();\n for (const [url, timestamp] of this.temporaryFailureUrls) {\n if (now - timestamp >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(url);\n }\n }\n }\n\n isGamInCooldown(): boolean {\n return Date.now() < this.globalNoFillCooldownUntil;\n }\n\n recordGamNoFill(): void {\n this.globalConsecutiveNoFills++;\n\n if (this.globalConsecutiveNoFills >= this.globalNoFillThreshold) {\n const exponent = this.globalConsecutiveNoFills - this.globalNoFillThreshold;\n const backoff = Math.min(\n this.globalNoFillBackoffBaseMs * Math.pow(2, exponent),\n this.globalNoFillBackoffMaxMs\n );\n this.globalNoFillCooldownUntil = Date.now() + backoff;\n\n if (this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] GAM cooldown activated: ${backoff}ms (${this.globalConsecutiveNoFills} consecutive no-fills)`\n );\n }\n }\n }\n\n resetGamNoFillCounter(): void {\n if (this.globalConsecutiveNoFills > 0 && this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] Resetting no-fill counter (was ${this.globalConsecutiveNoFills})`\n );\n }\n this.globalConsecutiveNoFills = 0;\n this.globalNoFillCooldownUntil = 0;\n }\n\n async enforceGlobalRateLimit(): Promise<void> {\n const now = Date.now();\n const elapsed = now - this.globalLastGamRequestTime;\n if (elapsed < this.globalMinGamIntervalMs) {\n const waitMs = this.globalMinGamIntervalMs - elapsed;\n if (this.debug) {\n console.log(`[RATE-LIMIT] Waiting ${waitMs}ms before next GAM request`);\n }\n await new Promise(resolve => setTimeout(resolve, waitMs));\n }\n this.globalLastGamRequestTime = Date.now();\n }\n\n // --- Watchdog ---\n\n startAdRequestWatchdog(token: number): void {\n this.clearAdRequestWatchdog();\n\n const timeoutMs = this.config.adFailsafeTimeoutMs ?? 10000;\n this.adRequestWatchdogToken = token;\n this.adRequestWatchdogId = window.setTimeout(() => {\n if (this.adRequestWatchdogToken !== token) {\n return;\n }\n\n this.adRequestWatchdogId = undefined;\n this.adRequestWatchdogToken = null;\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n\n this.logAdState(\"ad_request_timeout\", { token, timeoutMs });\n this.callbacks.onWatchdogTimeout();\n }, timeoutMs) as unknown as number;\n\n this.logAdState(\"ad_request_watchdog_started\", { token, timeoutMs });\n }\n\n clearAdRequestWatchdog(): void {\n if (this.adRequestWatchdogId != null) {\n clearTimeout(this.adRequestWatchdogId);\n this.adRequestWatchdogId = undefined;\n }\n\n if (this.adRequestWatchdogToken != null) {\n this.logAdState(\"ad_request_watchdog_cleared\", {\n token: this.adRequestWatchdogToken,\n });\n this.adRequestWatchdogToken = null;\n }\n }\n\n // --- Failsafe ---\n\n startAdFailsafeTimer(token: number): void {\n this.clearAdFailsafeTimer();\n\n const failsafeMs = this.config.adFailsafeTimeoutMs ?? 10000;\n this.adFailsafeToken = token;\n\n this.adFailsafeTimerId = window.setTimeout(() => {\n if (this.adFailsafeToken !== token) {\n return;\n }\n\n this.adFailsafeTimerId = undefined;\n this.adFailsafeToken = null;\n\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n\n this.logAdState(\"ad_failsafe_triggered\", {\n token,\n failsafeMs,\n videoPaused: this.video.paused,\n });\n\n this.callbacks.onFailsafeTimeout();\n }, failsafeMs) as unknown as number;\n\n this.logAdState(\"ad_failsafe_started\", { token, failsafeMs });\n }\n\n clearAdFailsafeTimer(): void {\n if (this.adFailsafeTimerId != null) {\n clearTimeout(this.adFailsafeTimerId);\n this.logAdState(\"ad_failsafe_cleared\", { token: this.adFailsafeToken });\n this.adFailsafeTimerId = undefined;\n }\n\n this.adFailsafeToken = null;\n }\n\n logAdState(event: string, extra: Record<string, unknown> = {}, adPlayer?: AdController, inAdBreak?: boolean, showAds?: boolean): void {\n if (!this.debug) {\n return;\n }\n\n console.log(\"[StormcloudVideoPlayer][AdState]\", {\n event,\n timestamp: new Date().toISOString(),\n showAds: showAds ?? false,\n adPlaying: adPlayer?.isAdPlaying() ?? false,\n inAdBreak: inAdBreak ?? false,\n activeAdRequestToken: this.activeAdRequestToken,\n ...extra,\n });\n }\n\n // --- Reset for ad break end ---\n\n resetForBreakEnd(): void {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdRequestWatchdog();\n this.clearAdFailsafeTimer();\n this.activeAdRequestToken = null;\n this.expectedAdBreakDurationMs = undefined;\n this.currentAdBreakStartWallClockMs = undefined;\n this.scteAdBreakEndWallClockMs = undefined;\n this.adBreakPlayedDurationMs = 0;\n this.currentAdIndex = 0;\n this.totalAdsInBreak = 0;\n this.consecutiveFailures = 0;\n this.globalConsecutiveNoFills = 0;\n this.adBreakEpoch++;\n }\n\n destroy(): void {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdFailsafeTimer();\n this.clearAdRequestWatchdog();\n }\n}\n"]}
1
+ {"version":3,"sources":["/home/ubuntu24-new/Dev/stormcloud-vp/lib/player/AdTimingService.cjs","../../src/player/AdTimingService.ts"],"names":["__defProp","Object","defineProperty","__getOwnPropDesc","getOwnPropertyDescriptor","__getOwnPropNames","getOwnPropertyNames","__hasOwnProp","prototype","hasOwnProperty","__export","target","all","name","get","enumerable","__copyProps","to","from","except","desc","key","call","__toCommonJS","mod","value","AdTimingService_exports","AdTimingService","module","exports","config","video","callbacks","adRequestWatchdogToken","adFailsafeToken","ptsDriftEmaMs","adBreakPlayedDurationMs","adBreakEpoch","adRequestTokenCounter","currentAdIndex","totalAdsInBreak","totalAdRequestsInBreak","maxTotalAdRequestsPerBreak","consecutiveFailures","maxConsecutiveFailures","minAdRequestIntervalMs","backoffBaseMs","maxBackoffMs","defaultMinRemainingMsToStartAd","failedVastUrls","Set","temporaryFailureCooldownMs","globalConsecutiveNoFills","globalNoFillThreshold","globalNoFillCooldownUntil","globalNoFillBackoffBaseMs","globalNoFillBackoffMaxMs","globalLastGamRequestTime","globalMinGamIntervalMs","debug","debugAdTiming","scheduleAdStopCountdown","remainingMs","clearAdStopTimer","ms","Math","max","floor","ensureAdStoppedByTimer","adStopTimerId","window","setTimeout","clearTimeout","onAdStopTimerFired","scheduleAdStartIn","delayMs","marker","type","cueKey","clearAdStartTimer","onAdStartTimerFired","adStartTimerId","updatePtsDrift","ptsSecondsSample","sampleMs","currentTime","Number","isFinite","abs","alpha","resolveScteBreakStartWallClockMs","nowWallClockMs","Date","now","ptsSeconds","nowMs","estimatedCurrentPtsMs","deltaMs","setAdBreakDurationBoundary","durationMs","expectedAdBreakDurationMs","currentAdBreakStartWallClockMs","scteAdBreakEndWallClockMs","scheduleAdStopAtBreakBoundary"],"mappings":"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","sourcesContent":["\"use strict\";\nvar __defProp = Object.defineProperty;\nvar __getOwnPropDesc = Object.getOwnPropertyDescriptor;\nvar __getOwnPropNames = Object.getOwnPropertyNames;\nvar __hasOwnProp = Object.prototype.hasOwnProperty;\nvar __export = (target, all) => {\n for (var name in all)\n __defProp(target, name, { get: all[name], enumerable: true });\n};\nvar __copyProps = (to, from, except, desc) => {\n if (from && typeof from === \"object\" || typeof from === \"function\") {\n for (let key of __getOwnPropNames(from))\n if (!__hasOwnProp.call(to, key) && key !== except)\n __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });\n }\n return to;\n};\nvar __toCommonJS = (mod) => __copyProps(__defProp({}, \"__esModule\", { value: true }), mod);\n\n// src/player/AdTimingService.ts\nvar AdTimingService_exports = {};\n__export(AdTimingService_exports, {\n AdTimingService: () => AdTimingService\n});\nmodule.exports = __toCommonJS(AdTimingService_exports);\nvar AdTimingService = class {\n constructor(config, video, callbacks) {\n this.adRequestWatchdogToken = null;\n this.adFailsafeToken = null;\n this.ptsDriftEmaMs = 0;\n this.adBreakPlayedDurationMs = 0;\n this.adBreakEpoch = 0;\n this.adRequestTokenCounter = 0;\n this.activeAdRequestToken = null;\n this.currentAdIndex = 0;\n this.totalAdsInBreak = 0;\n this.totalAdRequestsInBreak = 0;\n this.maxTotalAdRequestsPerBreak = 10;\n this.lastAdRequestTime = 0;\n this.consecutiveFailures = 0;\n this.maxConsecutiveFailures = 5;\n this.minAdRequestIntervalMs = 3e3;\n this.backoffBaseMs = 1e3;\n this.maxBackoffMs = 15e3;\n this.defaultMinRemainingMsToStartAd = 4e3;\n this.failedVastUrls = /* @__PURE__ */ new Set();\n this.temporaryFailureUrls = /* @__PURE__ */ new Map();\n this.temporaryFailureCooldownMs = 3e4;\n this.globalConsecutiveNoFills = 0;\n this.globalNoFillThreshold = 3;\n this.globalNoFillCooldownUntil = 0;\n this.globalNoFillBackoffBaseMs = 5e3;\n this.globalNoFillBackoffMaxMs = 6e4;\n this.globalLastGamRequestTime = 0;\n this.globalMinGamIntervalMs = 3e3;\n this.config = config;\n this.video = video;\n this.callbacks = callbacks;\n }\n get debug() {\n return !!this.config.debugAdTiming;\n }\n // --- Ad stop timer ---\n scheduleAdStopCountdown(remainingMs) {\n this.clearAdStopTimer();\n const ms = Math.max(0, Math.floor(remainingMs));\n if (ms === 0) {\n this.ensureAdStoppedByTimer();\n return;\n }\n this.adStopTimerId = window.setTimeout(() => {\n this.ensureAdStoppedByTimer();\n }, ms);\n }\n clearAdStopTimer() {\n if (this.adStopTimerId != null) {\n clearTimeout(this.adStopTimerId);\n this.adStopTimerId = void 0;\n }\n }\n ensureAdStoppedByTimer() {\n this.adStopTimerId = void 0;\n this.callbacks.onAdStopTimerFired();\n }\n // --- Ad start timer ---\n scheduleAdStartIn(delayMs, marker = { type: \"start\" }, cueKey) {\n this.clearAdStartTimer();\n const ms = Math.max(0, Math.floor(delayMs));\n if (ms === 0) {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n return;\n }\n this.adStartTimerId = window.setTimeout(() => {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n }, ms);\n }\n clearAdStartTimer() {\n if (this.adStartTimerId != null) {\n clearTimeout(this.adStartTimerId);\n this.adStartTimerId = void 0;\n }\n }\n // --- PTS drift ---\n updatePtsDrift(ptsSecondsSample) {\n const sampleMs = (this.video.currentTime - ptsSecondsSample) * 1e3;\n if (!Number.isFinite(sampleMs) || Math.abs(sampleMs) > 6e4) return;\n const alpha = 0.1;\n this.ptsDriftEmaMs = this.ptsDriftEmaMs * (1 - alpha) + sampleMs * alpha;\n }\n resolveScteBreakStartWallClockMs(marker) {\n const nowWallClockMs = Date.now();\n if (typeof marker.ptsSeconds !== \"number\") {\n return nowWallClockMs;\n }\n const nowMs = this.video.currentTime * 1e3;\n const estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;\n const deltaMs = marker.ptsSeconds * 1e3 - estimatedCurrentPtsMs;\n if (!Number.isFinite(deltaMs)) {\n return nowWallClockMs;\n }\n return nowWallClockMs + Math.floor(deltaMs);\n }\n // --- Duration boundary ---\n setAdBreakDurationBoundary(durationMs) {\n this.expectedAdBreakDurationMs = durationMs;\n if (durationMs != null && this.currentAdBreakStartWallClockMs != null) {\n this.scteAdBreakEndWallClockMs = this.currentAdBreakStartWallClockMs + durationMs;\n } else {\n this.scteAdBreakEndWallClockMs = void 0;\n }\n }\n scheduleAdStopAtBreakBoundary() {\n if (this.expectedAdBreakDurationMs == null) {\n return;\n }\n this.scheduleAdStopCountdown(this.getWallClockRemainingAdMs());\n }\n // --- Duration budget ---\n getWallClockRemainingAdMs() {\n const endWallClockMs = this.getAdBreakEndWallClockMs();\n if (endWallClockMs != null) {\n return Math.max(0, endWallClockMs - Date.now());\n }\n return 0;\n }\n getAdBreakEndWallClockMs() {\n if (this.scteAdBreakEndWallClockMs != null) {\n return this.scteAdBreakEndWallClockMs;\n }\n if (this.expectedAdBreakDurationMs == null || this.currentAdBreakStartWallClockMs == null) {\n return void 0;\n }\n return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;\n }\n getDurationBudgetRemainingMs() {\n if (this.expectedAdBreakDurationMs == null) {\n return 0;\n }\n return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);\n }\n getLoadedAdDurationMs(adController) {\n const durationSec = adController.getLoadedAdDuration?.();\n if (durationSec == null || durationSec <= 0) {\n return 0;\n }\n return Math.round(durationSec * 1e3);\n }\n adFitsRemainingBudget(durationMs) {\n if (durationMs <= 0) {\n return true;\n }\n return durationMs <= this.getDurationBudgetRemainingMs();\n }\n logAdDurationDecision(accepted, durationMs, adController) {\n if (!this.debug) {\n return;\n }\n const remainingBudget = this.getDurationBudgetRemainingMs();\n const adId = adController.getLoadedAdId?.() ?? \"unknown\";\n if (accepted) {\n console.log(\n `[AD-DURATION] Accepted ad id=${adId}, duration=${durationMs}ms, budget remaining after play=${Math.max(0, remainingBudget - durationMs)}ms`\n );\n } else {\n console.log(\n `[AD-DURATION] Rejected ad id=${adId}, duration=${durationMs}ms exceeds remaining budget=${remainingBudget}ms \\u2014 requesting another ad`\n );\n }\n }\n recordPlayedAdDuration(adController) {\n const durationMs = this.getLoadedAdDurationMs(adController);\n if (durationMs <= 0) {\n return;\n }\n this.adBreakPlayedDurationMs += durationMs;\n if (this.debug) {\n console.log(\n `[AD-DURATION] Played ${durationMs}ms \\u2014 total played=${this.adBreakPlayedDurationMs}ms / ${this.expectedAdBreakDurationMs ?? \"?\"}ms`\n );\n }\n }\n ensureLoadedAdFitsBudget(adController) {\n const durationMs = this.getLoadedAdDurationMs(adController);\n const fits = this.adFitsRemainingBudget(durationMs);\n this.logAdDurationDecision(fits, durationMs, adController);\n return fits;\n }\n rejectLoadedAdForDuration(adController) {\n try {\n adController.destroy();\n } catch {\n }\n }\n markAdStarted() {\n this.currentAdIndex++;\n this.totalAdsInBreak = this.currentAdIndex;\n this.totalAdRequestsInBreak++;\n this.lastAdRequestTime = Date.now();\n }\n notePodAdStarted() {\n this.currentAdIndex++;\n this.totalAdsInBreak = this.currentAdIndex;\n }\n getRemainingAdMs() {\n const wallClockRemaining = this.getWallClockRemainingAdMs();\n const budgetRemaining = this.getDurationBudgetRemainingMs();\n if (this.expectedAdBreakDurationMs == null) {\n return wallClockRemaining;\n }\n return Math.min(wallClockRemaining, budgetRemaining);\n }\n getMinRemainingMsToStartAd() {\n const configured = this.config.minRemainingMsToStartAd;\n if (typeof configured === \"number\" && configured >= 0) {\n return configured;\n }\n return this.defaultMinRemainingMsToStartAd;\n }\n hasTimeToStartAnotherAd() {\n const minRemaining = this.getMinRemainingMsToStartAd();\n return this.getWallClockRemainingAdMs() > minRemaining && this.getDurationBudgetRemainingMs() > minRemaining;\n }\n // --- Circuit breaker ---\n isTemporaryAdError(error) {\n const message = error?.message?.toLowerCase() || \"\";\n if (message.includes(\"no ads available\") || message.includes(\"no fill\") || message.includes(\"nofill\")) {\n return true;\n }\n if (message.includes(\"timeout\")) {\n return true;\n }\n if (message.includes(\"rate limit\") || message.includes(\"too many requests\")) {\n return true;\n }\n if (message.includes(\"ad playback error\")) {\n return true;\n }\n return false;\n }\n isUrlInCooldown(vastTagUrl) {\n const failureTime = this.temporaryFailureUrls.get(vastTagUrl);\n if (failureTime == null) {\n return false;\n }\n const elapsed = Date.now() - failureTime;\n if (elapsed >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(vastTagUrl);\n return false;\n }\n return true;\n }\n cleanupTemporaryFailures() {\n const now = Date.now();\n for (const [url, timestamp] of this.temporaryFailureUrls) {\n if (now - timestamp >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(url);\n }\n }\n }\n isGamInCooldown() {\n return Date.now() < this.globalNoFillCooldownUntil;\n }\n recordGamNoFill() {\n this.globalConsecutiveNoFills++;\n if (this.globalConsecutiveNoFills >= this.globalNoFillThreshold) {\n const exponent = this.globalConsecutiveNoFills - this.globalNoFillThreshold;\n const backoff = Math.min(\n this.globalNoFillBackoffBaseMs * Math.pow(2, exponent),\n this.globalNoFillBackoffMaxMs\n );\n this.globalNoFillCooldownUntil = Date.now() + backoff;\n if (this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] GAM cooldown activated: ${backoff}ms (${this.globalConsecutiveNoFills} consecutive no-fills)`\n );\n }\n }\n }\n resetGamNoFillCounter() {\n if (this.globalConsecutiveNoFills > 0 && this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] Resetting no-fill counter (was ${this.globalConsecutiveNoFills})`\n );\n }\n this.globalConsecutiveNoFills = 0;\n this.globalNoFillCooldownUntil = 0;\n }\n async enforceGlobalRateLimit() {\n const now = Date.now();\n const elapsed = now - this.globalLastGamRequestTime;\n if (elapsed < this.globalMinGamIntervalMs) {\n const waitMs = this.globalMinGamIntervalMs - elapsed;\n if (this.debug) {\n console.log(`[RATE-LIMIT] Waiting ${waitMs}ms before next GAM request`);\n }\n await new Promise((resolve) => setTimeout(resolve, waitMs));\n }\n this.globalLastGamRequestTime = Date.now();\n }\n // --- Watchdog ---\n startAdRequestWatchdog(token) {\n this.clearAdRequestWatchdog();\n const timeoutMs = this.config.adFailsafeTimeoutMs ?? 1e4;\n this.adRequestWatchdogToken = token;\n this.adRequestWatchdogId = window.setTimeout(() => {\n if (this.adRequestWatchdogToken !== token) {\n return;\n }\n this.adRequestWatchdogId = void 0;\n this.adRequestWatchdogToken = null;\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n this.logAdState(\"ad_request_timeout\", { token, timeoutMs });\n this.callbacks.onWatchdogTimeout();\n }, timeoutMs);\n this.logAdState(\"ad_request_watchdog_started\", { token, timeoutMs });\n }\n clearAdRequestWatchdog() {\n if (this.adRequestWatchdogId != null) {\n clearTimeout(this.adRequestWatchdogId);\n this.adRequestWatchdogId = void 0;\n }\n if (this.adRequestWatchdogToken != null) {\n this.logAdState(\"ad_request_watchdog_cleared\", {\n token: this.adRequestWatchdogToken\n });\n this.adRequestWatchdogToken = null;\n }\n }\n // --- Failsafe ---\n startAdFailsafeTimer(token) {\n this.clearAdFailsafeTimer();\n const failsafeMs = this.config.adFailsafeTimeoutMs ?? 1e4;\n this.adFailsafeToken = token;\n this.adFailsafeTimerId = window.setTimeout(() => {\n if (this.adFailsafeToken !== token) {\n return;\n }\n this.adFailsafeTimerId = void 0;\n this.adFailsafeToken = null;\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n this.logAdState(\"ad_failsafe_triggered\", {\n token,\n failsafeMs,\n videoPaused: this.video.paused\n });\n this.callbacks.onFailsafeTimeout();\n }, failsafeMs);\n this.logAdState(\"ad_failsafe_started\", { token, failsafeMs });\n }\n clearAdFailsafeTimer() {\n if (this.adFailsafeTimerId != null) {\n clearTimeout(this.adFailsafeTimerId);\n this.logAdState(\"ad_failsafe_cleared\", { token: this.adFailsafeToken });\n this.adFailsafeTimerId = void 0;\n }\n this.adFailsafeToken = null;\n }\n logAdState(event, extra = {}, adPlayer, inAdBreak, showAds) {\n if (!this.debug) {\n return;\n }\n console.log(\"[StormcloudVideoPlayer][AdState]\", {\n event,\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n showAds: showAds ?? false,\n adPlaying: adPlayer?.isAdPlaying() ?? false,\n inAdBreak: inAdBreak ?? false,\n activeAdRequestToken: this.activeAdRequestToken,\n ...extra\n });\n }\n // --- Reset for ad break end ---\n resetForBreakEnd() {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdRequestWatchdog();\n this.clearAdFailsafeTimer();\n this.activeAdRequestToken = null;\n this.expectedAdBreakDurationMs = void 0;\n this.currentAdBreakStartWallClockMs = void 0;\n this.scteAdBreakEndWallClockMs = void 0;\n this.adBreakPlayedDurationMs = 0;\n this.currentAdIndex = 0;\n this.totalAdsInBreak = 0;\n this.consecutiveFailures = 0;\n this.globalConsecutiveNoFills = 0;\n this.adBreakEpoch++;\n }\n destroy() {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdFailsafeTimer();\n this.clearAdRequestWatchdog();\n }\n};\n// Annotate the CommonJS export names for ESM import in node:\n0 && (module.exports = {\n AdTimingService\n});\n","import type { AdController, StormcloudVideoPlayerConfig, Scte35Marker } from \"../types\";\n\nexport interface AdTimingCallbacks {\n onAdStopTimerFired(): void;\n onAdStartTimerFired(marker: Scte35Marker, cueKey: string | undefined): void;\n onWatchdogTimeout(): void;\n onFailsafeTimeout(): void;\n}\n\nexport class AdTimingService {\n private adStopTimerId: number | undefined;\n private adStartTimerId: number | undefined;\n private adFailsafeTimerId: number | undefined;\n private adRequestWatchdogId: number | undefined;\n private adRequestWatchdogToken: number | null = null;\n private adFailsafeToken: number | null = null;\n\n ptsDriftEmaMs = 0;\n expectedAdBreakDurationMs: number | undefined;\n currentAdBreakStartWallClockMs: number | undefined;\n scteAdBreakEndWallClockMs: number | undefined;\n adBreakPlayedDurationMs: number = 0;\n adBreakEpoch: number = 0;\n\n adRequestTokenCounter = 0;\n activeAdRequestToken: number | null = null;\n currentAdIndex: number = 0;\n totalAdsInBreak: number = 0;\n totalAdRequestsInBreak: number = 0;\n readonly maxTotalAdRequestsPerBreak: number = 10;\n lastAdRequestTime: number = 0;\n consecutiveFailures: number = 0;\n readonly maxConsecutiveFailures: number = 5;\n readonly minAdRequestIntervalMs: number = 3000;\n readonly backoffBaseMs: number = 1000;\n readonly maxBackoffMs: number = 15000;\n private readonly defaultMinRemainingMsToStartAd: number = 4000;\n\n failedVastUrls: Set<string> = new Set();\n temporaryFailureUrls: Map<string, number> = new Map();\n private readonly temporaryFailureCooldownMs: number = 30000;\n\n private globalConsecutiveNoFills: number = 0;\n private readonly globalNoFillThreshold: number = 3;\n private globalNoFillCooldownUntil: number = 0;\n private readonly globalNoFillBackoffBaseMs: number = 5000;\n private readonly globalNoFillBackoffMaxMs: number = 60000;\n private globalLastGamRequestTime: number = 0;\n private readonly globalMinGamIntervalMs: number = 3000;\n\n private config: StormcloudVideoPlayerConfig;\n private video: HTMLVideoElement;\n private callbacks: AdTimingCallbacks;\n\n constructor(\n config: StormcloudVideoPlayerConfig,\n video: HTMLVideoElement,\n callbacks: AdTimingCallbacks\n ) {\n this.config = config;\n this.video = video;\n this.callbacks = callbacks;\n }\n\n get debug(): boolean {\n return !!this.config.debugAdTiming;\n }\n\n // --- Ad stop timer ---\n\n scheduleAdStopCountdown(remainingMs: number): void {\n this.clearAdStopTimer();\n const ms = Math.max(0, Math.floor(remainingMs));\n if (ms === 0) {\n this.ensureAdStoppedByTimer();\n return;\n }\n this.adStopTimerId = window.setTimeout(() => {\n this.ensureAdStoppedByTimer();\n }, ms) as unknown as number;\n }\n\n clearAdStopTimer(): void {\n if (this.adStopTimerId != null) {\n clearTimeout(this.adStopTimerId);\n this.adStopTimerId = undefined;\n }\n }\n\n private ensureAdStoppedByTimer(): void {\n this.adStopTimerId = undefined;\n this.callbacks.onAdStopTimerFired();\n }\n\n // --- Ad start timer ---\n\n scheduleAdStartIn(\n delayMs: number,\n marker: Scte35Marker = { type: \"start\" } as Scte35Marker,\n cueKey?: string\n ): void {\n this.clearAdStartTimer();\n const ms = Math.max(0, Math.floor(delayMs));\n if (ms === 0) {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n return;\n }\n this.adStartTimerId = window.setTimeout(() => {\n this.callbacks.onAdStartTimerFired(marker, cueKey);\n }, ms) as unknown as number;\n }\n\n clearAdStartTimer(): void {\n if (this.adStartTimerId != null) {\n clearTimeout(this.adStartTimerId);\n this.adStartTimerId = undefined;\n }\n }\n\n // --- PTS drift ---\n\n updatePtsDrift(ptsSecondsSample: number): void {\n const sampleMs = (this.video.currentTime - ptsSecondsSample) * 1000;\n if (!Number.isFinite(sampleMs) || Math.abs(sampleMs) > 60000) return;\n const alpha = 0.1;\n this.ptsDriftEmaMs = this.ptsDriftEmaMs * (1 - alpha) + sampleMs * alpha;\n }\n\n resolveScteBreakStartWallClockMs(marker: Scte35Marker): number {\n const nowWallClockMs = Date.now();\n if (typeof marker.ptsSeconds !== \"number\") {\n return nowWallClockMs;\n }\n\n const nowMs = this.video.currentTime * 1000;\n const estimatedCurrentPtsMs = nowMs - this.ptsDriftEmaMs;\n const deltaMs = marker.ptsSeconds * 1000 - estimatedCurrentPtsMs;\n if (!Number.isFinite(deltaMs)) {\n return nowWallClockMs;\n }\n\n return nowWallClockMs + Math.floor(deltaMs);\n }\n\n // --- Duration boundary ---\n\n setAdBreakDurationBoundary(durationMs: number | undefined): void {\n this.expectedAdBreakDurationMs = durationMs;\n if (\n durationMs != null &&\n this.currentAdBreakStartWallClockMs != null\n ) {\n this.scteAdBreakEndWallClockMs =\n this.currentAdBreakStartWallClockMs + durationMs;\n } else {\n this.scteAdBreakEndWallClockMs = undefined;\n }\n }\n\n scheduleAdStopAtBreakBoundary(): void {\n if (this.expectedAdBreakDurationMs == null) {\n return;\n }\n this.scheduleAdStopCountdown(this.getWallClockRemainingAdMs());\n }\n\n // --- Duration budget ---\n\n getWallClockRemainingAdMs(): number {\n const endWallClockMs = this.getAdBreakEndWallClockMs();\n if (endWallClockMs != null) {\n return Math.max(0, endWallClockMs - Date.now());\n }\n return 0;\n }\n\n getAdBreakEndWallClockMs(): number | undefined {\n if (this.scteAdBreakEndWallClockMs != null) {\n return this.scteAdBreakEndWallClockMs;\n }\n if (\n this.expectedAdBreakDurationMs == null ||\n this.currentAdBreakStartWallClockMs == null\n ) {\n return undefined;\n }\n return this.currentAdBreakStartWallClockMs + this.expectedAdBreakDurationMs;\n }\n\n getDurationBudgetRemainingMs(): number {\n if (this.expectedAdBreakDurationMs == null) {\n return 0;\n }\n return Math.max(0, this.expectedAdBreakDurationMs - this.adBreakPlayedDurationMs);\n }\n\n getLoadedAdDurationMs(adController: AdController): number {\n const durationSec = adController.getLoadedAdDuration?.();\n if (durationSec == null || durationSec <= 0) {\n return 0;\n }\n return Math.round(durationSec * 1000);\n }\n\n adFitsRemainingBudget(durationMs: number): boolean {\n if (durationMs <= 0) {\n return true;\n }\n return durationMs <= this.getDurationBudgetRemainingMs();\n }\n\n logAdDurationDecision(\n accepted: boolean,\n durationMs: number,\n adController: AdController\n ): void {\n if (!this.debug) {\n return;\n }\n const remainingBudget = this.getDurationBudgetRemainingMs();\n const adId = adController.getLoadedAdId?.() ?? \"unknown\";\n if (accepted) {\n console.log(\n `[AD-DURATION] Accepted ad id=${adId}, duration=${durationMs}ms, budget remaining after play=${Math.max(0, remainingBudget - durationMs)}ms`\n );\n } else {\n console.log(\n `[AD-DURATION] Rejected ad id=${adId}, duration=${durationMs}ms exceeds remaining budget=${remainingBudget}ms — requesting another ad`\n );\n }\n }\n\n recordPlayedAdDuration(adController: AdController): void {\n const durationMs = this.getLoadedAdDurationMs(adController);\n if (durationMs <= 0) {\n return;\n }\n this.adBreakPlayedDurationMs += durationMs;\n if (this.debug) {\n console.log(\n `[AD-DURATION] Played ${durationMs}ms — total played=${this.adBreakPlayedDurationMs}ms / ${this.expectedAdBreakDurationMs ?? \"?\"}ms`\n );\n }\n }\n\n ensureLoadedAdFitsBudget(adController: AdController): boolean {\n const durationMs = this.getLoadedAdDurationMs(adController);\n const fits = this.adFitsRemainingBudget(durationMs);\n this.logAdDurationDecision(fits, durationMs, adController);\n return fits;\n }\n\n rejectLoadedAdForDuration(adController: AdController): void {\n try {\n adController.destroy();\n } catch {}\n }\n\n markAdStarted(): void {\n this.currentAdIndex++;\n this.totalAdsInBreak = this.currentAdIndex;\n this.totalAdRequestsInBreak++;\n this.lastAdRequestTime = Date.now();\n }\n\n notePodAdStarted(): void {\n this.currentAdIndex++;\n this.totalAdsInBreak = this.currentAdIndex;\n }\n\n getRemainingAdMs(): number {\n const wallClockRemaining = this.getWallClockRemainingAdMs();\n const budgetRemaining = this.getDurationBudgetRemainingMs();\n if (this.expectedAdBreakDurationMs == null) {\n return wallClockRemaining;\n }\n return Math.min(wallClockRemaining, budgetRemaining);\n }\n\n getMinRemainingMsToStartAd(): number {\n const configured = this.config.minRemainingMsToStartAd;\n if (typeof configured === \"number\" && configured >= 0) {\n return configured;\n }\n return this.defaultMinRemainingMsToStartAd;\n }\n\n hasTimeToStartAnotherAd(): boolean {\n const minRemaining = this.getMinRemainingMsToStartAd();\n return (\n this.getWallClockRemainingAdMs() > minRemaining &&\n this.getDurationBudgetRemainingMs() > minRemaining\n );\n }\n\n // --- Circuit breaker ---\n\n isTemporaryAdError(error: Error): boolean {\n const message = error?.message?.toLowerCase() || \"\";\n\n if (message.includes(\"no ads available\") || message.includes(\"no fill\") || message.includes(\"nofill\")) {\n return true;\n }\n if (message.includes(\"timeout\")) {\n return true;\n }\n if (message.includes(\"rate limit\") || message.includes(\"too many requests\")) {\n return true;\n }\n if (message.includes(\"ad playback error\")) {\n return true;\n }\n return false;\n }\n\n isUrlInCooldown(vastTagUrl: string): boolean {\n const failureTime = this.temporaryFailureUrls.get(vastTagUrl);\n if (failureTime == null) {\n return false;\n }\n\n const elapsed = Date.now() - failureTime;\n if (elapsed >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(vastTagUrl);\n return false;\n }\n\n return true;\n }\n\n cleanupTemporaryFailures(): void {\n const now = Date.now();\n for (const [url, timestamp] of this.temporaryFailureUrls) {\n if (now - timestamp >= this.temporaryFailureCooldownMs) {\n this.temporaryFailureUrls.delete(url);\n }\n }\n }\n\n isGamInCooldown(): boolean {\n return Date.now() < this.globalNoFillCooldownUntil;\n }\n\n recordGamNoFill(): void {\n this.globalConsecutiveNoFills++;\n\n if (this.globalConsecutiveNoFills >= this.globalNoFillThreshold) {\n const exponent = this.globalConsecutiveNoFills - this.globalNoFillThreshold;\n const backoff = Math.min(\n this.globalNoFillBackoffBaseMs * Math.pow(2, exponent),\n this.globalNoFillBackoffMaxMs\n );\n this.globalNoFillCooldownUntil = Date.now() + backoff;\n\n if (this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] GAM cooldown activated: ${backoff}ms (${this.globalConsecutiveNoFills} consecutive no-fills)`\n );\n }\n }\n }\n\n resetGamNoFillCounter(): void {\n if (this.globalConsecutiveNoFills > 0 && this.debug) {\n console.log(\n `[CIRCUIT-BREAKER] Resetting no-fill counter (was ${this.globalConsecutiveNoFills})`\n );\n }\n this.globalConsecutiveNoFills = 0;\n this.globalNoFillCooldownUntil = 0;\n }\n\n async enforceGlobalRateLimit(): Promise<void> {\n const now = Date.now();\n const elapsed = now - this.globalLastGamRequestTime;\n if (elapsed < this.globalMinGamIntervalMs) {\n const waitMs = this.globalMinGamIntervalMs - elapsed;\n if (this.debug) {\n console.log(`[RATE-LIMIT] Waiting ${waitMs}ms before next GAM request`);\n }\n await new Promise(resolve => setTimeout(resolve, waitMs));\n }\n this.globalLastGamRequestTime = Date.now();\n }\n\n // --- Watchdog ---\n\n startAdRequestWatchdog(token: number): void {\n this.clearAdRequestWatchdog();\n\n const timeoutMs = this.config.adFailsafeTimeoutMs ?? 10000;\n this.adRequestWatchdogToken = token;\n this.adRequestWatchdogId = window.setTimeout(() => {\n if (this.adRequestWatchdogToken !== token) {\n return;\n }\n\n this.adRequestWatchdogId = undefined;\n this.adRequestWatchdogToken = null;\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n\n this.logAdState(\"ad_request_timeout\", { token, timeoutMs });\n this.callbacks.onWatchdogTimeout();\n }, timeoutMs) as unknown as number;\n\n this.logAdState(\"ad_request_watchdog_started\", { token, timeoutMs });\n }\n\n clearAdRequestWatchdog(): void {\n if (this.adRequestWatchdogId != null) {\n clearTimeout(this.adRequestWatchdogId);\n this.adRequestWatchdogId = undefined;\n }\n\n if (this.adRequestWatchdogToken != null) {\n this.logAdState(\"ad_request_watchdog_cleared\", {\n token: this.adRequestWatchdogToken,\n });\n this.adRequestWatchdogToken = null;\n }\n }\n\n // --- Failsafe ---\n\n startAdFailsafeTimer(token: number): void {\n this.clearAdFailsafeTimer();\n\n const failsafeMs = this.config.adFailsafeTimeoutMs ?? 10000;\n this.adFailsafeToken = token;\n\n this.adFailsafeTimerId = window.setTimeout(() => {\n if (this.adFailsafeToken !== token) {\n return;\n }\n\n this.adFailsafeTimerId = undefined;\n this.adFailsafeToken = null;\n\n if (this.activeAdRequestToken === token) {\n this.activeAdRequestToken = null;\n }\n\n this.logAdState(\"ad_failsafe_triggered\", {\n token,\n failsafeMs,\n videoPaused: this.video.paused,\n });\n\n this.callbacks.onFailsafeTimeout();\n }, failsafeMs) as unknown as number;\n\n this.logAdState(\"ad_failsafe_started\", { token, failsafeMs });\n }\n\n clearAdFailsafeTimer(): void {\n if (this.adFailsafeTimerId != null) {\n clearTimeout(this.adFailsafeTimerId);\n this.logAdState(\"ad_failsafe_cleared\", { token: this.adFailsafeToken });\n this.adFailsafeTimerId = undefined;\n }\n\n this.adFailsafeToken = null;\n }\n\n logAdState(event: string, extra: Record<string, unknown> = {}, adPlayer?: AdController, inAdBreak?: boolean, showAds?: boolean): void {\n if (!this.debug) {\n return;\n }\n\n console.log(\"[StormcloudVideoPlayer][AdState]\", {\n event,\n timestamp: new Date().toISOString(),\n showAds: showAds ?? false,\n adPlaying: adPlayer?.isAdPlaying() ?? false,\n inAdBreak: inAdBreak ?? false,\n activeAdRequestToken: this.activeAdRequestToken,\n ...extra,\n });\n }\n\n // --- Reset for ad break end ---\n\n resetForBreakEnd(): void {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdRequestWatchdog();\n this.clearAdFailsafeTimer();\n this.activeAdRequestToken = null;\n this.expectedAdBreakDurationMs = undefined;\n this.currentAdBreakStartWallClockMs = undefined;\n this.scteAdBreakEndWallClockMs = undefined;\n this.adBreakPlayedDurationMs = 0;\n this.currentAdIndex = 0;\n this.totalAdsInBreak = 0;\n this.consecutiveFailures = 0;\n this.globalConsecutiveNoFills = 0;\n this.adBreakEpoch++;\n }\n\n destroy(): void {\n this.clearAdStopTimer();\n this.clearAdStartTimer();\n this.clearAdFailsafeTimer();\n this.clearAdRequestWatchdog();\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import { a as StormcloudVideoPlayerConfig, S as Scte35Marker, A as AdController } from '../types-Bz4aRmzc.cjs';
1
+ import { a as StormcloudVideoPlayerConfig, S as Scte35Marker, A as AdController } from '../types-Xgz2_W1C.cjs';
2
2
 
3
3
  interface AdTimingCallbacks {
4
4
  onAdStopTimerFired(): void;
@@ -66,6 +66,7 @@ declare class AdTimingService {
66
66
  ensureLoadedAdFitsBudget(adController: AdController): boolean;
67
67
  rejectLoadedAdForDuration(adController: AdController): void;
68
68
  markAdStarted(): void;
69
+ notePodAdStarted(): void;
69
70
  getRemainingAdMs(): number;
70
71
  getMinRemainingMsToStartAd(): number;
71
72
  hasTimeToStartAnotherAd(): boolean;
@@ -1,5 +1,5 @@
1
1
  import Hls from 'hls.js';
2
- import { a as StormcloudVideoPlayerConfig, S as Scte35Marker } from '../types-Bz4aRmzc.cjs';
2
+ import { a as StormcloudVideoPlayerConfig, S as Scte35Marker } from '../types-Xgz2_W1C.cjs';
3
3
  import { Scte35CueContext } from './playerTypes.cjs';
4
4
  import { Scte35CueManager } from './Scte35CueManager.cjs';
5
5
  import { AdTimingService } from './AdTimingService.cjs';
@@ -1,4 +1,4 @@
1
- import { A as AdController } from '../types-Bz4aRmzc.cjs';
1
+ import { A as AdController } from '../types-Xgz2_W1C.cjs';
2
2
 
3
3
  declare function getAdAudioVolume(adPlayer: AdController): number;
4
4
  declare function toggleMute(video: HTMLVideoElement, adPlayer: AdController, debug: boolean): void;
@@ -1,4 +1,4 @@
1
- import { S as Scte35Marker } from '../types-Bz4aRmzc.cjs';
1
+ import { S as Scte35Marker } from '../types-Xgz2_W1C.cjs';
2
2
  import { Scte35CueSource, Scte35CueReadiness, Scte35CueContext } from './playerTypes.cjs';
3
3
 
4
4
  interface Scte35CueManagerCallbacks {
@@ -1,4 +1,4 @@
1
- import { S as Scte35Marker, I as Id3TagInfo } from '../types-Bz4aRmzc.cjs';
1
+ import { S as Scte35Marker, I as Id3TagInfo } from '../types-Xgz2_W1C.cjs';
2
2
 
3
3
  declare function parseCueOutDuration(value: string): number | undefined;
4
4
  declare function parseCueOutCont(value: string): {