stormcloud-video-player 0.8.48 → 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.
- package/dist/stormcloud-vp.min.js +2 -2
- package/lib/index.cjs +107 -5
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +107 -5
- package/lib/index.js.map +1 -1
- package/lib/player/AdTimingService.cjs +5 -5
- package/lib/player/AdTimingService.cjs.map +1 -1
- package/lib/player/AdTimingService.d.cts +6 -1
- package/lib/player/StormcloudVideoPlayer.cjs +107 -5
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.cjs +107 -5
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/index.cjs +107 -5
- package/lib/players/index.cjs.map +1 -1
- package/lib/sdk/hlsAdPlayer.cjs +95 -0
- package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
- package/lib/types-iDjS8f_7.d.cts +133 -0
- package/lib/ui/StormcloudVideoPlayer.cjs +107 -5
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/package.json +1 -1
|
@@ -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] : {}
|
|
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
|
|
806
|
-
adPlaying:
|
|
807
|
-
inAdBreak: inAdBreak
|
|
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"]}
|
|
@@ -5,6 +5,11 @@ interface AdTimingCallbacks {
|
|
|
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
|
|
89
|
+
logAdState(event: string, extra?: Record<string, unknown>): void;
|
|
85
90
|
resetForBreakEnd(): void;
|
|
86
91
|
destroy(): void;
|
|
87
92
|
}
|
|
@@ -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;
|
|
@@ -374,9 +375,12 @@ function createHlsAdPlayer(contentVideo, options) {
|
|
|
374
375
|
var pendingTimeouts = [];
|
|
375
376
|
var STALL_TIMEOUT_MS = 4e3;
|
|
376
377
|
var STALL_CHECK_INTERVAL_MS = 1e3;
|
|
378
|
+
var BLACKFRAME_MIN_PLAYBACK_S = 1.5;
|
|
377
379
|
var stallWatchdogId;
|
|
378
380
|
var lastAdProgressTime = 0;
|
|
379
381
|
var lastAdProgressPosition = 0;
|
|
382
|
+
var sawDecodedVideoFrame = false;
|
|
383
|
+
var videoFrameCallbackHandle;
|
|
380
384
|
var trackingFired = {
|
|
381
385
|
impression: false,
|
|
382
386
|
start: false,
|
|
@@ -557,6 +561,11 @@ function createHlsAdPlayer(contentVideo, options) {
|
|
|
557
561
|
console.warn("[HlsAdPlayer] MediaFile ".concat(index, ' skipped (audio-only, type="').concat(type, '") — audio ads are not played to avoid a black screen'));
|
|
558
562
|
return;
|
|
559
563
|
}
|
|
564
|
+
var hasExplicitDimensions = mf.getAttribute("width") != null && mf.getAttribute("height") != null;
|
|
565
|
+
if (!isHlsMediaFile(mediaFile) && hasExplicitDimensions && (mediaFile.width <= 1 || mediaFile.height <= 1)) {
|
|
566
|
+
console.warn("[HlsAdPlayer] MediaFile ".concat(index, " skipped (degenerate dimensions ").concat(mediaFile.width, "x").concat(mediaFile.height, ', type="').concat(type, '") — likely an audio-only placeholder rendition'));
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
560
569
|
if (isHlsMediaFile(mediaFile) || isProgressiveMediaFile(mediaFile)) {
|
|
561
570
|
mediaFiles.push(mediaFile);
|
|
562
571
|
if (verbose) {
|
|
@@ -1024,6 +1033,14 @@ function createHlsAdPlayer(contentVideo, options) {
|
|
|
1024
1033
|
fireTrackingPixels(currentAd.trackingUrls.thirdQuartile);
|
|
1025
1034
|
}
|
|
1026
1035
|
};
|
|
1036
|
+
var onLoadedMetadata = function onLoadedMetadata() {
|
|
1037
|
+
if (!adPlaying || !adVideoElement) return;
|
|
1038
|
+
var videoWidth = adVideoElement.videoWidth, videoHeight = adVideoElement.videoHeight;
|
|
1039
|
+
if (videoWidth <= 1 || videoHeight <= 1) {
|
|
1040
|
+
console.warn("[HlsAdPlayer] Ad creative has no usable video dimensions (".concat(videoWidth, "x").concat(videoHeight, ") - skipping this creative"));
|
|
1041
|
+
handleVideoDecodeFailure();
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1027
1044
|
var onPlaying = function onPlaying() {
|
|
1028
1045
|
startStallWatchdog();
|
|
1029
1046
|
if (!currentAd || trackingFired.start) return;
|
|
@@ -1075,6 +1092,10 @@ function createHlsAdPlayer(contentVideo, options) {
|
|
|
1075
1092
|
type: "timeupdate",
|
|
1076
1093
|
handler: onTimeUpdate
|
|
1077
1094
|
},
|
|
1095
|
+
{
|
|
1096
|
+
type: "loadedmetadata",
|
|
1097
|
+
handler: onLoadedMetadata
|
|
1098
|
+
},
|
|
1078
1099
|
{
|
|
1079
1100
|
type: "playing",
|
|
1080
1101
|
handler: onPlaying
|
|
@@ -1246,6 +1267,7 @@ function createHlsAdPlayer(contentVideo, options) {
|
|
|
1246
1267
|
clearInterval(stallWatchdogId);
|
|
1247
1268
|
stallWatchdogId = void 0;
|
|
1248
1269
|
}
|
|
1270
|
+
cancelVideoFrameProbe();
|
|
1249
1271
|
}
|
|
1250
1272
|
function noteAdProgress() {
|
|
1251
1273
|
lastAdProgressTime = Date.now();
|
|
@@ -1264,13 +1286,86 @@ function createHlsAdPlayer(contentVideo, options) {
|
|
|
1264
1286
|
}
|
|
1265
1287
|
handleAdError();
|
|
1266
1288
|
}
|
|
1289
|
+
function getDecodedVideoFrameCount(video) {
|
|
1290
|
+
try {
|
|
1291
|
+
if (typeof video.getVideoPlaybackQuality === "function") {
|
|
1292
|
+
var quality = video.getVideoPlaybackQuality();
|
|
1293
|
+
if (quality && typeof quality.totalVideoFrames === "number") {
|
|
1294
|
+
return quality.totalVideoFrames;
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
var legacyCount = video.webkitDecodedFrameCount;
|
|
1298
|
+
if (typeof legacyCount === "number") {
|
|
1299
|
+
return legacyCount;
|
|
1300
|
+
}
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
console.warn("[HlsAdPlayer] Unable to read decoded video frame count:", error);
|
|
1303
|
+
}
|
|
1304
|
+
return void 0;
|
|
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
|
+
}
|
|
1339
|
+
function handleVideoDecodeFailure() {
|
|
1340
|
+
console.warn("[HlsAdPlayer] Ad audio is advancing but no video frames decoded (black screen) - skipping this creative");
|
|
1341
|
+
clearStallWatchdog();
|
|
1342
|
+
if (currentAd) {
|
|
1343
|
+
fireTrackingPixels(currentAd.trackingUrls.error);
|
|
1344
|
+
}
|
|
1345
|
+
if (podIndex < podAds.length - 1 && advanceToNextPodAd()) {
|
|
1346
|
+
return;
|
|
1347
|
+
}
|
|
1348
|
+
handleAdError();
|
|
1349
|
+
}
|
|
1267
1350
|
function startStallWatchdog() {
|
|
1268
1351
|
clearStallWatchdog();
|
|
1269
1352
|
noteAdProgress();
|
|
1353
|
+
sawDecodedVideoFrame = false;
|
|
1354
|
+
startVideoFrameProbe();
|
|
1270
1355
|
stallWatchdogId = window.setInterval(function() {
|
|
1271
1356
|
if (!adPlaying || !adVideoElement) {
|
|
1272
1357
|
return;
|
|
1273
1358
|
}
|
|
1359
|
+
if (!sawDecodedVideoFrame && !adVideoElement.paused) {
|
|
1360
|
+
var decodedFrames = getDecodedVideoFrameCount(adVideoElement);
|
|
1361
|
+
if (decodedFrames !== void 0 && decodedFrames > 0) {
|
|
1362
|
+
sawDecodedVideoFrame = true;
|
|
1363
|
+
markFrameCounterReliable();
|
|
1364
|
+
} else if (decodedFrames === 0 && adVideoElement.currentTime >= BLACKFRAME_MIN_PLAYBACK_S && isFrameCounterReliable()) {
|
|
1365
|
+
handleVideoDecodeFailure();
|
|
1366
|
+
return;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1274
1369
|
if (adVideoElement.currentTime > lastAdProgressPosition + 0.05) {
|
|
1275
1370
|
noteAdProgress();
|
|
1276
1371
|
return;
|
|
@@ -6111,17 +6206,17 @@ var AdTimingService = /*#__PURE__*/ function() {
|
|
|
6111
6206
|
{
|
|
6112
6207
|
key: "logAdState",
|
|
6113
6208
|
value: function logAdState(event) {
|
|
6114
|
-
var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}
|
|
6115
|
-
var _ref;
|
|
6209
|
+
var extra = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {};
|
|
6116
6210
|
if (!this.debug) {
|
|
6117
6211
|
return;
|
|
6118
6212
|
}
|
|
6213
|
+
var _this_callbacks_getAdState = this.callbacks.getAdState(), inAdBreak = _this_callbacks_getAdState.inAdBreak, showAds = _this_callbacks_getAdState.showAds, adPlaying = _this_callbacks_getAdState.adPlaying;
|
|
6119
6214
|
console.log("[StormcloudVideoPlayer][AdState]", _object_spread({
|
|
6120
6215
|
event: event,
|
|
6121
6216
|
timestamp: /* @__PURE__ */ new Date().toISOString(),
|
|
6122
|
-
showAds: showAds
|
|
6123
|
-
adPlaying:
|
|
6124
|
-
inAdBreak: inAdBreak
|
|
6217
|
+
showAds: showAds,
|
|
6218
|
+
adPlaying: adPlaying,
|
|
6219
|
+
inAdBreak: inAdBreak,
|
|
6125
6220
|
activeAdRequestToken: this.activeAdRequestToken
|
|
6126
6221
|
}, extra));
|
|
6127
6222
|
}
|
|
@@ -8471,6 +8566,13 @@ var StormcloudVideoPlayer = /*#__PURE__*/ function() {
|
|
|
8471
8566
|
},
|
|
8472
8567
|
onFailsafeTimeout: function onFailsafeTimeout() {
|
|
8473
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
|
+
};
|
|
8474
8576
|
}
|
|
8475
8577
|
});
|
|
8476
8578
|
this.placeholder = new PlaceholderLayer(this.video, !!this.config.debugAdTiming);
|