@dolby-optiview/ads-sdk-core 1.0.0-beta.1 → 1.0.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/OptiViewAds.d.ts +7 -10
  2. package/dist/OptiViewAds.js +1 -1
  3. package/dist/generated/diagnosticCodes.d.ts +15 -0
  4. package/dist/generated/diagnosticCodes.js +1 -1
  5. package/dist/generated/version.d.ts +1 -1
  6. package/dist/generated/version.js +1 -1
  7. package/dist/index.d.ts +1 -0
  8. package/dist/services/AdPlayerController.d.ts +5 -0
  9. package/dist/services/AdPlayerController.js +1 -1
  10. package/dist/services/CmcdAdReporter.d.ts +3 -0
  11. package/dist/services/CmcdAdReporter.js +1 -1
  12. package/dist/services/GamStreamManager.js +1 -1
  13. package/dist/services/SsaiController.d.ts +3 -1
  14. package/dist/services/SsaiController.js +1 -1
  15. package/dist/services/adPlayer/AdPlayerEvents.d.ts +1 -0
  16. package/dist/services/adPlayer/AdPlayerEvents.js +1 -1
  17. package/dist/services/adPlayer/AdPlayerState.d.ts +2 -2
  18. package/dist/services/adPlayer/AdPlayerState.js +1 -1
  19. package/dist/services/adPlayer/AssetParameterResolver.d.ts +3 -0
  20. package/dist/services/adPlayer/AssetParameterResolver.js +1 -1
  21. package/dist/services/adPlayer/AssetPlayback.js +1 -1
  22. package/dist/services/adPlayer/GamPodBridge.d.ts +1 -1
  23. package/dist/services/adPlayer/GamPodBridge.js +1 -1
  24. package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
  25. package/dist/services/anvatoCueRuntime.d.ts +10 -0
  26. package/dist/services/anvatoCueRuntime.js +1 -0
  27. package/dist/services/assetParameterMacros.d.ts +2 -0
  28. package/dist/services/assetParameterMacros.js +1 -1
  29. package/dist/session/BreakLifecycle.js +1 -1
  30. package/dist/session/SessionStarter.d.ts +10 -0
  31. package/dist/session/SessionStarter.js +1 -1
  32. package/dist/session/SessionState.d.ts +3 -0
  33. package/dist/session/SessionState.js +1 -1
  34. package/dist/types/OptiViewAdsConfig.d.ts +6 -1
  35. package/package.json +4 -4
@@ -1,4 +1,4 @@
1
- import { OptiViewAdsConfig, StartSessionConfig } from './types/OptiViewAdsConfig';
1
+ import { OptiViewAdsConfig, StartSessionConfig, AssetParameterMacroUpdates } from './types/OptiViewAdsConfig';
2
2
  import { AdBreakStatus, OptiViewAdsEventType, OptiViewAdsEventHandler } from './types/Events';
3
3
  import { DiagnosticHandler, DiagnosticReport } from './diagnostics/DiagnosticEvent';
4
4
  /**
@@ -128,6 +128,12 @@ export declare class OptiViewAds {
128
128
  * the tag URL). No-op with a warning without an active session.
129
129
  * @category Lifecycle */
130
130
  updateAssetParameters(params: Record<string, string>): void;
131
+ /**
132
+ * Merge per-name macro updates; `null` removes a name and other names stay unchanged.
133
+ * Active GAM targeting is re-resolved via `replaceAdTagParameters()`, while VAST URLs use
134
+ * new values on the next request without re-requesting an already loaded ad.
135
+ * @category Lifecycle */
136
+ updateAssetParameterMacros(macros: AssetParameterMacroUpdates): void;
131
137
  private endSessionInternal;
132
138
  /**
133
139
  * Subscribe to an SDK event.
@@ -147,15 +153,6 @@ export declare class OptiViewAds {
147
153
  offDiagnostic(handler: DiagnosticHandler): void;
148
154
  /** Self-contained, redacted diagnostic report (no secrets or ad-tag-parameter values). @category Diagnostics */
149
155
  exportDiagnostics(): DiagnosticReport;
150
- /** Only Anvato break cues carry a schedulable instant; SSAI ID3, EXT-X-DATERANGE and emsg pass through untouched. */
151
- private armBreakFromPresentedCue;
152
- /**
153
- * Break ids not seen in a manifest before this session. A break that leaves and returns is
154
- * NOT re-reported: the metric is when the backend first signalled it.
155
- */
156
- private notedManifestBreakIds;
157
- /** Only `type=cue` frames are surfaced; captions/beacons arrive once per segment and would flood the buffer. */
158
- private diagnoseAnvatoCueObserved;
159
156
  /** Push to the ring buffer, notify subscribers, mirror to the console in debug. Category/level come from the code catalog. */
160
157
  private diagnose;
161
158
  /** Whether an ad is currently playing. @category Playback & UI */
@@ -1 +1 @@
1
- import{DEFAULT_MODE as e,DEFAULT_AD_PRELOAD_MODE as t,DEFAULT_AD_INSERTION_MODE as s,DEFAULT_AD_BREAK_CUT_SAFETY_MARGIN_SEC as r,DEFAULT_DOUBLE_BOX_AUDIO as i,resolveChaining as a,resolveTuneIn as n,resolveBreakWarnings as o,resolvePdtGraceSeconds as d}from"./types/OptiViewAdsConfig";import{SsaiController as l}from"./services/SsaiController";import{resolvePreloadMode as h}from"./services/detectPreloadMode";import{isSingleSurfaceWebDevice as c,resolveInsertionMode as u}from"./services/detectInsertionMode";import{ManifestService as m}from"./services/ManifestService";import{BreakScheduler as p}from"./services/BreakScheduler";import{MetadataCueTracker as g,isAnvatoCue as k,parseAnvatoPayload as f}from"./services/MetadataCueTracker";import{AdPlayerController as y}from"./services/AdPlayerController";import{effectiveFormat as S}from"./services/pictureInPicture";import{resolveBreakFormat as A}from"./services/AdBreakSequencer";import{connectPauseAd as v}from"./services/adPlayer/connectPauseAd";import{BreakTransitionTimer as b}from"./services/BreakTransitionTimer";import{LensReporter as C}from"./services/LensReporter";import{CmcdAdReporter as P}from"./services/CmcdAdReporter";import{DiagnosticBuffer as T}from"./diagnostics/DiagnosticBuffer";import{DIAGNOSTIC_CODES as B}from"./diagnostics/errorCodes";import{SDK_VERSION as w}from"./generated/version";import{playbackErrorContext as D}from"./diagnostics/playbackErrorContext";import{debugLog as E}from"./diagnostics/debugLog";import{AdBreakStatusTracker as I}from"./session/AdBreakStatusTracker";import{BreakLifecycle as O}from"./session/BreakLifecycle";import{SessionStarter as R}from"./session/SessionStarter";import{SessionState as M}from"./session/SessionState";export class OptiViewAds{static get version(){return w}constructor(l){if(this.contentErrorReloadGuard=null,this.CONTENT_ERROR_RELOAD_GUARD_MS=5e3,this.eventHandlers=new Map,this.diagnosticHandlers=new Set,this.destroyed=!1,this.continueContentDuringBreak=!1,this.state=new M,this.metadataCueTracker=null,this.anvatoObservedLogged=new Set,this.seenManifestBreakIds=new Set,this.player=l.player,this.debug=l.debug??!1,this.mode=l.mode??e,this.hasExplicitMode=void 0!==l.mode,this.lensReporter=new C((...e)=>this.log(...e)),this.cmcdAdReporter=new P(()=>this.lensReporter.handle,(...e)=>this.log(...e)),this.adBreakStatusTracker=new I({emit:(e,t)=>this.emit(e,t),effectiveFormatOf:e=>this.effectiveFormatOf(e),secondsUntilBreak:e=>this.breakScheduler?.getSecondsUntilBreak(e)??void 0,breakTransitionTimer:()=>this.breakTransitionTimer}),this.onContentSeeked=()=>{const e=this.breakScheduler?.getActiveBreak();if(!e?.controls?.snapback)return;if(this.state.snapbackSeekInFlight)return void(this.state.snapbackSeekInFlight=!1);const t="number"==typeof e.start?e.start:this.player.currentTime;Math.abs(this.player.currentTime-t)<=.5||(this.log("Snapback: seek detected during locked break, snapping back to break start"),this.state.snapbackSeekInFlight=!0,this.player.seek(t))},this.onContentWaiting=()=>{this.adPlayerController?.isAdPlaying()||this.emit("waiting",{type:"waiting",timestamp:Date.now(),source:"content"})},this.onContentPlaying=()=>{this.adPlayerController?.isAdPlaying()||(this.breakScheduler?.markPlaybackStart(),this.breakTransitionTimer?.settle("out-of-break"),!1!==this.pauseAdController?.onResume()?this.emit("playing",{type:"playing",timestamp:Date.now(),source:"content"}):this.player.pause())},this.onContentPause=()=>{this.adPlayerController?.isAdPlaying()||this.breakScheduler?.isAdStartDelayActive()||this.pauseAdController?.onPause()},this.onContentEnded=()=>{this.adPlayerController?.isAdPlaying()||this.breakScheduler?.markPlaybackEnd()},this.onContentError=e=>{const t=this.toPlaybackError(e,"Content playback error");this.diagnose("DA-CONTENT-PLAYBACK-ERROR",t.message,D(e)),this.breakScheduler?.setContentReloading(!0),this.contentErrorReloadGuard&&clearTimeout(this.contentErrorReloadGuard),this.contentErrorReloadGuard=setTimeout(()=>{this.contentErrorReloadGuard=null,this.breakScheduler?.setContentReloading(!1)},this.CONTENT_ERROR_RELOAD_GUARD_MS),this.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"content",error:t})},this.onTimedMetadata=e=>{if(!e)return;const t=e,s=this.metadataCueTracker?.handleCue(t)??!1;this.diagnoseAnvatoCueObserved(t,s),"presented"===t.phase&&this.armBreakFromPresentedCue(t)},this.continueContentDuringBreak=l.continueContentDuringBreak??!1,"ssai"===this.mode)return void this.initSsai(l);if(!l.createAdAdapter)throw new Error("[OptiViewAds] createAdAdapter is required. Use the @dolby-optiview/ads-sdk package for a default HLS.js ad player, or supply your own createAdAdapter factory.");this.state.steeredSsaiOptions={maxBitrate:l.maxBitrate,autoplay:l.autoplay??!1,streamActivityMonitorId:l.gam?.streamActivityMonitorId};const k=h(l.adPreload??t),f=u(l.adInsertion??s),S=a(l.chaining),A=n(l.tuneIn),B=o(l.breakWarnings),w=d(l.pdtGraceSeconds);"anvatoCue"===l.ptsSource&&(this.metadataCueTracker=new g),this.diagnosticBuffer=new T(l.diagnostics?.bufferSize),this.breakTransitionTimer=new b(e=>{this.diagnose("DA-BREAK-TRANSITION",`Transition ${e.kind} took ${e.durationMs}ms`,{transition:e.kind,durationMs:e.durationMs,breakId:e.breakId,adPreload:k,adInsertion:f,...e.phases?{phases:e.phases}:{}})}),this.state.configSummary={mode:this.mode,adPreload:k,adInsertion:f,chaining:{enabled:S.enabled,maxGapSeconds:S.maxGapSeconds},tuneIn:{enabled:A.enabled,minBreakDurationSeconds:A.minBreakDurationSeconds},breakWarnings:{seconds:B.seconds},ptsSource:l.ptsSource??"mediaTime",continueContentDuringBreak:this.continueContentDuringBreak,gam:l.gam?{hasAssetParameters:!1,streamActivityMonitorId:l.gam.streamActivityMonitorId}:void 0};const E=l.adPreload??t;this.diagnose("DA-PRELOAD-MODE-RESOLVED",`Ad preload mode: ${k}`+(E!==k?` (configured "${E}")`:""),{configured:E,resolved:k}),"shared-element"===l.adInsertion&&"overlay"===f&&this.diagnose("DA-SHARED-ELEMENT-UNSUPPORTED",'adInsertion "shared-element" is unsupported on this platform (non-iPhone/iPod); fell back to "overlay".',{configured:"shared-element",resolved:f}),this.manifestService=new m(this.debug,l.interceptManifestResponse,(e,t,s)=>this.diagnose(e,t,s),l.interceptManifestRequest),this.breakScheduler=new p(this.player,this.debug,S,void 0,A,(e,t,s)=>this.diagnose(e,t,s),B,this.metadataCueTracker,w),this.adPlayerController=new y(l.player,l.container,l.playerContainer,l.createAdAdapter,this.debug,l.gam,k,f,l.adBreakCutSafetyMarginSec??r,l.doubleBoxAudio??i,c("undefined"!=typeof navigator?navigator.userAgent:"","undefined"!=typeof navigator?navigator.maxTouchPoints??0:0)),this.adPlayerController.setTransitionPhaseReporter(this.debug?e=>this.breakTransitionTimer?.phase(e):null),this.adPlayerController.setChainResolver(S.enabled?e=>this.breakScheduler.getChainedSuccessor(e):null),this.adPlayerController.initialize();const L=(e,t)=>void 0===e||void 0===t?{}:{adIndex:e,totalAds:t};this.adPlayerController.setGamEventCallbacks({onAdFirstQuartile:(e,t,s,r)=>{this.emit("adfirstquartile",{type:"adfirstquartile",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...L(s,r)})},onAdMidpoint:(e,t,s,r)=>{this.emit("admidpoint",{type:"admidpoint",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...L(s,r)})},onAdThirdQuartile:(e,t,s,r)=>{this.emit("adthirdquartile",{type:"adthirdquartile",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...L(s,r)})}}),this.adPlayerController.setDiagnoseHandler((e,t,s)=>{this.diagnose(e,t,s)}),this.pauseAdController=v(this.adPlayerController,{debug:this.debug,diagnose:(e,t,s)=>this.diagnose(e,t,s)}),this.adPlayerController.setVolumeChangeCallback((e,t)=>{this.emit("volumechange",{type:"volumechange",timestamp:Date.now(),muted:e,volume:t})}),this.breakLifecycle=new O(this.state,{adPlayerController:this.adPlayerController,breakScheduler:this.breakScheduler,manifestService:this.manifestService,breakTransitionTimer:this.breakTransitionTimer,cmcdAdReporter:this.cmcdAdReporter,adBreakStatusTracker:this.adBreakStatusTracker,emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e),effectiveFormatOf:e=>this.effectiveFormatOf(e),onBreakSettled:()=>this.sessionStarter?.applyPendingChannelSwitch()}),this.sessionStarter=new R(this.state,{player:this.player,debug:this.debug,mode:this.mode,hasExplicitMode:this.hasExplicitMode,continueContentDuringBreak:this.continueContentDuringBreak,manifestService:this.manifestService,breakScheduler:this.breakScheduler,adPlayerController:this.adPlayerController,pauseAdController:this.pauseAdController,breakLifecycle:this.breakLifecycle,cmcdAdReporter:this.cmcdAdReporter,lensReporter:this.lensReporter,adBreakStatusTracker:this.adBreakStatusTracker,emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e),endSessionInternal:e=>this.endSessionInternal(e),notedManifestBreakIds:e=>this.notedManifestBreakIds(e)});for(const[e,t]of this.contentListeners())this.player.on(e,t);this.log("OptiViewAds SDK initialized")}initSsai(e){if(!e.gam)throw new Error('[OptiViewAds] mode "ssai" requires a gam config.');if(!e.ssai?.networkCode)throw new Error('[OptiViewAds] mode "ssai" requires an ssai config (networkCode).');const t=e.ssai;this.diagnosticBuffer=new T(e.diagnostics?.bufferSize);const s=e.autoplay??!1;this.state.configSummary={mode:this.mode,adPreload:"n/a",adInsertion:"ssai",chaining:{enabled:!1,maxGapSeconds:0},tuneIn:{enabled:!1,minBreakDurationSeconds:0},gam:{networkCode:t.networkCode,hasAssetParameters:!1,streamActivityMonitorId:e.gam.streamActivityMonitorId},ssai:{maxBitrate:e.maxBitrate,autoplay:s},continueContentDuringBreak:this.continueContentDuringBreak},this.state.ssaiController=new l(this.player,{gam:e.gam,ssai:t,maxBitrate:e.maxBitrate,autoplay:s,debug:this.debug},{emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e)}),this.player.on("volumechange",()=>{this.emit("volumechange",{type:"volumechange",timestamp:Date.now(),muted:this.player.muted,volume:this.player.volume})}),this.sessionStarter=new R(this.state,{player:this.player,debug:this.debug,mode:this.mode,hasExplicitMode:this.hasExplicitMode,continueContentDuringBreak:this.continueContentDuringBreak,manifestService:this.manifestService,breakScheduler:this.breakScheduler,adPlayerController:this.adPlayerController,pauseAdController:this.pauseAdController,breakLifecycle:this.breakLifecycle,cmcdAdReporter:this.cmcdAdReporter,lensReporter:this.lensReporter,adBreakStatusTracker:this.adBreakStatusTracker,emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e),endSessionInternal:e=>this.endSessionInternal(e),notedManifestBreakIds:e=>this.notedManifestBreakIds(e)});for(const[e,t]of this.contentListeners())this.player.on(e,t);this.log("OptiViewAds SDK initialized (SSAI mode)")}contentListeners(){const e=[["seeked",this.onContentSeeked],["waiting",this.onContentWaiting],["playing",this.onContentPlaying],["pause",this.onContentPause],["ended",this.onContentEnded],["error",this.onContentError]];return this.metadataCueTracker&&e.push(["timedmetadata",this.onTimedMetadata]),e}async startSession(e){return this.sessionStarter.startSession(e)}play(){return this.adPlayerController?.isAdPlaying()?(this.log("play() suppressed — ad break is active"),Promise.resolve()):this.player.play()}pause(){this.adPlayerController?.isAdPlaying()?this.log("pause() suppressed — ad break is active"):this.player.pause()}seek(e){if(!this.state.sessionActive)return;const t=this.breakScheduler?.getActiveBreak();t?.controls?.snapback?this.log("seek() suppressed — break has snapback enabled"):(this.breakScheduler?.notifyViewerSeek(),this.player.seek(e))}get muted(){return this.adPlayerController?this.adPlayerController.muted:this.player.muted}set muted(e){this.adPlayerController?this.adPlayerController.muted=e:this.player.muted=e}get volume(){return this.adPlayerController?this.adPlayerController.volume:this.player.volume}set volume(e){this.adPlayerController?this.adPlayerController.volume=e:this.player.volume=e}skipAd(){const e=this.adBreakStatusTracker.activeBreak;if(!this.state.sessionActive||!this.adPlayerController?.isAdPlaying()||!e)return this.log("skipAd() suppressed — no active ad break"),!1;const t=e.controls?.skipOffset;if(null==t)return this.log(`skipAd() suppressed — break '${e.id}' declares no controls.skipOffset`),this.diagnose("DA-AD-SKIP-SUPPRESSED","Skip requested but break declares no skipOffset",{breakId:e.id}),!1;const s=this.adBreakStatusTracker.activeBreakElapsedSec;return s<t?(this.log(`skipAd() suppressed — skipOffset ${t}s not reached (${s}s)`),this.diagnose("DA-AD-SKIP-SUPPRESSED","Skip requested before skipOffset",{breakId:e.id,skipOffset:t,elapsedSec:s}),!1):(this.log(`skipAd() — skipping break '${e.id}' at ${s}s (skipOffset ${t}s)`),this.diagnose("DA-AD-SKIPPED","Break skipped by viewer",{breakId:e.id,skipOffset:t,elapsedSec:s}),this.cmcdAdReporter.onSkipRequested(e.id),this.adPlayerController.skipBreak(),!0)}clickAd(){const e=this.adBreakStatusTracker.activeBreak,t=this.adPlayerController?.currentAdAsset()??null;if(!(this.state.sessionActive&&this.adPlayerController?.isAdPlaying()&&e&&t))return this.log("clickAd() suppressed — no ad on screen"),null;const s=t.interaction?.clickThrough;return this.diagnose("DA-AD-CLICKED","Ad clicked",{breakId:e.id,assetId:t.id,clickThrough:s??""}),this.emit("adclick",{type:"adclick",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...s?{clickThrough:s}:{}}),s??null}endSession(){this.state.sessionActive&&this.endSessionInternal()}updateAssetParameters(e){if(this.state.sessionActive){if("ssai"===this.mode||this.state.steeredSsaiActive)return this.state.ssaiController.updateAssetParameters(e),void this.log("Asset parameters updated (SSAI)");this.adPlayerController.updateAssetParameters(e),this.log("Asset parameters updated")}else console.warn("[OptiViewAds] updateAssetParameters called but no active session.")}endSessionInternal(e="end-session"){this.state.sessionActive=!1,this.state.sessionGeneration++,this.breakLifecycle?.reportAbandonedBreak(e),"ssai"===this.mode?this.state.ssaiController?.stop():(this.state.steeredSsaiActive&&(this.state.ssaiController?.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1),this.manifestService?.reset(),this.breakScheduler?.stop(),this.breakScheduler?.destroy(),this.adBreakStatusTracker.resetAdBreakStatus(),this.state.pauseAdTickInterval&&(clearInterval(this.state.pauseAdTickInterval),this.state.pauseAdTickInterval=null),this.pauseAdController?.reset(),this.breakTransitionTimer?.reset(),this.adPlayerController?.stopActiveBreak(),this.adPlayerController?.releaseContentHold(),this.adPlayerController?.endGamSession()),this.state.clearSessionIdentity(),this.anvatoObservedLogged.clear(),this.seenManifestBreakIds.clear(),this.cmcdAdReporter.onSessionEnd(),this.lensReporter.endSession(),this.diagnose("DA-SESSION-ENDED","Session ended"),this.log("Session ended")}addEventListener(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}removeEventListener(e,t){const s=this.eventHandlers.get(e);s&&s.delete(t)}emit(e,t){this.recordEventTimeline(t),this.cmcdAdReporter.onEvent(t);const s=this.eventHandlers.get(e);if(s)for(const e of s)try{e(t)}catch(e){this.log("Event handler error:",e)}}recordEventTimeline(e){const t={eventType:e.type},s=e;s.source&&(t.source=s.source),s.break?.id&&(t.breakId=s.break.id),s.asset?.id&&(t.assetId=s.asset.id),this.diagnosticBuffer.record({ts:e.timestamp,level:"aderror"===e.type?"error":"info",code:"DA-EVENT",category:"event",message:`SDK event: ${e.type}`,context:t})}onDiagnostic(e){this.diagnosticHandlers.add(e)}offDiagnostic(e){this.diagnosticHandlers.delete(e)}exportDiagnostics(){const e={...this.state.configSummary,...this.state.manifestOrigin?{manifestOrigin:this.state.manifestOrigin}:{},...this.state.configSummary.ssai&&this.state.stitcherOrigin?{ssai:{...this.state.configSummary.ssai,stitcherOrigin:this.state.stitcherOrigin}}:{}};return{sdkVersion:w,generatedAt:Date.now(),userAgent:"undefined"!=typeof navigator?navigator.userAgent:null,config:e,diagnostics:this.diagnosticBuffer.getAll()}}armBreakFromPresentedCue(e){if(!this.metadataCueTracker||!this.breakScheduler||!k(e))return;const t=f(e.messageData??"");if("cue"!==t.type)return;const s=Number(t.pts);Number.isFinite(s)&&this.breakScheduler.notifyCuePresented(s)}notedManifestBreakIds(e){const t=[];for(const s of e.breaks)this.seenManifestBreakIds.has(s.id)||(this.seenManifestBreakIds.add(s.id),t.push(s.id));return t}diagnoseAnvatoCueObserved(e,t){if(!this.metadataCueTracker||!k(e))return;const s=e.messageData??"",r=f(s);if("cue"!==r.type)return;const i=`${e.startTime}|${s}`;if(this.anvatoObservedLogged.has(i))return;this.anvatoObservedLogged.add(i);const a=Number(r.pts);this.diagnose("DA-ANVATO-CUE-OBSERVED",`Anvato cue observed: pts=${r.pts} at media time ${e.startTime}s`,{pts:Number.isFinite(a)?a:r.pts,mediaTime:e.startTime,playheadSec:this.player.currentTime,recorded:t,payload:s.slice(0,300)})}diagnose(e,t,s){if(this.destroyed)return;const r=B[e],i={ts:Date.now(),level:r?.level??"info",code:e,category:r?.category??"unknown",message:t,...s?{context:s}:{}};this.diagnosticBuffer.record(i);for(const e of this.diagnosticHandlers)try{e(i)}catch(e){this.log("Diagnostic handler error:",e)}this.debug&&E("[OptiViewAds]",e,t,s??"")}isAdPlaying(){return this.adPlayerController?.isAdPlaying()??!1}isPictureInPicture(){return this.adPlayerController?.isPictureInPicture()??!1}getPresentationState(){return{pictureInPicture:this.isPictureInPicture(),showing:this.adPlayerController?.isShowingAd()?"ad":"content",insertion:this.adPlayerController?.currentInsertionMode()??"overlay",windowOwner:this.adPlayerController?.pictureInPictureOwner()??null}}effectiveFormatOf(e){const t=this.adPlayerController?.declaredFormatOf(e)??A(e);return S(t,this.isPictureInPicture())}isSessionActive(){return this.state.sessionActive}getAdBreakStatus(){return this.adBreakStatusTracker.getAdBreakStatus()}destroy(){this.contentErrorReloadGuard&&(clearTimeout(this.contentErrorReloadGuard),this.contentErrorReloadGuard=null);for(const[e,t]of this.contentListeners())this.player.off(e,t);this.state.sessionActive&&this.endSessionInternal("destroy"),this.manifestService?.destroy(),this.pauseAdController?.destroy(),this.adPlayerController?.destroy(),this.state.ssaiController?.stop(),this.lensReporter.destroy(),this.eventHandlers.clear(),this.diagnosticHandlers.clear(),this.destroyed=!0,this.log("Destroyed")}toPlaybackError(e,t){const s=e;if(s&&("number"==typeof s.code||s.message)){const e="number"==typeof s.code?` (code ${s.code})`:"",r=s.message?`: ${s.message}`:"";return new Error(`${t}${e}${r}`)}return new Error(t)}log(...e){this.debug&&E("[OptiViewAds]",...e)}}
1
+ import{DEFAULT_MODE as e,DEFAULT_AD_PRELOAD_MODE as t,DEFAULT_AD_INSERTION_MODE as s,DEFAULT_AD_BREAK_CUT_SAFETY_MARGIN_SEC as r,DEFAULT_DOUBLE_BOX_AUDIO as i,resolveChaining as a,resolveTuneIn as n,resolveBreakWarnings as o,resolvePdtGraceSeconds as d}from"./types/OptiViewAdsConfig";import{SsaiController as l}from"./services/SsaiController";import{resolvePreloadMode as h}from"./services/detectPreloadMode";import{isSingleSurfaceWebDevice as c,resolveInsertionMode as u}from"./services/detectInsertionMode";import{ManifestService as m}from"./services/ManifestService";import{BreakScheduler as p}from"./services/BreakScheduler";import{MetadataCueTracker as g}from"./services/MetadataCueTracker";import{armBreakFromPresentedCue as k,diagnoseAnvatoCueObserved as f,notedManifestBreakIds as y}from"./services/anvatoCueRuntime";import{AdPlayerController as S}from"./services/AdPlayerController";import{effectiveFormat as A}from"./services/pictureInPicture";import{resolveBreakFormat as v}from"./services/AdBreakSequencer";import{connectPauseAd as C}from"./services/adPlayer/connectPauseAd";import{BreakTransitionTimer as b}from"./services/BreakTransitionTimer";import{LensReporter as P}from"./services/LensReporter";import{CmcdAdReporter as T}from"./services/CmcdAdReporter";import{DiagnosticBuffer as w}from"./diagnostics/DiagnosticBuffer";import{DIAGNOSTIC_CODES as B}from"./diagnostics/errorCodes";import{SDK_VERSION as I}from"./generated/version";import{playbackErrorContext as D}from"./diagnostics/playbackErrorContext";import{debugLog as E}from"./diagnostics/debugLog";import{AdBreakStatusTracker as R}from"./session/AdBreakStatusTracker";import{BreakLifecycle as O}from"./session/BreakLifecycle";import{SessionStarter as M}from"./session/SessionStarter";import{SessionState as L}from"./session/SessionState";export class OptiViewAds{static get version(){return I}constructor(l){if(this.contentErrorReloadGuard=null,this.CONTENT_ERROR_RELOAD_GUARD_MS=5e3,this.eventHandlers=new Map,this.diagnosticHandlers=new Set,this.destroyed=!1,this.continueContentDuringBreak=!1,this.state=new L,this.metadataCueTracker=null,this.anvatoObservedLogged=new Set,this.seenManifestBreakIds=new Set,this.player=l.player,this.debug=l.debug??!1,this.mode=l.mode??e,this.hasExplicitMode=void 0!==l.mode,this.lensReporter=new P((...e)=>this.log(...e)),this.cmcdAdReporter=new T(()=>this.lensReporter.handle,(...e)=>this.log(...e)),this.adBreakStatusTracker=new R({emit:(e,t)=>this.emit(e,t),effectiveFormatOf:e=>this.effectiveFormatOf(e),secondsUntilBreak:e=>this.breakScheduler?.getSecondsUntilBreak(e)??void 0,breakTransitionTimer:()=>this.breakTransitionTimer}),this.onContentSeeked=()=>{const e=this.breakScheduler?.getActiveBreak();if(!e?.controls?.snapback)return;if(this.state.snapbackSeekInFlight)return void(this.state.snapbackSeekInFlight=!1);const t="number"==typeof e.start?e.start:this.player.currentTime;Math.abs(this.player.currentTime-t)<=.5||(this.log("Snapback: seek detected during locked break, snapping back to break start"),this.state.snapbackSeekInFlight=!0,this.player.seek(t))},this.onContentWaiting=()=>{this.adPlayerController?.isAdPlaying()||this.emit("waiting",{type:"waiting",timestamp:Date.now(),source:"content"})},this.onContentPlaying=()=>{this.adPlayerController?.isAdPlaying()||(this.breakScheduler?.markPlaybackStart(),this.breakTransitionTimer?.settle("out-of-break"),!1!==this.pauseAdController?.onResume()?this.emit("playing",{type:"playing",timestamp:Date.now(),source:"content"}):this.player.pause())},this.onContentPause=()=>{this.adPlayerController?.isAdPlaying()||this.breakScheduler?.isAdStartDelayActive()||this.pauseAdController?.onPause()},this.onContentEnded=()=>{this.adPlayerController?.isAdPlaying()||this.breakScheduler?.markPlaybackEnd()},this.onContentError=e=>{this.sessionStarter?.cancelHeldImmediatePreRollOnContentError();const t=this.toPlaybackError(e,"Content playback error");this.diagnose("DA-CONTENT-PLAYBACK-ERROR",t.message,D(e)),this.breakScheduler?.setContentReloading(!0),this.contentErrorReloadGuard&&clearTimeout(this.contentErrorReloadGuard),this.contentErrorReloadGuard=setTimeout(()=>{this.contentErrorReloadGuard=null,this.breakScheduler?.setContentReloading(!1)},this.CONTENT_ERROR_RELOAD_GUARD_MS),this.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"content",error:t})},this.onTimedMetadata=e=>{if(!e)return;const t=e,s=this.metadataCueTracker?.handleCue(t)??!1;f(t,s,this.metadataCueTracker,this.anvatoObservedLogged,this.player.currentTime,(e,t,s)=>this.diagnose(e,t,s)),"presented"===t.phase&&k(t,this.metadataCueTracker,this.breakScheduler)},this.continueContentDuringBreak=l.continueContentDuringBreak??!1,"ssai"===this.mode)return void this.initSsai(l);if(!l.createAdAdapter)throw new Error("[OptiViewAds] createAdAdapter is required. Use the @dolby-optiview/ads-sdk package for a default HLS.js ad player, or supply your own createAdAdapter factory.");this.state.steeredSsaiOptions={maxBitrate:l.maxBitrate,autoplay:l.autoplay??!1,streamActivityMonitorId:l.gam?.streamActivityMonitorId};const A=h(l.adPreload??t),v=u(l.adInsertion??s),B=a(l.chaining),I=n(l.tuneIn),E=o(l.breakWarnings),x=d(l.pdtGraceSeconds);"anvatoCue"===l.ptsSource&&(this.metadataCueTracker=new g),this.diagnosticBuffer=new w(l.diagnostics?.bufferSize),this.breakTransitionTimer=new b(e=>{this.diagnose("DA-BREAK-TRANSITION",`Transition ${e.kind} took ${e.durationMs}ms`,{transition:e.kind,durationMs:e.durationMs,breakId:e.breakId,adPreload:A,adInsertion:v,...e.phases?{phases:e.phases}:{}})}),this.state.configSummary={mode:this.mode,adPreload:A,adInsertion:v,chaining:{enabled:B.enabled,maxGapSeconds:B.maxGapSeconds},tuneIn:{enabled:I.enabled,minBreakDurationSeconds:I.minBreakDurationSeconds},breakWarnings:{seconds:E.seconds},ptsSource:l.ptsSource??"mediaTime",continueContentDuringBreak:this.continueContentDuringBreak,gam:l.gam?{hasAssetParameters:!1,streamActivityMonitorId:l.gam.streamActivityMonitorId}:void 0};const H=l.adPreload??t;this.diagnose("DA-PRELOAD-MODE-RESOLVED",`Ad preload mode: ${A}`+(H!==A?` (configured "${H}")`:""),{configured:H,resolved:A}),"shared-element"===l.adInsertion&&"overlay"===v&&this.diagnose("DA-SHARED-ELEMENT-UNSUPPORTED",'adInsertion "shared-element" is unsupported on this platform (non-iPhone/iPod); fell back to "overlay".',{configured:"shared-element",resolved:v}),this.manifestService=new m(this.debug,l.interceptManifestResponse,(e,t,s)=>this.diagnose(e,t,s),l.interceptManifestRequest),this.breakScheduler=new p(this.player,this.debug,B,void 0,I,(e,t,s)=>this.diagnose(e,t,s),E,this.metadataCueTracker,x),this.adPlayerController=new S(l.player,l.container,l.playerContainer,l.createAdAdapter,this.debug,l.gam,A,v,l.adBreakCutSafetyMarginSec??r,l.doubleBoxAudio??i,c("undefined"!=typeof navigator?navigator.userAgent:"","undefined"!=typeof navigator?navigator.maxTouchPoints??0:0)),this.adPlayerController.setTransitionPhaseReporter(this.debug?e=>this.breakTransitionTimer?.phase(e):null),this.adPlayerController.setChainResolver(B.enabled?e=>this.breakScheduler.getChainedSuccessor(e):null),this.adPlayerController.initialize();const G=(e,t)=>void 0===e||void 0===t?{}:{adIndex:e,totalAds:t};this.adPlayerController.setGamEventCallbacks({onAdFirstQuartile:(e,t,s,r)=>{this.emit("adfirstquartile",{type:"adfirstquartile",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...G(s,r)})},onAdMidpoint:(e,t,s,r)=>{this.emit("admidpoint",{type:"admidpoint",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...G(s,r)})},onAdThirdQuartile:(e,t,s,r)=>{this.emit("adthirdquartile",{type:"adthirdquartile",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...G(s,r)})}}),this.adPlayerController.setDiagnoseHandler((e,t,s)=>{this.diagnose(e,t,s)}),this.pauseAdController=C(this.adPlayerController,{debug:this.debug,diagnose:(e,t,s)=>this.diagnose(e,t,s)}),this.adPlayerController.setVolumeChangeCallback((e,t)=>{this.emit("volumechange",{type:"volumechange",timestamp:Date.now(),muted:e,volume:t})}),this.breakLifecycle=new O(this.state,{adPlayerController:this.adPlayerController,breakScheduler:this.breakScheduler,manifestService:this.manifestService,breakTransitionTimer:this.breakTransitionTimer,cmcdAdReporter:this.cmcdAdReporter,adBreakStatusTracker:this.adBreakStatusTracker,emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e),effectiveFormatOf:e=>this.effectiveFormatOf(e),onBreakSettled:()=>this.sessionStarter?.applyPendingChannelSwitch()}),this.sessionStarter=new M(this.state,{player:this.player,debug:this.debug,mode:this.mode,hasExplicitMode:this.hasExplicitMode,continueContentDuringBreak:this.continueContentDuringBreak,manifestService:this.manifestService,breakScheduler:this.breakScheduler,adPlayerController:this.adPlayerController,pauseAdController:this.pauseAdController,breakLifecycle:this.breakLifecycle,cmcdAdReporter:this.cmcdAdReporter,lensReporter:this.lensReporter,adBreakStatusTracker:this.adBreakStatusTracker,emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e),endSessionInternal:e=>this.endSessionInternal(e),notedManifestBreakIds:e=>y(e,this.seenManifestBreakIds)});for(const[e,t]of this.contentListeners())this.player.on(e,t);this.log("OptiViewAds SDK initialized")}initSsai(e){if(!e.gam)throw new Error('[OptiViewAds] mode "ssai" requires a gam config.');if(!e.ssai?.networkCode)throw new Error('[OptiViewAds] mode "ssai" requires an ssai config (networkCode).');const t=e.ssai;this.diagnosticBuffer=new w(e.diagnostics?.bufferSize);const s=e.autoplay??!1;this.state.configSummary={mode:this.mode,adPreload:"n/a",adInsertion:"ssai",chaining:{enabled:!1,maxGapSeconds:0},tuneIn:{enabled:!1,minBreakDurationSeconds:0},gam:{networkCode:t.networkCode,hasAssetParameters:!1,streamActivityMonitorId:e.gam.streamActivityMonitorId},ssai:{maxBitrate:e.maxBitrate,autoplay:s},continueContentDuringBreak:this.continueContentDuringBreak},this.state.ssaiController=new l(this.player,{gam:e.gam,ssai:t,maxBitrate:e.maxBitrate,autoplay:s,debug:this.debug},{emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e)}),this.player.on("volumechange",()=>{this.emit("volumechange",{type:"volumechange",timestamp:Date.now(),muted:this.player.muted,volume:this.player.volume})}),this.sessionStarter=new M(this.state,{player:this.player,debug:this.debug,mode:this.mode,hasExplicitMode:this.hasExplicitMode,continueContentDuringBreak:this.continueContentDuringBreak,manifestService:this.manifestService,breakScheduler:this.breakScheduler,adPlayerController:this.adPlayerController,pauseAdController:this.pauseAdController,breakLifecycle:this.breakLifecycle,cmcdAdReporter:this.cmcdAdReporter,lensReporter:this.lensReporter,adBreakStatusTracker:this.adBreakStatusTracker,emit:(e,t)=>this.emit(e,t),diagnose:(e,t,s)=>this.diagnose(e,t,s),log:(...e)=>this.log(...e),endSessionInternal:e=>this.endSessionInternal(e),notedManifestBreakIds:e=>y(e,this.seenManifestBreakIds)});for(const[e,t]of this.contentListeners())this.player.on(e,t);this.log("OptiViewAds SDK initialized (SSAI mode)")}contentListeners(){const e=[["seeked",this.onContentSeeked],["waiting",this.onContentWaiting],["playing",this.onContentPlaying],["pause",this.onContentPause],["ended",this.onContentEnded],["error",this.onContentError]];return this.metadataCueTracker&&e.push(["timedmetadata",this.onTimedMetadata]),e}async startSession(e){return this.sessionStarter.startSession(e)}play(){return this.adPlayerController?.isAdPlaying()?(this.log("play() suppressed — ad break is active"),Promise.resolve()):this.player.play()}pause(){this.adPlayerController?.isAdPlaying()?this.log("pause() suppressed — ad break is active"):this.player.pause()}seek(e){if(!this.state.sessionActive)return;const t=this.breakScheduler?.getActiveBreak();t?.controls?.snapback?this.log("seek() suppressed — break has snapback enabled"):(this.breakScheduler?.notifyViewerSeek(),this.player.seek(e))}get muted(){return this.adPlayerController?this.adPlayerController.muted:this.player.muted}set muted(e){this.adPlayerController?this.adPlayerController.muted=e:this.player.muted=e}get volume(){return this.adPlayerController?this.adPlayerController.volume:this.player.volume}set volume(e){this.adPlayerController?this.adPlayerController.volume=e:this.player.volume=e}skipAd(){const e=this.adBreakStatusTracker.activeBreak;if(!this.state.sessionActive||!this.adPlayerController?.isAdPlaying()||!e)return this.log("skipAd() suppressed — no active ad break"),!1;const t=e.controls?.skipOffset;if(null==t)return this.log(`skipAd() suppressed — break '${e.id}' declares no controls.skipOffset`),this.diagnose("DA-AD-SKIP-SUPPRESSED","Skip requested but break declares no skipOffset",{breakId:e.id}),!1;const s=this.adBreakStatusTracker.activeBreakElapsedSec;return s<t?(this.log(`skipAd() suppressed — skipOffset ${t}s not reached (${s}s)`),this.diagnose("DA-AD-SKIP-SUPPRESSED","Skip requested before skipOffset",{breakId:e.id,skipOffset:t,elapsedSec:s}),!1):(this.log(`skipAd() — skipping break '${e.id}' at ${s}s (skipOffset ${t}s)`),this.diagnose("DA-AD-SKIPPED","Break skipped by viewer",{breakId:e.id,skipOffset:t,elapsedSec:s}),this.cmcdAdReporter.onSkipRequested(e.id),this.adPlayerController.skipBreak(),!0)}clickAd(){const e=this.adBreakStatusTracker.activeBreak,t=this.adPlayerController?.currentAdAsset()??null;if(!(this.state.sessionActive&&this.adPlayerController?.isAdPlaying()&&e&&t))return this.log("clickAd() suppressed — no ad on screen"),null;const s=t.interaction?.clickThrough;return this.diagnose("DA-AD-CLICKED","Ad clicked",{breakId:e.id,assetId:t.id,clickThrough:s??""}),this.emit("adclick",{type:"adclick",timestamp:Date.now(),break:e,format:this.effectiveFormatOf(e),asset:t,...s?{clickThrough:s}:{}}),s??null}endSession(){this.state.sessionActive&&this.endSessionInternal()}updateAssetParameters(e){if(this.state.sessionActive){if("ssai"===this.mode||this.state.steeredSsaiActive)return this.state.ssaiController.updateAssetParameters(e),void this.log("Asset parameters updated (SSAI)");this.adPlayerController.updateAssetParameters(e),this.log("Asset parameters updated")}else console.warn("[OptiViewAds] updateAssetParameters called but no active session.")}updateAssetParameterMacros(e){if(this.state.sessionActive){if("ssai"===this.mode||this.state.steeredSsaiActive)return this.state.ssaiController.updateAssetParameterMacros(e),void this.log("Asset parameter macros updated (SSAI)");this.adPlayerController.updateAssetParameterMacros(e),this.log("Asset parameter macros updated")}else console.warn("[OptiViewAds] updateAssetParameterMacros called but no active session.")}endSessionInternal(e="end-session"){this.state.sessionActive=!1,this.state.sessionGeneration++,this.breakLifecycle?.reportAbandonedBreak(e),"ssai"===this.mode?this.state.ssaiController?.stop():(this.state.steeredSsaiActive&&(this.state.ssaiController?.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1),this.manifestService?.reset(),this.breakScheduler?.stop(),this.breakScheduler?.destroy(),this.adBreakStatusTracker.resetAdBreakStatus(),this.state.pauseAdTickInterval&&(clearInterval(this.state.pauseAdTickInterval),this.state.pauseAdTickInterval=null),this.sessionStarter?.clearHeldImmediatePreRollArm(),this.pauseAdController?.reset(),this.breakTransitionTimer?.reset(),this.adPlayerController?.stopActiveBreak(),this.adPlayerController?.releaseContentHold(),this.adPlayerController?.endGamSession()),this.state.clearSessionIdentity(),this.anvatoObservedLogged.clear(),this.seenManifestBreakIds.clear(),this.cmcdAdReporter.onSessionEnd(),this.lensReporter.endSession(),this.diagnose("DA-SESSION-ENDED","Session ended"),this.log("Session ended")}addEventListener(e,t){this.eventHandlers.has(e)||this.eventHandlers.set(e,new Set),this.eventHandlers.get(e).add(t)}removeEventListener(e,t){const s=this.eventHandlers.get(e);s&&s.delete(t)}emit(e,t){this.recordEventTimeline(t),this.cmcdAdReporter.onEvent(t);const s=this.eventHandlers.get(e);if(s)for(const e of s)try{e(t)}catch(e){this.log("Event handler error:",e)}}recordEventTimeline(e){const t={eventType:e.type},s=e;s.source&&(t.source=s.source),s.break?.id&&(t.breakId=s.break.id),s.asset?.id&&(t.assetId=s.asset.id),this.diagnosticBuffer.record({ts:e.timestamp,level:"aderror"===e.type?"error":"info",code:"DA-EVENT",category:"event",message:`SDK event: ${e.type}`,context:t})}onDiagnostic(e){this.diagnosticHandlers.add(e)}offDiagnostic(e){this.diagnosticHandlers.delete(e)}exportDiagnostics(){const e={...this.state.configSummary,...this.state.manifestOrigin?{manifestOrigin:this.state.manifestOrigin}:{},...this.state.configSummary.ssai&&this.state.stitcherOrigin?{ssai:{...this.state.configSummary.ssai,stitcherOrigin:this.state.stitcherOrigin}}:{}};return{sdkVersion:I,generatedAt:Date.now(),userAgent:"undefined"!=typeof navigator?navigator.userAgent:null,config:e,diagnostics:this.diagnosticBuffer.getAll()}}diagnose(e,t,s){if(this.destroyed)return;const r=B[e],i={ts:Date.now(),level:r?.level??"info",code:e,category:r?.category??"unknown",message:t,...s?{context:s}:{}};this.diagnosticBuffer.record(i);for(const e of this.diagnosticHandlers)try{e(i)}catch(e){this.log("Diagnostic handler error:",e)}this.debug&&E("[OptiViewAds]",e,t,s??"")}isAdPlaying(){return this.adPlayerController?.isAdPlaying()??!1}isPictureInPicture(){return this.adPlayerController?.isPictureInPicture()??!1}getPresentationState(){return{pictureInPicture:this.isPictureInPicture(),showing:this.adPlayerController?.isShowingAd()?"ad":"content",insertion:this.adPlayerController?.currentInsertionMode()??"overlay",windowOwner:this.adPlayerController?.pictureInPictureOwner()??null}}effectiveFormatOf(e){const t=this.adPlayerController?.declaredFormatOf(e)??v(e);return A(t,this.isPictureInPicture())}isSessionActive(){return this.state.sessionActive}getAdBreakStatus(){return this.adBreakStatusTracker.getAdBreakStatus()}destroy(){this.contentErrorReloadGuard&&(clearTimeout(this.contentErrorReloadGuard),this.contentErrorReloadGuard=null);for(const[e,t]of this.contentListeners())this.player.off(e,t);this.state.sessionActive&&this.endSessionInternal("destroy"),this.manifestService?.destroy(),this.pauseAdController?.destroy(),this.adPlayerController?.destroy(),this.state.ssaiController?.stop(),this.lensReporter.destroy(),this.eventHandlers.clear(),this.diagnosticHandlers.clear(),this.destroyed=!0,this.log("Destroyed")}toPlaybackError(e,t){const s=e;if(s&&("number"==typeof s.code||s.message)){const e="number"==typeof s.code?` (code ${s.code})`:"",r=s.message?`: ${s.message}`:"";return new Error(`${t}${e}${r}`)}return new Error(t)}log(...e){this.debug&&E("[OptiViewAds]",...e)}}
@@ -124,6 +124,16 @@ export declare const DIAGNOSTIC_CODES: {
124
124
  readonly level: "info";
125
125
  readonly summary: "Content playback was held at session start until a pending pre-roll began, so no content is shown before the ad.";
126
126
  };
127
+ readonly "DA-PREROLL-CONTENT-WAIT-TIMEOUT": {
128
+ readonly category: "break";
129
+ readonly level: "info";
130
+ readonly summary: "A pending pre-roll began after content readiness was not reported within the wait timeout.";
131
+ };
132
+ readonly "DA-PREROLL-CANCELLED-CONTENT-ERROR": {
133
+ readonly category: "break";
134
+ readonly level: "warn";
135
+ readonly summary: "A pending pre-roll was cancelled because the content failed to load before it could begin.";
136
+ };
127
137
  readonly "DA-SHARED-ELEMENT-UNSUPPORTED": {
128
138
  readonly category: "break";
129
139
  readonly level: "warn";
@@ -159,6 +169,11 @@ export declare const DIAGNOSTIC_CODES: {
159
169
  readonly level: "info";
160
170
  readonly summary: "The content player is in picture-in-picture, so the break's declared format was overruled and it plays as a single fullscreen ad.";
161
171
  };
172
+ readonly "DA-PIP-ADOPTION-STATE": {
173
+ readonly category: "break";
174
+ readonly level: "info";
175
+ readonly summary: "Native PiP reconciliation deferred or reused a layout/handoff decision. Context carries reason, active, adPlaying, applied, and asset/break IDs when available; this is not evidence that a format override occurred.";
176
+ };
162
177
  readonly "DA-CHAIN-RESOLVER-ERROR": {
163
178
  readonly category: "chaining";
164
179
  readonly level: "warn";
@@ -1 +1 @@
1
- export const DIAGNOSTIC_CODES={"DA-SESSION-STARTED":{category:"session",level:"info",summary:"Monetization session started; manifest polling began. context: manifestUrl (stitcherUrl in SSAI mode), reported verbatim."},"DA-SESSION-ENDED":{category:"session",level:"info",summary:"Monetization session ended; polling and scheduling stopped."},"DA-ASSET-PARAMETERS-UPDATED":{category:"session",level:"info",summary:"The break manifest's session-level asset parameters changed; the merged parameters were pushed to the active GAM stream. context: source ('manifest'), keys (parameter names only, never values)."},"DA-MANIFEST-FETCH-FAILED":{category:"manifest",level:"error",summary:"Initial break-manifest fetch failed; the session could not start."},"DA-MANIFEST-POLL-FAILED":{category:"manifest",level:"warn",summary:"A manifest poll failed; the SDK keeps the last good manifest and retries."},"DA-MANIFEST-CHANNEL-CHANGED":{category:"manifest",level:"info",summary:"A polled manifest describes a different ads channel (channelId, GAM identity or timebase changed); the SDK re-aligned its ad setup and resolved delivery mode again while the session and content playback continued. context: previousChannelId, channelId, reason, manifestUrl."},"DA-MANIFEST-INTERCEPT-FAILED":{category:"manifest",level:"warn",summary:"The customer manifest-response interceptor threw; the un-modified parsed manifest was used instead."},"DA-MANIFEST-REQUEST-INTERCEPT-FAILED":{category:"manifest",level:"warn",summary:"The customer manifest-request interceptor threw; the normal network fetch of the original URL was used instead."},"DA-MANIFEST-SIGNATURE-INVALID":{category:"manifest",level:"error",summary:"The break-manifest response failed detached-JWS signature verification (missing/malformed X-Manifest-Signature, unsupported alg, unknown kid, unknown critical param, or signature mismatch); the manifest was rejected and no breaks were scheduled. context: url, reason."},"DA-PDT-MISSING":{category:"playback",level:"warn",summary:"No EXT-X-PROGRAM-DATE-TIME once content is playing (or after the startup backstop window); wallclock-timebase breaks fall back to the system clock."},"DA-ANVATO-CUE-PENDING":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — no Anvato in-stream cue observed yet for a PTS break; the break stays pending until its cue arrives."},"DA-ANVATO-CUE-MATCHED":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — a PTS break's start was matched to an observed Anvato cue and scheduled at the cue's media time."},"DA-ANVATO-CUE-ENTERED":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — a break was started by the cue reaching the playhead rather than by the scheduler's 250ms tick; context carries the breakId, the cue pts and the media time it fired at, so the two start paths are distinguishable in the field."},"DA-ANVATO-CUE-OBSERVED":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — an Anvato break-signaling (type=cue) frame was observed in the stream; context carries the cue's pts, the media time it landed at, whether the tracker recorded it, and the raw payload — so a manifest-PTS vs in-stream-PTS misalignment is observable in the field."},"DA-PRELOAD-FAILED":{category:"preload",level:"warn",summary:"Ad preload failed; the asset will be loaded on demand at break start."},"DA-PRELOAD-MODE-RESOLVED":{category:"preload",level:"info",summary:"The configured ad preload mode was resolved for this session; context carries both the configured and the resolved mode."},"DA-PRELOAD-PATH":{category:"preload",level:"info",summary:"The preload path actually taken for a break's ad (parallel buffer / detached prefetch / none) and the creative container (stream vs progressive)."},"DA-BREAK-EARLY-RETURN":{category:"break",level:"info",summary:"The active break was removed from a polled manifest (early return); the break ends immediately and content resumes at break start plus the seconds already played."},"DA-BREAK-WINDOW-ESCAPED":{category:"break",level:"warn",summary:"An active wallclock break outlived its window on the stream clock (ad stall, bogus trigger position, or a re-fired completed break); the SDK ended it and returned to content. context.overrunSec says by how much."},"DA-BREAK-EVENT-UNSUPPORTED":{category:"break",level:"warn",summary:"A break is triggered by a spec event this SDK version does not schedule on this platform (web schedules `start`, `pause` and `end`; Android and iOS do not schedule `end` yet); the break was ignored. context.event names the event."},"DA-BREAK-SUPPRESSED-OVERLAP":{category:"break",level:"warn",summary:"A break was suppressed because an ad was already playing."},"DA-BREAK-TRANSITION":{category:"break",level:"info",summary:"Measured duration of a playback transition into an ad break, between two ads, between chained breaks, or back to content."},"DA-AD-SURFACE-REVEALED":{category:"break",level:"info",summary:"The ad surface became visible: how long it was held back waiting for the ad's first frame, and whether it was revealed because that frame arrived or because the wait timed out."},"DA-PREROLL-CONTENT-HELD":{category:"break",level:"info",summary:"Content playback was held at session start until a pending pre-roll began, so no content is shown before the ad."},"DA-SHARED-ELEMENT-UNSUPPORTED":{category:"break",level:"warn",summary:"Configured shared-element insertion is unsupported on this platform (non-iPhone/iPod); fell back to overlay insertion."},"DA-SHARED-ELEMENT-RESTORE-FAILED":{category:"playback",level:"error",summary:"Content did not resume after a shared-element break: the reload/seek/play restore failed twice; the viewer is left paused on the handed-back element."},"DA-IMAGE-LOAD-FAILED":{category:"break",level:"warn",summary:"An image (companion, backdrop or image ad) could not be fetched or decoded within the timeout; the break continues without it."},"DA-PIP-TRANSFER-FAILED":{category:"break",level:"warn",summary:"The picture-in-picture window could not be moved between the content and ad elements; the break continues but the viewer may not see the ad in the PiP window."},"DA-PIP-AUTODETECT-UNAVAILABLE":{category:"break",level:"warn",summary:"No Activity could be resolved from the renderer's Context or the overlay's view tree, so picture-in-picture cannot be detected automatically; the host must call OptiViewAds.setPictureInPicture() from Activity.onPictureInPictureModeChanged or breaks will be laid out for a fullscreen surface. Android only."},"DA-CONTENT-ELEMENT-SWAPPED":{category:"break",level:"info",summary:"The content player replaced its <video> element; the SDK re-bound its presentation listeners to the new one."},"DA-PIP-FORMAT-OVERRIDE":{category:"break",level:"info",summary:"The content player is in picture-in-picture, so the break's declared format was overruled and it plays as a single fullscreen ad."},"DA-CHAIN-RESOLVER-ERROR":{category:"chaining",level:"warn",summary:"The chained-successor resolver threw; chaining was skipped for this break."},"DA-GAM-NO-ASSET-KEY":{category:"gam",level:"warn",summary:"Retired: the GAM stream identity now comes from the manifest's vendorConfiguration. Kept declared so the code is never reused; see DA-GAM-CONFIG-MISSING."},"DA-GAM-SESSION-FAILED":{category:"gam",level:"error",summary:"The GAM/IMA session failed to initialize; GAM breaks will not serve."},"DA-GAM-CONFIG-RESOLVED":{category:"gam",level:"info",summary:"The GAM stream identity for this session was resolved from the manifest's vendorConfiguration."},"DA-GAM-CONFIG-MISSING":{category:"gam",level:"error",summary:'The break manifest carries no usable "gam" vendorConfiguration entry; GAM assets are skipped.'},"DA-GAM-POD-IDENTITY-MISMATCH":{category:"gam",level:"error",summary:"A GAM pod asset's vendorParameters name a different network code or custom asset key than the stream session was created with; the asset is skipped because the pod request cannot serve."},"DA-VENDOR-CONFIG-UNKNOWN-TYPE":{category:"manifest",level:"info",summary:"One or more vendorConfiguration entries declare a type this SDK version does not recognise and were ignored."},"DA-CONTENT-PLAYBACK-ERROR":{category:"playback",level:"error",summary:"The content player reported a playback error."},"DA-CONTENT-RESUME-STALLED":{category:"playback",level:"warn",summary:"Content did not resume within the watchdog window after a break that paused it ended; the SDK re-issued play() once. iOS only for now."},"DA-AD-PLAYBACK-ERROR":{category:"playback",level:"error",summary:"The ad player reported a playback error during a break."},"DA-AD-START-INTERRUPTED":{category:"playback",level:"warn",summary:"The ad element's play() was interrupted by a browser-initiated pause before the ad started (Chromium AbortError, seen on Samsung Tizen); the SDK re-issued play() once. context: breakId, error."},"DA-DELIVERY-MODE-RESOLVED":{category:"session",level:"info",summary:"Delivery mode (sgai/ssai) resolved for the session at start and again on an ads-channel switch from the manifest's delivery rules (or the explicit client config)."},"DA-DELIVERY-MODE-UNKNOWN":{category:"session",level:"warn",summary:"The matching delivery rule carried an unrecognized mode value; sgai is used (forward compatibility)."},"DA-DELIVERY-MODE-OVERRIDDEN":{category:"session",level:"info",summary:"The manifest's delivery rules resolved a different mode, but the explicit client config takes precedence."},"DA-DELIVERY-MODE-CHANGE-IGNORED":{category:"session",level:"warn",summary:"An ordinary manifest poll resolves a different delivery mode; the mode pinned for the session stays in force. An ads-channel switch resolves the mode again."},"DA-SSAI-UNAVAILABLE":{category:"session",level:"error",summary:"The manifest selected ssai delivery but no vendor configuration can provide a stitched stream (no gam entry with an assetKey); at start the session fails, while on a channel switch the session stays in sgai."},"DA-SSAI-SESSION-FAILED":{category:"session",level:"error",summary:"The SSAI session failed to start (IMA stream init, stream id, or stitched-stream load)."},"DA-SSAI-IMA-ERROR":{category:"gam",level:"error",summary:"IMA reported an error during the SSAI stream session."},"DA-INSERTION-TYPE-RESOLVED":{category:"session",level:"info",summary:"Per-session ad insertion type resolved, and whether the integrator set it or left it unset (unset lets the manifest's resumeOffset drive the resume point)."},"DA-RESUME-OFFSET-CLAMPED":{category:"break",level:"info",summary:"The resume offset was shortened to the ad duration actually played, so the break could not skip further than the ads it delivered."},"DA-INSERTION-TYPE-IGNORED-SSAI":{category:"session",level:"warn",summary:"adInsertionType is ignored in SSAI mode; the stitcher controls ad insertion server-side."},"DA-VAST-UNSUPPORTED-FORMAT":{category:"break",level:"warn",summary:"A VAST asset appeared in an unsupported break format; VAST is only supported in single, double, and lshape_ad. The break was skipped."},"DA-ASSET-MACRO-UNKNOWN":{category:"break",level:"warn",summary:"An asset parameter value contained a macro the SDK has no value for (an unknown or unavailable OptiView macro, or a registered customer macro that returned no value); the parameter was still sent, with the macro kept as its literal token. Raised once per parameter key and macro. context: key, macro, breakId, assetId."},"DA-VAST-SSAI-UNSUPPORTED":{category:"session",level:"warn",summary:"VAST assets are not supported in SSAI mode (VAST is SGAI-only); the asset was skipped."},"DA-VAST-IMA-ERROR":{category:"gam",level:"error",summary:"Google IMA reported an error while loading or playing a VAST (client-side / CSAI) ad."},"DA-VAST-IMA-SDK-MISSING":{category:"gam",level:"error",summary:"The Google IMA client-side SDK (ima3.js) is unavailable; VAST (CSAI) ads cannot be played."},"DA-PAUSE-AD-SHOWN":{category:"break",level:"info",summary:"A pause ad (full-screen image or video) was shown while content is paused."},"DA-PAUSE-AD-DISMISSED":{category:"break",level:"info",summary:"A pause ad was dismissed (content resumed, or the duration cap elapsed)."},"DA-PAUSE-AD-RESUME-ALLOWED":{category:"break",level:"info",summary:"The viewer may now resume content over the shown pause ad: the break's controls.skipOffset elapsed, or (no skipOffset) the ad was watched fully: a video creative ended, an image reached its duration cap or has none. context: breakId."},"DA-PAUSE-AD-RESUME-BLOCKED":{category:"break",level:"info",summary:"Content tried to resume while a pause ad must still be watched (controls.skipOffset not reached, or no skipOffset and the video creative has not ended / the image duration cap has not elapsed); the SDK keeps content paused. context: breakId, elapsedSec, skipOffset."},"DA-PAUSE-AD-DVR-EXPIRED":{category:"break",level:"info",summary:"The viewer paused for longer than the DVR window is deep, so the position they paused at aged out and playback resumed at the live edge instead. context: pinnedPosition, liveEdge."},"DA-PAUSE-AD-NO-ASSET":{category:"break",level:"warn",summary:"A pause break had no usable image or video asset (no first asset, unsupported media type, or no resolvable VAST creative); the pause ad was skipped."},"DA-PAUSE-AD-SSAI-UNSUPPORTED":{category:"session",level:"warn",summary:"Pause ads are SGAI-only and not supported in SSAI mode; the pause break was skipped."},"DA-MANIFEST-POLLED":{category:"manifest",level:"info",summary:"A manifest poll refresh succeeded (cadence follows the manifest polling config)."},"DA-AD-SKIPPED":{category:"playback",level:"info",summary:"The viewer skipped the active ad break (controls.skipOffset honored)."},"DA-AD-SKIP-SUPPRESSED":{category:"playback",level:"info",summary:"A skip request was suppressed (no skipOffset declared, or requested before the offset)."},"DA-AD-CLICKED":{category:"playback",level:"info",summary:"The viewer clicked the ad on screen (adclick emitted)."},"DA-RESUME-SEEK":{category:"playback",level:"info",summary:"Content was resume-seeked to the resolved post-break position (insertion type policy)."},"DA-RESUME-LANDED":{category:"playback",level:"info",summary:"Where the post-break resume-seek actually landed, measured on the seeked event rather than inferred: context carries breakId, target (the position requested), landed and errorSec (landed - target; negative means the player settled EARLIER than asked). landed is null with a reason when no seeked event arrived, which is normal for a seek to the position the playhead already holds. Report-only: the SDK does not correct the position."},"DA-RESUME-PREPOSITIONED":{category:"playback",level:"info",summary:"The break resumed without a seek: content was moved onto its resume position DURING the break, so the transition needed no jump. Context carries breakId, target, prepositionedTo, landed, shortfallSec and exact. exact=true means it reached the policy target. exact=false means the live window never published those frames, so content was moved as close as exists — the viewer lands short, but arrives already in place rather than via a visible seek at the cut."},"DA-RESUME-CATCHUP":{category:"playback",level:"info",summary:"Content began playing behind the ad to reach its resume point. Context carries breakId, startPos, target, remainingAdSec, publishedEnd and clampedStart — clampedStart true means the stream had not published the ideal start position, so playback began late and the alignment loop must make up the difference (the zero-live-headroom case). Its ABSENCE on a live break that resumed short means catch-up never ran at all, which is a different fault from it running and falling short."},"DA-RESUME-TARGET-CLAMPED":{category:"playback",level:"warn",summary:"The computed resume position sat inside the engine's minimum live offset and was clamped to the closest position the engine will hold; context carries breakId, requested, clamped and shortfallSec. Distinct from DA-RESUME-OFFSET-CLAMPED, which shortens the OFFSET to the ad seconds actually played."},"DA-RESUME-ALIGN-BLOCKED":{category:"playback",level:"warn",summary:"Resume alignment wanted to move content past the live edge and stood down, because no seek can reach media the stream has not published yet; content is left riding the edge and the break-end clamp places it. Context carries target, requested, safeEnd, shortfallSec and remainingSec. Emitted once per break. A persistent shortfall here means the resume target is further ahead than the feed's published edge — typically the player's view of the edge is stale, not that the seek is wrong."},"DA-CONTINUE-PLAYOUT-BEGIN":{category:"playback",level:"info",summary:"continueContentDuringBreak engaged: content keeps PLAYING through the break, hidden and muted, so no resume seek is needed. Context carries breakId, anchor, breakDurationSec and edgeDistanceSec."},"DA-CONTINUE-PLAYOUT-END":{category:"playback",level:"info",summary:"continueContentDuringBreak reached the cut. Context carries driftSec (content position minus where the break says it should be), maxDriftSec, the corrections applied, rateHonoured (false if the engine drove playbackRate itself) and driftUnrecoverable."},"DA-CONTINUE-PLAYOUT-REFUSED":{category:"playback",level:"info",summary:"continueContentDuringBreak was not used this session, so breaks pause content and resume with a seek as before. Emitted once per session; context carries the reason (disabled, not-replacement, format, shared-element, picture-in-picture, single-decoder) and the break format. Its ABSENCE on an eligible session is the signal the mode engaged."},"DA-CONTINUE-PLAYOUT-QUALITY-UNAVAILABLE":{category:"playback",level:"warn",summary:"Content continues through the replacement break without a video-quality constraint, so the content stream may retain its normal bitrate alongside the ad. Context carries breakId and reason (unsupported when the optional Web adapter method is absent, not-applied when the adapter returned false or could not apply the request)."},"DA-CONTINUE-PLAYOUT-DRIFT":{category:"playback",level:"warn",summary:"Content drifted further from the break timeline than the remaining break can remove within the playback-rate bounds, so part of it is carried into the cut. Context carries driftSec, remainingSec, the rate applied and seekIssued (always false: a corrective seek would be clipped)."},"DA-CONTINUE-PLAYOUT-ABANDONED":{category:"playback",level:"warn",summary:"Content could not be kept playing through the break, so it reverted to the pause-and-resume path mid-break. Context carries breakId, the reason and the driftSec at the moment it converted."},"DA-AD-AV-DESYNC":{category:"playback",level:"warn",summary:"The ad player's audio sink was handed a buffer whose timestamp does not continue the audio it has already been fed, so the media clock had to be re-anchored mid-ad. Video is scheduled off that clock while the audio already queued in the output track keeps draining, so every re-anchor pushes the picture further from the sound actually being heard. Context carries breakId, assetId, expectedSec, actualSec, deltaSec (negative = audio running behind the picture) and positionSec. Seen on server-stitched pods whose per-ad audio and video durations do not match; a single event near the end of a pod is the accumulated total, not an isolated glitch."},"DA-AD-PLAYOUT-SYNC":{category:"playback",level:"info",summary:"Per-break summary of ad playout health, emitted once at break end. Context carries podUri (the resolved ad media, so a stitched pod can be fetched and inspected), desyncEvents, maxDesyncSec, droppedFrames, audioUnderruns, audioFormats and videoFormats (the distinct decoder input formats seen during the break — more than one means the stitched stream changed sample rate or codec mid-pod) and frameOffsetMeanMs (mean video frame processing offset; large negative values mean frames were released late against the audio clock)."},"DA-EVENT":{category:"event",level:"info",summary:"An SDK event, recorded into the diagnostic ring buffer so exportDiagnostics() carries one unified timeline of diagnostics and playback events. Raised to error for aderror. Buffer-only: not dispatched to onDiagnostic handlers, which would duplicate the event stream they already receive."},"DA-BREAK-CONTENT-MASK":{category:"break",level:"warn",summary:"The host's content surface cannot follow view transforms (punch-through SurfaceView, e.g. react-native-video's default), so the two-box format masks the backdrop with a transparent hole over the content box instead of reframing the content view. context: format."},"DA-BREAK-FORMAT-DEGRADED":{category:"break",level:"warn",summary:"Split break reframing degraded: the host supplied no content view or sibling container for the ad."},"DA-BREAK-NO-PLAYABLE-ASSET":{category:"break",level:"warn",summary:"A break had nothing to play, so it never began: no adbreakbegin/adbreakend was emitted, content kept playing, and a public aderror (source ad, carrying the break) reports it. context.reason says why: no-variant (none declared), no-qualifying-variant (variant selection - device targeting + format playability - matched nothing and no downgrade applied), no-assets (the selected variant was empty), or lshape-content-skipped (shared-element insertion cannot render a backdrop). This is the BREAK-level outcome; when a particular variant was passed over for an unrecognised format, DA-BREAK-VARIANT-FORMAT-UNKNOWN names that format alongside it. context: breakId, reason, declaredAssetCount, format."},"DA-BREAK-VARIANT-FORMAT-UNKNOWN":{category:"break",level:"warn",summary:"A break variant declares a format this SDK version does not recognise, so it was passed over during variant selection rather than guessed at - an unknown format is never playable, because rendering it as a fullscreen single would be the most invasive possible misreading. Raised once per skipped variant: a break that still has a playable variant plays it and reports this too, so the skip is never silent, and a break left with nothing also reports DA-BREAK-NO-PLAYABLE-ASSET with reason no-qualifying-variant. An ABSENT format is treated identically to an unrecognised one. context: breakId, format (the raw manifest string; empty when the field was absent)."},"DA-AD-STALLED":{category:"playback",level:"warn",summary:"An ad began playing and then stopped advancing: the playhead did not move for the stall threshold while the ad was meant to be playing. context: assetId, breakId, playbackState, stalledForMs, positionMs, bufferedAheadMs, reason."},"DA-AD-FOREGROUND-RESUMED":{category:"playback",level:"info",summary:"An in-flight ad resumed after the app returned to the foreground; context carries the suspended duration and ad position at resume."},"DA-AD-FOCUS-LOST":{category:"playback",level:"warn",summary:"The overlay ad lost permanent audio focus during an active break; the host player must not reclaim focus while the SDK ad is on screen. context: breakId, transient, reason."},"DA-AD-FOCUS-GRANTED":{category:"playback",level:"info",summary:"The overlay ad was granted media audio focus for the break, so system focus semantics (call, duck, permanent loss) apply to ad playback. context: breakId, cause, alreadyHeld."},"DA-AD-FOCUS-DENIED":{category:"playback",level:"warn",summary:"The platform refused the overlay ad's media audio-focus request, so the break plays with no focus of its own: another app's audio can mix over the ad, and a call can neither duck nor pause it. context: breakId, cause."},"DA-AD-FOCUS-ABANDONED":{category:"playback",level:"info",summary:"The overlay ad gave media audio focus back, so the content player's own focus request finds a clean stack. context: breakId, cause."},"DA-BREAK-ABANDONED":{category:"lifecycle",level:"warn",summary:"A break was still in flight when the session was torn down (endSession, destroy, or a startSession over an active session). context: breakId, cause, phase, unbalancedAdBegin, adIndex, totalAds, assetId."},"DA-RENDERER-UNAVAILABLE":{category:"lifecycle",level:"error",summary:"The renderer could not render a break because a required piece of it was not attached (e.g. no ad player), after the SDK had already emitted adbreakbegin — so the break began and will never end. context: breakId, missing."},"DA-SESSION-STARTING":{category:"session",level:"info",summary:"A step of startSession() was entered: begin, manifest-fetch, gam-init or scheduling. Emitted on entry, so the last one recorded names the step a hung startSession never finished. context: step, manifestUrl, elapsedMs. The URL is reported verbatim; the redacted config summary carries only its origin (config.manifestOrigin)."}};
1
+ export const DIAGNOSTIC_CODES={"DA-SESSION-STARTED":{category:"session",level:"info",summary:"Monetization session started; manifest polling began. context: manifestUrl (stitcherUrl in SSAI mode), reported verbatim."},"DA-SESSION-ENDED":{category:"session",level:"info",summary:"Monetization session ended; polling and scheduling stopped."},"DA-ASSET-PARAMETERS-UPDATED":{category:"session",level:"info",summary:"The break manifest's session-level asset parameters changed; the merged parameters were pushed to the active GAM stream. context: source ('manifest'), keys (parameter names only, never values)."},"DA-MANIFEST-FETCH-FAILED":{category:"manifest",level:"error",summary:"Initial break-manifest fetch failed; the session could not start."},"DA-MANIFEST-POLL-FAILED":{category:"manifest",level:"warn",summary:"A manifest poll failed; the SDK keeps the last good manifest and retries."},"DA-MANIFEST-CHANNEL-CHANGED":{category:"manifest",level:"info",summary:"A polled manifest describes a different ads channel (channelId, GAM identity or timebase changed); the SDK re-aligned its ad setup and resolved delivery mode again while the session and content playback continued. context: previousChannelId, channelId, reason, manifestUrl."},"DA-MANIFEST-INTERCEPT-FAILED":{category:"manifest",level:"warn",summary:"The customer manifest-response interceptor threw; the un-modified parsed manifest was used instead."},"DA-MANIFEST-REQUEST-INTERCEPT-FAILED":{category:"manifest",level:"warn",summary:"The customer manifest-request interceptor threw; the normal network fetch of the original URL was used instead."},"DA-MANIFEST-SIGNATURE-INVALID":{category:"manifest",level:"error",summary:"The break-manifest response failed detached-JWS signature verification (missing/malformed X-Manifest-Signature, unsupported alg, unknown kid, unknown critical param, or signature mismatch); the manifest was rejected and no breaks were scheduled. context: url, reason."},"DA-PDT-MISSING":{category:"playback",level:"warn",summary:"No EXT-X-PROGRAM-DATE-TIME once content is playing (or after the startup backstop window); wallclock-timebase breaks fall back to the system clock."},"DA-ANVATO-CUE-PENDING":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — no Anvato in-stream cue observed yet for a PTS break; the break stays pending until its cue arrives."},"DA-ANVATO-CUE-MATCHED":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — a PTS break's start was matched to an observed Anvato cue and scheduled at the cue's media time."},"DA-ANVATO-CUE-ENTERED":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — a break was started by the cue reaching the playhead rather than by the scheduler's 250ms tick; context carries the breakId, the cue pts and the media time it fired at, so the two start paths are distinguishable in the field."},"DA-ANVATO-CUE-OBSERVED":{category:"break",level:"info",summary:"ptsSource: 'anvatoCue' — an Anvato break-signaling (type=cue) frame was observed in the stream; context carries the cue's pts, the media time it landed at, whether the tracker recorded it, and the raw payload — so a manifest-PTS vs in-stream-PTS misalignment is observable in the field."},"DA-PRELOAD-FAILED":{category:"preload",level:"warn",summary:"Ad preload failed; the asset will be loaded on demand at break start."},"DA-PRELOAD-MODE-RESOLVED":{category:"preload",level:"info",summary:"The configured ad preload mode was resolved for this session; context carries both the configured and the resolved mode."},"DA-PRELOAD-PATH":{category:"preload",level:"info",summary:"The preload path actually taken for a break's ad (parallel buffer / detached prefetch / none) and the creative container (stream vs progressive)."},"DA-BREAK-EARLY-RETURN":{category:"break",level:"info",summary:"The active break was removed from a polled manifest (early return); the break ends immediately and content resumes at break start plus the seconds already played."},"DA-BREAK-WINDOW-ESCAPED":{category:"break",level:"warn",summary:"An active wallclock break outlived its window on the stream clock (ad stall, bogus trigger position, or a re-fired completed break); the SDK ended it and returned to content. context.overrunSec says by how much."},"DA-BREAK-EVENT-UNSUPPORTED":{category:"break",level:"warn",summary:"A break is triggered by a spec event this SDK version does not schedule on this platform (web schedules `start`, `pause` and `end`; Android and iOS do not schedule `end` yet); the break was ignored. context.event names the event."},"DA-BREAK-SUPPRESSED-OVERLAP":{category:"break",level:"warn",summary:"A break was suppressed because an ad was already playing."},"DA-BREAK-TRANSITION":{category:"break",level:"info",summary:"Measured duration of a playback transition into an ad break, between two ads, between chained breaks, or back to content."},"DA-AD-SURFACE-REVEALED":{category:"break",level:"info",summary:"The ad surface became visible: how long it was held back waiting for the ad's first frame, and whether it was revealed because that frame arrived or because the wait timed out."},"DA-PREROLL-CONTENT-HELD":{category:"break",level:"info",summary:"Content playback was held at session start until a pending pre-roll began, so no content is shown before the ad."},"DA-PREROLL-CONTENT-WAIT-TIMEOUT":{category:"break",level:"info",summary:"A pending pre-roll began after content readiness was not reported within the wait timeout."},"DA-PREROLL-CANCELLED-CONTENT-ERROR":{category:"break",level:"warn",summary:"A pending pre-roll was cancelled because the content failed to load before it could begin."},"DA-SHARED-ELEMENT-UNSUPPORTED":{category:"break",level:"warn",summary:"Configured shared-element insertion is unsupported on this platform (non-iPhone/iPod); fell back to overlay insertion."},"DA-SHARED-ELEMENT-RESTORE-FAILED":{category:"playback",level:"error",summary:"Content did not resume after a shared-element break: the reload/seek/play restore failed twice; the viewer is left paused on the handed-back element."},"DA-IMAGE-LOAD-FAILED":{category:"break",level:"warn",summary:"An image (companion, backdrop or image ad) could not be fetched or decoded within the timeout; the break continues without it."},"DA-PIP-TRANSFER-FAILED":{category:"break",level:"warn",summary:"The picture-in-picture window could not be moved between the content and ad elements; the break continues but the viewer may not see the ad in the PiP window."},"DA-PIP-AUTODETECT-UNAVAILABLE":{category:"break",level:"warn",summary:"No Activity could be resolved from the renderer's Context or the overlay's view tree, so picture-in-picture cannot be detected automatically; the host must call OptiViewAds.setPictureInPicture() from Activity.onPictureInPictureModeChanged or breaks will be laid out for a fullscreen surface. Android only."},"DA-CONTENT-ELEMENT-SWAPPED":{category:"break",level:"info",summary:"The content player replaced its <video> element; the SDK re-bound its presentation listeners to the new one."},"DA-PIP-FORMAT-OVERRIDE":{category:"break",level:"info",summary:"The content player is in picture-in-picture, so the break's declared format was overruled and it plays as a single fullscreen ad."},"DA-PIP-ADOPTION-STATE":{category:"break",level:"info",summary:"Native PiP reconciliation deferred or reused a layout/handoff decision. Context carries reason, active, adPlaying, applied, and asset/break IDs when available; this is not evidence that a format override occurred."},"DA-CHAIN-RESOLVER-ERROR":{category:"chaining",level:"warn",summary:"The chained-successor resolver threw; chaining was skipped for this break."},"DA-GAM-NO-ASSET-KEY":{category:"gam",level:"warn",summary:"Retired: the GAM stream identity now comes from the manifest's vendorConfiguration. Kept declared so the code is never reused; see DA-GAM-CONFIG-MISSING."},"DA-GAM-SESSION-FAILED":{category:"gam",level:"error",summary:"The GAM/IMA session failed to initialize; GAM breaks will not serve."},"DA-GAM-CONFIG-RESOLVED":{category:"gam",level:"info",summary:"The GAM stream identity for this session was resolved from the manifest's vendorConfiguration."},"DA-GAM-CONFIG-MISSING":{category:"gam",level:"error",summary:'The break manifest carries no usable "gam" vendorConfiguration entry; GAM assets are skipped.'},"DA-GAM-POD-IDENTITY-MISMATCH":{category:"gam",level:"error",summary:"A GAM pod asset's vendorParameters name a different network code or custom asset key than the stream session was created with; the asset is skipped because the pod request cannot serve."},"DA-VENDOR-CONFIG-UNKNOWN-TYPE":{category:"manifest",level:"info",summary:"One or more vendorConfiguration entries declare a type this SDK version does not recognise and were ignored."},"DA-CONTENT-PLAYBACK-ERROR":{category:"playback",level:"error",summary:"The content player reported a playback error."},"DA-CONTENT-RESUME-STALLED":{category:"playback",level:"warn",summary:"Content did not resume within the watchdog window after a break that paused it ended; the SDK re-issued play() once. iOS only for now."},"DA-AD-PLAYBACK-ERROR":{category:"playback",level:"error",summary:"The ad player reported a playback error during a break."},"DA-AD-START-INTERRUPTED":{category:"playback",level:"warn",summary:"The ad element's play() was interrupted by a browser-initiated pause before the ad started (Chromium AbortError, seen on Samsung Tizen); the SDK re-issued play() once. context: breakId, error."},"DA-DELIVERY-MODE-RESOLVED":{category:"session",level:"info",summary:"Delivery mode (sgai/ssai) resolved for the session at start and again on an ads-channel switch from the manifest's delivery rules (or the explicit client config)."},"DA-DELIVERY-MODE-UNKNOWN":{category:"session",level:"warn",summary:"The matching delivery rule carried an unrecognized mode value; sgai is used (forward compatibility)."},"DA-DELIVERY-MODE-OVERRIDDEN":{category:"session",level:"info",summary:"The manifest's delivery rules resolved a different mode, but the explicit client config takes precedence."},"DA-DELIVERY-MODE-CHANGE-IGNORED":{category:"session",level:"warn",summary:"An ordinary manifest poll resolves a different delivery mode; the mode pinned for the session stays in force. An ads-channel switch resolves the mode again."},"DA-SSAI-UNAVAILABLE":{category:"session",level:"error",summary:"The manifest selected ssai delivery but no vendor configuration can provide a stitched stream (no gam entry with an assetKey); at start the session fails, while on a channel switch the session stays in sgai."},"DA-SSAI-SESSION-FAILED":{category:"session",level:"error",summary:"The SSAI session failed to start (IMA stream init, stream id, or stitched-stream load)."},"DA-SSAI-IMA-ERROR":{category:"gam",level:"error",summary:"IMA reported an error during the SSAI stream session."},"DA-INSERTION-TYPE-RESOLVED":{category:"session",level:"info",summary:"Per-session ad insertion type resolved, and whether the integrator set it or left it unset (unset lets the manifest's resumeOffset drive the resume point)."},"DA-RESUME-OFFSET-CLAMPED":{category:"break",level:"info",summary:"The resume offset was shortened to the ad duration actually played, so the break could not skip further than the ads it delivered."},"DA-INSERTION-TYPE-IGNORED-SSAI":{category:"session",level:"warn",summary:"adInsertionType is ignored in SSAI mode; the stitcher controls ad insertion server-side."},"DA-VAST-UNSUPPORTED-FORMAT":{category:"break",level:"warn",summary:"A VAST asset appeared in an unsupported break format; VAST is only supported in single, double, and lshape_ad. The break was skipped."},"DA-ASSET-MACRO-UNKNOWN":{category:"break",level:"warn",summary:"An asset parameter value contained a macro the SDK has no value for (an unknown or unavailable OptiView macro, or a registered customer macro that returned no value); the parameter was still sent, with the macro kept as its literal token. Raised once per parameter key and macro. context: key, macro, breakId, assetId."},"DA-VAST-SSAI-UNSUPPORTED":{category:"session",level:"warn",summary:"VAST assets are not supported in SSAI mode (VAST is SGAI-only); the asset was skipped."},"DA-VAST-IMA-ERROR":{category:"gam",level:"error",summary:"Google IMA reported an error while loading or playing a VAST (client-side / CSAI) ad."},"DA-VAST-IMA-SDK-MISSING":{category:"gam",level:"error",summary:"The Google IMA client-side SDK (ima3.js) is unavailable; VAST (CSAI) ads cannot be played."},"DA-PAUSE-AD-SHOWN":{category:"break",level:"info",summary:"A pause ad (full-screen image or video) was shown while content is paused."},"DA-PAUSE-AD-DISMISSED":{category:"break",level:"info",summary:"A pause ad was dismissed (content resumed, or the duration cap elapsed)."},"DA-PAUSE-AD-RESUME-ALLOWED":{category:"break",level:"info",summary:"The viewer may now resume content over the shown pause ad: the break's controls.skipOffset elapsed, or (no skipOffset) the ad was watched fully: a video creative ended, an image reached its duration cap or has none. context: breakId."},"DA-PAUSE-AD-RESUME-BLOCKED":{category:"break",level:"info",summary:"Content tried to resume while a pause ad must still be watched (controls.skipOffset not reached, or no skipOffset and the video creative has not ended / the image duration cap has not elapsed); the SDK keeps content paused. context: breakId, elapsedSec, skipOffset."},"DA-PAUSE-AD-DVR-EXPIRED":{category:"break",level:"info",summary:"The viewer paused for longer than the DVR window is deep, so the position they paused at aged out and playback resumed at the live edge instead. context: pinnedPosition, liveEdge."},"DA-PAUSE-AD-NO-ASSET":{category:"break",level:"warn",summary:"A pause break had no usable image or video asset (no first asset, unsupported media type, or no resolvable VAST creative); the pause ad was skipped."},"DA-PAUSE-AD-SSAI-UNSUPPORTED":{category:"session",level:"warn",summary:"Pause ads are SGAI-only and not supported in SSAI mode; the pause break was skipped."},"DA-MANIFEST-POLLED":{category:"manifest",level:"info",summary:"A manifest poll refresh succeeded (cadence follows the manifest polling config)."},"DA-AD-SKIPPED":{category:"playback",level:"info",summary:"The viewer skipped the active ad break (controls.skipOffset honored)."},"DA-AD-SKIP-SUPPRESSED":{category:"playback",level:"info",summary:"A skip request was suppressed (no skipOffset declared, or requested before the offset)."},"DA-AD-CLICKED":{category:"playback",level:"info",summary:"The viewer clicked the ad on screen (adclick emitted)."},"DA-RESUME-SEEK":{category:"playback",level:"info",summary:"Content was resume-seeked to the resolved post-break position (insertion type policy)."},"DA-RESUME-LANDED":{category:"playback",level:"info",summary:"Where the post-break resume-seek actually landed, measured on the seeked event rather than inferred: context carries breakId, target (the position requested), landed and errorSec (landed - target; negative means the player settled EARLIER than asked). landed is null with a reason when no seeked event arrived, which is normal for a seek to the position the playhead already holds. Report-only: the SDK does not correct the position."},"DA-RESUME-PREPOSITIONED":{category:"playback",level:"info",summary:"The break resumed without a seek: content was moved onto its resume position DURING the break, so the transition needed no jump. Context carries breakId, target, prepositionedTo, landed, shortfallSec and exact. exact=true means it reached the policy target. exact=false means the live window never published those frames, so content was moved as close as exists — the viewer lands short, but arrives already in place rather than via a visible seek at the cut."},"DA-RESUME-CATCHUP":{category:"playback",level:"info",summary:"Content began playing behind the ad to reach its resume point. Context carries breakId, startPos, target, remainingAdSec, publishedEnd and clampedStart — clampedStart true means the stream had not published the ideal start position, so playback began late and the alignment loop must make up the difference (the zero-live-headroom case). Its ABSENCE on a live break that resumed short means catch-up never ran at all, which is a different fault from it running and falling short."},"DA-RESUME-TARGET-CLAMPED":{category:"playback",level:"warn",summary:"The computed resume position sat inside the engine's minimum live offset and was clamped to the closest position the engine will hold; context carries breakId, requested, clamped and shortfallSec. Distinct from DA-RESUME-OFFSET-CLAMPED, which shortens the OFFSET to the ad seconds actually played."},"DA-RESUME-ALIGN-BLOCKED":{category:"playback",level:"warn",summary:"Resume alignment wanted to move content past the live edge and stood down, because no seek can reach media the stream has not published yet; content is left riding the edge and the break-end clamp places it. Context carries target, requested, safeEnd, shortfallSec and remainingSec. Emitted once per break. A persistent shortfall here means the resume target is further ahead than the feed's published edge — typically the player's view of the edge is stale, not that the seek is wrong."},"DA-CONTINUE-PLAYOUT-BEGIN":{category:"playback",level:"info",summary:"continueContentDuringBreak engaged: content keeps PLAYING through the break, hidden and muted, so no resume seek is needed. Context carries breakId, anchor, breakDurationSec and edgeDistanceSec."},"DA-CONTINUE-PLAYOUT-END":{category:"playback",level:"info",summary:"continueContentDuringBreak reached the cut. Context carries driftSec (content position minus where the break says it should be), maxDriftSec, the corrections applied, rateHonoured (false if the engine drove playbackRate itself) and driftUnrecoverable."},"DA-CONTINUE-PLAYOUT-REFUSED":{category:"playback",level:"info",summary:"continueContentDuringBreak was not used this session, so breaks pause content and resume with a seek as before. Emitted once per session; context carries the reason (disabled, not-replacement, format, shared-element, picture-in-picture, single-decoder) and the break format. Its ABSENCE on an eligible session is the signal the mode engaged."},"DA-CONTINUE-PLAYOUT-QUALITY-UNAVAILABLE":{category:"playback",level:"warn",summary:"Content continues through the replacement break without a video-quality constraint, so the content stream may retain its normal bitrate alongside the ad. Context carries breakId and reason (unsupported when the optional Web adapter method is absent, not-applied when the adapter returned false or could not apply the request)."},"DA-CONTINUE-PLAYOUT-DRIFT":{category:"playback",level:"warn",summary:"Content drifted further from the break timeline than the remaining break can remove within the playback-rate bounds, so part of it is carried into the cut. Context carries driftSec, remainingSec, the rate applied and seekIssued (always false: a corrective seek would be clipped)."},"DA-CONTINUE-PLAYOUT-ABANDONED":{category:"playback",level:"warn",summary:"Content could not be kept playing through the break, so it reverted to the pause-and-resume path mid-break. Context carries breakId, the reason and the driftSec at the moment it converted."},"DA-AD-AV-DESYNC":{category:"playback",level:"warn",summary:"The ad player's audio sink was handed a buffer whose timestamp does not continue the audio it has already been fed, so the media clock had to be re-anchored mid-ad. Video is scheduled off that clock while the audio already queued in the output track keeps draining, so every re-anchor pushes the picture further from the sound actually being heard. Context carries breakId, assetId, expectedSec, actualSec, deltaSec (negative = audio running behind the picture) and positionSec. Seen on server-stitched pods whose per-ad audio and video durations do not match; a single event near the end of a pod is the accumulated total, not an isolated glitch."},"DA-AD-PLAYOUT-SYNC":{category:"playback",level:"info",summary:"Per-break summary of ad playout health, emitted once at break end. Context carries podUri (the resolved ad media, so a stitched pod can be fetched and inspected), desyncEvents, maxDesyncSec, droppedFrames, audioUnderruns, audioFormats and videoFormats (the distinct decoder input formats seen during the break — more than one means the stitched stream changed sample rate or codec mid-pod) and frameOffsetMeanMs (mean video frame processing offset; large negative values mean frames were released late against the audio clock)."},"DA-EVENT":{category:"event",level:"info",summary:"An SDK event, recorded into the diagnostic ring buffer so exportDiagnostics() carries one unified timeline of diagnostics and playback events. Raised to error for aderror. Buffer-only: not dispatched to onDiagnostic handlers, which would duplicate the event stream they already receive."},"DA-BREAK-CONTENT-MASK":{category:"break",level:"warn",summary:"The host's content surface cannot follow view transforms (punch-through SurfaceView, e.g. react-native-video's default), so the two-box format masks the backdrop with a transparent hole over the content box instead of reframing the content view. context: format."},"DA-BREAK-FORMAT-DEGRADED":{category:"break",level:"warn",summary:"Split break reframing degraded: the host supplied no content view or sibling container for the ad."},"DA-BREAK-NO-PLAYABLE-ASSET":{category:"break",level:"warn",summary:"A break had nothing to play, so it never began: no adbreakbegin/adbreakend was emitted, content kept playing, and a public aderror (source ad, carrying the break) reports it. context.reason says why: no-variant (none declared), no-qualifying-variant (variant selection - device targeting + format playability - matched nothing and no downgrade applied), no-assets (the selected variant was empty), or lshape-content-skipped (shared-element insertion cannot render a backdrop). This is the BREAK-level outcome; when a particular variant was passed over for an unrecognised format, DA-BREAK-VARIANT-FORMAT-UNKNOWN names that format alongside it. context: breakId, reason, declaredAssetCount, format."},"DA-BREAK-VARIANT-FORMAT-UNKNOWN":{category:"break",level:"warn",summary:"A break variant declares a format this SDK version does not recognise, so it was passed over during variant selection rather than guessed at - an unknown format is never playable, because rendering it as a fullscreen single would be the most invasive possible misreading. Raised once per skipped variant: a break that still has a playable variant plays it and reports this too, so the skip is never silent, and a break left with nothing also reports DA-BREAK-NO-PLAYABLE-ASSET with reason no-qualifying-variant. An ABSENT format is treated identically to an unrecognised one. context: breakId, format (the raw manifest string; empty when the field was absent)."},"DA-AD-STALLED":{category:"playback",level:"warn",summary:"An ad began playing and then stopped advancing: the playhead did not move for the stall threshold while the ad was meant to be playing. context: assetId, breakId, playbackState, stalledForMs, positionMs, bufferedAheadMs, reason."},"DA-AD-FOREGROUND-RESUMED":{category:"playback",level:"info",summary:"An in-flight ad resumed after the app returned to the foreground; context carries the suspended duration and ad position at resume."},"DA-AD-FOCUS-LOST":{category:"playback",level:"warn",summary:"The overlay ad lost permanent audio focus during an active break; the host player must not reclaim focus while the SDK ad is on screen. context: breakId, transient, reason."},"DA-AD-FOCUS-GRANTED":{category:"playback",level:"info",summary:"The overlay ad was granted media audio focus for the break, so system focus semantics (call, duck, permanent loss) apply to ad playback. context: breakId, cause, alreadyHeld."},"DA-AD-FOCUS-DENIED":{category:"playback",level:"warn",summary:"The platform refused the overlay ad's media audio-focus request, so the break plays with no focus of its own: another app's audio can mix over the ad, and a call can neither duck nor pause it. context: breakId, cause."},"DA-AD-FOCUS-ABANDONED":{category:"playback",level:"info",summary:"The overlay ad gave media audio focus back, so the content player's own focus request finds a clean stack. context: breakId, cause."},"DA-BREAK-ABANDONED":{category:"lifecycle",level:"warn",summary:"A break was still in flight when the session was torn down (endSession, destroy, or a startSession over an active session). context: breakId, cause, phase, unbalancedAdBegin, adIndex, totalAds, assetId."},"DA-RENDERER-UNAVAILABLE":{category:"lifecycle",level:"error",summary:"The renderer could not render a break because a required piece of it was not attached (e.g. no ad player), after the SDK had already emitted adbreakbegin — so the break began and will never end. context: breakId, missing."},"DA-SESSION-STARTING":{category:"session",level:"info",summary:"A step of startSession() was entered: begin, manifest-fetch, gam-init or scheduling. Emitted on entry, so the last one recorded names the step a hung startSession never finished. context: step, manifestUrl, elapsedMs. The URL is reported verbatim; the redacted config summary carries only its origin (config.manifestOrigin)."}};
@@ -2,4 +2,4 @@
2
2
  * SDK version embedded in diagnostic reports.
3
3
  * Stamped from the monorepo lockstep version (lerna.json) by the taxonomy generator.
4
4
  */
5
- export declare const SDK_VERSION = "1.0.0-beta.1";
5
+ export declare const SDK_VERSION = "1.0.0-beta.3";
@@ -1 +1 @@
1
- export const SDK_VERSION="1.0.0-beta.1";
1
+ export const SDK_VERSION="1.0.0-beta.3";
package/dist/index.d.ts CHANGED
@@ -13,6 +13,7 @@ export { buildStitcherMasterUrl } from './services/StitcherUrlBuilder';
13
13
  export type { GamStreamConfig, GamAdEventCallbacks } from './services/GamStreamManager';
14
14
  export { GamStreamManager } from './services/GamStreamManager';
15
15
  export type { AssetParameterMacro, AssetParameterMacroValue, AssetParameterMacros, } from './services/assetParameterMacros';
16
+ export type { AssetParameterMacroUpdates } from './types/OptiViewAdsConfig';
16
17
  export { OPTIVIEW_ASSET_PARAMETER_MACROS } from './services/assetParameterMacros';
17
18
  export type { VastAdCallbacks } from './services/VastAdManager';
18
19
  export { VastAdManager, isImaCsaiSdkAvailable } from './services/VastAdManager';
@@ -2,6 +2,7 @@ import { PlayerAdapter } from '../interfaces/PlayerAdapter';
2
2
  import { Asset, Break, BreakFormat, Timebase } from '../types/BreakManifest';
3
3
  import { AdInsertionType, CreateAdAdapter, DoubleBoxAudio, GamConfig } from '../types/OptiViewAdsConfig';
4
4
  import { AssetParameterMacros } from './assetParameterMacros';
5
+ import type { AssetParameterMacroUpdates } from '../types/OptiViewAdsConfig';
5
6
  import { ResolvedPreloadMode } from './detectPreloadMode';
6
7
  import { ResolvedInsertionMode } from './detectInsertionMode';
7
8
  import type { GamSessionIdentity } from './resolveGamVendorConfiguration';
@@ -25,6 +26,8 @@ export interface AdPlayerCallbacks {
25
26
  onAdWaiting: (breakInfo: Break, asset: Asset) => void;
26
27
  onAdPlaying: (breakInfo: Break, asset: Asset) => void;
27
28
  onBreakEnd: (breakInfo: Break) => void;
29
+ /** The break-cut timer expired with an ad still in flight; fired before that ad's `adend`. */
30
+ onBreakCut?: (breakInfo: Break) => void;
28
31
  /**
29
32
  * A content-covering break ended before its content pause landed, so no player transition
30
33
  * announces the resume. Only the deferred-pause renderers (Kotlin/Swift) fire this.
@@ -261,10 +264,12 @@ export declare class AdPlayerController {
261
264
  * mid-break update applies from the next asset on.
262
265
  */
263
266
  updateAssetParameters(params: Record<string, string>): void;
267
+ updateAssetParameterMacros(macros: AssetParameterMacroUpdates): void;
264
268
  /** The live-update layer last set by {@link updateAssetParameters}; `endGamSession()` clears it. */
265
269
  get updatedAssetParameters(): Record<string, string> | undefined;
266
270
  /** Record the session's customer macros (`SessionConfig.assetParameterMacros`). */
267
271
  setSessionAssetParameterMacros(macros: AssetParameterMacros | undefined): void;
272
+ get sessionAssetParameterMacros(): AssetParameterMacros;
268
273
  private buildVastTagUrl;
269
274
  private pushAssetParameters;
270
275
  private setupOverlay;
@@ -1 +1 @@
1
- import{BreakFormat as e,Timebase as t,isPostRollBreak as r}from"../types/BreakManifest";import{DEFAULT_AD_BREAK_CUT_SAFETY_MARGIN_SEC as a,DEFAULT_DOUBLE_BOX_AUDIO as i}from"../types/OptiViewAdsConfig";import{pipOverridesFormat as s,pipSuppressesBackdrop as n}from"./pictureInPicture";import{AdBreakSequencer as o,assetsOfVariant as l,resolveBreakFormat as d}from"./AdBreakSequencer";import{selectVariantDetailed as h}from"./selectVariant";import{debugLog as u}from"../diagnostics/debugLog";import{systemClock as c}from"./BreakScheduler";import{createAdPlayerState as m}from"./adPlayer/AdPlayerState";import{PauseAdPresenter as g}from"./adPlayer/PauseAdPresenter";import{AssetParameterResolver as P}from"./adPlayer/AssetParameterResolver";import{GamPodBridge as y}from"./adPlayer/GamPodBridge";import{AdStage as p}from"./adPlayer/AdStage";import{AdPreloader as A}from"./adPlayer/AdPreloader";import{PresentationModeTracker as C}from"./adPlayer/PresentationModeTracker";import{SharedElementBreakPlayer as v}from"./adPlayer/SharedElementBreakPlayer";import{ContentResumeCoordinator as k}from"./adPlayer/ContentResumeCoordinator";import{AssetPlayback as f}from"./adPlayer/AssetPlayback";import{BreakSequenceDriver as S}from"./adPlayer/BreakSequenceDriver";import{VariantSelector as B}from"./adPlayer/VariantSelector";import{AdPlayerEvents as E}from"./adPlayer/AdPlayerEvents";export class AdPlayerController{get contentPaused(){return this.state.contentPaused}set contentPaused(e){this.state.contentPaused=e}get firstFrameGateActive(){return this.state.firstFrameGateActive}set firstFrameGateActive(e){this.state.firstFrameGateActive=e}get firstFrameGateShown(){return this.state.firstFrameGateShown}set firstFrameGateShown(e){this.state.firstFrameGateShown=e}get firstFrameRejector(){return this.state.firstFrameRejector}set firstFrameRejector(e){this.state.firstFrameRejector=e}get gamEventCallbacks(){return this.gamPodBridge.eventCallbacks}get preloadedBreakId(){return this.state.preloadedBreakId}set preloadedBreakId(e){this.state.preloadedBreakId=e}get vastAdManager(){return this.state.vastAdManager}set vastAdManager(e){this.state.vastAdManager=e}get currentBreak(){return this.state.currentBreak}set currentBreak(e){this.state.currentBreak=e}get currentAsset(){return this.state.currentAsset}set currentAsset(e){this.state.currentAsset=e}get callbacks(){return this.state.callbacks}set callbacks(e){this.state.callbacks=e}get isPlaying(){return this.state.isPlaying}set isPlaying(e){this.state.isPlaying=e}get effectiveBreakDurationSec(){return this.state.effectiveBreakDurationSec}set effectiveBreakDurationSec(e){this.state.effectiveBreakDurationSec=e}get adBreakCutSafetyMarginSec(){return this.state.adBreakCutSafetyMarginSec}set adBreakCutSafetyMarginSec(e){this.state.adBreakCutSafetyMarginSec=e}get durationTimer(){return this.state.durationTimer}set durationTimer(e){this.state.durationTimer=e}get adInsertionType(){return this.state.adInsertionType}set adInsertionType(e){this.state.adInsertionType=e}get breakTimebase(){return this.state.breakTimebase}set breakTimebase(e){this.state.breakTimebase=e}constructor(e,t,r,s,n=!1,o,l="parallel",d="overlay",h=a,u=i,b=!1,T=c){this.playerContainerCreatedBySDK=!1,this.adMediaContainer=null,this.adPlayer=null,this.adContainer=null,this.companionElement=null,this.staticQuartilesFired=new Set,this.sequencer=null,this.pendingAdError=null,this.preRollHoldActive=!1,this.preRollHoldHandler=null,this.breakContentGateHandler=null,this.RESUME_GUARD_RELEASE_GRACE_MS=2e3,this.SINGLE_DECODER_RESUME_DELAY_MS=300,this.abortingBreak=!1,this.overlayImageEl=null,this.imageAssetTimer=null,this.chainResolver=null,this.transitionPhaseReporter=null,this.chaining=!1,this.diagnoseHandler=null,this.playerContainerOriginalStyle="",this.companionRemoveTimer=null,this.adFadeTimer=null,this.syncing=!1,this.contentVolumeHandler=null,this.adVolumeChangeHandler=null,this.volumeChangeCallback=null,this.stage=null,this.resizeObserver=null,this.contentVideoElement=null,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.singleSurfaceDevice=!1,this.currentSelectedFormat=void 0,this.audioFocus=null,this.state=m(),this.state.playerContainer=r??null,this.clock=T,this.singleSurfaceDevice=b,this.contentPlayer=e,this.adBreakCutSafetyMarginSec=h,this.doubleBoxAudio=u,this.container=t,r&&(this.playerContainer=r),this.createAdAdapter=s,this.debug=n,this.state.gamConfig=o,this.preloadMode=l,this.insertionMode=d,this.pauseAdPresenter=new g({contentPlayer:this.contentPlayer,getStage:()=>this.stage,resolveUri:e=>this.resolveUri(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.assetParameters=new P(this.state,{contentPlayer:this.contentPlayer,container:this.container,getGamStreamManager:()=>this.state.gamStreamManager,getCurrentBreak:()=>this.currentBreak,getEffectiveBreakDuration:()=>this.effectiveBreakDurationSec,diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.gamPodBridge=new y(this.state,{contentPlayer:this.contentPlayer,debug:this.debug,getAdPlayer:()=>this.adPlayer,getAdContainer:()=>this.adContainer,isSharedElement:()=>"shared-element"===this.effectiveInsertionMode(),getCurrentBreak:()=>this.currentBreak,getCurrentAsset:()=>this.currentAsset,getEffectiveBreakDuration:()=>this.effectiveBreakDurationSec,getAdPlayerStreamRemainingSec:()=>this.adPlayerStreamRemainingSec(),emitStep:e=>this.emitStep(e),emitAdStart:(e,t,r,a,i)=>this.callbacks?.onAdStart(e,t,r,a,i),emitAdEnd:(e,t,r,a,i)=>this.callbacks?.onAdEnd(e,t,r,a,i),onAdEnded:()=>this.onAdEnded(),pushAssetParameters:(e,t)=>this.pushAssetParameters(e,t),resolveSessionAdTagParameters:()=>this.assetParameters.resolveSessionAdTagParameters(),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.adStage=new p({contentPlayer:this.contentPlayer,container:this.container,getPlayerContainer:()=>this.playerContainer,setPlayerContainer:e=>{this.playerContainer=e},setPlayerContainerOriginalStyle:e=>{this.playerContainerOriginalStyle=e},onPlayerContainerCreated:()=>{this.playerContainerCreatedBySDK=!0},getStage:()=>this.stage,setStage:e=>{this.stage=e},getContentVideo:()=>this.contentVideoElement,setContentVideo:e=>{this.contentVideoElement=e},getAdContainer:()=>this.adContainer,setAdContainer:e=>{this.adContainer=e},getAdMediaContainer:()=>this.adMediaContainer,setAdMediaContainer:e=>{this.adMediaContainer=e},setAdPlayer:e=>{this.adPlayer=e},createAdAdapter:e=>this.createAdAdapter(e),setupEventListeners:()=>this.setupEventListeners(),resizeVast:()=>this.vastAdManager?.resize(),getVariant:e=>this.getVariant(e),getCurrentBreak:()=>this.currentBreak,resolveUri:e=>this.resolveUri(e),getCompanion:()=>this.companionElement,setCompanion:e=>{this.companionElement=e},getOverlayImage:()=>this.overlayImageEl,setOverlayImage:e=>{this.overlayImageEl=e},getImageAssetTimer:()=>this.imageAssetTimer,setImageAssetTimer:e=>{this.imageAssetTimer=e},isChaining:()=>this.chaining,log:(...e)=>this.log(...e)}),this.preloader=new A({state:this.state,clock:this.clock,adStartBudgetMs:AdPlayerController.AD_START_BUDGET_MS,gamPodAdStartBudgetMs:AdPlayerController.GAM_POD_AD_START_BUDGET_MS,minAdStartBudgetMs:AdPlayerController.MIN_AD_START_BUDGET_MS,debug:this.debug,isPlaying:()=>this.isPlaying,preloadMode:this.preloadMode,effectiveInsertionMode:()=>this.effectiveInsertionMode(),getVariant:e=>this.getVariant(e),getAssets:e=>this.getAssets(e),resolveUri:e=>this.resolveUri(e),resolveAssetUri:(e,t)=>this.resolveAssetUri(e,t),getCompanionVariant:e=>this.getVariant(e),getAdPlayer:()=>this.adPlayer,getAdContainer:()=>this.adContainer,buildVastTagUrl:(e,t)=>this.buildVastTagUrl(e,t),resolveChainedSuccessor:e=>this.resolveChainedSuccessor(e),show:()=>this.show(),reapplyAdContainerLayout:e=>this.reapplyAdContainerLayout(e),getCurrentBreak:()=>this.currentBreak,onBreakVisible:e=>this.callbacks?.onBreakVisible?.(e),onAdError:e=>this.onAdError(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.presentationModes=new C(this.state,{contentPlayer:this.contentPlayer,getAdPlayer:()=>this.adPlayer,insertionMode:this.insertionMode,getCurrentBreak:()=>this.currentBreak,getCurrentFormat:()=>this.currentSelectedFormat,isSharedElementActive:()=>this.state.sharedAdClaimActive,getVariant:e=>this.getVariant(e),isAdPlaying:()=>this.isAdPlaying(),hasCurrentAsset:()=>null!==this.currentAsset,contentSourceIsReloadable:()=>null!==this.sharedElementPlayer.restorableContentSource(),adoptPictureInPictureMidBreak:()=>this.adoptPictureInPictureMidBreak(),restoreLayout:e=>this.restoreLayout(e),applyLayout:e=>this.applyLayout(e),show:()=>this.show(),playAdThroughContentElement:()=>this.sharedElementPlayer.playAdThroughContentElement(),getContentPositionAtBreakStart:()=>this.state.contentPositionAtBreakStart,setContentPositionAtBreakStart:e=>{this.state.contentPositionAtBreakStart=e},gateContentForBreak:()=>this.gateContentForBreak(),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.resumeCoordinator=new k(this.state,{contentPlayer:this.contentPlayer,clock:this.clock,getInsertionMode:()=>this.effectiveInsertionMode(),gateContentForBreak:()=>this.gateContentForBreak(),clearContentGate:e=>this.clearContentGate(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.sharedElementPlayer=new v(this.state,{contentPlayer:this.contentPlayer,getAdPlayer:()=>this.adPlayer,resolveAssetUri:(e,t)=>this.resolveAssetUri(e,t),liveEdgeDistanceSec:()=>this.resumeCoordinator.liveEdgeDistanceSec(),rebindContentElementIfSwapped:()=>this.rebindContentElementIfSwapped(),createAdAdapter:(e,t)=>this.createAdAdapter(e,t),reportAdProgress:e=>this.reportAdProgress(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e),clearContentGate:()=>this.clearContentGate(),hide:()=>this.hide(),waitForLiveEdge:()=>this.resumeCoordinator.waitForLiveEdge(),getVariant:e=>this.getVariant(e),getAssets:e=>this.getAssets(e)}),this.variantSelector=new B(this.state,{getEffectiveInsertionMode:()=>this.effectiveInsertionMode(),singleSurfaceDevice:this.singleSurfaceDevice,preloadMode:this.preloadMode,getIsContentPictureInPicture:()=>this.isContentPictureInPicture(),getIsGamReady:()=>this.isGamReady(),getSupportsParallelBuffering:()=>this.contentPlayer.supportsParallelBuffering,diagnose:(e,t,r)=>this.diagnose(e,t,r)}),this.assetPlayback=new f(this.state,{getAdPlayer:()=>this.adPlayer,getAdContainer:()=>this.adContainer,getAdMediaContainer:()=>this.adMediaContainer,clearStaticQuartiles:()=>this.staticQuartilesFired.clear(),getVariant:e=>this.getVariant(e),getGamEventCallbacks:()=>this.gamEventCallbacks,getSequencer:()=>this.sequencer,debug:this.debug,resolveUri:e=>this.resolveUri(e),buildGamUrl:(e,t)=>this.buildGamUrl(e,t),buildVastTagUrl:(e,t)=>this.buildVastTagUrl(e,t),onAdError:e=>this.onAdError(e),onAdEnded:()=>this.onAdEnded(),drive:e=>this.drive(e),clearOverlayImage:()=>this.clearOverlayImage(),revealFirstFrame:()=>this.preloader.revealFirstFrame(),transferPictureInPictureToAd:()=>this.presentationModes.transferPictureInPictureToAd(),isGamPodAsset:e=>this.isGamPodAsset(e),beginGamPodTracking:e=>this.beginGamPodTracking(e),recordAdProgress:e=>this.adPlayerEvents.recordAdProgress(e),reportPhase:e=>this.reportPhase(e),armAdStartBudget:e=>this.armAdStartBudget(e),remainingAdStartBudgetMs:()=>this.remainingAdStartBudgetMs(),loadWithinBudget:e=>this.loadWithinBudget(e),waitForFirstFrame:()=>this.waitForFirstFrame(),getOverlayImage:()=>this.overlayImageEl,setOverlayImage:e=>{this.overlayImageEl=e},getImageAssetTimer:()=>this.imageAssetTimer,setImageAssetTimer:e=>{this.imageAssetTimer=e},getVastPodPosition:()=>this.vastPodPosition,setVastPodPosition:e=>{this.vastPodPosition=e},diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.breakSequenceDriver=new S(this.state,{getChainResolver:()=>this.chainResolver,getEffectiveInsertionMode:()=>this.effectiveInsertionMode(),getVastPodPosition:()=>this.vastPodPosition,setVastPodPosition:e=>{this.vastPodPosition=e},getPendingAdError:()=>this.pendingAdError,setPendingAdError:e=>{this.pendingAdError=e},getChaining:()=>this.chaining,setChaining:e=>{this.chaining=e},isExpandingPodAsset:e=>this.isExpandingPodAsset(e),fallBackToPodLevelReporting:()=>this.fallBackToPodLevelReporting(),resetGamPodTracking:()=>this.resetGamPodTracking(),emitGamPodSteps:e=>this.gamPodBridge.emitGamPodSteps(e),playAsset:(e,t,r)=>this.playAsset(e,t,r),endBreak:()=>this.endBreak(),playBreak:(e,t,r)=>this.playBreak(e,t,r),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.adPlayerEvents=new E(this.state,{getAdPlayer:()=>this.adPlayer,getContentPlayer:()=>this.contentPlayer,getGamEventCallbacks:()=>this.gamEventCallbacks,getSequencer:()=>this.sequencer,getAudioFocus:()=>this.audioFocus,getSyncing:()=>this.syncing,setSyncing:e=>{this.syncing=e},setAdVolumeChangeHandler:e=>{this.adVolumeChangeHandler=e},setContentVolumeHandler:e=>{this.contentVolumeHandler=e},notifyVolumeChange:()=>this.notifyVolumeChange(),onAdEnded:()=>this.onAdEnded(),processTimedMetadata:e=>this.gamPodBridge.processTimedMetadata(e),toAdError:e=>this.toAdError(e),revealFirstFrame:()=>this.revealFirstFrame(),clearFirstFrameResolver:()=>this.preloader.clearFirstFrameResolver(),drive:e=>this.drive(e),setPendingAdError:e=>{this.pendingAdError=e},log:(...e)=>this.log(...e),maybePrepositionContentForResume:()=>this.resumeCoordinator.maybePrepositionContentForResume(),maybeCatchUpContentForResume:e=>this.resumeCoordinator.maybeCatchUpContentForResume(e),getStaticQuartilesFired:()=>this.staticQuartilesFired})}setGamEventCallbacks(e){this.gamPodBridge.setEventCallbacks(e)}setDiagnoseHandler(e){this.diagnoseHandler=e}diagnose(e,t,r){this.diagnoseHandler?.(e,t,r)}holdContentForPreRoll(e){if(this.preRollHoldActive)return;const t=this.getVariant(e);t&&this.shouldPauseContent(t.format)&&(this.preRollHoldActive=!0,this.contentPlayer.pause(),this.preRollHoldHandler=()=>{this.preRollHoldActive&&this.contentPlayer.pause()},this.contentPlayer.on("playing",this.preRollHoldHandler),this.diagnose("DA-PREROLL-CONTENT-HELD","Content held at session start until the pending pre-roll begins.",{breakId:e.id}))}releaseContentHold(){this.preRollHoldActive&&(this.preRollHoldActive=!1,this.preRollHoldHandler&&(this.contentPlayer.off("playing",this.preRollHoldHandler),this.preRollHoldHandler=null))}gateContentForBreak(){this.contentPlayer.pause(),this.breakContentGateHandler||(this.breakContentGateHandler=()=>{this.contentPaused&&!this.state.resumeCatchupActive&&this.contentPlayer.pause()},this.contentPlayer.on("playing",this.breakContentGateHandler))}clearContentGate(e=!1){this.resumeCoordinator.endResumeCatchup(e),this.breakContentGateHandler&&(this.contentPlayer.off("playing",this.breakContentGateHandler),this.breakContentGateHandler=null)}setChainResolver(e){this.chainResolver=e}setTransitionPhaseReporter(e){this.transitionPhaseReporter=e}reportPhase(e){this.transitionPhaseReporter?.(e)}setBreakResumePolicy(e,t,r=!1){this.adInsertionType=e,this.breakTimebase=t,this.state.continueContentDuringBreak=r,this.state.continueRefusalReported=!1}initialize(){this.adContainer||(this.setupOverlay(),this.presentationModes.setupPictureInPictureTracking(),"adaptive"===this.insertionMode&&this.presentationModes.setupFullscreenTracking())}async startGamSession(e,t){await this.gamPodBridge.startGamSession(e,t)}endGamSession(){this.gamPodBridge.endGamSession()}setSessionAssetParameters(e){this.assetParameters.setSessionAssetParameters(e)}setManifestAssetParameters(e){this.assetParameters.setManifestAssetParameters(e)}updateAssetParameters(e){this.assetParameters.updateAssetParameters(e)}get updatedAssetParameters(){return this.state.updatedAssetParameters}setSessionAssetParameterMacros(e){this.assetParameters.setSessionAssetParameterMacros(e)}buildVastTagUrl(e,t){return this.assetParameters.buildVastTagUrl(e,t)}pushAssetParameters(e,t){this.assetParameters.pushAssetParameters(e,t)}setupOverlay(){this.adStage.setupOverlay()}isGamPodAsset(e){return this.gamPodBridge.isGamPodAsset(e)}isExpandingPodAsset(e){return this.gamPodBridge.isExpandingPodAsset(e)}beginGamPodTracking(e){this.gamPodBridge.beginGamPodTracking(e)}fallBackToPodLevelReporting(){this.gamPodBridge.fallBackToPodLevelReporting()}resetGamPodTracking(){this.gamPodBridge.resetGamPodTracking()}emitGamPodSteps(e){this.gamPodBridge.emitGamPodSteps(e)}isGamEnabled(){return this.gamPodBridge.isGamEnabled()}isGamReady(){return this.gamPodBridge.isGamReady()}isOverlayInsertion(){return"overlay"===this.effectiveInsertionMode()}settleAssetProgress(){this.adPlayerEvents.settleAssetProgress()}reportAdProgress(e){this.adPlayerEvents.reportAdProgress(e)}setupEventListeners(){this.adPlayerEvents.setupEventListeners()}async preload(e){return this.preloader.preload(e)}renderLshapeContentBackdrop(e){this.adStage.renderLshapeContentBackdrop(e)}currentInsertionMode(){return this.presentationModes.currentInsertionMode()}effectiveInsertionMode(){return this.presentationModes.effectiveInsertionMode()}isContentPictureInPicture(){return this.presentationModes.isPictureInPicture()}rebindContentElementIfSwapped(){this.presentationModes.rebindContentElementIfSwapped()}teardownFullscreenTracking(){this.presentationModes.teardownFullscreenTracking()}teardownPictureInPictureTracking(){this.presentationModes.teardownPictureInPictureTracking()}isPictureInPicture(){return this.presentationModes.isPictureInPicture()}pictureInPictureOwner(){return this.presentationModes.pictureInPictureOwner()}isShowingAd(){if(!this.isAdPlaying())return!1;const e=this.currentDeclaredFormat();return!n(e,this.isContentPictureInPicture())}currentDeclaredFormat(){return this.currentSelectedFormat??(this.currentBreak?d(this.currentBreak):void 0)}async playBreak(t,a,i){this.releaseContentHold(),this.rebindContentElementIfSwapped(),this.sharedElementPlayer.clearSharedRestoreWatchdog();const n=i?.effectiveDurationSec??null;this.effectiveBreakDurationSec=null!==n&&n>0?n:null;const l=this.effectiveBreakDurationSec??t.duration;if("shared-element"===this.effectiveInsertionMode())return void await this.sharedElementPlayer.playSharedElementBreak(t,a);if(!this.adPlayer)return void this.log("playBreak called before initialize()");const d=this.getVariant(t);if(!d)return void this.log("No valid variant found for break:",t.id);this.currentBreak=t,this.callbacks=a,this.isPlaying=!0,this.firstFrameGateActive=i?.firstFrameGate??!1,this.firstFrameGateShown=!1;const h=this.continueContentDuringBreakReason(d.format),u=r(t),c=this.shouldPauseContent(d.format)&&"ok"===h&&!u;c||u||this.state.continueRefusalReported||!this.shouldPauseContent(d.format)||(this.state.continueRefusalReported=!0,this.diagnose("DA-CONTINUE-PLAYOUT-REFUSED",`Content will be paused for breaks this session: ${h}`,{reason:h,format:d.format})),this.audioFocus=d.format===e.DOUBLE?this.doubleBoxAudio:c?"ad":null,this.adPlayer&&("content"===this.audioFocus?(this.adPlayer.muted=!0,this.adPlayer.volume=0):(this.adPlayer.muted=this.contentPlayer.muted,this.adPlayer.volume=this.contentPlayer.volume,"ad"===this.audioFocus&&(this.contentPlayer.muted=!0)));const m=this.declaredFormatOf(t);if(this.currentSelectedFormat=m,this.state.pipAdoptedForBreak=s(m,this.isContentPictureInPicture()),this.state.pipAdoptedForBreak&&this.diagnose("DA-PIP-FORMAT-OVERRIDE",`Picture-in-picture: ${m} break plays as single`,{declared:m,effective:e.SINGLE,reason:"picture-in-picture",midBreak:!1}),this.shouldPauseContent(d.format)?(this.state.contentPositionAtBreakStart=this.contentPlayer.currentTime,this.resumeCoordinator.resetBreakResumeState(),c?(this.contentPaused=!1,this.resumeCoordinator.beginContinueContentPlayout()):(this.contentPaused=!0,this.gateContentForBreak())):this.contentPaused=!1,this.applyLayout(d),d.format===e.LSHAPE_CONTENT)return this.renderLshapeContentBackdrop(d),void(l&&l>0?(this.log("Starting duration timer:",l,"seconds"),this.durationTimer=setTimeout(()=>{this.log("Duration timer expired, ending break"),this.endBreak()},1e3*l)):this.endBreak());this.firstFrameGateActive||(this.show(),this.reapplyAdContainerLayout(d));if(0===this.getAssets(t).length)return this.log("No assets in break, ending immediately"),void this.endBreak();{const e=Math.max(0,l||0)+this.adBreakCutSafetyMarginSec;this.log("Starting break-cut timer:",e,"seconds"),this.durationTimer=setTimeout(()=>{this.log("Break-cut timer expired, ending break"),this.firstFrameRejector?this.onAdError(new Error("Break cut before first frame rendered")):this.sequencer&&this.currentBreak&&this.isPlaying?this.drive(this.sequencer.onOutcome({kind:"break-elapsed"})):this.endBreak()},1e3*e)}this.sequencer=new o("overlay",()=>null,this.selectionInputs()),await this.drive(this.sequencer.beginBreak(t)),this.preloadChainedSuccessor(t.id)}get resumeHandoverInMotion(){return this.state.resumeCatchupActive}reapplyAdContainerLayout(e){this.adStage.reapplyAdContainerLayout(e)}adoptPictureInPictureMidBreak(){this.presentationModes.adoptPictureInPictureMidBreak()}async restorePictureInPictureToContent(){await this.presentationModes.restorePictureInPictureToContent()}continueContentDuringBreakReason(e){return this.variantSelector.continueContentDuringBreakReason(e)}shouldPauseContent(t){return t===e.SINGLE||t===e.LSHAPE_AD}selectionInputs(){return this.variantSelector.selectionInputs()}breakNothingToPlayReason(e){return this.variantSelector.breakNothingToPlayReason(e)}unknownVariantFormatsOf(e){return this.variantSelector.unknownVariantFormatsOf(e)}diagnoseUnknownVariantFormats(e){this.variantSelector.diagnoseUnknownVariantFormats(e)}declaredFormatOf(e){const{selected:t,deviceQualified:r}=h(e,this.selectionInputs());return(t??r)?.format??d(e)}getVariant(e){return this.variantSelector.getVariant(e)}applyLayout(e){this.adStage.applyLayout(e)}restoreLayout(e){this.adStage.restoreLayout(e)}getAssets(e){return l(this.getVariant(e))}async playAsset(e,t,r){return this.assetPlayback.playAsset(e,t,r)}clearOverlayImage(){this.adStage.clearOverlayImage()}async showPauseImage(e){return this.pauseAdPresenter.showPauseImage(e)}hidePauseImage(){this.pauseAdPresenter.hidePauseImage()}async drive(e){return this.breakSequenceDriver.drive(e)}emitStep(e){this.breakSequenceDriver.emitStep(e)}async resolveAssetUri(e,t){return this.assetPlayback.resolveAssetUri(e,t)}async buildGamUrl(e,t){return this.gamPodBridge.buildGamUrl(e,t)}resolveUri(e){return this.assetParameters.resolveUri(e)}adPlayerStreamRemainingSec(){return this.assetPlayback.adPlayerStreamRemainingSec()}onAdEnded(){this.currentBreak&&this.isPlaying&&this.sequencer&&(this.settleAssetProgress(),this.drive(this.sequencer.onOutcome({kind:"asset-ended"})))}toAdError(e){return this.assetPlayback.toAdError(e)}onAdError(e){this.adPlayerEvents.onAdError(e)}endBreak(){if(!this.currentBreak)return;this.log("Break ended:",this.currentBreak.id),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.state.gamPodTracker&&this.emitGamPodSteps(this.state.gamPodTracker.finish()),this.resetGamPodTracking(),this.pushAssetParameters(void 0);const e=this.currentBreak,a=this.getVariant(e)?.format;let i=this.abortingBreak?null:this.resolveChainedSuccessor(e.id);if(i){const e=this.breakNothingToPlayReason(i);this.diagnoseUnknownVariantFormats(i),e&&(this.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`chained break ${i.id} has no playable variant (${e}); it will not begin.`,{breakId:i.id,reason:e}),this.callbacks?.onAdError(i,void 0,new Error(`Break ${i.id} has no playable variant (${e})`)),this.callbacks?.onChainedBreakSkipped?.(i),i=null)}if(i)this.advanceToChainedBreak(e,i);else{if(this.currentSelectedFormat=void 0,this.resumeCoordinator.endContinueContentPlayout(),this.restorePictureInPictureToContent(),this.isPlaying=!1,this.currentBreak=null,this.effectiveBreakDurationSec=null,this.preloadedBreakId=null,this.preloader.clearWarmedBreaks(),this.firstFrameGateActive=!1,this.firstFrameGateShown=!1,this.clearFirstFrame("Break ended before first frame"),"ad"===this.audioFocus&&this.adPlayer?(this.contentPlayer.muted=this.adPlayer.muted,this.contentPlayer.volume=this.adPlayer.volume):"content"===this.audioFocus&&this.adPlayer&&(this.adPlayer.muted=this.contentPlayer.muted,this.adPlayer.volume=this.contentPlayer.volume),this.audioFocus=null,this.clearOverlayImage(),this.vastAdManager?.reset(),this.hide(),this.restoreLayout(a),this.adPlayer?.pause(),this.state.sharedAdClaimActive)return this.sharedElementPlayer.detachSharedAdListeners(),this.sharedElementPlayer.restoreSharedElementContent(e),this.abortingBreak=!1,this.state.earlyReturnResumeOffsetSec=null,void this.callbacks?.onBreakEnd(e);if("single-decoder"===this.preloadMode&&this.adPlayer?.unload?.(),this.contentPaused&&r(e))this.contentPaused=!1,this.clearContentGate();else if(this.contentPaused){this.contentPaused=!1,this.clearContentGate(this.resumeHandoverInMotion);const r=this.state.earlyReturnResumeOffsetSec,a=this.breakTimebase===t.WALLCLOCK,i=()=>{a&&this.callbacks?.onContentReloading?.(!0),this.resumeCoordinator.applyResumeSeek(e,r),this.contentPlayer.play().catch(e=>{this.log("Content resume failed:",e)}).finally(()=>{a&&setTimeout(()=>this.callbacks?.onContentReloading?.(!1),this.RESUME_GUARD_RELEASE_GRACE_MS)})};"single-decoder"===this.preloadMode?setTimeout(i,this.SINGLE_DECODER_RESUME_DELAY_MS):i()}this.abortingBreak=!1,this.state.earlyReturnResumeOffsetSec=null,this.callbacks?.onBreakEnd(e)}}currentAdAsset(){return this.isPlaying?this.currentAsset:null}skipBreak(){this.isPlaying&&(this.log("Skipping break:",this.currentBreak?.id),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.sequencer&&this.currentBreak?this.drive(this.sequencer.onOutcome({kind:"break-elapsed"})):this.endBreak())}abortBreak(e){this.isPlaying&&(this.log("Early return — break removed from manifest:",this.currentBreak?.id),this.state.earlyReturnResumeOffsetSec=Math.max(0,e),this.abortingBreak=!0,this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.sequencer&&this.currentBreak?this.drive(this.sequencer.onOutcome({kind:"break-elapsed"})):this.endBreak())}stopActiveBreak(){this.isPlaying&&(this.log("Aborting active break on session end:",this.currentBreak?.id),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.sharedElementPlayer.detachSharedAdListeners(),this.sharedElementPlayer.releaseContentElementAdPlayer(),this.adPlayer?.pause(),this.clearOverlayImage(),this.vastAdManager?.reset(),this.hide(),this.restoreLayout(this.currentBreak?this.getVariant(this.currentBreak)?.format??void 0:void 0),this.isPlaying=!1,this.currentBreak=null,this.currentAsset=null,this.contentPaused=!1,this.clearContentGate(),this.resumeCoordinator.endContinueContentPlayout(),this.effectiveBreakDurationSec=null,this.firstFrameGateActive=!1,this.firstFrameGateShown=!1,this.clearFirstFrame("Break aborted before first frame"),this.audioFocus=null)}resolveChainedSuccessor(e){return this.breakSequenceDriver.resolveChainedSuccessor(e)}advanceToChainedBreak(e,t){this.breakSequenceDriver.advanceToChainedBreak(e,t)}preloadChainedSuccessor(e){this.preloader.preloadChainedSuccessor(e)}revealFirstFrame(){this.assetPlayback.revealFirstFrame()}armAdStartBudget(e){this.preloader.armAdStartBudget(e)}remainingAdStartBudgetMs(){return this.preloader.remainingAdStartBudgetMs()}loadWithinBudget(e){return this.preloader.loadWithinBudget(e)}async waitForFirstFrame(){return this.preloader.waitForFirstFrame()}clearFirstFrame(e="Break ended before first frame"){this.preloader.clearFirstFrame(e)}show(){this.adStage.show()}hide(){this.adStage.hide()}isAdPlaying(){return this.isPlaying}get muted(){return"content"===this.audioFocus?this.contentPlayer.muted:"ad"===this.audioFocus?this.adPlayer?this.adPlayer.muted:this.contentPlayer.muted:this.isPlaying&&this.adPlayer?this.adPlayer.muted:this.contentPlayer.muted}set muted(e){if("content"===this.audioFocus)this.contentPlayer.muted=e;else if("ad"===this.audioFocus)this.adPlayer&&(this.adPlayer.muted=e);else{(this.isPlaying&&this.adPlayer?this.adPlayer:this.contentPlayer).muted=e}this.vastAdManager?.setMuted(e),this.notifyVolumeChange()}get volume(){return"content"===this.audioFocus?this.contentPlayer.volume:"ad"===this.audioFocus?this.adPlayer?this.adPlayer.volume:this.contentPlayer.volume:this.isPlaying&&this.adPlayer?this.adPlayer.volume:this.contentPlayer.volume}set volume(e){if("content"===this.audioFocus)this.contentPlayer.volume=e;else if("ad"===this.audioFocus)this.adPlayer&&(this.adPlayer.volume=e);else{(this.isPlaying&&this.adPlayer?this.adPlayer:this.contentPlayer).volume=e}this.vastAdManager?.setVolume(e),this.notifyVolumeChange()}setVolumeChangeCallback(e){this.volumeChangeCallback=e}notifyVolumeChange(){this.volumeChangeCallback?.(this.muted,this.volume)}destroy(){if(this.adStage.destroy(),this.resumeCoordinator.cancelResumeLandedProbe(),this.resumeCoordinator.endResumeCatchup(),this.resumeCoordinator.endContinueContentPlayout(),this.state.resumePrepositionSettle&&(clearTimeout(this.state.resumePrepositionSettle),this.state.resumePrepositionSettle=null),this.sharedElementPlayer.clearSharedRestoreWatchdog(),this.releaseContentHold(),this.clearContentGate(),this.clearFirstFrame("Controller destroyed before first frame"),this.firstFrameGateActive=!1,this.firstFrameGateShown=!1,this.resetGamPodTracking(),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.companionRemoveTimer&&(clearTimeout(this.companionRemoveTimer),this.companionRemoveTimer=null),this.adFadeTimer&&(clearTimeout(this.adFadeTimer),this.adFadeTimer=null),this.adPlayer?.pause(),this.clearOverlayImage(),this.pauseAdPresenter.destroy(),this.contentVolumeHandler&&(this.contentPlayer.off("volumechange",this.contentVolumeHandler),this.contentVolumeHandler=null),this.sharedElementPlayer.detachSharedAdListeners(),this.sharedElementPlayer.releaseContentElementAdPlayer(),this.state.pipTransferredToAd=!1,this.teardownFullscreenTracking(),this.teardownPictureInPictureTracking(),this.adVolumeChangeHandler&&this.adPlayer&&this.adPlayer.off("volumechange",this.adVolumeChangeHandler),this.adVolumeChangeHandler=null,this.hide(),this.resizeObserver?.disconnect(),this.resizeObserver=null,this.contentVideoMutationObserver?.disconnect(),this.contentVideoMutationObserver=null,this.contentVideoElement&&this.contentVideoGeometryHandler&&(this.contentVideoElement.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),this.contentVideoElement.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoElement=null,this.contentVideoGeometryHandler=null,this.playerContainerCreatedBySDK)for(;this.playerContainer.firstChild;)this.container.appendChild(this.playerContainer.firstChild);else this.container.appendChild(this.playerContainer),this.playerContainer.style.cssText=this.playerContainerOriginalStyle;this.companionElement&&this.companionElement.parentElement&&(this.companionElement.parentElement.removeChild(this.companionElement),this.companionElement=null),this.isPlaying=!1,this.currentBreak=null,this.adContainer&&this.adContainer.parentElement&&this.adContainer.parentElement.removeChild(this.adContainer),this.stage&&this.stage.parentElement&&this.stage.parentElement.removeChild(this.stage),this.stage=null,this.adContainer=null,this.adMediaContainer=null;try{this.adPlayer?.destroy()}catch(e){this.log("Ad player adapter destroy failed (ignored):",e)}this.adPlayer=null,this.callbacks=null,this.gamPodBridge.destroy(),this.vastAdManager?.destroy(),this.vastAdManager=null,this.log("Ad player destroyed")}log(...e){this.debug&&u("[OptiViewAds:AdPlayerController]",...e)}}AdPlayerController.AD_START_BUDGET_MS=5e3,AdPlayerController.GAM_POD_AD_START_BUDGET_MS=1e4,AdPlayerController.MIN_AD_START_BUDGET_MS=3e3;
1
+ import{BreakFormat as e,Timebase as t,isPostRollBreak as r}from"../types/BreakManifest";import{DEFAULT_AD_BREAK_CUT_SAFETY_MARGIN_SEC as a,DEFAULT_DOUBLE_BOX_AUDIO as s}from"../types/OptiViewAdsConfig";import{pipOverridesFormat as i,pipSuppressesBackdrop as n}from"./pictureInPicture";import{AdBreakSequencer as o,assetsOfVariant as l,resolveBreakFormat as d}from"./AdBreakSequencer";import{selectVariantDetailed as h}from"./selectVariant";import{debugLog as u}from"../diagnostics/debugLog";import{systemClock as c}from"./BreakScheduler";import{createAdPlayerState as m}from"./adPlayer/AdPlayerState";import{PauseAdPresenter as g}from"./adPlayer/PauseAdPresenter";import{AssetParameterResolver as P}from"./adPlayer/AssetParameterResolver";import{GamPodBridge as y}from"./adPlayer/GamPodBridge";import{AdStage as p}from"./adPlayer/AdStage";import{AdPreloader as A}from"./adPlayer/AdPreloader";import{PresentationModeTracker as C}from"./adPlayer/PresentationModeTracker";import{SharedElementBreakPlayer as k}from"./adPlayer/SharedElementBreakPlayer";import{ContentResumeCoordinator as v}from"./adPlayer/ContentResumeCoordinator";import{AssetPlayback as f}from"./adPlayer/AssetPlayback";import{BreakSequenceDriver as S}from"./adPlayer/BreakSequenceDriver";import{VariantSelector as B}from"./adPlayer/VariantSelector";import{AdPlayerEvents as E}from"./adPlayer/AdPlayerEvents";export class AdPlayerController{get contentPaused(){return this.state.contentPaused}set contentPaused(e){this.state.contentPaused=e}get firstFrameGateActive(){return this.state.firstFrameGateActive}set firstFrameGateActive(e){this.state.firstFrameGateActive=e}get firstFrameGateShown(){return this.state.firstFrameGateShown}set firstFrameGateShown(e){this.state.firstFrameGateShown=e}get firstFrameRejector(){return this.state.firstFrameRejector}set firstFrameRejector(e){this.state.firstFrameRejector=e}get gamEventCallbacks(){return this.gamPodBridge.eventCallbacks}get preloadedBreakId(){return this.state.preloadedBreakId}set preloadedBreakId(e){this.state.preloadedBreakId=e}get vastAdManager(){return this.state.vastAdManager}set vastAdManager(e){this.state.vastAdManager=e}get currentBreak(){return this.state.currentBreak}set currentBreak(e){this.state.currentBreak=e}get currentAsset(){return this.state.currentAsset}set currentAsset(e){this.state.currentAsset=e}get callbacks(){return this.state.callbacks}set callbacks(e){this.state.callbacks=e}get isPlaying(){return this.state.isPlaying}set isPlaying(e){this.state.isPlaying=e}get effectiveBreakDurationSec(){return this.state.effectiveBreakDurationSec}set effectiveBreakDurationSec(e){this.state.effectiveBreakDurationSec=e}get adBreakCutSafetyMarginSec(){return this.state.adBreakCutSafetyMarginSec}set adBreakCutSafetyMarginSec(e){this.state.adBreakCutSafetyMarginSec=e}get durationTimer(){return this.state.durationTimer}set durationTimer(e){this.state.durationTimer=e}get adInsertionType(){return this.state.adInsertionType}set adInsertionType(e){this.state.adInsertionType=e}get breakTimebase(){return this.state.breakTimebase}set breakTimebase(e){this.state.breakTimebase=e}constructor(e,t,r,i,n=!1,o,l="parallel",d="overlay",h=a,u=s,b=!1,T=c){this.playerContainerCreatedBySDK=!1,this.adMediaContainer=null,this.adPlayer=null,this.adContainer=null,this.companionElement=null,this.staticQuartilesFired=new Set,this.sequencer=null,this.pendingAdError=null,this.preRollHoldActive=!1,this.preRollHoldHandler=null,this.breakContentGateHandler=null,this.RESUME_GUARD_RELEASE_GRACE_MS=2e3,this.SINGLE_DECODER_RESUME_DELAY_MS=300,this.abortingBreak=!1,this.overlayImageEl=null,this.imageAssetTimer=null,this.chainResolver=null,this.transitionPhaseReporter=null,this.chaining=!1,this.diagnoseHandler=null,this.playerContainerOriginalStyle="",this.companionRemoveTimer=null,this.adFadeTimer=null,this.syncing=!1,this.contentVolumeHandler=null,this.adVolumeChangeHandler=null,this.volumeChangeCallback=null,this.stage=null,this.resizeObserver=null,this.contentVideoElement=null,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.singleSurfaceDevice=!1,this.currentSelectedFormat=void 0,this.audioFocus=null,this.state=m(),this.state.playerContainer=r??null,this.clock=T,this.singleSurfaceDevice=b,this.contentPlayer=e,this.adBreakCutSafetyMarginSec=h,this.doubleBoxAudio=u,this.container=t,r&&(this.playerContainer=r),this.createAdAdapter=i,this.debug=n,this.state.gamConfig=o,this.preloadMode=l,this.insertionMode=d,this.pauseAdPresenter=new g({contentPlayer:this.contentPlayer,getStage:()=>this.stage,resolveUri:e=>this.resolveUri(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.assetParameters=new P(this.state,{contentPlayer:this.contentPlayer,container:this.container,getGamStreamManager:()=>this.state.gamStreamManager,getCurrentBreak:()=>this.currentBreak,getEffectiveBreakDuration:()=>this.effectiveBreakDurationSec,diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.gamPodBridge=new y(this.state,{contentPlayer:this.contentPlayer,debug:this.debug,getAdPlayer:()=>this.adPlayer,getAdContainer:()=>this.adContainer,isSharedElement:()=>"shared-element"===this.effectiveInsertionMode(),getCurrentBreak:()=>this.currentBreak,getCurrentAsset:()=>this.currentAsset,getEffectiveBreakDuration:()=>this.effectiveBreakDurationSec,getAdPlayerStreamRemainingSec:()=>this.adPlayerStreamRemainingSec(),emitStep:e=>this.emitStep(e),emitAdStart:(e,t,r,a,s)=>this.callbacks?.onAdStart(e,t,r,a,s),emitAdEnd:(e,t,r,a,s)=>this.callbacks?.onAdEnd(e,t,r,a,s),onAdEnded:()=>this.onAdEnded(),pushAssetParameters:(e,t)=>this.pushAssetParameters(e,t),resolveSessionAdTagParameters:()=>this.assetParameters.resolveSessionAdTagParameters(),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.adStage=new p({contentPlayer:this.contentPlayer,container:this.container,getPlayerContainer:()=>this.playerContainer,setPlayerContainer:e=>{this.playerContainer=e},setPlayerContainerOriginalStyle:e=>{this.playerContainerOriginalStyle=e},onPlayerContainerCreated:()=>{this.playerContainerCreatedBySDK=!0},getStage:()=>this.stage,setStage:e=>{this.stage=e},getContentVideo:()=>this.contentVideoElement,setContentVideo:e=>{this.contentVideoElement=e},getAdContainer:()=>this.adContainer,setAdContainer:e=>{this.adContainer=e},getAdMediaContainer:()=>this.adMediaContainer,setAdMediaContainer:e=>{this.adMediaContainer=e},setAdPlayer:e=>{this.adPlayer=e},createAdAdapter:e=>this.createAdAdapter(e),setupEventListeners:()=>this.setupEventListeners(),resizeVast:()=>this.vastAdManager?.resize(),getVariant:e=>this.getVariant(e),getCurrentBreak:()=>this.currentBreak,resolveUri:e=>this.resolveUri(e),getCompanion:()=>this.companionElement,setCompanion:e=>{this.companionElement=e},getOverlayImage:()=>this.overlayImageEl,setOverlayImage:e=>{this.overlayImageEl=e},getImageAssetTimer:()=>this.imageAssetTimer,setImageAssetTimer:e=>{this.imageAssetTimer=e},isChaining:()=>this.chaining,log:(...e)=>this.log(...e)}),this.preloader=new A({state:this.state,clock:this.clock,adStartBudgetMs:AdPlayerController.AD_START_BUDGET_MS,gamPodAdStartBudgetMs:AdPlayerController.GAM_POD_AD_START_BUDGET_MS,minAdStartBudgetMs:AdPlayerController.MIN_AD_START_BUDGET_MS,debug:this.debug,isPlaying:()=>this.isPlaying,preloadMode:this.preloadMode,effectiveInsertionMode:()=>this.effectiveInsertionMode(),getVariant:e=>this.getVariant(e),getAssets:e=>this.getAssets(e),resolveUri:e=>this.resolveUri(e),resolveAssetUri:(e,t)=>this.resolveAssetUri(e,t),getCompanionVariant:e=>this.getVariant(e),getAdPlayer:()=>this.adPlayer,getAdContainer:()=>this.adContainer,buildVastTagUrl:(e,t)=>this.buildVastTagUrl(e,t),resolveChainedSuccessor:e=>this.resolveChainedSuccessor(e),show:()=>this.show(),reapplyAdContainerLayout:e=>this.reapplyAdContainerLayout(e),getCurrentBreak:()=>this.currentBreak,onBreakVisible:e=>this.callbacks?.onBreakVisible?.(e),onAdError:e=>this.onAdError(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.presentationModes=new C(this.state,{contentPlayer:this.contentPlayer,getAdPlayer:()=>this.adPlayer,insertionMode:this.insertionMode,getCurrentBreak:()=>this.currentBreak,getCurrentFormat:()=>this.currentSelectedFormat,isSharedElementActive:()=>this.state.sharedAdClaimActive,getVariant:e=>this.getVariant(e),isAdPlaying:()=>this.isAdPlaying(),hasCurrentAsset:()=>null!==this.currentAsset,contentSourceIsReloadable:()=>null!==this.sharedElementPlayer.restorableContentSource(),adoptPictureInPictureMidBreak:()=>this.adoptPictureInPictureMidBreak(),restoreLayout:e=>this.restoreLayout(e),applyLayout:e=>this.applyLayout(e),show:()=>this.show(),playAdThroughContentElement:()=>this.sharedElementPlayer.playAdThroughContentElement(),getContentPositionAtBreakStart:()=>this.state.contentPositionAtBreakStart,setContentPositionAtBreakStart:e=>{this.state.contentPositionAtBreakStart=e},gateContentForBreak:()=>this.gateContentForBreak(),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.resumeCoordinator=new v(this.state,{contentPlayer:this.contentPlayer,clock:this.clock,getInsertionMode:()=>this.effectiveInsertionMode(),gateContentForBreak:()=>this.gateContentForBreak(),clearContentGate:e=>this.clearContentGate(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.sharedElementPlayer=new k(this.state,{contentPlayer:this.contentPlayer,getAdPlayer:()=>this.adPlayer,resolveAssetUri:(e,t)=>this.resolveAssetUri(e,t),liveEdgeDistanceSec:()=>this.resumeCoordinator.liveEdgeDistanceSec(),rebindContentElementIfSwapped:()=>this.rebindContentElementIfSwapped(),createAdAdapter:(e,t)=>this.createAdAdapter(e,t),reportAdProgress:e=>this.reportAdProgress(e),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e),clearContentGate:()=>this.clearContentGate(),hide:()=>this.hide(),waitForLiveEdge:()=>this.resumeCoordinator.waitForLiveEdge(),getVariant:e=>this.getVariant(e),getAssets:e=>this.getAssets(e)}),this.variantSelector=new B(this.state,{getEffectiveInsertionMode:()=>this.effectiveInsertionMode(),singleSurfaceDevice:this.singleSurfaceDevice,preloadMode:this.preloadMode,getIsContentPictureInPicture:()=>this.isContentPictureInPicture(),getIsGamReady:()=>this.isGamReady(),getSupportsParallelBuffering:()=>this.contentPlayer.supportsParallelBuffering,diagnose:(e,t,r)=>this.diagnose(e,t,r)}),this.assetPlayback=new f(this.state,{getAdPlayer:()=>this.adPlayer,getAdContainer:()=>this.adContainer,getAdMediaContainer:()=>this.adMediaContainer,clearStaticQuartiles:()=>this.staticQuartilesFired.clear(),getVariant:e=>this.getVariant(e),getGamEventCallbacks:()=>this.gamEventCallbacks,getSequencer:()=>this.sequencer,debug:this.debug,resolveUri:e=>this.resolveUri(e),buildGamUrl:(e,t)=>this.buildGamUrl(e,t),buildVastTagUrl:(e,t)=>this.buildVastTagUrl(e,t),onAdError:e=>this.onAdError(e),onAdEnded:()=>this.onAdEnded(),drive:e=>this.drive(e),clearOverlayImage:()=>this.clearOverlayImage(),revealFirstFrame:()=>this.preloader.revealFirstFrame(),transferPictureInPictureToAd:()=>this.presentationModes.transferPictureInPictureToAd(),isGamPodAsset:e=>this.isGamPodAsset(e),beginGamPodTracking:e=>this.beginGamPodTracking(e),recordAdProgress:e=>this.adPlayerEvents.recordAdProgress(e),reportPhase:e=>this.reportPhase(e),armAdStartBudget:e=>this.armAdStartBudget(e),remainingAdStartBudgetMs:()=>this.remainingAdStartBudgetMs(),loadWithinBudget:e=>this.loadWithinBudget(e),waitForFirstFrame:()=>this.waitForFirstFrame(),getOverlayImage:()=>this.overlayImageEl,setOverlayImage:e=>{this.overlayImageEl=e},getImageAssetTimer:()=>this.imageAssetTimer,setImageAssetTimer:e=>{this.imageAssetTimer=e},getVastPodPosition:()=>this.vastPodPosition,setVastPodPosition:e=>{this.vastPodPosition=e},diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.breakSequenceDriver=new S(this.state,{getChainResolver:()=>this.chainResolver,getEffectiveInsertionMode:()=>this.effectiveInsertionMode(),getVastPodPosition:()=>this.vastPodPosition,setVastPodPosition:e=>{this.vastPodPosition=e},getPendingAdError:()=>this.pendingAdError,setPendingAdError:e=>{this.pendingAdError=e},getChaining:()=>this.chaining,setChaining:e=>{this.chaining=e},isExpandingPodAsset:e=>this.isExpandingPodAsset(e),fallBackToPodLevelReporting:()=>this.fallBackToPodLevelReporting(),resetGamPodTracking:()=>this.resetGamPodTracking(),emitGamPodSteps:e=>this.gamPodBridge.emitGamPodSteps(e),playAsset:(e,t,r)=>this.playAsset(e,t,r),endBreak:()=>this.endBreak(),playBreak:(e,t,r)=>this.playBreak(e,t,r),diagnose:(e,t,r)=>this.diagnose(e,t,r),log:(...e)=>this.log(...e)}),this.adPlayerEvents=new E(this.state,{getAdPlayer:()=>this.adPlayer,getContentPlayer:()=>this.contentPlayer,getGamEventCallbacks:()=>this.gamEventCallbacks,getSequencer:()=>this.sequencer,getAudioFocus:()=>this.audioFocus,getSyncing:()=>this.syncing,setSyncing:e=>{this.syncing=e},setAdVolumeChangeHandler:e=>{this.adVolumeChangeHandler=e},setContentVolumeHandler:e=>{this.contentVolumeHandler=e},notifyVolumeChange:()=>this.notifyVolumeChange(),onAdEnded:()=>this.onAdEnded(),processTimedMetadata:e=>this.gamPodBridge.processTimedMetadata(e),toAdError:e=>this.toAdError(e),revealFirstFrame:()=>this.revealFirstFrame(),clearFirstFrameResolver:()=>this.preloader.clearFirstFrameResolver(),drive:e=>this.drive(e),setPendingAdError:e=>{this.pendingAdError=e},log:(...e)=>this.log(...e),maybePrepositionContentForResume:()=>this.resumeCoordinator.maybePrepositionContentForResume(),maybeCatchUpContentForResume:e=>this.resumeCoordinator.maybeCatchUpContentForResume(e),getStaticQuartilesFired:()=>this.staticQuartilesFired,onGamPodAdProgress:e=>this.gamPodBridge.onGamPodAdProgress(e)})}setGamEventCallbacks(e){this.gamPodBridge.setEventCallbacks(e)}setDiagnoseHandler(e){this.diagnoseHandler=e}diagnose(e,t,r){this.diagnoseHandler?.(e,t,r)}holdContentForPreRoll(e){if(this.preRollHoldActive)return;const t=this.getVariant(e);t&&this.shouldPauseContent(t.format)&&(this.preRollHoldActive=!0,this.contentPlayer.pause(),this.preRollHoldHandler=()=>{this.preRollHoldActive&&this.contentPlayer.pause()},this.contentPlayer.on("playing",this.preRollHoldHandler),this.diagnose("DA-PREROLL-CONTENT-HELD","Content held at session start until the pending pre-roll begins.",{breakId:e.id}))}releaseContentHold(){this.preRollHoldActive&&(this.preRollHoldActive=!1,this.preRollHoldHandler&&(this.contentPlayer.off("playing",this.preRollHoldHandler),this.preRollHoldHandler=null))}gateContentForBreak(){this.contentPlayer.pause(),this.breakContentGateHandler||(this.breakContentGateHandler=()=>{this.contentPaused&&!this.state.resumeCatchupActive&&this.contentPlayer.pause()},this.contentPlayer.on("playing",this.breakContentGateHandler))}clearContentGate(e=!1){this.resumeCoordinator.endResumeCatchup(e),this.breakContentGateHandler&&(this.contentPlayer.off("playing",this.breakContentGateHandler),this.breakContentGateHandler=null)}setChainResolver(e){this.chainResolver=e}setTransitionPhaseReporter(e){this.transitionPhaseReporter=e}reportPhase(e){this.transitionPhaseReporter?.(e)}setBreakResumePolicy(e,t,r=!1){this.adInsertionType=e,this.breakTimebase=t,this.state.continueContentDuringBreak=r,this.state.continueRefusalReported=!1}initialize(){this.adContainer||(this.setupOverlay(),this.presentationModes.setupPictureInPictureTracking(),"adaptive"===this.insertionMode&&this.presentationModes.setupFullscreenTracking())}async startGamSession(e,t){await this.gamPodBridge.startGamSession(e,t)}endGamSession(){this.gamPodBridge.endGamSession()}setSessionAssetParameters(e){this.assetParameters.setSessionAssetParameters(e)}setManifestAssetParameters(e){this.assetParameters.setManifestAssetParameters(e)}updateAssetParameters(e){this.assetParameters.updateAssetParameters(e)}updateAssetParameterMacros(e){this.assetParameters.updateAssetParameterMacros(e)}get updatedAssetParameters(){return this.state.updatedAssetParameters}setSessionAssetParameterMacros(e){this.assetParameters.setSessionAssetParameterMacros(e)}get sessionAssetParameterMacros(){return this.assetParameters.getSessionAssetParameterMacros()}buildVastTagUrl(e,t){return this.assetParameters.buildVastTagUrl(e,t)}pushAssetParameters(e,t){this.assetParameters.pushAssetParameters(e,t)}setupOverlay(){this.adStage.setupOverlay()}isGamPodAsset(e){return this.gamPodBridge.isGamPodAsset(e)}isExpandingPodAsset(e){return this.gamPodBridge.isExpandingPodAsset(e)}beginGamPodTracking(e){this.gamPodBridge.beginGamPodTracking(e)}fallBackToPodLevelReporting(){this.gamPodBridge.fallBackToPodLevelReporting()}resetGamPodTracking(){this.gamPodBridge.resetGamPodTracking()}emitGamPodSteps(e){this.gamPodBridge.emitGamPodSteps(e)}isGamEnabled(){return this.gamPodBridge.isGamEnabled()}isGamReady(){return this.gamPodBridge.isGamReady()}isOverlayInsertion(){return"overlay"===this.effectiveInsertionMode()}settleAssetProgress(){this.adPlayerEvents.settleAssetProgress()}reportAdProgress(e){this.adPlayerEvents.reportAdProgress(e)}setupEventListeners(){this.adPlayerEvents.setupEventListeners()}async preload(e){return this.preloader.preload(e)}renderLshapeContentBackdrop(e){this.adStage.renderLshapeContentBackdrop(e)}currentInsertionMode(){return this.presentationModes.currentInsertionMode()}effectiveInsertionMode(){return this.presentationModes.effectiveInsertionMode()}isContentPictureInPicture(){return this.presentationModes.isPictureInPicture()}rebindContentElementIfSwapped(){this.presentationModes.rebindContentElementIfSwapped()}teardownFullscreenTracking(){this.presentationModes.teardownFullscreenTracking()}teardownPictureInPictureTracking(){this.presentationModes.teardownPictureInPictureTracking()}isPictureInPicture(){return this.presentationModes.isPictureInPicture()}pictureInPictureOwner(){return this.presentationModes.pictureInPictureOwner()}isShowingAd(){if(!this.isAdPlaying())return!1;const e=this.currentDeclaredFormat();return!n(e,this.isContentPictureInPicture())}currentDeclaredFormat(){return this.currentSelectedFormat??(this.currentBreak?d(this.currentBreak):void 0)}async playBreak(t,a,s){this.releaseContentHold(),this.rebindContentElementIfSwapped(),this.sharedElementPlayer.clearSharedRestoreWatchdog();const n=s?.effectiveDurationSec??null;this.effectiveBreakDurationSec=null!==n&&n>0?n:null;const l=this.effectiveBreakDurationSec??t.duration;if("shared-element"===this.effectiveInsertionMode())return void await this.sharedElementPlayer.playSharedElementBreak(t,a);if(!this.adPlayer)return void this.log("playBreak called before initialize()");const d=this.getVariant(t);if(!d)return void this.log("No valid variant found for break:",t.id);this.currentBreak=t,this.callbacks=a,this.isPlaying=!0,this.firstFrameGateActive=s?.firstFrameGate??!1,this.firstFrameGateShown=!1;const h=this.continueContentDuringBreakReason(d.format),u=r(t),c=this.shouldPauseContent(d.format)&&"ok"===h&&!u;c||u||this.state.continueRefusalReported||!this.shouldPauseContent(d.format)||(this.state.continueRefusalReported=!0,this.diagnose("DA-CONTINUE-PLAYOUT-REFUSED",`Content will be paused for breaks this session: ${h}`,{reason:h,format:d.format})),this.audioFocus=d.format===e.DOUBLE?this.doubleBoxAudio:c?"ad":null,this.adPlayer&&("content"===this.audioFocus?(this.adPlayer.muted=!0,this.adPlayer.volume=0):(this.adPlayer.muted=this.contentPlayer.muted,this.adPlayer.volume=this.contentPlayer.volume,"ad"===this.audioFocus&&(this.contentPlayer.muted=!0)));const m=this.declaredFormatOf(t);if(this.currentSelectedFormat=m,this.state.pipAdoptedForBreak=i(m,this.isContentPictureInPicture()),this.state.pipAdoptedForBreak&&this.diagnose("DA-PIP-FORMAT-OVERRIDE",`Picture-in-picture: ${m} break plays as single`,{declared:m,effective:e.SINGLE,reason:"picture-in-picture",midBreak:!1}),this.shouldPauseContent(d.format)?(this.state.contentPositionAtBreakStart=this.contentPlayer.currentTime,this.resumeCoordinator.resetBreakResumeState(),c?(this.contentPaused=!1,this.resumeCoordinator.beginContinueContentPlayout()):(this.contentPaused=!0,this.gateContentForBreak())):this.contentPaused=!1,this.applyLayout(d),d.format===e.LSHAPE_CONTENT)return this.renderLshapeContentBackdrop(d),void(l&&l>0?(this.log("Starting duration timer:",l,"seconds"),this.durationTimer=setTimeout(()=>{this.log("Duration timer expired, ending break"),this.endBreak()},1e3*l)):this.endBreak());this.firstFrameGateActive||(this.show(),this.reapplyAdContainerLayout(d));if(0===this.getAssets(t).length)return this.log("No assets in break, ending immediately"),void this.endBreak();{const e=Math.max(0,l||0)+this.adBreakCutSafetyMarginSec;this.log("Starting break-cut timer:",e,"seconds"),this.durationTimer=setTimeout(()=>{this.log("Break-cut timer expired, ending break"),this.firstFrameRejector?this.onAdError(new Error("Break cut before first frame rendered")):this.sequencer&&this.currentBreak&&this.isPlaying?(this.callbacks?.onBreakCut?.(this.currentBreak),this.drive(this.sequencer.onOutcome({kind:"break-elapsed"}))):this.endBreak()},1e3*e)}this.sequencer=new o("overlay",()=>null,this.selectionInputs()),await this.drive(this.sequencer.beginBreak(t)),this.preloadChainedSuccessor(t.id)}get resumeHandoverInMotion(){return this.state.resumeCatchupActive}reapplyAdContainerLayout(e){this.adStage.reapplyAdContainerLayout(e)}adoptPictureInPictureMidBreak(){this.presentationModes.adoptPictureInPictureMidBreak()}async restorePictureInPictureToContent(){await this.presentationModes.restorePictureInPictureToContent()}continueContentDuringBreakReason(e){return this.variantSelector.continueContentDuringBreakReason(e)}shouldPauseContent(t){return t===e.SINGLE||t===e.LSHAPE_AD}selectionInputs(){return this.variantSelector.selectionInputs()}breakNothingToPlayReason(e){return this.variantSelector.breakNothingToPlayReason(e)}unknownVariantFormatsOf(e){return this.variantSelector.unknownVariantFormatsOf(e)}diagnoseUnknownVariantFormats(e){this.variantSelector.diagnoseUnknownVariantFormats(e)}declaredFormatOf(e){const{selected:t,deviceQualified:r}=h(e,this.selectionInputs());return(t??r)?.format??d(e)}getVariant(e){return this.variantSelector.getVariant(e)}applyLayout(e){this.adStage.applyLayout(e)}restoreLayout(e){this.adStage.restoreLayout(e)}getAssets(e){return l(this.getVariant(e))}async playAsset(e,t,r){return this.assetPlayback.playAsset(e,t,r)}clearOverlayImage(){this.adStage.clearOverlayImage()}async showPauseImage(e){return this.pauseAdPresenter.showPauseImage(e)}hidePauseImage(){this.pauseAdPresenter.hidePauseImage()}async drive(e){return this.breakSequenceDriver.drive(e)}emitStep(e){this.breakSequenceDriver.emitStep(e)}async resolveAssetUri(e,t){return this.assetPlayback.resolveAssetUri(e,t)}async buildGamUrl(e,t){return this.gamPodBridge.buildGamUrl(e,t)}resolveUri(e){return this.assetParameters.resolveUri(e)}adPlayerStreamRemainingSec(){return this.assetPlayback.adPlayerStreamRemainingSec()}onAdEnded(){this.currentBreak&&this.isPlaying&&this.sequencer&&(this.settleAssetProgress(),this.drive(this.sequencer.onOutcome({kind:"asset-ended"})))}toAdError(e){return this.assetPlayback.toAdError(e)}onAdError(e){this.adPlayerEvents.onAdError(e)}endBreak(){if(!this.currentBreak)return;this.log("Break ended:",this.currentBreak.id),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.state.gamPodTracker&&this.emitGamPodSteps(this.state.gamPodTracker.finish()),this.resetGamPodTracking(),this.pushAssetParameters(void 0);const e=this.currentBreak,a=this.getVariant(e)?.format;let s=this.abortingBreak?null:this.resolveChainedSuccessor(e.id);if(s){const e=this.breakNothingToPlayReason(s);this.diagnoseUnknownVariantFormats(s),e&&(this.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`chained break ${s.id} has no playable variant (${e}); it will not begin.`,{breakId:s.id,reason:e}),this.callbacks?.onAdError(s,void 0,new Error(`Break ${s.id} has no playable variant (${e})`)),this.callbacks?.onChainedBreakSkipped?.(s),s=null)}if(s)this.advanceToChainedBreak(e,s);else{if(this.currentSelectedFormat=void 0,this.resumeCoordinator.endContinueContentPlayout(),this.restorePictureInPictureToContent(),this.isPlaying=!1,this.currentBreak=null,this.effectiveBreakDurationSec=null,this.preloadedBreakId=null,this.preloader.clearWarmedBreaks(),this.firstFrameGateActive=!1,this.firstFrameGateShown=!1,this.clearFirstFrame("Break ended before first frame"),"ad"===this.audioFocus&&this.adPlayer?(this.contentPlayer.muted=this.adPlayer.muted,this.contentPlayer.volume=this.adPlayer.volume):"content"===this.audioFocus&&this.adPlayer&&(this.adPlayer.muted=this.contentPlayer.muted,this.adPlayer.volume=this.contentPlayer.volume),this.audioFocus=null,this.clearOverlayImage(),this.vastAdManager?.reset(),this.hide(),this.restoreLayout(a),this.adPlayer?.pause(),this.state.sharedAdClaimActive)return this.sharedElementPlayer.detachSharedAdListeners(),this.sharedElementPlayer.restoreSharedElementContent(e),this.abortingBreak=!1,this.state.earlyReturnResumeOffsetSec=null,void this.callbacks?.onBreakEnd(e);if("single-decoder"===this.preloadMode&&this.adPlayer?.unload?.(),this.contentPaused&&r(e))this.contentPaused=!1,this.clearContentGate();else if(this.contentPaused){this.contentPaused=!1,this.clearContentGate(this.resumeHandoverInMotion);const r=this.state.earlyReturnResumeOffsetSec,a=this.breakTimebase===t.WALLCLOCK,s=()=>{a&&this.callbacks?.onContentReloading?.(!0),this.resumeCoordinator.applyResumeSeek(e,r),this.contentPlayer.play().catch(e=>{this.log("Content resume failed:",e)}).finally(()=>{a&&setTimeout(()=>this.callbacks?.onContentReloading?.(!1),this.RESUME_GUARD_RELEASE_GRACE_MS)})};"single-decoder"===this.preloadMode?setTimeout(s,this.SINGLE_DECODER_RESUME_DELAY_MS):s()}this.abortingBreak=!1,this.state.earlyReturnResumeOffsetSec=null,this.callbacks?.onBreakEnd(e)}}currentAdAsset(){return this.isPlaying?this.currentAsset:null}skipBreak(){this.isPlaying&&(this.log("Skipping break:",this.currentBreak?.id),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.sequencer&&this.currentBreak?this.drive(this.sequencer.onOutcome({kind:"break-elapsed"})):this.endBreak())}abortBreak(e){this.isPlaying&&(this.log("Early return — break removed from manifest:",this.currentBreak?.id),this.state.earlyReturnResumeOffsetSec=Math.max(0,e),this.abortingBreak=!0,this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.sequencer&&this.currentBreak?this.drive(this.sequencer.onOutcome({kind:"break-elapsed"})):this.endBreak())}stopActiveBreak(){this.isPlaying&&(this.log("Aborting active break on session end:",this.currentBreak?.id),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.sharedElementPlayer.detachSharedAdListeners(),this.sharedElementPlayer.releaseContentElementAdPlayer(),this.adPlayer?.pause(),this.clearOverlayImage(),this.vastAdManager?.reset(),this.hide(),this.restoreLayout(this.currentBreak?this.getVariant(this.currentBreak)?.format??void 0:void 0),this.isPlaying=!1,this.currentBreak=null,this.currentAsset=null,this.contentPaused=!1,this.clearContentGate(),this.resumeCoordinator.endContinueContentPlayout(),this.effectiveBreakDurationSec=null,this.firstFrameGateActive=!1,this.firstFrameGateShown=!1,this.clearFirstFrame("Break aborted before first frame"),this.audioFocus=null)}resolveChainedSuccessor(e){return this.breakSequenceDriver.resolveChainedSuccessor(e)}advanceToChainedBreak(e,t){this.breakSequenceDriver.advanceToChainedBreak(e,t)}preloadChainedSuccessor(e){this.preloader.preloadChainedSuccessor(e)}revealFirstFrame(){this.assetPlayback.revealFirstFrame()}armAdStartBudget(e){this.preloader.armAdStartBudget(e)}remainingAdStartBudgetMs(){return this.preloader.remainingAdStartBudgetMs()}loadWithinBudget(e){return this.preloader.loadWithinBudget(e)}async waitForFirstFrame(){return this.preloader.waitForFirstFrame()}clearFirstFrame(e="Break ended before first frame"){this.preloader.clearFirstFrame(e)}show(){this.adStage.show()}hide(){this.adStage.hide()}isAdPlaying(){return this.isPlaying}get muted(){return"content"===this.audioFocus?this.contentPlayer.muted:"ad"===this.audioFocus?this.adPlayer?this.adPlayer.muted:this.contentPlayer.muted:this.isPlaying&&this.adPlayer?this.adPlayer.muted:this.contentPlayer.muted}set muted(e){if("content"===this.audioFocus)this.contentPlayer.muted=e;else if("ad"===this.audioFocus)this.adPlayer&&(this.adPlayer.muted=e);else{(this.isPlaying&&this.adPlayer?this.adPlayer:this.contentPlayer).muted=e}this.vastAdManager?.setMuted(e),this.notifyVolumeChange()}get volume(){return"content"===this.audioFocus?this.contentPlayer.volume:"ad"===this.audioFocus?this.adPlayer?this.adPlayer.volume:this.contentPlayer.volume:this.isPlaying&&this.adPlayer?this.adPlayer.volume:this.contentPlayer.volume}set volume(e){if("content"===this.audioFocus)this.contentPlayer.volume=e;else if("ad"===this.audioFocus)this.adPlayer&&(this.adPlayer.volume=e);else{(this.isPlaying&&this.adPlayer?this.adPlayer:this.contentPlayer).volume=e}this.vastAdManager?.setVolume(e),this.notifyVolumeChange()}setVolumeChangeCallback(e){this.volumeChangeCallback=e}notifyVolumeChange(){this.volumeChangeCallback?.(this.muted,this.volume)}destroy(){if(this.adStage.destroy(),this.resumeCoordinator.cancelResumeLandedProbe(),this.resumeCoordinator.endResumeCatchup(),this.resumeCoordinator.endContinueContentPlayout(),this.state.resumePrepositionSettle&&(clearTimeout(this.state.resumePrepositionSettle),this.state.resumePrepositionSettle=null),this.sharedElementPlayer.clearSharedRestoreWatchdog(),this.releaseContentHold(),this.clearContentGate(),this.clearFirstFrame("Controller destroyed before first frame"),this.firstFrameGateActive=!1,this.firstFrameGateShown=!1,this.resetGamPodTracking(),this.durationTimer&&(clearTimeout(this.durationTimer),this.durationTimer=null),this.companionRemoveTimer&&(clearTimeout(this.companionRemoveTimer),this.companionRemoveTimer=null),this.adFadeTimer&&(clearTimeout(this.adFadeTimer),this.adFadeTimer=null),this.adPlayer?.pause(),this.clearOverlayImage(),this.pauseAdPresenter.destroy(),this.contentVolumeHandler&&(this.contentPlayer.off("volumechange",this.contentVolumeHandler),this.contentVolumeHandler=null),this.sharedElementPlayer.detachSharedAdListeners(),this.sharedElementPlayer.releaseContentElementAdPlayer(),this.state.pipTransferredToAd=!1,this.teardownFullscreenTracking(),this.teardownPictureInPictureTracking(),this.adVolumeChangeHandler&&this.adPlayer&&this.adPlayer.off("volumechange",this.adVolumeChangeHandler),this.adVolumeChangeHandler=null,this.hide(),this.resizeObserver?.disconnect(),this.resizeObserver=null,this.contentVideoMutationObserver?.disconnect(),this.contentVideoMutationObserver=null,this.contentVideoElement&&this.contentVideoGeometryHandler&&(this.contentVideoElement.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),this.contentVideoElement.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoElement=null,this.contentVideoGeometryHandler=null,this.playerContainerCreatedBySDK)for(;this.playerContainer.firstChild;)this.container.appendChild(this.playerContainer.firstChild);else this.container.appendChild(this.playerContainer),this.playerContainer.style.cssText=this.playerContainerOriginalStyle;this.companionElement&&this.companionElement.parentElement&&(this.companionElement.parentElement.removeChild(this.companionElement),this.companionElement=null),this.isPlaying=!1,this.currentBreak=null,this.adContainer&&this.adContainer.parentElement&&this.adContainer.parentElement.removeChild(this.adContainer),this.stage&&this.stage.parentElement&&this.stage.parentElement.removeChild(this.stage),this.stage=null,this.adContainer=null,this.adMediaContainer=null;try{this.adPlayer?.destroy()}catch(e){this.log("Ad player adapter destroy failed (ignored):",e)}this.adPlayer=null,this.callbacks=null,this.gamPodBridge.destroy(),this.vastAdManager?.destroy(),this.vastAdManager=null,this.log("Ad player destroyed")}log(...e){this.debug&&u("[OptiViewAds:AdPlayerController]",...e)}}AdPlayerController.AD_START_BUDGET_MS=5e3,AdPlayerController.GAM_POD_AD_START_BUDGET_MS=1e4,AdPlayerController.MIN_AD_START_BUDGET_MS=3e3;
@@ -36,6 +36,7 @@ export declare class CmcdAdReporter {
36
36
  private activeAdDeclaredSec;
37
37
  private skipRequestedBreakId;
38
38
  private abortedBreakId;
39
+ private cutBreakId;
39
40
  private erroredAssetId;
40
41
  constructor(getHandle: () => LensHandle | null, log: (...args: unknown[]) => void);
41
42
  /**
@@ -49,6 +50,8 @@ export declare class CmcdAdReporter {
49
50
  onSkipRequested(breakId: string): void;
50
51
  /** The break is being aborted (early return / teardown) — the next `adend` is an abort. */
51
52
  onBreakAborted(breakId: string): void;
53
+ /** The break-cut timer ended the break with an ad in flight — that ad did not complete. */
54
+ onBreakCut(breakId: string): void;
52
55
  /** Feed one SDK event from the unified funnel. */
53
56
  onEvent(event: OptiViewAdsEventMap[OptiViewAdsEventType]): void;
54
57
  private onBreakBegin;
@@ -1 +1 @@
1
- import{LensEventType as e,LensToken as t}from"../vendor/lens-sdk/index.js";import{AssetType as i,BreakFormat as s}from"../types/BreakManifest";import{VendorAdError as a}from"../types/VendorAdError";import{SDK_VERSION as d}from"../generated/version";const r={[s.SINGLE]:"s",[s.DOUBLE]:"d",[s.LSHAPE_AD]:"la",[s.LSHAPE_CONTENT]:"lc",[s.OVERLAY]:"o"},n={[i.STATIC]:"s",[i.VAST]:"va",[i.VENDOR]:"ve"};function o(e){return n[e]??e}export class CmcdAdReporter{constructor(e,t){this.getHandle=e,this.log=t,this.sessionKeys={},this.activeBreakId=null,this.breakBeginTimestamp=null,this.firstAdReported=!1,this.breakFilledMs=0,this.breakWatchedMs=0,this.activeAdKeys=null,this.activeAdAssetId=null,this.pendingAdStart=null,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=null,this.skipRequestedBreakId=null,this.abortedBreakId=null,this.erroredAssetId=null}onSessionStart(e){this.sessionKeys={adv:d,admv:e.version,...e.organizationId?{adoid:e.organizationId}:{},...e.channelId?{adcid:e.channelId}:{},...e.eventId?{adeid:e.eventId}:{}},this.updateHeartbeat()}onSessionEnd(){this.sessionKeys={},this.resetBreakState(),this.guard("setHeartbeatPayload",e=>e.setHeartbeatPayload())}onSkipRequested(e){this.skipRequestedBreakId=e}onBreakAborted(e){this.abortedBreakId=e}onEvent(e){switch(e.type){case"adbreakbegin":this.onBreakBegin(e);break;case"adbegin":this.onAdBegin(e);break;case"adtimeupdate":{const t=e;null!==this.pendingAdStart&&t.asset.id===this.pendingAdStart.assetId&&t.currentTime>0&&null!==this.breakBeginTimestamp&&this.emitPendingAdStart(t.timestamp-this.breakBeginTimestamp),t.asset.id===this.activeAdAssetId&&(this.activeAdWatchedSec=Math.max(this.activeAdWatchedSec,t.currentTime));break}case"adend":this.onAdEnd(e);break;case"adbreakend":this.onBreakEnd(e);break;case"adfirstquartile":case"admidpoint":case"adthirdquartile":this.onMilestone(e);break;case"adclick":this.onClick(e);break;case"aderror":this.onAdError(e)}}onBreakBegin(i){this.resetBreakState(),this.activeBreakId=i.break.id,this.breakBeginTimestamp=i.timestamp;const s=Math.round(1e3*i.remainingSec),a=this.requestedPlayableMs(i.break,i.format,s);this.guard("abs",d=>{return d.emit(e.AD_BREAK_START,{custom:{...this.sessionKeys,brid:i.break.id,brst:String(i.break.start),brmd:Math.round(1e3*i.break.duration),brad:s,...null!==a?{brrd:a}:{},...i.format?{brf:new t((n=i.format,r[n]??n))}:{}}});var n}),this.updateHeartbeat()}onAdBegin(e){this.emitPendingAdStart(null);const i=e.asset,s=i.interaction?.clickThrough,a=!this.firstAdReported&&null!==this.breakBeginTimestamp;this.firstAdReported=!0;const d={brid:e.break.id,adasid:i.id,adid:e.adId??i.id,...void 0!==e.podPosition?{adpos:String(e.podPosition)}:{},...void 0!==i.duration?{adasd:Math.round(1e3*i.duration)}:{},adast:new t(o(i.type)),...s?{adcl:!0}:{},...e.creativeId?{adgcrid:e.creativeId}:{},...e.adId&&"string"==typeof i.uri&&"vendor"===i.type?{adgpid:i.uri}:{}};this.activeAdKeys=d,this.activeAdAssetId=i.id,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=i.duration??null;const r={...this.sessionKeys,...d},n=i.companion?{...this.sessionKeys,brid:e.break.id,adasid:i.companion.id,adid:i.companion.id,adast:new t(o(i.companion.type)),adcomp:!0,adpasid:i.id}:null;a?this.pendingAdStart={assetId:i.id,custom:r,companionCustom:n}:this.emitAdStart(r,n),this.updateHeartbeat()}emitAdStart(t,i){this.guard("as",i=>i.emit(e.AD_START,{custom:t})),i&&this.guard("as",t=>t.emit(e.AD_START,{custom:i}))}emitPendingAdStart(e){const t=this.pendingAdStart;if(!t)return;this.pendingAdStart=null;const i=null!==e?{...t.custom,brttf:e}:t.custom;this.emitAdStart(i,t.companionCustom)}onAdEnd(i){this.emitPendingAdStart(null);const s=i.asset,a=this.adEndReason(i),d=this.activeAdKeys??{brid:i.break.id,adasid:s.id,adid:i.adId??s.id},r=null!==this.activeAdDeclaredSec?Math.round(1e3*this.activeAdDeclaredSec):null,n=Math.round(1e3*this.activeAdWatchedSec),o=null!==r?Math.min(n,r):n;if("s"===a&&this.guard("sk",t=>t.emit(e.AD_SKIP,{custom:{...this.sessionKeys,...d}})),this.guard("ae",i=>i.emit(e.AD_END,{custom:{...this.sessionKeys,...d,ader:new t(a),adwd:o}})),s.companion){const d=s.companion;this.guard("ae",r=>r.emit(e.AD_END,{custom:{...this.sessionKeys,brid:i.break.id,adasid:d.id,adid:d.id,adcomp:!0,adpasid:s.id,ader:new t(a)}}))}this.breakWatchedMs+=o,this.breakFilledMs+=null!==r?r:o,this.activeAdKeys=null,this.activeAdAssetId=null,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=null,this.erroredAssetId===s.id&&(this.erroredAssetId=null),this.updateHeartbeat()}onBreakEnd(t){this.emitPendingAdStart(null),this.guard("abe",i=>i.emit(e.AD_BREAK_END,{custom:{...this.sessionKeys,brid:t.break.id,brfd:this.breakFilledMs,brwd:this.breakWatchedMs}})),this.resetBreakState(),this.updateHeartbeat()}onMilestone(e){this.emitPendingAdStart(null),this.emitCustomWithAdContext(e.type,e.break,e.asset)}onClick(e){this.emitPendingAdStart(null),this.emitCustomWithAdContext("adclick",e.break,e.asset)}onAdError(e){"ad"===e.source&&(this.emitPendingAdStart(null),e.asset&&(this.erroredAssetId=e.asset.id),this.guard("aderror",t=>t.emitCustom("aderror",{custom:{...this.sessionKeys,adec:e.asset?"DA-AD-PLAYBACK-ERROR":"DA-BREAK-NO-PLAYABLE-ASSET",...e.error instanceof a?{adevc:e.error.vendorCode}:{},...e.break?{brid:e.break.id}:{},...e.asset?{adasid:e.asset.id,adid:e.asset.id}:{}}})))}emitCustomWithAdContext(e,t,i){this.guard(e,s=>s.emitCustom(e,{custom:{...this.sessionKeys,brid:t.id,adasid:i.id,...void 0!==this.activeAdKeys?.adid?{adid:this.activeAdKeys.adid}:{}}}))}adEndReason(e){return this.skipRequestedBreakId===e.break.id?(this.skipRequestedBreakId=null,"s"):this.erroredAssetId===e.asset.id?"e":this.abortedBreakId===e.break.id?"a":"c"}requestedPlayableMs(e,t,i){const s=Array.isArray(e.variant)?e.variant:[e.variant],a=s.find(e=>e.format===t)??s[0];if(!a)return null;const d=a.assets.reduce((e,t)=>e+(t.duration??0),0);return d<=0?null:Math.min(Math.round(1e3*d),i)}updateHeartbeat(){this.guard("setHeartbeatPayload",e=>e.setHeartbeatPayload({custom:{...this.sessionKeys,...this.activeBreakId?{brid:this.activeBreakId}:{},...void 0!==this.activeAdKeys?.adid?{adid:this.activeAdKeys.adid}:{}}}))}resetBreakState(){this.emitPendingAdStart(null),this.activeBreakId=null,this.breakBeginTimestamp=null,this.firstAdReported=!1,this.breakFilledMs=0,this.breakWatchedMs=0,this.activeAdKeys=null,this.activeAdAssetId=null,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=null,this.skipRequestedBreakId=null,this.abortedBreakId=null,this.erroredAssetId=null}guard(e,t){const i=this.getHandle();if(i)try{t(i)}catch(t){this.log(`Lens CMCD ${e} failed:`,t)}}}
1
+ import{LensEventType as t,LensToken as e}from"../vendor/lens-sdk/index.js";import{AssetType as i,BreakFormat as s}from"../types/BreakManifest";import{VendorAdError as a}from"../types/VendorAdError";import{SDK_VERSION as d}from"../generated/version";const r={[s.SINGLE]:"s",[s.DOUBLE]:"d",[s.LSHAPE_AD]:"la",[s.LSHAPE_CONTENT]:"lc",[s.OVERLAY]:"o"},n={[i.STATIC]:"s",[i.VAST]:"va",[i.VENDOR]:"ve"};function o(t){return n[t]??t}export class CmcdAdReporter{constructor(t,e){this.getHandle=t,this.log=e,this.sessionKeys={},this.activeBreakId=null,this.breakBeginTimestamp=null,this.firstAdReported=!1,this.breakFilledMs=0,this.breakWatchedMs=0,this.activeAdKeys=null,this.activeAdAssetId=null,this.pendingAdStart=null,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=null,this.skipRequestedBreakId=null,this.abortedBreakId=null,this.cutBreakId=null,this.erroredAssetId=null}onSessionStart(t){this.sessionKeys={adv:d,admv:t.version,...t.organizationId?{adoid:t.organizationId}:{},...t.channelId?{adcid:t.channelId}:{},...t.eventId?{adeid:t.eventId}:{}},this.updateHeartbeat()}onSessionEnd(){this.sessionKeys={},this.resetBreakState(),this.guard("setHeartbeatPayload",t=>t.setHeartbeatPayload())}onSkipRequested(t){this.skipRequestedBreakId=t}onBreakAborted(t){this.abortedBreakId=t}onBreakCut(t){this.cutBreakId=t}onEvent(t){switch(t.type){case"adbreakbegin":this.onBreakBegin(t);break;case"adbegin":this.onAdBegin(t);break;case"adtimeupdate":{const e=t;null!==this.pendingAdStart&&e.asset.id===this.pendingAdStart.assetId&&e.currentTime>0&&null!==this.breakBeginTimestamp&&this.emitPendingAdStart(e.timestamp-this.breakBeginTimestamp),e.asset.id===this.activeAdAssetId&&(this.activeAdWatchedSec=Math.max(this.activeAdWatchedSec,e.currentTime));break}case"adend":this.onAdEnd(t);break;case"adbreakend":this.onBreakEnd(t);break;case"adfirstquartile":case"admidpoint":case"adthirdquartile":this.onMilestone(t);break;case"adclick":this.onClick(t);break;case"aderror":this.onAdError(t)}}onBreakBegin(i){this.resetBreakState(),this.activeBreakId=i.break.id,this.breakBeginTimestamp=i.timestamp;const s=Math.round(1e3*i.remainingSec),a=this.requestedPlayableMs(i.break,i.format,s);this.guard("abs",d=>{return d.emit(t.AD_BREAK_START,{custom:{...this.sessionKeys,brid:i.break.id,brst:String(i.break.start),brmd:Math.round(1e3*i.break.duration),brad:s,...null!==a?{brrd:a}:{},...i.format?{brf:new e((n=i.format,r[n]??n))}:{}}});var n}),this.updateHeartbeat()}onAdBegin(t){this.emitPendingAdStart(null);const i=t.asset,s=i.interaction?.clickThrough,a=!this.firstAdReported&&null!==this.breakBeginTimestamp;this.firstAdReported=!0;const d={brid:t.break.id,adasid:i.id,adid:t.adId??i.id,...void 0!==t.podPosition?{adpos:String(t.podPosition)}:{},...void 0!==i.duration?{adasd:Math.round(1e3*i.duration)}:{},adast:new e(o(i.type)),...s?{adcl:!0}:{},...t.creativeId?{adgcrid:t.creativeId}:{},...t.adId&&"string"==typeof i.uri&&"vendor"===i.type?{adgpid:i.uri}:{}};this.activeAdKeys=d,this.activeAdAssetId=i.id,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=i.duration??null;const r={...this.sessionKeys,...d},n=i.companion?{...this.sessionKeys,brid:t.break.id,adasid:i.companion.id,adid:i.companion.id,adast:new e(o(i.companion.type)),adcomp:!0,adpasid:i.id}:null;a?this.pendingAdStart={assetId:i.id,custom:r,companionCustom:n}:this.emitAdStart(r,n),this.updateHeartbeat()}emitAdStart(e,i){this.guard("as",i=>i.emit(t.AD_START,{custom:e})),i&&this.guard("as",e=>e.emit(t.AD_START,{custom:i}))}emitPendingAdStart(t){const e=this.pendingAdStart;if(!e)return;this.pendingAdStart=null;const i=null!==t?{...e.custom,brttf:t}:e.custom;this.emitAdStart(i,e.companionCustom)}onAdEnd(i){this.emitPendingAdStart(null);const s=i.asset,a=this.adEndReason(i),d=this.activeAdKeys??{brid:i.break.id,adasid:s.id,adid:i.adId??s.id},r=null!==this.activeAdDeclaredSec?Math.round(1e3*this.activeAdDeclaredSec):null,n=Math.round(1e3*this.activeAdWatchedSec),o=null!==r?Math.min(n,r):n;if("s"===a&&this.guard("sk",e=>e.emit(t.AD_SKIP,{custom:{...this.sessionKeys,...d}})),this.guard("ae",i=>i.emit(t.AD_END,{custom:{...this.sessionKeys,...d,ader:new e(a),adwd:o}})),s.companion){const d=s.companion;this.guard("ae",r=>r.emit(t.AD_END,{custom:{...this.sessionKeys,brid:i.break.id,adasid:d.id,adid:d.id,adcomp:!0,adpasid:s.id,ader:new e(a)}}))}this.breakWatchedMs+=o,this.breakFilledMs+=null!==r?r:o,this.activeAdKeys=null,this.activeAdAssetId=null,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=null,this.erroredAssetId===s.id&&(this.erroredAssetId=null),this.updateHeartbeat()}onBreakEnd(e){this.emitPendingAdStart(null),this.guard("abe",i=>i.emit(t.AD_BREAK_END,{custom:{...this.sessionKeys,brid:e.break.id,brfd:this.breakFilledMs,brwd:this.breakWatchedMs}})),this.resetBreakState(),this.updateHeartbeat()}onMilestone(t){this.emitPendingAdStart(null),this.emitCustomWithAdContext(t.type,t.break,t.asset)}onClick(t){this.emitPendingAdStart(null),this.emitCustomWithAdContext("adclick",t.break,t.asset)}onAdError(t){"ad"===t.source&&(this.emitPendingAdStart(null),t.asset&&(this.erroredAssetId=t.asset.id),this.guard("aderror",e=>e.emitCustom("aderror",{custom:{...this.sessionKeys,adec:t.asset?"DA-AD-PLAYBACK-ERROR":"DA-BREAK-NO-PLAYABLE-ASSET",...t.error instanceof a?{adevc:t.error.vendorCode}:{},...t.break?{brid:t.break.id}:{},...t.asset?{adasid:t.asset.id,adid:t.asset.id}:{}}})))}emitCustomWithAdContext(t,e,i){this.guard(t,s=>s.emitCustom(t,{custom:{...this.sessionKeys,brid:e.id,adasid:i.id,...void 0!==this.activeAdKeys?.adid?{adid:this.activeAdKeys.adid}:{}}}))}adEndReason(t){return this.skipRequestedBreakId===t.break.id?(this.skipRequestedBreakId=null,"s"):this.erroredAssetId===t.asset.id?"e":this.abortedBreakId===t.break.id?"a":this.cutBreakId===t.break.id?(this.cutBreakId=null,"a"):"c"}requestedPlayableMs(t,e,i){const s=Array.isArray(t.variant)?t.variant:[t.variant],a=s.find(t=>t.format===e)??s[0];if(!a)return null;const d=a.assets.reduce((t,e)=>t+(e.duration??0),0);return d<=0?null:Math.min(Math.round(1e3*d),i)}updateHeartbeat(){this.guard("setHeartbeatPayload",t=>t.setHeartbeatPayload({custom:{...this.sessionKeys,...this.activeBreakId?{brid:this.activeBreakId}:{},...void 0!==this.activeAdKeys?.adid?{adid:this.activeAdKeys.adid}:{}}}))}resetBreakState(){this.emitPendingAdStart(null),this.activeBreakId=null,this.breakBeginTimestamp=null,this.firstAdReported=!1,this.breakFilledMs=0,this.breakWatchedMs=0,this.activeAdKeys=null,this.activeAdAssetId=null,this.activeAdWatchedSec=0,this.activeAdDeclaredSec=null,this.skipRequestedBreakId=null,this.abortedBreakId=null,this.cutBreakId=null,this.erroredAssetId=null}guard(t,e){const i=this.getHandle();if(i)try{e(i)}catch(e){this.log(`Lens CMCD ${t} failed:`,e)}}}
@@ -1 +1 @@
1
- import{buildGamPodUrl as t}from"./GamPodUrlBuilder";import{debugLog as e}from"../diagnostics/debugLog";export class GamStreamManager{constructor(t,e,a=!1){this.videoElement=t,this.adUiContainer=e,this.streamManager=null,this.streamId=null,this.config=null,this.initPromise=null,this.callbacks={},this.debug=a}setCallbacks(t){this.callbacks=t}isImaSdkAvailable(){return"undefined"!=typeof google&&void 0!==google.ima&&void 0!==google.ima.dai&&void 0!==google.ima.dai.api}async initialize(t){return this.streamManager&&this.reset(),this.config=t,this.initPromise=this.doInitialize(),this.initPromise}async initializeLiveStream(t){if(this.streamManager&&this.reset(),!this.isImaSdkAvailable())throw new Error('Google IMA DAI SDK not found. Include <script src="https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js"><\/script> in your page.');const e=this.adUiContainer||this.videoElement.parentElement||document.body;this.streamManager=new google.ima.dai.api.StreamManager(this.videoElement,e);const a=new google.ima.dai.api.LiveStreamRequest;return a.assetKey=t.assetKey,a.format="hls",t.networkCode&&(a.networkCode=t.networkCode),t.adTagParameters&&(a.adTagParameters=t.adTagParameters),t.streamActivityMonitorId&&(a.streamActivityMonitorId=t.streamActivityMonitorId),new Promise((e,i)=>{this.setupEventListeners(void 0,i,t=>e(t)),this.log(`Requesting live stream for asset: ${t.assetKey}`),this.streamManager.requestStream(a)})}async doInitialize(){if(!this.isImaSdkAvailable())throw new Error('Google IMA DAI SDK not found. Include <script src="https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js"><\/script> in your page.');if(!this.config)throw new Error("GamStreamManager config not set");this.log("Initializing IMA StreamManager...");const t=this.adUiContainer||this.videoElement.parentElement||document.body;this.streamManager=new google.ima.dai.api.StreamManager(this.videoElement,t);const e=new google.ima.dai.api.PodStreamRequest;return e.networkCode=this.config.networkCode,e.customAssetKey=this.config.customAssetKey,e.format="hls",this.config.adTagParameters&&(e.adTagParameters=this.config.adTagParameters),this.config.streamActivityMonitorId&&(e.streamActivityMonitorId=this.config.streamActivityMonitorId),new Promise((t,a)=>{this.setupEventListeners(t,a),this.log(`Requesting stream for network: ${this.config.networkCode}, asset: ${this.config.customAssetKey}`),this.streamManager.requestStream(e)})}setupEventListeners(t,e,a){if(!this.streamManager)return;const i=google.ima.dai.api.StreamEvent.Type;this.streamManager.addEventListener(i.STREAM_INITIALIZED,e=>{const a=e.getStreamData();this.streamId=a.streamId,this.log(`Stream initialized, streamId: ${this.streamId}`),t?.()}),this.streamManager.addEventListener(i.LOADED,t=>{const e=t.getStreamData();this.streamId=e.streamId,e.url&&(this.log(`Stream loaded, url: ${e.url}`),a?.(e.url))}),this.streamManager.addEventListener(i.AD_BREAK_STARTED,()=>{this.log("Ad break started"),this.callbacks.onAdBreakStarted?.()}),this.streamManager.addEventListener(i.AD_BREAK_ENDED,()=>{this.log("Ad break ended"),this.callbacks.onAdBreakEnded?.()}),this.streamManager.addEventListener(i.STARTED,t=>{const e=function(t){const e=t.getAd?.();if(!e)return;const a=e.getAdPodInfo?.();return{adPosition:a?.getAdPosition(),totalAds:a?.getTotalAds(),adId:e.getAdId?.(),creativeId:e.getCreativeId?.()}}(t);this.log(`Ad started (ad ${e?.adPosition??"?"} of ${e?.totalAds??"?"})`),this.callbacks.onAdStarted?.(e)}),this.streamManager.addEventListener(i.COMPLETE,()=>{this.log("Ad complete"),this.callbacks.onAdComplete?.()}),this.streamManager.addEventListener(i.FIRST_QUARTILE,()=>{this.log("Ad first quartile"),this.callbacks.onAdFirstQuartile?.()}),this.streamManager.addEventListener(i.MIDPOINT,()=>{this.log("Ad midpoint"),this.callbacks.onAdMidpoint?.()}),this.streamManager.addEventListener(i.THIRD_QUARTILE,()=>{this.log("Ad third quartile"),this.callbacks.onAdThirdQuartile?.()}),this.streamManager.addEventListener(i.ERROR,t=>{const a=t.getStreamData().errorMessage||"Unknown IMA error";this.log(`IMA error: ${a}`),e?.(new Error(`IMA stream initialization failed: ${a}`)),this.callbacks.onError?.(a)})}getStreamId(){return this.streamId}isInitialized(){return null!==this.streamId}replaceAdTagParameters(t){this.streamManager&&this.streamId?(this.streamManager.replaceAdTagParameters(t),this.log("Ad tag parameters replaced")):this.log("replaceAdTagParameters called but stream not initialized — ignoring")}processTimedMetadata(t){this.streamManager&&(t.raw&&t.raw.length>0?this.streamManager.processMetadata("ID3",t.raw,t.startTime):t.frames&&Object.keys(t.frames).length>0?this.streamManager.onTimedMetadata(t.frames):t.messageData&&this.streamManager.onTimedMetadata({TXXX:t.messageData}))}buildPodUrl(e,a,i){if(!this.streamId)throw new Error("GamStreamManager not initialized. Call initialize() first.");const s=t(e,i,this.streamId,a);return this.log(`Built pod URL: ${s}`),s}reset(){this.streamManager&&this.streamManager.reset(),this.streamId=null,this.initPromise=null,this.streamManager=null,this.log("Stream manager reset")}destroy(){this.reset(),this.config=null,this.callbacks={}}log(t){this.debug&&e(`[GamStreamManager] ${t}`)}}
1
+ import{buildGamPodUrl as t}from"./GamPodUrlBuilder";import{debugLog as e}from"../diagnostics/debugLog";export class GamStreamManager{constructor(t,e,a=!1){this.videoElement=t,this.adUiContainer=e,this.streamManager=null,this.streamId=null,this.config=null,this.initPromise=null,this.callbacks={},this.debug=a}setCallbacks(t){this.callbacks=t}isImaSdkAvailable(){return"undefined"!=typeof google&&void 0!==google.ima&&void 0!==google.ima.dai&&void 0!==google.ima.dai.api}async initialize(t){return this.streamManager&&this.reset(),this.config=t,this.initPromise=this.doInitialize(),this.initPromise}async initializeLiveStream(t){if(this.streamManager&&this.reset(),!this.isImaSdkAvailable())throw new Error('Google IMA DAI SDK not found. Include <script src="https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js"><\/script> in your page.');const e=this.adUiContainer||this.videoElement.parentElement||document.body;this.streamManager=new google.ima.dai.api.StreamManager(this.videoElement,e);const a=new google.ima.dai.api.LiveStreamRequest;return a.assetKey=t.assetKey,a.format="hls",t.networkCode&&(a.networkCode=t.networkCode),t.adTagParameters&&(a.adTagParameters=t.adTagParameters),t.streamActivityMonitorId&&(a.streamActivityMonitorId=t.streamActivityMonitorId),new Promise((e,i)=>{this.setupEventListeners(void 0,i,t=>e(t)),this.log(`Requesting live stream for asset: ${t.assetKey}`),this.streamManager.requestStream(a)})}async doInitialize(){if(!this.isImaSdkAvailable())throw new Error('Google IMA DAI SDK not found. Include <script src="https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js"><\/script> in your page.');if(!this.config)throw new Error("GamStreamManager config not set");this.log("Initializing IMA StreamManager...");const t=this.adUiContainer||this.videoElement.parentElement||document.body;this.streamManager=new google.ima.dai.api.StreamManager(this.videoElement,t);const e=new google.ima.dai.api.PodStreamRequest;return e.networkCode=this.config.networkCode,e.customAssetKey=this.config.customAssetKey,e.format="hls",this.config.adTagParameters&&(e.adTagParameters=this.config.adTagParameters),this.config.streamActivityMonitorId&&(e.streamActivityMonitorId=this.config.streamActivityMonitorId),new Promise((t,a)=>{this.setupEventListeners(t,a),this.log(`Requesting stream for network: ${this.config.networkCode}, asset: ${this.config.customAssetKey}`),this.streamManager.requestStream(e)})}setupEventListeners(t,e,a){if(!this.streamManager)return;const i=google.ima.dai.api.StreamEvent.Type;this.streamManager.addEventListener(i.STREAM_INITIALIZED,e=>{const a=e.getStreamData();this.streamId=a.streamId,this.log(`Stream initialized, streamId: ${this.streamId}`),t?.()}),this.streamManager.addEventListener(i.LOADED,t=>{const e=t.getStreamData();this.streamId=e.streamId,e.url&&(this.log(`Stream loaded, url: ${e.url}`),a?.(e.url))}),this.streamManager.addEventListener(i.AD_BREAK_STARTED,()=>{this.log("Ad break started"),this.callbacks.onAdBreakStarted?.()}),this.streamManager.addEventListener(i.AD_BREAK_ENDED,()=>{this.log("Ad break ended"),this.callbacks.onAdBreakEnded?.()}),this.streamManager.addEventListener(i.STARTED,t=>{const e=function(t){const e=t.getAd?.();if(!e)return;const a=e.getAdPodInfo?.();return{adPosition:a?.getAdPosition(),totalAds:a?.getTotalAds(),adId:e.getAdId?.(),creativeId:e.getCreativeId?.()}}(t);this.log(`Ad started (ad ${e?.adPosition??"?"} of ${e?.totalAds??"?"})`),this.callbacks.onAdStarted?.(e)}),this.streamManager.addEventListener(i.COMPLETE,()=>{this.log("Ad complete"),this.callbacks.onAdComplete?.()}),this.streamManager.addEventListener(i.FIRST_QUARTILE,()=>{this.log("Ad first quartile"),this.callbacks.onAdFirstQuartile?.()}),this.streamManager.addEventListener(i.MIDPOINT,()=>{this.log("Ad midpoint"),this.callbacks.onAdMidpoint?.()}),this.streamManager.addEventListener(i.THIRD_QUARTILE,()=>{this.log("Ad third quartile"),this.callbacks.onAdThirdQuartile?.()}),this.streamManager.addEventListener(i.ERROR,t=>{const a=t.getStreamData().errorMessage||"Unknown IMA error";this.log(`IMA error: ${a}`),e?.(new Error(`IMA stream initialization failed: ${a}`)),this.callbacks.onError?.(a)})}getStreamId(){return this.streamId}isInitialized(){return null!==this.streamId}replaceAdTagParameters(t){this.streamManager&&this.streamId?(this.streamManager.replaceAdTagParameters(t),this.log("Ad tag parameters replaced")):this.log("replaceAdTagParameters called but stream not initialized — ignoring")}processTimedMetadata(t){if(this.streamManager)if(t.raw&&t.raw.length>0){if("presented"===t.phase)return;this.streamManager.processMetadata("ID3",t.raw,t.startTime)}else"parsed"!==t.phase&&(t.frames&&Object.keys(t.frames).length>0?this.streamManager.onTimedMetadata(t.frames):t.messageData&&this.streamManager.onTimedMetadata({TXXX:t.messageData}))}buildPodUrl(e,a,i){if(!this.streamId)throw new Error("GamStreamManager not initialized. Call initialize() first.");const s=t(e,i,this.streamId,a);return this.log(`Built pod URL: ${s}`),s}reset(){this.streamManager&&this.streamManager.reset(),this.streamId=null,this.initPromise=null,this.streamManager=null,this.log("Stream manager reset")}destroy(){this.reset(),this.config=null,this.callbacks={}}log(t){this.debug&&e(`[GamStreamManager] ${t}`)}}
@@ -1,5 +1,5 @@
1
1
  import type { PlayerAdapter } from '../interfaces/PlayerAdapter';
2
- import type { GamConfig, SsaiConfig, SsaiSessionConfig } from '../types/OptiViewAdsConfig';
2
+ import type { AssetParameterMacroUpdates, GamConfig, SsaiConfig, SsaiSessionConfig } from '../types/OptiViewAdsConfig';
3
3
  import type { StitchedStreamVendor } from './resolveDeliveryMode';
4
4
  import { AssetParameterMacros } from './assetParameterMacros';
5
5
  import type { OptiViewAdsEventMap, OptiViewAdsEventType } from '../types/Events';
@@ -45,6 +45,8 @@ export declare class SsaiController {
45
45
  startSteered(vendor: StitchedStreamVendor, sessionConfigParameters?: Record<string, string>, macros?: AssetParameterMacros): Promise<void>;
46
46
  private resolveAdTagParameters;
47
47
  updateAssetParameters(params: Record<string, string>): void;
48
+ updateAssetParameterMacros(macros: AssetParameterMacroUpdates): void;
49
+ get sessionAssetParameterMacros(): AssetParameterMacros;
48
50
  setManifestAssetParameters(params: Record<string, string> | undefined): void;
49
51
  /** Stop timed-metadata forwarding and destroy IMA. */
50
52
  stop(): void;
@@ -1 +1 @@
1
- import{applyAssetParameterMacros as e,omitBreakScopedParameters as t}from"./assetParameterMacros";import{buildAssetParameterMacroTable as a}from"./assetParameterMacroTable";import{resolveAssetParameters as s}from"./resolveAssetParameters";import{AssetType as r,MediaType as i,BreakFormat as n}from"../types/BreakManifest";import{GamStreamManager as o}from"./GamStreamManager";import{resolveBreakFormat as m}from"./AdBreakSequencer";import{buildStitcherMasterUrl as d}from"./StitcherUrlBuilder";export class SsaiController{constructor(e,t,a){this.player=e,this.options=t,this.callbacks=a,this.gamStreamManager=null,this.breakCounter=0,this.adIndexInBreak=0,this.currentBreak=null,this.currentAsset=null,this.customerMacros={},this.onTimedMetadata=e=>{if(!e)return;const t=e;"presented"!==t.phase&&this.gamStreamManager?.processTimedMetadata(t)}}async start(e){const t=this.player.videoElement;if(!t)throw new Error("SSAI mode requires a content PlayerAdapter that exposes a videoElement (for IMA DAI).");if(!e.customAssetKey)throw new Error("SSAI mode requires a customAssetKey in startSession().");this.sessionConfigParameters=e.assetParameters,this.updatedParameters=void 0,this.manifestParameters=void 0,this.customerMacros={...e.assetParameterMacros};const a=new o(t,void 0,this.options.debug);a.setCallbacks(this.buildGamCallbacks()),this.gamStreamManager=a,await a.initialize({networkCode:this.options.ssai.networkCode,customAssetKey:e.customAssetKey,adTagParameters:this.resolveAdTagParameters(),streamActivityMonitorId:this.options.gam.streamActivityMonitorId});const s=a.getStreamId();if(!s)throw new Error("SSAI mode: IMA DAI session did not return a stream id.");const r=d({stitcherUrl:e.stitcherUrl,streamId:s,maxBitrate:this.options.maxBitrate});this.callbacks.log("SSAI stitched master URL:",r),this.player.on("timedmetadata",this.onTimedMetadata),await this.player.load(r),this.options.autoplay&&await this.player.play()}async startSteered(e,t,a){const s=this.player.videoElement;if(!s)throw new Error("SSAI mode requires a content PlayerAdapter that exposes a videoElement (for IMA DAI).");this.sessionConfigParameters=t,this.updatedParameters=void 0,this.manifestParameters=e.assetParameters,this.customerMacros={...a};const r=new o(s,void 0,this.options.debug);r.setCallbacks(this.buildGamCallbacks()),this.gamStreamManager=r;const i=await r.initializeLiveStream({assetKey:e.assetKey,networkCode:e.networkCode,adTagParameters:this.resolveAdTagParameters(),streamActivityMonitorId:this.options.gam.streamActivityMonitorId});this.callbacks.log("SSAI full-service DAI stream URL:",i),this.player.on("timedmetadata",this.onTimedMetadata),await this.player.load(i),this.options.autoplay&&await this.player.play()}resolveAdTagParameters(){const r=s(this.sessionConfigParameters,this.updatedParameters,this.manifestParameters,void 0),i=e(t(r),a({customerMacros:this.customerMacros,element:this.player.videoElement??null,breakDurationSec:void 0}));for(const{key:e,macro:t}of i.unresolved)this.callbacks.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${t}': the macro has no value.`,{key:e,macro:t});return i.params}updateAssetParameters(e){this.updatedParameters=e,this.gamStreamManager?.replaceAdTagParameters(this.resolveAdTagParameters()??{})}setManifestAssetParameters(e){(function(e,t){e&&0===Object.keys(e).length&&(e=void 0);t&&0===Object.keys(t).length&&(t=void 0);if(e===t)return!0;if(!e||!t)return!1;const a=Object.keys(e);return a.length===Object.keys(t).length&&a.every(a=>e[a]===t[a])})(this.manifestParameters,e)||(this.manifestParameters=e,this.gamStreamManager&&(this.gamStreamManager.replaceAdTagParameters(this.resolveAdTagParameters()??{}),this.callbacks.diagnose("DA-ASSET-PARAMETERS-UPDATED","Manifest asset parameters changed; IMA targeting updated.",{source:"manifest",keys:Object.keys(e??{}).sort()})))}stop(){this.player.off("timedmetadata",this.onTimedMetadata),this.gamStreamManager?.destroy(),this.gamStreamManager=null,this.sessionConfigParameters=void 0,this.updatedParameters=void 0,this.manifestParameters=void 0,this.customerMacros={},this.currentBreak=null,this.currentAsset=null,this.breakCounter=0,this.adIndexInBreak=0}buildGamCallbacks(){return{onAdBreakStarted:()=>{this.breakCounter+=1,this.adIndexInBreak=0,this.currentBreak=this.synthBreak(`ssai-break-${this.breakCounter}`),this.callbacks.emit("adbreakbegin",{type:"adbreakbegin",timestamp:Date.now(),break:this.currentBreak,format:m(this.currentBreak),elapsedSec:0,remainingSec:this.currentBreak.duration})},onAdStarted:e=>{const t=this.ensureBreak(),a=this.adIndexInBreak;this.adIndexInBreak+=1,this.currentAsset=this.synthAsset(`${t.id}-ad-${this.adIndexInBreak}`),this.callbacks.emit("adbegin",{type:"adbegin",timestamp:Date.now(),break:t,format:m(t),asset:this.currentAsset,adIndex:a,totalAds:0,...void 0!==e?.adPosition&&Number.isInteger(e.adPosition)&&e.adPosition>=1?{podPosition:e.adPosition}:{}})},onAdFirstQuartile:()=>this.emitQuartile("adfirstquartile"),onAdMidpoint:()=>this.emitQuartile("admidpoint"),onAdThirdQuartile:()=>this.emitQuartile("adthirdquartile"),onAdComplete:()=>{const e=this.ensureBreak(),t=this.currentAsset??this.synthAsset(`${e.id}-ad`);this.callbacks.emit("adend",{type:"adend",timestamp:Date.now(),break:e,format:m(e),asset:t,adIndex:Math.max(0,this.adIndexInBreak-1),totalAds:0}),this.currentAsset=null},onAdBreakEnded:()=>{const e=this.ensureBreak();this.callbacks.emit("adbreakend",{type:"adbreakend",timestamp:Date.now(),break:e,format:m(e)}),this.currentBreak=null,this.currentAsset=null},onError:e=>{this.callbacks.diagnose("DA-SSAI-IMA-ERROR",e),this.callbacks.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",...this.currentBreak?{break:this.currentBreak,format:m(this.currentBreak)}:{},...this.currentAsset?{asset:this.currentAsset}:{},error:new Error(e)})}}}emitQuartile(e){const t=this.ensureBreak(),a=this.currentAsset??this.synthAsset(`${t.id}-ad`);this.callbacks.emit(e,{type:e,timestamp:Date.now(),break:t,format:m(t),asset:a})}ensureBreak(){return this.currentBreak||(this.breakCounter+=1,this.currentBreak=this.synthBreak(`ssai-break-${this.breakCounter}`)),this.currentBreak}synthBreak(e){return{id:e,start:this.player.currentTime,duration:0,variant:{format:n.SINGLE,assets:[]}}}synthAsset(e){return{id:e,type:r.STATIC,mediaType:i.VIDEO,uri:""}}}
1
+ import{applyAssetParameterMacros as e,omitBreakScopedParameters as t}from"./assetParameterMacros";import{buildAssetParameterMacroTable as a}from"./assetParameterMacroTable";import{resolveAssetParameters as s}from"./resolveAssetParameters";import{AssetType as r,MediaType as i,BreakFormat as o}from"../types/BreakManifest";import{GamStreamManager as n}from"./GamStreamManager";import{resolveBreakFormat as m}from"./AdBreakSequencer";import{buildStitcherMasterUrl as d}from"./StitcherUrlBuilder";export class SsaiController{constructor(e,t,a){this.player=e,this.options=t,this.callbacks=a,this.gamStreamManager=null,this.breakCounter=0,this.adIndexInBreak=0,this.currentBreak=null,this.currentAsset=null,this.customerMacros={},this.onTimedMetadata=e=>{if(!e)return;const t=e;this.gamStreamManager?.processTimedMetadata(t)}}async start(e){const t=this.player.videoElement;if(!t)throw new Error("SSAI mode requires a content PlayerAdapter that exposes a videoElement (for IMA DAI).");if(!e.customAssetKey)throw new Error("SSAI mode requires a customAssetKey in startSession().");this.sessionConfigParameters=e.assetParameters,this.updatedParameters=void 0,this.manifestParameters=void 0,this.customerMacros={...e.assetParameterMacros};const a=new n(t,void 0,this.options.debug);a.setCallbacks(this.buildGamCallbacks()),this.gamStreamManager=a,await a.initialize({networkCode:this.options.ssai.networkCode,customAssetKey:e.customAssetKey,adTagParameters:this.resolveAdTagParameters(),streamActivityMonitorId:this.options.gam.streamActivityMonitorId});const s=a.getStreamId();if(!s)throw new Error("SSAI mode: IMA DAI session did not return a stream id.");const r=d({stitcherUrl:e.stitcherUrl,streamId:s,maxBitrate:this.options.maxBitrate});this.callbacks.log("SSAI stitched master URL:",r),this.player.on("timedmetadata",this.onTimedMetadata),await this.player.load(r),this.options.autoplay&&await this.player.play()}async startSteered(e,t,a){const s=this.player.videoElement;if(!s)throw new Error("SSAI mode requires a content PlayerAdapter that exposes a videoElement (for IMA DAI).");this.sessionConfigParameters=t,this.updatedParameters=void 0,this.manifestParameters=e.assetParameters,this.customerMacros={...a};const r=new n(s,void 0,this.options.debug);r.setCallbacks(this.buildGamCallbacks()),this.gamStreamManager=r;const i=await r.initializeLiveStream({assetKey:e.assetKey,networkCode:e.networkCode,adTagParameters:this.resolveAdTagParameters(),streamActivityMonitorId:this.options.gam.streamActivityMonitorId});this.callbacks.log("SSAI full-service DAI stream URL:",i),this.player.on("timedmetadata",this.onTimedMetadata),await this.player.load(i),this.options.autoplay&&await this.player.play()}resolveAdTagParameters(){const r=s(this.sessionConfigParameters,this.updatedParameters,this.manifestParameters,void 0),i=e(t(r),a({customerMacros:this.customerMacros,element:this.player.videoElement??null,breakDurationSec:void 0}));for(const{key:e,macro:t}of i.unresolved)this.callbacks.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${t}': the macro has no value.`,{key:e,macro:t});return i.params}updateAssetParameters(e){this.updatedParameters=e,this.gamStreamManager?.replaceAdTagParameters(this.resolveAdTagParameters()??{})}updateAssetParameterMacros(e){const t={...this.customerMacros};for(const[a,s]of Object.entries(e))null===s?delete t[a]:t[a]=s;this.customerMacros=t,this.callbacks.diagnose("DA-ASSET-PARAMETERS-UPDATED","Asset parameter macros updated.",{source:"macros",keys:Object.keys(e).sort()}),this.gamStreamManager?.replaceAdTagParameters(this.resolveAdTagParameters()??{})}get sessionAssetParameterMacros(){return{...this.customerMacros}}setManifestAssetParameters(e){(function(e,t){e&&0===Object.keys(e).length&&(e=void 0);t&&0===Object.keys(t).length&&(t=void 0);if(e===t)return!0;if(!e||!t)return!1;const a=Object.keys(e);return a.length===Object.keys(t).length&&a.every(a=>e[a]===t[a])})(this.manifestParameters,e)||(this.manifestParameters=e,this.gamStreamManager&&(this.gamStreamManager.replaceAdTagParameters(this.resolveAdTagParameters()??{}),this.callbacks.diagnose("DA-ASSET-PARAMETERS-UPDATED","Manifest asset parameters changed; IMA targeting updated.",{source:"manifest",keys:Object.keys(e??{}).sort()})))}stop(){this.player.off("timedmetadata",this.onTimedMetadata),this.gamStreamManager?.destroy(),this.gamStreamManager=null,this.sessionConfigParameters=void 0,this.updatedParameters=void 0,this.manifestParameters=void 0,this.customerMacros={},this.currentBreak=null,this.currentAsset=null,this.breakCounter=0,this.adIndexInBreak=0}buildGamCallbacks(){return{onAdBreakStarted:()=>{this.breakCounter+=1,this.adIndexInBreak=0,this.currentBreak=this.synthBreak(`ssai-break-${this.breakCounter}`),this.callbacks.emit("adbreakbegin",{type:"adbreakbegin",timestamp:Date.now(),break:this.currentBreak,format:m(this.currentBreak),elapsedSec:0,remainingSec:this.currentBreak.duration})},onAdStarted:e=>{const t=this.ensureBreak(),a=this.adIndexInBreak;this.adIndexInBreak+=1,this.currentAsset=this.synthAsset(`${t.id}-ad-${this.adIndexInBreak}`),this.callbacks.emit("adbegin",{type:"adbegin",timestamp:Date.now(),break:t,format:m(t),asset:this.currentAsset,adIndex:a,totalAds:0,...void 0!==e?.adPosition&&Number.isInteger(e.adPosition)&&e.adPosition>=1?{podPosition:e.adPosition}:{}})},onAdFirstQuartile:()=>this.emitQuartile("adfirstquartile"),onAdMidpoint:()=>this.emitQuartile("admidpoint"),onAdThirdQuartile:()=>this.emitQuartile("adthirdquartile"),onAdComplete:()=>{const e=this.ensureBreak(),t=this.currentAsset??this.synthAsset(`${e.id}-ad`);this.callbacks.emit("adend",{type:"adend",timestamp:Date.now(),break:e,format:m(e),asset:t,adIndex:Math.max(0,this.adIndexInBreak-1),totalAds:0}),this.currentAsset=null},onAdBreakEnded:()=>{const e=this.ensureBreak();this.callbacks.emit("adbreakend",{type:"adbreakend",timestamp:Date.now(),break:e,format:m(e)}),this.currentBreak=null,this.currentAsset=null},onError:e=>{this.callbacks.diagnose("DA-SSAI-IMA-ERROR",e),this.callbacks.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",...this.currentBreak?{break:this.currentBreak,format:m(this.currentBreak)}:{},...this.currentAsset?{asset:this.currentAsset}:{},error:new Error(e)})}}}emitQuartile(e){const t=this.ensureBreak(),a=this.currentAsset??this.synthAsset(`${t.id}-ad`);this.callbacks.emit(e,{type:e,timestamp:Date.now(),break:t,format:m(t),asset:a})}ensureBreak(){return this.currentBreak||(this.breakCounter+=1,this.currentBreak=this.synthBreak(`ssai-break-${this.breakCounter}`)),this.currentBreak}synthBreak(e){return{id:e,start:this.player.currentTime,duration:0,variant:{format:o.SINGLE,assets:[]}}}synthAsset(e){return{id:e,type:r.STATIC,mediaType:i.VIDEO,uri:""}}}
@@ -25,6 +25,7 @@ export interface AdPlayerEventsDeps {
25
25
  maybePrepositionContentForResume: () => void;
26
26
  maybeCatchUpContentForResume: (breakInfo: Break) => void;
27
27
  getStaticQuartilesFired: () => Set<'first' | 'mid' | 'third'>;
28
+ onGamPodAdProgress: (currentTime: number) => void;
28
29
  }
29
30
  export declare class AdPlayerEvents {
30
31
  private readonly state;
@@ -1 +1 @@
1
- import{AssetType as t}from"../../types/BreakManifest";export class AdPlayerEvents{constructor(t,e){this.state=t,this.deps=e}recordAdProgress(t){!Number.isFinite(t)||t<=0||t>this.state.currentAssetElapsedSec&&(this.state.currentAssetElapsedSec=t)}settleAssetProgress(){let t=this.state.currentAssetElapsedSec;if(t<=0){const e=this.state.currentAsset?.duration;"number"==typeof e&&Number.isFinite(e)&&e>0&&(t=e)}this.state.adSecondsPlayedThisBreak+=t,this.state.currentAssetElapsedSec=0}reportAdProgress(e){if(this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&(this.recordAdProgress(e.currentTime),this.deps.maybePrepositionContentForResume(),this.state.currentBreak&&this.deps.maybeCatchUpContentForResume(this.state.currentBreak),this.state.callbacks?.onAdTimeUpdate(this.state.currentBreak,this.state.currentAsset,e.currentTime,e.duration),this.state.currentAsset.type===t.STATIC&&e.duration>0)){const t=e.currentTime/e.duration,s=this.deps.getStaticQuartilesFired(),r=this.deps.getGamEventCallbacks();t>=.25&&!s.has("first")&&(s.add("first"),r.onAdFirstQuartile?.(this.state.currentBreak,this.state.currentAsset)),t>=.5&&!s.has("mid")&&(s.add("mid"),r.onAdMidpoint?.(this.state.currentBreak,this.state.currentAsset)),t>=.75&&!s.has("third")&&(s.add("third"),r.onAdThirdQuartile?.(this.state.currentBreak,this.state.currentAsset))}}setupEventListeners(){const t=this.deps.getAdPlayer();if(!t)return;t.on("ended",()=>{this.deps.onAdEnded()}),t.on("timedmetadata",t=>{t&&this.deps.processTimedMetadata(t)}),t.on("error",t=>{this.onAdError(this.deps.toAdError(t))}),t.on("timeupdate",()=>this.reportAdProgress(t)),t.on("waiting",()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&this.state.callbacks?.onAdWaiting(this.state.currentBreak,this.state.currentAsset)}),t.on("playing",()=>{if(this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset)return this.state.firstFrameResolver?(this.deps.revealFirstFrame(),this.state.firstFrameResolver(),void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset)):void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset)});const e=()=>{const t=this.deps.getAdPlayer();if(this.deps.getSyncing()||!t)return;const e=this.deps.getAudioFocus();e?"ad"===e?this.deps.notifyVolumeChange():(this.deps.setSyncing(!0),t.muted=!0,t.volume=0,this.deps.setSyncing(!1)):this.state.isPlaying&&(this.deps.setSyncing(!0),this.deps.getContentPlayer().muted=t.muted,this.deps.getContentPlayer().volume=t.volume,this.deps.setSyncing(!1),this.deps.notifyVolumeChange())};this.deps.setAdVolumeChangeHandler(e),t.on("volumechange",e);const s=()=>{const t=this.deps.getAdPlayer();if(this.deps.getSyncing()||!t)return;const e=this.deps.getAudioFocus();e?"content"===e?this.deps.notifyVolumeChange():(this.deps.setSyncing(!0),this.deps.getContentPlayer().muted=!0,this.deps.setSyncing(!1)):(this.deps.setSyncing(!0),t.muted=this.deps.getContentPlayer().muted,t.volume=this.deps.getContentPlayer().volume,this.deps.setSyncing(!1),this.deps.notifyVolumeChange())};this.deps.setContentVolumeHandler(s),this.deps.getContentPlayer().on("volumechange",s)}onAdError(t){const e=this.deps.getSequencer();if(this.state.currentBreak&&e){if(this.state.firstFrameRejector){const e=this.state.firstFrameRejector;this.deps.clearFirstFrameResolver(),e(t)}this.deps.log("Ad error:",t.message),this.deps.setPendingAdError(t),this.deps.drive(e.onOutcome({kind:"asset-errored"}))}}}
1
+ import{AssetType as t}from"../../types/BreakManifest";export class AdPlayerEvents{constructor(t,e){this.state=t,this.deps=e}recordAdProgress(t){!Number.isFinite(t)||t<=0||(this.deps.onGamPodAdProgress(t),t>this.state.currentAssetElapsedSec&&(this.state.currentAssetElapsedSec=t))}settleAssetProgress(){let t=this.state.currentAssetElapsedSec;if(t<=0){const e=this.state.currentAsset?.duration;"number"==typeof e&&Number.isFinite(e)&&e>0&&(t=e)}this.state.adSecondsPlayedThisBreak+=t,this.state.currentAssetElapsedSec=0}reportAdProgress(e){if(this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&(this.recordAdProgress(e.currentTime),this.deps.maybePrepositionContentForResume(),this.state.currentBreak&&this.deps.maybeCatchUpContentForResume(this.state.currentBreak),this.state.callbacks?.onAdTimeUpdate(this.state.currentBreak,this.state.currentAsset,e.currentTime,e.duration),this.state.currentAsset.type===t.STATIC&&e.duration>0)){const t=e.currentTime/e.duration,s=this.deps.getStaticQuartilesFired(),r=this.deps.getGamEventCallbacks();t>=.25&&!s.has("first")&&(s.add("first"),r.onAdFirstQuartile?.(this.state.currentBreak,this.state.currentAsset)),t>=.5&&!s.has("mid")&&(s.add("mid"),r.onAdMidpoint?.(this.state.currentBreak,this.state.currentAsset)),t>=.75&&!s.has("third")&&(s.add("third"),r.onAdThirdQuartile?.(this.state.currentBreak,this.state.currentAsset))}}setupEventListeners(){const t=this.deps.getAdPlayer();if(!t)return;t.on("ended",()=>{this.deps.onAdEnded()}),t.on("timedmetadata",t=>{t&&this.deps.processTimedMetadata(t)}),t.on("error",t=>{this.onAdError(this.deps.toAdError(t))}),t.on("timeupdate",()=>this.reportAdProgress(t)),t.on("waiting",()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&this.state.callbacks?.onAdWaiting(this.state.currentBreak,this.state.currentAsset)}),t.on("playing",()=>{if(this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset)return this.state.firstFrameResolver?(this.deps.revealFirstFrame(),this.state.firstFrameResolver(),void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset)):void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset)});const e=()=>{const t=this.deps.getAdPlayer();if(this.deps.getSyncing()||!t)return;const e=this.deps.getAudioFocus();e?"ad"===e?this.deps.notifyVolumeChange():(this.deps.setSyncing(!0),t.muted=!0,t.volume=0,this.deps.setSyncing(!1)):this.state.isPlaying&&(this.deps.setSyncing(!0),this.deps.getContentPlayer().muted=t.muted,this.deps.getContentPlayer().volume=t.volume,this.deps.setSyncing(!1),this.deps.notifyVolumeChange())};this.deps.setAdVolumeChangeHandler(e),t.on("volumechange",e);const s=()=>{const t=this.deps.getAdPlayer();if(this.deps.getSyncing()||!t)return;const e=this.deps.getAudioFocus();e?"content"===e?this.deps.notifyVolumeChange():(this.deps.setSyncing(!0),this.deps.getContentPlayer().muted=!0,this.deps.setSyncing(!1)):(this.deps.setSyncing(!0),t.muted=this.deps.getContentPlayer().muted,t.volume=this.deps.getContentPlayer().volume,this.deps.setSyncing(!1),this.deps.notifyVolumeChange())};this.deps.setContentVolumeHandler(s),this.deps.getContentPlayer().on("volumechange",s)}onAdError(t){const e=this.deps.getSequencer();if(this.state.currentBreak&&e){if(this.state.firstFrameRejector){const e=this.state.firstFrameRejector;this.deps.clearFirstFrameResolver(),e(t)}this.deps.log("Ad error:",t.message),this.deps.setPendingAdError(t),this.deps.drive(e.onOutcome({kind:"asset-errored"}))}}}
@@ -3,7 +3,7 @@ import type { AdInsertionType } from '../../types/OptiViewAdsConfig';
3
3
  import type { AdPlayerCallbacks } from '../AdPlayerController';
4
4
  import type { AdBreakStep } from '../AdBreakSequencer';
5
5
  import type { GamStreamManager } from '../GamStreamManager';
6
- import type { GamPodAdTracker } from '../GamPodAdTracker';
6
+ import type { GamPodAdInfo, GamPodAdTracker } from '../GamPodAdTracker';
7
7
  import type { GamConfig } from '../../types/OptiViewAdsConfig';
8
8
  import type { GamSessionIdentity } from '../resolveGamVendorConfiguration';
9
9
  import type { AssetParameterMacros } from '../assetParameterMacros';
@@ -30,7 +30,7 @@ export interface AdPlayerState {
30
30
  gamPodMode: 'none' | 'pending' | 'per-ad' | 'legacy';
31
31
  gamPodTracker: GamPodAdTracker | null;
32
32
  gamPodAsset: Asset | null;
33
- gamPodReportTimer: ReturnType<typeof setTimeout> | null;
33
+ earlyGamPodAdStart: GamPodAdInfo | null;
34
34
  withheldPodAdBegin: AdBreakStep | null;
35
35
  gamConfig: GamConfig | undefined;
36
36
  gamInitPromise: Promise<void> | null;
@@ -1 +1 @@
1
- export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,effectiveBreakDurationSec:null,adInsertionType:void 0,breakTimebase:null,adBreakCutSafetyMarginSec:0,detectedDeviceType:void 0,sessionAssetParameters:void 0,updatedAssetParameters:void 0,manifestAssetParameters:void 0,sessionAssetParameterMacros:{},pushedAssetParameters:null,gamStreamManager:null,gamPodMode:"none",gamPodTracker:null,gamPodAsset:null,gamPodReportTimer:null,withheldPodAdBegin:null,gamConfig:void 0,gamInitPromise:null,currentGamIdentity:void 0,playerContainer:null,stage:null,contentVideoElement:null,adContainer:null,adMediaContainer:null,adPlayer:null,companionElement:null,overlayImageEl:null,isContentFullscreen:!1,trackedContentVideo:null,fullscreenEnterHandler:null,fullscreenExitHandler:null,contentPictureInPicture:!1,pipTransferredToAd:!1,pipAdoptedForBreak:!1,sharedAdClaimActive:!1,savedContentSrc:null,savedContentTime:0,savedContentLive:!1,savedContentEdgeDistance:null,earlyReturnResumeOffsetSec:null,contentPositionAtBreakStart:0,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,lastResumeAlignRemainingSec:Number.POSITIVE_INFINITY,resumeAlignBlockedDiagnosed:!1,lastResumeSafeEnd:Number.NEGATIVE_INFINITY,resumeSawSeekDone:!1,resumeBestSawMarginSec:Number.NEGATIVE_INFINITY,resumeAlignTicks:0,resumeSawSteps:[],continueContentDuringBreak:!1,continueQualityOverrideApplied:!1,continueContentDuringBreakActive:!1,keepContentPlayingHandler:null,continuePlayoutStartWallMs:0,continuePlayoutTicker:null,continueRateWritten:null,continueRateWrittenAtMs:0,continueRateHonoured:!0,continueMaxAbsDriftSec:0,continueCorrections:[],continueDriftUnrecoverable:!1,continueRefusalReported:!1,contentMutedBeforeCatchup:null,resumeAlignCorrectionsMs:[],resumeLandedProbe:null,adSecondsPlayedThisBreak:0,currentAssetElapsedSec:0,preloadedBreakId:null,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
1
+ export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,effectiveBreakDurationSec:null,adInsertionType:void 0,breakTimebase:null,adBreakCutSafetyMarginSec:0,detectedDeviceType:void 0,sessionAssetParameters:void 0,updatedAssetParameters:void 0,manifestAssetParameters:void 0,sessionAssetParameterMacros:{},pushedAssetParameters:null,gamStreamManager:null,gamPodMode:"none",gamPodTracker:null,gamPodAsset:null,earlyGamPodAdStart:null,withheldPodAdBegin:null,gamConfig:void 0,gamInitPromise:null,currentGamIdentity:void 0,playerContainer:null,stage:null,contentVideoElement:null,adContainer:null,adMediaContainer:null,adPlayer:null,companionElement:null,overlayImageEl:null,isContentFullscreen:!1,trackedContentVideo:null,fullscreenEnterHandler:null,fullscreenExitHandler:null,contentPictureInPicture:!1,pipTransferredToAd:!1,pipAdoptedForBreak:!1,sharedAdClaimActive:!1,savedContentSrc:null,savedContentTime:0,savedContentLive:!1,savedContentEdgeDistance:null,earlyReturnResumeOffsetSec:null,contentPositionAtBreakStart:0,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,lastResumeAlignRemainingSec:Number.POSITIVE_INFINITY,resumeAlignBlockedDiagnosed:!1,lastResumeSafeEnd:Number.NEGATIVE_INFINITY,resumeSawSeekDone:!1,resumeBestSawMarginSec:Number.NEGATIVE_INFINITY,resumeAlignTicks:0,resumeSawSteps:[],continueContentDuringBreak:!1,continueQualityOverrideApplied:!1,continueContentDuringBreakActive:!1,keepContentPlayingHandler:null,continuePlayoutStartWallMs:0,continuePlayoutTicker:null,continueRateWritten:null,continueRateWrittenAtMs:0,continueRateHonoured:!0,continueMaxAbsDriftSec:0,continueCorrections:[],continueDriftUnrecoverable:!1,continueRefusalReported:!1,contentMutedBeforeCatchup:null,resumeAlignCorrectionsMs:[],resumeLandedProbe:null,adSecondsPlayedThisBreak:0,currentAssetElapsedSec:0,preloadedBreakId:null,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
@@ -3,6 +3,7 @@ import type { AssetUri, Break, VastAsset } from '../../types/BreakManifest';
3
3
  import { AssetParameterMacros } from '../assetParameterMacros';
4
4
  import type { GamStreamManager } from '../GamStreamManager';
5
5
  import type { AdPlayerState } from './AdPlayerState';
6
+ import type { AssetParameterMacroUpdates } from '../../types/OptiViewAdsConfig';
6
7
  export interface AssetParameterResolverDeps {
7
8
  contentPlayer: PlayerAdapter;
8
9
  container: HTMLElement;
@@ -20,7 +21,9 @@ export declare class AssetParameterResolver {
20
21
  setSessionAssetParameters(params: Record<string, string> | undefined): void;
21
22
  setManifestAssetParameters(params: Record<string, string> | undefined): void;
22
23
  updateAssetParameters(params: Record<string, string>): void;
24
+ updateAssetParameterMacros(macros: AssetParameterMacroUpdates): void;
23
25
  setSessionAssetParameterMacros(macros: AssetParameterMacros | undefined): void;
26
+ getSessionAssetParameterMacros(): AssetParameterMacros;
24
27
  /**
25
28
  * Substitute the `$` macros in an effective (already layered) parameter map.
26
29
  * Precedence per macro name: session macros, then the built-ins; a customer
@@ -1 +1 @@
1
- import{isVendorAsset as e}from"../../types/BreakManifest";import{appendQueryParameters as s,applyAssetParameterMacros as t,omitBreakScopedParameters as a}from"../assetParameterMacros";import{buildAssetParameterMacroTable as r}from"../assetParameterMacroTable";import{resolveAssetParameters as i}from"../resolveAssetParameters";import{detectDeviceType as o}from"../detectDeviceType";import{resolveTargetedUri as n}from"../resolveTargetedUri";function d(e,s){if(e&&0===Object.keys(e).length&&(e=void 0),s&&0===Object.keys(s).length&&(s=void 0),e===s)return!0;if(!e||!s)return!1;const t=Object.keys(e);return t.length===Object.keys(s).length&&t.every(t=>e[t]===s[t])}export class AssetParameterResolver{constructor(e,s){this.state=e,this.deps=s}setSessionAssetParameters(e){this.state.sessionAssetParameters=e}setManifestAssetParameters(s){if(d(this.state.manifestAssetParameters,s))return;if(this.state.manifestAssetParameters=s,!this.deps.getGamStreamManager()?.isInitialized())return void this.deps.log("Manifest asset parameters stored before GAM initialization");const t=this.state.gamPodAsset;this.pushAssetParameters(t&&e(t)?t.assetParameters:void 0,t?.id),this.deps.diagnose("DA-ASSET-PARAMETERS-UPDATED","Manifest asset parameters changed; IMA targeting updated.",{source:"manifest",keys:Object.keys(s??{}).sort()})}updateAssetParameters(e){this.state.updatedAssetParameters=e,this.deps.getGamStreamManager()?.isInitialized()?this.pushAssetParameters(void 0):this.deps.log("updateAssetParameters: GAM not initialized, stored for VAST only")}setSessionAssetParameterMacros(e){this.state.sessionAssetParameterMacros={...e}}resolveAssetParameterMacros(e,s,i,o){const n="session"===i?a(e):e,d=t(n,r({customerMacros:this.state.sessionAssetParameterMacros,element:this.deps.container,breakDurationSec:"session"===i?void 0:s}));for(const{key:e,macro:s}of d.unresolved)this.deps.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${s}': the macro has no value.`,{...o,key:e,macro:s});return d.params}resolveSessionAdTagParameters(){const e=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,void 0);return this.resolveAssetParameterMacros(e,void 0,"session",{breakId:void 0,assetId:void 0})}buildVastTagUrl(e,t){const a=this.resolveUri(e.uri);if(!a)return null;const r=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,e.assetParameters),o=e.duration??this.deps.getEffectiveBreakDuration()??t.duration,n=this.resolveAssetParameterMacros(r,o,"break",{breakId:t.id,assetId:e.id});return s(a,n)}pushAssetParameters(e,s){const t=this.deps.getGamStreamManager();if(!t?.isInitialized())return;const a=this.deps.getCurrentBreak(),r=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,e),o=this.resolveAssetParameterMacros(r,this.deps.getEffectiveBreakDuration()??a?.duration,"break",{breakId:a?.id,assetId:s});null!==this.state.pushedAssetParameters&&d(this.state.pushedAssetParameters,o)||(this.state.pushedAssetParameters=o,t.replaceAdTagParameters(o??{}))}resolveUri(e){return void 0===this.state.detectedDeviceType&&(this.state.detectedDeviceType=o()),n(e,this.state.detectedDeviceType)}}
1
+ import{isVendorAsset as e}from"../../types/BreakManifest";import{appendQueryParameters as s,applyAssetParameterMacros as t,omitBreakScopedParameters as a}from"../assetParameterMacros";import{buildAssetParameterMacroTable as r}from"../assetParameterMacroTable";import{resolveAssetParameters as i}from"../resolveAssetParameters";import{detectDeviceType as o}from"../detectDeviceType";import{resolveTargetedUri as n}from"../resolveTargetedUri";function d(e,s){if(e&&0===Object.keys(e).length&&(e=void 0),s&&0===Object.keys(s).length&&(s=void 0),e===s)return!0;if(!e||!s)return!1;const t=Object.keys(e);return t.length===Object.keys(s).length&&t.every(t=>e[t]===s[t])}export class AssetParameterResolver{constructor(e,s){this.state=e,this.deps=s}setSessionAssetParameters(e){this.state.sessionAssetParameters=e}setManifestAssetParameters(s){if(d(this.state.manifestAssetParameters,s))return;if(this.state.manifestAssetParameters=s,!this.deps.getGamStreamManager()?.isInitialized())return void this.deps.log("Manifest asset parameters stored before GAM initialization");const t=this.state.gamPodAsset;this.pushAssetParameters(t&&e(t)?t.assetParameters:void 0,t?.id),this.deps.diagnose("DA-ASSET-PARAMETERS-UPDATED","Manifest asset parameters changed; IMA targeting updated.",{source:"manifest",keys:Object.keys(s??{}).sort()})}updateAssetParameters(e){this.state.updatedAssetParameters=e,this.deps.getGamStreamManager()?.isInitialized()?this.pushAssetParameters(void 0):this.deps.log("updateAssetParameters: GAM not initialized, stored for VAST only")}updateAssetParameterMacros(e){const s={...this.state.sessionAssetParameterMacros};for(const[t,a]of Object.entries(e))null===a?delete s[t]:s[t]=a;this.state.sessionAssetParameterMacros=s,this.deps.diagnose("DA-ASSET-PARAMETERS-UPDATED","Asset parameter macros updated.",{source:"macros",keys:Object.keys(e).sort()}),this.deps.getGamStreamManager()?.isInitialized()?this.pushAssetParameters(void 0):this.deps.log("updateAssetParameterMacros: GAM not initialized, stored for VAST only")}setSessionAssetParameterMacros(e){this.state.sessionAssetParameterMacros={...e}}getSessionAssetParameterMacros(){return{...this.state.sessionAssetParameterMacros}}resolveAssetParameterMacros(e,s,i,o){const n="session"===i?a(e):e,d=t(n,r({customerMacros:this.state.sessionAssetParameterMacros,element:this.deps.container,breakDurationSec:"session"===i?void 0:s}));for(const{key:e,macro:s}of d.unresolved)this.deps.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${s}': the macro has no value.`,{...o,key:e,macro:s});return d.params}resolveSessionAdTagParameters(){const e=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,void 0);return this.resolveAssetParameterMacros(e,void 0,"session",{breakId:void 0,assetId:void 0})}buildVastTagUrl(e,t){const a=this.resolveUri(e.uri);if(!a)return null;const r=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,e.assetParameters),o=e.duration??this.deps.getEffectiveBreakDuration()??t.duration,n=this.resolveAssetParameterMacros(r,o,"break",{breakId:t.id,assetId:e.id});return s(a,n)}pushAssetParameters(e,s){const t=this.deps.getGamStreamManager();if(!t?.isInitialized())return;const a=this.deps.getCurrentBreak(),r=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,e),o=this.resolveAssetParameterMacros(r,this.deps.getEffectiveBreakDuration()??a?.duration,"break",{breakId:a?.id,assetId:s});null!==this.state.pushedAssetParameters&&d(this.state.pushedAssetParameters,o)||(this.state.pushedAssetParameters=o,t.replaceAdTagParameters(o??{}))}resolveUri(e){return void 0===this.state.detectedDeviceType&&(this.state.detectedDeviceType=o()),n(e,this.state.detectedDeviceType)}}
@@ -1 +1 @@
1
- import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as a}from"../../types/BreakManifest";import{resolveVastAdmission as i}from"../resolveVastAdmission";import{VastAdManager as d,isImaCsaiSdkAvailable as n}from"../VastAdManager";export class AssetPlayback{constructor(e,t){this.state=e,this.deps=t}get currentBreak(){return this.state.currentBreak}async playAsset(e,t,s){if(!this.currentBreak||!this.deps.getAdPlayer())return;if(this.state.currentAsset=e,this.state.currentAssetElapsedSec=0,this.deps.clearStaticQuartiles(),this.deps.clearOverlayImage(),this.deps.log("Playing asset:",e.id,"type:",e.type,"index:",t),a(e))return void this.playVastAsset(e,t,s);if(e.mediaType===r.IMAGE)return void await this.playImageAsset(e,t,s);const i=await this.resolveAssetUri(e,this.state.effectiveBreakDurationSec??this.currentBreak.duration);if(!i)return void this.deps.onAdError(new Error("No valid URI for asset"));this.deps.log("Resolved URI:",i),this.deps.reportPhase("uri-resolved");const d=this.deps.getAdPlayer();if(!d)return;const n=this.currentBreak.id;try{if(this.deps.armAdStartBudget(e),this.state.preloadedBreakId!==this.currentBreak.id){if(this.deps.reportPhase("load-start"),!await this.deps.loadWithinBudget(d.load(i)))return;this.deps.reportPhase("load-resolved")}else this.deps.reportPhase("load-skipped-preloaded");const r=d.videoElement;if(r&&(r.style.cssText="display:block;width:100%;height:100%;object-fit:contain;"),this.deps.reportPhase("play-called"),this.state.firstFrameGateActive&&!this.state.firstFrameGateShown){const e=this.deps.waitForFirstFrame();await this.playRetryingInterruption(d,n),await e}else{let e=null;const t=await Promise.race([this.playRetryingInterruption(d,n).then(()=>!0),new Promise(t=>{e=setTimeout(()=>t(!1),this.deps.remainingAdStartBudgetMs())})]);if(e&&clearTimeout(e),!t)return void this.deps.onAdError(new Error(`Ad did not start within ${this.state.adStartBudgetMs}ms ad-start budget`))}this.state.adStartDeadlineMs=null,this.state.adStartBudgetMs=0,this.deps.transferPictureInPictureToAd();const a=this.deps.getSequencer();a?(this.deps.isGamPodAsset(e)&&this.deps.beginGamPodTracking(e),await this.deps.drive(a.onOutcome({kind:"asset-started"}))):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s)}catch(e){if(this.state.firstFrameGateActive&&null===this.state.firstFrameResolver)return;this.deps.onAdError(e instanceof Error?e:new Error(String(e)))}}async playRetryingInterruption(e,t){try{await e.play()}catch(s){if(!function(e){return"AbortError"===e?.name}(s)||!this.isStartingBreak(t))throw s;this.deps.diagnose("DA-AD-START-INTERRUPTED","Ad play() was interrupted by a browser-initiated pause before the ad started; play() re-issued once.",{breakId:t,error:s instanceof Error?s.message:String(s)}),this.deps.reportPhase("play-retried"),await e.play()}}isStartingBreak(e){return this.state.isPlaying&&this.state.currentBreak?.id===e}playVastAsset(e,s,r){if(!this.currentBreak||!this.deps.getAdPlayer())return;const a=this.deps.getVariant(this.currentBreak)?.format??t.SINGLE,o=i({format:a,ssai:!1});if(!o.admitted)return this.deps.diagnose(o.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT",`VAST asset '${e.id}' is not supported in '${a}' breaks (single/double/lshape_ad only); skipping.`,{breakId:this.currentBreak.id,assetId:e.id,format:a}),void this.deps.onAdError(new Error(`VAST not supported in ${a} format`));if(!n())return this.deps.diagnose("DA-VAST-IMA-SDK-MISSING","Google IMA client-side SDK (ima3.js) is unavailable; cannot play VAST ad.",{breakId:this.currentBreak.id,assetId:e.id}),void this.deps.onAdError(new Error("IMA client-side SDK (ima3.js) not available"));const h=this.deps.buildVastTagUrl(e,this.currentBreak);if(!h)return void this.deps.onAdError(new Error("No valid VAST tag URL for asset"));const l=this.deps.getAdPlayer(),c=l?.videoElement,p=this.deps.getAdContainer();if(!p||!c||!l)return void this.deps.onAdError(new Error("VAST playout requires an ad container and ad video element"));this.deps.revealFirstFrame(),c.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.state.vastAdManager||(this.state.vastAdManager=new d(p,c,this.deps.debug)),this.state.vastAdManager.setCallbacks({onAdStarted:t=>{if(!this.currentBreak)return;this.deps.setVastPodPosition(t);const a=this.deps.getSequencer();a?this.deps.drive(a.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,s,r,{podPosition:t})},onAdProgress:(t,s)=>{this.currentBreak&&(this.deps.recordAdProgress(t),this.state.callbacks?.onAdTimeUpdate(this.currentBreak,e,t,s))},onAdFirstQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdFirstQuartile?.(this.currentBreak,e)},onAdMidpoint:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdMidpoint?.(this.currentBreak,e)},onAdThirdQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdThirdQuartile?.(this.currentBreak,e)}}),this.deps.log("Playing VAST asset via IMA CSAI:",h);(this.state.vastAdManager.hasPreloadedManager()?(this.state.vastAdManager.setMuted(l.muted),this.state.vastAdManager.startPreloaded()):this.state.vastAdManager.playVast(h,{muted:l.muted})).then(()=>{this.deps.onAdEnded()},t=>{const s=t instanceof Error?t:new Error(String(t)),r=/ima3\.js|client-side SDK/i.test(s.message)?"DA-VAST-IMA-SDK-MISSING":"DA-VAST-IMA-ERROR";this.deps.diagnose(r,s.message,{...this.currentBreak?{breakId:this.currentBreak.id}:{},assetId:e.id}),this.deps.onAdError(s)})}async playImageAsset(e,t,s){const r=this.currentBreak,a=this.deps.getAdMediaContainer();if(!r||!a)return;const i=this.state.effectiveBreakDurationSec??r.duration,d=await this.resolveAssetUri(e,i);if(!d)return void this.deps.onAdError(new Error("No valid URI for asset"));const n=document.createElement("img");n.className="dolby-overlay-image",n.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.deps.setOverlayImage(n);const o=e.duration??i;n.onload=()=>{if(this.deps.getOverlayImage()!==n||!this.currentBreak)return;const r=this.deps.getSequencer();r?this.deps.drive(r.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s),o>0?this.deps.setImageAssetTimer(setTimeout(()=>this.onImageAssetEnded(),1e3*o)):this.onImageAssetEnded()},n.onerror=()=>{this.deps.getOverlayImage()===n&&this.deps.onAdError(new Error(`Overlay image failed to load: ${d}`))},n.src=d,a.appendChild(n)}onImageAssetEnded(){const e=this.deps.getSequencer();if(!this.currentBreak||!this.state.isPlaying||!e)return;const t=this.deps.getImageAssetTimer();t&&(clearTimeout(t),this.deps.setImageAssetTimer(null)),this.deps.drive(e.onOutcome({kind:"asset-ended"}))}async resolveAssetUri(t,r){switch(t.type){case e.STATIC:return this.deps.resolveUri(t.uri);case e.VENDOR:{const e=t;return e.vendor===s.GAM?await this.deps.buildGamUrl(e,r):(this.deps.log("Unsupported vendor:",e.vendor),null)}default:return this.deps.log("Unsupported asset type:",t.type),null}}adPlayerStreamRemainingSec(){const e=this.deps.getAdPlayer();if(!e)return 0;const{duration:t,currentTime:s}=e;if(!Number.isFinite(t)||t<=0)return 0;if(!Number.isFinite(s)||s<0)return 0;const r=t-s;return r>0?r:0}revealFirstFrame(){this.deps.revealFirstFrame()}toAdError(e){const t=e;if(t&&("number"==typeof t.code||t.message)){const e="number"==typeof t.code?` (code ${t.code})`:"",s=t.message?`: ${t.message}`:"";return new Error(`Ad playback error${e}${s}`)}return new Error("Ad playback error")}}
1
+ import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as a}from"../../types/BreakManifest";import{resolveVastAdmission as i}from"../resolveVastAdmission";import{VastAdManager as d,isImaCsaiSdkAvailable as n}from"../VastAdManager";export class AssetPlayback{constructor(e,t){this.state=e,this.deps=t}get currentBreak(){return this.state.currentBreak}async playAsset(e,t,s){if(!this.currentBreak||!this.deps.getAdPlayer())return;if(this.state.currentAsset=e,this.state.currentAssetElapsedSec=0,this.deps.clearStaticQuartiles(),this.deps.clearOverlayImage(),this.deps.log("Playing asset:",e.id,"type:",e.type,"index:",t),a(e))return void this.playVastAsset(e,t,s);if(e.mediaType===r.IMAGE)return void await this.playImageAsset(e,t,s);const i=await this.resolveAssetUri(e,this.state.effectiveBreakDurationSec??this.currentBreak.duration);if(!i)return void this.deps.onAdError(new Error("No valid URI for asset"));this.deps.log("Resolved URI:",i),this.deps.reportPhase("uri-resolved");const d=this.deps.getAdPlayer();if(!d)return;const n=this.currentBreak.id;try{this.deps.armAdStartBudget(e);if(this.state.preloadedBreakId===this.currentBreak.id&&0===t)this.deps.reportPhase("load-skipped-preloaded");else{if(this.deps.reportPhase("load-start"),!await this.deps.loadWithinBudget(d.load(i)))return;this.deps.reportPhase("load-resolved")}const r=d.videoElement;if(r&&(r.style.cssText="display:block;width:100%;height:100%;object-fit:contain;"),this.deps.reportPhase("play-called"),this.state.firstFrameGateActive&&!this.state.firstFrameGateShown){const e=this.deps.waitForFirstFrame();await this.playRetryingInterruption(d,n),await e}else{let e=null;const t=await Promise.race([this.playRetryingInterruption(d,n).then(()=>!0),new Promise(t=>{e=setTimeout(()=>t(!1),this.deps.remainingAdStartBudgetMs())})]);if(e&&clearTimeout(e),!t)return void this.deps.onAdError(new Error(`Ad did not start within ${this.state.adStartBudgetMs}ms ad-start budget`))}this.state.adStartDeadlineMs=null,this.state.adStartBudgetMs=0,this.deps.transferPictureInPictureToAd();const a=this.deps.getSequencer();a?(this.deps.isGamPodAsset(e)&&this.deps.beginGamPodTracking(e),await this.deps.drive(a.onOutcome({kind:"asset-started"}))):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s)}catch(e){if(this.state.firstFrameGateActive&&null===this.state.firstFrameResolver)return;this.deps.onAdError(e instanceof Error?e:new Error(String(e)))}}async playRetryingInterruption(e,t){try{await e.play()}catch(s){if(!function(e){return"AbortError"===e?.name}(s)||!this.isStartingBreak(t))throw s;this.deps.diagnose("DA-AD-START-INTERRUPTED","Ad play() was interrupted by a browser-initiated pause before the ad started; play() re-issued once.",{breakId:t,error:s instanceof Error?s.message:String(s)}),this.deps.reportPhase("play-retried"),await e.play()}}isStartingBreak(e){return this.state.isPlaying&&this.state.currentBreak?.id===e}playVastAsset(e,s,r){if(!this.currentBreak||!this.deps.getAdPlayer())return;const a=this.deps.getVariant(this.currentBreak)?.format??t.SINGLE,o=i({format:a,ssai:!1});if(!o.admitted)return this.deps.diagnose(o.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT",`VAST asset '${e.id}' is not supported in '${a}' breaks (single/double/lshape_ad only); skipping.`,{breakId:this.currentBreak.id,assetId:e.id,format:a}),void this.deps.onAdError(new Error(`VAST not supported in ${a} format`));if(!n())return this.deps.diagnose("DA-VAST-IMA-SDK-MISSING","Google IMA client-side SDK (ima3.js) is unavailable; cannot play VAST ad.",{breakId:this.currentBreak.id,assetId:e.id}),void this.deps.onAdError(new Error("IMA client-side SDK (ima3.js) not available"));const h=this.deps.buildVastTagUrl(e,this.currentBreak);if(!h)return void this.deps.onAdError(new Error("No valid VAST tag URL for asset"));const l=this.deps.getAdPlayer(),c=l?.videoElement,p=this.deps.getAdContainer();if(!p||!c||!l)return void this.deps.onAdError(new Error("VAST playout requires an ad container and ad video element"));this.deps.revealFirstFrame(),c.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.state.vastAdManager||(this.state.vastAdManager=new d(p,c,this.deps.debug)),this.state.vastAdManager.setCallbacks({onAdStarted:t=>{if(!this.currentBreak)return;this.deps.setVastPodPosition(t);const a=this.deps.getSequencer();a?this.deps.drive(a.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,s,r,{podPosition:t})},onAdProgress:(t,s)=>{this.currentBreak&&(this.deps.recordAdProgress(t),this.state.callbacks?.onAdTimeUpdate(this.currentBreak,e,t,s))},onAdFirstQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdFirstQuartile?.(this.currentBreak,e)},onAdMidpoint:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdMidpoint?.(this.currentBreak,e)},onAdThirdQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdThirdQuartile?.(this.currentBreak,e)}}),this.deps.log("Playing VAST asset via IMA CSAI:",h);(this.state.vastAdManager.hasPreloadedManager()?(this.state.vastAdManager.setMuted(l.muted),this.state.vastAdManager.startPreloaded()):this.state.vastAdManager.playVast(h,{muted:l.muted})).then(()=>{this.deps.onAdEnded()},t=>{const s=t instanceof Error?t:new Error(String(t)),r=/ima3\.js|client-side SDK/i.test(s.message)?"DA-VAST-IMA-SDK-MISSING":"DA-VAST-IMA-ERROR";this.deps.diagnose(r,s.message,{...this.currentBreak?{breakId:this.currentBreak.id}:{},assetId:e.id}),this.deps.onAdError(s)})}async playImageAsset(e,t,s){const r=this.currentBreak,a=this.deps.getAdMediaContainer();if(!r||!a)return;const i=this.state.effectiveBreakDurationSec??r.duration,d=await this.resolveAssetUri(e,i);if(!d)return void this.deps.onAdError(new Error("No valid URI for asset"));const n=document.createElement("img");n.className="dolby-overlay-image",n.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.deps.setOverlayImage(n);const o=e.duration??i;n.onload=()=>{if(this.deps.getOverlayImage()!==n||!this.currentBreak)return;const r=this.deps.getSequencer();r?this.deps.drive(r.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s),o>0?this.deps.setImageAssetTimer(setTimeout(()=>this.onImageAssetEnded(),1e3*o)):this.onImageAssetEnded()},n.onerror=()=>{this.deps.getOverlayImage()===n&&this.deps.onAdError(new Error(`Overlay image failed to load: ${d}`))},n.src=d,a.appendChild(n)}onImageAssetEnded(){const e=this.deps.getSequencer();if(!this.currentBreak||!this.state.isPlaying||!e)return;const t=this.deps.getImageAssetTimer();t&&(clearTimeout(t),this.deps.setImageAssetTimer(null)),this.deps.drive(e.onOutcome({kind:"asset-ended"}))}async resolveAssetUri(t,r){switch(t.type){case e.STATIC:return this.deps.resolveUri(t.uri);case e.VENDOR:{const e=t;return e.vendor===s.GAM?await this.deps.buildGamUrl(e,r):(this.deps.log("Unsupported vendor:",e.vendor),null)}default:return this.deps.log("Unsupported asset type:",t.type),null}}adPlayerStreamRemainingSec(){const e=this.deps.getAdPlayer();if(!e)return 0;const{duration:t,currentTime:s}=e;if(!Number.isFinite(t)||t<=0)return 0;if(!Number.isFinite(s)||s<0)return 0;const r=t-s;return r>0?r:0}revealFirstFrame(){this.deps.revealFirstFrame()}toAdError(e){const t=e;if(t&&("number"==typeof t.code||t.message)){const e="number"==typeof t.code?` (code ${t.code})`:"",s=t.message?`: ${t.message}`:"";return new Error(`Ad playback error${e}${s}`)}return new Error("Ad playback error")}}
@@ -54,8 +54,8 @@ export declare class GamPodBridge {
54
54
  isExpandingPodAsset(asset: Asset | undefined): boolean;
55
55
  beginGamPodTracking(asset: Asset): void;
56
56
  fallBackToPodLevelReporting(): void;
57
- clearGamPodReportTimer(): void;
58
57
  resetGamPodTracking(): void;
58
+ onGamPodAdProgress(currentTime: number): void;
59
59
  onGamPodAdStarted(info?: GamPodAdInfo): void;
60
60
  emitGamPodSteps(steps: GamPodAdStep[]): void;
61
61
  emitGamQuartile(callback: ((breakInfo: Break, asset: Asset, adIndex?: number, totalAds?: number) => void) | undefined): void;
@@ -1 +1 @@
1
- import{Vendor as t}from"../../types/BreakManifest";import{GamPodAdTracker as e}from"../GamPodAdTracker";import{GamStreamManager as s}from"../GamStreamManager";import{isGamVendorParameters as a}from"../GamPodUrlBuilder";import{gamPodIdentityMismatches as i}from"../resolveGamVendorConfiguration";export class GamPodBridge{constructor(t,e){this.state=t,this.deps=e,this.callbacks={}}setConfig(t){this.state.gamConfig=t}setEventCallbacks(t){this.callbacks=t}get eventCallbacks(){return this.callbacks}async startGamSession(t,e){this.state.currentGamIdentity=t,this.state.sessionAssetParameters=e,this.state.pushedAssetParameters=null,await this.initializeGam()}endGamSession(){this.state.gamStreamManager?.reset(),this.state.gamInitPromise=null,this.state.currentGamIdentity=void 0,this.state.sessionAssetParameters=void 0,this.state.manifestAssetParameters=void 0,this.state.sessionAssetParameterMacros={},this.state.updatedAssetParameters=void 0,this.state.pushedAssetParameters=null}async initializeGam(){if(!this.state.currentGamIdentity)return;if(this.deps.log("Initializing GAM stream manager..."),this.state.gamStreamManager)this.state.gamStreamManager.reset();else{const t=this.deps.isSharedElement()?this.deps.contentPlayer.videoElement:this.deps.getAdPlayer()?.videoElement;if(!t)return void console.warn("[OptiViewAds] GAM requires an adapter that exposes videoElement. GAM will be skipped.");const e=this.deps.isSharedElement()?void 0:this.deps.getAdContainer()||void 0;this.state.gamStreamManager=new s(t,e,this.deps.debug)}this.state.gamStreamManager.setCallbacks({onAdFirstQuartile:()=>this.emitGamQuartile(this.callbacks.onAdFirstQuartile),onAdMidpoint:()=>this.emitGamQuartile(this.callbacks.onAdMidpoint),onAdThirdQuartile:()=>this.emitGamQuartile(this.callbacks.onAdThirdQuartile),onAdStarted:t=>{this.deps.log("GAM ad started"),this.onGamPodAdStarted(t)},onAdComplete:()=>{this.deps.log("GAM ad complete"),"per-ad"===this.state.gamPodMode&&this.state.gamPodTracker&&this.emitGamPodSteps(this.state.gamPodTracker.onAdComplete())},onAdBreakEnded:()=>{this.deps.log("GAM ad break ended");const t=this.deps.getAdPlayerStreamRemainingSec();if(t>.25)return this.deps.log(`GAM pod has ${t.toFixed(3)}s of slate left; playing it out`),void("per-ad"===this.state.gamPodMode&&this.state.gamPodTracker&&this.emitGamPodSteps(this.state.gamPodTracker.finish()));this.deps.onAdEnded()},onError:t=>this.deps.log("GAM error:",t)});const t=this.state.currentGamIdentity,e=this.deps.resolveSessionAdTagParameters(),a={networkCode:t.networkCode,customAssetKey:t.customAssetKey,...e&&{adTagParameters:e},...this.state.gamConfig?.streamActivityMonitorId&&{streamActivityMonitorId:this.state.gamConfig.streamActivityMonitorId}};this.state.gamInitPromise=this.state.gamStreamManager.initialize(a);try{await this.state.gamInitPromise,this.deps.log("GAM stream manager initialized, streamId:",this.state.gamStreamManager.getStreamId())}catch(t){throw this.state.gamInitPromise=null,t}}isGamEnabled(){return!1!==this.state.gamConfig?.enabled}isGamReady(){return!0===this.state.gamStreamManager?.isInitialized()}isGamPodAsset(e){return"vendor"===e.type&&e.vendor===t.GAM&&this.isGamReady()}isExpandingPodAsset(t){return("pending"===this.state.gamPodMode||"per-ad"===this.state.gamPodMode)&&(!!t&&t.id===this.state.gamPodAsset?.id)}beginGamPodTracking(t){this.resetGamPodTracking(),this.state.gamPodAsset=t,this.state.gamPodTracker=new e(t),this.state.gamPodMode="pending",this.state.gamPodReportTimer=setTimeout(()=>{this.deps.log("No IMA pod ad reported in time; reporting the pod as a single ad"),this.fallBackToPodLevelReporting()},2e3)}fallBackToPodLevelReporting(){if("pending"!==this.state.gamPodMode)return;this.clearGamPodReportTimer(),this.state.gamPodMode="legacy",this.state.gamPodTracker=null;const t=this.state.withheldPodAdBegin;this.state.withheldPodAdBegin=null,t&&this.deps.emitStep(t)}clearGamPodReportTimer(){this.state.gamPodReportTimer&&(clearTimeout(this.state.gamPodReportTimer),this.state.gamPodReportTimer=null)}resetGamPodTracking(){this.clearGamPodReportTimer(),this.state.gamPodMode="none",this.state.gamPodTracker=null,this.state.gamPodAsset=null,this.state.withheldPodAdBegin=null}onGamPodAdStarted(t){t&&this.state.gamPodTracker&&("pending"===this.state.gamPodMode&&(this.clearGamPodReportTimer(),this.state.withheldPodAdBegin=null,this.state.gamPodMode="per-ad"),"per-ad"===this.state.gamPodMode&&this.emitGamPodSteps(this.state.gamPodTracker.onAdStarted(t)))}emitGamPodSteps(t){const e=this.deps.getCurrentBreak();if(e)for(const s of t){const t={adId:s.adId,creativeId:s.creativeId,podPosition:s.adPosition};"adbegin"===s.type?this.deps.emitAdStart(e,s.asset,s.adIndex,s.totalAds,t):this.deps.emitAdEnd(e,s.asset,s.adIndex,s.totalAds,t)}}emitGamQuartile(t){const e=this.deps.getCurrentBreak();if(!t||!e)return;const s="per-ad"===this.state.gamPodMode?this.state.gamPodTracker?.currentAd:null;if(s)return void t(e,s.asset,s.adIndex,s.totalAds);const a=this.deps.getCurrentAsset();a&&t(e,a)}async buildGamUrl(t,e){if(!a(t.vendorParameters))return this.deps.log("Invalid GAM vendor parameters:",t.vendorParameters),null;if(this.state.gamInitPromise&&await this.state.gamInitPromise,!this.state.gamStreamManager?.isInitialized())throw this.deps.log("GAM stream manager not initialized"),new Error("GAM stream manager not initialized. The stream identity comes from the break manifest's vendorConfiguration — check for DA-GAM-CONFIG-MISSING.");const s=t.vendorParameters;if(this.state.currentGamIdentity){const e=i(this.state.currentGamIdentity,s);for(const s of e)this.deps.diagnose("DA-GAM-POD-IDENTITY-MISMATCH",`GAM asset ${t.id} skipped: vendorParameters.${s.field} "${s.assetValue}" does not match the stream session's "${s.sessionValue}".`,{assetId:t.id,field:s.field,assetValue:s.assetValue,sessionValue:s.sessionValue});if(e.length>0)return null}this.deps.pushAssetParameters(t.assetParameters,t.id);const r=this.state.gamStreamManager.buildPodUrl(t.uri,e,s);return this.deps.log("Built GAM pod URL:",r),r}processTimedMetadata(t){this.state.gamStreamManager?.processTimedMetadata(t)}destroy(){this.resetGamPodTracking(),this.state.gamStreamManager?.destroy(),this.state.gamStreamManager=null,this.state.gamInitPromise=null}}
1
+ import{Vendor as t}from"../../types/BreakManifest";import{GamPodAdTracker as e}from"../GamPodAdTracker";import{GamStreamManager as s}from"../GamStreamManager";import{isGamVendorParameters as a}from"../GamPodUrlBuilder";import{gamPodIdentityMismatches as i}from"../resolveGamVendorConfiguration";export class GamPodBridge{constructor(t,e){this.state=t,this.deps=e,this.callbacks={}}setConfig(t){this.state.gamConfig=t}setEventCallbacks(t){this.callbacks=t}get eventCallbacks(){return this.callbacks}async startGamSession(t,e){this.state.currentGamIdentity=t,this.state.sessionAssetParameters=e,this.state.pushedAssetParameters=null,await this.initializeGam()}endGamSession(){this.state.gamStreamManager?.reset(),this.state.gamInitPromise=null,this.state.currentGamIdentity=void 0,this.state.sessionAssetParameters=void 0,this.state.manifestAssetParameters=void 0,this.state.sessionAssetParameterMacros={},this.state.updatedAssetParameters=void 0,this.state.pushedAssetParameters=null,this.state.earlyGamPodAdStart=null}async initializeGam(){if(!this.state.currentGamIdentity)return;if(this.deps.log("Initializing GAM stream manager..."),this.state.gamStreamManager)this.state.gamStreamManager.reset();else{const t=this.deps.isSharedElement()?this.deps.contentPlayer.videoElement:this.deps.getAdPlayer()?.videoElement;if(!t)return void console.warn("[OptiViewAds] GAM requires an adapter that exposes videoElement. GAM will be skipped.");const e=this.deps.isSharedElement()?void 0:this.deps.getAdContainer()||void 0;this.state.gamStreamManager=new s(t,e,this.deps.debug)}this.state.gamStreamManager.setCallbacks({onAdFirstQuartile:()=>this.emitGamQuartile(this.callbacks.onAdFirstQuartile),onAdMidpoint:()=>this.emitGamQuartile(this.callbacks.onAdMidpoint),onAdThirdQuartile:()=>this.emitGamQuartile(this.callbacks.onAdThirdQuartile),onAdStarted:t=>{this.deps.log("GAM ad started"),this.onGamPodAdStarted(t)},onAdComplete:()=>{this.deps.log("GAM ad complete"),"per-ad"===this.state.gamPodMode&&this.state.gamPodTracker&&this.emitGamPodSteps(this.state.gamPodTracker.onAdComplete())},onAdBreakEnded:()=>{this.deps.log("GAM ad break ended");const t=this.deps.getAdPlayerStreamRemainingSec();if(t>.25)return this.deps.log(`GAM pod has ${t.toFixed(3)}s of slate left; playing it out`),void("per-ad"===this.state.gamPodMode&&this.state.gamPodTracker&&this.emitGamPodSteps(this.state.gamPodTracker.finish()));this.deps.onAdEnded()},onError:t=>this.deps.log("GAM error:",t)});const t=this.state.currentGamIdentity,e=this.deps.resolveSessionAdTagParameters(),a={networkCode:t.networkCode,customAssetKey:t.customAssetKey,...e&&{adTagParameters:e},...this.state.gamConfig?.streamActivityMonitorId&&{streamActivityMonitorId:this.state.gamConfig.streamActivityMonitorId}};this.state.gamInitPromise=this.state.gamStreamManager.initialize(a);try{await this.state.gamInitPromise,this.deps.log("GAM stream manager initialized, streamId:",this.state.gamStreamManager.getStreamId())}catch(t){throw this.state.gamInitPromise=null,t}}isGamEnabled(){return!1!==this.state.gamConfig?.enabled}isGamReady(){return!0===this.state.gamStreamManager?.isInitialized()}isGamPodAsset(e){return"vendor"===e.type&&e.vendor===t.GAM&&this.isGamReady()}isExpandingPodAsset(t){return("pending"===this.state.gamPodMode||"per-ad"===this.state.gamPodMode)&&(!!t&&t.id===this.state.gamPodAsset?.id)}beginGamPodTracking(t){this.resetGamPodTracking(),this.state.gamPodAsset=t,this.state.gamPodTracker=new e(t),this.state.gamPodMode="pending";const s=this.state.earlyGamPodAdStart;this.state.earlyGamPodAdStart=null,s&&this.onGamPodAdStarted(s)}fallBackToPodLevelReporting(){if("pending"!==this.state.gamPodMode)return;this.state.gamPodMode="legacy",this.state.gamPodTracker=null;const t=this.state.withheldPodAdBegin;this.state.withheldPodAdBegin=null,t&&this.deps.emitStep(t)}resetGamPodTracking(){this.state.gamPodMode="none",this.state.gamPodTracker=null,this.state.gamPodAsset=null,this.state.withheldPodAdBegin=null}onGamPodAdProgress(t){"pending"===this.state.gamPodMode&&(t<2||(this.deps.log("No IMA pod ad reported after the pod started playing; reporting the pod as a single ad"),this.fallBackToPodLevelReporting()))}onGamPodAdStarted(t){t&&("none"!==this.state.gamPodMode?this.state.gamPodTracker&&("pending"===this.state.gamPodMode&&(this.state.withheldPodAdBegin=null,this.state.gamPodMode="per-ad"),"per-ad"===this.state.gamPodMode&&this.emitGamPodSteps(this.state.gamPodTracker.onAdStarted(t))):this.state.earlyGamPodAdStart=t)}emitGamPodSteps(t){const e=this.deps.getCurrentBreak();if(e)for(const s of t){const t={adId:s.adId,creativeId:s.creativeId,podPosition:s.adPosition};"adbegin"===s.type?this.deps.emitAdStart(e,s.asset,s.adIndex,s.totalAds,t):this.deps.emitAdEnd(e,s.asset,s.adIndex,s.totalAds,t)}}emitGamQuartile(t){const e=this.deps.getCurrentBreak();if(!t||!e)return;const s="per-ad"===this.state.gamPodMode?this.state.gamPodTracker?.currentAd:null;if(s)return void t(e,s.asset,s.adIndex,s.totalAds);const a=this.deps.getCurrentAsset();a&&t(e,a)}async buildGamUrl(t,e){if(!a(t.vendorParameters))return this.deps.log("Invalid GAM vendor parameters:",t.vendorParameters),null;if(this.state.gamInitPromise&&await this.state.gamInitPromise,!this.state.gamStreamManager?.isInitialized())throw this.deps.log("GAM stream manager not initialized"),new Error("GAM stream manager not initialized. The stream identity comes from the break manifest's vendorConfiguration — check for DA-GAM-CONFIG-MISSING.");const s=t.vendorParameters;if(this.state.currentGamIdentity){const e=i(this.state.currentGamIdentity,s);for(const s of e)this.deps.diagnose("DA-GAM-POD-IDENTITY-MISMATCH",`GAM asset ${t.id} skipped: vendorParameters.${s.field} "${s.assetValue}" does not match the stream session's "${s.sessionValue}".`,{assetId:t.id,field:s.field,assetValue:s.assetValue,sessionValue:s.sessionValue});if(e.length>0)return null}this.state.earlyGamPodAdStart=null,this.deps.pushAssetParameters(t.assetParameters,t.id);const r=this.state.gamStreamManager.buildPodUrl(t.uri,e,s);return this.deps.log("Built GAM pod URL:",r),r}processTimedMetadata(t){this.state.gamStreamManager?.processTimedMetadata(t)}destroy(){this.resetGamPodTracking(),this.state.earlyGamPodAdStart=null,this.state.gamStreamManager?.destroy(),this.state.gamStreamManager=null,this.state.gamInitPromise=null}}
@@ -1 +1 @@
1
- import{BreakFormat as e,isPostRollBreak as t}from"../../types/BreakManifest";export class SharedElementBreakPlayer{constructor(e,t){this.state=e,this.deps=t,this.contentElementAdPlayer=null,this.sharedAdSource=null,this.sharedAdEndedHandler=null,this.sharedAdTimeUpdateHandler=null,this.sharedAdFirstFrameTimer=null,this.sharedRestoreWatchdog=null}sharedElementAdPlayer(){const e=this.deps.contentPlayer.videoElement;if(!e)return null;if(this.contentElementAdPlayer?.videoElement===e)return this.contentElementAdPlayer;this.releaseContentElementAdPlayer();const t=this.deps.createAdAdapter(document.createElement("div"),e);return t.videoElement!==e?(this.deps.log("shared-element: createAdAdapter ignored the content element; falling back to the content player for ad playback"),t.destroy(),null):(this.contentElementAdPlayer=t,t)}async sharedAdStep(e,t,s){try{await s()}catch(s){const a=t.videoElement,n=s instanceof Error?s.message:String(s);throw new Error(`shared-element ${e} failed: ${n} (readyState=${a?.readyState??"-"} paused=${a?.paused??"-"} networkState=${a?.networkState??"-"})`,{cause:s})}}clearSharedAdFirstFrameWatchdog(){this.sharedAdFirstFrameTimer&&(clearTimeout(this.sharedAdFirstFrameTimer),this.sharedAdFirstFrameTimer=null)}armSharedAdFirstFrameWatchdog(e,t){this.clearSharedAdFirstFrameWatchdog(),this.sharedAdFirstFrameTimer=setTimeout(()=>{if(this.sharedAdFirstFrameTimer=null,!this.state.isPlaying||!this.state.currentBreak)return;const s=e.videoElement,a=this.deps.contentPlayer.videoElement,n=s?.currentSrc??"",r=`readyState=${s?.readyState??"-"} networkState=${s?.networkState??"-"} paused=${s?.paused??"-"} connected=${s?.isConnected??"-"} srcKind=${n.startsWith("blob:")?"blob":n?"url":"none"} sameElementAsContentPlayer=${String(s===a)}`;this.deps.log(`shared-element: no first frame within ${t}ms (${r})`),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",`the ad never rendered a frame on the content element within ${t}ms`,{breakId:this.state.currentBreak.id,detail:r});const d=this.state.currentAsset;d&&this.state.callbacks?.onAdError(this.state.currentBreak,d,new Error(`shared-element ad produced no frames within ${t}ms (${r})`)),this.onSharedElementAdEnded()},t)}attachSharedAdListeners(e){this.sharedAdSource=e,this.sharedAdEndedHandler=()=>this.onSharedElementAdEnded(),e.on("ended",this.sharedAdEndedHandler),this.sharedAdTimeUpdateHandler=()=>{this.sharedAdFirstFrameTimer&&e.currentTime>0&&this.clearSharedAdFirstFrameWatchdog(),this.deps.reportAdProgress(e)},e.on("timeupdate",this.sharedAdTimeUpdateHandler)}detachSharedAdListeners(){const e=this.sharedAdSource??this.deps.contentPlayer;this.sharedAdSource=null,this.state.sharedAdClaimActive=!1,this.clearSharedAdFirstFrameWatchdog(),this.sharedAdTimeUpdateHandler&&(e.off("timeupdate",this.sharedAdTimeUpdateHandler),this.sharedAdTimeUpdateHandler=null),this.sharedAdEndedHandler&&(e.off("ended",this.sharedAdEndedHandler),this.sharedAdEndedHandler=null)}async releaseContentElementFromContentEngine(){if(this.deps.contentPlayer.releaseMediaElement)try{await this.deps.contentPlayer.releaseMediaElement()}catch(e){this.deps.log("shared-element: the content engine did not release the element:",e)}}releaseContentElementAdPlayer(){if(this.contentElementAdPlayer){try{this.contentElementAdPlayer.destroy()}catch(e){this.deps.log("shared-element: releasing the ad player on the content element failed:",e)}this.contentElementAdPlayer=null}}async playAdThroughContentElement(){const e=this.state.currentAsset,t=this.state.currentBreak;if(!e||!t)return;const s=this.state.effectiveBreakDurationSec??t.duration,a=await this.deps.resolveAssetUri(e,s);if(!a)return void this.deps.log("shared-element: no URI for the running ad; leaving it on the ad element");const n=this.deps.getAdPlayer()?.currentTime??0;this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const r=this.sharedElementAdPlayer()??this.deps.contentPlayer;try{this.deps.getAdPlayer()?.pause(),this.deps.clearContentGate(),await r.load(a),this.attachSharedAdListeners(r),n>0&&r.seek(n),await r.play(),this.deps.hide(),this.deps.log("shared-element: the ad now plays through the content element")}catch(e){this.deps.log("shared-element: could not move the ad onto the content element:",e),this.state.sharedAdClaimActive=!1,this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null}}async playSharedElementBreak(t,s){const a=this.deps.getVariant(t);if(!a)return this.deps.log("shared-element: no valid variant, skipping break:",t.id),void s.onBreakEnd(t);if(a.format===e.LSHAPE_CONTENT)return this.deps.log("shared-element: skipping lshape_content break (no fullscreen video asset):",t.id),void s.onBreakEnd(t);const n=this.deps.getAssets(t)[0];if(!n)return this.deps.log("shared-element: no asset to play, skipping break:",t.id),void s.onBreakEnd(t);a.format!==e.SINGLE&&this.deps.log(`shared-element: downgrading ${a.format} to a single fullscreen ad:`,t.id),this.state.currentBreak=t,this.state.currentAsset=n,this.state.callbacks=s,this.state.isPlaying=!0;const r=this.state.effectiveBreakDurationSec??t.duration,d=await this.deps.resolveAssetUri(n,r);if(!d)return this.deps.log("shared-element: no valid URI, skipping break:",t.id),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null,void s.onBreakEnd(t);this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const i=this.sharedElementAdPlayer()??this.deps.contentPlayer;if(r&&r>0){const e=r+this.state.adBreakCutSafetyMarginSec;this.state.durationTimer=setTimeout(()=>{this.deps.log("shared-element: break-cut timer expired, ending break"),this.onSharedElementAdEnded()},1e3*e)}try{this.deps.clearContentGate(),await this.sharedAdStep("load",i,()=>i.load(d)),this.attachSharedAdListeners(i),this.armSharedAdFirstFrameWatchdog(i,Math.max(Math.min(1e3*(r??0)-100,5e3),3e3)),await this.sharedAdStep("play",i,()=>i.play()),this.state.callbacks?.onAdStart(t,n,0,1)}catch(e){this.deps.log("shared-element: ad playback failed:",e),this.state.callbacks?.onAdError(t,n,e instanceof Error?e:new Error(String(e))),this.onSharedElementAdEnded()}}onSharedElementAdEnded(){if(!this.state.isPlaying||!this.state.currentBreak)return;const e=this.state.currentBreak,t=this.state.currentAsset;this.detachSharedAdListeners(),this.state.durationTimer&&(clearTimeout(this.state.durationTimer),this.state.durationTimer=null),this.state.isPlaying=!1,this.state.currentBreak=null,t&&this.state.callbacks?.onAdEnd(e,t,0,1),this.state.currentAsset=null,this.restoreSharedElementContent(e),this.state.callbacks?.onBreakEnd(e)}restoreSharedElementContent(e){const s=this.state.savedContentSrc,a=this.state.savedContentTime,n=this.state.savedContentLive,r=this.state.savedContentEdgeDistance;if(this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null,!s)return this.deps.log("shared-element: no saved content source to restore"),void this.releaseContentElementAdPlayer();e&&"number"==typeof e.start&&this.state.callbacks?.onContentResumeSeek?.(n?this.deps.contentPlayer.currentTime:a,e.start+e.duration),this.state.callbacks?.onContentReloading?.(!0);const d=!!e&&t(e),i=async()=>{if(await this.deps.contentPlayer.load(s),n){if(!d&&null!==r&&r>0){const e=await this.deps.waitForLiveEdge();if(null!==e){const t=Math.max(e-r,0);this.deps.log(`shared-element: live resume ${r.toFixed(2)}s behind the edge (${t.toFixed(2)}s)`),this.deps.contentPlayer.seek(t)}}}else this.deps.contentPlayer.seek(a);d||await this.deps.contentPlayer.play()};i().catch(async t=>{this.deps.log("shared-element: content restore failed, retrying once:",t);try{await i()}catch(t){this.deps.diagnose("DA-SHARED-ELEMENT-RESTORE-FAILED",`Content did not resume after the shared-element break: ${t instanceof Error?t.message:String(t)}`,{breakId:e?.id,live:n,attempts:2})}}).finally(()=>{this.state.contentPaused=!1,this.deps.clearContentGate(),this.releaseContentElementAdPlayer(),this.deps.rebindContentElementIfSwapped(),this.state.callbacks?.onContentReloading?.(!1),d||this.armSharedRestoreWatchdog(e)})}armSharedRestoreWatchdog(e){this.sharedRestoreWatchdog&&clearTimeout(this.sharedRestoreWatchdog);const t=this.deps.contentPlayer.currentTime;this.sharedRestoreWatchdog=setTimeout(()=>{this.sharedRestoreWatchdog=null;const s=this.deps.contentPlayer.currentTime-t;(this.deps.contentPlayer.paused||s<1)&&(this.deps.diagnose("DA-CONTENT-RESUME-STALLED",`Content did not resume within 5s of the shared-element restore (paused: ${this.deps.contentPlayer.paused}, advanced: ${s.toFixed(1)}s); nudging play()`,{breakId:e?.id??"?",paused:this.deps.contentPlayer.paused,advancedSec:s}),this.deps.contentPlayer.play().catch(e=>{this.deps.log("shared-element: watchdog play() nudge failed:",e)}))},5e3)}clearSharedRestoreWatchdog(){this.sharedRestoreWatchdog&&(clearTimeout(this.sharedRestoreWatchdog),this.sharedRestoreWatchdog=null)}restorableContentSource(){const e=this.deps.contentPlayer.sourceUrl;if(e&&!e.startsWith("blob:"))return e;const t=this.deps.contentPlayer.videoElement,s=t?.currentSrc||t?.src||"";return s.length>0&&!s.startsWith("blob:")?s:null}}
1
+ import{BreakFormat as e,isPostRollBreak as t}from"../../types/BreakManifest";export class SharedElementBreakPlayer{constructor(e,t){this.state=e,this.deps=t,this.contentElementAdPlayer=null,this.sharedAdSource=null,this.sharedAdEndedHandler=null,this.sharedAdTimeUpdateHandler=null,this.sharedAdFirstFrameTimer=null,this.sharedRestoreWatchdog=null}sharedElementAdPlayer(){const e=this.deps.contentPlayer.videoElement;if(!e)return null;if(this.contentElementAdPlayer?.videoElement===e)return this.contentElementAdPlayer;this.releaseContentElementAdPlayer();const t=this.deps.createAdAdapter(document.createElement("div"),e);return t.videoElement!==e?(this.deps.log("shared-element: createAdAdapter ignored the content element; falling back to the content player for ad playback"),t.destroy(),null):(this.contentElementAdPlayer=t,t)}async sharedAdStep(e,t,s){try{await s()}catch(s){const a=t.videoElement,n=s instanceof Error?s.message:String(s);throw new Error(`shared-element ${e} failed: ${n} (readyState=${a?.readyState??"-"} paused=${a?.paused??"-"} networkState=${a?.networkState??"-"})`,{cause:s})}}clearSharedAdFirstFrameWatchdog(){this.sharedAdFirstFrameTimer&&(clearTimeout(this.sharedAdFirstFrameTimer),this.sharedAdFirstFrameTimer=null)}armSharedAdFirstFrameWatchdog(e,t){this.clearSharedAdFirstFrameWatchdog(),this.sharedAdFirstFrameTimer=setTimeout(()=>{if(this.sharedAdFirstFrameTimer=null,!this.state.isPlaying||!this.state.currentBreak)return;const s=e.videoElement,a=this.deps.contentPlayer.videoElement,n=s?.currentSrc??"",r=`readyState=${s?.readyState??"-"} networkState=${s?.networkState??"-"} paused=${s?.paused??"-"} connected=${s?.isConnected??"-"} srcKind=${n.startsWith("blob:")?"blob":n?"url":"none"} sameElementAsContentPlayer=${String(s===a)}`;this.deps.log(`shared-element: no first frame within ${t}ms (${r})`),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",`the ad never rendered a frame on the content element within ${t}ms`,{breakId:this.state.currentBreak.id,detail:r});const d=this.state.currentAsset;d&&this.state.callbacks?.onAdError(this.state.currentBreak,d,new Error(`shared-element ad produced no frames within ${t}ms (${r})`)),this.onSharedElementAdEnded()},t)}attachSharedAdListeners(e){this.sharedAdSource=e,this.sharedAdEndedHandler=()=>this.onSharedElementAdEnded(),e.on("ended",this.sharedAdEndedHandler),this.sharedAdTimeUpdateHandler=()=>{this.sharedAdFirstFrameTimer&&e.currentTime>0&&this.clearSharedAdFirstFrameWatchdog(),this.deps.reportAdProgress(e)},e.on("timeupdate",this.sharedAdTimeUpdateHandler)}detachSharedAdListeners(){const e=this.sharedAdSource??this.deps.contentPlayer;this.sharedAdSource=null,this.state.sharedAdClaimActive=!1,this.clearSharedAdFirstFrameWatchdog(),this.sharedAdTimeUpdateHandler&&(e.off("timeupdate",this.sharedAdTimeUpdateHandler),this.sharedAdTimeUpdateHandler=null),this.sharedAdEndedHandler&&(e.off("ended",this.sharedAdEndedHandler),this.sharedAdEndedHandler=null)}async releaseContentElementFromContentEngine(){if(this.deps.contentPlayer.releaseMediaElement)try{await this.deps.contentPlayer.releaseMediaElement()}catch(e){this.deps.log("shared-element: the content engine did not release the element:",e)}}releaseContentElementAdPlayer(){if(this.contentElementAdPlayer){try{this.contentElementAdPlayer.destroy()}catch(e){this.deps.log("shared-element: releasing the ad player on the content element failed:",e)}this.contentElementAdPlayer=null}}async playAdThroughContentElement(){const e=this.state.currentAsset,t=this.state.currentBreak;if(!e||!t)return;const s=this.state.effectiveBreakDurationSec??t.duration,a=await this.deps.resolveAssetUri(e,s);if(!a)return void this.deps.log("shared-element: no URI for the running ad; leaving it on the ad element");const n=this.deps.getAdPlayer()?.currentTime??0;this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const r=this.sharedElementAdPlayer()??this.deps.contentPlayer;try{this.deps.getAdPlayer()?.pause(),this.deps.clearContentGate(),await r.load(a),this.attachSharedAdListeners(r),n>0&&r.seek(n),await r.play(),this.deps.hide(),this.deps.log("shared-element: the ad now plays through the content element")}catch(e){this.deps.log("shared-element: could not move the ad onto the content element:",e),this.state.sharedAdClaimActive=!1,this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null}}async playSharedElementBreak(t,s){const a=this.deps.getVariant(t);if(!a)return this.deps.log("shared-element: no valid variant, skipping break:",t.id),void s.onBreakEnd(t);if(a.format===e.LSHAPE_CONTENT)return this.deps.log("shared-element: skipping lshape_content break (no fullscreen video asset):",t.id),void s.onBreakEnd(t);const n=this.deps.getAssets(t)[0];if(!n)return this.deps.log("shared-element: no asset to play, skipping break:",t.id),void s.onBreakEnd(t);a.format!==e.SINGLE&&this.deps.log(`shared-element: downgrading ${a.format} to a single fullscreen ad:`,t.id),this.state.currentBreak=t,this.state.currentAsset=n,this.state.callbacks=s,this.state.isPlaying=!0;const r=this.state.effectiveBreakDurationSec??t.duration,d=await this.deps.resolveAssetUri(n,r);if(!d)return this.deps.log("shared-element: no valid URI, skipping break:",t.id),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null,void s.onBreakEnd(t);this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const i=this.sharedElementAdPlayer()??this.deps.contentPlayer;if(r&&r>0){const e=r+this.state.adBreakCutSafetyMarginSec;this.state.durationTimer=setTimeout(()=>{this.deps.log("shared-element: break-cut timer expired, ending break"),this.state.isPlaying&&this.state.currentBreak&&this.state.callbacks?.onBreakCut?.(this.state.currentBreak),this.onSharedElementAdEnded()},1e3*e)}try{this.deps.clearContentGate(),await this.sharedAdStep("load",i,()=>i.load(d)),this.attachSharedAdListeners(i),this.armSharedAdFirstFrameWatchdog(i,Math.max(Math.min(1e3*(r??0)-100,5e3),3e3)),await this.sharedAdStep("play",i,()=>i.play()),this.state.callbacks?.onAdStart(t,n,0,1)}catch(e){this.deps.log("shared-element: ad playback failed:",e),this.state.callbacks?.onAdError(t,n,e instanceof Error?e:new Error(String(e))),this.onSharedElementAdEnded()}}onSharedElementAdEnded(){if(!this.state.isPlaying||!this.state.currentBreak)return;const e=this.state.currentBreak,t=this.state.currentAsset;this.detachSharedAdListeners(),this.state.durationTimer&&(clearTimeout(this.state.durationTimer),this.state.durationTimer=null),this.state.isPlaying=!1,this.state.currentBreak=null,t&&this.state.callbacks?.onAdEnd(e,t,0,1),this.state.currentAsset=null,this.restoreSharedElementContent(e),this.state.callbacks?.onBreakEnd(e)}restoreSharedElementContent(e){const s=this.state.savedContentSrc,a=this.state.savedContentTime,n=this.state.savedContentLive,r=this.state.savedContentEdgeDistance;if(this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null,!s)return this.deps.log("shared-element: no saved content source to restore"),void this.releaseContentElementAdPlayer();e&&"number"==typeof e.start&&this.state.callbacks?.onContentResumeSeek?.(n?this.deps.contentPlayer.currentTime:a,e.start+e.duration),this.state.callbacks?.onContentReloading?.(!0);const d=!!e&&t(e),i=async()=>{if(await this.deps.contentPlayer.load(s),n){if(!d&&null!==r&&r>0){const e=await this.deps.waitForLiveEdge();if(null!==e){const t=Math.max(e-r,0);this.deps.log(`shared-element: live resume ${r.toFixed(2)}s behind the edge (${t.toFixed(2)}s)`),this.deps.contentPlayer.seek(t)}}}else this.deps.contentPlayer.seek(a);d||await this.deps.contentPlayer.play()};i().catch(async t=>{this.deps.log("shared-element: content restore failed, retrying once:",t);try{await i()}catch(t){this.deps.diagnose("DA-SHARED-ELEMENT-RESTORE-FAILED",`Content did not resume after the shared-element break: ${t instanceof Error?t.message:String(t)}`,{breakId:e?.id,live:n,attempts:2})}}).finally(()=>{this.state.contentPaused=!1,this.deps.clearContentGate(),this.releaseContentElementAdPlayer(),this.deps.rebindContentElementIfSwapped(),this.state.callbacks?.onContentReloading?.(!1),d||this.armSharedRestoreWatchdog(e)})}armSharedRestoreWatchdog(e){this.sharedRestoreWatchdog&&clearTimeout(this.sharedRestoreWatchdog);const t=this.deps.contentPlayer.currentTime;this.sharedRestoreWatchdog=setTimeout(()=>{this.sharedRestoreWatchdog=null;const s=this.deps.contentPlayer.currentTime-t;(this.deps.contentPlayer.paused||s<1)&&(this.deps.diagnose("DA-CONTENT-RESUME-STALLED",`Content did not resume within 5s of the shared-element restore (paused: ${this.deps.contentPlayer.paused}, advanced: ${s.toFixed(1)}s); nudging play()`,{breakId:e?.id??"?",paused:this.deps.contentPlayer.paused,advancedSec:s}),this.deps.contentPlayer.play().catch(e=>{this.deps.log("shared-element: watchdog play() nudge failed:",e)}))},5e3)}clearSharedRestoreWatchdog(){this.sharedRestoreWatchdog&&(clearTimeout(this.sharedRestoreWatchdog),this.sharedRestoreWatchdog=null)}restorableContentSource(){const e=this.deps.contentPlayer.sourceUrl;if(e&&!e.startsWith("blob:"))return e;const t=this.deps.contentPlayer.videoElement,s=t?.currentSrc||t?.src||"";return s.length>0&&!s.startsWith("blob:")?s:null}}
@@ -0,0 +1,10 @@
1
+ import type { TimedMetadataCue } from '../interfaces/PlayerAdapter';
2
+ import { MetadataCueTracker } from './MetadataCueTracker';
3
+ import type { BreakScheduler } from './BreakScheduler';
4
+ export declare function armBreakFromPresentedCue(cue: TimedMetadataCue, metadataCueTracker: MetadataCueTracker | null, breakScheduler: BreakScheduler | undefined): void;
5
+ export declare function notedManifestBreakIds(manifest: {
6
+ breaks: Array<{
7
+ id: string;
8
+ }>;
9
+ }, seenManifestBreakIds: Set<string>): string[];
10
+ export declare function diagnoseAnvatoCueObserved(cue: TimedMetadataCue, recorded: boolean, metadataCueTracker: MetadataCueTracker | null, anvatoObservedLogged: Set<string>, currentTime: number, diagnose: (code: 'DA-ANVATO-CUE-OBSERVED', message: string, context: Record<string, unknown>) => void): void;
@@ -0,0 +1 @@
1
+ import{isAnvatoCue as e,parseAnvatoPayload as t}from"./MetadataCueTracker";export function armBreakFromPresentedCue(r,s,a){if(!s||!a||!e(r))return;const i=t(r.messageData??"");if("cue"!==i.type)return;const n=Number(i.pts);Number.isFinite(n)&&a.notifyCuePresented(n)}export function notedManifestBreakIds(e,t){const r=[];for(const s of e.breaks)t.has(s.id)||(t.add(s.id),r.push(s.id));return r}export function diagnoseAnvatoCueObserved(r,s,a,i,n,o){if(!a||!e(r))return;const d=r.messageData??"",u=t(d);if("cue"!==u.type)return;const c=`${r.startTime}|${d}`;if(i.has(c))return;i.add(c);const m=Number(u.pts);o("DA-ANVATO-CUE-OBSERVED",`Anvato cue observed: pts=${u.pts} at media time ${r.startTime}s`,{pts:Number.isFinite(m)?m:u.pts,mediaTime:r.startTime,playheadSec:n,recorded:s,payload:d.slice(0,300)})}
@@ -65,5 +65,7 @@ export declare function applyAssetParameterMacros(params: Record<string, string>
65
65
  * `#fragment`, reusing an existing `?`. Keys are sorted so the three cores
66
66
  * produce the same bytes regardless of their map ordering, and both halves are
67
67
  * `encodeURIComponent`-encoded. Returns `url` unchanged when `params` is empty.
68
+ * A `data:` URL has no query component, so parameters are not appended;
69
+ * a `?` would corrupt the inline document.
68
70
  */
69
71
  export declare function appendQueryParameters(url: string, params: Record<string, string> | undefined): string;
@@ -1 +1 @@
1
- export const OPTIVIEW_MACRO_PREFIX="$OPTIVIEW_";export const OPTIVIEW_ASSET_PARAMETER_MACROS={USER_AGENT:"$OPTIVIEW_USER_AGENT$",PLAYER_WIDTH:"$OPTIVIEW_PLAYER_WIDTH$",PLAYER_HEIGHT:"$OPTIVIEW_PLAYER_HEIGHT$",BREAK_DURATION_MS:"$OPTIVIEW_BREAK_DURATION_MS$",BREAK_DURATION_SECONDS:"$OPTIVIEW_BREAK_DURATION_SECONDS$"};export const OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES={[OPTIVIEW_ASSET_PARAMETER_MACROS.USER_AGENT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_WIDTH]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_HEIGHT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_MS]:"break",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_SECONDS]:"break"};export function omitBreakScopedParameters(e){if(!e)return;const t=Object.fromEntries(Object.entries(e).filter(([,e])=>!Object.entries(OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES).some(([t,n])=>"break"===n&&e.includes(t))));return Object.keys(t).length>0?t:void 0}export function resolveAssetParameterMacro(e){return"function"==typeof e?e():e}export function applyAssetParameterMacros(t,n){const E=t??{},o={},r=[];for(const t of Object.keys(E).sort()){const s=e(E[t],n);o[t]=s.value,r.push(...s.unresolved.map(e=>({key:t,macro:e})))}return{params:Object.keys(E).length>0?o:void 0,unresolved:r}}function e(e,t){let E="",o=0;const r=[],s=new Set;for(;o<e.length;){const A=e.indexOf("$",o);if(-1===A){E+=e.slice(o);break}E+=e.slice(o,A);const _=n(e,A);if(void 0===_){E+="$",o=A+1;continue}const R=t[_]?.();void 0!==R?E+=R:(E+=_,!_.startsWith("$OPTIVIEW_")&&!(_ in t)||s.has(_)||(r.push(_),s.add(_))),o=A+_.length}return{value:E,unresolved:r}}function t(e){return e>="A"&&e<="Z"||e>="a"&&e<="z"||e>="0"&&e<="9"||"_"===e}function n(e,n){let E=n+1;for(;E<e.length&&t(e[E]);)E++;return E>n+1&&"$"===e[E]?e.slice(n,E+1):void 0}export function appendQueryParameters(e,t){const n=t??{},E=Object.keys(n).sort();if(0===E.length)return e;const o=E.map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(n[e])}`).join("&"),r=e.indexOf("#"),s=-1===r?e:e.slice(0,r),A=-1===r?"":e.slice(r),_=s.includes("?")?s.endsWith("?")||s.endsWith("&")?"":"&":"?";return`${s}${_}${o}${A}`}
1
+ export const OPTIVIEW_MACRO_PREFIX="$OPTIVIEW_";export const OPTIVIEW_ASSET_PARAMETER_MACROS={USER_AGENT:"$OPTIVIEW_USER_AGENT$",PLAYER_WIDTH:"$OPTIVIEW_PLAYER_WIDTH$",PLAYER_HEIGHT:"$OPTIVIEW_PLAYER_HEIGHT$",BREAK_DURATION_MS:"$OPTIVIEW_BREAK_DURATION_MS$",BREAK_DURATION_SECONDS:"$OPTIVIEW_BREAK_DURATION_SECONDS$"};export const OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES={[OPTIVIEW_ASSET_PARAMETER_MACROS.USER_AGENT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_WIDTH]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_HEIGHT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_MS]:"break",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_SECONDS]:"break"};export function omitBreakScopedParameters(e){if(!e)return;const t=Object.fromEntries(Object.entries(e).filter(([,e])=>!Object.entries(OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES).some(([t,n])=>"break"===n&&e.includes(t))));return Object.keys(t).length>0?t:void 0}export function resolveAssetParameterMacro(e){return"function"==typeof e?e():e}export function applyAssetParameterMacros(t,n){const E=t??{},o={},r=[];for(const t of Object.keys(E).sort()){const s=e(E[t],n);o[t]=s.value,r.push(...s.unresolved.map(e=>({key:t,macro:e})))}return{params:Object.keys(E).length>0?o:void 0,unresolved:r}}function e(e,t){let E="",o=0;const r=[],s=new Set;for(;o<e.length;){const A=e.indexOf("$",o);if(-1===A){E+=e.slice(o);break}E+=e.slice(o,A);const _=n(e,A);if(void 0===_){E+="$",o=A+1;continue}const R=t[_]?.();void 0!==R?E+=R:(E+=_,!_.startsWith("$OPTIVIEW_")&&!(_ in t)||s.has(_)||(r.push(_),s.add(_))),o=A+_.length}return{value:E,unresolved:r}}function t(e){return e>="A"&&e<="Z"||e>="a"&&e<="z"||e>="0"&&e<="9"||"_"===e}function n(e,n){let E=n+1;for(;E<e.length&&t(e[E]);)E++;return E>n+1&&"$"===e[E]?e.slice(n,E+1):void 0}export function appendQueryParameters(e,t){const n=t??{},E=Object.keys(n).sort();if(0===E.length)return e;if(/^data:/i.test(e))return e;const o=E.map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(n[e])}`).join("&"),r=e.indexOf("#"),s=-1===r?e:e.slice(0,r),A=-1===r?"":e.slice(r),_=s.includes("?")?s.endsWith("?")||s.endsWith("&")?"":"&":"?";return`${s}${_}${o}${A}`}
@@ -1 +1 @@
1
- import{BreakFormat as e}from"../types/BreakManifest";import{playbackErrorContext as t}from"../diagnostics/playbackErrorContext";export class BreakLifecycle{constructor(e,t){this.state=e,this.deps=t,this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null}emitAdBreakBegin(e,t){this.breakBeginEmittedId!==e.id&&(this.breakBeginEmittedId=e.id,this.pendingAdBreakBegin=null,this.deps.emit("adbreakbegin",{type:"adbreakbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),elapsedSec:t?.elapsedSec??0,remainingSec:t?.remainingSec??e.duration??0,...t?.tuneIn?{tuneIn:{elapsedSec:t.elapsedSec,remainingSec:t.remainingSec}}:{}}))}flushPendingAdBreakBegin(){this.pendingAdBreakBegin?.()}async onBreakStart(a,i){if(this.state.snapbackSeekInFlight=!1,this.deps.adPlayerController.isAdPlaying())return this.deps.diagnose("DA-BREAK-SUPPRESSED-OVERLAP",`Break suppressed (ad already playing): ${a.id}`,{breakId:a.id}),this.deps.breakScheduler.completeBreak(a.id),void this.deps.onBreakSettled?.();this.deps.adPlayerController.diagnoseUnknownVariantFormats(a);const d=this.deps.adPlayerController.breakNothingToPlayReason(a);if(d)return this.deps.log("Break not begun (no playable variant):",a.id,d),this.deps.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`break ${a.id} has no playable variant (${d}); it will not begin.`,{breakId:a.id,reason:d}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:a,format:this.deps.effectiveFormatOf(a),error:new Error(`Break ${a.id} has no playable variant (${d})`)}),this.deps.breakScheduler.completeBreak(a.id),void this.deps.onBreakSettled?.();this.deps.log("Break starting:",a.id,i.tuneIn?"(tune-in)":""),this.deps.breakTransitionTimer?.mark("into-break",a.id),this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null;const r=this.state.heldImmediatePreRollId===a.id,s=this.deps.effectiveFormatOf(a),n=s===e.SINGLE||s===e.LSHAPE_AD,o=r&&n;r&&(this.state.heldImmediatePreRollId=null),this.deps.adBreakStatusTracker.setAdBreakStatusActive(a,void 0!==i.remainingSec?i.remainingSec:a.duration),o||this.emitAdBreakBegin(a,i),this.deps.manifestService.setActive(!0),this.deps.breakScheduler.claimChain(a.id);const k={onAdStart:(e,t,a,i,d)=>{this.deps.adBreakStatusTracker.onAdStarted(e,t,a,i),this.deps.emit("adbegin",{type:"adbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...d?.adId?{adId:d.adId}:{},...d?.creativeId?{creativeId:d.creativeId}:{},...void 0!==d?.podPosition?{podPosition:d.podPosition}:{}})},onAdEnd:(e,t,a,i,d)=>{this.deps.adBreakStatusTracker.onAdEnded(e),a+1<i&&this.deps.breakTransitionTimer?.mark("ad-to-ad",e.id),this.deps.emit("adend",{type:"adend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...d?.adId?{adId:d.adId}:{},...d?.creativeId?{creativeId:d.creativeId}:{}})},onAdError:(e,a,i)=>{this.flushPendingAdBreakBegin(),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",i.message,{breakId:e.id,assetId:a?.id,...t(i)}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:e,format:this.deps.effectiveFormatOf(e),...a?{asset:a}:{},error:i})},onAdTimeUpdate:(e,t,a,i)=>{this.deps.adBreakStatusTracker.onAdTimeUpdated(e,a,i),this.deps.emit("adtimeupdate",{type:"adtimeupdate",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,currentTime:a,duration:i})},onAdWaiting:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackWaiting(),this.deps.emit("waiting",{type:"waiting",timestamp:Date.now(),source:"ad",break:e,asset:t})},onAdPlaying:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackPlaying(),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"ad",break:e,asset:t})},onBreakEnd:e=>{this.onBreakEnd(e)},onBreakEndedWithoutContentPause:()=>{this.onBreakEndedWithoutContentPause()},onChainedBreakBegin:e=>{this.onChainedBreakBegin(e)},onChainedBreakSkipped:e=>{this.deps.log("Chained break skipped (no playable variant):",e.id),this.deps.breakScheduler.completeBreak(e.id)},onContentResumeSeek:(e,t)=>{this.deps.breakScheduler.notifyResumeSeek(e,t)},onContentReloading:e=>{this.deps.breakScheduler.setContentReloading(e)}};o&&(this.pendingAdBreakBegin=()=>this.emitAdBreakBegin(a,i),k.onBreakVisible=e=>{this.deps.adBreakStatusTracker.setAdBreakVisible(e),this.flushPendingAdBreakBegin()}),await this.deps.adPlayerController.playBreak(a,k,{effectiveDurationSec:i.remainingSec,firstFrameGate:o})}onChainedBreakBegin(e){this.deps.log("Chained break starting:",e.id),this.deps.breakTransitionTimer?.mark("break-to-break",e.id),this.deps.breakScheduler.activateChainedBreak(e.id),this.deps.manifestService.setActive(!0),this.deps.adBreakStatusTracker.setAdBreakStatusActive(e,e.duration),this.emitAdBreakBegin(e)}onBreakEnd(e){this.deps.log("Break ended:",e.id),this.deps.breakTransitionTimer?.mark("out-of-break",e.id),this.deps.breakScheduler.completeBreak(e.id),this.deps.manifestService.setActive(!1),this.deps.adBreakStatusTracker.resetAdBreakStatus(),this.flushPendingAdBreakBegin(),this.deps.emit("adbreakend",{type:"adbreakend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e)}),this.deps.onBreakSettled?.()}onBreakEndedWithoutContentPause(){this.deps.breakTransitionTimer?.settle("out-of-break"),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"content"})}onBreakAborted(e){if(this.deps.adPlayerController.isAdPlaying()){const t=this.deps.adBreakStatusTracker.currentBreakElapsedSec();this.deps.log(`Early return — ending break '${e.id}' at ${t.toFixed(2)}s`),this.deps.cmcdAdReporter.onBreakAborted(e.id),this.deps.adPlayerController.abortBreak(t)}else this.deps.breakScheduler.completeBreak(e.id),this.deps.onBreakSettled?.()}reportAbandonedBreak(e){const t=this.deps.adBreakStatusTracker.activeBreak;if(!t)return;const a=this.deps.adBreakStatusTracker.currentAdIndex;this.deps.diagnose("DA-BREAK-ABANDONED",`Session torn down (${e}) while break ${t.id} was still in flight`+(null!==a?"; an ad began and will never end.":"."),{breakId:t.id,cause:e,phase:this.deps.adBreakStatusTracker.adBreakStatus.phase,unbalancedAdBegin:null!==a,...null!==a?{adIndex:a}:{},...null!==this.deps.adBreakStatusTracker.currentAdTotal?{totalAds:this.deps.adBreakStatusTracker.currentAdTotal}:{}})}}
1
+ import{BreakFormat as e}from"../types/BreakManifest";import{playbackErrorContext as t}from"../diagnostics/playbackErrorContext";export class BreakLifecycle{constructor(e,t){this.state=e,this.deps=t,this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null}emitAdBreakBegin(e,t){this.breakBeginEmittedId!==e.id&&(this.breakBeginEmittedId=e.id,this.pendingAdBreakBegin=null,this.deps.emit("adbreakbegin",{type:"adbreakbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),elapsedSec:t?.elapsedSec??0,remainingSec:t?.remainingSec??e.duration??0,...t?.tuneIn?{tuneIn:{elapsedSec:t.elapsedSec,remainingSec:t.remainingSec}}:{}}))}flushPendingAdBreakBegin(){this.pendingAdBreakBegin?.()}async onBreakStart(a,i){if(this.state.snapbackSeekInFlight=!1,this.deps.adPlayerController.isAdPlaying())return this.deps.diagnose("DA-BREAK-SUPPRESSED-OVERLAP",`Break suppressed (ad already playing): ${a.id}`,{breakId:a.id}),this.deps.breakScheduler.completeBreak(a.id),void this.deps.onBreakSettled?.();this.deps.adPlayerController.diagnoseUnknownVariantFormats(a);const d=this.deps.adPlayerController.breakNothingToPlayReason(a);if(d)return this.deps.log("Break not begun (no playable variant):",a.id,d),this.deps.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`break ${a.id} has no playable variant (${d}); it will not begin.`,{breakId:a.id,reason:d}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:a,format:this.deps.effectiveFormatOf(a),error:new Error(`Break ${a.id} has no playable variant (${d})`)}),this.deps.breakScheduler.completeBreak(a.id),void this.deps.onBreakSettled?.();this.deps.log("Break starting:",a.id,i.tuneIn?"(tune-in)":""),this.deps.breakTransitionTimer?.mark("into-break",a.id),this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null;const r=this.state.heldImmediatePreRollId===a.id,s=this.deps.effectiveFormatOf(a),n=s===e.SINGLE||s===e.LSHAPE_AD,o=r&&n;r&&(this.state.heldImmediatePreRollId=null),this.deps.adBreakStatusTracker.setAdBreakStatusActive(a,void 0!==i.remainingSec?i.remainingSec:a.duration),o||this.emitAdBreakBegin(a,i),this.deps.manifestService.setActive(!0),this.deps.breakScheduler.claimChain(a.id);const k={onAdStart:(e,t,a,i,d)=>{this.deps.adBreakStatusTracker.onAdStarted(e,t,a,i),this.deps.emit("adbegin",{type:"adbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...d?.adId?{adId:d.adId}:{},...d?.creativeId?{creativeId:d.creativeId}:{},...void 0!==d?.podPosition?{podPosition:d.podPosition}:{}})},onAdEnd:(e,t,a,i,d)=>{this.deps.adBreakStatusTracker.onAdEnded(e),a+1<i&&this.deps.breakTransitionTimer?.mark("ad-to-ad",e.id),this.deps.emit("adend",{type:"adend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...d?.adId?{adId:d.adId}:{},...d?.creativeId?{creativeId:d.creativeId}:{}})},onAdError:(e,a,i)=>{this.flushPendingAdBreakBegin(),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",i.message,{breakId:e.id,assetId:a?.id,...t(i)}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:e,format:this.deps.effectiveFormatOf(e),...a?{asset:a}:{},error:i})},onAdTimeUpdate:(e,t,a,i)=>{this.deps.adBreakStatusTracker.onAdTimeUpdated(e,a,i),this.deps.emit("adtimeupdate",{type:"adtimeupdate",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,currentTime:a,duration:i})},onAdWaiting:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackWaiting(),this.deps.emit("waiting",{type:"waiting",timestamp:Date.now(),source:"ad",break:e,asset:t})},onAdPlaying:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackPlaying(),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"ad",break:e,asset:t})},onBreakEnd:e=>{this.onBreakEnd(e)},onBreakCut:e=>{this.deps.cmcdAdReporter.onBreakCut(e.id)},onBreakEndedWithoutContentPause:()=>{this.onBreakEndedWithoutContentPause()},onChainedBreakBegin:e=>{this.onChainedBreakBegin(e)},onChainedBreakSkipped:e=>{this.deps.log("Chained break skipped (no playable variant):",e.id),this.deps.breakScheduler.completeBreak(e.id)},onContentResumeSeek:(e,t)=>{this.deps.breakScheduler.notifyResumeSeek(e,t)},onContentReloading:e=>{this.deps.breakScheduler.setContentReloading(e)}};o&&(this.pendingAdBreakBegin=()=>this.emitAdBreakBegin(a,i),k.onBreakVisible=e=>{this.deps.adBreakStatusTracker.setAdBreakVisible(e),this.flushPendingAdBreakBegin()}),await this.deps.adPlayerController.playBreak(a,k,{effectiveDurationSec:i.remainingSec,firstFrameGate:o})}onChainedBreakBegin(e){this.deps.log("Chained break starting:",e.id),this.deps.breakTransitionTimer?.mark("break-to-break",e.id),this.deps.breakScheduler.activateChainedBreak(e.id),this.deps.manifestService.setActive(!0),this.deps.adBreakStatusTracker.setAdBreakStatusActive(e,e.duration),this.emitAdBreakBegin(e)}onBreakEnd(e){this.deps.log("Break ended:",e.id),this.deps.breakTransitionTimer?.mark("out-of-break",e.id),this.deps.breakScheduler.completeBreak(e.id),this.deps.manifestService.setActive(!1),this.deps.adBreakStatusTracker.resetAdBreakStatus(),this.flushPendingAdBreakBegin(),this.deps.emit("adbreakend",{type:"adbreakend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e)}),this.deps.onBreakSettled?.()}onBreakEndedWithoutContentPause(){this.deps.breakTransitionTimer?.settle("out-of-break"),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"content"})}onBreakAborted(e){if(this.deps.adPlayerController.isAdPlaying()){const t=this.deps.adBreakStatusTracker.currentBreakElapsedSec();this.deps.log(`Early return — ending break '${e.id}' at ${t.toFixed(2)}s`),this.deps.cmcdAdReporter.onBreakAborted(e.id),this.deps.adPlayerController.abortBreak(t)}else this.deps.breakScheduler.completeBreak(e.id),this.deps.onBreakSettled?.()}reportAbandonedBreak(e){const t=this.deps.adBreakStatusTracker.activeBreak;if(!t)return;const a=this.deps.adBreakStatusTracker.currentAdIndex;this.deps.diagnose("DA-BREAK-ABANDONED",`Session torn down (${e}) while break ${t.id} was still in flight`+(null!==a?"; an ad began and will never end.":"."),{breakId:t.id,cause:e,phase:this.deps.adBreakStatusTracker.adBreakStatus.phase,unbalancedAdBegin:null!==a,...null!==a?{adIndex:a}:{},...null!==this.deps.adBreakStatusTracker.currentAdTotal?{totalAds:this.deps.adBreakStatusTracker.currentAdTotal}:{}})}}
@@ -49,6 +49,16 @@ export declare class SessionStarter {
49
49
  * anchor (spec `event: 'start'`) is then the session start itself.
50
50
  */
51
51
  private markPlaybackStartIfPlaying;
52
+ private contentIsReady;
53
+ private clearHeldImmediatePreRollArmInterval;
54
+ private armHeldImmediatePreRoll;
55
+ private armHeldImmediatePreRollIfTimedOut;
56
+ /**
57
+ * Content errors can arrive before the held pre-roll is armed. Cancel that pending break so
58
+ * an erroring content load never reveals an ad that was scheduled only for the failed stream.
59
+ */
60
+ cancelHeldImmediatePreRollOnContentError(): boolean;
61
+ clearHeldImmediatePreRollArm(): void;
52
62
  /**
53
63
  * Tear down a session that failed to start and report it. `hostOk`/`hostFail` say whether this
54
64
  * host was serving this client moments ago — a backend to escalate vs a network path to fix.
@@ -1 +1 @@
1
- import{getPreRollDelay as e}from"../types/BreakManifest";import{manifestHasGamAssets as s,resolveGamVendorConfiguration as t}from"../services/resolveGamVendorConfiguration";import{SsaiController as i}from"../services/SsaiController";import{resolveUrlOrigin as a}from"../services/urlOrigin";import{filterSsaiClientBreaks as r,resolveStitchedStreamVendor as n,SsaiUnavailableError as o}from"../services/resolveDeliveryMode";import{detectDeviceType as d}from"../services/detectDeviceType";import{decideDeliveryMode as l,detectChannelSwitch as h,gamPodIdentityChanged as c}from"../services/channelSwitch";function p(e){return"stitcherUrl"in e}function S(e){return"string"==typeof e&&""!==e.trim()}export class SessionStarter{constructor(e,s){this.state=e,this.deps=s}async startSession(s){if("ssai"===this.deps.mode){if(!p(s)||!S(s.stitcherUrl))throw new Error('[OptiViewAds] mode "ssai" requires a stitcherUrl in startSession().')}else if(p(s)||!S(s.manifestUrl))throw new Error("[OptiViewAds] startSession requires a manifestUrl (the full break-manifest URL).");this.state.sessionActive&&(this.deps.log("Ending previous session before starting new one"),this.deps.endSessionInternal("session-restart"));const t=this.state.sessionGeneration;if(this.state.sessionActive=!0,this.deps.lensReporter.startSession(),p(s))return s.adInsertionType&&this.deps.diagnose("DA-INSERTION-TYPE-IGNORED-SSAI",`adInsertionType "${s.adInsertionType}" ignored in SSAI mode; the stitcher controls ad insertion.`,{adInsertionType:s.adInsertionType}),void await this.startSsaiSession(s,t);const i=s.manifestUrl;this.state.sessionConfig=s,this.state.manifestOrigin=a(i),this.deps.manifestService.setManifestUrl(i),this.deps.log("Session starting, manifest URL:",i);const r=Date.now(),n=e=>{this.deps.diagnose("DA-SESSION-STARTING",`Session start: ${e}`,{step:e,manifestUrl:i,elapsedMs:Date.now()-r})};n("begin");const o=s.assetParameters;let d;this.deps.adPlayerController.setSessionAssetParameters(o),this.deps.adPlayerController.setSessionAssetParameterMacros(s.assetParameterMacros),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!o}),n("manifest-fetch");try{d=await this.deps.manifestService.fetchManifest()}catch(e){if(this.isStaleStart(t))return;throw this.failSessionStart(e)}if(this.deps.cmcdAdReporter.onSessionStart(d),!this.isStaleStart(t))if(this.state.sessionManifest=d,"ssai"!==this.resolveSessionDeliveryMode(d))try{if(await this.startGamForManifest(d,o,n),this.isStaleStart(t))return;const a=s.adInsertionType;this.deps.adPlayerController.setBreakResumePolicy(a,d.timebase,this.deps.continueContentDuringBreak),this.deps.diagnose("DA-INSERTION-TYPE-RESOLVED",void 0===a?`Ad insertion type: unset — the manifest's resumeOffset drives the resume point (timebase: ${d.timebase})`:`Ad insertion type: ${a}, client-set (timebase: ${d.timebase})`,{adInsertionType:a??null,source:void 0===a?"unset":"client",timebase:d.timebase}),n("scheduling"),this.deps.breakScheduler.updateManifest(d),this.deps.pauseAdController.updateManifest(d),this.state.pauseAdTickInterval=setInterval(()=>this.deps.pauseAdController?.tick(),250);const r=(d.adStartDelay??0)>0?void 0:d.breaks.find(s=>0===e(s));r?(this.deps.adPlayerController.holdContentForPreRoll(r),this.state.heldImmediatePreRollId=r.id,this.deps.breakScheduler.markPlaybackStart()):this.markPlaybackStartIfPlaying(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`Session active for manifest ${i}`,{manifestUrl:i}),this.deps.log("Session active")}catch(e){if(this.isStaleStart(t))return;throw this.failSessionStart(e)}else await this.startSteeredSsaiSession(d,s,n)}markPlaybackStartIfPlaying(){this.deps.player.paused||this.deps.breakScheduler.markPlaybackStart()}failSessionStart(e){const s=e instanceof Error?e:new Error(String(e)),t=this.deps.manifestService?.takeLastFailure();return this.deps.diagnose("DA-MANIFEST-FETCH-FAILED",s.message,t?{...t}:void 0),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),s}resolveSessionDeliveryMode(e,s="manifest"){const t=this.state.sessionDeliveryMode,i=l(e,d(),this.deps.hasExplicitMode?this.deps.mode:null,s);return null!==i.unknownMode&&this.deps.diagnose("DA-DELIVERY-MODE-UNKNOWN",`Delivery rule ${i.ruleIndex} carries unrecognized mode "${i.unknownMode}"; using sgai.`,{unknownMode:i.unknownMode,ruleIndex:i.ruleIndex}),this.deps.hasExplicitMode&&i.manifestMode!==i.mode&&this.deps.diagnose("DA-DELIVERY-MODE-OVERRIDDEN",`Manifest delivery rules resolve "${i.manifestMode}" but the explicit config mode "${i.mode}" takes precedence.`,{manifestMode:i.manifestMode,configuredMode:i.mode}),this.state.sessionDeliveryMode=i.mode,this.deps.diagnose("DA-DELIVERY-MODE-RESOLVED",`Delivery mode: ${i.mode}`,{mode:i.mode,ruleIndex:i.ruleIndex,source:i.source,..."channel-switch"===s?{previousMode:t}:{}}),i.mode}async startSteeredSsaiSession(e,s,t){const i=n(e.vendorConfiguration);if(!i){const e="Manifest selected ssai delivery but no vendor configuration provides a stitched stream (no gam entry with an assetKey).";throw this.deps.diagnose("DA-SSAI-UNAVAILABLE",e),this.deps.manifestService.reset(),this.state.sessionActive=!1,new o(e)}try{t("ssai-live-stream"),this.state.steeredSsaiActive=!0,this.state.ssaiController=this.createSsaiController(i.networkCode),await this.state.ssaiController.startSteered(i,s.assetParameters,s.assetParameterMacros),t("scheduling");const a=r(e);this.deps.adPlayerController.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),this.deps.breakScheduler.updateManifest(a),this.deps.pauseAdController.updateManifest(a),this.state.pauseAdTickInterval=setInterval(()=>this.deps.pauseAdController?.tick(),250),this.markPlaybackStartIfPlaying(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active (manifest-steered) for ${s.manifestUrl}`,{manifestUrl:s.manifestUrl}),this.deps.log("SSAI session active (manifest-steered)")}catch(e){const s=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",s.message),this.state.steeredSsaiActive=!1,this.deps.manifestService.reset(),this.state.sessionActive=!1,s}}createSsaiController(e){return new i(this.deps.player,{gam:this.state.steeredSsaiOptions?.streamActivityMonitorId?{streamActivityMonitorId:this.state.steeredSsaiOptions.streamActivityMonitorId}:{},ssai:{networkCode:e},maxBitrate:this.state.steeredSsaiOptions?.maxBitrate,autoplay:this.state.steeredSsaiOptions?.autoplay,debug:this.deps.debug},{emit:(e,s)=>this.deps.emit(e,s),diagnose:(e,s,t)=>this.deps.diagnose(e,s,t),log:(...e)=>this.deps.log(...e)})}startBreakScheduler(){this.deps.breakScheduler.start((e,s)=>{this.deps.breakLifecycle.onBreakStart(e,s)},e=>!this.deps.adPlayerController.isAdPlaying()&&(this.deps.adPlayerController.preload(e).catch(s=>{this.deps.diagnose("DA-PRELOAD-FAILED",s instanceof Error?s.message:String(s),{breakId:e.id})}),!0),(e,s,t)=>{this.deps.adBreakStatusTracker.onBreakWarning(e,s,t)},e=>{this.deps.breakLifecycle.onBreakAborted(e)})}startManifestPolling(){this.deps.manifestService.startPolling(e=>{if(!this.state.sessionActive)return;const s=this.deps.notedManifestBreakIds(e);this.deps.diagnose("DA-MANIFEST-POLLED","Manifest polled",{breaks:e.breaks.length,newBreakIds:s,pollingIntervalSeconds:this.deps.manifestService.currentPollingIntervalSeconds});const t=this.state.sessionManifest,i=t?h(t,e):null;if(this.state.pendingChannelSwitch||i&&this.deps.breakScheduler.getActiveBreak())return this.state.pendingChannelSwitch=e,void this.deps.log("Channel switch deferred until the active break ends");i?this.switchChannel(e,i):this.applyManifestUpdate(e)},e=>{this.state.sessionActive&&this.deps.diagnose("DA-MANIFEST-POLL-FAILED",e.message)})}applyPendingChannelSwitch(){if(!this.state.sessionActive||!this.state.pendingChannelSwitch)return;const e=this.state.pendingChannelSwitch;this.state.pendingChannelSwitch=null;const s=h(this.state.sessionManifest,e);s?this.switchChannel(e,s):this.applyManifestUpdate(e)}applyManifestUpdate(e){this.diagnoseIgnoredDeliveryModeChange(e);const s=t(e);this.state.steeredSsaiActive?this.state.ssaiController?.setManifestAssetParameters(s.ssai?.assetParameters):this.deps.adPlayerController.setManifestAssetParameters(s.sgaiAssetParameters);const i=this.state.steeredSsaiActive?r(e):e;this.deps.breakScheduler.updateManifest(i),this.deps.pauseAdController.updateManifest(i)}diagnoseIgnoredDeliveryModeChange(e){if(!this.state.sessionDeliveryMode)return;const s=l(e,d(),null,"manifest");s.manifestMode!==this.state.sessionDeliveryMode&&this.deps.diagnose("DA-DELIVERY-MODE-CHANGE-IGNORED",`Polled manifest resolves delivery mode "${s.manifestMode}" but "${this.state.sessionDeliveryMode}" is pinned for this session.`,{polledMode:s.manifestMode,pinnedMode:this.state.sessionDeliveryMode})}async switchChannel(e,s){if(this.state.channelSwitchInFlight)return;const t=this.state.sessionConfig,i=this.state.sessionManifest,a={previousChannelId:s.previousChannelId,channelId:s.channelId,reason:s.reason,manifestUrl:t.manifestUrl};this.deps.diagnose("DA-MANIFEST-CHANNEL-CHANGED",`Manifest channel changed (${s.reason}): ${s.previousChannelId??"none"} -> ${s.channelId??"none"}`,a),this.state.sessionManifest=e,this.state.channelSwitchInFlight=!0;const r=this.state.sessionGeneration,n=this.state.sessionDeliveryMode??"sgai";try{let s=this.resolveSessionDeliveryMode(e,"channel-switch");if(this.deps.cmcdAdReporter.onSessionEnd(),this.deps.cmcdAdReporter.onSessionStart(e),this.deps.lensReporter.endSession(),this.deps.lensReporter.startSession(),"ssai"===s&&"sgai"===n){const a=await this.switchToSteeredSsai(e,t);if(this.isStaleStart(r))return;a||(s="sgai",this.state.sessionDeliveryMode="sgai",await this.applySameModeChannelSwitch(i,e,t,r))}else"sgai"===s&&"ssai"===n?await this.switchToSgai(e,t,r):await this.applySameModeChannelSwitch(i,e,t,r);if(this.isStaleStart(r))return}finally{this.state.channelSwitchInFlight=!1}this.emitChannelChange({...a,previousDeliveryMode:n,deliveryMode:this.state.sessionDeliveryMode??n})}emitChannelChange(e){this.deps.emit("adchannelchange",{type:"adchannelchange",timestamp:Date.now(),...e})}async applySameModeChannelSwitch(e,s,i,a){const n=this.state.steeredSsaiActive,o=n?r(s):s;if(this.deps.breakScheduler.resetForChannelSwitch(o),this.deps.pauseAdController.updateManifest(o),this.deps.adPlayerController.setBreakResumePolicy(i.adInsertionType,s.timebase,this.deps.continueContentDuringBreak),!n&&c(e,s)){const e=this.deps.adPlayerController.updatedAssetParameters;if(this.deps.adPlayerController.endGamSession(),this.deps.adPlayerController.setSessionAssetParameters(i.assetParameters),this.deps.adPlayerController.setSessionAssetParameterMacros(i.assetParameterMacros),await this.startGamForManifest(s,i.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:i.manifestUrl})}),this.isStaleStart(a))return;e&&this.deps.adPlayerController.updateAssetParameters(e)}else if(n){const e=t(s);this.state.ssaiController?.setManifestAssetParameters(e.ssai?.assetParameters)}else{const e=t(s);this.deps.adPlayerController.setManifestAssetParameters(e.sgaiAssetParameters)}}async switchToSteeredSsai(e,s){const t=n(e.vendorConfiguration);if(!t)return this.deps.diagnose("DA-SSAI-UNAVAILABLE","Channel switch resolved ssai delivery but no vendor configuration provides a stitched stream; the session stays in sgai.",{manifestUrl:s.manifestUrl,previousMode:"sgai"}),!1;const i=this.deps.adPlayerController,a=i.updatedAssetParameters;i.stopActiveBreak(),i.releaseContentHold(),i.endGamSession(),i.setSessionAssetParameters(s.assetParameters),i.setSessionAssetParameterMacros(s.assetParameterMacros),a&&i.updateAssetParameters(a);const o=this.createSsaiController(t.networkCode);this.state.ssaiController=o,this.state.steeredSsaiActive=!0,this.deps.diagnose("DA-SESSION-STARTING","Channel switch: ssai-live-stream",{step:"ssai-live-stream",manifestUrl:s.manifestUrl});try{await o.startSteered(t,s.assetParameters,s.assetParameterMacros),a&&o.updateAssetParameters(a);const n=r(e);return this.deps.breakScheduler.resetForChannelSwitch(n),this.deps.pauseAdController.updateManifest(n),i.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),!0}catch(e){const s=e instanceof Error?e.message:String(e);return this.deps.diagnose("DA-SSAI-SESSION-FAILED",s),o.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,!1}}async switchToSgai(e,s,t){this.state.ssaiController?.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,this.deps.breakScheduler.resetForChannelSwitch(e),this.deps.pauseAdController.updateManifest(e);const i=this.deps.adPlayerController;i.setBreakResumePolicy(s.adInsertionType,e.timebase,this.deps.continueContentDuringBreak);const a=i.updatedAssetParameters;i.endGamSession(),i.setSessionAssetParameters(s.assetParameters),i.setSessionAssetParameterMacros(s.assetParameterMacros),await this.startGamForManifest(e,s.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:s.manifestUrl})}),this.isStaleStart(t)||a&&i.updateAssetParameters(a)}async startGamForManifest(e,i,a){const r=t(e);if(this.deps.adPlayerController.setManifestAssetParameters(r.sgaiAssetParameters),r.unknownTypes.length>0&&this.deps.diagnose("DA-VENDOR-CONFIG-UNKNOWN-TYPE",`Ignored ${r.unknownTypes.length} vendorConfiguration `+(1===r.unknownTypes.length?"entry":"entries")+" with unrecognised type: "+`${r.unknownTypes.join(", ")}.`,{types:r.unknownTypes}),this.deps.adPlayerController.isGamEnabled())if(r.identity){this.deps.diagnose("DA-GAM-CONFIG-RESOLVED",`GAM session identity: network ${r.identity.networkCode}, custom asset ${r.identity.customAssetKey}`,{networkCode:r.identity.networkCode,customAssetKey:r.identity.customAssetKey,channelId:e.channelId,duplicateEntriesIgnored:r.duplicateGam});try{a("gam-init"),await this.deps.adPlayerController.startGamSession(r.identity,i),this.deps.log("GAM session started")}catch(e){this.deps.diagnose("DA-GAM-SESSION-FAILED",e instanceof Error?e.message:String(e))}}else s(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING",'The break manifest carries GAM pod assets but no usable "gam" vendorConfiguration entry; those assets will be skipped.',{reason:r.reason});else s(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING","The break manifest carries GAM pod assets but GAM is disabled for this session (gam.enabled === false); those assets will be skipped.",{reason:"gam-disabled"})}async startSsaiSession(e,s){try{if(this.state.stitcherOrigin=a(e.stitcherUrl),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!e.assetParameters}),await this.state.ssaiController.start(e),this.isStaleStart(s))return;this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active for ${e.stitcherUrl}`,{stitcherUrl:e.stitcherUrl}),this.deps.log("SSAI session active")}catch(e){if(this.isStaleStart(s))return;const t=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",t.message),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),t}}isStaleStart(e){return e!==this.state.sessionGeneration&&(this.deps.log("Session start aborted — the session ended or was replaced while starting"),!0)}}
1
+ import{getPreRollDelay as e}from"../types/BreakManifest";import{manifestHasGamAssets as s,resolveGamVendorConfiguration as t}from"../services/resolveGamVendorConfiguration";import{SsaiController as i}from"../services/SsaiController";import{resolveUrlOrigin as a}from"../services/urlOrigin";import{filterSsaiClientBreaks as r,resolveStitchedStreamVendor as n,SsaiUnavailableError as o}from"../services/resolveDeliveryMode";import{detectDeviceType as d}from"../services/detectDeviceType";import{decideDeliveryMode as l,detectChannelSwitch as h,gamPodIdentityChanged as c}from"../services/channelSwitch";const m=1e4;function p(e){return"stitcherUrl"in e}function S(e){return"string"==typeof e&&""!==e.trim()}export class SessionStarter{constructor(e,s){this.state=e,this.deps=s}async startSession(s){if("ssai"===this.deps.mode){if(!p(s)||!S(s.stitcherUrl))throw new Error('[OptiViewAds] mode "ssai" requires a stitcherUrl in startSession().')}else if(p(s)||!S(s.manifestUrl))throw new Error("[OptiViewAds] startSession requires a manifestUrl (the full break-manifest URL).");this.state.sessionActive&&(this.deps.log("Ending previous session before starting new one"),this.deps.endSessionInternal("session-restart"));const t=this.state.sessionGeneration;if(this.state.sessionActive=!0,this.deps.lensReporter.startSession(),p(s))return s.adInsertionType&&this.deps.diagnose("DA-INSERTION-TYPE-IGNORED-SSAI",`adInsertionType "${s.adInsertionType}" ignored in SSAI mode; the stitcher controls ad insertion.`,{adInsertionType:s.adInsertionType}),void await this.startSsaiSession(s,t);const i=s.manifestUrl;this.state.sessionConfig=s,this.state.manifestOrigin=a(i),this.deps.manifestService.setManifestUrl(i),this.deps.log("Session starting, manifest URL:",i);const r=Date.now(),n=e=>{this.deps.diagnose("DA-SESSION-STARTING",`Session start: ${e}`,{step:e,manifestUrl:i,elapsedMs:Date.now()-r})};n("begin");const o=s.assetParameters;let d;this.deps.adPlayerController.setSessionAssetParameters(o),this.deps.adPlayerController.setSessionAssetParameterMacros(s.assetParameterMacros),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!o}),n("manifest-fetch");try{d=await this.deps.manifestService.fetchManifest()}catch(e){if(this.isStaleStart(t))return;throw this.failSessionStart(e)}if(this.deps.cmcdAdReporter.onSessionStart(d),!this.isStaleStart(t))if(this.state.sessionManifest=d,"ssai"!==this.resolveSessionDeliveryMode(d))try{if(await this.startGamForManifest(d,o,n),this.isStaleStart(t))return;const a=s.adInsertionType;this.deps.adPlayerController.setBreakResumePolicy(a,d.timebase,this.deps.continueContentDuringBreak),this.deps.diagnose("DA-INSERTION-TYPE-RESOLVED",void 0===a?`Ad insertion type: unset — the manifest's resumeOffset drives the resume point (timebase: ${d.timebase})`:`Ad insertion type: ${a}, client-set (timebase: ${d.timebase})`,{adInsertionType:a??null,source:void 0===a?"unset":"client",timebase:d.timebase}),n("scheduling"),this.deps.breakScheduler.updateManifest(d),this.deps.pauseAdController.updateManifest(d),this.state.pauseAdTickInterval=setInterval(()=>this.deps.pauseAdController?.tick(),250);const r=(d.adStartDelay??0)>0?void 0:d.breaks.find(s=>0===e(s));r?(this.deps.adPlayerController.holdContentForPreRoll(r),this.state.heldImmediatePreRollId=r.id,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=Date.now(),this.contentIsReady()?this.armHeldImmediatePreRoll():this.state.heldPreRollArmInterval=setInterval(()=>{this.contentIsReady()?this.armHeldImmediatePreRoll():this.armHeldImmediatePreRollIfTimedOut()},250)):this.markPlaybackStartIfPlaying(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`Session active for manifest ${i}`,{manifestUrl:i}),this.deps.log("Session active")}catch(e){if(this.isStaleStart(t))return;throw this.failSessionStart(e)}else await this.startSteeredSsaiSession(d,s,n)}markPlaybackStartIfPlaying(){this.deps.player.paused||this.deps.breakScheduler.markPlaybackStart()}contentIsReady(){return!Number.isNaN(this.deps.player.duration)||!this.deps.player.paused}clearHeldImmediatePreRollArmInterval(){this.state.heldPreRollArmInterval&&(clearInterval(this.state.heldPreRollArmInterval),this.state.heldPreRollArmInterval=null)}armHeldImmediatePreRoll(){this.state.heldImmediatePreRollId&&!this.state.heldPreRollArmed&&(this.clearHeldImmediatePreRollArmInterval(),this.state.heldPreRollArmed=!0,this.deps.breakScheduler.markPlaybackStart())}armHeldImmediatePreRollIfTimedOut(){if(!this.state.heldImmediatePreRollId||this.state.heldPreRollArmed)return;const e=this.state.heldPreRollWaitStartedAt;if(null===e||Date.now()-e<m)return;const s=this.state.heldImmediatePreRollId;this.armHeldImmediatePreRoll(),this.deps.diagnose("DA-PREROLL-CONTENT-WAIT-TIMEOUT","Pending pre-roll began after waiting 10000ms for content readiness.",{breakId:s,waitMs:m})}cancelHeldImmediatePreRollOnContentError(){const e=this.state.heldImmediatePreRollId;return!(!e||this.state.heldPreRollArmed)&&(this.clearHeldImmediatePreRollArmInterval(),this.deps.breakScheduler?.completeBreak(e),this.deps.adPlayerController?.releaseContentHold(),this.state.heldImmediatePreRollId=null,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=null,this.deps.diagnose("DA-PREROLL-CANCELLED-CONTENT-ERROR","A pending pre-roll was cancelled because the content failed to load before it could begin.",{breakId:e}),!0)}clearHeldImmediatePreRollArm(){this.clearHeldImmediatePreRollArmInterval(),this.state.heldImmediatePreRollId=null,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=null}failSessionStart(e){const s=e instanceof Error?e:new Error(String(e)),t=this.deps.manifestService?.takeLastFailure();return this.deps.diagnose("DA-MANIFEST-FETCH-FAILED",s.message,t?{...t}:void 0),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),s}resolveSessionDeliveryMode(e,s="manifest"){const t=this.state.sessionDeliveryMode,i=l(e,d(),this.deps.hasExplicitMode?this.deps.mode:null,s);return null!==i.unknownMode&&this.deps.diagnose("DA-DELIVERY-MODE-UNKNOWN",`Delivery rule ${i.ruleIndex} carries unrecognized mode "${i.unknownMode}"; using sgai.`,{unknownMode:i.unknownMode,ruleIndex:i.ruleIndex}),this.deps.hasExplicitMode&&i.manifestMode!==i.mode&&this.deps.diagnose("DA-DELIVERY-MODE-OVERRIDDEN",`Manifest delivery rules resolve "${i.manifestMode}" but the explicit config mode "${i.mode}" takes precedence.`,{manifestMode:i.manifestMode,configuredMode:i.mode}),this.state.sessionDeliveryMode=i.mode,this.deps.diagnose("DA-DELIVERY-MODE-RESOLVED",`Delivery mode: ${i.mode}`,{mode:i.mode,ruleIndex:i.ruleIndex,source:i.source,..."channel-switch"===s?{previousMode:t}:{}}),i.mode}async startSteeredSsaiSession(e,s,t){const i=n(e.vendorConfiguration);if(!i){const e="Manifest selected ssai delivery but no vendor configuration provides a stitched stream (no gam entry with an assetKey).";throw this.deps.diagnose("DA-SSAI-UNAVAILABLE",e),this.deps.manifestService.reset(),this.state.sessionActive=!1,new o(e)}try{t("ssai-live-stream"),this.state.steeredSsaiActive=!0,this.state.ssaiController=this.createSsaiController(i.networkCode),await this.state.ssaiController.startSteered(i,s.assetParameters,s.assetParameterMacros),t("scheduling");const a=r(e);this.deps.adPlayerController.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),this.deps.breakScheduler.updateManifest(a),this.deps.pauseAdController.updateManifest(a),this.state.pauseAdTickInterval=setInterval(()=>this.deps.pauseAdController?.tick(),250),this.markPlaybackStartIfPlaying(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active (manifest-steered) for ${s.manifestUrl}`,{manifestUrl:s.manifestUrl}),this.deps.log("SSAI session active (manifest-steered)")}catch(e){const s=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",s.message),this.state.steeredSsaiActive=!1,this.deps.manifestService.reset(),this.state.sessionActive=!1,s}}createSsaiController(e){return new i(this.deps.player,{gam:this.state.steeredSsaiOptions?.streamActivityMonitorId?{streamActivityMonitorId:this.state.steeredSsaiOptions.streamActivityMonitorId}:{},ssai:{networkCode:e},maxBitrate:this.state.steeredSsaiOptions?.maxBitrate,autoplay:this.state.steeredSsaiOptions?.autoplay,debug:this.deps.debug},{emit:(e,s)=>this.deps.emit(e,s),diagnose:(e,s,t)=>this.deps.diagnose(e,s,t),log:(...e)=>this.deps.log(...e)})}startBreakScheduler(){this.deps.breakScheduler.start((e,s)=>{this.deps.breakLifecycle.onBreakStart(e,s)},e=>!this.deps.adPlayerController.isAdPlaying()&&(this.deps.adPlayerController.preload(e).catch(s=>{this.deps.diagnose("DA-PRELOAD-FAILED",s instanceof Error?s.message:String(s),{breakId:e.id})}),!0),(e,s,t)=>{this.deps.adBreakStatusTracker.onBreakWarning(e,s,t)},e=>{this.deps.breakLifecycle.onBreakAborted(e)})}startManifestPolling(){this.deps.manifestService.startPolling(e=>{if(!this.state.sessionActive)return;const s=this.deps.notedManifestBreakIds(e);this.deps.diagnose("DA-MANIFEST-POLLED","Manifest polled",{breaks:e.breaks.length,newBreakIds:s,pollingIntervalSeconds:this.deps.manifestService.currentPollingIntervalSeconds});const t=this.state.sessionManifest,i=t?h(t,e):null;if(this.state.pendingChannelSwitch||i&&this.deps.breakScheduler.getActiveBreak())return this.state.pendingChannelSwitch=e,void this.deps.log("Channel switch deferred until the active break ends");i?this.switchChannel(e,i):this.applyManifestUpdate(e)},e=>{this.state.sessionActive&&this.deps.diagnose("DA-MANIFEST-POLL-FAILED",e.message)})}applyPendingChannelSwitch(){if(!this.state.sessionActive||!this.state.pendingChannelSwitch)return;const e=this.state.pendingChannelSwitch;this.state.pendingChannelSwitch=null;const s=h(this.state.sessionManifest,e);s?this.switchChannel(e,s):this.applyManifestUpdate(e)}applyManifestUpdate(e){this.diagnoseIgnoredDeliveryModeChange(e);const s=t(e);this.state.steeredSsaiActive?this.state.ssaiController?.setManifestAssetParameters(s.ssai?.assetParameters):this.deps.adPlayerController.setManifestAssetParameters(s.sgaiAssetParameters);const i=this.state.steeredSsaiActive?r(e):e;this.deps.breakScheduler.updateManifest(i),this.deps.pauseAdController.updateManifest(i)}diagnoseIgnoredDeliveryModeChange(e){if(!this.state.sessionDeliveryMode)return;const s=l(e,d(),null,"manifest");s.manifestMode!==this.state.sessionDeliveryMode&&this.deps.diagnose("DA-DELIVERY-MODE-CHANGE-IGNORED",`Polled manifest resolves delivery mode "${s.manifestMode}" but "${this.state.sessionDeliveryMode}" is pinned for this session.`,{polledMode:s.manifestMode,pinnedMode:this.state.sessionDeliveryMode})}async switchChannel(e,s){if(this.state.channelSwitchInFlight)return;const t=this.state.sessionConfig,i=this.state.sessionManifest,a={previousChannelId:s.previousChannelId,channelId:s.channelId,reason:s.reason,manifestUrl:t.manifestUrl};this.deps.diagnose("DA-MANIFEST-CHANNEL-CHANGED",`Manifest channel changed (${s.reason}): ${s.previousChannelId??"none"} -> ${s.channelId??"none"}`,a),this.state.sessionManifest=e,this.state.channelSwitchInFlight=!0;const r=this.state.sessionGeneration,n=this.state.sessionDeliveryMode??"sgai";try{let s=this.resolveSessionDeliveryMode(e,"channel-switch");if(this.deps.cmcdAdReporter.onSessionEnd(),this.deps.cmcdAdReporter.onSessionStart(e),this.deps.lensReporter.endSession(),this.deps.lensReporter.startSession(),"ssai"===s&&"sgai"===n){const a=await this.switchToSteeredSsai(e,t);if(this.isStaleStart(r))return;a||(s="sgai",this.state.sessionDeliveryMode="sgai",await this.applySameModeChannelSwitch(i,e,t,r))}else"sgai"===s&&"ssai"===n?await this.switchToSgai(e,t,r):await this.applySameModeChannelSwitch(i,e,t,r);if(this.isStaleStart(r))return}finally{this.state.channelSwitchInFlight=!1}this.emitChannelChange({...a,previousDeliveryMode:n,deliveryMode:this.state.sessionDeliveryMode??n})}emitChannelChange(e){this.deps.emit("adchannelchange",{type:"adchannelchange",timestamp:Date.now(),...e})}async applySameModeChannelSwitch(e,s,i,a){const n=this.state.steeredSsaiActive,o=n?r(s):s;if(this.deps.breakScheduler.resetForChannelSwitch(o),this.deps.pauseAdController.updateManifest(o),this.deps.adPlayerController.setBreakResumePolicy(i.adInsertionType,s.timebase,this.deps.continueContentDuringBreak),!n&&c(e,s)){const e=this.deps.adPlayerController.updatedAssetParameters,t=this.deps.adPlayerController.sessionAssetParameterMacros;if(this.deps.adPlayerController.endGamSession(),this.deps.adPlayerController.setSessionAssetParameters(i.assetParameters),this.deps.adPlayerController.setSessionAssetParameterMacros(t),await this.startGamForManifest(s,i.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:i.manifestUrl})}),this.isStaleStart(a))return;e&&this.deps.adPlayerController.updateAssetParameters(e)}else if(n){const e=t(s);this.state.ssaiController?.setManifestAssetParameters(e.ssai?.assetParameters)}else{const e=t(s);this.deps.adPlayerController.setManifestAssetParameters(e.sgaiAssetParameters)}}async switchToSteeredSsai(e,s){const t=n(e.vendorConfiguration);if(!t)return this.deps.diagnose("DA-SSAI-UNAVAILABLE","Channel switch resolved ssai delivery but no vendor configuration provides a stitched stream; the session stays in sgai.",{manifestUrl:s.manifestUrl,previousMode:"sgai"}),!1;const i=this.deps.adPlayerController,a=i.updatedAssetParameters,o=i.sessionAssetParameterMacros;i.stopActiveBreak(),i.releaseContentHold(),i.endGamSession(),i.setSessionAssetParameters(s.assetParameters),i.setSessionAssetParameterMacros(o),a&&i.updateAssetParameters(a);const d=this.createSsaiController(t.networkCode);this.state.ssaiController=d,this.state.steeredSsaiActive=!0,this.deps.diagnose("DA-SESSION-STARTING","Channel switch: ssai-live-stream",{step:"ssai-live-stream",manifestUrl:s.manifestUrl});try{await d.startSteered(t,s.assetParameters,o),a&&d.updateAssetParameters(a);const n=r(e);return this.deps.breakScheduler.resetForChannelSwitch(n),this.deps.pauseAdController.updateManifest(n),i.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),!0}catch(e){const s=e instanceof Error?e.message:String(e);return this.deps.diagnose("DA-SSAI-SESSION-FAILED",s),d.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,!1}}async switchToSgai(e,s,t){const i=this.state.ssaiController?.sessionAssetParameterMacros;this.state.ssaiController?.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,this.deps.breakScheduler.resetForChannelSwitch(e),this.deps.pauseAdController.updateManifest(e);const a=this.deps.adPlayerController;a.setBreakResumePolicy(s.adInsertionType,e.timebase,this.deps.continueContentDuringBreak);const r=a.updatedAssetParameters;a.endGamSession(),a.setSessionAssetParameters(s.assetParameters),a.setSessionAssetParameterMacros(i??s.assetParameterMacros),await this.startGamForManifest(e,s.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:s.manifestUrl})}),this.isStaleStart(t)||r&&a.updateAssetParameters(r)}async startGamForManifest(e,i,a){const r=t(e);if(this.deps.adPlayerController.setManifestAssetParameters(r.sgaiAssetParameters),r.unknownTypes.length>0&&this.deps.diagnose("DA-VENDOR-CONFIG-UNKNOWN-TYPE",`Ignored ${r.unknownTypes.length} vendorConfiguration `+(1===r.unknownTypes.length?"entry":"entries")+" with unrecognised type: "+`${r.unknownTypes.join(", ")}.`,{types:r.unknownTypes}),this.deps.adPlayerController.isGamEnabled())if(r.identity){this.deps.diagnose("DA-GAM-CONFIG-RESOLVED",`GAM session identity: network ${r.identity.networkCode}, custom asset ${r.identity.customAssetKey}`,{networkCode:r.identity.networkCode,customAssetKey:r.identity.customAssetKey,channelId:e.channelId,duplicateEntriesIgnored:r.duplicateGam});try{a("gam-init"),await this.deps.adPlayerController.startGamSession(r.identity,i),this.deps.log("GAM session started")}catch(e){this.deps.diagnose("DA-GAM-SESSION-FAILED",e instanceof Error?e.message:String(e))}}else s(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING",'The break manifest carries GAM pod assets but no usable "gam" vendorConfiguration entry; those assets will be skipped.',{reason:r.reason});else s(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING","The break manifest carries GAM pod assets but GAM is disabled for this session (gam.enabled === false); those assets will be skipped.",{reason:"gam-disabled"})}async startSsaiSession(e,s){try{if(this.state.stitcherOrigin=a(e.stitcherUrl),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!e.assetParameters}),await this.state.ssaiController.start(e),this.isStaleStart(s))return;this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active for ${e.stitcherUrl}`,{stitcherUrl:e.stitcherUrl}),this.deps.log("SSAI session active")}catch(e){if(this.isStaleStart(s))return;const t=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",t.message),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),t}}isStaleStart(e){return e!==this.state.sessionGeneration&&(this.deps.log("Session start aborted — the session ended or was replaced while starting"),!0)}}
@@ -20,6 +20,7 @@ export declare class SessionState {
20
20
  } | null;
21
21
  ssaiController?: SsaiController;
22
22
  pauseAdTickInterval: ReturnType<typeof setInterval> | null;
23
+ heldPreRollArmInterval: ReturnType<typeof setInterval> | null;
23
24
  configSummary: ConfigSummary;
24
25
  /**
25
26
  * Origin of the polled URL, derived per startSession() and merged into the config summary
@@ -28,6 +29,8 @@ export declare class SessionState {
28
29
  manifestOrigin: string | null;
29
30
  stitcherOrigin: string | null;
30
31
  heldImmediatePreRollId: string | null;
32
+ heldPreRollArmed: boolean;
33
+ heldPreRollWaitStartedAt: number | null;
31
34
  snapbackSeekInFlight: boolean;
32
35
  sessionConfig: SessionConfig | null;
33
36
  sessionManifest: BreakManifest | null;
@@ -1 +1 @@
1
- export class SessionState{constructor(){this.sessionActive=!1,this.sessionGeneration=0,this.sessionDeliveryMode=null,this.steeredSsaiActive=!1,this.steeredSsaiOptions=null,this.pauseAdTickInterval=null,this.manifestOrigin=null,this.stitcherOrigin=null,this.heldImmediatePreRollId=null,this.snapbackSeekInFlight=!1,this.sessionConfig=null,this.sessionManifest=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}clearSessionIdentity(){this.sessionDeliveryMode=null,this.sessionManifest=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}}
1
+ export class SessionState{constructor(){this.sessionActive=!1,this.sessionGeneration=0,this.sessionDeliveryMode=null,this.steeredSsaiActive=!1,this.steeredSsaiOptions=null,this.pauseAdTickInterval=null,this.heldPreRollArmInterval=null,this.manifestOrigin=null,this.stitcherOrigin=null,this.heldImmediatePreRollId=null,this.heldPreRollArmed=!1,this.heldPreRollWaitStartedAt=null,this.snapbackSeekInFlight=!1,this.sessionConfig=null,this.sessionManifest=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}clearSessionIdentity(){this.sessionDeliveryMode=null,this.sessionManifest=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}}
@@ -1,5 +1,5 @@
1
1
  import { PlayerAdapter } from '../interfaces/PlayerAdapter';
2
- import { AssetParameterMacros } from '../services/assetParameterMacros';
2
+ import { AssetParameterMacroValue, AssetParameterMacros } from '../services/assetParameterMacros';
3
3
  import { BreakManifest } from './BreakManifest';
4
4
  /**
5
5
  * Factory for a custom ad player: create media elements inside the supplied container
@@ -393,6 +393,11 @@ export interface SessionConfig {
393
393
  */
394
394
  assetParameterMacros?: AssetParameterMacros;
395
395
  }
396
+ /**
397
+ * Per-name update for {@link SessionConfig.assetParameterMacros}: a new value per macro name, or
398
+ * `null` to remove the customer macro so the built-in value (if any) applies again.
399
+ * @category Configuration */
400
+ export type AssetParameterMacroUpdates = Record<string, AssetParameterMacroValue | null>;
396
401
  /**
397
402
  * Per-session configuration for `mode: 'ssai'`: keyed on the stitched master URL instead of a
398
403
  * manifest URL. The SDK appends `stream_id`, `max_bitrate` and extra params and never rebuilds the path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolby-optiview/ads-sdk-core",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.3",
4
4
  "description": "OptiView Ads SDK Core - Player-agnostic ad insertion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -42,12 +42,12 @@
42
42
  "video",
43
43
  "advertising"
44
44
  ],
45
- "author": "Dolby",
46
- "license": "MIT",
45
+ "author": "Dolby OptiView",
46
+ "license": "SEE LICENSE AT https://optiview.dolby.com/policies/terms-of-service/",
47
47
  "engines": {
48
48
  "node": ">=18.0.0"
49
49
  },
50
- "homepage": "https://ads-sdk.xnappet.live",
50
+ "homepage": "https://optiview.dolby.com/docs/ads/player-integration/optiview-ads-sdk/",
51
51
  "publishConfig": {
52
52
  "access": "public"
53
53
  },