@dolby-optiview/ads-sdk-core 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OptiViewAds.d.ts +8 -4
- package/dist/OptiViewAds.js +1 -1
- package/dist/OptiViewAdsExternalVendors.d.ts +47 -0
- package/dist/OptiViewAdsExternalVendors.js +1 -0
- package/dist/generated/diagnosticCodes.d.ts +72 -2
- package/dist/generated/diagnosticCodes.js +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/id3/index.d.ts +1 -0
- package/dist/id3/index.js +1 -1
- package/dist/id3/parseId3TextFrames.d.ts +8 -0
- package/dist/id3/parseId3TextFrames.js +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.js +1 -1
- package/dist/interfaces/PlayerAdapter.d.ts +5 -0
- package/dist/services/AdBreakSequencer.js +1 -1
- package/dist/services/AdPlayerController.d.ts +2 -0
- package/dist/services/AdPlayerController.js +1 -1
- package/dist/services/BreakScheduler.d.ts +31 -56
- package/dist/services/BreakScheduler.js +1 -1
- package/dist/services/BreakSchedulerTypes.d.ts +55 -0
- package/dist/services/BreakSchedulerTypes.js +1 -0
- package/dist/services/ExternalVendorConfigurationTracker.d.ts +13 -0
- package/dist/services/ExternalVendorConfigurationTracker.js +1 -0
- package/dist/services/GamStreamManager.d.ts +6 -0
- package/dist/services/GamStreamManager.js +1 -1
- package/dist/services/ImaMetadataDeduplicator.d.ts +5 -0
- package/dist/services/ImaMetadataDeduplicator.js +1 -0
- package/dist/services/SsaiController.d.ts +2 -0
- package/dist/services/SsaiController.js +1 -1
- package/dist/services/adPlayer/AdPlayerEvents.js +1 -1
- package/dist/services/adPlayer/AdPlayerState.d.ts +13 -0
- package/dist/services/adPlayer/AdPlayerState.js +1 -1
- package/dist/services/adPlayer/AdPreloader.js +1 -1
- package/dist/services/adPlayer/AdStage.d.ts +7 -3
- package/dist/services/adPlayer/AdStage.js +1 -1
- package/dist/services/adPlayer/AssetParameterResolver.d.ts +2 -0
- package/dist/services/adPlayer/AssetParameterResolver.js +1 -1
- package/dist/services/adPlayer/AssetPlayback.js +1 -1
- package/dist/services/adPlayer/ContentResumeCoordinator.d.ts +10 -3
- package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -1
- package/dist/services/adPlayer/GamPodBridge.js +1 -1
- package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
- package/dist/services/adPlayer/VariantSelector.js +1 -1
- package/dist/services/adPlayer/pdtClockResume.d.ts +57 -0
- package/dist/services/adPlayer/pdtClockResume.js +1 -0
- package/dist/services/assetParameterMacros.d.ts +0 -1
- package/dist/services/assetParameterMacros.js +1 -1
- package/dist/services/breakTiming.d.ts +2 -0
- package/dist/services/breakTiming.js +1 -1
- package/dist/services/channelSwitch.d.ts +3 -1
- package/dist/services/channelSwitch.js +1 -1
- package/dist/services/detectDeviceType.d.ts +7 -2
- package/dist/services/detectDeviceType.js +1 -1
- package/dist/services/diagnosticReport.d.ts +3 -0
- package/dist/services/diagnosticReport.js +1 -0
- package/dist/services/externalVendorConfigurationGetter.d.ts +12 -0
- package/dist/services/externalVendorConfigurationGetter.js +1 -0
- package/dist/services/matchTargeting.d.ts +19 -0
- package/dist/services/matchTargeting.js +1 -0
- package/dist/services/resolveDeliveryMode.d.ts +3 -1
- package/dist/services/resolveDeliveryMode.js +1 -1
- package/dist/services/resolveExternalVendorConfigurations.d.ts +20 -0
- package/dist/services/resolveExternalVendorConfigurations.js +1 -0
- package/dist/services/resolveGamVendorConfiguration.js +1 -1
- package/dist/services/resolveResumeClamp.d.ts +42 -0
- package/dist/services/resolveResumeClamp.js +1 -0
- package/dist/services/resolveTargetedUri.d.ts +2 -1
- package/dist/services/resolveTargetedUri.js +1 -1
- package/dist/services/selectVariant.d.ts +4 -0
- package/dist/services/selectVariant.js +1 -1
- package/dist/services/toPlaybackError.d.ts +1 -0
- package/dist/services/toPlaybackError.js +1 -0
- package/dist/services/wallclockBreakWindow.d.ts +41 -0
- package/dist/services/wallclockBreakWindow.js +1 -0
- package/dist/session/BreakLifecycle.d.ts +2 -0
- package/dist/session/BreakLifecycle.js +1 -1
- package/dist/session/SessionStarter.d.ts +6 -1
- package/dist/session/SessionStarter.js +1 -1
- package/dist/session/SessionState.d.ts +1 -0
- package/dist/session/SessionState.js +1 -1
- package/dist/types/BreakManifest.d.ts +39 -1
- package/dist/types/BreakManifest.js +1 -1
- package/dist/types/Events.d.ts +16 -1
- package/dist/types/ExternalVendorConfiguration.d.ts +5 -0
- package/dist/types/ExternalVendorConfiguration.js +1 -0
- package/dist/types/OptiViewAdsConfig.d.ts +2 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
import{buildGamPodUrl as t}from"./GamPodUrlBuilder";import{debugLog as e}from"../diagnostics/debugLog";export class GamStreamManager{constructor(t,e,
|
|
1
|
+
import{buildGamPodUrl as t}from"./GamPodUrlBuilder";import{debugLog as e}from"../diagnostics/debugLog";import{ImaMetadataDeduplicator as a}from"./ImaMetadataDeduplicator";import{parseId3TextFrames as i}from"../id3/parseId3TextFrames";export const GAM_DUPLICATE_METADATA_DIAGNOSTIC_MESSAGE="The stream carries the same ad marker more than once at the same time (for example in both the audio and video renditions of an fMP4 stream); the duplicate was not passed to IMA, so each ad event fires once. Reported once per stream session.";export class GamStreamManager{constructor(t,e,i=!1){this.videoElement=t,this.adUiContainer=e,this.streamManager=null,this.streamId=null,this.config=null,this.initPromise=null,this.callbacks={},this.metadataDeduplicator=new a,this.markerTextDeduplicator=new a,this.duplicateMetadataReported=!1,this.debug=i}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?.(),duration:e.getDuration?.()}}(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)return;if(t.raw&&t.raw.length>0){if("presented"===t.phase)return;if(!this.acceptMetadata(`id3:${Array.from(t.raw).join(",")}`,t.startTime))return;const e=i(t.raw);if(e.length>0&&!this.markerTextDeduplicator.accept(`marker:${e.join("\0")}`,t.startTime))return;return void this.streamManager.processMetadata("ID3",t.raw,t.startTime)}if("parsed"===t.phase)return;const e=t.frames?.TXXX??t.messageData;if(t.frames&&Object.keys(t.frames).length>0){if(!this.acceptMetadata(`txt:${JSON.stringify(t.frames)}`,t.startTime))return;if(void 0!==e&&!this.markerTextDeduplicator.accept(`marker:${e}`,t.startTime))return;this.streamManager.onTimedMetadata(t.frames)}else if(t.messageData){if(!this.acceptMetadata(`txt:${t.messageData}`,t.startTime))return;if(!this.markerTextDeduplicator.accept(`marker:${t.messageData}`,t.startTime))return;this.streamManager.onTimedMetadata({TXXX:t.messageData})}}acceptMetadata(t,e){return!!this.metadataDeduplicator.accept(t,e)||(this.duplicateMetadataReported||(this.duplicateMetadataReported=!0,this.callbacks.onDuplicateMetadata?.(e)),!1)}buildPodUrl(e,a,i){if(!this.streamId)throw new Error("GamStreamManager not initialized. Call initialize() first.");const r=t(e,i,this.streamId,a);return this.log(`Built pod URL: ${r}`),r}reset(){this.streamManager&&this.streamManager.reset(),this.streamId=null,this.initPromise=null,this.streamManager=null,this.metadataDeduplicator.reset(),this.markerTextDeduplicator.reset(),this.duplicateMetadataReported=!1,this.log("Stream manager reset")}destroy(){this.reset(),this.config=null,this.callbacks={}}log(t){this.debug&&e(`[GamStreamManager] ${t}`)}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class ImaMetadataDeduplicator{constructor(){this.entries=[]}accept(e,t){if(!Number.isFinite(t))return!0;return!this.entries.some(s=>s.key===e&&Math.abs(s.time-t)<=.5)&&(this.entries.push({key:e,time:t}),this.entries.length>64&&this.entries.shift(),!0)}reset(){this.entries=[]}}
|
|
@@ -3,6 +3,7 @@ import type { AssetParameterMacroUpdates, GamConfig, SsaiConfig, SsaiSessionConf
|
|
|
3
3
|
import type { StitchedStreamVendor } from './resolveDeliveryMode';
|
|
4
4
|
import { AssetParameterMacros } from './assetParameterMacros';
|
|
5
5
|
import type { OptiViewAdsEventMap, OptiViewAdsEventType } from '../types/Events';
|
|
6
|
+
import type { ExternalVendorConfiguration } from './resolveExternalVendorConfigurations';
|
|
6
7
|
/** Org-level options the SSAI controller needs from the SDK config. */
|
|
7
8
|
export interface SsaiControllerOptions {
|
|
8
9
|
gam: GamConfig;
|
|
@@ -45,6 +46,7 @@ export declare class SsaiController {
|
|
|
45
46
|
startSteered(vendor: StitchedStreamVendor, sessionConfigParameters?: Record<string, string>, macros?: AssetParameterMacros): Promise<void>;
|
|
46
47
|
private resolveAdTagParameters;
|
|
47
48
|
updateAssetParameters(params: Record<string, string>): void;
|
|
49
|
+
resolveExternalVendorConfiguration(entry: ExternalVendorConfiguration): ExternalVendorConfiguration;
|
|
48
50
|
updateAssetParameterMacros(macros: AssetParameterMacroUpdates): void;
|
|
49
51
|
get sessionAssetParameterMacros(): AssetParameterMacros;
|
|
50
52
|
setManifestAssetParameters(params: Record<string, string> | undefined): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{objectEntries as e}from"./runtimeObject";import{applyAssetParameterMacros as t
|
|
1
|
+
import{objectEntries as e}from"./runtimeObject";import{applyAssetParameterMacros 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{GAM_DUPLICATE_METADATA_DIAGNOSTIC_MESSAGE as n,GamStreamManager as m}from"./GamStreamManager";import{resolveBreakFormat as d}from"./AdBreakSequencer";import{buildStitcherMasterUrl as c}from"./StitcherUrlBuilder";import{resolveExternalVendorParameters as h}from"./resolveExternalVendorConfigurations";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 m(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=c({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 m(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 e=s(this.sessionConfigParameters,this.updatedParameters,this.manifestParameters,void 0),r=t(e,a({customerMacros:this.customerMacros,element:this.player.videoElement??null,breakDurationSec:void 0}),"gam");for(const{key:e,macro:t}of r.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});for(const{key:e,macro:t,pair:a}of r.omitted)this.callbacks.diagnose("DA-ASSET-PARAMETER-OMITTED",`Asset parameter '${e}' not sent: macro '${t}' is empty.`,{key:e,macro:t,...void 0!==a?{pair:a}:{}});return r.params}updateAssetParameters(e){this.updatedParameters=e,this.gamStreamManager?.replaceAdTagParameters(this.resolveAdTagParameters()??{})}resolveExternalVendorConfiguration(e){const t=h(e,s(this.sessionConfigParameters,this.updatedParameters,void 0,void 0),a({customerMacros:this.customerMacros,element:this.player.videoElement??null,breakDurationSec:void 0}));for(const{key:e,macro:a}of t.unresolved)this.callbacks.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${a}': the macro has no value.`,{key:e,macro:a});for(const{key:e,macro:a}of t.omitted)this.callbacks.diagnose("DA-ASSET-PARAMETER-OMITTED",`Asset parameter '${e}' not sent: macro '${a}' is empty.`,{key:e,macro:a});return t.configuration}updateAssetParameterMacros(t){const a={...this.customerMacros};for(const[s,r]of e(t))void 0===r?delete a[s]:a[s]=r;this.customerMacros=a,this.callbacks.diagnose("DA-ASSET-PARAMETERS-UPDATED","Asset parameter macros updated.",{source:"macros",keys:Object.keys(t).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:d(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:d(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"),onDuplicateMetadata:e=>{this.callbacks.diagnose("DA-GAM-DUPLICATE-METADATA",n,{...this.currentAsset?{assetId:this.currentAsset.id}:{},startTime:e})},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:d(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:d(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:d(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:d(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:""}}}
|
|
@@ -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||(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&&!t.paused){if(this.state.startPlaybackCutoff?.(),this.state.firstFrameResolver)return this.deps.revealFirstFrame(),this.state.firstFrameResolver(),void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset);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.
|
|
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&&!t.paused){if(this.state.startPlaybackCutoff?.(),this.state.firstFrameResolver)return this.deps.revealFirstFrame(),this.state.firstFrameResolver(),void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset);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(),s=this.state.currentBreak;if(!s||!e)return;const r=this.state.preloadGeneration,i=this.state.erroredAsset;if(i?.breakInfo!==s||i.generation!==r){if(this.state.erroredAsset={breakInfo:s,generation:r},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"}))}else this.deps.log("Ignoring further ad error for the same asset:",t.message)}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Asset, Break, DeviceType, Timebase } from '../../types/BreakManifest';
|
|
2
|
+
import type { DeviceProfile } from '../matchTargeting';
|
|
2
3
|
import type { AdInsertionType } from '../../types/OptiViewAdsConfig';
|
|
3
4
|
import type { AdPlayerCallbacks } from '../AdPlayerController';
|
|
4
5
|
import type { AdBreakStep } from '../AdBreakSequencer';
|
|
@@ -22,6 +23,7 @@ export interface AdPlayerState {
|
|
|
22
23
|
breakTimebase: Timebase | null;
|
|
23
24
|
adBreakCutSafetyMarginSec: number;
|
|
24
25
|
detectedDeviceType: DeviceType | undefined;
|
|
26
|
+
detectedDeviceProfile?: DeviceProfile | undefined;
|
|
25
27
|
sessionAssetParameters: Record<string, string> | undefined;
|
|
26
28
|
updatedAssetParameters: Record<string, string> | undefined;
|
|
27
29
|
manifestAssetParameters: Record<string, string> | undefined;
|
|
@@ -63,6 +65,12 @@ export interface AdPlayerState {
|
|
|
63
65
|
* whether the content under the ad is known slate of exactly the advertised duration.
|
|
64
66
|
*/
|
|
65
67
|
resumeAnchorIsCue: boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether this break's anchor is the content time of the manifest start, placed through the
|
|
70
|
+
* stream's Program Date Time (`wallclock` timebase) — like a cue, a stream-clock anchor under
|
|
71
|
+
* which the content for the whole advertised duration is the stream's own break.
|
|
72
|
+
*/
|
|
73
|
+
resumeAnchorIsPdt: boolean;
|
|
66
74
|
/**
|
|
67
75
|
* The seekable end as the engine reported it at break start, captured with the anchor so the
|
|
68
76
|
* two are comparable. Their difference is the cushion the resume has to work with: a playhead
|
|
@@ -106,6 +114,11 @@ export interface AdPlayerState {
|
|
|
106
114
|
player: PlayerAdapter;
|
|
107
115
|
} | null;
|
|
108
116
|
preloadGeneration: number;
|
|
117
|
+
/** The break + asset generation that already reported an error; later errors for it are dropped. */
|
|
118
|
+
erroredAsset: {
|
|
119
|
+
breakInfo: Break;
|
|
120
|
+
generation: number;
|
|
121
|
+
} | null;
|
|
109
122
|
firstFrameGateActive: boolean;
|
|
110
123
|
firstFrameGateShown: boolean;
|
|
111
124
|
firstFrameResolver: (() => void) | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function invalidateMediaPreload(e){return e.preparedMedia=null,++e.preloadGeneration}export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,startPlaybackCutoff: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,resumeAnchorIsCue:!1,resumeAnchorSeekableEnd:null,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,resumeCatchupRan:!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,preparedMedia:null,preloadGeneration:0,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
|
|
1
|
+
export function invalidateMediaPreload(e){return e.preparedMedia=null,++e.preloadGeneration}export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,startPlaybackCutoff: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,resumeAnchorIsCue:!1,resumeAnchorIsPdt:!1,resumeAnchorSeekableEnd:null,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,resumeCatchupRan:!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,preparedMedia:null,preloadGeneration:0,erroredAsset:null,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AssetType as e,BreakFormat as t,MediaType as s,Vendor as
|
|
1
|
+
import{AssetType as e,BreakFormat as t,MediaType as s,Vendor as a,isVastAsset as r,isVendorAsset as i}from"../../types/BreakManifest";import{AUX_FETCH_TIMEOUT_MS as d,fetchWithTimeout as o}from"../fetchWithTimeout";import{resolveVastAdmission as n}from"../resolveVastAdmission";import{isImaCsaiSdkAvailable as l,VastAdManager as p}from"../VastAdManager";import{invalidateMediaPreload as h}from"./AdPlayerState";import{AdError as m}from"../../types/AdError";export const AD_LOAD_RETRY_AFTER_MS=2e3;export function adLoadShowsNoProgress(e){return!e||0===e.readyState&&0===e.buffered.length}export class AdPreloader{constructor(e){this.deps=e,this.warmedBreakIds=new Set}classifyCreative(e){const t=(e.split(/[?#]/)[0]??"").toLowerCase();return t.endsWith(".m3u8")||t.endsWith(".mpd")?"stream":/\.(mp4|m4v|m4a|webm|mov|ogg|ogv|mp3)$/.test(t)?"progressive":"unknown"}diagnosePreloadPath(e,t,s){const a=this.classifyCreative(t);this.deps.diagnose("DA-PRELOAD-PATH",`Ad preload path: ${s} (${a} creative)`,{breakId:e,adPreload:this.deps.preloadMode,path:s,creative:a})}async preload(a){if(this.deps.isPlaying())return void this.deps.log("Preload skipped: ad already playing");const i=h(this.deps.state),d=this.deps.getAdPlayer();if("none"===this.deps.preloadMode){const t=this.deps.getAssets(a)[0],s=t&&t.type===e.STATIC?this.deps.resolveUri(t.uri)??"":"";return this.diagnosePreloadPath(a.id,s,"none"),void this.deps.log('Preload skipped: adPreload "none"')}if("shared-element"===this.deps.effectiveInsertionMode()){const s=this.deps.getVariant(a);if(s&&s.format!==t.LSHAPE_CONTENT){const t=this.deps.getAssets(a)[0];if(t&&t.type===e.STATIC){const e=this.deps.resolveUri(t.uri);e&&await this.warmCache([e])}}return}const o=this.deps.getAssets(a);if(0===o.length)return void this.deps.log("No assets to preload");this.preloadCompanion(a);const n=this.deps.getVariant(a);if(n?.format===t.LSHAPE_CONTENT)return;if(o[0].mediaType===s.IMAGE){const e=await this.deps.resolveAssetUri(o[0],a.duration);if(e){(new Image).src=e,this.deps.log("Preloading overlay image:",e)}return}if(r(o[0]))return void await this.preloadVastAsset(o[0],a);const l=await this.deps.resolveAssetUri(o[0],a.duration);if(l){if(d&&this.ownsPreload(i,d))if("single-decoder"!==this.deps.preloadMode){this.deps.log("Preloading ad (parallel):",l),this.diagnosePreloadPath(a.id,l,"parallel");try{if(await d.load(l),!this.ownsPreload(i,d))return;this.deps.state.preparedMedia={breakId:a.id,assetId:o[0].id,uri:l,player:d},this.deps.log("Preload complete for break:",a.id)}catch(e){this.deps.log("Preload failed (will load on demand):",e)}}else{if(this.deps.log("Preloading ad (single-decoder, detached):",l),this.diagnosePreloadPath(a.id,l,"single-decoder"),await this.warmCache([l]),!this.ownsPreload(i,d))return;try{await(d.preload?.(l))}catch(e){this.deps.log("Detached prefetch failed (will load on demand):",e)}}}else this.deps.log("No valid URI for preload")}ownsPreload(e,t){return e===this.deps.state.preloadGeneration&&!this.deps.isPlaying()&&t===this.deps.getAdPlayer()}async preloadVastAsset(e,s){const a=this.deps.getVariant(s),r=a?.format??t.SINGLE;if(!n({format:r,ssai:!1}).admitted)return void this.deps.log("VAST preload skipped: format not supported",r);if(!l())return void this.deps.log("VAST preload skipped: IMA client-side SDK unavailable");const i=this.deps.getAdPlayer(),d=this.deps.getAdContainer();if(!i||!d)return void this.deps.log("VAST preload skipped: ad player not initialized");const o=i.videoElement;if(!o)return void this.deps.log("VAST preload skipped: no ad video element");const h=this.deps.buildVastTagUrl(e,s);if(h){this.deps.state.vastAdManager||(this.deps.state.vastAdManager=new p(d,o,this.deps.debug)),this.deps.log("Preloading VAST asset via IMA CSAI:",h);try{await this.deps.state.vastAdManager.preloadVast(h,{muted:i.muted}),this.deps.log("VAST preload complete for break:",s.id)}catch(t){const a=t instanceof Error?t:new Error(String(t));this.deps.log("VAST preload failed (will load on demand):",a.message),this.deps.diagnose("DA-PRELOAD-FAILED",a.message,{breakId:s.id,assetId:e.id})}}else this.deps.log("No valid VAST tag URL for preload")}preloadChainedSuccessor(e){const t=this.deps.resolveChainedSuccessor(e);t&&this.preloadNext(t)}clearWarmedBreaks(){this.warmedBreakIds.clear()}async preloadNext(s){if(this.warmedBreakIds.has(s.id))return;this.preloadCompanion(s);const a=this.deps.getVariant(s);if(!a||a.format===t.LSHAPE_CONTENT)return;const r=this.deps.getAssets(s)[0];if(!r||r.type!==e.STATIC)return void this.deps.log("Chained successor first asset not static; skipping media warm:",s.id);const i=this.deps.resolveUri(r.uri);if(i){this.warmedBreakIds.add(s.id),this.deps.log("Warming chained successor media:",s.id,i),await this.warmCache([i]);try{await(this.deps.getAdPlayer()?.preload?.(i))}catch(e){this.deps.log("Chained prefetch failed (will load on demand):",e)}}}revealFirstFrame(){if(!this.deps.state.firstFrameGateActive||this.deps.state.firstFrameGateShown)return;const e=this.deps.getCurrentBreak();if(!e)return;this.deps.state.firstFrameGateShown=!0,this.deps.show();const t=this.deps.getVariant(e);t&&this.deps.reapplyAdContainerLayout(t),this.deps.onBreakVisible(e)}armAdStartBudget(e){const t=1e3*((this.deps.state.effectiveBreakDurationSec??this.deps.state.currentBreak?.duration??0)+this.deps.state.adBreakCutSafetyMarginSec),s=i(e)&&e.vendor===a.GAM?this.deps.gamPodAdStartBudgetMs:this.deps.adStartBudgetMs,r=Math.max(Math.min(t-100,s),this.deps.minAdStartBudgetMs);this.deps.state.adStartBudgetMs=r,this.deps.state.adStartDeadlineMs=this.deps.clock.now()+r}remainingAdStartBudgetMs(){return Math.max(0,(this.deps.state.adStartDeadlineMs??this.deps.clock.now())-this.deps.clock.now())}loadWithinBudget(e,t){const s=this.deps.state.preloadGeneration,a=this.remainingAdStartBudgetMs(),r=this.deps.clock.now();return new Promise((i,d)=>{let o=!1,n=!1;const l=e=>{o||(o=!0,clearTimeout(p),null!==h&&clearTimeout(h),e.ok?i(!0):d(e.error))},p=setTimeout(()=>{s===this.deps.state.preloadGeneration&&this.deps.onAdError(new m("Ad load timeout","DA-AD-START-TIMEOUT")),o||(o=!0,null!==h&&clearTimeout(h),i(!1))},a);let h=null;a>2e3&&(h=setTimeout(()=>{if(o||s!==this.deps.state.preloadGeneration)return;const a=this.deps.clock.now()-r,i=this.deps.getAdPlayer()?.videoElement;adLoadShowsNoProgress(i)?(n=!0,this.deps.log(`Ad load for ${t.assetId} stalled after ${a}ms; retrying once`),this.deps.diagnose("DA-AD-LOAD-RETRIED",`Ad load for ${t.assetId} in break ${t.breakId} had not settled after ${a}ms; issued a second load()`,{breakId:t.breakId,assetId:t.assetId,uri:t.uri,elapsedMs:a,remainingBudgetMs:this.remainingAdStartBudgetMs()}),e().then(()=>l({ok:!0}),e=>l({ok:!1,error:e}))):this.deps.log(`Ad load for ${t.assetId} still pending after ${a}ms but the element is progressing (readyState=${i?.readyState}, buffered=${i?.buffered.length}); no retry`)},2e3)),e().then(()=>l({ok:!0}),e=>{n||l({ok:!1,error:e})})})}async waitForFirstFrame(){return new Promise((e,t)=>{this.deps.state.firstFrameResolver=()=>{this.clearFirstFrameResolver(),e()},this.deps.state.firstFrameRejector=e=>{this.clearFirstFrameResolver(),t(e)},this.deps.state.firstFrameTimeout=setTimeout(()=>{this.deps.onAdError(new m("First frame timeout","DA-AD-START-TIMEOUT"))},this.remainingAdStartBudgetMs())})}clearFirstFrame(e="Break ended before first frame"){if(this.deps.state.adStartDeadlineMs=null,this.deps.state.adStartBudgetMs=0,this.deps.state.firstFrameTimeout&&(clearTimeout(this.deps.state.firstFrameTimeout),this.deps.state.firstFrameTimeout=null),this.deps.state.firstFrameRejector){const t=this.deps.state.firstFrameRejector;this.deps.state.firstFrameResolver=null,this.deps.state.firstFrameRejector=null,t(new Error(e))}}clearFirstFrameResolver(){this.deps.state.adStartDeadlineMs=null,this.deps.state.adStartBudgetMs=0,this.deps.state.firstFrameTimeout&&(clearTimeout(this.deps.state.firstFrameTimeout),this.deps.state.firstFrameTimeout=null),this.deps.state.firstFrameResolver=null,this.deps.state.firstFrameRejector=null}preloadCompanion(s){const a=this.deps.getCompanionVariant(s);if(!a)return;if(a.format===t.LSHAPE_CONTENT){const t=a.assets[0];if(!t||t.type!==e.STATIC)return;const s=this.deps.resolveUri(t.uri);if(!s)return;return(new Image).src=s,void this.deps.log("Preloading lshape_content backdrop:",s)}if(a.format!==t.DOUBLE&&a.format!==t.LSHAPE_AD)return;const r=a.assets[0]?.companion;if(!r||r.type!==e.STATIC)return;const i=this.deps.resolveUri(r.uri);if(!i)return;(new Image).src=i,this.deps.log("Preloading companion:",i)}async warmCache(e){"function"==typeof fetch&&await Promise.allSettled(e.map(e=>o(e,{mode:"no-cors"},d).catch(()=>{})))}}
|
|
@@ -31,15 +31,15 @@ export interface AdStageDeps {
|
|
|
31
31
|
isChaining: () => boolean;
|
|
32
32
|
log: (...args: unknown[]) => void;
|
|
33
33
|
}
|
|
34
|
-
/** Owns the overlay stage geometry, break layout
|
|
34
|
+
/** Owns the overlay stage geometry, break layout and companions. The content/ad switch is a cut; only the layout reflow glides. */
|
|
35
35
|
export declare class AdStage {
|
|
36
36
|
private readonly deps;
|
|
37
37
|
private contentVideoGeometryHandler;
|
|
38
38
|
private contentVideoMutationObserver;
|
|
39
39
|
private resizeObserver;
|
|
40
40
|
private resizePoll;
|
|
41
|
-
|
|
42
|
-
private
|
|
41
|
+
/** Layout whose box currently holds the content; `null` while the content is full-size. */
|
|
42
|
+
private contentLayout;
|
|
43
43
|
private preparedLease;
|
|
44
44
|
private unsubscribeGeometry;
|
|
45
45
|
constructor(deps: AdStageDeps);
|
|
@@ -54,6 +54,10 @@ export declare class AdStage {
|
|
|
54
54
|
applyLayout(variant: BreakVariant): void;
|
|
55
55
|
restoreLayout(format?: BreakFormat): void;
|
|
56
56
|
renderCompanion(assets: AssetWithCompanion[], cssText: string): void;
|
|
57
|
+
/** Shows `img` as the stage companion and retires the previous one, cross-fading between them. */
|
|
58
|
+
private installCompanion;
|
|
59
|
+
/** Removes that specific companion element at once (a cut, like the ad surface). */
|
|
60
|
+
private retireCompanion;
|
|
57
61
|
clearOverlayImage(): void;
|
|
58
62
|
show(): void;
|
|
59
63
|
hide(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getPreparedPresentation as e}from"./PreparedPresentation";import{AssetType as t,BreakFormat as s}from"../../types/BreakManifest";const i=16/9;export class AdStage{constructor(e){this.deps=e,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.resizePoll=null,this.companionRemoveTimer=null,this.adFadeTimer=null,this.preparedLease=null,this.unsubscribeGeometry=null}setupOverlay(){const t=e(this.deps.container);if(t){const e=this.deps.getPlayerContainer();if(e&&e!==t.playerContainer)throw new Error("The prepared content container cannot be replaced");this.preparedLease=t.acquire(),this.deps.setStage(this.preparedLease.stage),this.deps.setPlayerContainer(this.preparedLease.playerContainer),this.unsubscribeGeometry=this.preparedLease.subscribe(()=>this.deps.resizeVast());try{this.setupMediaLayer()}catch(e){const t=this.deps.getAdContainer();throw t?.parentNode?.removeChild(t),this.deps.setAdContainer(null),this.deps.setAdMediaContainer(null),this.unsubscribeGeometry?.(),this.preparedLease.release(),e}return}const s=window.getComputedStyle(this.deps.container);"static"!==s.position&&""!==s.position||(this.deps.container.style.position="relative"),this.deps.setStage(document.createElement("div"));const i=this.deps.getStage();if(!i)return;if(i.className="dolby-stage",i.style.cssText="position:absolute;overflow:hidden;",this.deps.container.appendChild(i),this.contentVideoGeometryHandler=()=>this.refreshStageGeometry(),"undefined"!=typeof MutationObserver&&(this.contentVideoMutationObserver=new MutationObserver(()=>{(this.deps.contentPlayer.videoElement??null)!==this.deps.getContentVideo()&&this.refreshStageGeometry()}),this.contentVideoMutationObserver.observe(this.deps.container,{childList:!0,subtree:!0})),"undefined"!=typeof ResizeObserver)this.resizeObserver=new ResizeObserver(()=>this.refreshStageGeometry()),this.resizeObserver.observe(this.deps.container);else{let e=this.deps.container.getBoundingClientRect();this.resizePoll=setInterval(()=>{const t=this.deps.container.getBoundingClientRect();t.width===e.width&&t.height===e.height||(e=t,this.refreshStageGeometry())},250)}if(this.refreshStageGeometry(),this.deps.getPlayerContainer()){const e=this.deps.getPlayerContainer();if(!e)return;i.appendChild(e)}else{const e=document.createElement("div"),t=Array.from(this.deps.container.childNodes).filter(e=>e!==i);for(const s of t)e.appendChild(s);i.appendChild(e),this.deps.setPlayerContainer(e),this.deps.onPlayerContainerCreated()}const o=this.deps.getPlayerContainer();o&&(this.deps.setPlayerContainerOriginalStyle(o.style.cssText),o.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;",this.setupMediaLayer())}setupMediaLayer(){const e=this.deps.getStage();if(!e)throw new Error("An ads presentation stage is required");this.deps.setAdContainer(document.createElement("div"));const t=this.deps.getAdContainer();if(!t)return;t.className="dolby-ad-container",t.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;",this.deps.setAdMediaContainer(document.createElement("div"));const s=this.deps.getAdMediaContainer();s&&(s.style.cssText="display:block;width:100%;height:100%;",t.appendChild(s),e.appendChild(t),this.deps.setAdPlayer(this.deps.createAdAdapter(s)),this.deps.setupEventListeners(),this.deps.log("Ad player overlay initialized"))}refreshStageGeometry(){this.rebindContentVideoGeometryListener(),this.updateStageGeometry(),this.deps.resizeVast()}rebindContentVideoGeometryListener(){const e=this.deps.contentPlayer.videoElement??null,t=e&&"function"==typeof e.addEventListener&&"function"==typeof e.removeEventListener?e:null;t!==this.deps.getContentVideo()&&(this.deps.getContentVideo()&&this.contentVideoGeometryHandler&&(this.deps.getContentVideo()?.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),this.deps.getContentVideo()?.removeEventListener("resize",this.contentVideoGeometryHandler)),this.deps.setContentVideo(t),t&&this.contentVideoGeometryHandler&&(t.addEventListener("loadedmetadata",this.contentVideoGeometryHandler),t.addEventListener("resize",this.contentVideoGeometryHandler)))}updateStageGeometry(){const e=this.deps.getStage();if(!e)return;const{width:t,height:s}=this.deps.container.getBoundingClientRect();if(0===t||0===s)return;const i=this.getContentAspectRatio();let o,n;t/s>i?(n=s,o=s*i):(o=t,n=t/i);const r=(s-n)/2,a=(t-o)/2;e.style.width=`${o}px`,e.style.height=`${n}px`,e.style.top=`${r}px`,e.style.left=`${a}px`}getContentAspectRatio(){const e=this.deps.contentPlayer.videoElement;if(!e)return i;const{videoWidth:t,videoHeight:s}=e;return!Number.isFinite(t)||!Number.isFinite(s)||t<=0||s<=0?i:t/s}renderLshapeContentBackdrop(e){const s=e.assets[0];if(!s||s.type!==t.STATIC)return;const i=this.deps.resolveUri(s.uri);if(!i)return;const o=document.createElement("img");o.className="dolby-companion",o.src=i,o.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;";const n=this.deps.getStage();n&&(n.appendChild(o),this.deps.setCompanion(o),requestAnimationFrame(()=>{const e=this.deps.getCompanion();e&&(e.style.opacity="1")}),this.deps.log("Rendered lshape_content backdrop:",i))}reapplyAdContainerLayout(e){const t=this.deps.getAdContainer();if(t){switch(e.format){case s.LSHAPE_AD:t.style.position="absolute",t.style.top="5%",t.style.left="5%",t.style.right="30%",t.style.bottom="30%",t.style.width="",t.style.height="",t.style.zIndex="100";break;case s.DOUBLE:t.style.position="absolute",t.style.top="26.04%",t.style.left="51.04%",t.style.right="1.04%",t.style.bottom="26.04%",t.style.width="",t.style.height="",t.style.zIndex="100"}this.deps.log("reapplyAdContainerLayout: re-stamped adContainer CSS for format:",e.format)}}applyLayout(e){const t=this.deps.getAdContainer(),i=this.deps.getPlayerContainer();if(!t||!i)return;const o="transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;";switch(e.format){case s.SINGLE:t.style.cssText="display:none;opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;";break;case s.DOUBLE:i.style.cssText=`position:absolute;top:26.04%;left:1.04%;right:51.04%;bottom:26.04%;z-index:101;${o}`,t.style.cssText="display:none;opacity:0;position:absolute;top:26.04%;left:51.04%;right:1.04%;bottom:26.04%;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;");break;case s.LSHAPE_AD:i.style.cssText=`position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:101;opacity:0;${o}`,t.style.cssText="display:none;opacity:0;position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;");break;case s.LSHAPE_CONTENT:i.style.top="5%",i.style.left="5%",i.style.right="30%",i.style.bottom="30%",i.style.zIndex="101";break;case s.OVERLAY:{const s=e,i=e=>void 0!==e?100*e+"%":"auto",o=i(s.position.top),n=i(s.position.bottom),r=i(s.position.left),a=i(s.position.right),d=100*s.size.width+"%",l=100*s.size.height+"%",p=s.opacity??1;t.style.cssText=`display:none;position:absolute;top:${o};bottom:${n};left:${r};right:${a};width:${d};height:${l};z-index:100;opacity:${p};`;break}default:t.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;"}}restoreLayout(e){if(this.preparedLease&&!this.preparedLease.isActive())return;const t=e===s.DOUBLE||e===s.LSHAPE_AD||e===s.LSHAPE_CONTENT,i=this.deps.getPlayerContainer();if(!i)return;i.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;"+(t?"z-index:101;":"")+"transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;",this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer);const o=this.deps.getCompanion();o&&(o.style.opacity="0"),this.companionRemoveTimer=setTimeout(()=>{const e=this.deps.getCompanion();e?.parentElement&&(e.parentElement.removeChild(e),this.deps.setCompanion(null)),this.companionRemoveTimer=null;const s=this.deps.getPlayerContainer();t&&s&&(s.style.zIndex="")},300)}renderCompanion(e,s){const i=e[0]?.companion;if(!i)return;const o=i.type===t.STATIC?this.deps.resolveUri(i.uri):null;if(!o)return;const n=document.createElement("img");n.className="dolby-companion",n.src=o,n.style.cssText=s;const r=this.deps.getStage();r&&(r.appendChild(n),this.deps.setCompanion(n),requestAnimationFrame(()=>{const e=this.deps.getCompanion();e&&(e.style.opacity="1")}))}clearOverlayImage(){const e=this.deps.getImageAssetTimer();e&&(clearTimeout(e),this.deps.setImageAssetTimer(null));const t=this.deps.getOverlayImage();t?.parentElement&&t.parentElement.removeChild(t),this.deps.setOverlayImage(null)}show(){const e=this.deps.getAdContainer();if(!e)return;this.adFadeTimer&&(clearTimeout(this.adFadeTimer),this.adFadeTimer=null);const t=this.deps.getCurrentBreak(),i=t?this.deps.getVariant(t):null;e.style.display="block",i?.format!==s.OVERLAY&&(this.deps.isChaining()?e.style.opacity="1":(e.style.opacity="0",requestAnimationFrame(()=>{const e=this.deps.getAdContainer();e&&(e.style.opacity="1")})))}hide(){const e=this.deps.getAdContainer();if(!e)return;const t=this.deps.getCurrentBreak(),i=t?this.deps.getVariant(t):null;i?.format!==s.OVERLAY?(e.style.opacity="0",this.adFadeTimer=setTimeout(()=>{const e=this.deps.getAdContainer();e&&(e.style.display="none"),this.adFadeTimer=null},300)):e.style.display="none"}restoreContent(e,t){if(this.releasePreparedPresentation())return!0;const s=this.deps.getPlayerContainer();if(!s)return!1;if(e)for(;s.firstChild;)this.deps.container.appendChild(s.firstChild);else this.deps.container.appendChild(s),s.style.cssText=t;return!1}releasePreparedPresentation(){return!!this.preparedLease&&(this.unsubscribeGeometry?.(),this.unsubscribeGeometry=null,this.preparedLease.release(),!0)}destroy(){this.unsubscribeGeometry?.(),this.unsubscribeGeometry=null;const e=this.deps.getContentVideo();e&&this.contentVideoGeometryHandler&&(e.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),e.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoMutationObserver?.disconnect(),this.resizeObserver?.disconnect(),null!==this.resizePoll&&clearInterval(this.resizePoll),this.resizePoll=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.contentVideoGeometryHandler=null,this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer),this.adFadeTimer&&clearTimeout(this.adFadeTimer)}}
|
|
1
|
+
import{getPreparedPresentation as e}from"./PreparedPresentation";import{AssetType as t,BreakFormat as s}from"../../types/BreakManifest";const i=16/9;export class AdStage{constructor(e){this.deps=e,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.resizePoll=null,this.contentLayout=null,this.preparedLease=null,this.unsubscribeGeometry=null}setupOverlay(){const t=e(this.deps.container);if(t){const e=this.deps.getPlayerContainer();if(e&&e!==t.playerContainer)throw new Error("The prepared content container cannot be replaced");this.preparedLease=t.acquire(),this.deps.setStage(this.preparedLease.stage),this.deps.setPlayerContainer(this.preparedLease.playerContainer),this.unsubscribeGeometry=this.preparedLease.subscribe(()=>this.deps.resizeVast());try{this.setupMediaLayer()}catch(e){const t=this.deps.getAdContainer();throw t?.parentNode?.removeChild(t),this.deps.setAdContainer(null),this.deps.setAdMediaContainer(null),this.unsubscribeGeometry?.(),this.preparedLease.release(),e}return}const s=window.getComputedStyle(this.deps.container);"static"!==s.position&&""!==s.position||(this.deps.container.style.position="relative"),this.deps.setStage(document.createElement("div"));const i=this.deps.getStage();if(!i)return;if(i.className="dolby-stage",i.style.cssText="position:absolute;overflow:hidden;",this.deps.container.appendChild(i),this.contentVideoGeometryHandler=()=>this.refreshStageGeometry(),"undefined"!=typeof MutationObserver&&(this.contentVideoMutationObserver=new MutationObserver(()=>{(this.deps.contentPlayer.videoElement??null)!==this.deps.getContentVideo()&&this.refreshStageGeometry()}),this.contentVideoMutationObserver.observe(this.deps.container,{childList:!0,subtree:!0})),"undefined"!=typeof ResizeObserver)this.resizeObserver=new ResizeObserver(()=>this.refreshStageGeometry()),this.resizeObserver.observe(this.deps.container);else{let e=this.deps.container.getBoundingClientRect();this.resizePoll=setInterval(()=>{const t=this.deps.container.getBoundingClientRect();t.width===e.width&&t.height===e.height||(e=t,this.refreshStageGeometry())},250)}if(this.refreshStageGeometry(),this.deps.getPlayerContainer()){const e=this.deps.getPlayerContainer();if(!e)return;i.appendChild(e)}else{const e=document.createElement("div"),t=Array.from(this.deps.container.childNodes).filter(e=>e!==i);for(const s of t)e.appendChild(s);i.appendChild(e),this.deps.setPlayerContainer(e),this.deps.onPlayerContainerCreated()}const n=this.deps.getPlayerContainer();n&&(this.deps.setPlayerContainerOriginalStyle(n.style.cssText),n.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out;",this.setupMediaLayer())}setupMediaLayer(){const e=this.deps.getStage();if(!e)throw new Error("An ads presentation stage is required");this.deps.setAdContainer(document.createElement("div"));const t=this.deps.getAdContainer();if(!t)return;t.className="dolby-ad-container",t.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;",this.deps.setAdMediaContainer(document.createElement("div"));const s=this.deps.getAdMediaContainer();if(!s)return;s.style.cssText="display:block;width:100%;height:100%;",t.appendChild(s),e.appendChild(t);const i=this.deps.createAdAdapter(s);i!==this.deps.contentPlayer&&i.prepareForAdPlayback?.(),this.deps.setAdPlayer(i),this.deps.setupEventListeners(),this.deps.log("Ad player overlay initialized")}refreshStageGeometry(){this.rebindContentVideoGeometryListener(),this.updateStageGeometry(),this.deps.resizeVast()}rebindContentVideoGeometryListener(){const e=this.deps.contentPlayer.videoElement??null,t=e&&"function"==typeof e.addEventListener&&"function"==typeof e.removeEventListener?e:null;t!==this.deps.getContentVideo()&&(this.deps.getContentVideo()&&this.contentVideoGeometryHandler&&(this.deps.getContentVideo()?.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),this.deps.getContentVideo()?.removeEventListener("resize",this.contentVideoGeometryHandler)),this.deps.setContentVideo(t),t&&this.contentVideoGeometryHandler&&(t.addEventListener("loadedmetadata",this.contentVideoGeometryHandler),t.addEventListener("resize",this.contentVideoGeometryHandler)))}updateStageGeometry(){const e=this.deps.getStage();if(!e)return;const{width:t,height:s}=this.deps.container.getBoundingClientRect();if(0===t||0===s)return;const i=this.getContentAspectRatio();let n,o;t/s>i?(o=s,n=s*i):(n=t,o=t/i);const r=(s-o)/2,a=(t-n)/2;e.style.width=`${n}px`,e.style.height=`${o}px`,e.style.top=`${r}px`,e.style.left=`${a}px`}getContentAspectRatio(){const e=this.deps.contentPlayer.videoElement;if(!e)return i;const{videoWidth:t,videoHeight:s}=e;return!Number.isFinite(t)||!Number.isFinite(s)||t<=0||s<=0?i:t/s}renderLshapeContentBackdrop(e){const s=e.assets[0];if(!s||s.type!==t.STATIC)return;const i=this.deps.resolveUri(s.uri);if(!i)return;const n=document.createElement("img");n.className="dolby-companion",n.src=i,n.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;";const o=this.deps.getStage();o&&(this.installCompanion(n,o),this.deps.log("Rendered lshape_content backdrop:",i))}reapplyAdContainerLayout(e){const t=this.deps.getAdContainer();if(t){switch(e.format){case s.LSHAPE_AD:t.style.position="absolute",t.style.top="5%",t.style.left="5%",t.style.right="30%",t.style.bottom="30%",t.style.width="",t.style.height="",t.style.zIndex="100";break;case s.DOUBLE:t.style.position="absolute",t.style.top="26.04%",t.style.left="51.04%",t.style.right="1.04%",t.style.bottom="26.04%",t.style.width="",t.style.height="",t.style.zIndex="100"}this.deps.log("reapplyAdContainerLayout: re-stamped adContainer CSS for format:",e.format)}}applyLayout(e){const t=this.deps.getAdContainer(),i=this.deps.getPlayerContainer();if(!t||!i)return;const n="transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out;";switch(e.format){case s.SINGLE:this.contentLayout&&(i.style.opacity="0"),t.style.cssText="display:none;opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;";break;case s.DOUBLE:i.style.cssText=`position:absolute;top:26.04%;left:1.04%;right:51.04%;bottom:26.04%;z-index:101;${n}`,t.style.cssText="display:none;opacity:0;position:absolute;top:26.04%;left:51.04%;right:1.04%;bottom:26.04%;z-index:100;background:#000;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;"),this.contentLayout=e.format;break;case s.LSHAPE_AD:i.style.cssText=`position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:101;opacity:0;${n}`,t.style.cssText="display:none;opacity:0;position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:100;background:#000;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;"),this.contentLayout=e.format;break;case s.LSHAPE_CONTENT:i.style.top="5%",i.style.left="5%",i.style.right="30%",i.style.bottom="30%",i.style.zIndex="101",this.contentLayout=e.format;break;case s.OVERLAY:{const s=e,i=e=>void 0!==e?100*e+"%":"auto",n=i(s.position.top),o=i(s.position.bottom),r=i(s.position.left),a=i(s.position.right),d=100*s.size.width+"%",l=100*s.size.height+"%",p=s.opacity??1;t.style.cssText=`display:none;position:absolute;top:${n};bottom:${o};left:${r};right:${a};width:${d};height:${l};z-index:100;opacity:${p};`;break}default:t.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;"}}restoreLayout(e){if(this.preparedLease&&!this.preparedLease.isActive())return;const t=this.deps.getPlayerContainer();if(!t)return;const i=function(e){return e===s.DOUBLE||e===s.LSHAPE_AD||e===s.LSHAPE_CONTENT}(e)||null!==this.contentLayout;this.contentLayout=null,t.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;"+(i?"z-index:101;":"")+"transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out;";const n=this.deps.getCompanion();n&&(this.deps.setCompanion(null),this.retireCompanion(n)),i&&(t.style.zIndex="")}renderCompanion(e,s){const i=e[0]?.companion;if(!i)return;const n=i.type===t.STATIC?this.deps.resolveUri(i.uri):null;if(!n)return;const o=document.createElement("img");o.className="dolby-companion",o.src=n,o.style.cssText=s;const r=this.deps.getStage();r&&this.installCompanion(o,r)}installCompanion(e,t){const s=this.deps.getCompanion();t.appendChild(e),this.deps.setCompanion(e),e.style.opacity="1",s&&s!==e&&this.retireCompanion(s)}retireCompanion(e){e.parentElement?.removeChild(e)}clearOverlayImage(){const e=this.deps.getImageAssetTimer();e&&(clearTimeout(e),this.deps.setImageAssetTimer(null));const t=this.deps.getOverlayImage();t?.parentElement&&t.parentElement.removeChild(t),this.deps.setOverlayImage(null)}show(){const e=this.deps.getAdContainer();if(!e)return;const t=this.deps.getCurrentBreak(),i=t?this.deps.getVariant(t):null;e.style.display="block",i?.format!==s.OVERLAY&&(e.style.opacity="1")}hide(){const e=this.deps.getAdContainer();e&&(e.style.opacity="0",e.style.display="none")}restoreContent(e,t){if(this.releasePreparedPresentation())return!0;const s=this.deps.getPlayerContainer();if(!s)return!1;if(e)for(;s.firstChild;)this.deps.container.appendChild(s.firstChild);else this.deps.container.appendChild(s),s.style.cssText=t;return!1}releasePreparedPresentation(){return!!this.preparedLease&&(this.unsubscribeGeometry?.(),this.unsubscribeGeometry=null,this.preparedLease.release(),!0)}destroy(){this.unsubscribeGeometry?.(),this.unsubscribeGeometry=null;const e=this.deps.getContentVideo();e&&this.contentVideoGeometryHandler&&(e.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),e.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoMutationObserver?.disconnect(),this.resizeObserver?.disconnect(),null!==this.resizePoll&&clearInterval(this.resizePoll),this.resizePoll=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.contentVideoGeometryHandler=null,this.contentLayout=null}}
|
|
@@ -4,6 +4,7 @@ import { AssetParameterMacros } from '../assetParameterMacros';
|
|
|
4
4
|
import type { GamStreamManager } from '../GamStreamManager';
|
|
5
5
|
import type { AdPlayerState } from './AdPlayerState';
|
|
6
6
|
import type { AssetParameterMacroUpdates } from '../../types/OptiViewAdsConfig';
|
|
7
|
+
import type { ExternalVendorConfiguration } from '../resolveExternalVendorConfigurations';
|
|
7
8
|
export interface AssetParameterResolverDeps {
|
|
8
9
|
contentPlayer: PlayerAdapter;
|
|
9
10
|
container: HTMLElement;
|
|
@@ -24,6 +25,7 @@ export declare class AssetParameterResolver {
|
|
|
24
25
|
updateAssetParameterMacros(macros: AssetParameterMacroUpdates): void;
|
|
25
26
|
setSessionAssetParameterMacros(macros: AssetParameterMacros | undefined): void;
|
|
26
27
|
getSessionAssetParameterMacros(): AssetParameterMacros;
|
|
28
|
+
resolveExternalVendorConfiguration(entry: ExternalVendorConfiguration): ExternalVendorConfiguration;
|
|
27
29
|
/**
|
|
28
30
|
* Substitute the `$` macros in an effective (already layered) parameter map.
|
|
29
31
|
* 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
|
|
1
|
+
import{isVendorAsset as e}from"../../types/BreakManifest";import{appendQueryParameters as s,applyAssetParameterMacros as t}from"../assetParameterMacros";import{buildAssetParameterMacroTable as a}from"../assetParameterMacroTable";import{objectEntries as r}from"../runtimeObject";import{resolveAssetParameters as i}from"../resolveAssetParameters";import{detectDeviceProfile as o}from"../detectDeviceType";import{resolveTargetedUri as n}from"../resolveTargetedUri";import{resolveExternalVendorParameters as d}from"../resolveExternalVendorConfigurations";function m(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(m(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 r(e))void 0===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}}resolveExternalVendorConfiguration(e){const s=d(e,i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,void 0,void 0),a({customerMacros:this.state.sessionAssetParameterMacros,element:this.deps.container,breakDurationSec:void 0}));for(const{key:e,macro:t}of s.unresolved)this.deps.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${t}': the macro has no value.`,{key:e,macro:t});for(const{key:e,macro:t}of s.omitted)this.deps.diagnose("DA-ASSET-PARAMETER-OMITTED",`Asset parameter '${e}' not sent: macro '${t}' is empty.`,{key:e,macro:t});return s.configuration}resolveAssetParameterMacros(e,s,r,i,o){const n=t(e,a({customerMacros:this.state.sessionAssetParameterMacros,element:this.deps.container,breakDurationSec:"session"===r?void 0:s}),i);for(const{key:e,macro:s}of n.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});for(const{key:e,macro:s,pair:t}of n.omitted)this.deps.diagnose("DA-ASSET-PARAMETER-OMITTED",`Asset parameter '${e}' not sent: macro '${s}' is empty.`,{...o,key:e,macro:s,...void 0!==t?{pair:t}:{}});return n.params}resolveSessionAdTagParameters(){const e=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,void 0);return this.resolveAssetParameterMacros(e,void 0,"session","gam",{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","vast",{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","gam",{breakId:a?.id,assetId:s});null!==this.state.pushedAssetParameters&&m(this.state.pushedAssetParameters,o)||(this.state.pushedAssetParameters=o,t.replaceAdTagParameters(o??{}))}resolveUri(e){if(void 0===this.state.detectedDeviceProfile){const e=o();void 0!==this.state.detectedDeviceType?e.deviceType=this.state.detectedDeviceType:this.state.detectedDeviceType=e.deviceType,this.state.detectedDeviceProfile=e}return n(e,this.state.detectedDeviceProfile??this.state.detectedDeviceType,e=>this.deps.diagnose(e.code,`Targeting ${e.field} was rejected.`,{field:e.field,...void 0===e.value?{}:{value:e.value}}))}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as i}from"../../types/BreakManifest";import{AdError as a}from"../../types/AdError";import{resolveVastAdmission as d}from"../resolveVastAdmission";import{classifyMediaError as n,toClassifiedAdError as o}from"./classifyMediaError";import{VastAdManager as h,isImaCsaiSdkAvailable as p}from"../VastAdManager";import{invalidateMediaPreload as c}from"./AdPlayerState";export class AssetPlayback{constructor(e,t){this.state=e,this.deps=t}get currentBreak(){return this.state.currentBreak}async playAsset(e,t,s){const d=this.currentBreak,n=this.deps.getAdPlayer();if(!d||!n)return;const h=this.state.preparedMedia,p=c(this.state),l=()=>this.state.preloadGeneration===p&&this.currentBreak===d&&this.deps.getAdPlayer()===n;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),i(e))return void this.playVastAsset(e,t,s);if(e.mediaType===r.IMAGE)return void await this.playImageAsset(e,t,s);const u=await this.resolveAssetUri(e,this.state.effectiveBreakDurationSec??d.duration);if(!l())return;if(!u)return void this.deps.onAdError(this.missingUriError(e));this.deps.log("Resolved URI:",u),this.deps.reportPhase("uri-resolved");const A=d.id;try{this.deps.armAdStartBudget(e);if(0===t&&h?.breakId===A&&h.assetId===e.id&&h.uri===u&&h.player===n)this.deps.reportPhase("load-skipped-preloaded");else{if(this.deps.reportPhase("load-start"),!await this.deps.loadWithinBudget(()=>n.load(u),{breakId:A,assetId:e.id,uri:u})||!l())return;this.deps.reportPhase("load-resolved")}this.reportPreparedDuration(n,d,e,t,s);const r=n.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(n,A),await e}else{let e=null;const t=await Promise.race([this.playRetryingInterruption(n,A).then(()=>!0),new Promise(t=>{e=setTimeout(()=>t(!1),this.deps.remainingAdStartBudgetMs())})]);if(e&&clearTimeout(e),!l())return;if(!t)return void this.deps.onAdError(new a(`Ad did not start within ${this.state.adStartBudgetMs}ms ad-start budget`,"DA-AD-START-TIMEOUT"))}if(!l())return;this.state.adStartDeadlineMs=null,this.state.adStartBudgetMs=0,this.deps.transferPictureInPictureToAd();const i=this.deps.getSequencer();i?(this.deps.isGamPodAsset(e)&&this.deps.beginGamPodTracking(e),await this.deps.drive(i.onOutcome({kind:"asset-started"}))):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s)}catch(e){if(!l())return;
|
|
1
|
+
import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as i}from"../../types/BreakManifest";import{AdError as a}from"../../types/AdError";import{resolveVastAdmission as d}from"../resolveVastAdmission";import{classifyMediaError as n,toClassifiedAdError as o}from"./classifyMediaError";import{VastAdManager as h,isImaCsaiSdkAvailable as p}from"../VastAdManager";import{invalidateMediaPreload as c}from"./AdPlayerState";export class AssetPlayback{constructor(e,t){this.state=e,this.deps=t}get currentBreak(){return this.state.currentBreak}async playAsset(e,t,s){const d=this.currentBreak,n=this.deps.getAdPlayer();if(!d||!n)return;const h=this.state.preparedMedia,p=c(this.state),l=()=>this.state.preloadGeneration===p&&this.currentBreak===d&&this.deps.getAdPlayer()===n;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),i(e))return void this.playVastAsset(e,t,s);if(e.mediaType===r.IMAGE)return void await this.playImageAsset(e,t,s);const u=await this.resolveAssetUri(e,this.state.effectiveBreakDurationSec??d.duration);if(!l())return;if(!u)return void this.deps.onAdError(this.missingUriError(e));this.deps.log("Resolved URI:",u),this.deps.reportPhase("uri-resolved");const A=d.id;try{this.deps.armAdStartBudget(e);if(0===t&&h?.breakId===A&&h.assetId===e.id&&h.uri===u&&h.player===n)this.deps.reportPhase("load-skipped-preloaded");else{if(this.deps.reportPhase("load-start"),!await this.deps.loadWithinBudget(()=>n.load(u),{breakId:A,assetId:e.id,uri:u})||!l())return;this.deps.reportPhase("load-resolved")}this.reportPreparedDuration(n,d,e,t,s);const r=n.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(n,A),await e}else{let e=null;const t=await Promise.race([this.playRetryingInterruption(n,A).then(()=>!0),new Promise(t=>{e=setTimeout(()=>t(!1),this.deps.remainingAdStartBudgetMs())})]);if(e&&clearTimeout(e),!l())return;if(!t)return void this.deps.onAdError(new a(`Ad did not start within ${this.state.adStartBudgetMs}ms ad-start budget`,"DA-AD-START-TIMEOUT"))}if(!l())return;this.state.adStartDeadlineMs=null,this.state.adStartBudgetMs=0,this.deps.transferPictureInPictureToAd();const i=this.deps.getSequencer();i?(this.deps.isGamPodAsset(e)&&this.deps.beginGamPodTracking(e),await this.deps.drive(i.onOutcome({kind:"asset-started"}))):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s)}catch(e){if(!l())return;this.deps.onAdError(o(e))}}reportPreparedDuration(e,t,s,r,i){const a=e.duration;if(!Number.isFinite(a)||a<=0)return;const d=s.duration;this.deps.diagnose("DA-AD-DURATION-PREPARED",`Ad media for break ${t.id} prepared: ${a}s of playable ad`,{breakId:t.id,assetId:s.id,index:r,total:i,preparedDurationSec:a,..."number"==typeof d?{declaredDurationSec:d}:{},breakDurationSec:this.state.effectiveBreakDurationSec??t.duration})}async playRetryingInterruption(e,t){const s=this.state.preloadGeneration;try{await e.play()}catch(r){if(s!==this.state.preloadGeneration||!function(e){return"AbortError"===e?.name}(r)||!this.isStartingBreak(t))throw r;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:r instanceof Error?r.message:String(r)}),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 i=this.deps.getVariant(this.currentBreak)?.format??t.SINGLE,n=d({format:i,ssai:!1});if(!n.admitted)return this.deps.diagnose(n.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT",`VAST asset '${e.id}' is not supported in '${i}' breaks (single/double/lshape_ad only); skipping.`,{breakId:this.currentBreak.id,assetId:e.id,format:i}),void this.deps.onAdError(new a(`VAST not supported in ${i} format`,n.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT"));if(!p())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 a("IMA client-side SDK (ima3.js) not available","DA-VAST-IMA-SDK-MISSING"));const o=this.deps.buildVastTagUrl(e,this.currentBreak);if(!o)return void this.deps.onAdError(new a("No valid VAST tag URL for asset","DA-AD-URI-MISSING"));const c=this.deps.getAdPlayer(),l=c?.videoElement,u=this.deps.getAdContainer();if(!u||!l||!c)return void this.deps.onAdError(new a("VAST playout requires an ad container and ad video element","DA-AD-URI-MISSING"));this.deps.revealFirstFrame(),l.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.state.vastAdManager||(this.state.vastAdManager=new h(u,l,this.deps.debug)),this.state.vastAdManager.setCallbacks({onAdStarted:t=>{if(!this.currentBreak)return;this.state.startPlaybackCutoff?.(),this.deps.setVastPodPosition(t);const i=this.deps.getSequencer();i?this.deps.drive(i.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:",o);(this.state.vastAdManager.hasPreloadedManager()?(this.state.vastAdManager.setMuted(c.muted),this.state.vastAdManager.startPreloaded()):this.state.vastAdManager.playVast(o,{muted:c.muted})).then(()=>{this.deps.onAdEnded()},t=>{const s=t instanceof Error?t.message:String(t),r=/ima3\.js|client-side SDK/i.test(s)?"DA-VAST-IMA-SDK-MISSING":"DA-VAST-IMA-ERROR",i=t instanceof a?t:new a(s,r);this.deps.diagnose(r,s,{...this.currentBreak?{breakId:this.currentBreak.id}:{},assetId:e.id}),this.deps.onAdError(i)})}async playImageAsset(e,t,s){const r=this.currentBreak,i=this.deps.getAdMediaContainer();if(!r||!i)return;const d=this.state.effectiveBreakDurationSec??r.duration,n=await this.resolveAssetUri(e,d);if(!n)return void this.deps.onAdError(this.missingUriError(e));const o=document.createElement("img");o.className="dolby-overlay-image",o.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.deps.setOverlayImage(o);const h=e.duration??d;o.onload=()=>{if(this.deps.getOverlayImage()!==o||!this.currentBreak)return;this.state.startPlaybackCutoff?.();const r=this.deps.getSequencer();r?this.deps.drive(r.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s),h>0?this.deps.setImageAssetTimer(setTimeout(()=>this.onImageAssetEnded(),1e3*h)):this.onImageAssetEnded()},o.onerror=()=>{this.deps.getOverlayImage()===o&&this.deps.onAdError(new a(`Overlay image failed to load: ${n}`,"DA-IMAGE-LOAD-FAILED"))},o.src=n,i.appendChild(o)}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,s=n(e);if(t&&("number"==typeof t.code||t.message)){const e="number"==typeof t.code?` (code ${t.code})`:"",r=t.message?`: ${t.message}`:"";return new a(`Ad playback error${e}${r}`,s)}return new a("Ad playback error",s)}missingUriError(e){return this.deps.hasGamIdentityMismatch(e)?new a("GAM pod asset identity does not match the stream session","DA-GAM-POD-IDENTITY-MISMATCH"):new a("No valid URI for asset","DA-AD-URI-MISSING")}}
|
|
@@ -3,7 +3,6 @@ import type { Break } from '../../types/BreakManifest';
|
|
|
3
3
|
import type { Clock } from '../BreakScheduler';
|
|
4
4
|
import type { AdPlayerState } from './AdPlayerState';
|
|
5
5
|
import { type BreakEndOnStreamClock } from './streamClockResume';
|
|
6
|
-
export declare const START_CLAMP_MAX_WINDOW_FRACTION = 0.5;
|
|
7
6
|
export interface ContentResumeCoordinatorDeps {
|
|
8
7
|
contentPlayer: PlayerAdapter;
|
|
9
8
|
clock: Clock;
|
|
@@ -28,6 +27,7 @@ export declare class ContentResumeCoordinator {
|
|
|
28
27
|
/** The break's matched Anvato `cue_end`; null without a cue tracker or before it is matched. */
|
|
29
28
|
private cueEndResolver;
|
|
30
29
|
private readonly streamClock;
|
|
30
|
+
private readonly pdtClock;
|
|
31
31
|
constructor(state: AdPlayerState, deps: ContentResumeCoordinatorDeps);
|
|
32
32
|
/**
|
|
33
33
|
* Supply the break's matched Anvato `cue_end` (`ptsSource: 'anvatoCue'`): the stream's own end
|
|
@@ -38,7 +38,7 @@ export declare class ContentResumeCoordinator {
|
|
|
38
38
|
ingestStreamClockCue(cue: TimedMetadataCue): void;
|
|
39
39
|
/** The content pipeline was rebuilt: its timeline has to be mapped to the stream clock again. */
|
|
40
40
|
noteContentReload(): void;
|
|
41
|
-
/** Stops the stream-clock landing
|
|
41
|
+
/** Stops the stream-clock landing measurements (controller teardown). */
|
|
42
42
|
dispose(): void;
|
|
43
43
|
/**
|
|
44
44
|
* Supply the in-stream cue position used to anchor the resume in place of the pause position.
|
|
@@ -56,6 +56,12 @@ export declare class ContentResumeCoordinator {
|
|
|
56
56
|
* that latency into the resume target and lands content late by the same amount.
|
|
57
57
|
*/
|
|
58
58
|
captureBreakAnchor(breakInfo: Break): void;
|
|
59
|
+
/**
|
|
60
|
+
* A wallclock break's anchor through the stream's Program Date Time: the content time of the
|
|
61
|
+
* manifest start, wherever the pause landed. Null off the wallclock timebase or without a PDT
|
|
62
|
+
* sample, which keeps the pause position. Reports a cut that landed late on the stream clock.
|
|
63
|
+
*/
|
|
64
|
+
private pdtBreakAnchor;
|
|
59
65
|
/**
|
|
60
66
|
* Replace the anchor part-way through a break, when content kept playing until now
|
|
61
67
|
* (picture-in-picture adoption).
|
|
@@ -79,7 +85,8 @@ export declare class ContentResumeCoordinator {
|
|
|
79
85
|
isPlausibleSeekEnd(end: number): boolean;
|
|
80
86
|
/**
|
|
81
87
|
* Where the run-up steers content: the stream-clock target once beacons since the run-up's last
|
|
82
|
-
* seek map it, else the
|
|
88
|
+
* seek map it, else the break end on the Program Date Time, else the matched `cue_end`, else
|
|
89
|
+
* the policy's resume point.
|
|
83
90
|
*/
|
|
84
91
|
plannedResumeTarget(breakInfo: Break): number | null;
|
|
85
92
|
/** The policy's resume point from the anchor, ignoring the stream's own clock. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Timebase as e,isPostRollBreak as t}from"../../types/BreakManifest";import{resolveResumePoint as s}from"../resolveResumePoint";import{ContinueContentPlayout as n}from"./ContinueContentPlayout";import{StreamClockResume as i}from"./streamClockResume";import{contentSeekableEnd as r,isPlausibleSeekEnd as a,liveEdgeDistanceSec as o,liveSafeSeekEnd as u,liveSafeSeekStart as l,liveSeekableWindowSec as d,unreachableEdgeSec as h}from"./liveEdge";export const START_CLAMP_MAX_WINDOW_FRACTION=.5;const c=.04,m=e=>Math.round(1e3*e)/1e3;export class ContentResumeCoordinator{constructor(e,t){this.state=e,this.deps=t,this.cueAnchorResolver=null,this.resumePolicyTarget=null,this.cueEndResolver=null,this.continuePlayout=new n(this.state,{contentPlayer:this.deps.contentPlayer,clock:this.deps.clock,gateContentForBreak:()=>this.deps.gateContentForBreak(),distanceToLiveEdgeSec:()=>this.distanceToLiveEdgeSec(),plannedResumeTarget:e=>this.plannedResumeTarget(e),diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),log:(...e)=>this.deps.log(...e)}),this.streamClock=new i({contentPlayer:this.deps.contentPlayer,diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),activeBreak:()=>this.state.currentBreak,anchorIsCue:()=>this.state.resumeAnchorIsCue,breakEnd:e=>this.cueEndResolver?.(e)??null,computedTarget:e=>this.computedResumeTarget(e),resumesAtBreakEnd:e=>this.resumesAtBreakEnd(e),now:()=>this.deps.clock.now()})}setCueEndResolver(e){this.cueEndResolver=e}ingestStreamClockCue(e){this.streamClock.ingestCue(e)}noteContentReload(){this.streamClock.noteReload()}dispose(){this.streamClock.cancelLanding()}setCueAnchorResolver(e){this.cueAnchorResolver=e}captureBreakAnchor(e){this.resetBreakResumeState();const t=this.cueAnchorResolver?.(e)??null,s=null!==t&&Number.isFinite(t);this.state.contentPositionAtBreakStart=s?t:this.deps.contentPlayer.currentTime,this.state.resumeAnchorIsCue=s,this.state.resumeAnchorSeekableEnd=this.contentSeekableEnd(),this.streamClock.beginBreak()}reanchorMidBreak(e){this.state.contentPositionAtBreakStart=e,this.state.resumeAnchorIsCue=!1,this.state.resumeAnchorSeekableEnd=this.contentSeekableEnd()}liveEdgeDistanceSec(){return o(this.deps.contentPlayer)}liveSafeSeekEnd(){return u(this.deps.contentPlayer)}liveSafeSeekStart(){return l(this.deps.contentPlayer)}liveSeekableWindowSec(){return d(this.deps.contentPlayer)}contentSeekableEnd(){return r(this.deps.contentPlayer)}resumeAnchorCushionSec(){const e=this.state.resumeAnchorSeekableEnd;return null!==e&&Number.isFinite(e)?m(e-this.state.contentPositionAtBreakStart):null}isPlausibleSeekEnd(e){return a(this.deps.contentPlayer,e)}plannedResumeTarget(e){const t=this.computedResumeTarget(e);return null===t?null:this.streamClock.target(e)??this.cueEndTarget(e)??t}computedResumeTarget(e){if(t(e))return null;const s=this.resolvePolicy(e,this.state.contentPositionAtBreakStart);return s.seek?s.target:null}resumesAtBreakEnd(e){if(t(e))return!1;const s=this.resolvePolicy(e,0);return s.seek&&Math.abs(s.offset-(e.duration??0))<.001}resolvePolicy(e,t){return s({anchor:t,duration:e.duration,adDuration:e.duration,...this.state.adInsertionType?{insertionType:this.state.adInsertionType}:{},..."number"==typeof e.resumeOffset?{resumeOffset:e.resumeOffset}:{}})}cueEndTarget(e){if(!this.state.resumeAnchorIsCue||!this.resumesAtBreakEnd(e))return null;const t=this.cueEndResolver?.(e)?.mediaTime;return"number"==typeof t&&Number.isFinite(t)?t:null}maybePrepositionContentForResume(){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||null!==this.state.resumePrepositionedTo)return;if(null!==this.state.resumePrepositionSettle)return;if(this.state.resumeCatchupActive)return;if(Number.isFinite(this.deps.contentPlayer.duration))return;const e=this.state.currentBreak;if(!e)return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.contentSeekableEnd();if(null===s)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;let r;if(t<=s)r=t;else{if(!(i<=3))return;r=s}this.deps.log(r===t?`Resume pre-positioning: seeking content to ${t}s during the break (window reaches ${s}s)`:`Resume pre-positioning: window only reaches ${s}s and the ad ends in ${i}s — moving content as close to ${t}s as exists`),this.streamClock.noteSeek(r),this.streamClock.arm(),this.deps.contentPlayer.seek(r);const a=setTimeout(()=>{this.state.resumePrepositionSettle=null;const e=this.deps.contentPlayer.currentTime;Math.abs(e-r)<=c?(this.state.resumePrepositionedTo=e,this.deps.log(`Resume pre-positioned: content is at ${e}s`)):this.deps.log(`Resume pre-position rejected: asked ${r}s, landed ${e}s — window is short, will retry`)},250);this.state.resumePrepositionSettle=a}maybeCatchUpContentForResume(e){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||Number.isFinite(this.deps.contentPlayer.duration))return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.deps.contentPlayer.currentTime;if(this.state.resumeCatchupActive){const n=(e.duration??0)-(this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec);if(s>t+1)return this.state.resumeCatchupActive=!1,this.deps.contentPlayer.pause(),this.state.resumePrepositionedTo=this.deps.contentPlayer.currentTime,void this.deps.log(`Resume catch-up halted: content reached ${this.deps.contentPlayer.currentTime}s, past the ${t}s target`);if(n<=30&&n>=1){this.state.resumeAlignTicks+=1;const e=s+n,i=e-t;if(Math.abs(i)>c){const r=s-i,a=this.liveSafeSeekEnd();if(null!==a){const e=a>this.state.lastResumeSafeEnd+.25;e&&Number.isFinite(this.state.lastResumeSafeEnd)&&this.state.resumeSawSteps.push(Math.round(100*(a-this.state.lastResumeSafeEnd))/100),a>this.state.lastResumeSafeEnd&&(this.state.lastResumeSafeEnd=a);const s=a-r;if(s>this.state.resumeBestSawMarginSec&&(this.state.resumeBestSawMarginSec=s),r>a)return void(this.state.resumeAlignBlockedDiagnosed||(this.state.resumeAlignBlockedDiagnosed=!0,this.deps.log(`Resume align waiting: ${r}s is past the published edge ${a}s (${Math.round(1e3*(r-a))}ms short) — rolling until a manifest step brings it in reach`),this.deps.diagnose?.("DA-RESUME-ALIGN-BLOCKED",`Resume alignment cannot reach ${t}s yet: the published edge is ${a}s`,{target:t,requested:r,safeEnd:a,shortfallSec:Math.round(1e3*(r-a))/1e3,remainingSec:n})));if(!e&&this.state.resumeSawSeekDone)return;this.state.resumeSawSeekDone=!0}if(this.state.lastResumeAlignRemainingSec-n<.5)return;this.state.lastResumeAlignRemainingSec=n,this.deps.log(`Resume align: landing ${e}s vs target ${t}s (${Math.round(1e3*i)}ms) — content to ${r}s, ${n}s of ad left`),this.state.resumeAlignCorrectionsMs.push(Math.round(1e3*i)),this.streamClock.noteSeek(r),this.deps.contentPlayer.seek(r)}}return}if(t-s<=c)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;if(i<=0)return;if(i>30)return;const r=this.contentSeekableEnd();if(null===r)return;const a=t-i+.3,o=Math.min(a,r);this.state.resumeCatchupActive=!0,this.state.resumeCatchupRan=!0,this.state.contentMutedBeforeCatchup=this.deps.contentPlayer.muted,this.deps.contentPlayer.muted=!0,this.deps.log(`Resume catch-up: content to ${o}s, playing toward ${t}s with ${i}s of ad left`),this.deps.diagnose?.("DA-RESUME-CATCHUP",`Break ${e.id}: playing content from ${o}s toward ${t}s with ${i}s of ad left`,{breakId:e.id,startPos:o,target:t,remainingAdSec:Math.round(1e3*i)/1e3,publishedEnd:r,clampedStart:o<a}),Math.abs(s-o)>c&&(this.streamClock.noteSeek(o),this.deps.contentPlayer.seek(o)),this.streamClock.arm(),this.deps.contentPlayer.play()}endResumeCatchup(e=!1){this.state.resumeCatchupActive&&(this.state.resumeCatchupActive=!1,e||this.deps.contentPlayer.pause()),null!==this.state.contentMutedBeforeCatchup&&(this.deps.contentPlayer.muted=this.state.contentMutedBeforeCatchup,this.state.contentMutedBeforeCatchup=null)}get resumeHandoverInMotion(){return this.state.resumeCatchupActive}resumeAlignSummary(){const e=this.state.resumeAlignCorrectionsMs;return{catchUpRan:this.state.resumeCatchupRan||this.state.resumeCatchupActive||e.length>0||null!==this.state.resumePrepositionedTo,alignCorrections:e.length,placedOnStep:this.state.resumeSawSeekDone,alignTicks:this.state.resumeAlignTicks,sawSteps:this.state.resumeSawSteps.slice(0,12),sawStepCount:this.state.resumeSawSteps.length,bestSawMarginSec:Number.isFinite(this.state.resumeBestSawMarginSec)?Math.round(1e3*this.state.resumeBestSawMarginSec)/1e3:null,alignCorrectionsMs:e.slice(0,12),alignLastErrorMs:e.length?e[e.length-1]:null}}cancelResumeLandedProbe(){const e=this.state.resumeLandedProbe;this.state.resumeLandedProbe=null,e?.()}probeResumeLanding(e,t,s=null){this.cancelResumeLandedProbe();let n=!1;const i=(i,r)=>{if(n)return;n=!0,this.cancelResumeLandedProbe();const a=null===i?null:Math.round(1e3*(i-t))/1e3,o=null===i||null===s?null:Math.round(1e3*(i-s))/1e3,u=null!==s&&Math.abs(s-t)>=5e-4&&null!==o?`; ${Math.abs(o)}s ${o<0?"before":"after"} the ${s}s resume point`:"";this.deps.log(null===i?`Resume landing unknown for break ${e} (${r})`:`Resume landed at ${i}s, ${a}s from the ${t}s asked for${u} (break ${e})`),this.deps.diagnose?.("DA-RESUME-LANDED",null===i?`Resume landing for break ${e} could not be measured (${r})`:`Resume for break ${e} landed at ${i}s, ${a}s from the ${t}s requested${u}`,{breakId:e,target:t,landed:i,errorSec:a,...null===s?{}:{policyTarget:s,policyErrorSec:o},...r?{reason:r}:{}})},r=()=>{this.streamClock.updateLanding(e,this.deps.contentPlayer.currentTime),i(this.deps.contentPlayer.currentTime)},a=setTimeout(()=>i(null,"no seeked event"),2e3);this.state.resumeLandedProbe=()=>{clearTimeout(a),this.deps.contentPlayer.off("seeked",r)},this.deps.contentPlayer.on("seeked",r)}async waitForLiveEdge(e=()=>!0){for(let t=0;t<20;t++){if(!e())return null;const t=this.deps.contentPlayer.videoElement,s=t?.seekable;if(s&&s.length>0){const e=s.end(s.length-1);if(Number.isFinite(e)&&e>0)return e}if(await new Promise(e=>setTimeout(e,100)),!e())return null}return this.deps.log("shared-element: no seekable range after restore; staying at the live edge"),null}resetBreakResumeState(){this.state.resumeAnchorIsCue=!1,this.state.resumeAnchorSeekableEnd=null,this.state.resumePrepositionedTo=null,this.state.resumeCatchupActive=!1,this.state.resumeCatchupRan=!1,this.state.contentMutedBeforeCatchup=null,this.state.resumeAlignCorrectionsMs=[],this.state.lastResumeAlignRemainingSec=Number.POSITIVE_INFINITY,this.state.resumeAlignBlockedDiagnosed=!1,this.state.lastResumeSafeEnd=Number.NEGATIVE_INFINITY,this.state.resumeSawSeekDone=!1,this.state.resumeBestSawMarginSec=Number.NEGATIVE_INFINITY,this.state.resumeAlignTicks=0,this.state.resumeSawSteps=[],this.state.resumePrepositionSettle&&(clearTimeout(this.state.resumePrepositionSettle),this.state.resumePrepositionSettle=null),this.state.adSecondsPlayedThisBreak=0,this.state.currentAssetElapsedSec=0}beginContinueContentPlayout(){this.continuePlayout.beginContinueContentPlayout()}distanceToLiveEdgeSec(){const e=this.contentSeekableEnd();return null===e?null:m(e-this.deps.contentPlayer.currentTime)}secondsUntilCut(e){return this.continuePlayout.secondsUntilCut(e)}restoreContinueQuality(e){this.continuePlayout.restoreContinueQuality(e)}currentContinueDriftSec(){return this.continuePlayout.currentContinueDriftSec()}abandonContinueContentPlayout(e){this.continuePlayout.abandonContinueContentPlayout(e)}endContinueContentPlayout(){this.continuePlayout.endContinueContentPlayout()}setContinueRate(e){this.continuePlayout.setContinueRate(e)}tickContinueContentPlayout(){this.continuePlayout.tickContinueContentPlayout()}resolveResumeTarget(t,n=null){this.resumePolicyTarget=null;const i=null!==n,r=this.state.resumeAnchorIsCue&&!i,a=i?n:r?t.duration:this.state.adSecondsPlayedThisBreak,o=s({anchor:this.state.contentPositionAtBreakStart,duration:t.duration,adDuration:a,...i?{}:{insertionType:this.state.adInsertionType},...i?{resumeOffset:n}:"number"==typeof t.resumeOffset?{resumeOffset:t.resumeOffset}:{}});if(o.clamped&&this.deps.diagnose?.("DA-RESUME-OFFSET-CLAMPED",`Resume offset shortened to the ${o.offset}s of ad actually played for break ${t.id}`,{breakId:t.id,offset:o.offset,source:o.source}),!o.seek)return this.deps.log(`Resume: no seek for break ${t.id} (offset 0, ${o.source})`),null;const u=this.streamClock.handBackTarget(t,i),l=null!==u||i?null:this.reportCueEnd(t,o.target),d=u??l??o.target;this.resumePolicyTarget=d;const m=this.streamClock.seekBasis(t)??(null!==l?"cue_end":`offset ${o.offset}s, ${o.source}`);this.deps.log(`Resume-seek to ${d}s (${m}) for break ${t.id}`);const p=this.resumeAlignSummary(),g=this.liveSafeSeekEnd();this.deps.diagnose?.("DA-RESUME-SEEK",`Resume-seek to ${d}s (${m}) for break ${t.id}`,{breakId:t.id,target:d,offset:o.offset,source:o.source,...d===o.target?{}:{computedTarget:o.target},...null===l?{}:{cueEndTarget:l},...this.streamClock.seekContext(t,d,o.target),anchorPosition:this.state.contentPositionAtBreakStart,anchorIsCue:this.state.resumeAnchorIsCue,anchorSeekableEnd:this.state.resumeAnchorSeekableEnd,anchorCushionSec:this.resumeAnchorCushionSec(),safeEnd:g,seekableEndNow:this.contentSeekableEnd(),liveEdge:this.deps.contentPlayer.liveEdge??null,unreachableEdgeSec:h(this.deps.contentPlayer),...p.catchUpRan?p:{}}),this.streamClock.reportCorrection(t,d,o.target,i);(this.state.breakTimebase===e.PTS||this.state.breakTimebase===e.MEDIATIME)&&"number"==typeof t.start&&this.state.callbacks?.onContentResumeSeek?.(d,t.start+t.duration);let k=d,S=0;null!==g&&k>g&&(S=Math.round(1e3*(k-g))/1e3,k=g);let f=S>0?"end":null;const P=this.liveSafeSeekStart();if(null!==P&&k<P){const e=Math.round(1e3*(P-k))/1e3,s=this.liveSeekableWindowSec(),n=null===s?null:.5*s;null!==n&&e<=n?(S=e,k=P,f="start"):(this.deps.log(`Resume-seek: ${d}s is ${e}s behind the element's live window start (window ${s}s); not clamping`),this.deps.diagnose?.("DA-RESUME-START-CLAMP-SKIPPED",`Resume target ${d}s for break ${t.id} is ${e}s behind the element's seekable start but the gap exceeds half the ${s}s window; left unchanged`,{breakId:t.id,requested:d,seekableStart:P,behindSec:e,windowSec:s,maxBehindSec:n,...this.resumeAlignSummary()}))}if(null!==this.state.resumePrepositionedTo&&Math.abs(this.deps.contentPlayer.currentTime-k)<=c){const e=this.deps.contentPlayer.currentTime;return this.deps.log(`Resume: content already at ${e}s for break ${t.id} (pre-positioned during the break)`+(S>0?` — ${S}s short of policy, those frames are not published yet`:"")+" — no seek"),this.deps.diagnose?.("DA-RESUME-PREPOSITIONED",`Break ${t.id} resumed without a seek: content was moved to ${e}s during the break`+(S>0?` (${S}s short of the ${d}s policy target)`:""),{breakId:t.id,target:d,prepositionedTo:this.state.resumePrepositionedTo,landed:e,shortfallSec:S,exact:0===S,...this.resumeAlignSummary()}),this.streamClock.armLanding(t,e),null}return S>0&&(this.deps.log("start"===f?`Resume-seek clamped: ${d}s is behind the live window start (already evicted); seeking ${k}s instead (${S}s early)`:`Resume-seek clamped: ${d}s is beyond what the stream has published; seeking ${k}s instead (${S}s short)`),this.deps.diagnose?.("DA-RESUME-TARGET-CLAMPED","start"===f?`Resume target ${d}s for break ${t.id} is behind the live window start (already evicted); clamped to ${k}s`:`Resume target ${d}s for break ${t.id} is beyond the published window; clamped to ${k}s`,{breakId:t.id,requested:d,clamped:k,shortfallSec:S,bound:f,...this.resumeAlignSummary()})),this.streamClock.armLanding(t,k),k}reportCueEnd(e,t){const s=this.state.resumeAnchorIsCue?this.cueEndResolver?.(e):null;if(!s||!Number.isFinite(s.mediaTime))return null;const n=this.resumesAtBreakEnd(e),i=m(s.mediaTime-t);return this.deps.diagnose("DA-RESUME-CUE-END",n?`Break ${e.id}: resuming on the stream's cue_end at ${s.mediaTime}s (${i}s from the computed ${t}s)`:`Break ${e.id}: cue_end at ${s.mediaTime}s not used; the resume policy does not resume at the break end`,{breakId:e.id,computedTarget:t,cueEndTarget:s.mediaTime,deltaSec:i,applied:n,stage:"hand-back",corrected:!1}),n?s.mediaTime:null}applyResumeSeek(e,t=null){const s=this.resolveResumeTarget(e,t);null!==s&&(this.probeResumeLanding(e.id,s,this.resumePolicyTarget),this.streamClock.noteSeek(s),this.deps.contentPlayer.seek(s))}}
|
|
1
|
+
import{Timebase as e,isPostRollBreak as t}from"../../types/BreakManifest";import{resolveResumePoint as s}from"../resolveResumePoint";import{resolveResumeClamp as n}from"../resolveResumeClamp";import{BREAK_CUT_LATE_TOLERANCE_SEC as r,resolveWallclockBreakWindow as a}from"../wallclockBreakWindow";import{ContinueContentPlayout as i}from"./ContinueContentPlayout";import{StreamClockResume as o}from"./streamClockResume";import{PdtClockResume as u,programDateTimeSample as l}from"./pdtClockResume";import{contentSeekableEnd as d,isPlausibleSeekEnd as h,liveEdgeDistanceSec as c,liveSafeSeekEnd as m,liveSafeSeekStart as p,liveSeekableWindowSec as g,unreachableEdgeSec as k}from"./liveEdge";const S=.04,f=e=>Math.round(1e3*e)/1e3;export class ContentResumeCoordinator{constructor(e,t){this.state=e,this.deps=t,this.cueAnchorResolver=null,this.resumePolicyTarget=null,this.cueEndResolver=null,this.continuePlayout=new i(this.state,{contentPlayer:this.deps.contentPlayer,clock:this.deps.clock,gateContentForBreak:()=>this.deps.gateContentForBreak(),distanceToLiveEdgeSec:()=>this.distanceToLiveEdgeSec(),plannedResumeTarget:e=>this.plannedResumeTarget(e),diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),log:(...e)=>this.deps.log(...e)}),this.streamClock=new o({contentPlayer:this.deps.contentPlayer,diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),activeBreak:()=>this.state.currentBreak,anchorIsCue:()=>this.state.resumeAnchorIsCue,breakEnd:e=>this.cueEndResolver?.(e)??null,computedTarget:e=>this.computedResumeTarget(e),resumesAtBreakEnd:e=>this.resumesAtBreakEnd(e),now:()=>this.deps.clock.now()}),this.pdtClock=new u({contentPlayer:this.deps.contentPlayer,diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),anchorIsPdt:()=>this.state.resumeAnchorIsPdt,computedTarget:e=>this.computedResumeTarget(e),resumesAtBreakEnd:e=>this.resumesAtBreakEnd(e),now:()=>this.deps.clock.now()})}setCueEndResolver(e){this.cueEndResolver=e}ingestStreamClockCue(e){this.streamClock.ingestCue(e)}noteContentReload(){this.streamClock.noteReload()}dispose(){this.streamClock.cancelLanding(),this.pdtClock.cancelLanding()}setCueAnchorResolver(e){this.cueAnchorResolver=e}captureBreakAnchor(e){this.resetBreakResumeState();const t=this.cueAnchorResolver?.(e)??null,s=null!==t&&Number.isFinite(t),n=s?null:this.pdtBreakAnchor(e);this.state.contentPositionAtBreakStart=s?t:n??this.deps.contentPlayer.currentTime,this.state.resumeAnchorIsCue=s,this.state.resumeAnchorIsPdt=null!==n,this.state.resumeAnchorSeekableEnd=this.contentSeekableEnd(),this.streamClock.beginBreak(),this.pdtClock.beginBreak()}pdtBreakAnchor(t){if(this.state.breakTimebase!==e.WALLCLOCK||"string"!=typeof t.start)return null;const s=l(this.deps.contentPlayer);if(!s)return null;const n=a({startMs:Date.parse(t.start),durationSec:t.duration??0,sample:s});return n?(n.sampleLateSec>r&&this.deps.diagnose?.("DA-BREAK-CUT-LATE",`Break ${t.id}: content was cut ${n.sampleLateSec}s after the break start on the stream clock; the viewer saw the stream's own break until then`,{breakId:t.id,lateSec:n.sampleLateSec,start:t.start,cutPdt:new Date(s.programDateTimeMs).toISOString(),cutAt:f(s.currentTime),anchor:n.anchor}),n.anchor):null}reanchorMidBreak(e){this.state.contentPositionAtBreakStart=e,this.state.resumeAnchorIsCue=!1,this.state.resumeAnchorIsPdt=!1,this.state.resumeAnchorSeekableEnd=this.contentSeekableEnd()}liveEdgeDistanceSec(){return c(this.deps.contentPlayer)}liveSafeSeekEnd(){return m(this.deps.contentPlayer)}liveSafeSeekStart(){return p(this.deps.contentPlayer)}liveSeekableWindowSec(){return g(this.deps.contentPlayer)}contentSeekableEnd(){return d(this.deps.contentPlayer)}resumeAnchorCushionSec(){const e=this.state.resumeAnchorSeekableEnd;return null!==e&&Number.isFinite(e)?f(e-this.state.contentPositionAtBreakStart):null}isPlausibleSeekEnd(e){return h(this.deps.contentPlayer,e)}plannedResumeTarget(e){const t=this.computedResumeTarget(e);return null===t?null:this.streamClock.target(e)??this.pdtClock.target(e)??this.cueEndTarget(e)??t}computedResumeTarget(e){if(t(e))return null;const s=this.resolvePolicy(e,this.state.contentPositionAtBreakStart);return s.seek?s.target:null}resumesAtBreakEnd(e){if(t(e))return!1;const s=this.resolvePolicy(e,0);return s.seek&&Math.abs(s.offset-(e.duration??0))<.001}resolvePolicy(e,t){return s({anchor:t,duration:e.duration,adDuration:e.duration,...this.state.adInsertionType?{insertionType:this.state.adInsertionType}:{},..."number"==typeof e.resumeOffset?{resumeOffset:e.resumeOffset}:{}})}cueEndTarget(e){if(!this.state.resumeAnchorIsCue||!this.resumesAtBreakEnd(e))return null;const t=this.cueEndResolver?.(e)?.mediaTime;return"number"==typeof t&&Number.isFinite(t)?t:null}maybePrepositionContentForResume(){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||null!==this.state.resumePrepositionedTo)return;if(null!==this.state.resumePrepositionSettle)return;if(this.state.resumeCatchupActive)return;if(Number.isFinite(this.deps.contentPlayer.duration))return;const e=this.state.currentBreak;if(!e)return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.contentSeekableEnd();if(null===s)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,r=(e.duration??0)-n;let a;if(t<=s)a=t;else{if(!(r<=3))return;a=s}this.deps.log(a===t?`Resume pre-positioning: seeking content to ${t}s during the break (window reaches ${s}s)`:`Resume pre-positioning: window only reaches ${s}s and the ad ends in ${r}s — moving content as close to ${t}s as exists`),this.streamClock.noteSeek(a),this.streamClock.arm(),this.deps.contentPlayer.seek(a);const i=setTimeout(()=>{this.state.resumePrepositionSettle=null;const e=this.deps.contentPlayer.currentTime;Math.abs(e-a)<=S?(this.state.resumePrepositionedTo=e,this.deps.log(`Resume pre-positioned: content is at ${e}s`)):this.deps.log(`Resume pre-position rejected: asked ${a}s, landed ${e}s — window is short, will retry`)},250);this.state.resumePrepositionSettle=i}maybeCatchUpContentForResume(e){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||Number.isFinite(this.deps.contentPlayer.duration))return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.deps.contentPlayer.currentTime;if(this.state.resumeCatchupActive){const n=(e.duration??0)-(this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec);if(s>t+1)return this.state.resumeCatchupActive=!1,this.deps.contentPlayer.pause(),this.state.resumePrepositionedTo=this.deps.contentPlayer.currentTime,void this.deps.log(`Resume catch-up halted: content reached ${this.deps.contentPlayer.currentTime}s, past the ${t}s target`);if(n<=30&&n>=1){this.state.resumeAlignTicks+=1;const e=s+n,r=e-t;if(Math.abs(r)>S){const a=s-r,i=this.liveSafeSeekEnd();if(null!==i){const e=i>this.state.lastResumeSafeEnd+.25;e&&Number.isFinite(this.state.lastResumeSafeEnd)&&this.state.resumeSawSteps.push(Math.round(100*(i-this.state.lastResumeSafeEnd))/100),i>this.state.lastResumeSafeEnd&&(this.state.lastResumeSafeEnd=i);const s=i-a;if(s>this.state.resumeBestSawMarginSec&&(this.state.resumeBestSawMarginSec=s),a>i)return void(this.state.resumeAlignBlockedDiagnosed||(this.state.resumeAlignBlockedDiagnosed=!0,this.deps.log(`Resume align waiting: ${a}s is past the published edge ${i}s (${Math.round(1e3*(a-i))}ms short) — rolling until a manifest step brings it in reach`),this.deps.diagnose?.("DA-RESUME-ALIGN-BLOCKED",`Resume alignment cannot reach ${t}s yet: the published edge is ${i}s`,{target:t,requested:a,safeEnd:i,shortfallSec:Math.round(1e3*(a-i))/1e3,remainingSec:n})));if(!e&&this.state.resumeSawSeekDone)return;this.state.resumeSawSeekDone=!0}if(this.state.lastResumeAlignRemainingSec-n<.5)return;this.state.lastResumeAlignRemainingSec=n,this.deps.log(`Resume align: landing ${e}s vs target ${t}s (${Math.round(1e3*r)}ms) — content to ${a}s, ${n}s of ad left`),this.state.resumeAlignCorrectionsMs.push(Math.round(1e3*r)),this.streamClock.noteSeek(a),this.deps.contentPlayer.seek(a)}}return}if(t-s<=S)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,r=(e.duration??0)-n;if(r<=0)return;if(r>30)return;const a=this.contentSeekableEnd();if(null===a)return;const i=t-r+.3,o=Math.min(i,a);this.state.resumeCatchupActive=!0,this.state.resumeCatchupRan=!0,this.state.contentMutedBeforeCatchup=this.deps.contentPlayer.muted,this.deps.contentPlayer.muted=!0,this.deps.log(`Resume catch-up: content to ${o}s, playing toward ${t}s with ${r}s of ad left`),this.deps.diagnose?.("DA-RESUME-CATCHUP",`Break ${e.id}: playing content from ${o}s toward ${t}s with ${r}s of ad left`,{breakId:e.id,startPos:o,target:t,remainingAdSec:Math.round(1e3*r)/1e3,publishedEnd:a,clampedStart:o<i}),Math.abs(s-o)>S&&(this.streamClock.noteSeek(o),this.deps.contentPlayer.seek(o)),this.streamClock.arm(),this.deps.contentPlayer.play()}endResumeCatchup(e=!1){this.state.resumeCatchupActive&&(this.state.resumeCatchupActive=!1,e||this.deps.contentPlayer.pause()),null!==this.state.contentMutedBeforeCatchup&&(this.deps.contentPlayer.muted=this.state.contentMutedBeforeCatchup,this.state.contentMutedBeforeCatchup=null)}get resumeHandoverInMotion(){return this.state.resumeCatchupActive}resumeAlignSummary(){const e=this.state.resumeAlignCorrectionsMs;return{catchUpRan:this.state.resumeCatchupRan||this.state.resumeCatchupActive||e.length>0||null!==this.state.resumePrepositionedTo,alignCorrections:e.length,placedOnStep:this.state.resumeSawSeekDone,alignTicks:this.state.resumeAlignTicks,sawSteps:this.state.resumeSawSteps.slice(0,12),sawStepCount:this.state.resumeSawSteps.length,bestSawMarginSec:Number.isFinite(this.state.resumeBestSawMarginSec)?Math.round(1e3*this.state.resumeBestSawMarginSec)/1e3:null,alignCorrectionsMs:e.slice(0,12),alignLastErrorMs:e.length?e[e.length-1]:null}}cancelResumeLandedProbe(){const e=this.state.resumeLandedProbe;this.state.resumeLandedProbe=null,e?.()}probeResumeLanding(e,t,s=null){this.cancelResumeLandedProbe();let n=!1;const r=(r,a)=>{if(n)return;n=!0,this.cancelResumeLandedProbe();const i=null===r?null:Math.round(1e3*(r-t))/1e3,o=null===r||null===s?null:Math.round(1e3*(r-s))/1e3,u=null!==s&&Math.abs(s-t)>=5e-4&&null!==o?`; ${Math.abs(o)}s ${o<0?"before":"after"} the ${s}s resume point`:"";this.deps.log(null===r?`Resume landing unknown for break ${e} (${a})`:`Resume landed at ${r}s, ${i}s from the ${t}s asked for${u} (break ${e})`),this.deps.diagnose?.("DA-RESUME-LANDED",null===r?`Resume landing for break ${e} could not be measured (${a})`:`Resume for break ${e} landed at ${r}s, ${i}s from the ${t}s requested${u}`,{breakId:e,target:t,landed:r,errorSec:i,...null===s?{}:{policyTarget:s,policyErrorSec:o},...a?{reason:a}:{}})},a=()=>{this.streamClock.updateLanding(e,this.deps.contentPlayer.currentTime),r(this.deps.contentPlayer.currentTime)},i=setTimeout(()=>r(null,"no seeked event"),2e3);this.state.resumeLandedProbe=()=>{clearTimeout(i),this.deps.contentPlayer.off("seeked",a)},this.deps.contentPlayer.on("seeked",a)}async waitForLiveEdge(e=()=>!0){for(let t=0;t<20;t++){if(!e())return null;const t=this.deps.contentPlayer.videoElement,s=t?.seekable;if(s&&s.length>0){const e=s.end(s.length-1);if(Number.isFinite(e)&&e>0)return e}if(await new Promise(e=>setTimeout(e,100)),!e())return null}return this.deps.log("shared-element: no seekable range after restore; staying at the live edge"),null}resetBreakResumeState(){this.state.resumeAnchorIsCue=!1,this.state.resumeAnchorIsPdt=!1,this.state.resumeAnchorSeekableEnd=null,this.state.resumePrepositionedTo=null,this.state.resumeCatchupActive=!1,this.state.resumeCatchupRan=!1,this.state.contentMutedBeforeCatchup=null,this.state.resumeAlignCorrectionsMs=[],this.state.lastResumeAlignRemainingSec=Number.POSITIVE_INFINITY,this.state.resumeAlignBlockedDiagnosed=!1,this.state.lastResumeSafeEnd=Number.NEGATIVE_INFINITY,this.state.resumeSawSeekDone=!1,this.state.resumeBestSawMarginSec=Number.NEGATIVE_INFINITY,this.state.resumeAlignTicks=0,this.state.resumeSawSteps=[],this.state.resumePrepositionSettle&&(clearTimeout(this.state.resumePrepositionSettle),this.state.resumePrepositionSettle=null),this.state.adSecondsPlayedThisBreak=0,this.state.currentAssetElapsedSec=0}beginContinueContentPlayout(){this.continuePlayout.beginContinueContentPlayout()}distanceToLiveEdgeSec(){const e=this.contentSeekableEnd();return null===e?null:f(e-this.deps.contentPlayer.currentTime)}secondsUntilCut(e){return this.continuePlayout.secondsUntilCut(e)}restoreContinueQuality(e){this.continuePlayout.restoreContinueQuality(e)}currentContinueDriftSec(){return this.continuePlayout.currentContinueDriftSec()}abandonContinueContentPlayout(e){this.continuePlayout.abandonContinueContentPlayout(e)}endContinueContentPlayout(){this.continuePlayout.endContinueContentPlayout()}setContinueRate(e){this.continuePlayout.setContinueRate(e)}tickContinueContentPlayout(){this.continuePlayout.tickContinueContentPlayout()}resolveResumeTarget(t,r=null){this.resumePolicyTarget=null;const a=null!==r,i=(this.state.resumeAnchorIsCue||this.state.resumeAnchorIsPdt)&&!a,o=a?r:i?t.duration:this.state.adSecondsPlayedThisBreak,u=s({anchor:this.state.contentPositionAtBreakStart,duration:t.duration,adDuration:o,...a?{}:{insertionType:this.state.adInsertionType},...a?{resumeOffset:r}:"number"==typeof t.resumeOffset?{resumeOffset:t.resumeOffset}:{}});if(u.clamped&&this.deps.diagnose?.("DA-RESUME-OFFSET-CLAMPED",`Resume offset shortened to the ${u.offset}s of ad actually played for break ${t.id}`,{breakId:t.id,offset:u.offset,source:u.source}),!u.seek)return this.deps.log(`Resume: no seek for break ${t.id} (offset 0, ${u.source})`),null;const l=this.streamClock.handBackTarget(t,a)??this.pdtClock.handBackTarget(t,a),d=null!==l||a?null:this.reportCueEnd(t,u.target),h=l??d??u.target;this.resumePolicyTarget=h;const c=this.streamClock.seekBasis(t)??this.pdtClock.seekBasis()??(null!==d?"cue_end":`offset ${u.offset}s, ${u.source}`);this.deps.log(`Resume-seek to ${h}s (${c}) for break ${t.id}`);const m=this.resumeAlignSummary(),p=this.liveSafeSeekEnd();this.deps.diagnose?.("DA-RESUME-SEEK",`Resume-seek to ${h}s (${c}) for break ${t.id}`,{breakId:t.id,target:h,offset:u.offset,source:u.source,...h===u.target?{}:{computedTarget:u.target},...null===d?{}:{cueEndTarget:d},...this.streamClock.seekContext(t,h,u.target),...this.pdtClock.seekContext(t,h,u.target),anchorPosition:this.state.contentPositionAtBreakStart,anchorIsCue:this.state.resumeAnchorIsCue,anchorIsPdt:this.state.resumeAnchorIsPdt,anchorSeekableEnd:this.state.resumeAnchorSeekableEnd,anchorCushionSec:this.resumeAnchorCushionSec(),safeEnd:p,seekableEndNow:this.contentSeekableEnd(),liveEdge:this.deps.contentPlayer.liveEdge??null,unreachableEdgeSec:k(this.deps.contentPlayer),...m.catchUpRan?m:{}}),this.streamClock.reportCorrection(t,h,u.target,a),this.pdtClock.reportCorrection(t,h,u.target,a);(this.state.breakTimebase===e.PTS||this.state.breakTimebase===e.MEDIATIME)&&"number"==typeof t.start&&this.state.callbacks?.onContentResumeSeek?.(h,t.start+t.duration);const g=this.liveSafeSeekStart(),f=n({target:h,safeStart:g,safeEnd:p,windowSec:this.liveSeekableWindowSec()}),{target:C,shortfallSec:P,bound:b}=f;if(f.startClampSkipped){const{behindSec:e,windowSec:s,maxBehindSec:n}=f.startClampSkipped;this.deps.log(`Resume-seek: ${h}s is ${e}s behind the element's live window start (window ${s}s); not clamping`),this.deps.diagnose?.("DA-RESUME-START-CLAMP-SKIPPED",`Resume target ${h}s for break ${t.id} is ${e}s behind the element's seekable start but the gap exceeds half the ${s}s window; left unchanged`,{breakId:t.id,requested:h,seekableStart:g,behindSec:e,windowSec:s,maxBehindSec:n,...this.resumeAlignSummary()})}if(null!==this.state.resumePrepositionedTo&&Math.abs(this.deps.contentPlayer.currentTime-C)<=S){const e=this.deps.contentPlayer.currentTime;return this.deps.log(`Resume: content already at ${e}s for break ${t.id} (pre-positioned during the break)`+(P>0?` — ${P}s short of policy, those frames are not published yet`:"")+" — no seek"),this.deps.diagnose?.("DA-RESUME-PREPOSITIONED",`Break ${t.id} resumed without a seek: content was moved to ${e}s during the break`+(P>0?` (${P}s short of the ${h}s policy target)`:""),{breakId:t.id,target:h,prepositionedTo:this.state.resumePrepositionedTo,landed:e,shortfallSec:P,exact:0===P,...this.resumeAlignSummary()}),this.streamClock.armLanding(t,e),this.pdtClock.armLanding(t),null}return P>0&&(this.deps.log("start"===b?`Resume-seek clamped: ${h}s is behind the live window start (already evicted); seeking ${C}s instead (${P}s early)`:`Resume-seek clamped: ${h}s is beyond what the stream has published; seeking ${C}s instead (${P}s short)`),this.deps.diagnose?.("DA-RESUME-TARGET-CLAMPED","start"===b?`Resume target ${h}s for break ${t.id} is behind the live window start (already evicted); clamped to ${C}s`:`Resume target ${h}s for break ${t.id} is beyond the published window; clamped to ${C}s`,{breakId:t.id,requested:h,clamped:C,shortfallSec:P,bound:b,...this.resumeAlignSummary()})),this.streamClock.armLanding(t,C),this.pdtClock.armLanding(t),C}reportCueEnd(e,t){const s=this.state.resumeAnchorIsCue?this.cueEndResolver?.(e):null;if(!s||!Number.isFinite(s.mediaTime))return null;const n=this.resumesAtBreakEnd(e),r=f(s.mediaTime-t);return this.deps.diagnose("DA-RESUME-CUE-END",n?`Break ${e.id}: resuming on the stream's cue_end at ${s.mediaTime}s (${r}s from the computed ${t}s)`:`Break ${e.id}: cue_end at ${s.mediaTime}s not used; the resume policy does not resume at the break end`,{breakId:e.id,computedTarget:t,cueEndTarget:s.mediaTime,deltaSec:r,applied:n,stage:"hand-back",corrected:!1}),n?s.mediaTime:null}applyResumeSeek(e,t=null){const s=this.resolveResumeTarget(e,t);null!==s&&(this.probeResumeLanding(e.id,s,this.resumePolicyTarget),this.streamClock.noteSeek(s),this.deps.contentPlayer.seek(s))}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Vendor as t}from"../../types/BreakManifest";import{GamPodAdTracker as e}from"../GamPodAdTracker";import{GamStreamManager as
|
|
1
|
+
import{Vendor as t}from"../../types/BreakManifest";import{GamPodAdTracker as e}from"../GamPodAdTracker";import{GAM_DUPLICATE_METADATA_DIAGNOSTIC_MESSAGE as s,GamStreamManager as a}from"../GamStreamManager";import{isGamVendorParameters as i}from"../GamPodUrlBuilder";import{gamPodIdentityMismatches as d}from"../resolveGamVendorConfiguration";export class GamPodBridge{constructor(t,e){this.state=t,this.deps=e,this.identityMismatchAssetId=null,this.midBreakEndPosition=null,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 a(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();t>.25?this.onAdBreakEndedWithPodLeft(t):this.deps.onAdEnded()},onError:t=>this.deps.log("GAM error:",t),onDuplicateMetadata:t=>{this.deps.diagnose("DA-GAM-DUPLICATE-METADATA",s,{assetId:this.state.gamPodAsset?.id,startTime:t})}});const t=this.state.currentGamIdentity,e=this.deps.resolveSessionAdTagParameters(),i={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(i);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,this.midBreakEndPosition=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.logRestartedPodPosition(t),this.emitGamPodSteps(this.state.gamPodTracker.onAdStarted(t)))):this.state.earlyGamPodAdStart=t)}logRestartedPodPosition(t){const e=this.midBreakEndPosition;null!==e&&(this.midBreakEndPosition=null,"number"==typeof t.adPosition&&t.adPosition<=e&&this.deps.log(`IMA restarted pod ad positions after a mid-pod ad break end (ad ${t.adPosition} after ad ${e})`))}onAdBreakEndedWithPodLeft(t){const e="per-ad"===this.state.gamPodMode?this.state.gamPodTracker:null;if(!e||e.reportedLastAd)return this.deps.log(`GAM pod has ${t.toFixed(3)}s of slate left; playing it out`),void(e&&this.emitGamPodSteps(e.finish()));const s=e.lastAdPosition,a=e.lastTotalAds,i=a>0?` of ${a}`:"";this.deps.log(`IMA ad break ended after ad ${s}${i}; ${t.toFixed(3)}s of pod left, waiting for the next ad`),this.deps.diagnose("DA-GAM-POD-MID-BREAK-END",`IMA reported the ad break ended after ad ${s}${i} with ${t.toFixed(3)}s of pod left; per-ad reporting continues until the pod ends.`,{assetId:this.state.gamPodAsset?.id,adPosition:s,totalAds:a,remainingSec:t}),this.midBreakEndPosition=s,this.emitGamPodSteps(e.onAdBreakEnded())}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(this.identityMismatchAssetId=null,!i(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=d(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 this.identityMismatchAssetId=t.id,null}this.state.earlyGamPodAdStart=null,this.deps.pushAssetParameters(t.assetParameters,t.id);const a=this.state.gamStreamManager.buildPodUrl(t.uri,e,s);return this.deps.log("Built GAM pod URL:",a),a}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}hasIdentityMismatch(t){return this.identityMismatchAssetId===t.id}}
|