@dolby-optiview/ads-sdk-core 1.4.0 → 1.6.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.
Files changed (119) hide show
  1. package/dist/OptiViewAds.d.ts +5 -0
  2. package/dist/OptiViewAds.js +1 -1
  3. package/dist/diagnostics/dispatchDiagnostic.d.ts +4 -0
  4. package/dist/diagnostics/dispatchDiagnostic.js +1 -0
  5. package/dist/generated/diagnosticCodes.d.ts +118 -3
  6. package/dist/generated/diagnosticCodes.js +1 -1
  7. package/dist/generated/version.d.ts +1 -1
  8. package/dist/generated/version.js +1 -1
  9. package/dist/index.d.ts +11 -4
  10. package/dist/index.js +1 -1
  11. package/dist/interfaces/PlayerAdapter.d.ts +27 -0
  12. package/dist/server/index.d.ts +2 -2
  13. package/dist/server/index.js +1 -1
  14. package/dist/services/AdBreakSequencer.d.ts +67 -3
  15. package/dist/services/AdBreakSequencer.js +1 -1
  16. package/dist/services/AdPlayerController.d.ts +9 -4
  17. package/dist/services/AdPlayerController.js +1 -1
  18. package/dist/services/BookmarkStart.d.ts +12 -0
  19. package/dist/services/BookmarkStart.js +1 -0
  20. package/dist/services/BreakScheduler.d.ts +15 -50
  21. package/dist/services/BreakScheduler.js +1 -1
  22. package/dist/services/BreakSchedulerTypes.d.ts +11 -1
  23. package/dist/services/BreakWarningTracker.d.ts +6 -0
  24. package/dist/services/BreakWarningTracker.js +1 -0
  25. package/dist/services/ContentEndTracker.d.ts +17 -0
  26. package/dist/services/ContentEndTracker.js +1 -0
  27. package/dist/services/ExternalPlaybackGate.d.ts +2 -2
  28. package/dist/services/ManifestService.d.ts +1 -1
  29. package/dist/services/ManifestService.js +1 -1
  30. package/dist/services/PauseAdController.d.ts +9 -5
  31. package/dist/services/PauseAdController.js +1 -1
  32. package/dist/services/PlaybackCrossing.d.ts +4 -0
  33. package/dist/services/PlaybackCrossing.js +1 -0
  34. package/dist/services/RewindRearm.js +1 -1
  35. package/dist/services/SchedulerSnapback.d.ts +33 -0
  36. package/dist/services/SchedulerSnapback.js +1 -0
  37. package/dist/services/SlateClock.d.ts +22 -0
  38. package/dist/services/SlateClock.js +1 -0
  39. package/dist/services/SnapbackGuard.d.ts +70 -0
  40. package/dist/services/SnapbackGuard.js +1 -0
  41. package/dist/services/VastAdManager.d.ts +2 -0
  42. package/dist/services/VastAdManager.js +1 -1
  43. package/dist/services/adPlayer/AdPlayerCallbacks.d.ts +11 -1
  44. package/dist/services/adPlayer/AdPlayerEvents.d.ts +21 -1
  45. package/dist/services/adPlayer/AdPlayerEvents.js +1 -1
  46. package/dist/services/adPlayer/AdPlayerState.d.ts +14 -0
  47. package/dist/services/adPlayer/AdPlayerState.js +1 -1
  48. package/dist/services/adPlayer/AdPreloader.d.ts +8 -1
  49. package/dist/services/adPlayer/AdPreloader.js +1 -1
  50. package/dist/services/adPlayer/AdStage.js +1 -1
  51. package/dist/services/adPlayer/AssetPlayback.d.ts +1 -0
  52. package/dist/services/adPlayer/AssetPlayback.js +1 -1
  53. package/dist/services/adPlayer/BreakSequenceDriver.d.ts +7 -1
  54. package/dist/services/adPlayer/BreakSequenceDriver.js +1 -1
  55. package/dist/services/adPlayer/ContentResumeCoordinator.d.ts +6 -0
  56. package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -1
  57. package/dist/services/adPlayer/PauseAdPresenter.d.ts +10 -9
  58. package/dist/services/adPlayer/PauseAdPresenter.js +1 -1
  59. package/dist/services/adPlayer/PauseAdTracking.d.ts +13 -0
  60. package/dist/services/adPlayer/PauseAdTracking.js +1 -0
  61. package/dist/services/adPlayer/SharedElementBreakPlayer.d.ts +31 -2
  62. package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
  63. package/dist/services/adPlayer/SlateCoordinator.d.ts +58 -0
  64. package/dist/services/adPlayer/SlateCoordinator.js +1 -0
  65. package/dist/services/adPlayer/SlatePlayback.d.ts +89 -0
  66. package/dist/services/adPlayer/SlatePlayback.js +1 -0
  67. package/dist/services/adPlayer/VariantSelector.d.ts +10 -1
  68. package/dist/services/adPlayer/VariantSelector.js +1 -1
  69. package/dist/services/adPlayer/connectPauseAd.js +1 -1
  70. package/dist/services/adPlayer/presentation/PresentationLayout.d.ts +5 -2
  71. package/dist/services/adPlayer/presentation/PresentationLayout.js +1 -1
  72. package/dist/services/breakTiming.d.ts +1 -0
  73. package/dist/services/breakTiming.js +1 -1
  74. package/dist/services/checkEventBreak.d.ts +5 -0
  75. package/dist/services/checkEventBreak.js +1 -0
  76. package/dist/services/parseBreakManifest.d.ts +2 -1
  77. package/dist/services/parseBreakManifest.js +1 -1
  78. package/dist/services/parseVastPause.d.ts +18 -0
  79. package/dist/services/parseVastPause.js +1 -0
  80. package/dist/services/resolveLshapeAnchor.d.ts +25 -0
  81. package/dist/services/resolveLshapeAnchor.js +1 -0
  82. package/dist/services/resolveResumePoint.d.ts +3 -1
  83. package/dist/services/resolveResumePoint.js +1 -1
  84. package/dist/services/resolveSlate.d.ts +25 -0
  85. package/dist/services/resolveSlate.js +1 -0
  86. package/dist/services/resolveSlateFill.d.ts +39 -0
  87. package/dist/services/resolveSlateFill.js +1 -0
  88. package/dist/services/ssbSwitchSignals.d.ts +3 -0
  89. package/dist/services/ssbSwitchSignals.js +1 -1
  90. package/dist/session/AdBreakStatusTracker.d.ts +22 -2
  91. package/dist/session/AdBreakStatusTracker.js +1 -1
  92. package/dist/session/BreakLifecycle.js +1 -1
  93. package/dist/session/ContentPlaybackStart.d.ts +23 -0
  94. package/dist/session/ContentPlaybackStart.js +1 -0
  95. package/dist/session/DiscoveredManifestUrl.d.ts +44 -0
  96. package/dist/session/DiscoveredManifestUrl.js +1 -0
  97. package/dist/session/ScheduledBreaksTracker.d.ts +22 -0
  98. package/dist/session/ScheduledBreaksTracker.js +1 -0
  99. package/dist/session/SessionStarter.d.ts +3 -9
  100. package/dist/session/SessionStarter.js +1 -1
  101. package/dist/session/SessionStarterDeps.d.ts +3 -0
  102. package/dist/session/SessionStarterHelpers.d.ts +22 -1
  103. package/dist/session/SessionStarterHelpers.js +1 -1
  104. package/dist/session/SessionState.d.ts +16 -0
  105. package/dist/session/SessionState.js +1 -1
  106. package/dist/session/SsbSwitch.js +1 -1
  107. package/dist/session/buildScheduledBreaks.d.ts +9 -0
  108. package/dist/session/buildScheduledBreaks.js +1 -0
  109. package/dist/session/contentSnapback.d.ts +5 -1
  110. package/dist/session/contentSnapback.js +1 -1
  111. package/dist/session/scheduledBreaksSession.d.ts +6 -0
  112. package/dist/session/scheduledBreaksSession.js +1 -0
  113. package/dist/types/BreakManifest.d.ts +40 -2
  114. package/dist/types/BreakManifest.js +1 -1
  115. package/dist/types/Events.d.ts +13 -4
  116. package/dist/types/OptiViewAdsConfig.d.ts +15 -2
  117. package/dist/types/ScheduledBreak.d.ts +17 -0
  118. package/dist/types/ScheduledBreak.js +1 -0
  119. package/package.json +1 -1
