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.
- package/dist/stormcloud-vp.min.js +2 -2
- package/lib/index.cjs +51 -6
- package/lib/index.cjs.map +1 -1
- package/lib/index.js +51 -6
- package/lib/index.js.map +1 -1
- package/lib/player/AdBreakOrchestrator.d.cts +1 -1
- package/lib/player/AdConfigManager.d.cts +1 -1
- package/lib/player/AdTimingService.cjs +5 -5
- package/lib/player/AdTimingService.cjs.map +1 -1
- package/lib/player/AdTimingService.d.cts +7 -2
- package/lib/player/HlsEngine.d.cts +1 -1
- package/lib/player/PlayerControls.d.cts +1 -1
- package/lib/player/Scte35CueManager.d.cts +1 -1
- package/lib/player/Scte35Parser.d.cts +1 -1
- package/lib/player/StormcloudVideoPlayer.cjs +51 -6
- package/lib/player/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/player/StormcloudVideoPlayer.d.cts +1 -1
- package/lib/player/playerTypes.d.cts +1 -1
- package/lib/players/HlsPlayer.cjs +51 -6
- package/lib/players/HlsPlayer.cjs.map +1 -1
- package/lib/players/HlsPlayer.d.cts +1 -1
- package/lib/players/index.cjs +51 -6
- package/lib/players/index.cjs.map +1 -1
- package/lib/sdk/hlsAdPlayer.cjs +39 -1
- package/lib/sdk/hlsAdPlayer.cjs.map +1 -1
- package/lib/sdk/hlsAdPlayer.d.cts +1 -1
- package/lib/ui/StormcloudVideoPlayer.cjs +51 -6
- package/lib/ui/StormcloudVideoPlayer.cjs.map +1 -1
- package/lib/ui/StormcloudVideoPlayer.d.cts +1 -1
- package/lib/utils/tracking.d.cts +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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,
|
|
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] : {}
|
|
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"]}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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 {
|
|
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-
|
|
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 {
|
|
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
|
-
|
|
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] : {}
|
|
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
|
|
6180
|
-
adPlaying:
|
|
6181
|
-
inAdBreak: inAdBreak
|
|
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);
|