@dolby-optiview/ads-sdk-core 1.2.1 → 1.3.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/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 +45 -5
- 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 +6 -4
- 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 +8 -61
- 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 +2 -0
- package/dist/services/adPlayer/AdPlayerEvents.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 +6 -2
- package/dist/services/adPlayer/AssetPlayback.js +1 -1
- package/dist/services/adPlayer/BreakSequenceDriver.d.ts +9 -3
- package/dist/services/adPlayer/BreakSequenceDriver.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/SuccessorQueue.d.ts +36 -0
- package/dist/services/adPlayer/SuccessorQueue.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 +34 -2
- package/dist/types/Events.d.ts +20 -10
- 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)))}})}}
|
|
@@ -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,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}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GamSsbConfiguration } from '../types/BreakManifest';
|
|
2
|
+
import { type AssetParameterMacroTable } from './assetParameterMacros';
|
|
3
|
+
export interface SsbStreamParameterLayers {
|
|
4
|
+
/** `ssb.assetParameters`; the highest layer, as the manifest outranks the client everywhere. */
|
|
5
|
+
manifest?: Record<string, string> | undefined;
|
|
6
|
+
/** `SessionConfig.assetParameters`; the lowest layer. */
|
|
7
|
+
session?: Record<string, string> | undefined;
|
|
8
|
+
/** `updateAssetParameters()` overrides. */
|
|
9
|
+
runtime?: Record<string, string> | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The SSB stream request URL: `ssb.uri` plus the merged asset parameters as query parameters.
|
|
13
|
+
* Layers merge session < runtime < manifest per key (see `resolveAssetParameters`), macros resolve like every other asset
|
|
14
|
+
* parameter (an empty or null macro drops the parameter), and keys are sorted by
|
|
15
|
+
* {@link appendQueryParameters}, which also keeps an existing query and any fragment.
|
|
16
|
+
*/
|
|
17
|
+
export declare function buildSsbStreamUrl(ssb: Pick<GamSsbConfiguration, 'uri'>, layers: SsbStreamParameterLayers, macros: AssetParameterMacroTable): string;
|
|
18
|
+
/** Where the previous source resumes after the SSB stream: the live edge (`null`) or the clamped position. */
|
|
19
|
+
export declare function resolveSsbRestorePosition(input: {
|
|
20
|
+
live: boolean;
|
|
21
|
+
recordedPosition: number;
|
|
22
|
+
duration: number | null;
|
|
23
|
+
}): number | null;
|
|
24
|
+
/** Ids of the breaks whose start passed while the SSB stream played, in input order. */
|
|
25
|
+
export declare function selectBreaksPlayedBySsb(pending: Array<{
|
|
26
|
+
id: string;
|
|
27
|
+
startTime: number;
|
|
28
|
+
}>, schedulerNow: number): string[];
|
|
29
|
+
/** Skips the tracked breaks whose start (non-null) passed `now`; returns their ids. */
|
|
30
|
+
export declare function skipBreaksStartedBy<T extends {
|
|
31
|
+
break: {
|
|
32
|
+
id: string;
|
|
33
|
+
};
|
|
34
|
+
}>(now: number | null, tracked: T[], startTime: (entry: T) => number | null, skip: (entry: T) => void): string[];
|
|
35
|
+
/** The break with the fewest seconds until it (ties: smaller id); breaks without a known time are ignored. */
|
|
36
|
+
export declare function soonestBreak(ids: string[], secondsUntil: (id: string) => number | null): {
|
|
37
|
+
id: string;
|
|
38
|
+
secondsUntil: number;
|
|
39
|
+
} | null;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{appendQueryParameters as e,applyAssetParameterMacros as t}from"./assetParameterMacros";import{resolveAssetParameters as r}from"./resolveAssetParameters";export function buildSsbStreamUrl(n,o,s){const i=r(o.session,o.runtime,o.manifest,void 0);return e(n.uri,t(i,s).params)}export function resolveSsbRestorePosition(e){return e.live?null:Math.max(0,Math.min(e.recordedPosition,e.duration??e.recordedPosition))}export function selectBreaksPlayedBySsb(e,t){return e.filter(e=>e.startTime<=t).map(e=>e.id)}export function skipBreaksStartedBy(e,t,r,n){if(null===e)return[];const o=t.flatMap(e=>{const t=r(e);return null===t?[]:[{id:e.break.id,startTime:t,entry:e}]}),s=new Set(selectBreaksPlayedBySsb(o,e)),i=o.filter(e=>s.has(e.id));for(const e of i)n(e.entry);return i.map(e=>e.id)}export function soonestBreak(e,t){let r=null;for(const n of e){const e=t(n);null!==e&&((null===r||e<r.secondsUntil||e===r.secondsUntil&&n<r.id)&&(r={id:n,secondsUntil:e}))}return r}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type SsbSwitchPhase = 'switching' | 'active' | 'restoring' | 'failed';
|
|
2
|
+
/** The `adchannelchange` due when the SSB stream is left: only after the switch was announced. */
|
|
3
|
+
export declare function resolveSsbRestoreChange(announced: boolean, restoredMode: 'sgai' | 'ssai'): {
|
|
4
|
+
previousDeliveryMode: 'ssb';
|
|
5
|
+
deliveryMode: 'sgai' | 'ssai';
|
|
6
|
+
} | null;
|
|
7
|
+
/** `adbreakstatus.externalPlaybackAllowed`: absent when the gate is unmanaged, always true in `ssb`, otherwise the guard answer outside an active break. */
|
|
8
|
+
export declare function resolveStatusExternalPlaybackAllowed(gateAllowed: boolean | undefined, statusPhase: 'idle' | 'upcoming' | 'active' | 'complete', deliveryMode: 'sgai' | 'ssai' | 'ssb' | undefined): boolean | undefined;
|
|
9
|
+
export type ExternalPlaybackRequestTrigger = 'connecting' | 'active';
|
|
10
|
+
/** Which gate evaluation an `externalplaybackchange` is: `connecting` always; `active` only when the
|
|
11
|
+
* route was `inactive` before and no blocked request is pending (THEOplayer reports connected without
|
|
12
|
+
* connecting). Null for every other change. */
|
|
13
|
+
export declare function resolveExternalPlaybackRequestTrigger(state: string, previousState: string, pending: boolean): ExternalPlaybackRequestTrigger | null;
|
|
14
|
+
/** A gate request is evaluated only while no SSB switch owns the route. */
|
|
15
|
+
export declare function shouldEvaluateExternalPlaybackRequest(ssbPhase: SsbSwitchPhase | null): boolean;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function resolveSsbRestoreChange(e,t){return e?{previousDeliveryMode:"ssb",deliveryMode:t}:null}export function resolveStatusExternalPlaybackAllowed(e,t,n){if(void 0!==e)return"ssb"===n||e&&"active"!==t}export function resolveExternalPlaybackRequestTrigger(e,t,n){return"connecting"===e?"connecting":"active"!==e||"inactive"!==t||n?null:"active"}export function shouldEvaluateExternalPlaybackRequest(e){return null===e}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AppliedAssetParameters, AssetParameterMacroTable } from './assetParameterMacros';
|
|
2
|
+
/** The layers that apply to a static asset request, lowest precedence first. */
|
|
3
|
+
export interface StaticAssetParameterLayers {
|
|
4
|
+
/** `SessionConfig.assetParameters`. */
|
|
5
|
+
sessionConfigParameters?: Record<string, string> | undefined;
|
|
6
|
+
/** What `updateAssetParameters()` last set. */
|
|
7
|
+
updateParameters?: Record<string, string> | undefined;
|
|
8
|
+
/** The static asset's own manifest `assetParameters`. */
|
|
9
|
+
assetParameters?: Record<string, string> | undefined;
|
|
10
|
+
}
|
|
11
|
+
export interface StaticAssetUrl {
|
|
12
|
+
/** The URL to request; the input URL unchanged when no parameter is left to send. */
|
|
13
|
+
url: string;
|
|
14
|
+
/** The substituted parameters and the macros to diagnose. */
|
|
15
|
+
applied: AppliedAssetParameters;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* The request URL of a static asset (resource URL, master playlist or MPD), as a pure
|
|
19
|
+
* function shared by behavioural contract with the Kotlin and Swift cores and locked
|
|
20
|
+
* by the `static-asset-parameters` conformance fixture.
|
|
21
|
+
*
|
|
22
|
+
* The application layers and the asset's own `assetParameters` are merged per key
|
|
23
|
+
* (asset wins); manifest session-level parameters (`vendorConfiguration` gam
|
|
24
|
+
* `sgai`/`ssai`) only target GAM and never apply here. Macros are substituted with
|
|
25
|
+
* the result is appended with {@link appendQueryParameters}.
|
|
26
|
+
* A `data:` URL is returned as-is without evaluating any macro.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildStaticAssetUrl(url: string, layers: StaticAssetParameterLayers, macros: AssetParameterMacroTable): StaticAssetUrl;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{appendQueryParameters as e,applyAssetParameterMacros as r}from"./assetParameterMacros";import{resolveAssetParameters as t}from"./resolveAssetParameters";const a={params:void 0,unresolved:[],omitted:[]};export function buildStaticAssetUrl(s,o,i){if(/^data:/i.test(s))return{url:s,applied:a};const m=t(o.sessionConfigParameters,o.updateParameters,void 0,o.assetParameters),p=r(m,i);return{url:e(s,p.params),applied:p}}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Asset, Break, BreakFormat } from '../types/BreakManifest';
|
|
2
|
+
import type { SessionDeliveryMode } from '../types/OptiViewAdsConfig';
|
|
2
3
|
import { AdBreakStatus, OptiViewAdsEventMap, OptiViewAdsEventType } from '../types/Events';
|
|
3
4
|
import { BreakTransitionTimer } from '../services/BreakTransitionTimer';
|
|
4
5
|
export type EmitEvent = <T extends OptiViewAdsEventType>(event: T, data: OptiViewAdsEventMap[T]) => void;
|
|
@@ -6,6 +7,10 @@ export interface AdBreakStatusTrackerDeps {
|
|
|
6
7
|
emit: EmitEvent;
|
|
7
8
|
effectiveFormatOf: (breakInfo: Break) => BreakFormat | undefined;
|
|
8
9
|
secondsUntilBreak: (breakId: string) => number | undefined;
|
|
10
|
+
/** Whether AirPlay may start now; undefined when the SDK does not manage the gate. */
|
|
11
|
+
externalPlaybackAllowed: () => boolean | undefined;
|
|
12
|
+
/** The active session's delivery mode; undefined between sessions. */
|
|
13
|
+
deliveryMode: () => SessionDeliveryMode | undefined;
|
|
9
14
|
breakTransitionTimer: () => BreakTransitionTimer | undefined;
|
|
10
15
|
}
|
|
11
16
|
/** Playback-gated `AdBreakStatus` state machine behind `getAdBreakStatus()` and the `adbreakstatus` event. */
|
|
@@ -26,9 +31,22 @@ export declare class AdBreakStatusTracker {
|
|
|
26
31
|
currentAdDuration: number | null;
|
|
27
32
|
activeBreakWaiting: boolean;
|
|
28
33
|
activeBreakPlaying: boolean;
|
|
34
|
+
/** The `externalPlaybackAllowed` value last attached to a status; undefined before the first. */
|
|
35
|
+
private lastExternalPlaybackAllowed;
|
|
36
|
+
private lastDeliveryMode;
|
|
29
37
|
constructor(deps: AdBreakStatusTrackerDeps);
|
|
30
38
|
/** Current playback-gated ad break status; for polling when an `adbreakstatus` event was missed. */
|
|
31
39
|
getAdBreakStatus(): AdBreakStatus;
|
|
40
|
+
/**
|
|
41
|
+
* Emits `adbreakstatus` when the AirPlay guard flips so the app can grey out its AirPlay button;
|
|
42
|
+
* the baseline before the first status is `true`. No-op while the SDK does not manage the gate.
|
|
43
|
+
*/
|
|
44
|
+
refreshExternalPlayback(): void;
|
|
45
|
+
/** Emits `adbreakstatus` after a delivery-mode switch or restore so the app sees the new mode. */
|
|
46
|
+
refreshDeliveryMode(): void;
|
|
47
|
+
private liveAdBreakStatus;
|
|
48
|
+
/** Adds `externalPlaybackAllowed` when the SDK manages the AirPlay gate; an active break never allows it. */
|
|
49
|
+
private withExternalPlayback;
|
|
32
50
|
/** Pre-break warning: emits `adbreakstatus` with `phase: 'upcoming'`. */
|
|
33
51
|
onBreakWarning(breakInfo: Break, warningSec: number, secondsUntil: number): void;
|
|
34
52
|
/** Promote a break to `phase: 'active'`; `ticking` starts `false` until playback events toggle it. */
|