@@ -1 +1 @@
1
- import{MediaType as e}from"../../types/BreakManifest";import{AUX_FETCH_TIMEOUT_MS as t,fetchWithTimeout as s}from"../fetchWithTimeout";import{parseVastCompanionImage as i}from"../parseVastCompanion";import{parseVastLinearMediaFile as a}from"../parseVastLinear";import{resolvePauseResumePoint as n}from"../resolvePauseResumePoint";export class PauseAdPresenter{constructor(e){this.deps=e,this.pauseAdContainer=null,this.pauseAdVideoEl=null,this.pauseAdFadeTimer=null,this.pauseAdPinnedPosition=0,this.pauseAdResumeControls=[],this.pauseAdCreativeEndedCallback=null}async showPauseImage(e){const t=this.deps.getStage();if(!t)return;const s=await this.resolvePauseAsset(e);if(!s)return void this.deps.diagnose("DA-PAUSE-AD-NO-ASSET",`Pause ad '${e.break.id}' asset could not be resolved; nothing shown.`,{breakId:e.break.id,source:e.source,mediaType:e.mediaType});this.removePauseAdNow(),this.pauseAdPinnedPosition=this.deps.contentPlayer.currentTime;const i=document.createElement("div");if(i.className="dolby-pause-ad",i.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;z-index:150;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.35);opacity:0;transition:opacity 300ms ease-in-out;"+(s.clickThrough?"cursor:pointer;":""),"video"===s.kind){const e=document.createElement("video");e.className="dolby-pause-ad-video",e.style.cssText="max-width:100%;max-height:100%;object-fit:contain;",e.muted=!0,e.loop=!1,e.autoplay=!0,e.playsInline=!0,e.setAttribute("playsinline",""),e.src=s.url,this.pauseAdVideoEl=e,i.appendChild(e)}else{const e=document.createElement("img");e.className="dolby-pause-ad-image",e.style.cssText="max-width:100%;max-height:100%;object-fit:contain;",e.src=s.url,i.appendChild(e)}if(s.clickThrough){const e=s.clickThrough;i.addEventListener("click",()=>{window.open(e,"_blank","noopener")})}const a=this.buildPauseResumeButton(),n=this.buildPauseCloseButton();if(a.style.display="none",n.style.display="none",this.pauseAdResumeControls=[a,n],i.appendChild(a),i.appendChild(n),this.pauseAdVideoEl){const t=this.pauseAdVideoEl,s=e.break.id,i=()=>{this.pauseAdVideoEl===t&&this.pauseAdCreativeEndedCallback?.(s)};t.addEventListener("ended",i,{once:!0}),t.addEventListener("error",i,{once:!0}),t.play().catch(i)}t.appendChild(i),this.pauseAdContainer=i,requestAnimationFrame(()=>{this.pauseAdContainer===i&&(i.style.opacity="1")}),this.firePauseImpressions(s.impressions),this.deps.log("Pause ad shown:",e.break.id,e.source,e.mediaType)}hidePauseImage(){const e=this.pauseAdContainer;e&&(this.stopPauseAdVideo(),this.pauseAdContainer=null,e.style.opacity="0",this.pauseAdFadeTimer&&clearTimeout(this.pauseAdFadeTimer),this.pauseAdFadeTimer=setTimeout(()=>{e.parentElement&&e.parentElement.removeChild(e),this.pauseAdFadeTimer=null},300))}revealPauseAdResume(){for(const e of this.pauseAdResumeControls)e.style.display=""}setPauseAdCreativeEndedCallback(e){this.pauseAdCreativeEndedCallback=e}destroy(){this.removePauseAdNow()}removePauseAdNow(){this.pauseAdFadeTimer&&(clearTimeout(this.pauseAdFadeTimer),this.pauseAdFadeTimer=null),this.stopPauseAdVideo(),this.pauseAdContainer?.parentElement&&this.pauseAdContainer.parentElement.removeChild(this.pauseAdContainer),this.pauseAdContainer=null}stopPauseAdVideo(){this.pauseAdResumeControls=[];const e=this.pauseAdVideoEl;if(e){try{e.pause(),e.removeAttribute("src")}catch{}this.pauseAdVideoEl=null}}resumeFromPauseAd(){const e=n(this.pauseAdPinnedPosition,this.seekableStart(),this.seekableEnd());null!==e&&(this.deps.diagnose("DA-PAUSE-AD-DVR-EXPIRED",`Pause outlived the DVR window; resuming at the live edge (${e.toFixed(2)}s).`,{pinnedPosition:this.pauseAdPinnedPosition,liveEdge:e}),this.deps.contentPlayer.seek(e)),this.deps.contentPlayer.play()}seekableStart(){const e=this.deps.contentPlayer.videoElement?.seekable;return e&&0!==e.length?e.start(0):Number.NaN}seekableEnd(){const e=this.deps.contentPlayer.videoElement?.seekable;return e&&0!==e.length?e.end(e.length-1):Number.NaN}buildPauseResumeButton(){const e=document.createElement("button");return e.className="dolby-pause-ad-resume",e.setAttribute("aria-label","Resume playback"),e.style.cssText="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:72px;height:72px;border:none;border-radius:50%;cursor:pointer;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;",e.innerHTML='<svg width="32" height="32" viewBox="0 0 24 24" fill="#fff" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>',e.addEventListener("click",e=>{e.stopPropagation(),this.resumeFromPauseAd()}),e}buildPauseCloseButton(){const e=document.createElement("button");return e.className="dolby-pause-ad-close",e.setAttribute("aria-label","Close pause ad and resume"),e.style.cssText="position:absolute;top:8px;right:8px;width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;background:rgba(0,0,0,0.6);color:#fff;font-size:18px;line-height:32px;text-align:center;padding:0;",e.textContent="✕",e.addEventListener("click",e=>{e.stopPropagation(),this.resumeFromPauseAd()}),e}async resolvePauseAsset(n){const o=n.asset,r=n.mediaType===e.VIDEO?"video":"image";if("static"===n.source){const e=this.deps.buildStaticAssetUrl(o,n.break);if(!e)return null;const t=o.interaction?.clickThrough;return{kind:r,url:e,...t?{clickThrough:t}:{},impressions:[]}}const d=this.deps.resolveUri(o.uri);if(!d)return null;try{const e=await s(d,{},t),n=await e.text();if("video"===r){const e=a(n);if(!e)return null;const t=e.clickThrough??o.interaction?.clickThrough;return{kind:r,url:e.mediaFileUrl,...t?{clickThrough:t}:{},impressions:e.impressions}}const l=i(n);if(!l)return null;const u=l.clickThrough??o.interaction?.clickThrough;return{kind:r,url:l.imageUrl,...u?{clickThrough:u}:{},impressions:l.impressions}}catch(e){return this.deps.log("Pause VAST fetch/parse failed:",e),null}}firePauseImpressions(e){for(const t of e)try{(new Image).src=t}catch{}}}
1
+ import{MediaType as e}from"../../types/BreakManifest";import{AUX_FETCH_TIMEOUT_MS as t,fetchWithTimeout as s}from"../fetchWithTimeout";import{resolveVastPauseCreative as a}from"../parseVastPause";import{PauseAdTracking as i}from"./PauseAdTracking";import{resolvePauseResumePoint as n}from"../resolvePauseResumePoint";export class PauseAdPresenter{constructor(e){this.deps=e,this.pauseAdContainer=null,this.pauseAdVideoEl=null,this.pauseAdFadeTimer=null,this.pauseAdPinnedPosition=0,this.pauseAdGeneration=0,this.pauseTracking=null,this.pauseAdResumeControls=[],this.pauseAdResumeRevealed=!1,this.pauseAdCreativeResolvedCallback=null,this.pauseAdCreativeEndedCallback=null}async showPauseImage(t){const s=this.deps.getStage();if(!s)return;const a=++this.pauseAdGeneration;this.removePauseAdNow(),this.pauseAdResumeRevealed=!1;const n=await this.resolvePauseAsset(t);if(a!==this.pauseAdGeneration)return;const r=n.creative;if(!r)return this.deps.diagnose("DA-PAUSE-AD-NO-ASSET",`Pause ad '${t.break.id}' asset could not be resolved; nothing shown.`,{breakId:t.break.id,source:t.source,mediaType:t.mediaType,reason:n.reason}),this.pauseAdCreativeResolvedCallback?.(t.break.id,null),void this.pauseAdCreativeEndedCallback?.(t.break.id);this.pauseAdCreativeResolvedCallback?.(t.break.id,"video"===r.kind?e.VIDEO:e.IMAGE);const o=new i(r,e=>this.firePauseBeacons(e));this.pauseTracking=o,this.pauseAdPinnedPosition=this.deps.contentPlayer.currentTime;const d=document.createElement("div");if(d.className="dolby-pause-ad",d.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;z-index:150;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,0.35);opacity:0;transition:opacity 300ms ease-in-out;"+(r.clickThrough?"cursor:pointer;":""),"video"===r.kind){const e=document.createElement("video");e.className="dolby-pause-ad-video",e.style.cssText="max-width:100%;max-height:100%;object-fit:contain;",e.muted=!0,e.loop=!1,e.autoplay=!0,e.playsInline=!0,e.setAttribute("playsinline",""),e.src=r.url,this.pauseAdVideoEl=e,d.appendChild(e)}else{const e=document.createElement("img");e.className="dolby-pause-ad-image",e.style.cssText="max-width:100%;max-height:100%;object-fit:contain;",e.addEventListener("error",()=>{this.pauseAdContainer===d&&(this.deps.diagnose("DA-PAUSE-AD-NO-ASSET","Pause ad image failed to load.",{breakId:t.break.id,source:t.source,reason:"image-load-failed"}),this.pauseAdCreativeResolvedCallback?.(t.break.id,null),this.pauseAdCreativeEndedCallback?.(t.break.id))},{once:!0}),e.src=r.url,d.appendChild(e)}if(r.clickThrough){const e=r.clickThrough;d.addEventListener("click",()=>{this.pauseTracking===o&&(o.click(),window.open(e,"_blank","noopener"))})}const l=this.buildPauseResumeButton(),u=this.buildPauseCloseButton();if(l.style.display=this.pauseAdResumeRevealed?"":"none",u.style.display=this.pauseAdResumeRevealed?"":"none",this.pauseAdResumeControls=[l,u],d.appendChild(l),d.appendChild(u),this.pauseAdVideoEl){const e=this.pauseAdVideoEl,s=t.break.id,a=()=>{this.pauseAdVideoEl===e&&this.pauseAdCreativeEndedCallback?.(s)};e.addEventListener("ended",()=>o.complete(e.duration)),e.addEventListener("ended",a,{once:!0}),e.addEventListener("playing",()=>o.progress(e.currentTime,e.duration,!0)),e.addEventListener("timeupdate",()=>o.progress(e.currentTime,e.duration));const i=()=>{this.pauseAdVideoEl===e&&(this.pauseAdCreativeResolvedCallback?.(s,null),a())};e.addEventListener("error",i,{once:!0}),e.play().catch(i)}s.appendChild(d),this.pauseAdContainer=d,requestAnimationFrame(()=>{this.pauseAdContainer===d&&(d.style.opacity="1")}),this.firePauseBeacons(r.impressions),this.deps.log("Pause ad shown:",t.break.id,t.source,t.mediaType)}hidePauseImage(){++this.pauseAdGeneration,this.pauseTracking?.dismiss(),this.pauseTracking=null;const e=this.pauseAdContainer;e&&(this.stopPauseAdVideo(),this.pauseAdContainer=null,e.style.opacity="0",this.pauseAdFadeTimer&&clearTimeout(this.pauseAdFadeTimer),this.pauseAdFadeTimer=setTimeout(()=>{e.parentElement&&e.parentElement.removeChild(e),this.pauseAdFadeTimer=null},300))}revealPauseAdResume(){this.pauseAdResumeRevealed=!0;for(const e of this.pauseAdResumeControls)e.style.display=""}setPauseAdCreativeResolvedCallback(e){this.pauseAdCreativeResolvedCallback=e}setPauseAdCreativeEndedCallback(e){this.pauseAdCreativeEndedCallback=e}destroy(){++this.pauseAdGeneration,this.removePauseAdNow()}removePauseAdNow(){this.pauseTracking?.dismiss(),this.pauseTracking=null,this.pauseAdFadeTimer&&(clearTimeout(this.pauseAdFadeTimer),this.pauseAdFadeTimer=null),this.stopPauseAdVideo(),this.pauseAdContainer?.parentElement&&this.pauseAdContainer.parentElement.removeChild(this.pauseAdContainer),this.pauseAdContainer=null}stopPauseAdVideo(){this.pauseAdResumeControls=[];const e=this.pauseAdVideoEl;if(e){try{e.pause(),e.removeAttribute("src")}catch{}this.pauseAdVideoEl=null}}resumeFromPauseAd(){const e=n(this.pauseAdPinnedPosition,this.seekableStart(),this.seekableEnd());null!==e&&(this.deps.diagnose("DA-PAUSE-AD-DVR-EXPIRED",`Pause outlived the DVR window; resuming at the live edge (${e.toFixed(2)}s).`,{pinnedPosition:this.pauseAdPinnedPosition,liveEdge:e}),this.deps.contentPlayer.seek(e)),this.deps.contentPlayer.play()}seekableStart(){const e=this.deps.contentPlayer.videoElement?.seekable;return e&&0!==e.length?e.start(0):Number.NaN}seekableEnd(){const e=this.deps.contentPlayer.videoElement?.seekable;return e&&0!==e.length?e.end(e.length-1):Number.NaN}buildPauseResumeButton(){const e=document.createElement("button");return e.className="dolby-pause-ad-resume",e.setAttribute("aria-label","Resume playback"),e.style.cssText="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:72px;height:72px;border:none;border-radius:50%;cursor:pointer;background:rgba(0,0,0,0.6);display:flex;align-items:center;justify-content:center;",e.innerHTML='<svg width="32" height="32" viewBox="0 0 24 24" fill="#fff" aria-hidden="true"><path d="M8 5v14l11-7z"/></svg>',e.addEventListener("click",e=>{e.stopPropagation(),this.resumeFromPauseAd()}),e}buildPauseCloseButton(){const e=document.createElement("button");return e.className="dolby-pause-ad-close",e.setAttribute("aria-label","Close pause ad and resume"),e.style.cssText="position:absolute;top:8px;right:8px;width:32px;height:32px;border:none;border-radius:50%;cursor:pointer;background:rgba(0,0,0,0.6);color:#fff;font-size:18px;line-height:32px;text-align:center;padding:0;",e.textContent="✕",e.addEventListener("click",e=>{e.stopPropagation(),this.resumeFromPauseAd()}),e}async resolvePauseAsset(t){const s=t.asset,i=t.mediaType===e.VIDEO?"video":"image";if("static"===t.source){const e=this.deps.buildStaticAssetUrl(s,t.break);if(!e)return{creative:null,reason:"no-creative"};const a=s.interaction?.clickThrough;return{reason:null,creative:{kind:i,url:e,...a?{clickThrough:a}:{},impressions:[],clickTracking:[],closeTracking:[],videoTracking:{start:[],firstQuartile:[],midpoint:[],thirdQuartile:[],complete:[]}}}}const n=this.deps.resolveUri(s.uri);if(!n)return{creative:null,reason:"no-creative"};try{const e=await this.fetchPauseVast(n),t=await a(e,e=>this.fetchPauseVast(e));return t.creative&&!t.creative.clickThrough&&s.interaction?.clickThrough&&(t.creative.clickThrough=s.interaction.clickThrough),t}catch(e){return this.deps.log("Pause VAST fetch/parse failed:",e),{creative:null,reason:"fetch-failed"}}}async fetchPauseVast(e){const a="undefined"==typeof AbortController?null:new AbortController;let i;try{return await Promise.race([s(e,a?{signal:a.signal}:{},t).then(e=>{if(!1===e.ok)throw new Error("Pause VAST HTTP failure");return e.text()}),new Promise((e,s)=>{i=setTimeout(()=>{a?.abort(),s(new Error("Pause VAST timeout"))},t)})])}finally{clearTimeout(i)}}firePauseBeacons(e){for(const t of e)try{(new Image).src=t}catch{}}}
@@ -0,0 +1,13 @@
1
+ import type { VastPauseCreative } from '../parseVastPause';
2
+ export declare class PauseAdTracking {
3
+ private creative;
4
+ private beacon;
5
+ private active;
6
+ private fired;
7
+ constructor(creative: VastPauseCreative, beacon: (urls: string[]) => void);
8
+ click(): void;
9
+ dismiss(): void;
10
+ progress(time: number, duration: number, playing?: boolean): void;
11
+ complete(duration: number): void;
12
+ private event;
13
+ }
@@ -0,0 +1 @@
1
+ export class PauseAdTracking{constructor(i,t){this.creative=i,this.beacon=t,this.active=!0,this.fired=new Set}click(){this.active&&this.beacon(this.creative.clickTracking)}dismiss(){this.active&&(this.active=!1,this.beacon(this.creative.closeTracking))}progress(i,t,e=!1){if((e||i>0)&&this.event("start"),Number.isFinite(t)&&!(t<=0))for(const[e,s]of[["firstQuartile",.25],["midpoint",.5],["thirdQuartile",.75]])i>=t*s&&this.event(e)}complete(i){this.progress(i,i,!0),this.event("complete")}event(i){this.active&&!this.fired.has(i)&&(this.fired.add(i),this.beacon(this.creative.videoTracking[i]))}}
@@ -1,10 +1,14 @@
1
1
  import type { PlayerAdapter, TimedMetadataCue } from '../../interfaces/PlayerAdapter';
2
2
  import type { Asset, Break } from '../../types/BreakManifest';
3
3
  import type { AdPlayerCallbacks } from '../AdPlayerController';
4
- import type { AdPlayerState } from './AdPlayerState';
4
+ import { type AdPlayerState } from './AdPlayerState';
5
+ import type { Clock } from '../BreakScheduler';
5
6
  import type { SurfaceRevealReporter } from './SurfaceRevealReporter';
7
+ import type { SlateStartReason } from '../AdBreakSequencer';
6
8
  export interface SharedElementBreakPlayerDeps {
7
9
  contentPlayer: PlayerAdapter;
10
+ /** The controller's clock: the break's scheduled end is measured on it. */
11
+ clock: Clock;
8
12
  getAdPlayer: () => PlayerAdapter | null;
9
13
  /** Watches for the ad's first frame on the content element (`DA-AD-SURFACE-REVEALED`). */
10
14
  surfaceReveal?: SurfaceRevealReporter;
@@ -24,14 +28,28 @@ export interface SharedElementBreakPlayerDeps {
24
28
  format: string;
25
29
  } | null;
26
30
  getAssets: (breakInfo: Break) => Asset[];
31
+ /** The manifest slate's media events while it plays on the content element: true when consumed. */
32
+ onSlateMediaEvent: (type: 'playing' | 'waiting' | 'ended' | 'error' | 'timeupdate', detail?: unknown) => boolean;
33
+ /** Banks the ad's played seconds before a slate is sized from them. */
34
+ settleAssetProgress: (errored: boolean) => void;
35
+ planSlateAfterAds: (breakInfo: Break) => {
36
+ durationSec: number;
37
+ reason: SlateStartReason;
38
+ } | null;
39
+ startSlate: (breakInfo: Break, plan: {
40
+ durationSec: number;
41
+ reason: SlateStartReason;
42
+ }) => void;
27
43
  }
28
44
  /** Owns playback through the content element and restoration after shared-element breaks. */
29
45
  export declare class SharedElementBreakPlayer {
30
46
  private readonly state;
31
47
  private readonly deps;
48
+ private sharedAdStarted;
32
49
  private contentElementAdPlayer;
33
50
  private sharedAdSource;
34
51
  private sharedAdEndedHandler;
52
+ private sharedAdErrorHandler;
35
53
  private sharedAdTimeUpdateHandler;
36
54
  private sharedAdTimedMetadataHandler;
37
55
  private sharedAdWaitingHandler;
@@ -41,7 +59,11 @@ export declare class SharedElementBreakPlayer {
41
59
  private sharedRestore;
42
60
  private sharedRestoreGeneration;
43
61
  private sharedActivityGeneration;
62
+ /** The break's ad has ended and the slate plays on the element until the break end. */
63
+ private slateStarted;
44
64
  constructor(state: AdPlayerState, deps: SharedElementBreakPlayerDeps);
65
+ /** The player carrying the shared-element break's media right now (its ad, then its slate). */
66
+ currentSharedAdSource(): PlayerAdapter | null;
45
67
  sharedElementAdPlayer(): PlayerAdapter | null;
46
68
  private sharedAdStep;
47
69
  private clearSharedAdFirstFrameWatchdog;
@@ -57,7 +79,14 @@ export declare class SharedElementBreakPlayer {
57
79
  releaseContentElementAdPlayer(): void;
58
80
  playAdThroughContentElement(): Promise<void>;
59
81
  playSharedElementBreak(breakInfo: Break, callbacks: AdPlayerCallbacks): Promise<void>;
60
- onSharedElementAdEnded(): void;
82
+ /**
83
+ * The ad is over. `startSlate`: it ended on its own (or failed), so the manifest slate may fill the
84
+ * rest of the break on the same element; a cut or a wedged element ends the break at once.
85
+ */
86
+ onSharedElementAdEnded(startSlate?: boolean, errored?: boolean): void;
87
+ /** The slate played until the break end (or failed): the break ends, its ad's `adend` long since sent. */
88
+ finishSlatedBreak(): void;
89
+ private endSharedBreak;
61
90
  restoreSharedElementContent(breakInfo?: Break | null): void;
62
91
  private runSharedRestore;
63
92
  private armSharedRestoreDeadline;
@@ -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)),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}}
1
+ import{BreakFormat as e,isPostRollBreak as t}from"../../types/BreakManifest";import{beginBreakTally as s}from"./AdPlayerState";import{armPlaybackCutoff as a,clearPlaybackCutoff as r}from"./PlaybackCutoff";import{AdError as n}from"../../types/AdError";import{classifyMediaError as i,toClassifiedAdError as d}from"./classifyMediaError";function h(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.sharedAdStarted=!1,this.contentElementAdPlayer=null,this.sharedAdSource=null,this.sharedAdEndedHandler=null,this.sharedAdErrorHandler=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,this.slateStarted=!1}currentSharedAdSource(){return this.sharedAdSource}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,r=s instanceof Error?s.message:String(s),d=new n(`shared-element ${e} failed: ${r} (readyState=${a?.readyState??"-"} paused=${a?.paused??"-"} networkState=${a?.networkState??"-"})`,i(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"),{adIndex:0,totalAds:1}),this.onSharedElementAdEnded()},t)}attachSharedAdListeners(e){this.sharedAdSource=e,this.sharedAdEndedHandler=()=>{this.deps.onSlateMediaEvent("ended")||this.onSharedElementAdEnded(!0)},e.on("ended",this.sharedAdEndedHandler),this.sharedAdErrorHandler=e=>{this.deps.onSlateMediaEvent("error",e)},e.on("error",this.sharedAdErrorHandler),this.sharedAdTimeUpdateHandler=()=>{this.deps.onSlateMediaEvent("timeupdate")||(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.deps.onSlateMediaEvent("waiting")||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.deps.onSlateMediaEvent("playing")||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.slateStarted=!1,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.sharedAdErrorHandler&&(e.off("error",this.sharedAdErrorHandler),this.sharedAdErrorHandler=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 r=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 n=this.sharedElementAdPlayer()??this.deps.contentPlayer;try{this.deps.getAdPlayer()?.pause(),this.deps.clearContentGate(),await n.load(a),this.attachSharedAdListeners(n),r>0&&n.seek(r),await n.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,r){const i=this.deps.getVariant(t);if(!i)return this.deps.log("shared-element: no valid variant, skipping break:",t.id),void r.onBreakEnd(t);if(i.format===e.LSHAPE_CONTENT)return this.deps.log("shared-element: skipping lshape_content break (no fullscreen video asset):",t.id),void r.onBreakEnd(t);const h=this.deps.getAssets(t)[0];if(!h)return this.deps.log("shared-element: no asset to play, skipping break:",t.id),void r.onBreakEnd(t);i.format!==e.SINGLE&&this.deps.log(`shared-element: downgrading ${i.format} to a single fullscreen ad:`,t.id),this.state.currentBreak=t,this.sharedAdStarted=!1,this.state.currentAsset=h,this.state.callbacks=r,this.state.isPlaying=!0,this.slateStarted=!1,s(this.state,this.deps.clock.now());const l=this.state.effectiveBreakDurationSec??t.duration,o=await this.deps.resolveAssetUri(h,l);if(!o)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 r.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 c=this.sharedElementAdPlayer()??this.deps.contentPlayer;if(this.sharedAdSource=c,l&&l>0){const e=l+this.state.adBreakCutSafetyMarginSec;a(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,h,new n("Ad playback startup timed out","DA-AD-START-TIMEOUT"),{adIndex:0,totalAds:1}),this.onSharedElementAdEnded()})}try{if(this.deps.clearContentGate(),await this.sharedAdStep("load",c,()=>c.load(o)),!this.state.isPlaying||this.state.currentBreak!==t)return;if(this.attachSharedAdListeners(c),this.armSharedAdFirstFrameWatchdog(c,Math.max(Math.min(1e3*(l??0)-100,5e3),3e3)),this.deps.surfaceReveal?.surfaceShown(),this.deps.surfaceReveal?.watch(h.id,t.id,c.videoElement),await this.sharedAdStep("play",c,()=>c.play()),!this.state.isPlaying||this.state.currentBreak!==t)return;this.sharedAdStarted=!0,this.state.callbacks?.onAdStart(t,h,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,h,d(e),{adIndex:0,totalAds:1}),this.sharedAdEndedHandler||this.attachSharedAdListeners(c),this.onSharedElementAdEnded(!0,!0)}}onSharedElementAdEnded(e=!1,t=!1){if(!this.state.isPlaying||!this.state.currentBreak)return;const s=this.state.currentBreak,a=this.state.currentAsset,r=this.state.callbacks;if(e&&!this.slateStarted){this.deps.settleAssetProgress(t);const e=this.deps.planSlateAfterAds(s);if(e)return this.slateStarted=!0,this.clearSharedAdFirstFrameWatchdog(),a&&this.sharedAdStarted&&r?.onAdEnd(s,a,0,1),this.sharedAdStarted=!1,void this.deps.startSlate(s,e)}this.endSharedBreak(s,a,r,this.slateStarted)}finishSlatedBreak(){const e=this.state.currentBreak;this.state.isPlaying&&e&&this.slateStarted&&this.endSharedBreak(e,this.state.currentAsset,this.state.callbacks,!0)}endSharedBreak(e,t,s,a){const n=this.sharedRestoreGeneration;this.detachSharedAdListeners(),r(this.state),this.state.isPlaying=!1,this.state.currentBreak=null,this.state.currentAsset=null;const i=this.sharedAdStarted;this.sharedAdStarted=!1,t&&i&&!a&&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.savedContentLive,r=a||void 0===this.state.snapbackResumePosition?this.state.savedContentTime:this.state.snapbackResumePosition,n=this.state.savedContentEdgeDistance,i=this.state.callbacks;this.state.savedContentSrc=null,this.state.savedContentEdgeDistance=null;const d={src:s,time:r,live:a,edgeDistance:n,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?.(a?this.deps.contentPlayer.currentTime:r,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:r,edgeDistance:n,postRoll:i}=e,d=()=>this.ownsSharedRestoreAttempt(e,t);if(!d())return;if(e.adPlayer?.releaseMediaElement&&(await this.releaseAdEngineGrip(e),!d()))return;const l=this.deps.contentPlayer.videoElement??null,o=h(l);if(await this.deps.contentPlayer.load(s),d()){if(this.armSharedRestoreDeadline(t,5e3),r){if(!i&&null!==n&&n>0){t.phase="live-edge";const e=await this.deps.waitForLiveEdge(d);if(!d())return;if(null!==e){const s=Math.max(e-n,0);if(this.deps.log(`shared-element: live resume ${n.toFixed(2)}s behind the edge (${s.toFixed(2)}s)`),!d())return;t.phase="seek",this.deps.contentPlayer.seek(s)}}}else t.phase="seek",this.deps.contentPlayer.seek(a);d()&&!i&&(t.phase="play",await this.deps.contentPlayer.play(),d()&&await this.waitForContentEngineToTakeElement(l,o,d))}}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 r=()=>{if(this.deps.contentPlayer.videoElement!==e)return!0;const s=h(e);if(!s)return!1;const a=!!s.src&&s.src!==t.src,r=!!s.srcObject&&s.srcObject!==t.srcObject;return a||r},n=performance.now();for(;!r()&&s()&&performance.now()-n<1500;)await new Promise(e=>setTimeout(e,25));if(!s())return;const i=Math.round(performance.now()-n);this.deps.log(`shared-element: content engine ${r()?"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,58 @@
1
+ import type { Break } from '../../types/BreakManifest';
2
+ import type { AdBreakSequencer, AdBreakTransition, SlateNotStartedNotice, SlateResolver, SlateStartReason } from '../AdBreakSequencer';
3
+ import type { Clock } from '../BreakScheduler';
4
+ import type { ResolvedSlate } from '../resolveSlate';
5
+ import type { AdPlayerState } from './AdPlayerState';
6
+ import { SlatePlayback, type SlatePlaybackDeps } from './SlatePlayback';
7
+ export interface SlateCoordinatorDeps extends Omit<SlatePlaybackDeps, 'settled'> {
8
+ getSequencer: () => AdBreakSequencer | null;
9
+ /** The slate takes the surface over from the ad: stop waiting for an ad frame that is no longer coming. */
10
+ cancelSurfaceReveal: () => void;
11
+ drive: (transition: AdBreakTransition) => Promise<void>;
12
+ clock: Clock;
13
+ /** Whether the slate can be put on screen where the break plays (a shared element has no surface for an image). */
14
+ rendersSlate: (slate: ResolvedSlate) => boolean;
15
+ /** The break plays through the content element, with no sequencer: the slate ends it through `finishSharedSlate`. */
16
+ isSharedBreak: () => boolean;
17
+ finishSharedSlate: () => void;
18
+ }
19
+ /**
20
+ * The controller's slate seam: holds the manifest's current slate, answers the sequencer's
21
+ * "does a slate fill what the ads left?" from the controller's own clocks, and ends the break
22
+ * when the slate does.
23
+ */
24
+ export declare class SlateCoordinator {
25
+ private readonly state;
26
+ private readonly deps;
27
+ private slate;
28
+ readonly playback: SlatePlayback;
29
+ constructor(state: AdPlayerState, deps: SlateCoordinatorDeps);
30
+ /** Returns true when this is a slate not seen before (first appearance, or a changed URL): worth warming. */
31
+ set(slate: ResolvedSlate | null): boolean;
32
+ get current(): ResolvedSlate | null;
33
+ /** Whether the slate would cover a break that has no ad to play: the pre-begin gate lets it begin. */
34
+ coversEmptyBreak(breakInfo: Break, effectiveDurationSec?: number): boolean;
35
+ private rendersSlate;
36
+ /**
37
+ * The slate to play now that the ads of a break the sequencer does not drive (the shared
38
+ * element) have ended, or null when none starts; a refusal worth reporting is diagnosed.
39
+ */
40
+ planAfterAds(breakInfo: Break): {
41
+ durationSec: number;
42
+ reason: SlateStartReason;
43
+ } | null;
44
+ /**
45
+ * For the break being played: its effective duration, the ad seconds already banked, the real
46
+ * time since it began and, while content plays on behind the ad, the wall clock that content
47
+ * reaches its resume point on. Ad seconds are reset when each break starts and final before
48
+ * this is asked. A first-frame-gated break that has shown nothing has not begun yet: its
49
+ * `adbreakbegin` goes out with the outcome being settled now.
50
+ */
51
+ readonly resolver: SlateResolver;
52
+ start(breakInfo: Break, plan: {
53
+ durationSec: number;
54
+ reason: SlateStartReason;
55
+ }): void;
56
+ diagnoseNotStarted(notices: SlateNotStartedNotice[]): void;
57
+ private settled;
58
+ }
@@ -0,0 +1 @@
1
+ import{resolveSlateFill as e}from"../resolveSlateFill";import{SlatePlayback as t}from"./SlatePlayback";export class SlateCoordinator{constructor(s,a){this.state=s,this.deps=a,this.slate=null,this.resolver=(t,s)=>e({slate:this.slate,effectiveDurationSec:this.state.effectiveBreakDurationSec??t.duration,adsPlayedSec:"begin"===s?0:this.state.adSecondsPlayedThisBreak,breakElapsedSec:"begin"===s||this.state.firstFrameGateActive&&!this.state.firstFrameGateShown?0:(this.deps.clock.now()-this.state.breakBeganAtMs)/1e3,continuePlayoutWallElapsedSec:this.state.continueContentDuringBreakActive?(this.deps.clock.now()-this.state.continuePlayoutStartWallMs)/1e3:null,rendersSlate:this.rendersSlate()}),this.playback=new t(s,{...a,now:()=>a.clock.now(),settled:e=>this.settled(e)})}set(e){const t=e=>e?`${e.asset.id}|${JSON.stringify(e.asset.uri)}`:"",s=null!==e&&t(e)!==t(this.slate);return this.slate=e,s}get current(){return this.slate}coversEmptyBreak(t,s){return e({slate:this.slate,effectiveDurationSec:s??t.duration,adsPlayedSec:0,continuePlayoutWallElapsedSec:null,rendersSlate:this.rendersSlate()}).fill}rendersSlate(){return null===this.slate||this.deps.rendersSlate(this.slate)}planAfterAds(e){const t=this.resolver(e,"ads-ended");return t.fill?{durationSec:t.durationSec,reason:"ads-ended"}:("below-min-duration"!==t.reason&&"not-rendered"!==t.reason||this.diagnoseNotStarted([{breakId:e.id,reason:t.reason,remainingSec:t.remainingSec}]),null)}start(e,t){this.deps.cancelSurfaceReveal(),this.slate?this.playback.start(this.slate,e,t):this.settled("slate-elapsed")}diagnoseNotStarted(e){for(const t of e)this.deps.diagnose("DA-SLATE-NOT-STARTED",`Slate did not start in break ${t.breakId} (${t.reason}); ${t.remainingSec}s were left`,{breakId:t.breakId,reason:t.reason,remainingSec:t.remainingSec,...this.slate?{minDurationSec:this.slate.minDurationSec}:{}})}settled(e){if(this.deps.isSharedBreak())return void this.deps.finishSharedSlate();const t=this.deps.getSequencer();t&&this.state.currentBreak&&this.state.isPlaying&&this.deps.drive(t.onOutcome({kind:e}))}}
@@ -0,0 +1,89 @@
1
+ import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
2
+ import { type Asset, type Break, type StaticAsset } from '../../types/BreakManifest';
3
+ import type { SlateStartReason } from '../AdBreakSequencer';
4
+ import type { ResolvedSlate } from '../resolveSlate';
5
+ import type { AdPlayerState } from './AdPlayerState';
6
+ /** How long the slate may go without presenting (at start, or after a stall) before it is abandoned. */
7
+ export declare const SLATE_STALL_BUDGET_MS = 5000;
8
+ export type SlateEndReason = 'break-end' | 'skipped' | 'removed' | 'failed';
9
+ export interface SlatePlaybackDeps {
10
+ getAdPlayer: () => PlayerAdapter | null;
11
+ getAdMediaContainer: () => HTMLElement | null;
12
+ buildStaticAssetUrl: (asset: StaticAsset, breakInfo: Break | null) => string | null;
13
+ setOverlayImage: (element: HTMLImageElement | null) => void;
14
+ getOverlayImage: () => HTMLImageElement | null;
15
+ clearOverlayImage: () => void;
16
+ /** Shows a first-frame-gated overlay once the slate is on screen. */
17
+ revealFirstFrame: () => void;
18
+ /** Break time moved on (or stopped moving): content catches up behind the slate, the countdown follows it. */
19
+ onProgress: (progress: SlateProgress) => void;
20
+ /** The slate is over: drives the sequencer's outcome. */
21
+ settled: (outcome: 'slate-elapsed' | 'slate-errored') => void;
22
+ /** The public `aderror` of a slate that could not play. */
23
+ emitAdError: (breakInfo: Break, asset: Asset, error: Error) => void;
24
+ diagnose: (code: string, message: string, context?: Record<string, unknown>) => void;
25
+ log: (...args: unknown[]) => void;
26
+ now?: () => number;
27
+ }
28
+ /** Where the slate stands: the wall time left to the break's scheduled end, and whether it is on screen. */
29
+ export interface SlateProgress {
30
+ breakInfo: Break;
31
+ remainingSec: number;
32
+ presenting: boolean;
33
+ }
34
+ /**
35
+ * Plays the manifest slate in the part of a break no ad covers: a video loops from its start, an
36
+ * image stays up. It owns the break from `play-slate` until the break's scheduled end: its deadline
37
+ * is armed once on the wall clock, so load, stalls and loop seams shorten the slate and never move
38
+ * the break end. It emits no ad events, and a failure ends the break as it would end without a slate.
39
+ */
40
+ export declare class SlatePlayback {
41
+ private readonly state;
42
+ private readonly deps;
43
+ private session;
44
+ constructor(state: AdPlayerState, deps: SlatePlaybackDeps);
45
+ get active(): boolean;
46
+ private now;
47
+ start(slate: ResolvedSlate, breakInfo: Break, plan: {
48
+ durationSec: number;
49
+ reason: SlateStartReason;
50
+ }): void;
51
+ /**
52
+ * The ad player's media events while a slate owns it. Returns true when consumed, so the ad
53
+ * paths (progress, `adend`, error) never see the slate's looping video.
54
+ */
55
+ handleMediaEvent(type: 'playing' | 'waiting' | 'ended' | 'error' | 'timeupdate', detail?: unknown): boolean;
56
+ /** The break is cut (skip, removal, session end): the slate stops where it is, the caller ends the break. */
57
+ stop(reason: 'skipped' | 'removed'): void;
58
+ private owns;
59
+ private startVideo;
60
+ private startImage;
61
+ private onPresented;
62
+ /**
63
+ * A restart need not fire `playing`: WebKit can end an MSE element without pausing it, and
64
+ * `play()` on an unpaused element fires none. Media time that moves forward is presentation too.
65
+ */
66
+ private onMediaProgress;
67
+ private onInterrupted;
68
+ /** A video slate ran out before the break did: loop from its start. */
69
+ private onVideoEnded;
70
+ private tick;
71
+ /** The slate's share of break time so far: the remainder it covers minus the wall time left. */
72
+ private shareSec;
73
+ private report;
74
+ /** Armed once at the scheduled end; a stall or loop seam never re-arms it. */
75
+ private armDeadline;
76
+ private armWatchdog;
77
+ private clearDeadline;
78
+ private clearWatchdog;
79
+ private finish;
80
+ private fail;
81
+ /**
82
+ * Stops the media and timers, banks the slate's break time, and reports the end. A slate that
83
+ * reached the scheduled end banks the whole remainder the ads left, so the break's banked seconds
84
+ * add up to its effective duration and content resumes at the scheduled end; an early end banks
85
+ * only the seconds on screen.
86
+ */
87
+ private settle;
88
+ private toError;
89
+ }
@@ -0,0 +1 @@
1
+ import{AdError as e}from"../../types/AdError";import{MediaType as t}from"../../types/BreakManifest";import{SlateClock as s}from"../SlateClock";import{clearPlaybackCutoff as i}from"./PlaybackCutoff";export const SLATE_STALL_BUDGET_MS=5e3;function a(e){return"AbortError"===e?.name}const r=e=>Math.round(1e3*e)/1e3;export class SlatePlayback{constructor(e,t){this.state=e,this.deps=t,this.session=null}get active(){return null!==this.session}now(){return(this.deps.now??Date.now)()}start(a,n,o){this.session&&this.settle(this.session,"removed");const d=a.asset.mediaType!==t.IMAGE,l=this.state.effectiveBreakDurationSec??n.duration,c=this.state.breakBeganAtMs+1e3*l,h={breakInfo:n,slate:a,video:d,plannedSec:o.durationSec,scheduledEndMs:c,remainderSec:Math.max(0,l-this.state.adSecondsPlayedThisBreak),clock:new s(c),startedAtMs:this.now(),loops:0,deadline:null,watchdog:null,ticker:null,image:null,mediaReady:!1,lastMediaTimeSec:null,restarting:!1};this.session=h,i(this.state),this.state.currentAsset=a.asset,this.state.currentAssetElapsedSec=0,this.state.currentAssetBaseSec=0,this.deps.clearOverlayImage(),this.deps.diagnose("DA-SLATE-STARTED",`Slate ${a.asset.id} starts in break ${n.id} for ${o.durationSec}s (${o.reason})`,{breakId:n.id,slateAssetId:a.asset.id,reason:o.reason,durationSec:o.durationSec,adsPlayedSec:r(this.state.adSecondsPlayedThisBreak),effectiveDurationSec:this.state.effectiveBreakDurationSec??n.duration,minDurationSec:a.minDurationSec}),this.report(h);const p=this.deps.buildStaticAssetUrl(a.asset,n);p?(this.armDeadline(h),this.armWatchdog(h),d?this.startVideo(h,p):this.startImage(h,p)):this.fail(h,"load",new e("No valid URI for the slate asset","DA-AD-URI-MISSING"))}handleMediaEvent(e,t){const s=this.session;if(!s)return!1;if(!s.video)return!0;switch(e){case"playing":this.onPresented(s);break;case"waiting":s.lastMediaTimeSec=null,this.onInterrupted(s);break;case"ended":this.onVideoEnded(s);break;case"error":this.fail(s,"load",this.toError(t));break;case"timeupdate":this.onMediaProgress(s)}return!0}stop(e){this.session&&this.settle(this.session,e)}owns(e){return this.session===e}startVideo(t,s){const i=this.deps.getAdPlayer();if(!i)return void this.fail(t,"load",new e("No ad player for the slate","DA-AD-URI-MISSING"));const r=i.videoElement;r&&(r.style.cssText="display:block;width:100%;height:100%;object-fit:contain;"),i.load(s).then(()=>{if(this.owns(t))return t.mediaReady=!0,t.lastMediaTimeSec=null,i.play()}).catch(e=>{this.owns(t)&&!a(e)&&this.fail(t,"load",this.toError(e))})}startImage(t,s){const i=this.deps.getAdMediaContainer();if(!i)return void this.fail(t,"load",new e("No ad container for the slate","DA-IMAGE-LOAD-FAILED"));const a=this.deps.getAdPlayer()?.videoElement;a&&(a.style.display="none");const r=document.createElement("img");r.className="dolby-overlay-image",r.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",t.image=r,this.deps.setOverlayImage(r),r.onload=()=>{this.owns(t)&&this.deps.getOverlayImage()===r&&this.onPresented(t)},r.onerror=()=>{this.owns(t)&&this.deps.getOverlayImage()===r&&this.fail(t,"load",new e(`Slate image failed to load: ${s}`,"DA-IMAGE-LOAD-FAILED"))},r.src=s,i.appendChild(r)}onPresented(e){this.owns(e)&&!e.clock.presenting&&(e.clock.onPresented(this.now()),this.clearWatchdog(e),this.deps.revealFirstFrame(),e.ticker??(e.ticker=setInterval(()=>this.tick(e),250)),this.report(e))}onMediaProgress(e){const t=this.deps.getAdPlayer();if(!t||!e.mediaReady)return;if(t.videoElement?.seeking)return void(e.lastMediaTimeSec=null);const s=t.currentTime,i=e.lastMediaTimeSec;e.lastMediaTimeSec=s,null!==i&&!t.paused&&s>i&&(e.restarting=!1,e.clock.presenting||this.deps.log("Slate presenting again from media progress, without a playing event"),this.onPresented(e))}onInterrupted(e){this.owns(e)&&e.clock.presenting&&(e.clock.onInterrupted(this.now()),this.armWatchdog(e),this.report(e))}onVideoEnded(e){if(!this.owns(e)||e.restarting)return;if(this.onInterrupted(e),e.clock.complete(this.now()))return void this.finish(e);e.loops++,e.lastMediaTimeSec=null,e.restarting=!0;const t=this.deps.getAdPlayer();t&&(t.seek(0),t.play().catch(t=>{this.owns(e)&&!a(t)&&this.fail(e,"load",this.toError(t))}))}tick(e){this.owns(e)&&this.report(e)}shareSec(e,t){return Math.max(0,e.remainderSec-e.clock.remainingMs(t)/1e3)}report(e){const t=this.now();this.state.currentAssetElapsedSec=this.shareSec(e,t),this.deps.onProgress({breakInfo:e.breakInfo,remainingSec:e.clock.remainingMs(t)/1e3,presenting:e.clock.presenting})}armDeadline(e){this.clearDeadline(e),e.deadline=setTimeout(()=>{e.deadline=null,this.owns(e)&&(e.clock.complete(this.now())?this.finish(e):this.armDeadline(e))},e.clock.remainingMs(this.now()))}armWatchdog(t){this.clearWatchdog(t),t.watchdog=setTimeout(()=>{t.watchdog=null,this.owns(t)&&!t.clock.presenting&&this.fail(t,"stalled",new e("Slate did not present in time","DA-AD-START-TIMEOUT"))},5e3)}clearDeadline(e){e.deadline&&clearTimeout(e.deadline),e.deadline=null}clearWatchdog(e){e.watchdog&&clearTimeout(e.watchdog),e.watchdog=null}finish(e){this.owns(e)&&(this.settle(e,"break-end"),this.deps.settled("slate-elapsed"))}fail(e,t,s){if(!this.owns(e))return;const{breakInfo:i,slate:a}=e;this.settle(e,"failed"),this.deps.diagnose("DA-SLATE-FAILED",`Slate ${a.asset.id} failed in break ${i.id} (${t}): ${s.message}`,{breakId:i.id,slateAssetId:a.asset.id,reason:t,error:s.message}),this.deps.emitAdError(i,a.asset,s),this.deps.settled("slate-errored")}settle(e,t){this.clearDeadline(e),this.clearWatchdog(e),e.ticker&&clearInterval(e.ticker),e.ticker=null;const s=this.now();e.clock.onInterrupted(s);const i=e.clock.presentedMs(s)/1e3;if(this.session=null,this.state.adSecondsPlayedThisBreak+="break-end"===t?Math.max(i,e.remainderSec):i,this.state.currentAssetElapsedSec=0,e.video?this.deps.getAdPlayer()?.pause():this.deps.clearOverlayImage(),"failed"===t)return;const{breakInfo:a,slate:n}=e;this.deps.diagnose("DA-SLATE-ENDED",`Slate ${n.asset.id} ended in break ${a.id} after ${r(i)}s (${t})`,{breakId:a.id,slateAssetId:n.asset.id,plannedSec:e.plannedSec,filledSec:r(i),loops:e.loops,interruptedSec:r(Math.max(0,(s-e.startedAtMs)/1e3-i)),breakOverrunSec:r((s-e.scheduledEndMs)/1e3),reason:t})}toError(t){if(t instanceof Error)return t;const s=t?.message;return new e("string"==typeof s&&s?s:"Slate playback error","DA-SLATE-FAILED")}}
@@ -24,7 +24,8 @@ export declare class VariantSelector {
24
24
  continueContentDuringBreakReason(format: BreakFormat): 'ok' | 'disabled' | 'not-replacement' | 'format' | 'shared-element' | 'picture-in-picture' | 'single-decoder';
25
25
  /** Device class plus the contextual playable-format set (PiP narrows to a single small surface). */
26
26
  selectionInputs(): VariantSelectionInputs;
27
- breakNothingToPlayReason(breakInfo: Break): NoPlayableAssetReason | null;
27
+ /** `slateFillsBreak`: the manifest slate covers a break with no ad to play, so such a break begins. */
28
+ breakNothingToPlayReason(breakInfo: Break, slateFillsBreak?: boolean): NoPlayableAssetReason | null;
28
29
  /**
29
30
  * GAM pods are only playable through a live IMA session; without one a GAM-only break is
30
31
  * treated as filtered out (fail-open) instead of erroring on its first asset. An
@@ -33,6 +34,14 @@ export declare class VariantSelector {
33
34
  onlyGamAssetsWithoutSession(breakInfo: Break, insertionMode: EffectiveInsertionMode, selection: VariantSelectionInputs): boolean;
34
35
  unknownVariantFormatsOf(breakInfo: Break): string[];
35
36
  diagnoseUnknownVariantFormats(breakInfo: Break): void;
37
+ /**
38
+ * Warn once per break begin when the SELECTED variant's L-shape `anchor` is an unrecognised
39
+ * string; the break still renders top_left. Resolved on the declared selected variant, so a
40
+ * downgraded break reports nothing — the box never renders there.
41
+ */
42
+ diagnoseLshapeAnchor(breakInfo: Break): void;
43
+ /** All break-begin variant warnings: skipped unrecognised formats plus an unrecognised `anchor` on the selected variant. */
44
+ diagnoseVariantAnomalies(breakInfo: Break): void;
36
45
  /** Manifest `targeting` + format-playability walk, with the last-resort downgrades behind it. */
37
46
  getVariant(breakInfo: Break): BreakVariant | null;
38
47
  }
@@ -1 +1 @@
1
- import{BreakFormat as e,Vendor as t,isVendorAsset as s}from"../../types/BreakManifest";import{detectDeviceProfile as i}from"../detectDeviceType";import{assetsOfVariant as n,breakPlanEmptyReason as r,extractAssets as o}from"../AdBreakSequencer";import{playableFormatsFor as a,selectVariantDetailed as d}from"../selectVariant";export class VariantSelector{constructor(e,t){this.state=e,this.deps=t}continueContentDuringBreakReason(t){return this.state.continueContentDuringBreak?"replacement"!==this.state.adInsertionType?"not-replacement":t!==e.SINGLE?"format":"shared-element"===this.deps.getEffectiveInsertionMode()?"shared-element":this.deps.getIsContentPictureInPicture()?"picture-in-picture":"single-decoder"===this.deps.preloadMode||!1===this.deps.getSupportsParallelBuffering()?"single-decoder":"ok":"disabled"}selectionInputs(){if(void 0===this.state.detectedDeviceProfile){const e=i();void 0!==this.state.detectedDeviceType?e.deviceType=this.state.detectedDeviceType:this.state.detectedDeviceType=e.deviceType,this.state.detectedDeviceProfile=e}return{deviceType:this.state.detectedDeviceType,deviceProfile:this.state.detectedDeviceProfile,playableFormats:a({pictureInPicture:this.deps.getIsContentPictureInPicture(),singleSurfaceDevice:this.deps.singleSurfaceDevice})}}breakNothingToPlayReason(e){const t=this.deps.getEffectiveInsertionMode(),s=this.selectionInputs(),i=r(e,t,s);return null!==i?i:this.onlyGamAssetsWithoutSession(e,t,s)?"no-assets":null}onlyGamAssetsWithoutSession(e,i,n){if(this.deps.getIsGamReady()||null!==this.state.gamInitPromise)return!1;const r=o(e,n),a="shared-element"===i?r.slice(0,1):r;return a.length>0&&a.every(e=>s(e)&&e.vendor===t.GAM)}unknownVariantFormatsOf(e){return d(e,this.selectionInputs()).skippedUnknownFormats}diagnoseUnknownVariantFormats(e){for(const t of this.unknownVariantFormatsOf(e))this.deps.diagnose("DA-BREAK-VARIANT-FORMAT-UNKNOWN",`break ${e.id}: variant format ${""===t?"(absent)":`"${t}"`} is not recognised; the variant was skipped.`,{breakId:e.id,format:t})}getVariant(t){const{selected:s,deviceQualified:i,targetingIssues:r}=d(t,this.selectionInputs());for(const e of r)this.deps.diagnose(e.code,`Targeting ${e.field} was rejected.`,{field:e.field,...void 0===e.value?{}:{value:e.value}});return s||(i?{format:e.SINGLE,assets:n(i).slice(0,1)}:null)}}
1
+ import{BreakFormat as e,Vendor as t,isVendorAsset as s}from"../../types/BreakManifest";import{detectDeviceProfile as i}from"../detectDeviceType";import{assetsOfVariant as n,breakPlanEmptyReason as o,extractAssets as r}from"../AdBreakSequencer";import{playableFormatsFor as a,selectVariantDetailed as d}from"../selectVariant";import{resolveLshapeAnchor as c}from"../resolveLshapeAnchor";export class VariantSelector{constructor(e,t){this.state=e,this.deps=t}continueContentDuringBreakReason(t){return this.state.continueContentDuringBreak?"replacement"!==this.state.adInsertionType?"not-replacement":t!==e.SINGLE?"format":"shared-element"===this.deps.getEffectiveInsertionMode()?"shared-element":this.deps.getIsContentPictureInPicture()?"picture-in-picture":"single-decoder"===this.deps.preloadMode||!1===this.deps.getSupportsParallelBuffering()?"single-decoder":"ok":"disabled"}selectionInputs(){if(void 0===this.state.detectedDeviceProfile){const e=i();void 0!==this.state.detectedDeviceType?e.deviceType=this.state.detectedDeviceType:this.state.detectedDeviceType=e.deviceType,this.state.detectedDeviceProfile=e}return{deviceType:this.state.detectedDeviceType,deviceProfile:this.state.detectedDeviceProfile,playableFormats:a({pictureInPicture:this.deps.getIsContentPictureInPicture(),singleSurfaceDevice:this.deps.singleSurfaceDevice})}}breakNothingToPlayReason(e,t=!1){const s=this.deps.getEffectiveInsertionMode(),i=this.selectionInputs(),n=o(e,s,i,t);return null!==n?n:!t&&this.onlyGamAssetsWithoutSession(e,s,i)?"no-assets":null}onlyGamAssetsWithoutSession(e,i,n){if(this.deps.getIsGamReady()||null!==this.state.gamInitPromise)return!1;const o=r(e,n),a="shared-element"===i?o.slice(0,1):o;return a.length>0&&a.every(e=>s(e)&&e.vendor===t.GAM)}unknownVariantFormatsOf(e){return d(e,this.selectionInputs()).skippedUnknownFormats}diagnoseUnknownVariantFormats(e){for(const t of this.unknownVariantFormatsOf(e))this.deps.diagnose("DA-BREAK-VARIANT-FORMAT-UNKNOWN",`break ${e.id}: variant format ${""===t?"(absent)":`"${t}"`} is not recognised; the variant was skipped.`,{breakId:e.id,format:t})}diagnoseLshapeAnchor(e){const{selected:t}=d(e,this.selectionInputs()),s=t?c(t)?.unknownValue:void 0;t&&void 0!==s&&this.deps.diagnose("DA-BREAK-LSHAPE-ANCHOR-UNKNOWN",`break ${e.id}: L-shape anchor "${s}" is not recognised; rendering top_left.`,{breakId:e.id,format:t.format,anchor:s})}diagnoseVariantAnomalies(e){this.diagnoseUnknownVariantFormats(e),this.diagnoseLshapeAnchor(e)}getVariant(t){const{selected:s,deviceQualified:i,targetingIssues:o}=d(t,this.selectionInputs());for(const e of o)this.deps.diagnose(e.code,`Targeting ${e.field} was rejected.`,{field:e.field,...void 0===e.value?{}:{value:e.value}});return s||(i?{format:e.SINGLE,assets:n(i).slice(0,1)}:null)}}
@@ -1 +1 @@
1
- import{PauseAdController as e}from"../PauseAdController";export function connectPauseAd(a,n){const o=a.pauseAdPresenter,s=new e({debug:n.debug});return s.setCallbacks({onShow:e=>{a.showPauseImage(e)},onHide:()=>a.hidePauseImage(),onResumeAllowed:()=>o.revealPauseAdResume(),diagnose:n.diagnose}),o.setPauseAdCreativeEndedCallback(e=>s.onCreativeEnded(e)),s}
1
+ import{PauseAdController as e}from"../PauseAdController";export function connectPauseAd(a,s){const o=a.pauseAdPresenter,d=new e({debug:s.debug});return d.setCallbacks({onShow:e=>{a.showPauseImage(e)},onHide:()=>a.hidePauseImage(),onResumeAllowed:()=>o.revealPauseAdResume(),diagnose:s.diagnose}),o.setPauseAdCreativeResolvedCallback((e,a)=>d.onCreativeResolved(e,a)),o.setPauseAdCreativeEndedCallback(e=>d.onCreativeEnded(e)),d}
@@ -1,4 +1,4 @@
1
- import { BreakFormat } from '../../../types/BreakManifest';
1
+ import { BreakFormat, LshapeAnchor } from '../../../types/BreakManifest';
2
2
  import { LayoutTransform } from './LayoutTransform';
3
3
  /** Where a layout places one stage surface, and whether the move is animated. */
4
4
  export interface LayoutPlacement {
@@ -12,8 +12,11 @@ export interface PresentationLayout {
12
12
  }
13
13
  /** Content returns to full size, animated — the placement restoreLayout() moves to. */
14
14
  export declare const RESTORED_CONTENT_PLACEMENT: LayoutPlacement;
15
+ /** The box transform for an anchor corner: same size and inset, the signs pick the edges. */
16
+ export declare function lshapeTransformFor(anchor: LshapeAnchor): LayoutTransform;
15
17
  /**
16
18
  * The presentation layout for a break format, or `null` when the format is not a layout state:
17
19
  * `overlay` positions its surface from manifest values and unknown formats keep the base style.
20
+ * `anchor` only matters for the L-shape formats (manifest spec 1.11.0, default `top_left`).
18
21
  */
19
- export declare function presentationLayoutFor(format: BreakFormat): PresentationLayout | null;
22
+ export declare function presentationLayoutFor(format: BreakFormat, anchor?: LshapeAnchor): PresentationLayout | null;
@@ -1 +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
+ import{BreakFormat as t,LshapeAnchor as n}from"../../../types/BreakManifest";import{isBottomAnchor as r,isRightAnchor as e}from"../../resolveLshapeAnchor";import{FULL_SIZE_TRANSFORM as a}from"./LayoutTransform";export const RESTORED_CONTENT_PLACEMENT={transform:a,visible:!0,transition:"animated"};const s=.4792,o=12.5;export function lshapeTransformFor(t){return{translateXPercent:e(t)?o:-12.5,translateYPercent:r(t)?o:-12.5,scale:.65}}export function presentationLayoutFor(r,e=n.TOP_LEFT){const o=lshapeTransformFor(e);switch(r){case t.SINGLE:return{content:{transform:a,visible:!0,transition:"animated"},ad:{transform:a,visible:!0,transition:"none"}};case t.DOUBLE:return{content:{transform:{translateXPercent:-25,translateYPercent:0,scale:s},visible:!0,transition:"animated"},ad:{transform:{translateXPercent:25,translateYPercent:0,scale:s},visible:!0,transition:"none"}};case t.LSHAPE_AD:return{content:{transform:o,visible:!1,transition:"animated"},ad:{transform:o,visible:!0,transition:"none"}};case t.LSHAPE_CONTENT:return{content:{transform:o,visible:!0,transition:"animated"},ad:{transform:a,visible:!0,transition:"none"}};default:return null}}
@@ -38,6 +38,7 @@ export interface BreakTimingDeps {
38
38
  pdtGraceSeconds: number;
39
39
  pdtGraceMs: number;
40
40
  }
41
+ export declare function peekPosition(state: BreakTimingState, player: PlayerAdapter): number | null;
41
42
  /**
42
43
  * Timebase position. Wallclock reads the stream's Program Date Time so breaks match the live
43
44
  * playhead. Without a usable PDT this returns `null` and the tick decides nothing, until the
@@ -1 +1 @@
1
- import{Timebase as e,getPostRollDelay as t,getPreRollDelay as a}from"../types/BreakManifest";export const PDT_PLAYBACK_SETTLE_MS=1e3;export function createBreakTimingState(){return{timebase:e.WALLCLOCK,pdtMissingWarned:!1,pdtSeen:!1,firstPdtAttemptMs:null,playbackStartedAtMs:null,anvatoPendingDiagnosed:new Set,anvatoMatchedDiagnosed:new Set,anvatoEndDiagnosed:new Set}}export function getCurrentTime(t,a){if(t.timebase!==e.WALLCLOCK)return 1e3*a.player.currentTime;const n=a.player.programDateTime;if(n){const e=n.getTime();if(!isNaN(e))return t.pdtSeen=!0,e}if(t.pdtSeen)return null;const r=a.clock.now();null===t.firstPdtAttemptMs&&(t.firstPdtAttemptMs=r),null===t.playbackStartedAtMs&&a.player.currentTime>0&&(t.playbackStartedAtMs=r);const i=null!==t.playbackStartedAtMs&&r-t.playbackStartedAtMs>=1e3,s=r-t.firstPdtAttemptMs>=a.pdtGraceMs;return i||s?(warnPdtMissing(t,a,i),r):null}export function warnPdtMissing(e,t,a){if(e.pdtMissingWarned)return;e.pdtMissingWarned=!0;const n=a?"content is playing and exposes none":`none appeared within ${t.pdtGraceSeconds}s`;t.log(`No Program Date Time on wallclock stream (${n}); falling back to the system clock`),t.diagnose?.("DA-PDT-MISSING",`No EXT-X-PROGRAM-DATE-TIME available (${n}); wallclock-timebase breaks are matched against the system clock instead of the stream position.`,{graceSeconds:t.pdtGraceSeconds,playbackStarted:a})}export function getBreakStartTime(t,a,n){if(t.timebase===e.WALLCLOCK){if("string"!=typeof n.start)return a.log("Invalid break start for wallclock timebase:",n.start),null;const e=new Date(n.start);return isNaN(e.getTime())?(a.log("Invalid break start date:",n.start),null):e.getTime()}return"number"!=typeof n.start?(a.log(`Invalid break start for ${t.timebase} timebase:`,n.start),null):a.cueTracker&&t.timebase===e.PTS?resolveAnvatoStartTime(t,a,n,n.start):1e3*n.start}export function resolveAnvatoStartTime(e,t,a,n){const r=t.cueTracker.mediaTimeForPts(n);return null===r?(e.anvatoPendingDiagnosed.has(a.id)||(e.anvatoPendingDiagnosed.add(a.id),t.log(`⌛ Break "${a.id}" awaiting Anvato cue for PTS ${n}`),t.diagnose?.("DA-ANVATO-CUE-PENDING",`No Anvato cue observed for break "${a.id}" (pts ${n}); break stays pending.`,{breakId:a.id,pts:n})),null):(e.anvatoMatchedDiagnosed.has(a.id)||(e.anvatoMatchedDiagnosed.add(a.id),t.log(`🎯 Break "${a.id}" matched Anvato cue: pts ${n} → ${r}s`),t.diagnose?.("DA-ANVATO-CUE-MATCHED",`Anvato cue matched for break "${a.id}": pts ${n} plays at media time ${r}s.`,{breakId:a.id,pts:n,mediaTime:r})),1e3*r)}export function diagnoseAnvatoBreakEnd(e,t,a){if(null===t.cueTracker||"number"!=typeof a.start)return;if(e.anvatoEndDiagnosed.has(a.id))return;const n=t.cueTracker.breakEnd(a.start,a.duration);if(null===n)return;e.anvatoEndDiagnosed.add(a.id);const r={breakId:a.id,mediaTime:n.mediaTime,lengthSec:n.lengthSec,durationSec:a.duration};n.accepted?(t.log(`Break "${a.id}" end signalled at media time ${n.mediaTime}s`),t.diagnose?.("DA-ANVATO-CUE-END-MATCHED",`Anvato cue_end matched for break "${a.id}": the stream ends it at media time ${n.mediaTime}s (${n.lengthSec}s on the stream clock).`,r)):t.diagnose?.("DA-ANVATO-CUE-END-REJECTED",`Anvato cue_end for break "${a.id}" signals ${n.lengthSec}s, not the ${a.duration}s duration; it is ignored.`,r)}export function secondsUntilBreak(e,t){const a=Math.round((e-t)/1e3);return a>0?a:0}export function describeBreakStart(e){const n=a(e);if(null!==n)return n>0?`playback start + ${n}s`:"playback start";const r=t(e);return null!==r?r>0?`playback end + ${r}s`:"playback end":"string"==typeof e.start?new Date(e.start).toLocaleTimeString():String(e.start)}
1
+ import{Timebase as e,getPostRollDelay as t,getPreRollDelay as a}from"../types/BreakManifest";export const PDT_PLAYBACK_SETTLE_MS=1e3;export function createBreakTimingState(){return{timebase:e.WALLCLOCK,pdtMissingWarned:!1,pdtSeen:!1,firstPdtAttemptMs:null,playbackStartedAtMs:null,anvatoPendingDiagnosed:new Set,anvatoMatchedDiagnosed:new Set,anvatoEndDiagnosed:new Set}}export function peekPosition(t,a){if(t.timebase!==e.WALLCLOCK)return 1e3*a.currentTime;const n=a.programDateTime?.getTime();return void 0===n||isNaN(n)?null:n}export function getCurrentTime(t,a){if(t.timebase!==e.WALLCLOCK)return 1e3*a.player.currentTime;const n=a.player.programDateTime;if(n){const e=n.getTime();if(!isNaN(e))return t.pdtSeen=!0,e}if(t.pdtSeen)return null;const r=a.clock.now();null===t.firstPdtAttemptMs&&(t.firstPdtAttemptMs=r),null===t.playbackStartedAtMs&&a.player.currentTime>0&&(t.playbackStartedAtMs=r);const i=null!==t.playbackStartedAtMs&&r-t.playbackStartedAtMs>=1e3,s=r-t.firstPdtAttemptMs>=a.pdtGraceMs;return i||s?(warnPdtMissing(t,a,i),r):null}export function warnPdtMissing(e,t,a){if(e.pdtMissingWarned)return;e.pdtMissingWarned=!0;const n=a?"content is playing and exposes none":`none appeared within ${t.pdtGraceSeconds}s`;t.log(`No Program Date Time on wallclock stream (${n}); falling back to the system clock`),t.diagnose?.("DA-PDT-MISSING",`No EXT-X-PROGRAM-DATE-TIME available (${n}); wallclock-timebase breaks are matched against the system clock instead of the stream position.`,{graceSeconds:t.pdtGraceSeconds,playbackStarted:a})}export function getBreakStartTime(t,a,n){if(t.timebase===e.WALLCLOCK){if("string"!=typeof n.start)return a.log("Invalid break start for wallclock timebase:",n.start),null;const e=new Date(n.start);return isNaN(e.getTime())?(a.log("Invalid break start date:",n.start),null):e.getTime()}return"number"!=typeof n.start?(a.log(`Invalid break start for ${t.timebase} timebase:`,n.start),null):a.cueTracker&&t.timebase===e.PTS?resolveAnvatoStartTime(t,a,n,n.start):1e3*n.start}export function resolveAnvatoStartTime(e,t,a,n){const r=t.cueTracker.mediaTimeForPts(n);return null===r?(e.anvatoPendingDiagnosed.has(a.id)||(e.anvatoPendingDiagnosed.add(a.id),t.log(`⌛ Break "${a.id}" awaiting Anvato cue for PTS ${n}`),t.diagnose?.("DA-ANVATO-CUE-PENDING",`No Anvato cue observed for break "${a.id}" (pts ${n}); break stays pending.`,{breakId:a.id,pts:n})),null):(e.anvatoMatchedDiagnosed.has(a.id)||(e.anvatoMatchedDiagnosed.add(a.id),t.log(`🎯 Break "${a.id}" matched Anvato cue: pts ${n} → ${r}s`),t.diagnose?.("DA-ANVATO-CUE-MATCHED",`Anvato cue matched for break "${a.id}": pts ${n} plays at media time ${r}s.`,{breakId:a.id,pts:n,mediaTime:r})),1e3*r)}export function diagnoseAnvatoBreakEnd(e,t,a){if(null===t.cueTracker||"number"!=typeof a.start)return;if(e.anvatoEndDiagnosed.has(a.id))return;const n=t.cueTracker.breakEnd(a.start,a.duration);if(null===n)return;e.anvatoEndDiagnosed.add(a.id);const r={breakId:a.id,mediaTime:n.mediaTime,lengthSec:n.lengthSec,durationSec:a.duration};n.accepted?(t.log(`Break "${a.id}" end signalled at media time ${n.mediaTime}s`),t.diagnose?.("DA-ANVATO-CUE-END-MATCHED",`Anvato cue_end matched for break "${a.id}": the stream ends it at media time ${n.mediaTime}s (${n.lengthSec}s on the stream clock).`,r)):t.diagnose?.("DA-ANVATO-CUE-END-REJECTED",`Anvato cue_end for break "${a.id}" signals ${n.lengthSec}s, not the ${a.duration}s duration; it is ignored.`,r)}export function secondsUntilBreak(e,t){const a=Math.round((e-t)/1e3);return a>0?a:0}export function describeBreakStart(e){const n=a(e);if(null!==n)return n>0?`playback start + ${n}s`:"playback start";const r=t(e);return null!==r?r>0?`playback end + ${r}s`:"playback end":"string"==typeof e.start?new Date(e.start).toLocaleTimeString():String(e.start)}
@@ -0,0 +1,5 @@
1
+ import type { Break } from '../types/BreakManifest';
2
+ import type { TrackedBreak } from './RewindRearm';
3
+ import type { BreakStartCallback } from './BreakSchedulerTypes';
4
+ /** Event delays are playback-relative for pre-rolls and wallclock-relative for post-rolls. */
5
+ export declare function checkEventBreak(tracked: TrackedBreak, delaySec: number, elapsedMs: number | null, adStartDelayMs: number, held: boolean, preload: (brk: Break, label: string) => void, skip: (tracked: TrackedBreak) => void, start: BreakStartCallback): void;
@@ -0,0 +1 @@
1
+ import{BreakState as e}from"./BreakSchedulerTypes";export function checkEventBreak(r,n,t,a,o,c,i,u){if(null===t)return;const d=1e3*n;if(a>0&&d<a)return void i(r);const k=Math.round((d-t)/1e3);k>0&&k<=8&&c(r.break,`event, ${k}s ahead`),t>=d&&!o&&(r.state=e.ACTIVE,u(r.break,{tuneIn:!1,elapsedSec:0,remainingSec:r.break.duration??0}))}
@@ -1,5 +1,6 @@
1
1
  import { BreakManifest } from '../types/BreakManifest';
2
2
  import type { ChannelEventIssue } from './resolveChannelEvents';
3
+ import type { SlateIssue } from './resolveSlate';
3
4
  /**
4
5
  * Supported major version of the break manifest.
5
6
  */
@@ -13,4 +14,4 @@ export declare const SUPPORTED_MANIFEST_MAJOR_VERSION = 1;
13
14
  *
14
15
  * @throws Error when the data is not a valid v1 break manifest.
15
16
  */
16
- export declare function parseBreakManifest(data: unknown, onEventIssue?: (issue: ChannelEventIssue) => void): BreakManifest;
17
+ export declare function parseBreakManifest(data: unknown, onEventIssue?: (issue: ChannelEventIssue) => void, onSlateIssue?: (issue: SlateIssue) => void): BreakManifest;
@@ -1 +1 @@
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")}
1
+ import{parseChannelEvents as e}from"./resolveChannelEvents";import{resolveSlate as r}from"./resolveSlate";export const SUPPORTED_MANIFEST_MAJOR_VERSION=1;export function parseBreakManifest(n,i,o){if(!n||"object"!=typeof n)throw new Error("Invalid manifest: expected object");const a=n;if("string"!=typeof a.version)throw new Error("Invalid manifest: missing version");if(1!==parseInt(a.version.split(".")[0],10))throw new Error(`Unsupported manifest version: ${a.version}. Supported major version: 1`);const s=a.timebase;if("wallclock"!==s&&"pts"!==s&&"mediatime"!==s)throw new Error("Invalid manifest: invalid or missing timebase");if(!Array.isArray(a.breaks))throw new Error("Invalid manifest: breaks must be an array");for(const e of a.breaks)t(e);if(void 0!==a.delivery){if(!Array.isArray(a.delivery)||0===a.delivery.length)throw new Error("Invalid manifest: delivery must be a non-empty array");for(const e of a.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!==a.vendorConfiguration&&!Array.isArray(a.vendorConfiguration))throw new Error("Invalid manifest: vendorConfiguration must be an array");if(void 0!==a.adStartDelay&&("number"!=typeof a.adStartDelay||!Number.isFinite(a.adStartDelay)||a.adStartDelay<0))throw new Error("Invalid manifest: adStartDelay must be a number >= 0");const f=a,v=e(a.events,s);v.issues.forEach(e=>i?.(e));const l={...f};delete l.events;const d=r(a.slate);return d.issue&&o?.(d.issue),null===d.slate&&delete l.slate,{...l,...void 0===v.events?{}:{events:v.events}}}function t(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,18 @@
1
+ export type VastPauseVideoEvent = 'start' | 'firstQuartile' | 'midpoint' | 'thirdQuartile' | 'complete';
2
+ export interface VastPauseCreative {
3
+ kind: 'image' | 'video';
4
+ url: string;
5
+ clickThrough?: string;
6
+ impressions: string[];
7
+ clickTracking: string[];
8
+ closeTracking: string[];
9
+ videoTracking: Record<VastPauseVideoEvent, string[]>;
10
+ }
11
+ export interface VastPauseResult {
12
+ creative: VastPauseCreative | null;
13
+ reason: 'no-creative' | 'fetch-failed' | 'wrapper-depth-exceeded' | null;
14
+ }
15
+ /** Select one NonLinear, companion image, or progressive video, in that order. */
16
+ export declare function parseVastPauseCreative(xml: string): VastPauseCreative | null;
17
+ /** Follow at most five wrapper URLs; fetching and diagnostics remain caller-owned. */
18
+ export declare function resolveVastPauseCreative(xml: string, fetchXml: (url: string) => Promise<string>): Promise<VastPauseResult>;
@@ -0,0 +1 @@
1
+ import{parseVastCompanionImage as e}from"./parseVastCompanion";import{parseVastLinearMediaFile as i}from"./parseVastLinear";function r(e,i){const r=new RegExp(`<${i}\\b([^>]*)>([\\s\\S]*?)<\\/${i}>`,"gi"),t=[];let n;for(;null!==(n=r.exec(e));)t.push({attributes:n[1],body:n[2],start:n.index,end:r.lastIndex});return t}function t(e){return{start:e("start"),firstQuartile:e("firstQuartile"),midpoint:e("midpoint"),thirdQuartile:e("thirdQuartile"),complete:e("complete")}}function n(e){const i=/<!\[CDATA\[([\s\S]*?)\]\]>/.exec(e);return i?i[1].trim():e.trim().replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&quot;/g,'"').replace(/&apos;/g,"'").replace(/&amp;/g,"&")}function o(e,i){return r(e,i).map(e=>n(e.body)).filter(Boolean)}function a(e,i){return new RegExp(`\\b${i}\\s*=\\s*["']([^"']*)["']`,"i").exec(e)?.[1]??""}function c(e,i){return r(e,"Tracking").filter(e=>i.some(i=>i.toLowerCase()===a(e.attributes,"event").toLowerCase())).map(e=>n(e.body)).filter(Boolean)}function s(e,i,n,a,s,l){const u="NonLinearClick"===s?r(a,"NonLinearAds").find(e=>e.body.includes(n)):void 0,d=(u?.body.replace(/<NonLinear\b[^>]*>[\s\S]*?<\/NonLinear>/gi,"")??"")+n,p=o(n,s+"Through")[0],f=r(a,"Ad").find(e=>e.start<=l&&l<e.end);return{kind:e,url:i,...p?{clickThrough:p}:{},impressions:[...o(f?.body??a,"Impression"),...c(d,["creativeView"])],clickTracking:o(n,s+"Tracking"),closeTracking:c(d,["close","closeLinear"]),videoTracking:t(i=>"video"===e?c(d,[i]):[])}}export function parseVastPauseCreative(t){for(const e of r(t,"NonLinear")){for(const i of r(e.body,"StaticResource")){const r=a(i.attributes,"creativeType").trim(),o=/^image\//i.test(r)?"image":/^video\//i.test(r)?"video":null,c=n(i.body);if(o&&c)return s(o,c,e.body,t,"NonLinearClick",e.start)}for(const i of r(e.body,"MediaFile")){const r=a(i.attributes,"type").trim(),o=a(i.attributes,"delivery"),c=n(i.body);if(/^video\//i.test(r)&&(!o||/^progressive$/i.test(o))&&c)return s("video",c,e.body,t,"NonLinearClick",e.start)}}for(const i of r(t,"Companion")){const r=e(`<Companion>${i.body}</Companion>`);if(r)return s("image",r.imageUrl,i.body,t,"CompanionClick",i.start)}const o=i(t);if(!o)return null;const c=r(t,"MediaFile").find(e=>i(`<MediaFile${e.attributes}>${e.body}</MediaFile>`)),l=s("video",o.mediaFileUrl,r(t,"Linear")[0]?.body??t,t,"Click",c?.start??0);return o.clickThrough&&(l.clickThrough=o.clickThrough),l}function l(e,i){return{...i,impressions:[...o(e,"Impression"),...c(e,["creativeView"]),...i.impressions],clickTracking:[...o(e,"NonLinearClickTracking"),...o(e,"CompanionClickTracking"),...o(e,"ClickTracking"),...i.clickTracking],closeTracking:[...c(e,["close","closeLinear"]),...i.closeTracking],videoTracking:t(r=>[..."video"===i.kind?c(e,[r]):[],...i.videoTracking[r]])}}export async function resolveVastPauseCreative(e,i){const t=[];let n=e;for(;;){const e=r(n,"Wrapper")[0];if(!e){let e=parseVastPauseCreative(n);if(!e)return{creative:null,reason:"no-creative"};for(const i of t.reverse())e=l(i,e);return{creative:e,reason:null}}const a=o(e.body,"VASTAdTagURI")[0];if(!a)return{creative:null,reason:"no-creative"};if(t.length>=5)return{creative:null,reason:"wrapper-depth-exceeded"};t.push(e.body);try{n=await i(a)}catch{return{creative:null,reason:"fetch-failed"}}}}
@@ -0,0 +1,25 @@
1
+ import { LshapeAnchor } from '../types/BreakManifest';
2
+ import type { BreakVariant } from '../types/BreakManifest';
3
+ /** The corner an L-shape variant renders in, plus the raw value when it was not recognised. */
4
+ export interface LshapeAnchorResolution {
5
+ anchor: LshapeAnchor;
6
+ /** The manifest string when it matched no known corner; the box then renders `top_left`. */
7
+ unknownValue?: string;
8
+ }
9
+ /** True when `anchor` places the box against the right edge. */
10
+ export declare function isRightAnchor(anchor: LshapeAnchor): boolean;
11
+ /** True when `anchor` places the box against the bottom edge. */
12
+ export declare function isBottomAnchor(anchor: LshapeAnchor): boolean;
13
+ /**
14
+ * Resolve the manifest `anchor` of an L-shape variant, as a pure function shared (by behavioural
15
+ * contract) with the Kotlin and Swift cores and locked by the `lshape-anchor` conformance fixture.
16
+ *
17
+ * - Not an L-shape format: `null`, the field does not apply.
18
+ * - Absent or not a string: `top_left`, silently (manifest spec 1.11.0 default).
19
+ * - A known corner: that corner.
20
+ * - Any other string: `top_left` plus `unknownValue`, so the caller can warn without dropping the
21
+ * break (the spec's forward-compatibility rule for this enum).
22
+ *
23
+ * The field is read as `unknown`: the declared enum type does not guard against future spec values.
24
+ */
25
+ export declare function resolveLshapeAnchor(variant: BreakVariant): LshapeAnchorResolution | null;
@@ -0,0 +1 @@
1
+ import{BreakFormat as T,LshapeAnchor as n}from"../types/BreakManifest";const r=[n.TOP_LEFT,n.TOP_RIGHT,n.BOTTOM_LEFT,n.BOTTOM_RIGHT];export function isRightAnchor(T){return T===n.TOP_RIGHT||T===n.BOTTOM_RIGHT}export function isBottomAnchor(T){return T===n.BOTTOM_LEFT||T===n.BOTTOM_RIGHT}export function resolveLshapeAnchor(o){if(o.format!==T.LSHAPE_AD&&o.format!==T.LSHAPE_CONTENT)return null;const t=o.anchor;return"string"!=typeof t?{anchor:n.TOP_LEFT}:r.includes(t)?{anchor:t}:{anchor:n.TOP_LEFT,unknownValue:t}}