stormcloud-video-player 0.8.49 → 0.8.50

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.
@@ -1,4 +1,4 @@
1
- import { a as Scte35Marker, S as StormcloudVideoPlayerConfig, A as AdController } from '../types-iDjS8f_7.cjs';
1
+ import { S as Scte35Marker, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-cTqIKw_D.cjs';
2
2
  import { AdTimingService } from './AdTimingService.cjs';
3
3
  import { AdConfigManager } from './AdConfigManager.cjs';
4
4
  import { Scte35CueManager } from './Scte35CueManager.cjs';
@@ -1,4 +1,4 @@
1
- import { b as AdBreak, S as StormcloudVideoPlayerConfig, A as AdController } from '../types-iDjS8f_7.cjs';
1
+ import { b as AdBreak, a as StormcloudVideoPlayerConfig, A as AdController } from '../types-cTqIKw_D.cjs';
2
2
  import { PalNonceManager } from '../sdk/pal.cjs';
3
3
  import { VastConsentSignals } from '../utils/vastMacros.cjs';
4
4
 
@@ -794,17 +794,17 @@ var AdTimingService = /*#__PURE__*/ function() {
794
794
  {
795
795
  key: "logAdState",
796
796
  value: function logAdState(event) {
797
- var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, adPlayer = arguments.length > 2 ? arguments[2] : void 0, inAdBreak = arguments.length > 3 ? arguments[3] : void 0, showAds = arguments.length > 4 ? arguments[4] : void 0;
798
- var _ref;
797
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
799
798
  if (!this.debug) {
800
799
  return;
801
800
  }
801
+ var _this_callbacks_getAdState = this.callbacks.getAdState(), inAdBreak = _this_callbacks_getAdState.inAdBreak, showAds = _this_callbacks_getAdState.showAds, adPlaying = _this_callbacks_getAdState.adPlaying;
802
802
  console.log("[StormcloudVideoPlayer][AdState]", _object_spread({
803
803
  event: event,
804
804
  timestamp: /* @__PURE__ */ new Date().toISOString(),
805
- showAds: showAds !== null && showAds !== void 0 ? showAds : false,
806
- adPlaying: (_ref = adPlayer === null || adPlayer === void 0 ? void 0 : adPlayer.isAdPlaying()) !== null && _ref !== void 0 ? _ref : false,
807
- inAdBreak: inAdBreak !== null && inAdBreak !== void 0 ? inAdBreak : false,
805
+ showAds: showAds,
806
+ adPlaying: adPlaying,
807
+ inAdBreak: inAdBreak,
808
808
  activeAdRequestToken: this.activeAdRequestToken
809
809
  }, extra));
810
810
  }
@@ -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","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
+ {"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 = {}) {\n if (!this.debug) {\n return;\n }\n const { inAdBreak, showAds, adPlaying } = this.callbacks.getAdState();\n console.log(\"[StormcloudVideoPlayer][AdState]\", {\n event,\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n showAds,\n adPlaying,\n inAdBreak,\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 getAdState(): { inAdBreak: boolean; showAds: boolean; adPlaying: boolean };\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> = {}): void {\n if (!this.debug) {\n return;\n }\n\n const { inAdBreak, showAds, adPlaying } = this.callbacks.getAdState();\n\n console.log(\"[StormcloudVideoPlayer][AdState]\", {\n event,\n timestamp: new Date().toISOString(),\n showAds,\n adPlaying,\n inAdBreak,\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,10 +1,15 @@
1
- import { S as StormcloudVideoPlayerConfig, a as Scte35Marker, A as AdController } from '../types-iDjS8f_7.cjs';
1
+ import { a as StormcloudVideoPlayerConfig, S as Scte35Marker, A as AdController } from '../types-cTqIKw_D.cjs';
2
2
 
3
3
  interface AdTimingCallbacks {
4
4
  onAdStopTimerFired(): void;
5
5
  onAdStartTimerFired(marker: Scte35Marker, cueKey: string | undefined): void;
6
6
  onWatchdogTimeout(): void;
7
7
  onFailsafeTimeout(): void;
8
+ getAdState(): {
9
+ inAdBreak: boolean;
10
+ showAds: boolean;
11
+ adPlaying: boolean;
12
+ };
8
13
  }
9
14
  declare class AdTimingService {
10
15
  private adStopTimerId;
@@ -81,7 +86,7 @@ declare class AdTimingService {
81
86
  clearAdRequestWatchdog(): void;
82
87
  startAdFailsafeTimer(token: number): void;
83
88
  clearAdFailsafeTimer(): void;
84
- logAdState(event: string, extra?: Record<string, unknown>, adPlayer?: AdController, inAdBreak?: boolean, showAds?: boolean): void;
89
+ logAdState(event: string, extra?: Record<string, unknown>): void;
85
90
  resetForBreakEnd(): void;
86
91
  destroy(): void;
87
92
  }
@@ -1,5 +1,5 @@
1
1
  import Hls from 'hls.js';
2
- import { S as StormcloudVideoPlayerConfig, a as Scte35Marker } from '../types-iDjS8f_7.cjs';
2
+ import { a as StormcloudVideoPlayerConfig, S as Scte35Marker } from '../types-cTqIKw_D.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-iDjS8f_7.cjs';
1
+ import { A as AdController } from '../types-cTqIKw_D.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 { a as Scte35Marker } from '../types-iDjS8f_7.cjs';
1
+ import { S as Scte35Marker } from '../types-cTqIKw_D.cjs';
2
2
  import { Scte35CueSource, Scte35CueReadiness, Scte35CueContext } from './playerTypes.cjs';
3
3
 
4
4
  interface Scte35CueManagerCallbacks {
@@ -1,4 +1,4 @@
1
- import { a as Scte35Marker, I as Id3TagInfo } from '../types-iDjS8f_7.cjs';
1
+ import { S as Scte35Marker, I as Id3TagInfo } from '../types-cTqIKw_D.cjs';
2
2
 
3
3
  declare function parseCueOutDuration(value: string): number | undefined;
4
4
  declare function parseCueOutCont(value: string): {
@@ -360,6 +360,7 @@ function createHlsAdPlayer(contentVideo, options) {
360
360
  var mainHlsInstance = options === null || options === void 0 ? void 0 : options.mainHlsInstance;
361
361
  var AD_CONTAINER_PROP = "__stormcloudAdContainer";
362
362
  var AD_VIDEO_PROP = "__stormcloudAdVideo";
363
+ var FRAME_COUNTER_RELIABLE_PROP = "__stormcloudFrameCounterReliable";
363
364
  var adVideoElement = (_contentVideo_AD_VIDEO_PROP = contentVideo[AD_VIDEO_PROP]) !== null && _contentVideo_AD_VIDEO_PROP !== void 0 ? _contentVideo_AD_VIDEO_PROP : void 0;
364
365
  var adHls;
365
366
  var adContainerEl = (_contentVideo_AD_CONTAINER_PROP = contentVideo[AD_CONTAINER_PROP]) !== null && _contentVideo_AD_CONTAINER_PROP !== void 0 ? _contentVideo_AD_CONTAINER_PROP : void 0;
@@ -379,6 +380,7 @@ function createHlsAdPlayer(contentVideo, options) {
379
380
  var lastAdProgressTime = 0;
380
381
  var lastAdProgressPosition = 0;
381
382
  var sawDecodedVideoFrame = false;
383
+ var videoFrameCallbackHandle;
382
384
  var trackingFired = {
383
385
  impression: false,
384
386
  start: false,
@@ -1265,6 +1267,7 @@ function createHlsAdPlayer(contentVideo, options) {
1265
1267
  clearInterval(stallWatchdogId);
1266
1268
  stallWatchdogId = void 0;
1267
1269
  }
1270
+ cancelVideoFrameProbe();
1268
1271
  }
1269
1272
  function noteAdProgress() {
1270
1273
  lastAdProgressTime = Date.now();
@@ -1300,6 +1303,39 @@ function createHlsAdPlayer(contentVideo, options) {
1300
1303
  }
1301
1304
  return void 0;
1302
1305
  }
1306
+ function isFrameCounterReliable() {
1307
+ return contentVideo[FRAME_COUNTER_RELIABLE_PROP] === true;
1308
+ }
1309
+ function markFrameCounterReliable() {
1310
+ contentVideo[FRAME_COUNTER_RELIABLE_PROP] = true;
1311
+ }
1312
+ function cancelVideoFrameProbe() {
1313
+ if (videoFrameCallbackHandle != null && adVideoElement && typeof adVideoElement.cancelVideoFrameCallback === "function") {
1314
+ try {
1315
+ adVideoElement.cancelVideoFrameCallback(videoFrameCallbackHandle);
1316
+ } catch (error) {
1317
+ console.warn("[HlsAdPlayer] Error cancelling video frame callback:", error);
1318
+ }
1319
+ }
1320
+ videoFrameCallbackHandle = void 0;
1321
+ }
1322
+ function startVideoFrameProbe() {
1323
+ cancelVideoFrameProbe();
1324
+ if (!adVideoElement) return;
1325
+ var rvfc = adVideoElement.requestVideoFrameCallback;
1326
+ if (typeof rvfc !== "function") return;
1327
+ var onPresentedFrame = function onPresentedFrame() {
1328
+ videoFrameCallbackHandle = void 0;
1329
+ if (!adPlaying) return;
1330
+ sawDecodedVideoFrame = true;
1331
+ };
1332
+ try {
1333
+ videoFrameCallbackHandle = rvfc.call(adVideoElement, onPresentedFrame);
1334
+ } catch (error) {
1335
+ console.warn("[HlsAdPlayer] Error requesting video frame callback:", error);
1336
+ videoFrameCallbackHandle = void 0;
1337
+ }
1338
+ }
1303
1339
  function handleVideoDecodeFailure() {
1304
1340
  console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
1305
1341
  clearStallWatchdog();
@@ -1315,6 +1351,7 @@ function createHlsAdPlayer(contentVideo, options) {
1315
1351
  clearStallWatchdog();
1316
1352
  noteAdProgress();
1317
1353
  sawDecodedVideoFrame = false;
1354
+ startVideoFrameProbe();
1318
1355
  stallWatchdogId = window.setInterval(function() {
1319
1356
  if (!adPlaying || !adVideoElement) {
1320
1357
  return;
@@ -1323,7 +1360,8 @@ function createHlsAdPlayer(contentVideo, options) {
1323
1360
  var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
1324
1361
  if (decodedFrames !== void 0 && decodedFrames > 0) {
1325
1362
  sawDecodedVideoFrame = true;
1326
- } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S) {
1363
+ markFrameCounterReliable();
1364
+ } else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && isFrameCounterReliable()) {
1327
1365
  handleVideoDecodeFailure();
1328
1366
  return;
1329
1367
  }
@@ -6168,17 +6206,17 @@ var AdTimingService = /*#__PURE__*/ function() {
6168
6206
  {
6169
6207
  key: "logAdState",
6170
6208
  value: function logAdState(event) {
6171
- var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, adPlayer = arguments.length > 2 ? arguments[2] : void 0, inAdBreak = arguments.length > 3 ? arguments[3] : void 0, showAds = arguments.length > 4 ? arguments[4] : void 0;
6172
- var _ref;
6209
+ var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
6173
6210
  if (!this.debug) {
6174
6211
  return;
6175
6212
  }
6213
+ var _this_callbacks_getAdState = this.callbacks.getAdState(), inAdBreak = _this_callbacks_getAdState.inAdBreak, showAds = _this_callbacks_getAdState.showAds, adPlaying = _this_callbacks_getAdState.adPlaying;
6176
6214
  console.log("[StormcloudVideoPlayer][AdState]", _object_spread({
6177
6215
  event: event,
6178
6216
  timestamp: /* @__PURE__ */ new Date().toISOString(),
6179
- showAds: showAds !== null && showAds !== void 0 ? showAds : false,
6180
- adPlaying: (_ref = adPlayer === null || adPlayer === void 0 ? void 0 : adPlayer.isAdPlaying()) !== null && _ref !== void 0 ? _ref : false,
6181
- inAdBreak: inAdBreak !== null && inAdBreak !== void 0 ? inAdBreak : false,
6217
+ showAds: showAds,
6218
+ adPlaying: adPlaying,
6219
+ inAdBreak: inAdBreak,
6182
6220
  activeAdRequestToken: this.activeAdRequestToken
6183
6221
  }, extra));
6184
6222
  }
@@ -8528,6 +8566,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
8528
8566
  },
8529
8567
  onFailsafeTimeout: function onFailsafeTimeout() {
8530
8568
  return _this.adBreak.handleAdFailure();
8569
+ },
8570
+ getAdState: function getAdState() {
8571
+ return {
8572
+ inAdBreak: _this.adBreak.inAdBreak,
8573
+ showAds: _this.adBreak.showAds,
8574
+ adPlaying: _this.adPlayer.isAdPlaying()
8575
+ };
8531
8576
  }
8532
8577
  });
8533
8578
  this.placeholder = new PlaceholderLayer(this.video, !!this.config.debugAdTiming);