@dolby-optiview/ads-sdk-core 1.0.0-beta.1
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 +190 -0
- package/dist/OptiViewAds.js +1 -0
- package/dist/config/keys.d.ts +23 -0
- package/dist/config/keys.js +1 -0
- package/dist/diagnostics/DiagnosticBuffer.d.ts +33 -0
- package/dist/diagnostics/DiagnosticBuffer.js +1 -0
- package/dist/diagnostics/DiagnosticEvent.d.ts +71 -0
- package/dist/diagnostics/DiagnosticEvent.js +1 -0
- package/dist/diagnostics/debugLog.d.ts +8 -0
- package/dist/diagnostics/debugLog.js +1 -0
- package/dist/diagnostics/errorCodes.d.ts +45 -0
- package/dist/diagnostics/errorCodes.js +1 -0
- package/dist/diagnostics/index.d.ts +6 -0
- package/dist/diagnostics/index.js +1 -0
- package/dist/diagnostics/playbackErrorContext.d.ts +22 -0
- package/dist/diagnostics/playbackErrorContext.js +1 -0
- package/dist/generated/diagnosticCodes.d.ts +487 -0
- package/dist/generated/diagnosticCodes.js +1 -0
- package/dist/generated/version.d.ts +5 -0
- package/dist/generated/version.js +1 -0
- package/dist/id3/buildId3.d.ts +25 -0
- package/dist/id3/buildId3.js +1 -0
- package/dist/id3/index.d.ts +3 -0
- package/dist/id3/index.js +1 -0
- package/dist/id3/parseId3Geob.d.ts +28 -0
- package/dist/id3/parseId3Geob.js +1 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +1 -0
- package/dist/interfaces/PlayerAdapter.d.ts +163 -0
- package/dist/interfaces/PlayerAdapter.js +1 -0
- package/dist/internal/index.d.ts +1 -0
- package/dist/internal/index.js +1 -0
- package/dist/server/index.d.ts +20 -0
- package/dist/server/index.js +1 -0
- package/dist/services/AdBreakSequencer.d.ts +146 -0
- package/dist/services/AdBreakSequencer.js +1 -0
- package/dist/services/AdPlayerController.d.ts +495 -0
- package/dist/services/AdPlayerController.js +1 -0
- package/dist/services/BreakScheduler.d.ts +215 -0
- package/dist/services/BreakScheduler.js +1 -0
- package/dist/services/BreakTransitionTimer.d.ts +48 -0
- package/dist/services/BreakTransitionTimer.js +1 -0
- package/dist/services/CmcdAdReporter.d.ts +81 -0
- package/dist/services/CmcdAdReporter.js +1 -0
- package/dist/services/GamPodAdTracker.d.ts +65 -0
- package/dist/services/GamPodAdTracker.js +1 -0
- package/dist/services/GamPodUrlBuilder.d.ts +30 -0
- package/dist/services/GamPodUrlBuilder.js +1 -0
- package/dist/services/GamStreamManager.d.ts +67 -0
- package/dist/services/GamStreamManager.js +1 -0
- package/dist/services/LensReporter.d.ts +30 -0
- package/dist/services/LensReporter.js +1 -0
- package/dist/services/ManifestService.d.ts +91 -0
- package/dist/services/ManifestService.js +1 -0
- package/dist/services/MetadataCueTracker.d.ts +44 -0
- package/dist/services/MetadataCueTracker.js +1 -0
- package/dist/services/PauseAdController.d.ts +108 -0
- package/dist/services/PauseAdController.js +1 -0
- package/dist/services/RewindRearm.d.ts +109 -0
- package/dist/services/RewindRearm.js +1 -0
- package/dist/services/SsaiController.d.ts +59 -0
- package/dist/services/SsaiController.js +1 -0
- package/dist/services/StitcherUrlBuilder.d.ts +34 -0
- package/dist/services/StitcherUrlBuilder.js +1 -0
- package/dist/services/VastAdManager.d.ts +89 -0
- package/dist/services/VastAdManager.js +1 -0
- package/dist/services/adPlayer/AdPlayerEvents.d.ts +39 -0
- package/dist/services/adPlayer/AdPlayerEvents.js +1 -0
- package/dist/services/adPlayer/AdPlayerState.d.ts +98 -0
- package/dist/services/adPlayer/AdPlayerState.js +1 -0
- package/dist/services/adPlayer/AdPreloader.d.ts +54 -0
- package/dist/services/adPlayer/AdPreloader.js +1 -0
- package/dist/services/adPlayer/AdStage.d.ts +57 -0
- package/dist/services/adPlayer/AdStage.js +1 -0
- package/dist/services/adPlayer/AssetParameterResolver.d.ts +44 -0
- package/dist/services/adPlayer/AssetParameterResolver.js +1 -0
- package/dist/services/adPlayer/AssetPlayback.d.ts +71 -0
- package/dist/services/adPlayer/AssetPlayback.js +1 -0
- package/dist/services/adPlayer/BreakSequenceDriver.d.ts +46 -0
- package/dist/services/adPlayer/BreakSequenceDriver.js +1 -0
- package/dist/services/adPlayer/ContentResumeCoordinator.d.ts +44 -0
- package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -0
- package/dist/services/adPlayer/ContinueContentPlayout.d.ts +26 -0
- package/dist/services/adPlayer/ContinueContentPlayout.js +1 -0
- package/dist/services/adPlayer/GamPodBridge.d.ts +66 -0
- package/dist/services/adPlayer/GamPodBridge.js +1 -0
- package/dist/services/adPlayer/PauseAdPresenter.d.ts +60 -0
- package/dist/services/adPlayer/PauseAdPresenter.js +1 -0
- package/dist/services/adPlayer/PresentationModeTracker.d.ts +66 -0
- package/dist/services/adPlayer/PresentationModeTracker.js +1 -0
- package/dist/services/adPlayer/SharedElementBreakPlayer.d.ts +54 -0
- package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -0
- package/dist/services/adPlayer/VariantSelector.d.ts +38 -0
- package/dist/services/adPlayer/VariantSelector.js +1 -0
- package/dist/services/adPlayer/connectPauseAd.d.ts +10 -0
- package/dist/services/adPlayer/connectPauseAd.js +1 -0
- package/dist/services/adPlayer/liveEdge.d.ts +6 -0
- package/dist/services/adPlayer/liveEdge.js +1 -0
- package/dist/services/assetParameterMacroTable.d.ts +9 -0
- package/dist/services/assetParameterMacroTable.js +1 -0
- package/dist/services/assetParameterMacros.d.ts +69 -0
- package/dist/services/assetParameterMacros.js +1 -0
- package/dist/services/breakTiming.d.ts +60 -0
- package/dist/services/breakTiming.js +1 -0
- package/dist/services/channelSwitch.d.ts +32 -0
- package/dist/services/channelSwitch.js +1 -0
- package/dist/services/detectDeviceType.d.ts +15 -0
- package/dist/services/detectDeviceType.js +1 -0
- package/dist/services/detectInsertionMode.d.ts +38 -0
- package/dist/services/detectInsertionMode.js +1 -0
- package/dist/services/detectPreloadMode.d.ts +13 -0
- package/dist/services/detectPreloadMode.js +1 -0
- package/dist/services/ed25519.d.ts +19 -0
- package/dist/services/ed25519.js +1 -0
- package/dist/services/fetchWithTimeout.d.ts +35 -0
- package/dist/services/fetchWithTimeout.js +1 -0
- package/dist/services/lensBackend.d.ts +16 -0
- package/dist/services/lensBackend.js +1 -0
- package/dist/services/lensBackendDevelopment.d.ts +1 -0
- package/dist/services/lensBackendDevelopment.js +1 -0
- package/dist/services/lensBackendProduction.d.ts +9 -0
- package/dist/services/lensBackendProduction.js +1 -0
- package/dist/services/manifestSignature.d.ts +43 -0
- package/dist/services/manifestSignature.js +1 -0
- package/dist/services/manifestTrustedKeys.d.ts +9 -0
- package/dist/services/manifestTrustedKeys.js +1 -0
- package/dist/services/parseBreakManifest.d.ts +15 -0
- package/dist/services/parseBreakManifest.js +1 -0
- package/dist/services/parseVastCompanion.d.ts +31 -0
- package/dist/services/parseVastCompanion.js +1 -0
- package/dist/services/parseVastLinear.d.ts +36 -0
- package/dist/services/parseVastLinear.js +1 -0
- package/dist/services/pictureInPicture.d.ts +28 -0
- package/dist/services/pictureInPicture.js +1 -0
- package/dist/services/resolveAssetParameters.d.ts +6 -0
- package/dist/services/resolveAssetParameters.js +1 -0
- package/dist/services/resolveDeliveryMode.d.ts +54 -0
- package/dist/services/resolveDeliveryMode.js +1 -0
- package/dist/services/resolveGamVendorConfiguration.d.ts +55 -0
- package/dist/services/resolveGamVendorConfiguration.js +1 -0
- package/dist/services/resolvePauseResumePoint.d.ts +32 -0
- package/dist/services/resolvePauseResumePoint.js +1 -0
- package/dist/services/resolvePollingDecision.d.ts +24 -0
- package/dist/services/resolvePollingDecision.js +1 -0
- package/dist/services/resolveResumePoint.d.ts +38 -0
- package/dist/services/resolveResumePoint.js +1 -0
- package/dist/services/resolveTargetedUri.d.ts +22 -0
- package/dist/services/resolveTargetedUri.js +1 -0
- package/dist/services/resolveVastAdmission.d.ts +34 -0
- package/dist/services/resolveVastAdmission.js +1 -0
- package/dist/services/selectVariant.d.ts +47 -0
- package/dist/services/selectVariant.js +1 -0
- package/dist/services/urlOrigin.d.ts +23 -0
- package/dist/services/urlOrigin.js +1 -0
- package/dist/session/AdBreakStatusTracker.d.ts +49 -0
- package/dist/session/AdBreakStatusTracker.js +1 -0
- package/dist/session/BreakLifecycle.d.ts +62 -0
- package/dist/session/BreakLifecycle.js +1 -0
- package/dist/session/SessionStarter.d.ts +98 -0
- package/dist/session/SessionStarter.js +1 -0
- package/dist/session/SessionState.d.ts +37 -0
- package/dist/session/SessionState.js +1 -0
- package/dist/types/BreakManifest.d.ts +335 -0
- package/dist/types/BreakManifest.js +1 -0
- package/dist/types/Events.d.ts +245 -0
- package/dist/types/Events.js +1 -0
- package/dist/types/OptiViewAdsConfig.d.ts +425 -0
- package/dist/types/OptiViewAdsConfig.js +1 -0
- package/dist/types/VendorAdError.d.ts +12 -0
- package/dist/types/VendorAdError.js +1 -0
- package/dist/vendor/lens-sdk/THIRD-PARTY-NOTICES.txt +266 -0
- package/dist/vendor/lens-sdk/compat.d.ts +9 -0
- package/dist/vendor/lens-sdk/encoding-cmcd.d.ts +9 -0
- package/dist/vendor/lens-sdk/encoding-json.d.ts +9 -0
- package/dist/vendor/lens-sdk/encoding.d.ts +42 -0
- package/dist/vendor/lens-sdk/environment.d.ts +35 -0
- package/dist/vendor/lens-sdk/index.d.ts +3 -0
- package/dist/vendor/lens-sdk/index.js +1 -0
- package/dist/vendor/lens-sdk/legacy.d.ts +12 -0
- package/dist/vendor/lens-sdk/lens-core-Q5lbQGMC.js +1 -0
- package/dist/vendor/lens-sdk/lens-global.d.ts +23 -0
- package/dist/vendor/lens-sdk/lens.d.ts +21 -0
- package/dist/vendor/lens-sdk/transport.d.ts +46 -0
- package/dist/vendor/lens-sdk/types.d.ts +218 -0
- package/dist/vendor/lens-sdk/user-id.d.ts +5 -0
- package/dist/vendor/lens-sdk/validation.d.ts +30 -0
- package/package.json +57 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { PlayerAdapter, PlayerAdapterEventHandler, TimedMetadataCue } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import { type Break } from '../../types/BreakManifest';
|
|
3
|
+
import type { GamEventCallbacks } from '../AdPlayerController';
|
|
4
|
+
import type { AdBreakSequencer, AdBreakTransition } from '../AdBreakSequencer';
|
|
5
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
6
|
+
export interface AdPlayerEventsDeps {
|
|
7
|
+
getAdPlayer: () => PlayerAdapter | null;
|
|
8
|
+
getContentPlayer: () => PlayerAdapter;
|
|
9
|
+
getGamEventCallbacks: () => GamEventCallbacks;
|
|
10
|
+
getSequencer: () => AdBreakSequencer | null;
|
|
11
|
+
getAudioFocus: () => 'content' | 'ad' | null;
|
|
12
|
+
getSyncing: () => boolean;
|
|
13
|
+
setSyncing: (value: boolean) => void;
|
|
14
|
+
setAdVolumeChangeHandler: (handler: PlayerAdapterEventHandler | null) => void;
|
|
15
|
+
setContentVolumeHandler: (handler: (() => void) | null) => void;
|
|
16
|
+
notifyVolumeChange: () => void;
|
|
17
|
+
onAdEnded: () => void;
|
|
18
|
+
processTimedMetadata: (cue: TimedMetadataCue) => void;
|
|
19
|
+
toAdError: (mediaError?: unknown) => Error;
|
|
20
|
+
revealFirstFrame: () => void;
|
|
21
|
+
clearFirstFrameResolver: () => void;
|
|
22
|
+
drive: (transition: AdBreakTransition) => Promise<void>;
|
|
23
|
+
setPendingAdError: (value: Error | null) => void;
|
|
24
|
+
log: (...args: unknown[]) => void;
|
|
25
|
+
maybePrepositionContentForResume: () => void;
|
|
26
|
+
maybeCatchUpContentForResume: (breakInfo: Break) => void;
|
|
27
|
+
getStaticQuartilesFired: () => Set<'first' | 'mid' | 'third'>;
|
|
28
|
+
}
|
|
29
|
+
export declare class AdPlayerEvents {
|
|
30
|
+
private readonly state;
|
|
31
|
+
private readonly deps;
|
|
32
|
+
constructor(state: AdPlayerState, deps: AdPlayerEventsDeps);
|
|
33
|
+
/** Summed into `adSecondsPlayedThisBreak`, which caps the resume offset to the ads actually delivered. */
|
|
34
|
+
recordAdProgress(currentTime: number): void;
|
|
35
|
+
settleAssetProgress(): void;
|
|
36
|
+
reportAdProgress(player: PlayerAdapter): void;
|
|
37
|
+
setupEventListeners(): void;
|
|
38
|
+
onAdError(error: Error): void;
|
|
39
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssetType as t}from"../../types/BreakManifest";export class AdPlayerEvents{constructor(t,e){this.state=t,this.deps=e}recordAdProgress(t){!Number.isFinite(t)||t<=0||t>this.state.currentAssetElapsedSec&&(this.state.currentAssetElapsedSec=t)}settleAssetProgress(){let t=this.state.currentAssetElapsedSec;if(t<=0){const e=this.state.currentAsset?.duration;"number"==typeof e&&Number.isFinite(e)&&e>0&&(t=e)}this.state.adSecondsPlayedThisBreak+=t,this.state.currentAssetElapsedSec=0}reportAdProgress(e){if(this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&(this.recordAdProgress(e.currentTime),this.deps.maybePrepositionContentForResume(),this.state.currentBreak&&this.deps.maybeCatchUpContentForResume(this.state.currentBreak),this.state.callbacks?.onAdTimeUpdate(this.state.currentBreak,this.state.currentAsset,e.currentTime,e.duration),this.state.currentAsset.type===t.STATIC&&e.duration>0)){const t=e.currentTime/e.duration,s=this.deps.getStaticQuartilesFired(),r=this.deps.getGamEventCallbacks();t>=.25&&!s.has("first")&&(s.add("first"),r.onAdFirstQuartile?.(this.state.currentBreak,this.state.currentAsset)),t>=.5&&!s.has("mid")&&(s.add("mid"),r.onAdMidpoint?.(this.state.currentBreak,this.state.currentAsset)),t>=.75&&!s.has("third")&&(s.add("third"),r.onAdThirdQuartile?.(this.state.currentBreak,this.state.currentAsset))}}setupEventListeners(){const t=this.deps.getAdPlayer();if(!t)return;t.on("ended",()=>{this.deps.onAdEnded()}),t.on("timedmetadata",t=>{t&&this.deps.processTimedMetadata(t)}),t.on("error",t=>{this.onAdError(this.deps.toAdError(t))}),t.on("timeupdate",()=>this.reportAdProgress(t)),t.on("waiting",()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&this.state.callbacks?.onAdWaiting(this.state.currentBreak,this.state.currentAsset)}),t.on("playing",()=>{if(this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset)return this.state.firstFrameResolver?(this.deps.revealFirstFrame(),this.state.firstFrameResolver(),void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset)):void this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset)});const e=()=>{const t=this.deps.getAdPlayer();if(this.deps.getSyncing()||!t)return;const e=this.deps.getAudioFocus();e?"ad"===e?this.deps.notifyVolumeChange():(this.deps.setSyncing(!0),t.muted=!0,t.volume=0,this.deps.setSyncing(!1)):this.state.isPlaying&&(this.deps.setSyncing(!0),this.deps.getContentPlayer().muted=t.muted,this.deps.getContentPlayer().volume=t.volume,this.deps.setSyncing(!1),this.deps.notifyVolumeChange())};this.deps.setAdVolumeChangeHandler(e),t.on("volumechange",e);const s=()=>{const t=this.deps.getAdPlayer();if(this.deps.getSyncing()||!t)return;const e=this.deps.getAudioFocus();e?"content"===e?this.deps.notifyVolumeChange():(this.deps.setSyncing(!0),this.deps.getContentPlayer().muted=!0,this.deps.setSyncing(!1)):(this.deps.setSyncing(!0),t.muted=this.deps.getContentPlayer().muted,t.volume=this.deps.getContentPlayer().volume,this.deps.setSyncing(!1),this.deps.notifyVolumeChange())};this.deps.setContentVolumeHandler(s),this.deps.getContentPlayer().on("volumechange",s)}onAdError(t){const e=this.deps.getSequencer();if(this.state.currentBreak&&e){if(this.state.firstFrameRejector){const e=this.state.firstFrameRejector;this.deps.clearFirstFrameResolver(),e(t)}this.deps.log("Ad error:",t.message),this.deps.setPendingAdError(t),this.deps.drive(e.onOutcome({kind:"asset-errored"}))}}}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Asset, Break, DeviceType, Timebase } from '../../types/BreakManifest';
|
|
2
|
+
import type { AdInsertionType } from '../../types/OptiViewAdsConfig';
|
|
3
|
+
import type { AdPlayerCallbacks } from '../AdPlayerController';
|
|
4
|
+
import type { AdBreakStep } from '../AdBreakSequencer';
|
|
5
|
+
import type { GamStreamManager } from '../GamStreamManager';
|
|
6
|
+
import type { GamPodAdTracker } from '../GamPodAdTracker';
|
|
7
|
+
import type { GamConfig } from '../../types/OptiViewAdsConfig';
|
|
8
|
+
import type { GamSessionIdentity } from '../resolveGamVendorConfiguration';
|
|
9
|
+
import type { AssetParameterMacros } from '../assetParameterMacros';
|
|
10
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
11
|
+
import type { VastAdManager } from '../VastAdManager';
|
|
12
|
+
export interface AdPlayerState {
|
|
13
|
+
currentBreak: Break | null;
|
|
14
|
+
currentAsset: Asset | null;
|
|
15
|
+
isPlaying: boolean;
|
|
16
|
+
contentPaused: boolean;
|
|
17
|
+
callbacks: AdPlayerCallbacks | null;
|
|
18
|
+
durationTimer: ReturnType<typeof setTimeout> | null;
|
|
19
|
+
effectiveBreakDurationSec: number | null;
|
|
20
|
+
adInsertionType: AdInsertionType | undefined;
|
|
21
|
+
breakTimebase: Timebase | null;
|
|
22
|
+
adBreakCutSafetyMarginSec: number;
|
|
23
|
+
detectedDeviceType: DeviceType | undefined;
|
|
24
|
+
sessionAssetParameters: Record<string, string> | undefined;
|
|
25
|
+
updatedAssetParameters: Record<string, string> | undefined;
|
|
26
|
+
manifestAssetParameters: Record<string, string> | undefined;
|
|
27
|
+
sessionAssetParameterMacros: AssetParameterMacros;
|
|
28
|
+
pushedAssetParameters: Record<string, string> | undefined | null;
|
|
29
|
+
gamStreamManager: GamStreamManager | null;
|
|
30
|
+
gamPodMode: 'none' | 'pending' | 'per-ad' | 'legacy';
|
|
31
|
+
gamPodTracker: GamPodAdTracker | null;
|
|
32
|
+
gamPodAsset: Asset | null;
|
|
33
|
+
gamPodReportTimer: ReturnType<typeof setTimeout> | null;
|
|
34
|
+
withheldPodAdBegin: AdBreakStep | null;
|
|
35
|
+
gamConfig: GamConfig | undefined;
|
|
36
|
+
gamInitPromise: Promise<void> | null;
|
|
37
|
+
currentGamIdentity: GamSessionIdentity | undefined;
|
|
38
|
+
playerContainer: HTMLElement | null;
|
|
39
|
+
stage: HTMLDivElement | null;
|
|
40
|
+
contentVideoElement: HTMLVideoElement | null;
|
|
41
|
+
adContainer: HTMLDivElement | null;
|
|
42
|
+
adMediaContainer: HTMLElement | null;
|
|
43
|
+
adPlayer: PlayerAdapter | null;
|
|
44
|
+
companionElement: HTMLImageElement | HTMLVideoElement | null;
|
|
45
|
+
overlayImageEl: HTMLImageElement | null;
|
|
46
|
+
isContentFullscreen: boolean;
|
|
47
|
+
trackedContentVideo: HTMLVideoElement | null;
|
|
48
|
+
fullscreenEnterHandler: EventListener | null;
|
|
49
|
+
fullscreenExitHandler: EventListener | null;
|
|
50
|
+
contentPictureInPicture: boolean;
|
|
51
|
+
pipTransferredToAd: boolean;
|
|
52
|
+
pipAdoptedForBreak: boolean;
|
|
53
|
+
sharedAdClaimActive: boolean;
|
|
54
|
+
savedContentSrc: string | null;
|
|
55
|
+
savedContentTime: number;
|
|
56
|
+
savedContentLive: boolean;
|
|
57
|
+
savedContentEdgeDistance: number | null;
|
|
58
|
+
earlyReturnResumeOffsetSec: number | null;
|
|
59
|
+
contentPositionAtBreakStart: number;
|
|
60
|
+
resumePrepositionedTo: number | null;
|
|
61
|
+
resumePrepositionSettle: ReturnType<typeof setTimeout> | null;
|
|
62
|
+
resumeCatchupActive: boolean;
|
|
63
|
+
lastResumeAlignRemainingSec: number;
|
|
64
|
+
resumeAlignBlockedDiagnosed: boolean;
|
|
65
|
+
lastResumeSafeEnd: number;
|
|
66
|
+
resumeSawSeekDone: boolean;
|
|
67
|
+
resumeBestSawMarginSec: number;
|
|
68
|
+
resumeAlignTicks: number;
|
|
69
|
+
resumeSawSteps: number[];
|
|
70
|
+
continueContentDuringBreak: boolean;
|
|
71
|
+
continueQualityOverrideApplied: boolean;
|
|
72
|
+
continueContentDuringBreakActive: boolean;
|
|
73
|
+
keepContentPlayingHandler: (() => void) | null;
|
|
74
|
+
continuePlayoutStartWallMs: number;
|
|
75
|
+
continuePlayoutTicker: ReturnType<typeof setInterval> | null;
|
|
76
|
+
continueRateWritten: number | null;
|
|
77
|
+
continueRateWrittenAtMs: number;
|
|
78
|
+
continueRateHonoured: boolean;
|
|
79
|
+
continueMaxAbsDriftSec: number;
|
|
80
|
+
continueCorrections: number[];
|
|
81
|
+
continueDriftUnrecoverable: boolean;
|
|
82
|
+
continueRefusalReported: boolean;
|
|
83
|
+
contentMutedBeforeCatchup: boolean | null;
|
|
84
|
+
resumeAlignCorrectionsMs: number[];
|
|
85
|
+
resumeLandedProbe: (() => void) | null;
|
|
86
|
+
adSecondsPlayedThisBreak: number;
|
|
87
|
+
currentAssetElapsedSec: number;
|
|
88
|
+
preloadedBreakId: string | null;
|
|
89
|
+
firstFrameGateActive: boolean;
|
|
90
|
+
firstFrameGateShown: boolean;
|
|
91
|
+
firstFrameResolver: (() => void) | null;
|
|
92
|
+
firstFrameRejector: ((error: Error) => void) | null;
|
|
93
|
+
firstFrameTimeout: ReturnType<typeof setTimeout> | null;
|
|
94
|
+
adStartDeadlineMs: number | null;
|
|
95
|
+
adStartBudgetMs: number;
|
|
96
|
+
vastAdManager: VastAdManager | null;
|
|
97
|
+
}
|
|
98
|
+
export declare function createAdPlayerState(): AdPlayerState;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,effectiveBreakDurationSec:null,adInsertionType:void 0,breakTimebase:null,adBreakCutSafetyMarginSec:0,detectedDeviceType:void 0,sessionAssetParameters:void 0,updatedAssetParameters:void 0,manifestAssetParameters:void 0,sessionAssetParameterMacros:{},pushedAssetParameters:null,gamStreamManager:null,gamPodMode:"none",gamPodTracker:null,gamPodAsset:null,gamPodReportTimer:null,withheldPodAdBegin:null,gamConfig:void 0,gamInitPromise:null,currentGamIdentity:void 0,playerContainer:null,stage:null,contentVideoElement:null,adContainer:null,adMediaContainer:null,adPlayer:null,companionElement:null,overlayImageEl:null,isContentFullscreen:!1,trackedContentVideo:null,fullscreenEnterHandler:null,fullscreenExitHandler:null,contentPictureInPicture:!1,pipTransferredToAd:!1,pipAdoptedForBreak:!1,sharedAdClaimActive:!1,savedContentSrc:null,savedContentTime:0,savedContentLive:!1,savedContentEdgeDistance:null,earlyReturnResumeOffsetSec:null,contentPositionAtBreakStart:0,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,lastResumeAlignRemainingSec:Number.POSITIVE_INFINITY,resumeAlignBlockedDiagnosed:!1,lastResumeSafeEnd:Number.NEGATIVE_INFINITY,resumeSawSeekDone:!1,resumeBestSawMarginSec:Number.NEGATIVE_INFINITY,resumeAlignTicks:0,resumeSawSteps:[],continueContentDuringBreak:!1,continueQualityOverrideApplied:!1,continueContentDuringBreakActive:!1,keepContentPlayingHandler:null,continuePlayoutStartWallMs:0,continuePlayoutTicker:null,continueRateWritten:null,continueRateWrittenAtMs:0,continueRateHonoured:!0,continueMaxAbsDriftSec:0,continueCorrections:[],continueDriftUnrecoverable:!1,continueRefusalReported:!1,contentMutedBeforeCatchup:null,resumeAlignCorrectionsMs:[],resumeLandedProbe:null,adSecondsPlayedThisBreak:0,currentAssetElapsedSec:0,preloadedBreakId:null,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import { Asset, Break, BreakVariant, StaticAsset, VastAsset } from '../../types/BreakManifest';
|
|
3
|
+
import type { ResolvedPreloadMode } from '../detectPreloadMode';
|
|
4
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
5
|
+
import type { Clock } from '../BreakScheduler';
|
|
6
|
+
export interface AdPreloaderDeps {
|
|
7
|
+
state: AdPlayerState;
|
|
8
|
+
clock: Clock;
|
|
9
|
+
adStartBudgetMs: number;
|
|
10
|
+
gamPodAdStartBudgetMs: number;
|
|
11
|
+
minAdStartBudgetMs: number;
|
|
12
|
+
debug: boolean;
|
|
13
|
+
isPlaying: () => boolean;
|
|
14
|
+
preloadMode: ResolvedPreloadMode;
|
|
15
|
+
effectiveInsertionMode: () => string;
|
|
16
|
+
getVariant: (breakInfo: Break) => BreakVariant | null;
|
|
17
|
+
getAssets: (breakInfo: Break) => Asset[];
|
|
18
|
+
resolveUri: (uri: StaticAsset['uri']) => string | null;
|
|
19
|
+
resolveAssetUri: (asset: Asset, duration: number) => Promise<string | null>;
|
|
20
|
+
getCompanionVariant: (breakInfo: Break) => BreakVariant | null;
|
|
21
|
+
getAdPlayer: () => PlayerAdapter | null;
|
|
22
|
+
getAdContainer: () => HTMLDivElement | null;
|
|
23
|
+
buildVastTagUrl: (asset: VastAsset, breakInfo: Break) => string | null;
|
|
24
|
+
resolveChainedSuccessor: (currentBreakId: string) => Break | null;
|
|
25
|
+
show: () => void;
|
|
26
|
+
reapplyAdContainerLayout: (variant: BreakVariant) => void;
|
|
27
|
+
getCurrentBreak: () => Break | null;
|
|
28
|
+
onBreakVisible: (breakInfo: Break) => void;
|
|
29
|
+
onAdError: (error: Error) => void;
|
|
30
|
+
diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
|
|
31
|
+
log: (...args: unknown[]) => void;
|
|
32
|
+
}
|
|
33
|
+
/** Owns ad preload classification, cache warming, and attached-player preloading. */
|
|
34
|
+
export declare class AdPreloader {
|
|
35
|
+
private readonly deps;
|
|
36
|
+
constructor(deps: AdPreloaderDeps);
|
|
37
|
+
private readonly warmedBreakIds;
|
|
38
|
+
private classifyCreative;
|
|
39
|
+
private diagnosePreloadPath;
|
|
40
|
+
preload(breakInfo: Break): Promise<void>;
|
|
41
|
+
preloadVastAsset(asset: VastAsset, breakInfo: Break): Promise<void>;
|
|
42
|
+
preloadChainedSuccessor(currentBreakId: string): void;
|
|
43
|
+
clearWarmedBreaks(): void;
|
|
44
|
+
private preloadNext;
|
|
45
|
+
revealFirstFrame(): void;
|
|
46
|
+
armAdStartBudget(asset: Asset): void;
|
|
47
|
+
remainingAdStartBudgetMs(): number;
|
|
48
|
+
loadWithinBudget(load: Promise<void>): Promise<boolean>;
|
|
49
|
+
waitForFirstFrame(): Promise<void>;
|
|
50
|
+
clearFirstFrame(reason?: string): void;
|
|
51
|
+
clearFirstFrameResolver(): void;
|
|
52
|
+
preloadCompanion(breakInfo: Break): void;
|
|
53
|
+
warmCache(urls: string[]): Promise<void>;
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssetType as e,BreakFormat as t,MediaType as s,Vendor as a,isVastAsset as i,isVendorAsset as r}from"../../types/BreakManifest";import{AUX_FETCH_TIMEOUT_MS as d,fetchWithTimeout as o}from"../fetchWithTimeout";import{resolveVastAdmission as n}from"../resolveVastAdmission";import{isImaCsaiSdkAvailable as p,VastAdManager as l}from"../VastAdManager";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");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 r=this.deps.getAssets(a);if(0===r.length)return void this.deps.log("No assets to preload");this.preloadCompanion(a);const d=this.deps.getVariant(a);if(d?.format===t.LSHAPE_CONTENT)return void(this.deps.state.preloadedBreakId=a.id);if(r[0].mediaType===s.IMAGE){const e=await this.deps.resolveAssetUri(r[0],a.duration);if(e){(new Image).src=e,this.deps.log("Preloading overlay image:",e)}return void(this.deps.state.preloadedBreakId=a.id)}if(i(r[0]))return void await this.preloadVastAsset(r[0],a);const o=await this.deps.resolveAssetUri(r[0],a.duration);if(!o)return void this.deps.log("No valid URI for preload");const n=this.deps.getAdPlayer();if(n)if("single-decoder"!==this.deps.preloadMode){this.deps.log("Preloading ad (parallel):",o),this.diagnosePreloadPath(a.id,o,"parallel");try{await n.load(o),this.deps.state.preloadedBreakId=a.id,this.deps.log("Preload complete for break:",a.id)}catch(e){this.deps.log("Preload failed (will load on demand):",e)}}else{this.deps.log("Preloading ad (single-decoder, detached):",o),this.diagnosePreloadPath(a.id,o,"single-decoder"),await this.warmCache([o]);try{await(n.preload?.(o))}catch(e){this.deps.log("Detached prefetch failed (will load on demand):",e)}}}async preloadVastAsset(e,s){const a=this.deps.getVariant(s),i=a?.format??t.SINGLE;if(!n({format:i,ssai:!1}).admitted)return void this.deps.log("VAST preload skipped: format not supported",i);if(!p())return void this.deps.log("VAST preload skipped: IMA client-side SDK unavailable");const r=this.deps.getAdPlayer(),d=this.deps.getAdContainer();if(!r||!d)return void this.deps.log("VAST preload skipped: ad player not initialized");const o=r.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 l(d,o,this.deps.debug)),this.deps.log("Preloading VAST asset via IMA CSAI:",h);try{await this.deps.state.vastAdManager.preloadVast(h,{muted:r.muted}),this.deps.state.preloadedBreakId=s.id,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 i=this.deps.getAssets(s)[0];if(!i||i.type!==e.STATIC)return void this.deps.log("Chained successor first asset not static; skipping media warm:",s.id);const r=this.deps.resolveUri(i.uri);if(r){this.warmedBreakIds.add(s.id),this.deps.log("Warming chained successor media:",s.id,r),await this.warmCache([r]);try{await(this.deps.getAdPlayer()?.preload?.(r))}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=r(e)&&e.vendor===a.GAM?this.deps.gamPodAdStartBudgetMs:this.deps.adStartBudgetMs,i=Math.max(Math.min(t-100,s),this.deps.minAdStartBudgetMs);this.deps.state.adStartBudgetMs=i,this.deps.state.adStartDeadlineMs=this.deps.clock.now()+i}remainingAdStartBudgetMs(){return Math.max(0,(this.deps.state.adStartDeadlineMs??this.deps.clock.now())-this.deps.clock.now())}loadWithinBudget(e){return new Promise((t,s)=>{const a=setTimeout(()=>{this.deps.onAdError(new Error("Ad load timeout")),t(!1)},this.remainingAdStartBudgetMs());e.then(()=>{clearTimeout(a),t(!0)},e=>{clearTimeout(a),s(e instanceof Error?e:new Error(String(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 Error("First frame 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 i=a.assets[0]?.companion;if(!i||i.type!==e.STATIC)return;const r=this.deps.resolveUri(i.uri);if(!r)return;(new Image).src=r,this.deps.log("Preloading companion:",r)}async warmCache(e){"function"==typeof fetch&&await Promise.allSettled(e.map(e=>o(e,{mode:"no-cors"},d).catch(()=>{})))}}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import { AssetWithCompanion, Break, BreakFormat, BreakVariant, LshapeContentBreakVariant, StaticAsset } from '../../types/BreakManifest';
|
|
3
|
+
export interface AdStageDeps {
|
|
4
|
+
contentPlayer: PlayerAdapter;
|
|
5
|
+
container: HTMLElement;
|
|
6
|
+
getPlayerContainer: () => HTMLElement | undefined;
|
|
7
|
+
setPlayerContainer: (container: HTMLElement) => void;
|
|
8
|
+
setPlayerContainerOriginalStyle: (style: string) => void;
|
|
9
|
+
onPlayerContainerCreated: () => void;
|
|
10
|
+
getStage: () => HTMLDivElement | null;
|
|
11
|
+
setStage: (stage: HTMLDivElement | null) => void;
|
|
12
|
+
getContentVideo: () => HTMLVideoElement | null;
|
|
13
|
+
setContentVideo: (video: HTMLVideoElement | null) => void;
|
|
14
|
+
getAdContainer: () => HTMLDivElement | null;
|
|
15
|
+
setAdContainer: (container: HTMLDivElement | null) => void;
|
|
16
|
+
getAdMediaContainer: () => HTMLElement | null;
|
|
17
|
+
setAdMediaContainer: (container: HTMLElement | null) => void;
|
|
18
|
+
setAdPlayer: (player: PlayerAdapter | null) => void;
|
|
19
|
+
createAdAdapter: (container: HTMLElement) => PlayerAdapter;
|
|
20
|
+
setupEventListeners: () => void;
|
|
21
|
+
resizeVast: () => void;
|
|
22
|
+
getVariant: (breakInfo: Break) => BreakVariant | null;
|
|
23
|
+
getCurrentBreak: () => Break | null;
|
|
24
|
+
resolveUri: (uri: string | StaticAsset['uri']) => string | null;
|
|
25
|
+
getCompanion: () => HTMLImageElement | HTMLVideoElement | null;
|
|
26
|
+
setCompanion: (element: HTMLImageElement | HTMLVideoElement | null) => void;
|
|
27
|
+
getOverlayImage: () => HTMLImageElement | null;
|
|
28
|
+
setOverlayImage: (element: HTMLImageElement | null) => void;
|
|
29
|
+
getImageAssetTimer: () => ReturnType<typeof setTimeout> | null;
|
|
30
|
+
setImageAssetTimer: (timer: ReturnType<typeof setTimeout> | null) => void;
|
|
31
|
+
isChaining: () => boolean;
|
|
32
|
+
log: (...args: unknown[]) => void;
|
|
33
|
+
}
|
|
34
|
+
/** Owns the overlay stage geometry, break layout, companions, and fade transitions. */
|
|
35
|
+
export declare class AdStage {
|
|
36
|
+
private readonly deps;
|
|
37
|
+
private contentVideoGeometryHandler;
|
|
38
|
+
private contentVideoMutationObserver;
|
|
39
|
+
private resizeObserver;
|
|
40
|
+
private companionRemoveTimer;
|
|
41
|
+
private adFadeTimer;
|
|
42
|
+
constructor(deps: AdStageDeps);
|
|
43
|
+
setupOverlay(): void;
|
|
44
|
+
private refreshStageGeometry;
|
|
45
|
+
private rebindContentVideoGeometryListener;
|
|
46
|
+
private updateStageGeometry;
|
|
47
|
+
private getContentAspectRatio;
|
|
48
|
+
renderLshapeContentBackdrop(variant: LshapeContentBreakVariant): void;
|
|
49
|
+
reapplyAdContainerLayout(variant: BreakVariant): void;
|
|
50
|
+
applyLayout(variant: BreakVariant): void;
|
|
51
|
+
restoreLayout(format?: BreakFormat): void;
|
|
52
|
+
renderCompanion(assets: AssetWithCompanion[], cssText: string): void;
|
|
53
|
+
clearOverlayImage(): void;
|
|
54
|
+
show(): void;
|
|
55
|
+
hide(): void;
|
|
56
|
+
destroy(): void;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssetType as e,BreakFormat as t}from"../../types/BreakManifest";const s=16/9;export class AdStage{constructor(e){this.deps=e,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.companionRemoveTimer=null,this.adFadeTimer=null}setupOverlay(){const e=window.getComputedStyle(this.deps.container);"static"!==e.position&&""!==e.position||(this.deps.container.style.position="relative"),this.deps.setStage(document.createElement("div"));const t=this.deps.getStage();if(!t)return;if(t.className="dolby-stage",t.style.cssText="position:absolute;overflow:hidden;",this.deps.container.appendChild(t),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})),this.resizeObserver=new ResizeObserver(()=>{this.refreshStageGeometry()}),this.resizeObserver.observe(this.deps.container),this.refreshStageGeometry(),this.deps.getPlayerContainer()){const e=this.deps.getPlayerContainer();if(!e)return;t.appendChild(e)}else{const e=document.createElement("div"),s=Array.from(this.deps.container.childNodes).filter(e=>e!==t);for(const t of s)e.appendChild(t);t.appendChild(e),this.deps.setPlayerContainer(e),this.deps.onPlayerContainerCreated()}const s=this.deps.getPlayerContainer();if(!s)return;this.deps.setPlayerContainerOriginalStyle(s.style.cssText),s.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.deps.setAdContainer(document.createElement("div"));const i=this.deps.getAdContainer();if(!i)return;i.className="dolby-ad-container",i.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 o=this.deps.getAdMediaContainer();o&&(o.style.cssText="display:block;width:100%;height:100%;",i.appendChild(o),t.appendChild(i),this.deps.setAdPlayer(this.deps.createAdAdapter(o)),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 a=(s-n)/2,r=(t-o)/2;e.style.width=`${o}px`,e.style.height=`${n}px`,e.style.top=`${a}px`,e.style.left=`${r}px`}getContentAspectRatio(){const e=this.deps.contentPlayer.videoElement;if(!e)return s;const{videoWidth:t,videoHeight:i}=e;return!Number.isFinite(t)||!Number.isFinite(i)||t<=0||i<=0?s:t/i}renderLshapeContentBackdrop(t){const s=t.assets[0];if(!s||s.type!==e.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 s=this.deps.getAdContainer();if(s){switch(e.format){case t.LSHAPE_AD:s.style.position="absolute",s.style.top="5%",s.style.left="5%",s.style.right="30%",s.style.bottom="30%",s.style.width="",s.style.height="",s.style.zIndex="100";break;case t.DOUBLE:s.style.position="absolute",s.style.top="26.04%",s.style.left="51.04%",s.style.right="1.04%",s.style.bottom="26.04%",s.style.width="",s.style.height="",s.style.zIndex="100"}this.deps.log("reapplyAdContainerLayout: re-stamped adContainer CSS for format:",e.format)}}applyLayout(e){const s=this.deps.getAdContainer(),i=this.deps.getPlayerContainer();if(!s||!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 t.SINGLE:s.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 t.DOUBLE:i.style.cssText=`position:absolute;top:26.04%;left:1.04%;right:51.04%;bottom:26.04%;z-index:101;${o}`,s.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 t.LSHAPE_AD:i.style.cssText=`position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:101;opacity:0;${o}`,s.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 t.LSHAPE_CONTENT:i.style.top="5%",i.style.left="5%",i.style.right="30%",i.style.bottom="30%",i.style.zIndex="101";break;case t.OVERLAY:{const t=e,i=e=>void 0!==e?100*e+"%":"auto",o=i(t.position.top),n=i(t.position.bottom),a=i(t.position.left),r=i(t.position.right),d=100*t.size.width+"%",l=100*t.size.height+"%",p=t.opacity??1;s.style.cssText=`display:none;position:absolute;top:${o};bottom:${n};left:${a};right:${r};width:${d};height:${l};z-index:100;opacity:${p};`;break}default:s.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;"}}restoreLayout(e){const s=e===t.DOUBLE||e===t.LSHAPE_AD||e===t.LSHAPE_CONTENT,i=this.deps.getPlayerContainer();if(!i)return;i.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;"+(s?"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 t=this.deps.getPlayerContainer();s&&t&&(t.style.zIndex="")},300)}renderCompanion(t,s){const i=t[0]?.companion;if(!i)return;const o=i.type===e.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 a=this.deps.getStage();a&&(a.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 s=this.deps.getCurrentBreak(),i=s?this.deps.getVariant(s):null;e.style.display="block",i?.format!==t.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 s=this.deps.getCurrentBreak(),i=s?this.deps.getVariant(s):null;i?.format!==t.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"}destroy(){const e=this.deps.getContentVideo();e&&this.contentVideoGeometryHandler&&(e.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),e.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoMutationObserver?.disconnect(),this.resizeObserver?.disconnect(),this.contentVideoMutationObserver=null,this.resizeObserver=null,this.contentVideoGeometryHandler=null,this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer),this.adFadeTimer&&clearTimeout(this.adFadeTimer)}}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import type { AssetUri, Break, VastAsset } from '../../types/BreakManifest';
|
|
3
|
+
import { AssetParameterMacros } from '../assetParameterMacros';
|
|
4
|
+
import type { GamStreamManager } from '../GamStreamManager';
|
|
5
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
6
|
+
export interface AssetParameterResolverDeps {
|
|
7
|
+
contentPlayer: PlayerAdapter;
|
|
8
|
+
container: HTMLElement;
|
|
9
|
+
getGamStreamManager: () => GamStreamManager | null;
|
|
10
|
+
getCurrentBreak: () => Break | null;
|
|
11
|
+
getEffectiveBreakDuration: () => number | null;
|
|
12
|
+
diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
|
|
13
|
+
log: (...args: unknown[]) => void;
|
|
14
|
+
}
|
|
15
|
+
/** Owns asset-parameter layering, macro expansion, targeted URI resolution, and IMA pushes. */
|
|
16
|
+
export declare class AssetParameterResolver {
|
|
17
|
+
private readonly state;
|
|
18
|
+
private readonly deps;
|
|
19
|
+
constructor(state: AdPlayerState, deps: AssetParameterResolverDeps);
|
|
20
|
+
setSessionAssetParameters(params: Record<string, string> | undefined): void;
|
|
21
|
+
setManifestAssetParameters(params: Record<string, string> | undefined): void;
|
|
22
|
+
updateAssetParameters(params: Record<string, string>): void;
|
|
23
|
+
setSessionAssetParameterMacros(macros: AssetParameterMacros | undefined): void;
|
|
24
|
+
/**
|
|
25
|
+
* Substitute the `$` macros in an effective (already layered) parameter map.
|
|
26
|
+
* Precedence per macro name: session macros, then the built-ins; a customer
|
|
27
|
+
* macro without a value falls through to the built-in.
|
|
28
|
+
* Every unresolved macro is sent literally and reported as
|
|
29
|
+
* `DA-ASSET-MACRO-UNKNOWN`.
|
|
30
|
+
*/
|
|
31
|
+
private resolveAssetParameterMacros;
|
|
32
|
+
resolveSessionAdTagParameters(): Record<string, string> | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The VAST tag URL with the effective asset parameters appended. Preload and
|
|
35
|
+
* playback both go through here so the two URLs are byte-identical.
|
|
36
|
+
*/
|
|
37
|
+
buildVastTagUrl(asset: VastAsset, breakInfo: Break): string | null;
|
|
38
|
+
/**
|
|
39
|
+
* IMA's `replaceAdTagParameters` replaces the whole map, so every push carries the full
|
|
40
|
+
* effective map. `undefined` falls back to the session baseline (the restore at break end).
|
|
41
|
+
*/
|
|
42
|
+
pushAssetParameters(assetParameters: Record<string, string> | undefined, assetId?: string): void;
|
|
43
|
+
resolveUri(uri: string | AssetUri[]): string | null;
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isVendorAsset as e}from"../../types/BreakManifest";import{appendQueryParameters as s,applyAssetParameterMacros as t,omitBreakScopedParameters as a}from"../assetParameterMacros";import{buildAssetParameterMacroTable as r}from"../assetParameterMacroTable";import{resolveAssetParameters as i}from"../resolveAssetParameters";import{detectDeviceType as o}from"../detectDeviceType";import{resolveTargetedUri as n}from"../resolveTargetedUri";function d(e,s){if(e&&0===Object.keys(e).length&&(e=void 0),s&&0===Object.keys(s).length&&(s=void 0),e===s)return!0;if(!e||!s)return!1;const t=Object.keys(e);return t.length===Object.keys(s).length&&t.every(t=>e[t]===s[t])}export class AssetParameterResolver{constructor(e,s){this.state=e,this.deps=s}setSessionAssetParameters(e){this.state.sessionAssetParameters=e}setManifestAssetParameters(s){if(d(this.state.manifestAssetParameters,s))return;if(this.state.manifestAssetParameters=s,!this.deps.getGamStreamManager()?.isInitialized())return void this.deps.log("Manifest asset parameters stored before GAM initialization");const t=this.state.gamPodAsset;this.pushAssetParameters(t&&e(t)?t.assetParameters:void 0,t?.id),this.deps.diagnose("DA-ASSET-PARAMETERS-UPDATED","Manifest asset parameters changed; IMA targeting updated.",{source:"manifest",keys:Object.keys(s??{}).sort()})}updateAssetParameters(e){this.state.updatedAssetParameters=e,this.deps.getGamStreamManager()?.isInitialized()?this.pushAssetParameters(void 0):this.deps.log("updateAssetParameters: GAM not initialized, stored for VAST only")}setSessionAssetParameterMacros(e){this.state.sessionAssetParameterMacros={...e}}resolveAssetParameterMacros(e,s,i,o){const n="session"===i?a(e):e,d=t(n,r({customerMacros:this.state.sessionAssetParameterMacros,element:this.deps.container,breakDurationSec:"session"===i?void 0:s}));for(const{key:e,macro:s}of d.unresolved)this.deps.diagnose("DA-ASSET-MACRO-UNKNOWN",`Asset parameter '${e}' sent with literal macro '${s}': the macro has no value.`,{...o,key:e,macro:s});return d.params}resolveSessionAdTagParameters(){const e=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,void 0);return this.resolveAssetParameterMacros(e,void 0,"session",{breakId:void 0,assetId:void 0})}buildVastTagUrl(e,t){const a=this.resolveUri(e.uri);if(!a)return null;const r=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,e.assetParameters),o=e.duration??this.deps.getEffectiveBreakDuration()??t.duration,n=this.resolveAssetParameterMacros(r,o,"break",{breakId:t.id,assetId:e.id});return s(a,n)}pushAssetParameters(e,s){const t=this.deps.getGamStreamManager();if(!t?.isInitialized())return;const a=this.deps.getCurrentBreak(),r=i(this.state.sessionAssetParameters,this.state.updatedAssetParameters,this.state.manifestAssetParameters,e),o=this.resolveAssetParameterMacros(r,this.deps.getEffectiveBreakDuration()??a?.duration,"break",{breakId:a?.id,assetId:s});null!==this.state.pushedAssetParameters&&d(this.state.pushedAssetParameters,o)||(this.state.pushedAssetParameters=o,t.replaceAdTagParameters(o??{}))}resolveUri(e){return void 0===this.state.detectedDeviceType&&(this.state.detectedDeviceType=o()),n(e,this.state.detectedDeviceType)}}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import { Asset, Break, BreakVariant, StaticAsset, VastAsset, VendorAsset } from '../../types/BreakManifest';
|
|
3
|
+
import type { AdBreakSequencer, AdBreakTransition } from '../AdBreakSequencer';
|
|
4
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
5
|
+
export interface AssetPlaybackDeps {
|
|
6
|
+
getAdPlayer: () => PlayerAdapter | null;
|
|
7
|
+
getAdContainer: () => HTMLDivElement | null;
|
|
8
|
+
getAdMediaContainer: () => HTMLElement | null;
|
|
9
|
+
clearStaticQuartiles: () => void;
|
|
10
|
+
getVariant: (breakInfo: Break) => BreakVariant | null;
|
|
11
|
+
getGamEventCallbacks: () => {
|
|
12
|
+
onAdFirstQuartile?: (breakInfo: Break, asset: Asset) => void;
|
|
13
|
+
onAdMidpoint?: (breakInfo: Break, asset: Asset) => void;
|
|
14
|
+
onAdThirdQuartile?: (breakInfo: Break, asset: Asset) => void;
|
|
15
|
+
};
|
|
16
|
+
getSequencer: () => AdBreakSequencer | null;
|
|
17
|
+
debug: boolean;
|
|
18
|
+
resolveUri: (uri: StaticAsset['uri']) => string | null;
|
|
19
|
+
buildGamUrl: (asset: VendorAsset, breakDuration: number) => Promise<string | null>;
|
|
20
|
+
buildVastTagUrl: (asset: VastAsset, breakInfo: Break) => string | null;
|
|
21
|
+
onAdError: (error: Error) => void;
|
|
22
|
+
onAdEnded: () => void;
|
|
23
|
+
drive: (transition: AdBreakTransition) => Promise<void>;
|
|
24
|
+
clearOverlayImage: () => void;
|
|
25
|
+
revealFirstFrame: () => void;
|
|
26
|
+
transferPictureInPictureToAd: () => Promise<void>;
|
|
27
|
+
isGamPodAsset: (asset: Asset) => boolean;
|
|
28
|
+
beginGamPodTracking: (asset: Asset) => void;
|
|
29
|
+
recordAdProgress: (currentTime: number) => void;
|
|
30
|
+
reportPhase: (name: string) => void;
|
|
31
|
+
armAdStartBudget: (asset: Asset) => void;
|
|
32
|
+
remainingAdStartBudgetMs: () => number;
|
|
33
|
+
loadWithinBudget: (load: Promise<void>) => Promise<boolean>;
|
|
34
|
+
waitForFirstFrame: () => Promise<void>;
|
|
35
|
+
getOverlayImage: () => HTMLImageElement | null;
|
|
36
|
+
setOverlayImage: (value: HTMLImageElement | null) => void;
|
|
37
|
+
getImageAssetTimer: () => ReturnType<typeof setTimeout> | null;
|
|
38
|
+
setImageAssetTimer: (value: ReturnType<typeof setTimeout> | null) => void;
|
|
39
|
+
getVastPodPosition: () => number | undefined;
|
|
40
|
+
setVastPodPosition: (value: number | undefined) => void;
|
|
41
|
+
diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
|
|
42
|
+
log: (...args: unknown[]) => void;
|
|
43
|
+
}
|
|
44
|
+
export declare class AssetPlayback {
|
|
45
|
+
private readonly state;
|
|
46
|
+
private readonly deps;
|
|
47
|
+
constructor(state: AdPlayerState, deps: AssetPlaybackDeps);
|
|
48
|
+
private get currentBreak();
|
|
49
|
+
playAsset(asset: Asset, index: number, total: number): Promise<void>;
|
|
50
|
+
/**
|
|
51
|
+
* `play()` with one retry when the browser itself pauses the element while the request is
|
|
52
|
+
* pending (Chromium: "The play() request was interrupted because the media paused", an
|
|
53
|
+
* AbortError; seen on Samsung Tizen while the content decoder is released). A pause the SDK
|
|
54
|
+
* issued (break ended or aborted) is not retried: the break is no longer current by then.
|
|
55
|
+
*/
|
|
56
|
+
private playRetryingInterruption;
|
|
57
|
+
private isStartingBreak;
|
|
58
|
+
playVastAsset(asset: VastAsset, index: number, total: number): void;
|
|
59
|
+
playImageAsset(asset: Asset, index: number, total: number): Promise<void>;
|
|
60
|
+
onImageAssetEnded(): void;
|
|
61
|
+
resolveAssetUri(asset: Asset, breakDuration: number): Promise<string | null>;
|
|
62
|
+
/**
|
|
63
|
+
* `duration` is Infinity for live and NaN before metadata; both read as zero so the caller
|
|
64
|
+
* does not hold a break open on a guess.
|
|
65
|
+
*/
|
|
66
|
+
adPlayerStreamRemainingSec(): number;
|
|
67
|
+
/** Only called once per first-frame-gated pre-roll. */
|
|
68
|
+
revealFirstFrame(): void;
|
|
69
|
+
/** Surfaces the MediaError's code/message so consumers can diagnose ad playback failures. */
|
|
70
|
+
toAdError(mediaError?: unknown): Error;
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as a}from"../../types/BreakManifest";import{resolveVastAdmission as i}from"../resolveVastAdmission";import{VastAdManager as d,isImaCsaiSdkAvailable as n}from"../VastAdManager";export class AssetPlayback{constructor(e,t){this.state=e,this.deps=t}get currentBreak(){return this.state.currentBreak}async playAsset(e,t,s){if(!this.currentBreak||!this.deps.getAdPlayer())return;if(this.state.currentAsset=e,this.state.currentAssetElapsedSec=0,this.deps.clearStaticQuartiles(),this.deps.clearOverlayImage(),this.deps.log("Playing asset:",e.id,"type:",e.type,"index:",t),a(e))return void this.playVastAsset(e,t,s);if(e.mediaType===r.IMAGE)return void await this.playImageAsset(e,t,s);const i=await this.resolveAssetUri(e,this.state.effectiveBreakDurationSec??this.currentBreak.duration);if(!i)return void this.deps.onAdError(new Error("No valid URI for asset"));this.deps.log("Resolved URI:",i),this.deps.reportPhase("uri-resolved");const d=this.deps.getAdPlayer();if(!d)return;const n=this.currentBreak.id;try{if(this.deps.armAdStartBudget(e),this.state.preloadedBreakId!==this.currentBreak.id){if(this.deps.reportPhase("load-start"),!await this.deps.loadWithinBudget(d.load(i)))return;this.deps.reportPhase("load-resolved")}else this.deps.reportPhase("load-skipped-preloaded");const r=d.videoElement;if(r&&(r.style.cssText="display:block;width:100%;height:100%;object-fit:contain;"),this.deps.reportPhase("play-called"),this.state.firstFrameGateActive&&!this.state.firstFrameGateShown){const e=this.deps.waitForFirstFrame();await this.playRetryingInterruption(d,n),await e}else{let e=null;const t=await Promise.race([this.playRetryingInterruption(d,n).then(()=>!0),new Promise(t=>{e=setTimeout(()=>t(!1),this.deps.remainingAdStartBudgetMs())})]);if(e&&clearTimeout(e),!t)return void this.deps.onAdError(new Error(`Ad did not start within ${this.state.adStartBudgetMs}ms ad-start budget`))}this.state.adStartDeadlineMs=null,this.state.adStartBudgetMs=0,this.deps.transferPictureInPictureToAd();const a=this.deps.getSequencer();a?(this.deps.isGamPodAsset(e)&&this.deps.beginGamPodTracking(e),await this.deps.drive(a.onOutcome({kind:"asset-started"}))):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s)}catch(e){if(this.state.firstFrameGateActive&&null===this.state.firstFrameResolver)return;this.deps.onAdError(e instanceof Error?e:new Error(String(e)))}}async playRetryingInterruption(e,t){try{await e.play()}catch(s){if(!function(e){return"AbortError"===e?.name}(s)||!this.isStartingBreak(t))throw s;this.deps.diagnose("DA-AD-START-INTERRUPTED","Ad play() was interrupted by a browser-initiated pause before the ad started; play() re-issued once.",{breakId:t,error:s instanceof Error?s.message:String(s)}),this.deps.reportPhase("play-retried"),await e.play()}}isStartingBreak(e){return this.state.isPlaying&&this.state.currentBreak?.id===e}playVastAsset(e,s,r){if(!this.currentBreak||!this.deps.getAdPlayer())return;const a=this.deps.getVariant(this.currentBreak)?.format??t.SINGLE,o=i({format:a,ssai:!1});if(!o.admitted)return this.deps.diagnose(o.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT",`VAST asset '${e.id}' is not supported in '${a}' breaks (single/double/lshape_ad only); skipping.`,{breakId:this.currentBreak.id,assetId:e.id,format:a}),void this.deps.onAdError(new Error(`VAST not supported in ${a} format`));if(!n())return this.deps.diagnose("DA-VAST-IMA-SDK-MISSING","Google IMA client-side SDK (ima3.js) is unavailable; cannot play VAST ad.",{breakId:this.currentBreak.id,assetId:e.id}),void this.deps.onAdError(new Error("IMA client-side SDK (ima3.js) not available"));const h=this.deps.buildVastTagUrl(e,this.currentBreak);if(!h)return void this.deps.onAdError(new Error("No valid VAST tag URL for asset"));const l=this.deps.getAdPlayer(),c=l?.videoElement,p=this.deps.getAdContainer();if(!p||!c||!l)return void this.deps.onAdError(new Error("VAST playout requires an ad container and ad video element"));this.deps.revealFirstFrame(),c.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.state.vastAdManager||(this.state.vastAdManager=new d(p,c,this.deps.debug)),this.state.vastAdManager.setCallbacks({onAdStarted:t=>{if(!this.currentBreak)return;this.deps.setVastPodPosition(t);const a=this.deps.getSequencer();a?this.deps.drive(a.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,s,r,{podPosition:t})},onAdProgress:(t,s)=>{this.currentBreak&&(this.deps.recordAdProgress(t),this.state.callbacks?.onAdTimeUpdate(this.currentBreak,e,t,s))},onAdFirstQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdFirstQuartile?.(this.currentBreak,e)},onAdMidpoint:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdMidpoint?.(this.currentBreak,e)},onAdThirdQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdThirdQuartile?.(this.currentBreak,e)}}),this.deps.log("Playing VAST asset via IMA CSAI:",h);(this.state.vastAdManager.hasPreloadedManager()?(this.state.vastAdManager.setMuted(l.muted),this.state.vastAdManager.startPreloaded()):this.state.vastAdManager.playVast(h,{muted:l.muted})).then(()=>{this.deps.onAdEnded()},t=>{const s=t instanceof Error?t:new Error(String(t)),r=/ima3\.js|client-side SDK/i.test(s.message)?"DA-VAST-IMA-SDK-MISSING":"DA-VAST-IMA-ERROR";this.deps.diagnose(r,s.message,{...this.currentBreak?{breakId:this.currentBreak.id}:{},assetId:e.id}),this.deps.onAdError(s)})}async playImageAsset(e,t,s){const r=this.currentBreak,a=this.deps.getAdMediaContainer();if(!r||!a)return;const i=this.state.effectiveBreakDurationSec??r.duration,d=await this.resolveAssetUri(e,i);if(!d)return void this.deps.onAdError(new Error("No valid URI for asset"));const n=document.createElement("img");n.className="dolby-overlay-image",n.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.deps.setOverlayImage(n);const o=e.duration??i;n.onload=()=>{if(this.deps.getOverlayImage()!==n||!this.currentBreak)return;const r=this.deps.getSequencer();r?this.deps.drive(r.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s),o>0?this.deps.setImageAssetTimer(setTimeout(()=>this.onImageAssetEnded(),1e3*o)):this.onImageAssetEnded()},n.onerror=()=>{this.deps.getOverlayImage()===n&&this.deps.onAdError(new Error(`Overlay image failed to load: ${d}`))},n.src=d,a.appendChild(n)}onImageAssetEnded(){const e=this.deps.getSequencer();if(!this.currentBreak||!this.state.isPlaying||!e)return;const t=this.deps.getImageAssetTimer();t&&(clearTimeout(t),this.deps.setImageAssetTimer(null)),this.deps.drive(e.onOutcome({kind:"asset-ended"}))}async resolveAssetUri(t,r){switch(t.type){case e.STATIC:return this.deps.resolveUri(t.uri);case e.VENDOR:{const e=t;return e.vendor===s.GAM?await this.deps.buildGamUrl(e,r):(this.deps.log("Unsupported vendor:",e.vendor),null)}default:return this.deps.log("Unsupported asset type:",t.type),null}}adPlayerStreamRemainingSec(){const e=this.deps.getAdPlayer();if(!e)return 0;const{duration:t,currentTime:s}=e;if(!Number.isFinite(t)||t<=0)return 0;if(!Number.isFinite(s)||s<0)return 0;const r=t-s;return r>0?r:0}revealFirstFrame(){this.deps.revealFirstFrame()}toAdError(e){const t=e;if(t&&("number"==typeof t.code||t.message)){const e="number"==typeof t.code?` (code ${t.code})`:"",s=t.message?`: ${t.message}`:"";return new Error(`Ad playback error${e}${s}`)}return new Error("Ad playback error")}}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Asset, Break } from '../../types/BreakManifest';
|
|
2
|
+
import type { AdPlayerCallbacks, PlayBreakOptions } from '../AdPlayerController';
|
|
3
|
+
import type { AdBreakAction, AdBreakStep, AdBreakTransition, EffectiveInsertionMode } from '../AdBreakSequencer';
|
|
4
|
+
import type { GamPodAdStep } from '../GamPodAdTracker';
|
|
5
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
6
|
+
export interface BreakSequenceDriverDeps {
|
|
7
|
+
getChainResolver: () => ((currentBreakId: string) => Break | null) | null;
|
|
8
|
+
getEffectiveInsertionMode: () => EffectiveInsertionMode;
|
|
9
|
+
getVastPodPosition: () => number | undefined;
|
|
10
|
+
setVastPodPosition: (value: number | undefined) => void;
|
|
11
|
+
getPendingAdError: () => Error | null;
|
|
12
|
+
setPendingAdError: (value: Error | null) => void;
|
|
13
|
+
getChaining: () => boolean;
|
|
14
|
+
setChaining: (value: boolean) => void;
|
|
15
|
+
isExpandingPodAsset: (asset: Asset | undefined) => boolean;
|
|
16
|
+
fallBackToPodLevelReporting: () => void;
|
|
17
|
+
resetGamPodTracking: () => void;
|
|
18
|
+
emitGamPodSteps: (steps: GamPodAdStep[]) => void;
|
|
19
|
+
playAsset: (asset: Asset, index: number, total: number) => Promise<void>;
|
|
20
|
+
endBreak: () => void;
|
|
21
|
+
playBreak: (breakInfo: Break, callbacks: AdPlayerCallbacks, options?: PlayBreakOptions) => Promise<void>;
|
|
22
|
+
diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
|
|
23
|
+
log: (...args: unknown[]) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare class BreakSequenceDriver {
|
|
26
|
+
private readonly state;
|
|
27
|
+
private readonly deps;
|
|
28
|
+
constructor(state: AdPlayerState, deps: BreakSequenceDriverDeps);
|
|
29
|
+
/** Emit the transition's ad events (break-level steps are owned elsewhere), then perform its side effect. */
|
|
30
|
+
drive(transition: AdBreakTransition): Promise<void>;
|
|
31
|
+
/** Map a sequencer step to the matching SDK callback. */
|
|
32
|
+
emitStep(step: AdBreakStep): void;
|
|
33
|
+
/**
|
|
34
|
+
* Returns true when the step was fully handled here and must not also be emitted as the
|
|
35
|
+
* pod-level `adend`. Reached from every path that ends an ad, so the pairs balance however
|
|
36
|
+
* the break finishes.
|
|
37
|
+
*/
|
|
38
|
+
settlePodAdEnd(step: AdBreakStep): boolean;
|
|
39
|
+
/** Perform the side effect a transition asks for (overlay intra-break only). */
|
|
40
|
+
performAction(action: AdBreakAction): Promise<void>;
|
|
41
|
+
emitGamPodSteps(steps: GamPodAdStep[]): void;
|
|
42
|
+
/** Null when no resolver is injected or in shared-element mode (those breaks always hard-end). */
|
|
43
|
+
resolveChainedSuccessor(currentBreakId: string): Break | null;
|
|
44
|
+
/** Overlay held, content never resumed, layout kept; the successor plays through the same overlay ad player. */
|
|
45
|
+
advanceToChainedBreak(finished: Break, next: Break): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export class BreakSequenceDriver{constructor(e,t){this.state=e,this.deps=t}async drive(e){for(const t of e.noPlayableAsset??[])this.deps.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`break ${t.breakId} had no playable ad (${t.reason}); it began and ended without an ad.`,{breakId:t.breakId,reason:t.reason,declaredAssetCount:t.declaredAssetCount,...void 0!==t.format?{format:t.format}:{}});for(const t of e.steps)this.emitStep(t);await this.performAction(e.action)}emitStep(e){if(this.state.currentBreak)switch(e.type){case"adbreakbegin":case"adbreakend":return;case"adbegin":return this.deps.isExpandingPodAsset(e.asset)?void("pending"===this.state.gamPodMode&&(this.state.withheldPodAdBegin=e)):void(void 0!==this.deps.getVastPodPosition()?(this.state.callbacks?.onAdStart(this.state.currentBreak,e.asset,e.adIndex,e.totalAds,{podPosition:this.deps.getVastPodPosition()}),this.deps.setVastPodPosition(void 0)):this.state.callbacks?.onAdStart(this.state.currentBreak,e.asset,e.adIndex,e.totalAds));case"adend":if(this.settlePodAdEnd(e))return;return this.state.callbacks?.onAdEnd(this.state.currentBreak,e.asset,e.adIndex,e.totalAds),void this.deps.resetGamPodTracking();case"aderror":{this.deps.isExpandingPodAsset(e.asset)&&(this.state.withheldPodAdBegin=null,this.state.gamPodTracker?.sawAnyAd&&this.emitGamPodSteps(this.state.gamPodTracker.finish()));const t=this.deps.getPendingAdError()??new Error("Ad playback error");return this.deps.setPendingAdError(null),void this.state.callbacks?.onAdError(this.state.currentBreak,e.asset,t)}}}settlePodAdEnd(e){return!!this.deps.isExpandingPodAsset(e.asset)&&(this.state.gamPodTracker?.sawAnyAd?(this.emitGamPodSteps(this.state.gamPodTracker.finish()),this.deps.resetGamPodTracking(),!0):(this.deps.fallBackToPodLevelReporting(),!1))}async performAction(e){switch(e.kind){case"play-asset":return void await this.deps.playAsset(e.asset,e.index,e.total);case"end-break":return void this.deps.endBreak();default:return}}emitGamPodSteps(e){this.deps.emitGamPodSteps(e)}resolveChainedSuccessor(e){const t=this.deps.getChainResolver();if(!t)return null;if("shared-element"===this.deps.getEffectiveInsertionMode())return null;try{return t(e)}catch(e){return this.deps.log("Chain resolver error:",e),null}}advanceToChainedBreak(e,t){this.deps.log(`Chaining: "${e.id}" -> "${t.id}" (overlay held, content stays paused)`),this.state.currentBreak=null,this.state.effectiveBreakDurationSec=null,this.state.preloadedBreakId=null;const s=this.state.callbacks;s?.onBreakEnd(e),s?.onChainedBreakBegin?.(t),s&&(this.deps.setChaining(!0),this.deps.playBreak(t,s).finally(()=>{this.deps.setChaining(!1)}))}}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import type { Break } from '../../types/BreakManifest';
|
|
3
|
+
import type { Clock } from '../BreakScheduler';
|
|
4
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
5
|
+
export interface ContentResumeCoordinatorDeps {
|
|
6
|
+
contentPlayer: PlayerAdapter;
|
|
7
|
+
clock: Clock;
|
|
8
|
+
getInsertionMode: () => string;
|
|
9
|
+
gateContentForBreak: () => void;
|
|
10
|
+
clearContentGate: (keepPlaying?: boolean) => void;
|
|
11
|
+
diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
|
|
12
|
+
log: (...args: unknown[]) => void;
|
|
13
|
+
}
|
|
14
|
+
/** Owns live resume alignment and continue-content playout during ad breaks. */
|
|
15
|
+
export declare class ContentResumeCoordinator {
|
|
16
|
+
private readonly state;
|
|
17
|
+
private readonly deps;
|
|
18
|
+
private readonly continuePlayout;
|
|
19
|
+
constructor(state: AdPlayerState, deps: ContentResumeCoordinatorDeps);
|
|
20
|
+
liveEdgeDistanceSec(): number | null;
|
|
21
|
+
liveSafeSeekEnd(): number | null;
|
|
22
|
+
contentSeekableEnd(): number | null;
|
|
23
|
+
isPlausibleSeekEnd(end: number): boolean;
|
|
24
|
+
plannedResumeTarget(breakInfo: Break): number | null;
|
|
25
|
+
maybePrepositionContentForResume(): void;
|
|
26
|
+
maybeCatchUpContentForResume(breakInfo: Break): void;
|
|
27
|
+
endResumeCatchup(keepPlaying?: boolean): void;
|
|
28
|
+
get resumeHandoverInMotion(): boolean;
|
|
29
|
+
resumeAlignSummary(): Record<string, unknown>;
|
|
30
|
+
cancelResumeLandedProbe(): void;
|
|
31
|
+
probeResumeLanding(breakId: string, target: number): void;
|
|
32
|
+
waitForLiveEdge(): Promise<number | null>;
|
|
33
|
+
resetBreakResumeState(): void;
|
|
34
|
+
beginContinueContentPlayout(): void;
|
|
35
|
+
distanceToLiveEdgeSec(): number | null;
|
|
36
|
+
secondsUntilCut(elapsedWallSec: number): number;
|
|
37
|
+
restoreContinueQuality(stage: 'staged' | 'break-end'): void;
|
|
38
|
+
currentContinueDriftSec(): number;
|
|
39
|
+
abandonContinueContentPlayout(reason: string): void;
|
|
40
|
+
endContinueContentPlayout(): void;
|
|
41
|
+
setContinueRate(rate: number): void;
|
|
42
|
+
tickContinueContentPlayout(): void;
|
|
43
|
+
applyResumeSeek(breakInfo: Break, earlyReturnOffsetSec?: number | null): void;
|
|
44
|
+
}
|
|
@@ -0,0 +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{contentSeekableEnd as i,isPlausibleSeekEnd as a,liveEdgeDistanceSec as r,liveSafeSeekEnd as o}from"./liveEdge";const u=.04;export class ContentResumeCoordinator{constructor(e,t){this.state=e,this.deps=t,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)})}liveEdgeDistanceSec(){return r(this.deps.contentPlayer)}liveSafeSeekEnd(){return o(this.deps.contentPlayer)}contentSeekableEnd(){return i(this.deps.contentPlayer)}isPlausibleSeekEnd(e){return a(this.deps.contentPlayer,e)}plannedResumeTarget(e){if(t(e))return null;const n=s({anchor:this.state.contentPositionAtBreakStart,duration:e.duration,adDuration:e.duration,...this.state.adInsertionType?{insertionType:this.state.adInsertionType}:{},..."number"==typeof e.resumeOffset?{resumeOffset:e.resumeOffset}:{}});return n.seek?n.target: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 a;if(t<=s)a=t;else{if(!(i<=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 ${i}s — moving content as close to ${t}s as exists`),this.deps.contentPlayer.seek(a);const r=setTimeout(()=>{this.state.resumePrepositionSettle=null;const e=this.deps.contentPlayer.currentTime;Math.abs(e-a)<=u?(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=r}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)>u){const a=s-i,r=this.liveSafeSeekEnd();if(null!==r){const e=r>this.state.lastResumeSafeEnd+.25;e&&Number.isFinite(this.state.lastResumeSafeEnd)&&this.state.resumeSawSteps.push(Math.round(100*(r-this.state.lastResumeSafeEnd))/100),r>this.state.lastResumeSafeEnd&&(this.state.lastResumeSafeEnd=r);const s=r-a;if(s>this.state.resumeBestSawMarginSec&&(this.state.resumeBestSawMarginSec=s),a>r)return void(this.state.resumeAlignBlockedDiagnosed||(this.state.resumeAlignBlockedDiagnosed=!0,this.deps.log(`Resume align waiting: ${a}s is past the published edge ${r}s (${Math.round(1e3*(a-r))}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 ${r}s`,{target:t,requested:a,safeEnd:r,shortfallSec:Math.round(1e3*(a-r))/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 ${a}s, ${n}s of ad left`),this.state.resumeAlignCorrectionsMs.push(Math.round(1e3*i)),this.deps.contentPlayer.seek(a)}}return}if(t-s<=u)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;if(i<=0)return;if(i>30)return;const a=this.contentSeekableEnd();if(null===a)return;const r=t-i+.3,o=Math.min(r,a);this.state.resumeCatchupActive=!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:a,clampedStart:o<r}),Math.abs(s-o)>u&&this.deps.contentPlayer.seek(o),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.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){this.cancelResumeLandedProbe();let s=!1;const n=(n,i)=>{if(s)return;s=!0,this.cancelResumeLandedProbe();const a=null===n?null:Math.round(1e3*(n-t))/1e3;this.deps.log(null===n?`Resume landing unknown for break ${e} (${i})`:`Resume landed at ${n}s, ${a}s from the ${t}s asked for (break ${e})`),this.deps.diagnose?.("DA-RESUME-LANDED",null===n?`Resume landing for break ${e} could not be measured (${i})`:`Resume for break ${e} landed at ${n}s, ${a}s from the ${t}s requested`,{breakId:e,target:t,landed:n,errorSec:a,...i?{reason:i}:{}})},i=()=>n(this.deps.contentPlayer.currentTime),a=setTimeout(()=>n(null,"no seeked event"),2e3);this.state.resumeLandedProbe=()=>{clearTimeout(a),this.deps.contentPlayer.off("seeked",i)},this.deps.contentPlayer.on("seeked",i)}async waitForLiveEdge(){for(let e=0;e<20;e++){const e=this.deps.contentPlayer.videoElement,t=e?.seekable;if(t&&t.length>0){const e=t.end(t.length-1);if(Number.isFinite(e)&&e>0)return e}await new Promise(e=>setTimeout(e,100))}return this.deps.log("shared-element: no seekable range after restore; staying at the live edge"),null}resetBreakResumeState(){this.state.resumePrepositionedTo=null,this.state.resumeCatchupActive=!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:(t=e-this.deps.contentPlayer.currentTime,Math.round(1e3*t)/1e3);var t}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()}applyResumeSeek(t,n=null){const i=null!==n,a=i?n:this.state.adSecondsPlayedThisBreak,r=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(r.clamped&&this.deps.diagnose?.("DA-RESUME-OFFSET-CLAMPED",`Resume offset shortened to the ${r.offset}s of ad actually played for break ${t.id}`,{breakId:t.id,offset:r.offset,source:r.source}),!r.seek)return void this.deps.log(`Resume: no seek for break ${t.id} (offset 0, ${r.source})`);this.deps.log(`Resume-seek to ${r.target}s (offset ${r.offset}s, ${r.source}) for break ${t.id}`),this.deps.diagnose?.("DA-RESUME-SEEK",`Resume-seek to ${r.target}s (offset ${r.offset}s, ${r.source}) for break ${t.id}`,{breakId:t.id,target:r.target,offset:r.offset,source:r.source});(this.state.breakTimebase===e.PTS||this.state.breakTimebase===e.MEDIATIME)&&"number"==typeof t.start&&this.state.callbacks?.onContentResumeSeek?.(r.target,t.start+t.duration);const o=this.liveSafeSeekEnd();let d=r.target,l=0;if(null!==o&&d>o&&(l=Math.round(1e3*(d-o))/1e3,d=o),null!==this.state.resumePrepositionedTo&&Math.abs(this.deps.contentPlayer.currentTime-d)<=u){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)`+(l>0?` — ${l}s short of policy, those frames are not published yet`:"")+" — no seek"),void this.deps.diagnose?.("DA-RESUME-PREPOSITIONED",`Break ${t.id} resumed without a seek: content was moved to ${e}s during the break`+(l>0?` (${l}s short of the ${r.target}s policy target)`:""),{breakId:t.id,target:r.target,prepositionedTo:this.state.resumePrepositionedTo,landed:e,shortfallSec:l,exact:0===l,...this.resumeAlignSummary()})}l>0&&(this.deps.log(`Resume-seek clamped: ${r.target}s is beyond what the stream has published; seeking ${d}s instead (${l}s short)`),this.deps.diagnose?.("DA-RESUME-TARGET-CLAMPED",`Resume target ${r.target}s for break ${t.id} is beyond the published window; clamped to ${d}s`,{breakId:t.id,requested:r.target,clamped:d,shortfallSec:l,...this.resumeAlignSummary()})),this.probeResumeLanding(t.id,d),this.deps.contentPlayer.seek(d)}}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import type { Break } from '../../types/BreakManifest';
|
|
3
|
+
import type { Clock } from '../BreakScheduler';
|
|
4
|
+
import type { AdPlayerState } from './AdPlayerState';
|
|
5
|
+
export interface ContinueContentPlayoutDeps {
|
|
6
|
+
contentPlayer: PlayerAdapter;
|
|
7
|
+
clock: Clock;
|
|
8
|
+
gateContentForBreak: () => void;
|
|
9
|
+
distanceToLiveEdgeSec: () => number | null;
|
|
10
|
+
plannedResumeTarget: (breakInfo: Break) => number | null;
|
|
11
|
+
diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
|
|
12
|
+
log: (...args: unknown[]) => void;
|
|
13
|
+
}
|
|
14
|
+
export declare class ContinueContentPlayout {
|
|
15
|
+
private readonly state;
|
|
16
|
+
private readonly deps;
|
|
17
|
+
constructor(state: AdPlayerState, deps: ContinueContentPlayoutDeps);
|
|
18
|
+
beginContinueContentPlayout(): void;
|
|
19
|
+
secondsUntilCut(elapsedWallSec: number): number;
|
|
20
|
+
restoreContinueQuality(stage: 'staged' | 'break-end'): void;
|
|
21
|
+
currentContinueDriftSec(): number;
|
|
22
|
+
abandonContinueContentPlayout(reason: string): void;
|
|
23
|
+
endContinueContentPlayout(): void;
|
|
24
|
+
setContinueRate(rate: number): void;
|
|
25
|
+
tickContinueContentPlayout(): void;
|
|
26
|
+
}
|