@dolby-optiview/ads-sdk-core 1.2.1 → 1.4.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 +5 -4
- package/dist/OptiViewAds.js +1 -1
- package/dist/OptiViewAdsExternalVendors.d.ts +32 -0
- package/dist/external-playback/index.d.ts +4 -0
- package/dist/external-playback/index.js +1 -0
- package/dist/external-playback/observeExternalPlayback.d.ts +19 -0
- package/dist/external-playback/observeExternalPlayback.js +1 -0
- package/dist/external-playback/reconnectHold.d.ts +19 -0
- package/dist/external-playback/reconnectHold.js +1 -0
- package/dist/generated/diagnosticCodes.d.ts +46 -6
- package/dist/generated/diagnosticCodes.js +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1 -1
- package/dist/interfaces/AdSourceQueue.d.ts +19 -0
- package/dist/interfaces/AdSourceQueue.js +1 -0
- package/dist/interfaces/PlayerAdapter.d.ts +31 -1
- package/dist/internal/index.d.ts +2 -0
- package/dist/internal/index.js +1 -1
- package/dist/services/AdBreakSequencer.d.ts +33 -4
- package/dist/services/AdBreakSequencer.js +1 -1
- package/dist/services/AdPlayerController.d.ts +12 -73
- package/dist/services/AdPlayerController.js +1 -1
- package/dist/services/BreakChain.d.ts +43 -0
- package/dist/services/BreakChain.js +1 -0
- package/dist/services/BreakChainRecorder.d.ts +63 -0
- package/dist/services/BreakChainRecorder.js +1 -0
- package/dist/services/BreakPreloadLedger.d.ts +13 -0
- package/dist/services/BreakPreloadLedger.js +1 -0
- package/dist/services/BreakScheduler.d.ts +20 -6
- package/dist/services/BreakScheduler.js +1 -1
- package/dist/services/BreakTransitionTimer.d.ts +27 -2
- package/dist/services/BreakTransitionTimer.js +1 -1
- package/dist/services/CmcdAdReporter.d.ts +1 -0
- package/dist/services/CmcdAdReporter.js +1 -1
- package/dist/services/ExternalPlaybackGate.d.ts +40 -0
- package/dist/services/ExternalPlaybackGate.js +1 -0
- package/dist/services/LiveStartPushbackController.d.ts +5 -10
- package/dist/services/LiveStartPushbackController.js +1 -1
- package/dist/services/ManifestService.d.ts +2 -1
- package/dist/services/ManifestService.js +1 -1
- package/dist/services/ReloadGuard.d.ts +11 -0
- package/dist/services/ReloadGuard.js +1 -0
- package/dist/services/RewindRearm.d.ts +3 -1
- package/dist/services/RewindRearm.js +1 -1
- package/dist/services/SsaiController.js +1 -1
- package/dist/services/adPlayer/AdPlayerCallbacks.d.ts +63 -0
- package/dist/services/adPlayer/AdPlayerCallbacks.js +1 -0
- package/dist/services/adPlayer/AdPlayerEvents.d.ts +3 -7
- package/dist/services/adPlayer/AdPlayerEvents.js +1 -1
- package/dist/services/adPlayer/AdPlayerState.d.ts +0 -1
- package/dist/services/adPlayer/AdPlayerState.js +1 -1
- package/dist/services/adPlayer/AdPreloader.d.ts +2 -2
- package/dist/services/adPlayer/AdPreloader.js +1 -1
- package/dist/services/adPlayer/AdStage.d.ts +7 -4
- package/dist/services/adPlayer/AdStage.js +1 -1
- package/dist/services/adPlayer/AssetParameterResolver.d.ts +10 -2
- package/dist/services/adPlayer/AssetParameterResolver.js +1 -1
- package/dist/services/adPlayer/AssetPlayback.d.ts +9 -2
- package/dist/services/adPlayer/AssetPlayback.js +1 -1
- package/dist/services/adPlayer/AudioController.d.ts +58 -0
- package/dist/services/adPlayer/AudioController.js +1 -0
- package/dist/services/adPlayer/BreakSequenceDriver.d.ts +9 -3
- package/dist/services/adPlayer/BreakSequenceDriver.js +1 -1
- package/dist/services/adPlayer/ContentResumeCoordinator.d.ts +2 -0
- package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -1
- package/dist/services/adPlayer/GamPodBridge.d.ts +11 -1
- package/dist/services/adPlayer/GamPodBridge.js +1 -1
- package/dist/services/adPlayer/PauseAdPresenter.d.ts +3 -1
- package/dist/services/adPlayer/PauseAdPresenter.js +1 -1
- package/dist/services/adPlayer/PreparedPresentation.js +1 -1
- package/dist/services/adPlayer/PresentationModeTracker.d.ts +1 -1
- package/dist/services/adPlayer/PresentationModeTracker.js +1 -1
- package/dist/services/adPlayer/SharedElementBreakPlayer.d.ts +3 -0
- package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
- package/dist/services/adPlayer/SuccessorQueue.d.ts +36 -0
- package/dist/services/adPlayer/SuccessorQueue.js +1 -0
- package/dist/services/adPlayer/SurfaceRevealReporter.d.ts +28 -0
- package/dist/services/adPlayer/SurfaceRevealReporter.js +1 -0
- package/dist/services/adPlayer/presentation/DomPresentationHost.d.ts +22 -0
- package/dist/services/adPlayer/presentation/DomPresentationHost.js +1 -0
- package/dist/services/adPlayer/presentation/LayoutTransform.d.ts +28 -0
- package/dist/services/adPlayer/presentation/LayoutTransform.js +1 -0
- package/dist/services/adPlayer/presentation/PresentationLayout.d.ts +19 -0
- package/dist/services/adPlayer/presentation/PresentationLayout.js +1 -0
- package/dist/services/assetParameterMacroTable.js +1 -1
- package/dist/services/assetParameterMacros.d.ts +9 -9
- package/dist/services/assetParameterMacros.js +1 -1
- package/dist/services/channelSwitch.d.ts +2 -2
- package/dist/services/externalPlaybackDiagnostic.d.ts +3 -0
- package/dist/services/externalPlaybackDiagnostic.js +1 -0
- package/dist/services/externalPlaybackGuard.d.ts +24 -0
- package/dist/services/externalPlaybackGuard.js +1 -0
- package/dist/services/parseBreakManifest.d.ts +2 -1
- package/dist/services/parseBreakManifest.js +1 -1
- package/dist/services/resolveChannelEvents.d.ts +17 -0
- package/dist/services/resolveChannelEvents.js +1 -0
- package/dist/services/resolveExternalVendorConfigurations.js +1 -1
- package/dist/services/resolveGamVendorConfiguration.d.ts +6 -1
- package/dist/services/resolveGamVendorConfiguration.js +1 -1
- package/dist/services/ssbStream.d.ts +39 -0
- package/dist/services/ssbStream.js +1 -0
- package/dist/services/ssbSwitchSignals.d.ts +15 -0
- package/dist/services/ssbSwitchSignals.js +1 -0
- package/dist/services/staticAssetUrl.d.ts +28 -0
- package/dist/services/staticAssetUrl.js +1 -0
- package/dist/session/AdBreakStatusTracker.d.ts +18 -0
- package/dist/session/AdBreakStatusTracker.js +1 -1
- package/dist/session/BreakLifecycle.d.ts +6 -0
- package/dist/session/BreakLifecycle.js +1 -1
- package/dist/session/SessionStarter.d.ts +6 -43
- package/dist/session/SessionStarter.js +1 -1
- package/dist/session/SessionStarterDeps.d.ts +39 -0
- package/dist/session/SessionStarterDeps.js +1 -0
- package/dist/session/SessionStarterHelpers.d.ts +36 -0
- package/dist/session/SessionStarterHelpers.js +1 -0
- package/dist/session/SessionState.d.ts +22 -2
- package/dist/session/SessionState.js +1 -1
- package/dist/session/SsbSwitch.d.ts +55 -0
- package/dist/session/SsbSwitch.js +1 -0
- package/dist/session/breakMeasurement.d.ts +16 -0
- package/dist/session/breakMeasurement.js +1 -0
- package/dist/session/contentSnapback.d.ts +13 -0
- package/dist/session/contentSnapback.js +1 -0
- package/dist/session/createExternalPlaybackGate.d.ts +19 -0
- package/dist/session/createExternalPlaybackGate.js +1 -0
- package/dist/session/sessionTick.d.ts +7 -0
- package/dist/session/sessionTick.js +1 -0
- package/dist/types/BreakManifest.d.ts +58 -5
- package/dist/types/Events.d.ts +21 -10
- package/dist/types/ExternalVendorConfiguration.d.ts +3 -1
- package/dist/types/OptiViewAdsConfig.d.ts +24 -8
- package/package.json +5 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{LAYOUT_TRANSITION as e}from"./presentation/LayoutTransform";const t=`position:absolute;top:0;left:0;right:0;bottom:0;transition:${e};`,s=new WeakMap,i=new WeakMap,r=new WeakSet;class PreparedPresentation{constructor(e,s,i){this.owner=e,this.container=s,this.getVideo=i,this.subscribers=new Set,this.token=null,this.disposed=!1,this.geometry="",this.video=null,this.observer=null,this.resizeObserver=null,this.poll=null,this.refresh=()=>{if(this.disposed)return;const e=this.getVideo();e!==this.video&&(this.unbindVideo(),this.video=e,e?.addEventListener("loadedmetadata",this.refresh),e?.addEventListener("resize",this.refresh));const{width:t,height:s}=this.container.getBoundingClientRect();if(!(t>0&&s>0))return;const i=e&&e.videoWidth>0&&e.videoHeight>0?e.videoWidth/e.videoHeight:16/9,r=Math.min(t,s*i),o=r/i,n=`${t}:${s}:${r}:${o}`;if(n!==this.geometry){this.geometry=n,this.stage.style.width=`${r}px`,this.stage.style.height=`${o}px`,this.stage.style.left=(t-r)/2+"px",this.stage.style.top=(s-o)/2+"px";for(const e of this.subscribers)e()}};for(const e of Array.from(s.querySelectorAll("video,audio")))if(e.currentSrc||e.getAttribute("src")||e.srcObject||e.readyState>0||!e.paused)throw new Error("Prepare the ads presentation before loading the content source");this.originalPosition=s.style.position;const r=s.ownerDocument.defaultView?.getComputedStyle(s).position;r&&"static"!==r||(s.style.position="relative"),this.stage=s.ownerDocument.createElement("div"),this.stage.className="dolby-stage",this.stage.style.cssText="position:absolute;overflow:hidden;",this.playerContainer=s.ownerDocument.createElement("div"),this.playerContainer.style.cssText=t;const o=Array.from(s.childNodes);s.appendChild(this.stage),this.stage.appendChild(this.playerContainer);for(const e of o)this.playerContainer.appendChild(e);this.refresh(),"undefined"!=typeof MutationObserver&&(this.observer=new MutationObserver(()=>{this.getVideo()!==this.video&&this.refresh()}),this.observer.observe(s,{childList:!0,subtree:!0})),"undefined"!=typeof ResizeObserver?(this.resizeObserver=new ResizeObserver(this.refresh),this.resizeObserver.observe(s)):this.poll=setInterval(this.refresh,250)}assertAvailable(){if(this.disposed)throw new Error("The content presentation has been disposed");if(this.token)throw new Error("The content player already has an ads presentation owner");if(this.stage.parentNode!==this.container||this.playerContainer.parentNode!==this.stage)throw new Error("The prepared content presentation has been moved")}acquire(){this.assertAvailable();const e={};this.token=e;const s=new Set;return{stage:this.stage,playerContainer:this.playerContainer,subscribe:e=>(this.subscribers.add(e),s.add(e),()=>{this.subscribers.delete(e),s.delete(e)}),isActive:()=>!this.disposed&&this.token===e,release:()=>{for(const e of s)this.subscribers.delete(e);s.clear(),this.disposed||this.token!==e||(this.token=null,this.playerContainer.style.cssText=t)}}}dispose(){this.disposed||(this.disposed=!0,this.observer?.disconnect(),this.resizeObserver?.disconnect(),null!==this.poll&&clearInterval(this.poll),this.poll=null,this.unbindVideo(),this.subscribers.clear(),this.stage.parentNode&&this.stage.parentNode.removeChild(this.stage),i.get(this.container)===this&&(i.delete(this.container),this.container.style.position=this.originalPosition),r.add(this.owner))}unbindVideo(){this.video?.removeEventListener("loadedmetadata",this.refresh),this.video?.removeEventListener("resize",this.refresh),this.video=null}}export function preparePresentation(e,t,o){if(r.has(e))throw new Error("Cannot prepare a disposed content player");const n=s.get(e);if(n){if(n.container!==t)throw new Error("The content player presentation container changed");return n}if(i.has(t))throw new Error("The container belongs to another content player");const a=new PreparedPresentation(e,t,o);return s.set(e,a),i.set(t,a),a}export function getPreparedPresentation(e){return i.get(e)}export function disposePreparedPresentation(e){s.get(e)?.dispose()}
|
|
@@ -13,7 +13,7 @@ export interface PresentationModeTrackerDeps {
|
|
|
13
13
|
hasCurrentAsset: () => boolean;
|
|
14
14
|
contentSourceIsReloadable: () => boolean;
|
|
15
15
|
adoptPictureInPictureMidBreak: () => void;
|
|
16
|
-
restoreLayout: (
|
|
16
|
+
restoreLayout: () => void;
|
|
17
17
|
applyLayout: (variant: BreakVariant) => void;
|
|
18
18
|
show: () => void;
|
|
19
19
|
playAdThroughContentElement: () => Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BreakFormat as e}from"../../types/BreakManifest";import{pipOverridesFormat as t,pipSuppressesBackdrop as n}from"../pictureInPicture";export class PresentationModeTracker{constructor(e,t){this.state=e,this.deps=t,this.pipEnterHandler=null,this.pipExitHandler=null,this.pipPresentationModeHandler=null}currentInsertionMode(){return this.deps.isSharedElementActive()?"shared-element":this.effectiveInsertionMode()}effectiveInsertionMode(){return"shared-element"===this.deps.insertionMode||this.isContentPictureInPicture()&&this.deps.contentSourceIsReloadable()&&!this.canReceivePictureInPictureWindow()||"adaptive"===this.deps.insertionMode&&this.isContentFullscreen()?"shared-element":"overlay"}isContentPictureInPicture(){const e=this.deps.getAdPlayer()?.videoElement,t=this.deps.contentPlayer.videoElement;return this.elementHasPictureInPicture(t)||this.elementHasPictureInPicture(e)}elementHasPictureInPicture(e){return!!e&&("picture-in-picture"===e.webkitPresentationMode||"undefined"!=typeof document&&document.pictureInPictureElement===e)}isContentFullscreen(){const e=this.deps.contentPlayer.videoElement;return e&&"boolean"==typeof e.webkitDisplayingFullscreen?e.webkitDisplayingFullscreen:this.state.isContentFullscreen}setupFullscreenTracking(){const e=this.deps.contentPlayer.videoElement;e&&(this.state.trackedContentVideo=e,this.state.fullscreenEnterHandler=()=>{this.state.isContentFullscreen=!0},this.state.fullscreenExitHandler=()=>{this.state.isContentFullscreen=!1},e.addEventListener("webkitbeginfullscreen",this.state.fullscreenEnterHandler),e.addEventListener("webkitendfullscreen",this.state.fullscreenExitHandler))}setupPictureInPictureTracking(){const e=this.deps.contentPlayer.videoElement;e&&(this.state.trackedContentVideo=e,this.pipEnterHandler=()=>{this.state.contentPictureInPicture=!0,this.deps.adoptPictureInPictureMidBreak()},this.pipExitHandler=()=>{this.state.pipTransferredToAd||(this.state.contentPictureInPicture=!1)},this.pipPresentationModeHandler=()=>{const t=e.webkitPresentationMode;this.state.contentPictureInPicture="picture-in-picture"===t,this.state.contentPictureInPicture&&this.deps.adoptPictureInPictureMidBreak()},e.addEventListener("enterpictureinpicture",this.pipEnterHandler),e.addEventListener("leavepictureinpicture",this.pipExitHandler),e.addEventListener("webkitpresentationmodechanged",this.pipPresentationModeHandler))}rebindContentElementIfSwapped(){const e=this.deps.contentPlayer.videoElement??null;if(e===this.state.trackedContentVideo)return;if(!this.state.trackedContentVideo||!e)return;const t=this.state.trackedContentVideo;this.teardownPictureInPictureTracking(),this.teardownFullscreenTracking(),this.state.trackedContentVideo=e,this.setupPictureInPictureTracking(),this.setupFullscreenTracking(),this.state.contentPictureInPicture=this.elementHasPictureInPicture(e),this.deps.diagnose("DA-CONTENT-ELEMENT-SWAPPED","the content player replaced its <video>",{wasConnected:t.isConnected,pictureInPicture:this.state.contentPictureInPicture})}teardownFullscreenTracking(){const e=this.state.trackedContentVideo??this.deps.contentPlayer.videoElement;e&&(this.state.fullscreenEnterHandler&&e.removeEventListener("webkitbeginfullscreen",this.state.fullscreenEnterHandler),this.state.fullscreenExitHandler&&e.removeEventListener("webkitendfullscreen",this.state.fullscreenExitHandler)),this.state.fullscreenEnterHandler=null,this.state.fullscreenExitHandler=null,this.state.isContentFullscreen=!1}teardownPictureInPictureTracking(){const e=this.state.trackedContentVideo??this.deps.contentPlayer.videoElement;e&&(this.pipEnterHandler&&e.removeEventListener("enterpictureinpicture",this.pipEnterHandler),this.pipExitHandler&&e.removeEventListener("leavepictureinpicture",this.pipExitHandler),this.pipPresentationModeHandler&&e.removeEventListener("webkitpresentationmodechanged",this.pipPresentationModeHandler)),this.pipEnterHandler=null,this.pipExitHandler=null,this.pipPresentationModeHandler=null,this.state.contentPictureInPicture=!1}isPictureInPicture(){return this.isContentPictureInPicture()}pictureInPictureOwner(){const e=this.deps.getAdPlayer()?.videoElement;if(this.elementHasPictureInPicture(e))return"ad";const t=this.deps.contentPlayer.videoElement;return t&&this.elementHasPictureInPicture(t)?"content":null}async moveAdIntoPictureInPictureWindow(){this.canReceivePictureInPictureWindow()&&(await this.transferPictureInPictureToAd(),this.state.pipTransferredToAd)||this.deps.isAdPlaying()&&this.deps.getCurrentBreak()&&this.deps.hasCurrentAsset()&&(this.deps.isSharedElementActive()||(this.deps.contentSourceIsReloadable()?await this.deps.playAdThroughContentElement():this.deps.diagnose("DA-SHARED-ELEMENT-UNSUPPORTED","The window could not be moved to the ad, and content cannot be reloaded, so the ad cannot be shown in the picture-in-picture window",{reason:"no-restorable-content-source",midBreak:!0})))}adoptPictureInPictureMidBreak(){if(!this.deps.isAdPlaying()||this.state.pipAdoptedForBreak)return;const i=this.deps.getCurrentFormat();if(n(i,!0))return this.state.pipAdoptedForBreak=!0,this.deps.restoreLayout(
|
|
1
|
+
import{BreakFormat as e}from"../../types/BreakManifest";import{pipOverridesFormat as t,pipSuppressesBackdrop as n}from"../pictureInPicture";export class PresentationModeTracker{constructor(e,t){this.state=e,this.deps=t,this.pipEnterHandler=null,this.pipExitHandler=null,this.pipPresentationModeHandler=null}currentInsertionMode(){return this.deps.isSharedElementActive()?"shared-element":this.effectiveInsertionMode()}effectiveInsertionMode(){return"shared-element"===this.deps.insertionMode||this.isContentPictureInPicture()&&this.deps.contentSourceIsReloadable()&&!this.canReceivePictureInPictureWindow()||"adaptive"===this.deps.insertionMode&&this.isContentFullscreen()?"shared-element":"overlay"}isContentPictureInPicture(){const e=this.deps.getAdPlayer()?.videoElement,t=this.deps.contentPlayer.videoElement;return this.elementHasPictureInPicture(t)||this.elementHasPictureInPicture(e)}elementHasPictureInPicture(e){return!!e&&("picture-in-picture"===e.webkitPresentationMode||"undefined"!=typeof document&&document.pictureInPictureElement===e)}isContentFullscreen(){const e=this.deps.contentPlayer.videoElement;return e&&"boolean"==typeof e.webkitDisplayingFullscreen?e.webkitDisplayingFullscreen:this.state.isContentFullscreen}setupFullscreenTracking(){const e=this.deps.contentPlayer.videoElement;e&&(this.state.trackedContentVideo=e,this.state.fullscreenEnterHandler=()=>{this.state.isContentFullscreen=!0},this.state.fullscreenExitHandler=()=>{this.state.isContentFullscreen=!1},e.addEventListener("webkitbeginfullscreen",this.state.fullscreenEnterHandler),e.addEventListener("webkitendfullscreen",this.state.fullscreenExitHandler))}setupPictureInPictureTracking(){const e=this.deps.contentPlayer.videoElement;e&&(this.state.trackedContentVideo=e,this.pipEnterHandler=()=>{this.state.contentPictureInPicture=!0,this.deps.adoptPictureInPictureMidBreak()},this.pipExitHandler=()=>{this.state.pipTransferredToAd||(this.state.contentPictureInPicture=!1)},this.pipPresentationModeHandler=()=>{const t=e.webkitPresentationMode;this.state.contentPictureInPicture="picture-in-picture"===t,this.state.contentPictureInPicture&&this.deps.adoptPictureInPictureMidBreak()},e.addEventListener("enterpictureinpicture",this.pipEnterHandler),e.addEventListener("leavepictureinpicture",this.pipExitHandler),e.addEventListener("webkitpresentationmodechanged",this.pipPresentationModeHandler))}rebindContentElementIfSwapped(){const e=this.deps.contentPlayer.videoElement??null;if(e===this.state.trackedContentVideo)return;if(!this.state.trackedContentVideo||!e)return;const t=this.state.trackedContentVideo;this.teardownPictureInPictureTracking(),this.teardownFullscreenTracking(),this.state.trackedContentVideo=e,this.setupPictureInPictureTracking(),this.setupFullscreenTracking(),this.state.contentPictureInPicture=this.elementHasPictureInPicture(e),this.deps.diagnose("DA-CONTENT-ELEMENT-SWAPPED","the content player replaced its <video>",{wasConnected:t.isConnected,pictureInPicture:this.state.contentPictureInPicture})}teardownFullscreenTracking(){const e=this.state.trackedContentVideo??this.deps.contentPlayer.videoElement;e&&(this.state.fullscreenEnterHandler&&e.removeEventListener("webkitbeginfullscreen",this.state.fullscreenEnterHandler),this.state.fullscreenExitHandler&&e.removeEventListener("webkitendfullscreen",this.state.fullscreenExitHandler)),this.state.fullscreenEnterHandler=null,this.state.fullscreenExitHandler=null,this.state.isContentFullscreen=!1}teardownPictureInPictureTracking(){const e=this.state.trackedContentVideo??this.deps.contentPlayer.videoElement;e&&(this.pipEnterHandler&&e.removeEventListener("enterpictureinpicture",this.pipEnterHandler),this.pipExitHandler&&e.removeEventListener("leavepictureinpicture",this.pipExitHandler),this.pipPresentationModeHandler&&e.removeEventListener("webkitpresentationmodechanged",this.pipPresentationModeHandler)),this.pipEnterHandler=null,this.pipExitHandler=null,this.pipPresentationModeHandler=null,this.state.contentPictureInPicture=!1}isPictureInPicture(){return this.isContentPictureInPicture()}pictureInPictureOwner(){const e=this.deps.getAdPlayer()?.videoElement;if(this.elementHasPictureInPicture(e))return"ad";const t=this.deps.contentPlayer.videoElement;return t&&this.elementHasPictureInPicture(t)?"content":null}async moveAdIntoPictureInPictureWindow(){this.canReceivePictureInPictureWindow()&&(await this.transferPictureInPictureToAd(),this.state.pipTransferredToAd)||this.deps.isAdPlaying()&&this.deps.getCurrentBreak()&&this.deps.hasCurrentAsset()&&(this.deps.isSharedElementActive()||(this.deps.contentSourceIsReloadable()?await this.deps.playAdThroughContentElement():this.deps.diagnose("DA-SHARED-ELEMENT-UNSUPPORTED","The window could not be moved to the ad, and content cannot be reloaded, so the ad cannot be shown in the picture-in-picture window",{reason:"no-restorable-content-source",midBreak:!0})))}adoptPictureInPictureMidBreak(){if(!this.deps.isAdPlaying()||this.state.pipAdoptedForBreak)return;const i=this.deps.getCurrentFormat();if(n(i,!0))return this.state.pipAdoptedForBreak=!0,this.deps.restoreLayout(),void this.deps.diagnose("DA-PIP-FORMAT-OVERRIDE","Entered picture-in-picture during an lshape_content break; the window shows content",{declared:i,effective:i,reason:"picture-in-picture",midBreak:!0});if(!t(i,!0))return void this.moveAdIntoPictureInPictureWindow();this.state.pipAdoptedForBreak=!0,this.state.contentPaused||(this.state.contentPaused=!0,this.deps.setContentPositionAtBreakStart(this.deps.contentPlayer.currentTime),this.deps.gateContentForBreak()),this.deps.restoreLayout();const r=this.deps.getCurrentBreak(),s=r?this.deps.getVariant(r):null;s&&(this.deps.applyLayout(s),this.deps.show()),this.deps.diagnose("DA-PIP-FORMAT-OVERRIDE",`Entered picture-in-picture during a ${i} break; it now plays as single`,{declared:i,effective:e.SINGLE,reason:"picture-in-picture",midBreak:!0}),this.moveAdIntoPictureInPictureWindow()}async restorePictureInPictureToContent(){if(!this.state.pipTransferredToAd)return;this.state.pipTransferredToAd=!1;const e=this.deps.contentPlayer.videoElement;if(e&&this.adElementHasPictureInPicture())try{await this.enterPictureInPicture(e),this.deps.log("PiP window handed back to content")}catch(e){this.deps.diagnose("DA-PIP-TRANSFER-FAILED",`Could not return the picture-in-picture window to content: ${e instanceof Error?e.message:String(e)}`,{direction:"to-content"})}}async transferPictureInPictureToAd(){if(!this.isContentPictureInPicture())return;if(this.state.pipTransferredToAd)return;const e=this.deps.getAdPlayer()?.videoElement;if(e&&!this.elementHasPictureInPicture(e))try{if(await this.enterPictureInPicture(e),!this.adElementHasPictureInPicture())throw new Error("the browser did not move the window to the ad element");this.state.pipTransferredToAd=!0,this.deps.log("PiP window handed to the ad element")}catch(e){this.deps.diagnose("DA-PIP-TRANSFER-FAILED",`Could not move the picture-in-picture window to the ad: ${e instanceof Error?e.message:String(e)}`,{direction:"to-ad"})}}canReceivePictureInPictureWindow(){const e=this.deps.getAdPlayer()?.videoElement;return!!e&&("function"==typeof e.requestPictureInPicture&&"function"!=typeof e.webkitSetPresentationMode)}adElementHasPictureInPicture(){return this.elementHasPictureInPicture(this.deps.getAdPlayer()?.videoElement)}async enterPictureInPicture(e){"function"!=typeof e.webkitSetPresentationMode?await(e.requestPictureInPicture?.()):await this.setWebkitPresentationMode(e,"picture-in-picture")}setWebkitPresentationMode(e,t){return new Promise((n,i)=>{if(e.webkitPresentationMode===t)return void n();const r=t=>{e.removeEventListener("webkitpresentationmodechanged",s),clearTimeout(o),t?i(t):n()},s=()=>{e.webkitPresentationMode===t&&r()};e.addEventListener("webkitpresentationmodechanged",s);const o=setTimeout(()=>r(new Error(`the browser did not enter '${t}' within 3000ms`)),3e3);try{e.webkitSetPresentationMode?.(t)}catch(e){r(e instanceof Error?e:new Error(String(e)))}})}}
|
|
@@ -2,9 +2,12 @@ import type { PlayerAdapter, TimedMetadataCue } from '../../interfaces/PlayerAda
|
|
|
2
2
|
import type { Asset, Break } from '../../types/BreakManifest';
|
|
3
3
|
import type { AdPlayerCallbacks } from '../AdPlayerController';
|
|
4
4
|
import type { AdPlayerState } from './AdPlayerState';
|
|
5
|
+
import type { SurfaceRevealReporter } from './SurfaceRevealReporter';
|
|
5
6
|
export interface SharedElementBreakPlayerDeps {
|
|
6
7
|
contentPlayer: PlayerAdapter;
|
|
7
8
|
getAdPlayer: () => PlayerAdapter | null;
|
|
9
|
+
/** Watches for the ad's first frame on the content element (`DA-AD-SURFACE-REVEALED`). */
|
|
10
|
+
surfaceReveal?: SurfaceRevealReporter;
|
|
8
11
|
resolveAssetUri: (asset: Asset, duration: number) => Promise<string | null>;
|
|
9
12
|
liveEdgeDistanceSec: () => number | null;
|
|
10
13
|
rebindContentElementIfSwapped: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BreakFormat as e,isPostRollBreak as t}from"../../types/BreakManifest";import{armPlaybackCutoff as s,clearPlaybackCutoff as a}from"./PlaybackCutoff";import{AdError as n}from"../../types/AdError";import{classifyMediaError as r,toClassifiedAdError as i}from"./classifyMediaError";function d(e){return e?{src:e.getAttribute("src"),srcObject:e.srcObject,sourceChild:null!==e.querySelector("source")}:null}export class SharedElementBreakPlayer{constructor(e,t){this.state=e,this.deps=t,this.contentElementAdPlayer=null,this.sharedAdSource=null,this.sharedAdEndedHandler=null,this.sharedAdTimeUpdateHandler=null,this.sharedAdTimedMetadataHandler=null,this.sharedAdWaitingHandler=null,this.sharedAdPlayingHandler=null,this.sharedAdFirstFrameTimer=null,this.sharedRestoreWatchdog=null,this.sharedRestore=null,this.sharedRestoreGeneration=0,this.sharedActivityGeneration=0}sharedElementAdPlayer(){const e=this.deps.contentPlayer.videoElement;if(!e)return null;if(this.contentElementAdPlayer?.videoElement===e)return this.contentElementAdPlayer;this.releaseContentElementAdPlayer();const t=this.deps.createAdAdapter(document.createElement("div"),e);return t!==this.deps.contentPlayer&&t.prepareForAdPlayback?.(),t.videoElement!==e?(this.deps.log("shared-element: createAdAdapter ignored the content element; falling back to the content player for ad playback"),t.destroy(),null):(this.contentElementAdPlayer=t,t)}async sharedAdStep(e,t,s){try{await s()}catch(s){const a=t.videoElement,i=s instanceof Error?s.message:String(s),d=new n(`shared-element ${e} failed: ${i} (readyState=${a?.readyState??"-"} paused=${a?.paused??"-"} networkState=${a?.networkState??"-"})`,r(s));throw d.cause=s,d}}clearSharedAdFirstFrameWatchdog(){this.sharedAdFirstFrameTimer&&(clearTimeout(this.sharedAdFirstFrameTimer),this.sharedAdFirstFrameTimer=null)}armSharedAdFirstFrameWatchdog(e,t){this.clearSharedAdFirstFrameWatchdog(),this.sharedAdFirstFrameTimer=setTimeout(()=>{if(this.sharedAdFirstFrameTimer=null,!this.state.isPlaying||!this.state.currentBreak)return;const s=e.videoElement,a=this.deps.contentPlayer.videoElement,r=s?.currentSrc??"",i=`readyState=${s?.readyState??"-"} networkState=${s?.networkState??"-"} paused=${s?.paused??"-"} connected=${s?.isConnected??"-"} srcKind=${r.startsWith("blob:")?"blob":r?"url":"none"} sameElementAsContentPlayer=${String(s===a)}`;this.deps.log(`shared-element: no first frame within ${t}ms (${i})`),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",`the ad never rendered a frame on the content element within ${t}ms`,{breakId:this.state.currentBreak.id,detail:i});const d=this.state.currentAsset;d&&this.state.callbacks?.onAdError(this.state.currentBreak,d,new n(`shared-element ad produced no frames within ${t}ms (${i})`,"DA-AD-START-TIMEOUT")),this.onSharedElementAdEnded()},t)}attachSharedAdListeners(e){this.sharedAdSource=e,this.sharedAdEndedHandler=()=>this.onSharedElementAdEnded(),e.on("ended",this.sharedAdEndedHandler),this.sharedAdTimeUpdateHandler=()=>{this.sharedAdFirstFrameTimer&&e.currentTime>0&&this.clearSharedAdFirstFrameWatchdog(),this.deps.reportAdProgress(e)},e.on("timeupdate",this.sharedAdTimeUpdateHandler),this.sharedAdTimedMetadataHandler=e=>{e&&this.deps.processTimedMetadata(e)},e.on("timedmetadata",this.sharedAdTimedMetadataHandler),this.sharedAdWaitingHandler=()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&this.state.callbacks?.onAdWaiting(this.state.currentBreak,this.state.currentAsset)},e.on("waiting",this.sharedAdWaitingHandler),this.sharedAdPlayingHandler=()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&!e.paused&&(this.clearSharedAdFirstFrameWatchdog(),this.state.startPlaybackCutoff?.(),this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset))},e.on("playing",this.sharedAdPlayingHandler)}detachSharedAdListeners(){const e=this.sharedAdSource??this.deps.contentPlayer;this.sharedAdSource=null,this.state.sharedAdClaimActive=!1,this.clearSharedAdFirstFrameWatchdog(),this.sharedAdTimedMetadataHandler&&(e.off("timedmetadata",this.sharedAdTimedMetadataHandler),this.sharedAdTimedMetadataHandler=null),this.sharedAdWaitingHandler&&(e.off("waiting",this.sharedAdWaitingHandler),this.sharedAdWaitingHandler=null),this.sharedAdPlayingHandler&&(e.off("playing",this.sharedAdPlayingHandler),this.sharedAdPlayingHandler=null),this.sharedAdTimeUpdateHandler&&(e.off("timeupdate",this.sharedAdTimeUpdateHandler),this.sharedAdTimeUpdateHandler=null),this.sharedAdEndedHandler&&(e.off("ended",this.sharedAdEndedHandler),this.sharedAdEndedHandler=null)}async releaseContentElementFromContentEngine(){if(this.deps.contentPlayer.releaseMediaElement)try{await this.deps.contentPlayer.releaseMediaElement()}catch(e){this.deps.log("shared-element: the content engine did not release the element:",e)}}releaseContentElementAdPlayer(){const e=this.contentElementAdPlayer;if(e){this.contentElementAdPlayer=null;try{e.destroy()}catch(e){this.deps.log("shared-element: releasing the ad player on the content element failed:",e)}}}async playAdThroughContentElement(){const e=this.state.currentAsset,t=this.state.currentBreak;if(!e||!t)return;const s=this.state.effectiveBreakDurationSec??t.duration,a=await this.deps.resolveAssetUri(e,s);if(!a)return void this.deps.log("shared-element: no URI for the running ad; leaving it on the ad element");const n=this.deps.getAdPlayer()?.currentTime??0;this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const r=this.sharedElementAdPlayer()??this.deps.contentPlayer;try{this.deps.getAdPlayer()?.pause(),this.deps.clearContentGate(),await r.load(a),this.attachSharedAdListeners(r),n>0&&r.seek(n),await r.play(),this.deps.hide(),this.deps.log("shared-element: the ad now plays through the content element")}catch(e){this.deps.log("shared-element: could not move the ad onto the content element:",e),this.state.sharedAdClaimActive=!1,this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null}}async playSharedElementBreak(t,a){const r=this.deps.getVariant(t);if(!r)return this.deps.log("shared-element: no valid variant, skipping break:",t.id),void a.onBreakEnd(t);if(r.format===e.LSHAPE_CONTENT)return this.deps.log("shared-element: skipping lshape_content break (no fullscreen video asset):",t.id),void a.onBreakEnd(t);const d=this.deps.getAssets(t)[0];if(!d)return this.deps.log("shared-element: no asset to play, skipping break:",t.id),void a.onBreakEnd(t);r.format!==e.SINGLE&&this.deps.log(`shared-element: downgrading ${r.format} to a single fullscreen ad:`,t.id),this.state.currentBreak=t,this.state.currentAsset=d,this.state.callbacks=a,this.state.isPlaying=!0;const h=this.state.effectiveBreakDurationSec??t.duration,l=await this.deps.resolveAssetUri(d,h);if(!l)return this.deps.log("shared-element: no valid URI, skipping break:",t.id),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null,void a.onBreakEnd(t);this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const o=this.sharedElementAdPlayer()??this.deps.contentPlayer;if(h&&h>0){const e=h+this.state.adBreakCutSafetyMarginSec;s(this.state,e,()=>{this.deps.log("shared-element: break-cut timer expired, ending break"),this.state.isPlaying&&this.state.currentBreak&&this.state.callbacks?.onBreakCut?.(this.state.currentBreak),this.onSharedElementAdEnded()},()=>{this.state.callbacks?.onAdError(t,d,new n("Ad playback startup timed out","DA-AD-START-TIMEOUT")),this.onSharedElementAdEnded()})}try{if(this.deps.clearContentGate(),await this.sharedAdStep("load",o,()=>o.load(l)),!this.state.isPlaying||this.state.currentBreak!==t)return;if(this.attachSharedAdListeners(o),this.armSharedAdFirstFrameWatchdog(o,Math.max(Math.min(1e3*(h??0)-100,5e3),3e3)),await this.sharedAdStep("play",o,()=>o.play()),!this.state.isPlaying||this.state.currentBreak!==t)return;this.state.callbacks?.onAdStart(t,d,0,1)}catch(e){if(!this.state.isPlaying||this.state.currentBreak!==t)return;this.deps.log("shared-element: ad playback failed:",e),this.state.callbacks?.onAdError(t,d,i(e)),this.onSharedElementAdEnded()}}onSharedElementAdEnded(){if(!this.state.isPlaying||!this.state.currentBreak)return;const e=this.state.currentBreak,t=this.state.currentAsset,s=this.state.callbacks,n=this.sharedRestoreGeneration;this.detachSharedAdListeners(),a(this.state),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null,t&&s?.onAdEnd(e,t,0,1),n===this.sharedRestoreGeneration&&this.restoreSharedElementContent(e),s?.onBreakEnd(e)}restoreSharedElementContent(e){const s=this.state.savedContentSrc;if(!s){if(this.sharedRestore)return;return this.deps.log("shared-element: no saved content source to restore"),void this.releaseContentElementAdPlayer()}if(!this.beginSharedActivity())return;const a=this.state.savedContentTime,n=this.state.savedContentLive,r=this.state.savedContentEdgeDistance,i=this.state.callbacks;this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null;const d={src:s,time:a,live:n,edgeDistance:r,breakInfo:e,postRoll:!!e&&t(e),onContentReloading:i?.onContentReloading?.bind(i),adPlayer:this.contentElementAdPlayer,reloading:!1,cleanedUp:!1,cancelled:!1,finishingActivity:null,attempt:null};this.sharedRestore=d,e&&"number"==typeof e.start&&i?.onContentResumeSeek?.(n?this.deps.contentPlayer.currentTime:a,e.start+e.duration),this.sharedRestore===d&&(d.reloading=!0,d.onContentReloading?.(!0),this.sharedRestore===d&&this.runSharedRestore(d))}async runSharedRestore(e){for(let t=1;t<=2;t++){const s={phase:"load",deadline:0,timer:null,settle:null};e.attempt=s;const a=await new Promise(t=>{s.settle=e=>{s.settle&&(s.settle=null,s.timer&&clearTimeout(s.timer),s.timer=null,t(e))},this.armSharedRestoreDeadline(s,1e4),this.reloadAndResume(e,s).then(()=>s.settle?.("success"),()=>s.settle?.("rejection"))});if(this.sharedRestore!==e||e.attempt!==s)return;if("success"===a||"cancelled"===a)break;if(2===t?this.deps.diagnose("DA-SHARED-ELEMENT-RESTORE-FAILED",`Content did not resume after the shared-element break: ${s.phase} ${a}`,{breakId:e.breakInfo?.id,live:e.live,attempts:t,phase:s.phase,reason:a}):this.deps.log(`shared-element: content restore ${s.phase} ${a}, retrying once`),this.sharedRestore!==e||e.attempt!==s)return}this.finishSharedRestore(e,!0)}armSharedRestoreDeadline(e,t){e.timer&&clearTimeout(e.timer),e.deadline=performance.now()+t,e.timer=setTimeout(()=>e.settle?.("timeout"),t)}ownsSharedRestoreAttempt(e,t){return!(this.sharedRestore!==e||e.attempt!==t||!t.settle)&&(performance.now()<t.deadline||(t.settle("timeout"),!1))}async reloadAndResume(e,t){const{src:s,time:a,live:n,edgeDistance:r,postRoll:i}=e,h=()=>this.ownsSharedRestoreAttempt(e,t);if(!h())return;if(e.adPlayer?.releaseMediaElement&&(await this.releaseAdEngineGrip(e),!h()))return;const l=this.deps.contentPlayer.videoElement??null,o=d(l);if(await this.deps.contentPlayer.load(s),h()){if(this.armSharedRestoreDeadline(t,5e3),n){if(!i&&null!==r&&r>0){t.phase="live-edge";const e=await this.deps.waitForLiveEdge(h);if(!h())return;if(null!==e){const s=Math.max(e-r,0);if(this.deps.log(`shared-element: live resume ${r.toFixed(2)}s behind the edge (${s.toFixed(2)}s)`),!h())return;t.phase="seek",this.deps.contentPlayer.seek(s)}}}else t.phase="seek",this.deps.contentPlayer.seek(a);h()&&!i&&(t.phase="play",await this.deps.contentPlayer.play(),h()&&await this.waitForContentEngineToTakeElement(l,o,h))}}async releaseAdEngineGrip(e){const t=e.adPlayer;if(t?.releaseMediaElement)try{await t.releaseMediaElement()}catch(e){this.deps.log("shared-element: the ad engine did not release the element:",e)}}async waitForContentEngineToTakeElement(e,t,s){if(!(e&&t&&(a=t,a.src||a.srcObject||a.sourceChild)))return;var a;const n=()=>{if(this.deps.contentPlayer.videoElement!==e)return!0;const s=d(e);if(!s)return!1;const a=!!s.src&&s.src!==t.src,n=!!s.srcObject&&s.srcObject!==t.srcObject;return a||n},r=performance.now();for(;!n()&&s()&&performance.now()-r<1500;)await new Promise(e=>setTimeout(e,25));if(!s())return;const i=Math.round(performance.now()-r);this.deps.log(`shared-element: content engine ${n()?"took":"did not take"} the element after ${i}ms`)}finishSharedRestore(e,t){this.sharedRestore===e&&(null===e.finishingActivity?(e.finishingActivity=this.sharedActivityGeneration,e.cleanedUp||(this.state.contentPaused=!1,this.contentElementAdPlayer===e.adPlayer&&this.releaseContentElementAdPlayer(),this.sharedRestore===e&&this.deps.clearContentGate(),this.sharedRestore===e&&this.deps.rebindContentElementIfSwapped(),e.cleanedUp=!0),this.endSharedRestoreReloading(e),e.finishingActivity=null,this.sharedRestore===e&&(!t||e.cancelled||e.postRoll?this.sharedRestore=null:this.armSharedRestoreWatchdog(e))):e.finishingActivity!==this.sharedActivityGeneration&&(this.endSharedRestoreReloading(e),this.sharedRestore===e&&(this.sharedRestore=null)))}endSharedRestoreReloading(e){e.reloading&&(e.reloading=!1,e.onContentReloading?.(!1))}armSharedRestoreWatchdog(e){const t=this.deps.contentPlayer.currentTime;this.sharedRestoreWatchdog=setTimeout(()=>{if(this.sharedRestore!==e)return;this.sharedRestoreWatchdog=null;const s=this.deps.contentPlayer.currentTime-t;(this.deps.contentPlayer.paused||s<1)&&(this.deps.diagnose("DA-CONTENT-RESUME-STALLED",`Content did not resume within 5s of the shared-element restore (paused: ${this.deps.contentPlayer.paused}, advanced: ${s.toFixed(1)}s); nudging play()`,{breakId:e.breakInfo?.id??"?",paused:this.deps.contentPlayer.paused,advancedSec:s}),this.sharedRestore===e&&this.deps.contentPlayer.play().catch(()=>{this.sharedRestore===e&&this.deps.log("shared-element: watchdog play() nudge failed")}))},5e3)}beginSharedActivity(){return this.sharedActivityGeneration++,this.cancelSharedRestore()}cancelSharedRestore(){const e=this.sharedActivityGeneration;this.sharedRestoreGeneration++,this.sharedRestoreWatchdog&&(clearTimeout(this.sharedRestoreWatchdog),this.sharedRestoreWatchdog=null);const t=this.sharedRestore;return t&&(t.cancelled=!0,t.attempt?.settle?.("cancelled"),this.finishSharedRestore(t,!1)),e===this.sharedActivityGeneration}restorableContentSource(){const e=this.deps.contentPlayer.sourceUrl;if(e&&!e.startsWith("blob:"))return e;const t=this.deps.contentPlayer.videoElement,s=t?.currentSrc||t?.src||"";return s.length>0&&!s.startsWith("blob:")?s:null}}
|
|
1
|
+
import{BreakFormat as e,isPostRollBreak as t}from"../../types/BreakManifest";import{armPlaybackCutoff as s,clearPlaybackCutoff as a}from"./PlaybackCutoff";import{AdError as n}from"../../types/AdError";import{classifyMediaError as r,toClassifiedAdError as i}from"./classifyMediaError";function d(e){return e?{src:e.getAttribute("src"),srcObject:e.srcObject,sourceChild:null!==e.querySelector("source")}:null}export class SharedElementBreakPlayer{constructor(e,t){this.state=e,this.deps=t,this.contentElementAdPlayer=null,this.sharedAdSource=null,this.sharedAdEndedHandler=null,this.sharedAdTimeUpdateHandler=null,this.sharedAdTimedMetadataHandler=null,this.sharedAdWaitingHandler=null,this.sharedAdPlayingHandler=null,this.sharedAdFirstFrameTimer=null,this.sharedRestoreWatchdog=null,this.sharedRestore=null,this.sharedRestoreGeneration=0,this.sharedActivityGeneration=0}sharedElementAdPlayer(){const e=this.deps.contentPlayer.videoElement;if(!e)return null;if(this.contentElementAdPlayer?.videoElement===e)return this.contentElementAdPlayer;this.releaseContentElementAdPlayer();const t=this.deps.createAdAdapter(document.createElement("div"),e);return t!==this.deps.contentPlayer&&t.prepareForAdPlayback?.(),t.videoElement!==e?(this.deps.log("shared-element: createAdAdapter ignored the content element; falling back to the content player for ad playback"),t.destroy(),null):(this.contentElementAdPlayer=t,t)}async sharedAdStep(e,t,s){try{await s()}catch(s){const a=t.videoElement,i=s instanceof Error?s.message:String(s),d=new n(`shared-element ${e} failed: ${i} (readyState=${a?.readyState??"-"} paused=${a?.paused??"-"} networkState=${a?.networkState??"-"})`,r(s));throw d.cause=s,d}}clearSharedAdFirstFrameWatchdog(){this.sharedAdFirstFrameTimer&&(clearTimeout(this.sharedAdFirstFrameTimer),this.sharedAdFirstFrameTimer=null)}armSharedAdFirstFrameWatchdog(e,t){this.clearSharedAdFirstFrameWatchdog(),this.sharedAdFirstFrameTimer=setTimeout(()=>{if(this.sharedAdFirstFrameTimer=null,!this.state.isPlaying||!this.state.currentBreak)return;const s=e.videoElement,a=this.deps.contentPlayer.videoElement,r=s?.currentSrc??"",i=`readyState=${s?.readyState??"-"} networkState=${s?.networkState??"-"} paused=${s?.paused??"-"} connected=${s?.isConnected??"-"} srcKind=${r.startsWith("blob:")?"blob":r?"url":"none"} sameElementAsContentPlayer=${String(s===a)}`;this.deps.log(`shared-element: no first frame within ${t}ms (${i})`),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",`the ad never rendered a frame on the content element within ${t}ms`,{breakId:this.state.currentBreak.id,detail:i});const d=this.state.currentAsset;d&&this.state.callbacks?.onAdError(this.state.currentBreak,d,new n(`shared-element ad produced no frames within ${t}ms (${i})`,"DA-AD-START-TIMEOUT")),this.onSharedElementAdEnded()},t)}attachSharedAdListeners(e){this.sharedAdSource=e,this.sharedAdEndedHandler=()=>this.onSharedElementAdEnded(),e.on("ended",this.sharedAdEndedHandler),this.sharedAdTimeUpdateHandler=()=>{this.sharedAdFirstFrameTimer&&e.currentTime>0&&this.clearSharedAdFirstFrameWatchdog(),this.deps.reportAdProgress(e)},e.on("timeupdate",this.sharedAdTimeUpdateHandler),this.sharedAdTimedMetadataHandler=e=>{e&&this.deps.processTimedMetadata(e)},e.on("timedmetadata",this.sharedAdTimedMetadataHandler),this.sharedAdWaitingHandler=()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&this.state.callbacks?.onAdWaiting(this.state.currentBreak,this.state.currentAsset)},e.on("waiting",this.sharedAdWaitingHandler),this.sharedAdPlayingHandler=()=>{this.state.isPlaying&&this.state.currentBreak&&this.state.currentAsset&&!e.paused&&(this.clearSharedAdFirstFrameWatchdog(),this.state.startPlaybackCutoff?.(),this.state.callbacks?.onAdPlaying(this.state.currentBreak,this.state.currentAsset))},e.on("playing",this.sharedAdPlayingHandler)}detachSharedAdListeners(){const e=this.sharedAdSource??this.deps.contentPlayer;this.sharedAdSource=null,this.state.sharedAdClaimActive=!1,this.clearSharedAdFirstFrameWatchdog(),this.sharedAdTimedMetadataHandler&&(e.off("timedmetadata",this.sharedAdTimedMetadataHandler),this.sharedAdTimedMetadataHandler=null),this.sharedAdWaitingHandler&&(e.off("waiting",this.sharedAdWaitingHandler),this.sharedAdWaitingHandler=null),this.sharedAdPlayingHandler&&(e.off("playing",this.sharedAdPlayingHandler),this.sharedAdPlayingHandler=null),this.sharedAdTimeUpdateHandler&&(e.off("timeupdate",this.sharedAdTimeUpdateHandler),this.sharedAdTimeUpdateHandler=null),this.sharedAdEndedHandler&&(e.off("ended",this.sharedAdEndedHandler),this.sharedAdEndedHandler=null)}async releaseContentElementFromContentEngine(){if(this.deps.contentPlayer.releaseMediaElement)try{await this.deps.contentPlayer.releaseMediaElement()}catch(e){this.deps.log("shared-element: the content engine did not release the element:",e)}}releaseContentElementAdPlayer(){const e=this.contentElementAdPlayer;if(e){this.contentElementAdPlayer=null;try{e.destroy()}catch(e){this.deps.log("shared-element: releasing the ad player on the content element failed:",e)}}}async playAdThroughContentElement(){const e=this.state.currentAsset,t=this.state.currentBreak;if(!e||!t)return;const s=this.state.effectiveBreakDurationSec??t.duration,a=await this.deps.resolveAssetUri(e,s);if(!a)return void this.deps.log("shared-element: no URI for the running ad; leaving it on the ad element");const n=this.deps.getAdPlayer()?.currentTime??0;this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const r=this.sharedElementAdPlayer()??this.deps.contentPlayer;try{this.deps.getAdPlayer()?.pause(),this.deps.clearContentGate(),await r.load(a),this.attachSharedAdListeners(r),n>0&&r.seek(n),await r.play(),this.deps.hide(),this.deps.log("shared-element: the ad now plays through the content element")}catch(e){this.deps.log("shared-element: could not move the ad onto the content element:",e),this.state.sharedAdClaimActive=!1,this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null}}async playSharedElementBreak(t,a){const r=this.deps.getVariant(t);if(!r)return this.deps.log("shared-element: no valid variant, skipping break:",t.id),void a.onBreakEnd(t);if(r.format===e.LSHAPE_CONTENT)return this.deps.log("shared-element: skipping lshape_content break (no fullscreen video asset):",t.id),void a.onBreakEnd(t);const d=this.deps.getAssets(t)[0];if(!d)return this.deps.log("shared-element: no asset to play, skipping break:",t.id),void a.onBreakEnd(t);r.format!==e.SINGLE&&this.deps.log(`shared-element: downgrading ${r.format} to a single fullscreen ad:`,t.id),this.state.currentBreak=t,this.state.currentAsset=d,this.state.callbacks=a,this.state.isPlaying=!0;const h=this.state.effectiveBreakDurationSec??t.duration,l=await this.deps.resolveAssetUri(d,h);if(!l)return this.deps.log("shared-element: no valid URI, skipping break:",t.id),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null,void a.onBreakEnd(t);this.state.savedContentSrc=this.restorableContentSource(),this.state.savedContentTime=this.deps.contentPlayer.currentTime,this.state.savedContentLive=!Number.isFinite(this.deps.contentPlayer.duration),this.state.savedContentEdgeDistance=this.state.savedContentLive?this.deps.liveEdgeDistanceSec():null,this.state.sharedAdClaimActive=!0,await this.releaseContentElementFromContentEngine(),this.deps.rebindContentElementIfSwapped();const o=this.sharedElementAdPlayer()??this.deps.contentPlayer;if(h&&h>0){const e=h+this.state.adBreakCutSafetyMarginSec;s(this.state,e,()=>{this.deps.log("shared-element: break-cut timer expired, ending break"),this.state.isPlaying&&this.state.currentBreak&&this.state.callbacks?.onBreakCut?.(this.state.currentBreak),this.onSharedElementAdEnded()},()=>{this.state.callbacks?.onAdError(t,d,new n("Ad playback startup timed out","DA-AD-START-TIMEOUT")),this.onSharedElementAdEnded()})}try{if(this.deps.clearContentGate(),await this.sharedAdStep("load",o,()=>o.load(l)),!this.state.isPlaying||this.state.currentBreak!==t)return;if(this.attachSharedAdListeners(o),this.armSharedAdFirstFrameWatchdog(o,Math.max(Math.min(1e3*(h??0)-100,5e3),3e3)),this.deps.surfaceReveal?.surfaceShown(),this.deps.surfaceReveal?.watch(d.id,t.id,o.videoElement),await this.sharedAdStep("play",o,()=>o.play()),!this.state.isPlaying||this.state.currentBreak!==t)return;this.state.callbacks?.onAdStart(t,d,0,1)}catch(e){if(!this.state.isPlaying||this.state.currentBreak!==t)return;this.deps.log("shared-element: ad playback failed:",e),this.state.callbacks?.onAdError(t,d,i(e)),this.onSharedElementAdEnded()}}onSharedElementAdEnded(){if(!this.state.isPlaying||!this.state.currentBreak)return;const e=this.state.currentBreak,t=this.state.currentAsset,s=this.state.callbacks,n=this.sharedRestoreGeneration;this.detachSharedAdListeners(),a(this.state),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null,t&&s?.onAdEnd(e,t,0,1),n===this.sharedRestoreGeneration&&this.restoreSharedElementContent(e),this.deps.surfaceReveal?.surfaceHidden(),s?.onBreakEnd(e)}restoreSharedElementContent(e){const s=this.state.savedContentSrc;if(!s){if(this.sharedRestore)return;return this.deps.log("shared-element: no saved content source to restore"),void this.releaseContentElementAdPlayer()}if(!this.beginSharedActivity())return;const a=this.state.savedContentTime,n=this.state.savedContentLive,r=this.state.savedContentEdgeDistance,i=this.state.callbacks;this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null;const d={src:s,time:a,live:n,edgeDistance:r,breakInfo:e,postRoll:!!e&&t(e),onContentReloading:i?.onContentReloading?.bind(i),adPlayer:this.contentElementAdPlayer,reloading:!1,cleanedUp:!1,cancelled:!1,finishingActivity:null,attempt:null};this.sharedRestore=d,e&&"number"==typeof e.start&&i?.onContentResumeSeek?.(n?this.deps.contentPlayer.currentTime:a,e.start+e.duration),this.sharedRestore===d&&(d.reloading=!0,d.onContentReloading?.(!0),this.sharedRestore===d&&this.runSharedRestore(d))}async runSharedRestore(e){for(let t=1;t<=2;t++){const s={phase:"load",deadline:0,timer:null,settle:null};e.attempt=s;const a=await new Promise(t=>{s.settle=e=>{s.settle&&(s.settle=null,s.timer&&clearTimeout(s.timer),s.timer=null,t(e))},this.armSharedRestoreDeadline(s,1e4),this.reloadAndResume(e,s).then(()=>s.settle?.("success"),()=>s.settle?.("rejection"))});if(this.sharedRestore!==e||e.attempt!==s)return;if("success"===a||"cancelled"===a)break;if(2===t?this.deps.diagnose("DA-SHARED-ELEMENT-RESTORE-FAILED",`Content did not resume after the shared-element break: ${s.phase} ${a}`,{breakId:e.breakInfo?.id,live:e.live,attempts:t,phase:s.phase,reason:a}):this.deps.log(`shared-element: content restore ${s.phase} ${a}, retrying once`),this.sharedRestore!==e||e.attempt!==s)return}this.finishSharedRestore(e,!0)}armSharedRestoreDeadline(e,t){e.timer&&clearTimeout(e.timer),e.deadline=performance.now()+t,e.timer=setTimeout(()=>e.settle?.("timeout"),t)}ownsSharedRestoreAttempt(e,t){return!(this.sharedRestore!==e||e.attempt!==t||!t.settle)&&(performance.now()<t.deadline||(t.settle("timeout"),!1))}async reloadAndResume(e,t){const{src:s,time:a,live:n,edgeDistance:r,postRoll:i}=e,h=()=>this.ownsSharedRestoreAttempt(e,t);if(!h())return;if(e.adPlayer?.releaseMediaElement&&(await this.releaseAdEngineGrip(e),!h()))return;const l=this.deps.contentPlayer.videoElement??null,o=d(l);if(await this.deps.contentPlayer.load(s),h()){if(this.armSharedRestoreDeadline(t,5e3),n){if(!i&&null!==r&&r>0){t.phase="live-edge";const e=await this.deps.waitForLiveEdge(h);if(!h())return;if(null!==e){const s=Math.max(e-r,0);if(this.deps.log(`shared-element: live resume ${r.toFixed(2)}s behind the edge (${s.toFixed(2)}s)`),!h())return;t.phase="seek",this.deps.contentPlayer.seek(s)}}}else t.phase="seek",this.deps.contentPlayer.seek(a);h()&&!i&&(t.phase="play",await this.deps.contentPlayer.play(),h()&&await this.waitForContentEngineToTakeElement(l,o,h))}}async releaseAdEngineGrip(e){const t=e.adPlayer;if(t?.releaseMediaElement)try{await t.releaseMediaElement()}catch(e){this.deps.log("shared-element: the ad engine did not release the element:",e)}}async waitForContentEngineToTakeElement(e,t,s){if(!(e&&t&&(a=t,a.src||a.srcObject||a.sourceChild)))return;var a;const n=()=>{if(this.deps.contentPlayer.videoElement!==e)return!0;const s=d(e);if(!s)return!1;const a=!!s.src&&s.src!==t.src,n=!!s.srcObject&&s.srcObject!==t.srcObject;return a||n},r=performance.now();for(;!n()&&s()&&performance.now()-r<1500;)await new Promise(e=>setTimeout(e,25));if(!s())return;const i=Math.round(performance.now()-r);this.deps.log(`shared-element: content engine ${n()?"took":"did not take"} the element after ${i}ms`)}finishSharedRestore(e,t){this.sharedRestore===e&&(null===e.finishingActivity?(e.finishingActivity=this.sharedActivityGeneration,e.cleanedUp||(this.state.contentPaused=!1,this.contentElementAdPlayer===e.adPlayer&&this.releaseContentElementAdPlayer(),this.sharedRestore===e&&this.deps.clearContentGate(),this.sharedRestore===e&&this.deps.rebindContentElementIfSwapped(),e.cleanedUp=!0),this.endSharedRestoreReloading(e),e.finishingActivity=null,this.sharedRestore===e&&(!t||e.cancelled||e.postRoll?this.sharedRestore=null:this.armSharedRestoreWatchdog(e))):e.finishingActivity!==this.sharedActivityGeneration&&(this.endSharedRestoreReloading(e),this.sharedRestore===e&&(this.sharedRestore=null)))}endSharedRestoreReloading(e){e.reloading&&(e.reloading=!1,e.onContentReloading?.(!1))}armSharedRestoreWatchdog(e){const t=this.deps.contentPlayer.currentTime;this.sharedRestoreWatchdog=setTimeout(()=>{if(this.sharedRestore!==e)return;this.sharedRestoreWatchdog=null;const s=this.deps.contentPlayer.currentTime-t;(this.deps.contentPlayer.paused||s<1)&&(this.deps.diagnose("DA-CONTENT-RESUME-STALLED",`Content did not resume within 5s of the shared-element restore (paused: ${this.deps.contentPlayer.paused}, advanced: ${s.toFixed(1)}s); nudging play()`,{breakId:e.breakInfo?.id??"?",paused:this.deps.contentPlayer.paused,advancedSec:s}),this.sharedRestore===e&&this.deps.contentPlayer.play().catch(()=>{this.sharedRestore===e&&this.deps.log("shared-element: watchdog play() nudge failed")}))},5e3)}beginSharedActivity(){return this.sharedActivityGeneration++,this.cancelSharedRestore()}cancelSharedRestore(){const e=this.sharedActivityGeneration;this.sharedRestoreGeneration++,this.sharedRestoreWatchdog&&(clearTimeout(this.sharedRestoreWatchdog),this.sharedRestoreWatchdog=null);const t=this.sharedRestore;return t&&(t.cancelled=!0,t.attempt?.settle?.("cancelled"),this.finishSharedRestore(t,!1)),e===this.sharedActivityGeneration}restorableContentSource(){const e=this.deps.contentPlayer.sourceUrl;if(e&&!e.startsWith("blob:"))return e;const t=this.deps.contentPlayer.videoElement,s=t?.currentSrc||t?.src||"";return s.length>0&&!s.startsWith("blob:")?s:null}}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
2
|
+
import { type Asset, type Break, type StaticAsset } from '../../types/BreakManifest';
|
|
3
|
+
import type { AdPlayerCallbacks } from './AdPlayerCallbacks';
|
|
4
|
+
export interface SuccessorQueueDeps {
|
|
5
|
+
getAdPlayer: () => PlayerAdapter | null;
|
|
6
|
+
/** Whether the playing break may keep the next ad buffered (overlay insertion, parallel preload). */
|
|
7
|
+
isEnabled: () => boolean;
|
|
8
|
+
buildStaticAssetUrl: (asset: StaticAsset, breakInfo: Break) => string | null;
|
|
9
|
+
getCallbacks: () => AdPlayerCallbacks | null;
|
|
10
|
+
log: (...args: unknown[]) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The next ad, queued on the ad player while the current ad plays: the playing break's next ad,
|
|
14
|
+
* or during its last ad the chained break's first ad (`prepare-successor` / `drop-successor`).
|
|
15
|
+
* Ads it cannot queue keep today's load at the switch.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SuccessorQueue {
|
|
18
|
+
private readonly deps;
|
|
19
|
+
private entry;
|
|
20
|
+
constructor(deps: SuccessorQueueDeps);
|
|
21
|
+
/** Queues `asset` of `breakInfo`; `late` when the successor changed while the last ad played. */
|
|
22
|
+
prepare(breakInfo: Break, asset: Asset, late: boolean): void;
|
|
23
|
+
/** Drops the queued item (of `breakId`, when given): it will not play next. */
|
|
24
|
+
drop(breakId?: string): void;
|
|
25
|
+
/**
|
|
26
|
+
* Before a chained break begins: keeps the queued item only when it is buffered and is the ad
|
|
27
|
+
* `next` opens with, so the switch measurement names the path that actually plays.
|
|
28
|
+
*/
|
|
29
|
+
confirm(next: Break, firstAsset: Asset | undefined): void;
|
|
30
|
+
/** At the switch: true when the queued item is this ad and became the current source. */
|
|
31
|
+
take(breakId: string, assetId: string, uri: string): boolean;
|
|
32
|
+
/** Teardown: forgets the queued item without reporting it; nothing is listening any more. */
|
|
33
|
+
reset(): void;
|
|
34
|
+
private forget;
|
|
35
|
+
private queueableUri;
|
|
36
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{hasAdSourceQueue as e}from"../../interfaces/AdSourceQueue";import{AssetType as t,MediaType as s}from"../../types/BreakManifest";export class SuccessorQueue{constructor(e){this.deps=e,this.entry=null}prepare(t,s,r){const i=this.queueableUri(t,s),d=this.entry;if(d&&d.breakId===t.id&&d.assetId===s.id&&d.uri===i)return;this.drop();const u=this.deps.getAdPlayer();if(!i||!e(u))return;const n={breakId:t.id,assetId:s.id,uri:i,late:r,buffered:!1};this.entry=n,u.queueNext(i,()=>this.forget(n)).then(e=>{if(this.entry===n){if(!e)return this.entry=null,void this.deps.log("Chained successor not queued; it loads at the switch:",n.breakId);n.buffered=!0,this.deps.getCallbacks()?.onSuccessorBuffered?.(n.breakId,n.late)}},e=>{this.entry===n&&(this.entry=null),this.deps.log("Chained successor queue failed; it loads at the switch:",e)})}drop(t){const s=this.entry;if(!s||void 0!==t&&s.breakId!==t)return;this.entry=null;const r=this.deps.getAdPlayer();e(r)&&r.clearQueued(),s.buffered&&this.deps.getCallbacks()?.onSuccessorDropped?.(s.breakId)}confirm(e,t){const s=this.entry;if(!s)return;const r=t?this.queueableUri(e,t):null;s.buffered&&s.breakId===e.id&&s.assetId===t?.id&&s.uri===r||this.drop()}take(t,s,r){const i=this.entry;if(!i)return!1;const d=this.deps.getAdPlayer();return i.buffered&&i.breakId===t&&i.assetId===s&&i.uri===r&&this.deps.isEnabled()&&e(d)&&d.takeQueued(r)?(this.entry=null,!0):(this.drop(),!1)}reset(){if(!this.entry)return;this.entry=null;const t=this.deps.getAdPlayer();e(t)&&t.clearQueued()}forget(e){this.entry===e&&(this.entry=null,e.buffered&&this.deps.getCallbacks()?.onSuccessorDropped?.(e.breakId))}queueableUri(e,r){return this.deps.isEnabled()?r.type!==t.STATIC||r.mediaType===s.IMAGE?null:this.deps.buildStaticAssetUrl(r,e):null}}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/** Native's ad-start deadline: an ad with no picture by then is reported as `reveal-timeout`. */
|
|
2
|
+
export declare const SURFACE_REVEAL_DEADLINE_MS = 10000;
|
|
3
|
+
type Diagnose = (code: string, message: string, context: Record<string, unknown>) => void;
|
|
4
|
+
/**
|
|
5
|
+
* Reports `DA-AD-SURFACE-REVEALED` once per ad when its first frame is on screen, with the same
|
|
6
|
+
* fields as Android and iOS. Web shows the surface before the picture unless a pre-roll is held,
|
|
7
|
+
* so that gap is a distinct reason (`shown-before-frame`, `visibleGapMs`) and never `waitedMs`.
|
|
8
|
+
*/
|
|
9
|
+
export declare class SurfaceRevealReporter {
|
|
10
|
+
private readonly diagnose;
|
|
11
|
+
private readonly deadlineMs;
|
|
12
|
+
private readonly now;
|
|
13
|
+
private visibleSince;
|
|
14
|
+
private pending;
|
|
15
|
+
constructor(diagnose: Diagnose, deadlineMs?: number, now?: () => number);
|
|
16
|
+
/** The surface carrying the ad is on screen from now on. */
|
|
17
|
+
surfaceShown(): void;
|
|
18
|
+
surfaceHidden(): void;
|
|
19
|
+
/** An ad starts; `video` draws it, or is passed later through `watchVideo` (IMA's own element). */
|
|
20
|
+
watch(assetId: string, breakId: string, video?: HTMLVideoElement | null): void;
|
|
21
|
+
/** Chrome, Edge and Firefox stop frame callbacks in a hidden tab, so the deadline only runs while it is visible. */
|
|
22
|
+
private armDeadline;
|
|
23
|
+
watchVideo(video: HTMLVideoElement): void;
|
|
24
|
+
cancel(): void;
|
|
25
|
+
/** The surface may be shown after the ad started (a held pre-roll), so hidden and visible time split here. */
|
|
26
|
+
private report;
|
|
27
|
+
}
|
|
28
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const SURFACE_REVEAL_DEADLINE_MS=1e4;export class SurfaceRevealReporter{constructor(e,t=1e4,i=()=>performance.now()){this.diagnose=e,this.deadlineMs=t,this.now=i,this.visibleSince=null,this.pending=null}surfaceShown(){this.visibleSince??(this.visibleSince=this.now())}surfaceHidden(){this.visibleSince=null,this.cancel()}watch(e,t,i){this.cancel();const n={assetId:e,breakId:t,armedAt:this.now(),shownAtStart:null!==this.visibleSince,stops:[]};this.pending=n,n.stops.push(this.armDeadline(n)),i&&this.watchVideo(i)}armDeadline(e){let t=this.deadlineMs,i=0,n=null;const s=()=>{i=this.now(),n=setTimeout(()=>this.report(e,!0),t)},a=()=>{null!==n&&(clearTimeout(n),n=null,t=Math.max(0,t-(this.now()-i)))},r=()=>{"hidden"===document.visibilityState?a():null===n&&s()};return"hidden"!==document.visibilityState&&s(),document.addEventListener("visibilitychange",r),()=>{a(),document.removeEventListener("visibilitychange",r)}}watchVideo(e){const t=this.pending;t&&t.stops.push(function(e,t){if("function"==typeof e.requestVideoFrameCallback){const i=e.requestVideoFrameCallback(()=>t());return()=>e.cancelVideoFrameCallback?.(i)}const i=()=>{e.currentTime<=0||(e.removeEventListener("timeupdate",i),t())};return e.addEventListener("timeupdate",i),()=>e.removeEventListener("timeupdate",i)}(e,()=>this.report(t,!1)))}cancel(){this.pending?.stops.forEach(e=>e()),this.pending=null}report(e,t){if(this.pending!==e)return;this.cancel();const i=this.now(),n=this.visibleSince,s=t?"reveal-timeout":e.shownAtStart?"shown-before-frame":"first-frame",a=Math.round(Math.max(0,(n??i)-e.armedAt)),r={reason:s,waitedMs:a,transitionMs:0,cut:!0,assetId:e.assetId,breakId:e.breakId};let o=`Ad surface revealed after ${a}ms (${s})`;if(null!==n){const t=Math.round(Math.max(0,i-Math.max(e.armedAt,n)));r.visibleGapMs=t,o+=`, ${t}ms on screen without a picture`}this.diagnose("DA-AD-SURFACE-REVEALED",o,r)}}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { LayoutPlacement } from './PresentationLayout';
|
|
2
|
+
/** The stage surfaces a presentation host places: content, ad, and the backdrop surface. */
|
|
3
|
+
export interface PresentationSurfaces {
|
|
4
|
+
content: HTMLElement;
|
|
5
|
+
ad: HTMLElement;
|
|
6
|
+
backdrop: HTMLElement;
|
|
7
|
+
}
|
|
8
|
+
export interface PresentationHost {
|
|
9
|
+
getSurfaces(): PresentationSurfaces | null;
|
|
10
|
+
placeContent(placement: LayoutPlacement): void;
|
|
11
|
+
placeAd(placement: LayoutPlacement): void;
|
|
12
|
+
/** Drops pending transition-end work so it cannot touch elements after restore/destroy. */
|
|
13
|
+
cancelPendingTransitions(): void;
|
|
14
|
+
}
|
|
15
|
+
/** Returns the stage surfaces once all three exist. */
|
|
16
|
+
export declare function toPresentationSurfaces(content: HTMLElement | null | undefined, ad: HTMLElement | null, backdrop: HTMLElement | null): PresentationSurfaces | null;
|
|
17
|
+
/**
|
|
18
|
+
* Places the stage surfaces with normalized transforms. Placements write only `transform`,
|
|
19
|
+
* `transition` and `visibility`: the layout properties (`top`/`left`/`right`/`bottom`/`width`/
|
|
20
|
+
* `height`) belong to the base style and stay untouched so the elements keep their intrinsic size.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createDomPresentationHost(getSurfaces: () => PresentationSurfaces | null): PresentationHost;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{jumpToTransform as t,toCssTransform as n,transitionToTransform as e}from"./LayoutTransform";export function toPresentationSurfaces(t,n,e){return t&&n&&e?{content:t,ad:n,backdrop:e}:null}export function createDomPresentationHost(r){const i=new Map,o=t=>{const n=i.get(t);n&&(t.removeEventListener("transitionend",n),i.delete(t))},s=(r,s)=>{o(r);const a=n(s.transform),c=r.style.transform===a;if("none"===s.transition)return c||t(r,s.transform),void(r.style.visibility=s.visible?"":"hidden");e(r,s.transform),s.visible?r.style.visibility="":c?r.style.visibility="hidden":((t,n)=>{const e=e=>{e.target===t&&"transform"===e.propertyName&&t.style.transform===n&&(o(t),t.style.visibility="hidden")};i.set(t,e),t.addEventListener("transitionend",e)})(r,a)};return{getSurfaces:r,placeContent(t){const n=r();n&&s(n.content,t)},placeAd(t){const n=r();n&&s(n.ad,t)},cancelPendingTransitions(){for(const[t,n]of i)t.removeEventListener("transitionend",n);i.clear()}}}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A placement of a stage surface: the full-size element scaled around its center, then moved by a
|
|
3
|
+
* percentage of its own size. Every placement has the same `translate() scale()` shape, so the
|
|
4
|
+
* browser interpolates between any two of them in a straight line — including when a new placement
|
|
5
|
+
* interrupts a transition still in flight.
|
|
6
|
+
*/
|
|
7
|
+
export interface LayoutTransform {
|
|
8
|
+
/** Horizontal move of the element's center, in percent of the element's width. */
|
|
9
|
+
translateXPercent: number;
|
|
10
|
+
/** Vertical move of the element's center, in percent of the element's height. */
|
|
11
|
+
translateYPercent: number;
|
|
12
|
+
scale: number;
|
|
13
|
+
}
|
|
14
|
+
/** The element fills the stage: no transform at all, so it creates no stacking context. */
|
|
15
|
+
export declare const FULL_SIZE_TRANSFORM: LayoutTransform;
|
|
16
|
+
export declare const LAYOUT_TRANSITION = "transform 0.3s ease-in-out";
|
|
17
|
+
/**
|
|
18
|
+
* Canonical serializer; full size is `''` (not `scale(1)`) to avoid a stacking context.
|
|
19
|
+
* Only this serializer's output may be compared against `element.style.transform`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function toCssTransform(transform: LayoutTransform): string;
|
|
22
|
+
/** Animates the element from where it is shown now to the given placement. */
|
|
23
|
+
export declare function transitionToTransform(el: HTMLElement, transform: LayoutTransform): void;
|
|
24
|
+
/**
|
|
25
|
+
* Puts the element at the given placement right away, without starting a transition.
|
|
26
|
+
* The forced style read applies the placement before the transition is restored.
|
|
27
|
+
*/
|
|
28
|
+
export declare function jumpToTransform(el: HTMLElement, transform: LayoutTransform): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const FULL_SIZE_TRANSFORM={translateXPercent:0,translateYPercent:0,scale:1};export const LAYOUT_TRANSITION="transform 0.3s ease-in-out";export function toCssTransform(t){const{translateXPercent:n,translateYPercent:r,scale:s}=t;return 0===n&&0===r&&1===s?"":`translate(${n}%, ${r}%) scale(${s})`}export function transitionToTransform(t,n){t.style.transition=LAYOUT_TRANSITION,t.style.transform=toCssTransform(n)}export function jumpToTransform(t,n){t.style.transition="none",t.style.transform=toCssTransform(n),getComputedStyle(t).transform,t.style.transition=LAYOUT_TRANSITION}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { BreakFormat } from '../../../types/BreakManifest';
|
|
2
|
+
import { LayoutTransform } from './LayoutTransform';
|
|
3
|
+
/** Where a layout places one stage surface, and whether the move is animated. */
|
|
4
|
+
export interface LayoutPlacement {
|
|
5
|
+
transform: LayoutTransform;
|
|
6
|
+
visible: boolean;
|
|
7
|
+
transition: 'none' | 'animated';
|
|
8
|
+
}
|
|
9
|
+
export interface PresentationLayout {
|
|
10
|
+
content: LayoutPlacement;
|
|
11
|
+
ad: LayoutPlacement;
|
|
12
|
+
}
|
|
13
|
+
/** Content returns to full size, animated — the placement restoreLayout() moves to. */
|
|
14
|
+
export declare const RESTORED_CONTENT_PLACEMENT: LayoutPlacement;
|
|
15
|
+
/**
|
|
16
|
+
* The presentation layout for a break format, or `null` when the format is not a layout state:
|
|
17
|
+
* `overlay` positions its surface from manifest values and unknown formats keep the base style.
|
|
18
|
+
*/
|
|
19
|
+
export declare function presentationLayoutFor(format: BreakFormat): PresentationLayout | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{BreakFormat as t}from"../../../types/BreakManifest";import{FULL_SIZE_TRANSFORM as n}from"./LayoutTransform";export const RESTORED_CONTENT_PLACEMENT={transform:n,visible:!0,transition:"animated"};const e=.4792,r={translateXPercent:-12.5,translateYPercent:-12.5,scale:.65};export function presentationLayoutFor(a){switch(a){case t.SINGLE:return{content:{transform:n,visible:!0,transition:"animated"},ad:{transform:n,visible:!0,transition:"none"}};case t.DOUBLE:return{content:{transform:{translateXPercent:-25,translateYPercent:0,scale:e},visible:!0,transition:"animated"},ad:{transform:{translateXPercent:25,translateYPercent:0,scale:e},visible:!0,transition:"none"}};case t.LSHAPE_AD:return{content:{transform:r,visible:!1,transition:"animated"},ad:{transform:r,visible:!0,transition:"none"}};case t.LSHAPE_CONTENT:return{content:{transform:r,visible:!0,transition:"animated"},ad:{transform:n,visible:!0,transition:"none"}};default:return null}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{OPTIVIEW_ASSET_PARAMETER_MACROS as e,resolveAssetParameterMacro as
|
|
1
|
+
import{OPTIVIEW_ASSET_PARAMETER_MACROS as e,resolveAssetParameterMacro as t}from"./assetParameterMacros";export function buildAssetParameterMacroTable(r){const n={};for(const t of Object.values(e))n[t]=()=>{switch(t){case e.USER_AGENT:return"undefined"!=typeof navigator?navigator.userAgent:void 0;case e.PLAYER_WIDTH:return r.element?String(Math.round(r.element.clientWidth)):void 0;case e.PLAYER_HEIGHT:return r.element?String(Math.round(r.element.clientHeight)):void 0;case e.BREAK_DURATION_MS:return void 0===r.breakDurationSec?void 0:String(Math.round(1e3*r.breakDurationSec));case e.BREAK_DURATION_SECONDS:return void 0===r.breakDurationSec?void 0:String(r.breakDurationSec);default:return}};for(const e of Object.keys(r.customerMacros)){const o=r.customerMacros[e];if(void 0===o)continue;const a=n[e];n[e]=()=>{try{const e=t(o);return""===e||null==e?null:e}catch(t){return console.warn(`[OptiViewAds] Failed to evaluate asset parameter macro '${e}'.`,t),a?.()}}}return n}
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
* `unresolved` (the caller diagnoses `DA-ASSET-MACRO-UNKNOWN`).
|
|
17
17
|
* 3. Any other unresolved token is kept literally, since it may be a vendor
|
|
18
18
|
* macro the ad server itself expands, and is not reported.
|
|
19
|
-
* 4. A macro resolving to `null` omits its parameter
|
|
20
|
-
* the
|
|
19
|
+
* 4. A macro resolving to `null` or `""` omits its parameter. In a value with
|
|
20
|
+
* a literal `&`, only the pair containing the macro is dropped.
|
|
21
21
|
*
|
|
22
22
|
* Matching is case-sensitive. Who fills the macro table is platform glue: the
|
|
23
23
|
* runtime puts the customer's registered macros in front of the built-in
|
|
@@ -33,17 +33,17 @@ export declare const OPTIVIEW_ASSET_PARAMETER_MACROS: {
|
|
|
33
33
|
readonly BREAK_DURATION_SECONDS: '$OPTIVIEW_BREAK_DURATION_SECONDS$';
|
|
34
34
|
};
|
|
35
35
|
export type AssetParameterMacroScope = 'session' | 'break';
|
|
36
|
-
export type AssetParameterTarget = 'gam' | 'vast';
|
|
37
36
|
export declare const OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES: Record<string, AssetParameterMacroScope>;
|
|
38
|
-
/** A macro
|
|
37
|
+
/** A macro replacement: `""`, `null`, or a registered callback's `undefined` result is empty; a core-table `undefined` is unresolved. An undefined customer map entry is unregistered, and `updateAssetParameterMacros` uses it to unregister a name. @category Manifest Customization */
|
|
39
38
|
export type AssetParameterMacroValue = string | null | undefined;
|
|
40
39
|
/**
|
|
41
40
|
* A customer macro: a fixed value, or a callback invoked with no arguments
|
|
42
|
-
* right before the URL is built. `null
|
|
43
|
-
* `undefined`
|
|
41
|
+
* right before the URL is built. `""`, `null`, and a callback result of
|
|
42
|
+
* `undefined` omit parameters that use the macro. An undefined map entry is
|
|
43
|
+
* not registered; in an update it unregisters the name.
|
|
44
44
|
* @category Manifest Customization */
|
|
45
45
|
export type AssetParameterMacro = AssetParameterMacroValue | (() => AssetParameterMacroValue);
|
|
46
|
-
/** Customer-registered macros, keyed by the full token (`'$CUSTOM_MACRO$'`). @category Manifest Customization */
|
|
46
|
+
/** Customer-registered macros, keyed by the full token (`'$CUSTOM_MACRO$'`); an `undefined` entry is not registered. @category Manifest Customization */
|
|
47
47
|
export type AssetParameterMacros = Record<string, AssetParameterMacro>;
|
|
48
48
|
/**
|
|
49
49
|
* Every macro that has a definition, keyed by token, each producing its value
|
|
@@ -60,7 +60,7 @@ export interface AppliedAssetParameters {
|
|
|
60
60
|
params: Record<string, string> | undefined;
|
|
61
61
|
/** Unresolved macros, ordered by sorted parameter key and scan order. */
|
|
62
62
|
unresolved: UnresolvedAssetParameterMacro[];
|
|
63
|
-
/** Omitted parameters, ordered by sorted parameter key, then
|
|
63
|
+
/** Omitted parameters, ordered by sorted parameter key, then pair order. */
|
|
64
64
|
omitted: OmittedAssetParameter[];
|
|
65
65
|
}
|
|
66
66
|
export interface OmittedAssetParameter {
|
|
@@ -68,7 +68,7 @@ export interface OmittedAssetParameter {
|
|
|
68
68
|
macro: string;
|
|
69
69
|
pair?: string;
|
|
70
70
|
}
|
|
71
|
-
export declare function applyAssetParameterMacros(params: Record<string, string> | undefined, macros: AssetParameterMacroTable
|
|
71
|
+
export declare function applyAssetParameterMacros(params: Record<string, string> | undefined, macros: AssetParameterMacroTable): AppliedAssetParameters;
|
|
72
72
|
/**
|
|
73
73
|
* Append `params` to `url` as `key=value` query parameters, before any
|
|
74
74
|
* `#fragment`, reusing an existing `?`. Keys are sorted so the three cores
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const OPTIVIEW_MACRO_PREFIX="$OPTIVIEW_";export const OPTIVIEW_ASSET_PARAMETER_MACROS={USER_AGENT:"$OPTIVIEW_USER_AGENT$",PLAYER_WIDTH:"$OPTIVIEW_PLAYER_WIDTH$",PLAYER_HEIGHT:"$OPTIVIEW_PLAYER_HEIGHT$",BREAK_DURATION_MS:"$OPTIVIEW_BREAK_DURATION_MS$",BREAK_DURATION_SECONDS:"$OPTIVIEW_BREAK_DURATION_SECONDS$"};export const OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES={[OPTIVIEW_ASSET_PARAMETER_MACROS.USER_AGENT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_WIDTH]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_HEIGHT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_MS]:"break",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_SECONDS]:"break"};export function resolveAssetParameterMacro(e){return"function"==typeof e?e():e}export function applyAssetParameterMacros(o,n
|
|
1
|
+
export const OPTIVIEW_MACRO_PREFIX="$OPTIVIEW_";export const OPTIVIEW_ASSET_PARAMETER_MACROS={USER_AGENT:"$OPTIVIEW_USER_AGENT$",PLAYER_WIDTH:"$OPTIVIEW_PLAYER_WIDTH$",PLAYER_HEIGHT:"$OPTIVIEW_PLAYER_HEIGHT$",BREAK_DURATION_MS:"$OPTIVIEW_BREAK_DURATION_MS$",BREAK_DURATION_SECONDS:"$OPTIVIEW_BREAK_DURATION_SECONDS$"};export const OPTIVIEW_ASSET_PARAMETER_MACRO_SCOPES={[OPTIVIEW_ASSET_PARAMETER_MACROS.USER_AGENT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_WIDTH]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.PLAYER_HEIGHT]:"session",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_MS]:"break",[OPTIVIEW_ASSET_PARAMETER_MACROS.BREAK_DURATION_SECONDS]:"break"};export function resolveAssetParameterMacro(e){return"function"==typeof e?e():e}export function applyAssetParameterMacros(o,n){const s=o??{},r={},E=[],i=[];for(const o of Object.keys(s).sort()){if(s[o].includes("&")){const e=t(s[o],n);void 0!==e.value&&(r[o]=e.value),i.push(...e.omitted.map(({macro:e,pair:t})=>({key:o,macro:e,pair:t}))),E.push(...e.unresolved.map(e=>({key:o,macro:e})));continue}const A=e(s[o],n);void 0===A.omittedBy?(r[o]=A.value,E.push(...A.unresolved.map(e=>({key:o,macro:e})))):i.push({key:o,macro:A.omittedBy})}return{params:Object.keys(r).length>0?r:void 0,unresolved:E,omitted:i}}function e(e,t){let o="",s=0;const r=[],E=new Set;for(;s<e.length;){const i=e.indexOf("$",s);if(-1===i){o+=e.slice(s);break}o+=e.slice(s,i);const A=n(e,i);if(void 0===A){o+="$",s=i+1;continue}const _=t[A]?.();if(null===_||""===_)return{value:"",unresolved:[],omittedBy:A};void 0!==_?o+=_:(o+=A,!A.startsWith("$OPTIVIEW_")&&!(A in t)||E.has(A)||(r.push(A),E.add(A))),s=i+A.length}return{value:o,unresolved:r}}function t(t,o){const n=[],s=[],r=new Set,E=[];for(const i of t.split("&")){const t=e(i,o);if(void 0===t.omittedBy){n.push(t.value);for(const e of t.unresolved)r.has(e)||(r.add(e),s.push(e))}else E.push({macro:t.omittedBy,pair:i})}return{value:n.length>0?n.join("&"):void 0,unresolved:s,omitted:E}}function o(e){return e>="A"&&e<="Z"||e>="a"&&e<="z"||e>="0"&&e<="9"||"_"===e}function n(e,t){let n=t+1;for(;n<e.length&&o(e[n]);)n++;return n>t+1&&"$"===e[n]?e.slice(t,n+1):void 0}export function appendQueryParameters(e,t){const o=t??{},n=Object.keys(o).sort();if(0===n.length)return e;if(/^data:/i.test(e))return e;const s=n.map(e=>`${encodeURIComponent(e)}=${encodeURIComponent(o[e])}`).join("&"),r=e.indexOf("#"),E=-1===r?e:e.slice(0,r),i=-1===r?"":e.slice(r),A=E.includes("?")?E.endsWith("?")||E.endsWith("&")?"":"&":"?";return`${E}${A}${s}${i}`}
|
|
@@ -9,7 +9,7 @@ export interface ChannelSwitch {
|
|
|
9
9
|
previousChannelId: string | null;
|
|
10
10
|
channelId: string | null;
|
|
11
11
|
}
|
|
12
|
-
export type DeliveryModeSource = 'manifest' | 'channel-switch' | 'config';
|
|
12
|
+
export type DeliveryModeSource = 'manifest' | 'channel-switch' | 'event-boundary' | 'config';
|
|
13
13
|
export interface DeliveryModeDecision {
|
|
14
14
|
/** Mode the manifest rules resolve to before the explicit config mode is applied. */
|
|
15
15
|
manifestMode: OptiViewAdsMode;
|
|
@@ -20,7 +20,7 @@ export interface DeliveryModeDecision {
|
|
|
20
20
|
targetingIssues: TargetingIssue[];
|
|
21
21
|
source: DeliveryModeSource;
|
|
22
22
|
}
|
|
23
|
-
export declare function decideDeliveryMode(manifest: BreakManifest, deviceProfile: DeviceType | DeviceProfile, explicitMode: OptiViewAdsMode | null, source:
|
|
23
|
+
export declare function decideDeliveryMode(manifest: BreakManifest, deviceProfile: DeviceType | DeviceProfile, explicitMode: OptiViewAdsMode | null, source: DeliveryModeSource): DeliveryModeDecision;
|
|
24
24
|
/** Blank ids count as absent: the backend omits the field, a proxy may emit `""`. */
|
|
25
25
|
export declare function normalizeChannelId(channelId: string | undefined): string | null;
|
|
26
26
|
/** True when the new manifest needs a new IMA session (different network or custom asset key). */
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { KnownDiagnosticCode } from '../diagnostics/errorCodes';
|
|
2
|
+
/** Reports an `externalplaybackchange` payload as `DA-EXTERNAL-PLAYBACK-CHANGED`. */
|
|
3
|
+
export declare function diagnoseExternalPlaybackChange(change: unknown, diagnose: (code: KnownDiagnosticCode, message: string, context?: Record<string, unknown>) => void): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function diagnoseExternalPlaybackChange(a,t){const{state:e,target:n}=a??{};t("DA-EXTERNAL-PLAYBACK-CHANGED",`External playback ${e??"changed"}`,{state:e,target:n})}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** What the guard needs to know about the break timeline at the moment AirPlay is requested. */
|
|
2
|
+
export interface ExternalPlaybackGuardInput {
|
|
3
|
+
/** The break that is playing now, or null. */
|
|
4
|
+
activeBreakId: string | null;
|
|
5
|
+
/** The nearest scheduled break that has not started, or null. */
|
|
6
|
+
nextBreakId: string | null;
|
|
7
|
+
/** Seconds until `nextBreakId` starts (negative once its start has passed), or null when unknown. */
|
|
8
|
+
secondsUntilNextBreak: number | null;
|
|
9
|
+
/** Window before a break in which AirPlay may not start; already sanitised (not negative). */
|
|
10
|
+
blockBeforeBreakSec: number;
|
|
11
|
+
}
|
|
12
|
+
export type ExternalPlaybackBlockReason = 'break-active' | 'break-imminent';
|
|
13
|
+
export type ExternalPlaybackGuardDecision = {
|
|
14
|
+
allowed: true;
|
|
15
|
+
} | {
|
|
16
|
+
allowed: false;
|
|
17
|
+
reason: ExternalPlaybackBlockReason;
|
|
18
|
+
breakId: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Whether the content player may start AirPlay now: not while a break plays, and not within
|
|
22
|
+
* `blockBeforeBreakSec` of the next one (inclusive, so a window of 0 blocks only a break that is due).
|
|
23
|
+
*/
|
|
24
|
+
export declare function decideExternalPlaybackGuard(input: ExternalPlaybackGuardInput): ExternalPlaybackGuardDecision;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function decideExternalPlaybackGuard(e){return null!==e.activeBreakId?{allowed:!1,reason:"break-active",breakId:e.activeBreakId}:null!==e.nextBreakId&&null!==e.secondsUntilNextBreak&&e.secondsUntilNextBreak<=e.blockBeforeBreakSec?{allowed:!1,reason:"break-imminent",breakId:e.nextBreakId}:{allowed:!0}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BreakManifest } from '../types/BreakManifest';
|
|
2
|
+
import type { ChannelEventIssue } from './resolveChannelEvents';
|
|
2
3
|
/**
|
|
3
4
|
* Supported major version of the break manifest.
|
|
4
5
|
*/
|
|
@@ -12,4 +13,4 @@ export declare const SUPPORTED_MANIFEST_MAJOR_VERSION = 1;
|
|
|
12
13
|
*
|
|
13
14
|
* @throws Error when the data is not a valid v1 break manifest.
|
|
14
15
|
*/
|
|
15
|
-
export declare function parseBreakManifest(data: unknown): BreakManifest;
|
|
16
|
+
export declare function parseBreakManifest(data: unknown, onEventIssue?: (issue: ChannelEventIssue) => void): BreakManifest;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const SUPPORTED_MANIFEST_MAJOR_VERSION=1;export function parseBreakManifest(
|
|
1
|
+
import{parseChannelEvents as e}from"./resolveChannelEvents";export const SUPPORTED_MANIFEST_MAJOR_VERSION=1;export function parseBreakManifest(t,n){if(!t||"object"!=typeof t)throw new Error("Invalid manifest: expected object");const i=t;if("string"!=typeof i.version)throw new Error("Invalid manifest: missing version");if(1!==parseInt(i.version.split(".")[0],10))throw new Error(`Unsupported manifest version: ${i.version}. Supported major version: 1`);const o=i.timebase;if("wallclock"!==o&&"pts"!==o&&"mediatime"!==o)throw new Error("Invalid manifest: invalid or missing timebase");if(!Array.isArray(i.breaks))throw new Error("Invalid manifest: breaks must be an array");for(const e of i.breaks)r(e);if(void 0!==i.delivery){if(!Array.isArray(i.delivery)||0===i.delivery.length)throw new Error("Invalid manifest: delivery must be a non-empty array");for(const e of i.delivery)if(!e||"object"!=typeof e||"string"!=typeof e.mode)throw new Error("Invalid manifest: each delivery rule must have a string mode")}if(void 0!==i.vendorConfiguration&&!Array.isArray(i.vendorConfiguration))throw new Error("Invalid manifest: vendorConfiguration must be an array");if(void 0!==i.adStartDelay&&("number"!=typeof i.adStartDelay||!Number.isFinite(i.adStartDelay)||i.adStartDelay<0))throw new Error("Invalid manifest: adStartDelay must be a number >= 0");const a=i,s=e(i.events,o);s.issues.forEach(e=>n?.(e));const f={...a};return delete f.events,{...f,...void 0===s.events?{}:{events:s.events}}}function r(e){if(!e||"object"!=typeof e)return;const r=e.start;if(!r||"object"!=typeof r)return;const t=r;if("event"!==t.type||"string"!=typeof t.event)throw new Error('Invalid manifest: break start object must be { type: "event", event: string }');if(void 0!==t.delay&&("number"!=typeof t.delay||!Number.isFinite(t.delay)||t.delay<0))throw new Error("Invalid manifest: event trigger delay must be a number >= 0")}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Timebase } from '../types/BreakManifest';
|
|
2
|
+
import type { BreakManifest, ChannelEvent } from '../types/BreakManifest';
|
|
3
|
+
export interface ChannelEventIssue {
|
|
4
|
+
key: string;
|
|
5
|
+
message: string;
|
|
6
|
+
context: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseChannelEvents(raw: unknown, timebase: Timebase): {
|
|
9
|
+
events: ChannelEvent[] | undefined;
|
|
10
|
+
issues: ChannelEventIssue[];
|
|
11
|
+
};
|
|
12
|
+
export declare function resolveActiveEvent(manifest: BreakManifest, positionMs: number): ChannelEvent | null;
|
|
13
|
+
export declare function resolveEventManifest(manifest: BreakManifest, positionMs: number): {
|
|
14
|
+
manifest: BreakManifest;
|
|
15
|
+
eventId: string | null;
|
|
16
|
+
};
|
|
17
|
+
export declare function nextEventBoundaryMs(manifest: BreakManifest, positionMs: number): number | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{Timebase as e}from"../types/BreakManifest";const t=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;function n(e){if("string"!=typeof e||!t.test(e))return null;const n=Date.parse(e);return Number.isFinite(n)?n:null}export function parseChannelEvents(t,i){if(void 0===t)return{events:void 0,issues:[]};if(i!==e.WALLCLOCK)return{events:void 0,issues:[{key:"non-wallclock",message:"Ignored events because the manifest timebase is not wallclock.",context:{timebase:i}}]};if(!Array.isArray(t))return{events:void 0,issues:[{key:"not-array",message:"Ignored events because the value is not an array.",context:{}}]};const r=[],a=[];return t.forEach((e,t)=>{const i=e&&"object"==typeof e?e:{},o="string"==typeof i.id&&i.id.trim()?i.id:null,d=(e,n)=>{a.push({key:`${o??t}:${e}`,message:n,context:{...o?{eventId:o}:{},index:t}})};if(!o)return void d("missing-id","Ignored event without a non-empty id.");const s=n(i.start),l=n(i.end);null!==s&&null!==l?l<=s?d("invalid-range","Ignored event whose end is not after its start."):void 0===i.adStartDelay||"number"==typeof i.adStartDelay&&Number.isFinite(i.adStartDelay)&&!(i.adStartDelay<0)?void 0===i.delivery||Array.isArray(i.delivery)&&0!==i.delivery.length&&!i.delivery.some(e=>!e||"object"!=typeof e||"string"!=typeof e.mode)?void 0===i.vendorConfiguration||Array.isArray(i.vendorConfiguration)?r.push(i):d("invalid-vendor-configuration","Ignored event with invalid vendorConfiguration."):d("invalid-delivery","Ignored event with invalid delivery rules."):d("invalid-ad-start-delay","Ignored event with an invalid adStartDelay."):d("invalid-time","Ignored event with an invalid start or end timestamp.")}),{events:r,issues:a}}export function resolveActiveEvent(t,i){return t.timebase===e.WALLCLOCK&&Number.isFinite(i)?t.events?.filter(e=>{const t=n(e.start),r=n(e.end);return null!==t&&null!==r&&r>t&&t<=i&&i<r}).at(-1)??null:null}export function resolveEventManifest(e,t){const n=resolveActiveEvent(e,t),i=void 0===e.events?e.eventId??null:n?.id??null;if(!n){const t={...e};return delete t.eventId,{manifest:void 0===e.events?e:t,eventId:i}}const r=n.adStartDelay??e.adStartDelay,a=n.delivery??e.delivery,o=n.vendorConfiguration??e.vendorConfiguration;return{manifest:{...e,eventId:i,...void 0===r?{}:{adStartDelay:r},...void 0===a?{}:{delivery:a},...void 0===o?{}:{vendorConfiguration:o}},eventId:i}}export function nextEventBoundaryMs(t,i){if(t.timebase!==e.WALLCLOCK||!Number.isFinite(i))return null;const r=(t.events??[]).flatMap(e=>[n(e.start),n(e.end)]).filter(e=>null!==e&&e>i);return r.length?Math.min(...r):null}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyAssetParameterMacros as r}from"./assetParameterMacros";import{objectEntries as e,objectFromEntries as t}from"./runtimeObject";export function changedExternalVendorProviders(r,e){const t=new Map(r.map(r=>[r.provider,r])),n=new Set(e.map(r=>r.provider)),o=e.filter(r=>{const e=t.get(r.provider);return void 0===e||!function(r,e){const t=Object.keys(r);return t.length===Object.keys(e).length&&t.every(t=>Object.prototype.hasOwnProperty.call(e,t)&&r[t]===e[t])}(e.assetParameters,r.assetParameters)}).map(r=>r.provider);for(const e of r)n.has(e.provider)||o.push(e.provider);return o}function n(r){if("object"!=typeof r||null===r||Array.isArray(r))return!1;const e=Object.getPrototypeOf(r);return e===Object.prototype||null===e}function o(r){return"string"==typeof r&&""!==r.trim()}function s(r){return n(r)?t(e(r).filter(r=>"string"==typeof r[1])):null}export function resolveExternalVendorConfigurations(r){const e=r.vendorConfiguration,t=[],i=[],a=new Set;let u=0;if(!Array.isArray(e))return{entries:t,duplicateProviders:i,invalidCount:u};for(const r of e){if(!n(r)||"external"!==r.type)continue;const e=s(r.assetParameters);if(!o(r.provider)||null===e){u+=1;continue}const p=r.provider;a.has(p)?i.includes(p)||i.push(p):(a.add(p),t.push({provider:p,assetParameters:e}))}return{entries:t,duplicateProviders:i,invalidCount:u}}export function resolveExternalVendorParameters(e,t,n){const o={...t??{},...e.assetParameters},s=r(o,n
|
|
1
|
+
import{applyAssetParameterMacros as r}from"./assetParameterMacros";import{objectEntries as e,objectFromEntries as t}from"./runtimeObject";export function changedExternalVendorProviders(r,e){const t=new Map(r.map(r=>[r.provider,r])),n=new Set(e.map(r=>r.provider)),o=e.filter(r=>{const e=t.get(r.provider);return void 0===e||!function(r,e){const t=Object.keys(r);return t.length===Object.keys(e).length&&t.every(t=>Object.prototype.hasOwnProperty.call(e,t)&&r[t]===e[t])}(e.assetParameters,r.assetParameters)}).map(r=>r.provider);for(const e of r)n.has(e.provider)||o.push(e.provider);return o}function n(r){if("object"!=typeof r||null===r||Array.isArray(r))return!1;const e=Object.getPrototypeOf(r);return e===Object.prototype||null===e}function o(r){return"string"==typeof r&&""!==r.trim()}function s(r){return n(r)?t(e(r).filter(r=>"string"==typeof r[1])):null}export function resolveExternalVendorConfigurations(r){const e=r.vendorConfiguration,t=[],i=[],a=new Set;let u=0;if(!Array.isArray(e))return{entries:t,duplicateProviders:i,invalidCount:u};for(const r of e){if(!n(r)||"external"!==r.type)continue;const e=s(r.assetParameters);if(!o(r.provider)||null===e){u+=1;continue}const p=r.provider;a.has(p)?i.includes(p)||i.push(p):(a.add(p),t.push({provider:p,assetParameters:e}))}return{entries:t,duplicateProviders:i,invalidCount:u}}export function resolveExternalVendorParameters(e,t,n){const o={...t??{},...e.assetParameters},s=r(o,n);return{configuration:{provider:e.provider,assetParameters:s.params??{}},unresolved:s.unresolved,omitted:s.omitted}}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BreakManifest } from '../types/BreakManifest';
|
|
1
|
+
import type { BreakManifest, GamSsbConfiguration, VendorConfiguration } from '../types/BreakManifest';
|
|
2
2
|
/**
|
|
3
3
|
* GAM stream identity for the IMA/DAI session. Both required: a blank value yields a pod URL like
|
|
4
4
|
* `.../network//custom_asset/...` whose 404 reads as a GAM outage instead of a config error.
|
|
@@ -32,6 +32,11 @@ export interface GamVendorConfigurationResult {
|
|
|
32
32
|
* per type (FIRST `gam` wins, duplicate reported), unknown types ignored and collected.
|
|
33
33
|
*/
|
|
34
34
|
export declare function resolveGamVendorConfiguration(manifest: Pick<BreakManifest, 'vendorConfiguration'>): GamVendorConfigurationResult;
|
|
35
|
+
/**
|
|
36
|
+
* The channel's SSB stream: the first `ssai[]` item of the first `gam` entry whose `ssb` carries a
|
|
37
|
+
* non-blank `uri`. An `ssb` without a usable `uri` counts as absent. `drm` is kept as an opaque object.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveSsbStream(vendorConfiguration: VendorConfiguration[] | undefined): GamSsbConfiguration | null;
|
|
35
40
|
/** One field on which a pod asset disagrees with the session it would be served under. */
|
|
36
41
|
export interface GamPodIdentityMismatch {
|
|
37
42
|
field: 'networkCode' | 'customAssetKey';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{objectEntries as e,objectFromEntries as
|
|
1
|
+
import{objectEntries as e,objectFromEntries as t}from"./runtimeObject";function s(e){return"string"==typeof e&&""!==e.trim()?e:null}export function resolveGamVendorConfiguration(e){const t=e.vendorConfiguration,n={identity:null,unknownTypes:[],duplicateGam:!1,sgaiAssetParameters:void 0,ssai:null};if(!Array.isArray(t))return{...n,reason:"absent"};const o=[];let a=null,i=0;for(const e of t){if("object"!=typeof e||null===e)continue;const t=e,n=s(t.type);null!==n&&("gam"!==n?"external"!==n&&(o.includes(n)||o.push(n)):(i+=1,null===a&&(a=t)))}const u=i>1;if(null===a)return{...n,reason:"absent",unknownTypes:o,duplicateGam:u};const l=s(a.networkCode);if(null===l)return{...n,reason:"missing-network-code",unknownTypes:o,duplicateGam:u};const c=Array.isArray(a.ssai)?a.ssai[0]:void 0,f=("object"==typeof c&&null!==c?s(c.assetKey):null)??s(a.assetKey),y=f?{networkCode:l,assetKey:f,assetParameters:r("object"==typeof c&&null!==c?c.assetParameters:void 0)}:null,m=Array.isArray(a.sgai)?a.sgai[0]:void 0,d=("object"==typeof m&&null!==m?s(m.customAssetKey):null)??s(a.customAssetKey);if(null===d)return{...n,reason:"missing-custom-asset-key",unknownTypes:o,duplicateGam:u,ssai:y};return{identity:{networkCode:l,customAssetKey:d},reason:"ok",unknownTypes:o,duplicateGam:u,sgaiAssetParameters:r("object"==typeof m&&null!==m?m.assetParameters:void 0),ssai:y}}export function resolveSsbStream(e){if(!Array.isArray(e))return null;const t=e.find(e=>"object"==typeof e&&null!==e&&"gam"===e.type);if(!t||!Array.isArray(t.ssai))return null;for(const e of t.ssai){const t=n(e)?e.ssb:void 0;if(!n(t))continue;const o=s(t.uri);if(null===o)continue;const a=s(t.format),i=r(t.assetParameters);return{uri:o,...null!==a?{format:a}:{},...i?{assetParameters:i}:{},...n(t.drm)?{drm:t.drm}:{}}}return null}function n(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function r(s){if("object"!=typeof s||null===s||Array.isArray(s))return;const n=t(e(s).filter(e=>"string"==typeof e[1]));return Object.keys(n).length>0?n:void 0}export function gamPodIdentityMismatches(e,t){const s=[];return t.networkCode!==e.networkCode&&s.push({field:"networkCode",assetValue:t.networkCode,sessionValue:e.networkCode}),t.customAssetKey!==e.customAssetKey&&s.push({field:"customAssetKey",assetValue:t.customAssetKey,sessionValue:e.customAssetKey}),s}export function manifestHasGamAssets(e){for(const t of e.breaks??[]){const e=[...t.variants??[],...t.variant?[t.variant]:[]];for(const t of e)for(const e of t?.assets??[])if("gam"===e?.vendor)return!0}return!1}
|