@dolby-optiview/ads-sdk-core 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/OptiViewAds.d.ts +5 -4
  2. package/dist/OptiViewAds.js +1 -1
  3. package/dist/OptiViewAdsExternalVendors.d.ts +32 -0
  4. package/dist/external-playback/index.d.ts +4 -0
  5. package/dist/external-playback/index.js +1 -0
  6. package/dist/external-playback/observeExternalPlayback.d.ts +19 -0
  7. package/dist/external-playback/observeExternalPlayback.js +1 -0
  8. package/dist/external-playback/reconnectHold.d.ts +19 -0
  9. package/dist/external-playback/reconnectHold.js +1 -0
  10. package/dist/generated/diagnosticCodes.d.ts +46 -6
  11. package/dist/generated/diagnosticCodes.js +1 -1
  12. package/dist/generated/version.d.ts +1 -1
  13. package/dist/generated/version.js +1 -1
  14. package/dist/index.d.ts +7 -5
  15. package/dist/index.js +1 -1
  16. package/dist/interfaces/AdSourceQueue.d.ts +19 -0
  17. package/dist/interfaces/AdSourceQueue.js +1 -0
  18. package/dist/interfaces/PlayerAdapter.d.ts +31 -1
  19. package/dist/internal/index.d.ts +2 -0
  20. package/dist/internal/index.js +1 -1
  21. package/dist/services/AdBreakSequencer.d.ts +33 -4
  22. package/dist/services/AdBreakSequencer.js +1 -1
  23. package/dist/services/AdPlayerController.d.ts +12 -73
  24. package/dist/services/AdPlayerController.js +1 -1
  25. package/dist/services/BreakChain.d.ts +43 -0
  26. package/dist/services/BreakChain.js +1 -0
  27. package/dist/services/BreakChainRecorder.d.ts +63 -0
  28. package/dist/services/BreakChainRecorder.js +1 -0
  29. package/dist/services/BreakPreloadLedger.d.ts +13 -0
  30. package/dist/services/BreakPreloadLedger.js +1 -0
  31. package/dist/services/BreakScheduler.d.ts +20 -6
  32. package/dist/services/BreakScheduler.js +1 -1
  33. package/dist/services/BreakTransitionTimer.d.ts +27 -2
  34. package/dist/services/BreakTransitionTimer.js +1 -1
  35. package/dist/services/CmcdAdReporter.d.ts +1 -0
  36. package/dist/services/CmcdAdReporter.js +1 -1
  37. package/dist/services/ExternalPlaybackGate.d.ts +40 -0
  38. package/dist/services/ExternalPlaybackGate.js +1 -0
  39. package/dist/services/LiveStartPushbackController.d.ts +5 -10
  40. package/dist/services/LiveStartPushbackController.js +1 -1
  41. package/dist/services/ManifestService.d.ts +2 -1
  42. package/dist/services/ManifestService.js +1 -1
  43. package/dist/services/ReloadGuard.d.ts +11 -0
  44. package/dist/services/ReloadGuard.js +1 -0
  45. package/dist/services/RewindRearm.d.ts +3 -1
  46. package/dist/services/RewindRearm.js +1 -1
  47. package/dist/services/SsaiController.js +1 -1
  48. package/dist/services/adPlayer/AdPlayerCallbacks.d.ts +63 -0
  49. package/dist/services/adPlayer/AdPlayerCallbacks.js +1 -0
  50. package/dist/services/adPlayer/AdPlayerEvents.d.ts +3 -7
  51. package/dist/services/adPlayer/AdPlayerEvents.js +1 -1
  52. package/dist/services/adPlayer/AdPlayerState.d.ts +0 -1
  53. package/dist/services/adPlayer/AdPlayerState.js +1 -1
  54. package/dist/services/adPlayer/AdPreloader.d.ts +2 -2
  55. package/dist/services/adPlayer/AdPreloader.js +1 -1
  56. package/dist/services/adPlayer/AdStage.d.ts +7 -4
  57. package/dist/services/adPlayer/AdStage.js +1 -1
  58. package/dist/services/adPlayer/AssetParameterResolver.d.ts +10 -2
  59. package/dist/services/adPlayer/AssetParameterResolver.js +1 -1
  60. package/dist/services/adPlayer/AssetPlayback.d.ts +9 -2
  61. package/dist/services/adPlayer/AssetPlayback.js +1 -1
  62. package/dist/services/adPlayer/AudioController.d.ts +58 -0
  63. package/dist/services/adPlayer/AudioController.js +1 -0
  64. package/dist/services/adPlayer/BreakSequenceDriver.d.ts +9 -3
  65. package/dist/services/adPlayer/BreakSequenceDriver.js +1 -1
  66. package/dist/services/adPlayer/ContentResumeCoordinator.d.ts +2 -0
  67. package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -1
  68. package/dist/services/adPlayer/GamPodBridge.d.ts +11 -1
  69. package/dist/services/adPlayer/GamPodBridge.js +1 -1
  70. package/dist/services/adPlayer/PauseAdPresenter.d.ts +3 -1
  71. package/dist/services/adPlayer/PauseAdPresenter.js +1 -1
  72. package/dist/services/adPlayer/PreparedPresentation.js +1 -1
  73. package/dist/services/adPlayer/PresentationModeTracker.d.ts +1 -1
  74. package/dist/services/adPlayer/PresentationModeTracker.js +1 -1
  75. package/dist/services/adPlayer/SharedElementBreakPlayer.d.ts +3 -0
  76. package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
  77. package/dist/services/adPlayer/SuccessorQueue.d.ts +36 -0
  78. package/dist/services/adPlayer/SuccessorQueue.js +1 -0
  79. package/dist/services/adPlayer/SurfaceRevealReporter.d.ts +28 -0
  80. package/dist/services/adPlayer/SurfaceRevealReporter.js +1 -0
  81. package/dist/services/adPlayer/presentation/DomPresentationHost.d.ts +22 -0
  82. package/dist/services/adPlayer/presentation/DomPresentationHost.js +1 -0
  83. package/dist/services/adPlayer/presentation/LayoutTransform.d.ts +28 -0
  84. package/dist/services/adPlayer/presentation/LayoutTransform.js +1 -0
  85. package/dist/services/adPlayer/presentation/PresentationLayout.d.ts +19 -0
  86. package/dist/services/adPlayer/presentation/PresentationLayout.js +1 -0
  87. package/dist/services/assetParameterMacroTable.js +1 -1
  88. package/dist/services/assetParameterMacros.d.ts +9 -9
  89. package/dist/services/assetParameterMacros.js +1 -1
  90. package/dist/services/channelSwitch.d.ts +2 -2
  91. package/dist/services/externalPlaybackDiagnostic.d.ts +3 -0
  92. package/dist/services/externalPlaybackDiagnostic.js +1 -0
  93. package/dist/services/externalPlaybackGuard.d.ts +24 -0
  94. package/dist/services/externalPlaybackGuard.js +1 -0
  95. package/dist/services/parseBreakManifest.d.ts +2 -1
  96. package/dist/services/parseBreakManifest.js +1 -1
  97. package/dist/services/resolveChannelEvents.d.ts +17 -0
  98. package/dist/services/resolveChannelEvents.js +1 -0
  99. package/dist/services/resolveExternalVendorConfigurations.js +1 -1
  100. package/dist/services/resolveGamVendorConfiguration.d.ts +6 -1
  101. package/dist/services/resolveGamVendorConfiguration.js +1 -1
  102. package/dist/services/ssbStream.d.ts +39 -0
  103. package/dist/services/ssbStream.js +1 -0
  104. package/dist/services/ssbSwitchSignals.d.ts +15 -0
  105. package/dist/services/ssbSwitchSignals.js +1 -0
  106. package/dist/services/staticAssetUrl.d.ts +28 -0
  107. package/dist/services/staticAssetUrl.js +1 -0
  108. package/dist/session/AdBreakStatusTracker.d.ts +18 -0
  109. package/dist/session/AdBreakStatusTracker.js +1 -1
  110. package/dist/session/BreakLifecycle.d.ts +6 -0
  111. package/dist/session/BreakLifecycle.js +1 -1
  112. package/dist/session/SessionStarter.d.ts +6 -43
  113. package/dist/session/SessionStarter.js +1 -1
  114. package/dist/session/SessionStarterDeps.d.ts +39 -0
  115. package/dist/session/SessionStarterDeps.js +1 -0
  116. package/dist/session/SessionStarterHelpers.d.ts +36 -0
  117. package/dist/session/SessionStarterHelpers.js +1 -0
  118. package/dist/session/SessionState.d.ts +22 -2
  119. package/dist/session/SessionState.js +1 -1
  120. package/dist/session/SsbSwitch.d.ts +55 -0
  121. package/dist/session/SsbSwitch.js +1 -0
  122. package/dist/session/breakMeasurement.d.ts +16 -0
  123. package/dist/session/breakMeasurement.js +1 -0
  124. package/dist/session/contentSnapback.d.ts +13 -0
  125. package/dist/session/contentSnapback.js +1 -0
  126. package/dist/session/createExternalPlaybackGate.d.ts +19 -0
  127. package/dist/session/createExternalPlaybackGate.js +1 -0
  128. package/dist/session/sessionTick.d.ts +7 -0
  129. package/dist/session/sessionTick.js +1 -0
  130. package/dist/types/BreakManifest.d.ts +58 -5
  131. package/dist/types/Events.d.ts +21 -10
  132. package/dist/types/ExternalVendorConfiguration.d.ts +3 -1
  133. package/dist/types/OptiViewAdsConfig.d.ts +24 -8
  134. package/package.json +5 -1
@@ -0,0 +1,39 @@
1
+ import type { GamSsbConfiguration } from '../types/BreakManifest';
2
+ import { type AssetParameterMacroTable } from './assetParameterMacros';
3
+ export interface SsbStreamParameterLayers {
4
+ /** `ssb.assetParameters`; the highest layer, as the manifest outranks the client everywhere. */
5
+ manifest?: Record<string, string> | undefined;
6
+ /** `SessionConfig.assetParameters`; the lowest layer. */
7
+ session?: Record<string, string> | undefined;
8
+ /** `updateAssetParameters()` overrides. */
9
+ runtime?: Record<string, string> | undefined;
10
+ }
11
+ /**
12
+ * The SSB stream request URL: `ssb.uri` plus the merged asset parameters as query parameters.
13
+ * Layers merge session < runtime < manifest per key (see `resolveAssetParameters`), macros resolve like every other asset
14
+ * parameter (an empty or null macro drops the parameter), and keys are sorted by
15
+ * {@link appendQueryParameters}, which also keeps an existing query and any fragment.
16
+ */
17
+ export declare function buildSsbStreamUrl(ssb: Pick<GamSsbConfiguration, 'uri'>, layers: SsbStreamParameterLayers, macros: AssetParameterMacroTable): string;
18
+ /** Where the previous source resumes after the SSB stream: the live edge (`null`) or the clamped position. */
19
+ export declare function resolveSsbRestorePosition(input: {
20
+ live: boolean;
21
+ recordedPosition: number;
22
+ duration: number | null;
23
+ }): number | null;
24
+ /** Ids of the breaks whose start passed while the SSB stream played, in input order. */
25
+ export declare function selectBreaksPlayedBySsb(pending: Array<{
26
+ id: string;
27
+ startTime: number;
28
+ }>, schedulerNow: number): string[];
29
+ /** Skips the tracked breaks whose start (non-null) passed `now`; returns their ids. */
30
+ export declare function skipBreaksStartedBy<T extends {
31
+ break: {
32
+ id: string;
33
+ };
34
+ }>(now: number | null, tracked: T[], startTime: (entry: T) => number | null, skip: (entry: T) => void): string[];
35
+ /** The break with the fewest seconds until it (ties: smaller id); breaks without a known time are ignored. */
36
+ export declare function soonestBreak(ids: string[], secondsUntil: (id: string) => number | null): {
37
+ id: string;
38
+ secondsUntil: number;
39
+ } | null;
@@ -0,0 +1 @@
1
+ import{appendQueryParameters as e,applyAssetParameterMacros as t}from"./assetParameterMacros";import{resolveAssetParameters as r}from"./resolveAssetParameters";export function buildSsbStreamUrl(n,o,s){const i=r(o.session,o.runtime,o.manifest,void 0);return e(n.uri,t(i,s).params)}export function resolveSsbRestorePosition(e){return e.live?null:Math.max(0,Math.min(e.recordedPosition,e.duration??e.recordedPosition))}export function selectBreaksPlayedBySsb(e,t){return e.filter(e=>e.startTime<=t).map(e=>e.id)}export function skipBreaksStartedBy(e,t,r,n){if(null===e)return[];const o=t.flatMap(e=>{const t=r(e);return null===t?[]:[{id:e.break.id,startTime:t,entry:e}]}),s=new Set(selectBreaksPlayedBySsb(o,e)),i=o.filter(e=>s.has(e.id));for(const e of i)n(e.entry);return i.map(e=>e.id)}export function soonestBreak(e,t){let r=null;for(const n of e){const e=t(n);null!==e&&((null===r||e<r.secondsUntil||e===r.secondsUntil&&n<r.id)&&(r={id:n,secondsUntil:e}))}return r}
@@ -0,0 +1,15 @@
1
+ export type SsbSwitchPhase = 'switching' | 'active' | 'restoring' | 'failed';
2
+ /** The `adchannelchange` due when the SSB stream is left: only after the switch was announced. */
3
+ export declare function resolveSsbRestoreChange(announced: boolean, restoredMode: 'sgai' | 'ssai'): {
4
+ previousDeliveryMode: 'ssb';
5
+ deliveryMode: 'sgai' | 'ssai';
6
+ } | null;
7
+ /** `adbreakstatus.externalPlaybackAllowed`: absent when the gate is unmanaged, always true in `ssb`, otherwise the guard answer outside an active break. */
8
+ export declare function resolveStatusExternalPlaybackAllowed(gateAllowed: boolean | undefined, statusPhase: 'idle' | 'upcoming' | 'active' | 'complete', deliveryMode: 'sgai' | 'ssai' | 'ssb' | undefined): boolean | undefined;
9
+ export type ExternalPlaybackRequestTrigger = 'connecting' | 'active';
10
+ /** Which gate evaluation an `externalplaybackchange` is: `connecting` always; `active` only when the
11
+ * route was `inactive` before and no blocked request is pending (THEOplayer reports connected without
12
+ * connecting). Null for every other change. */
13
+ export declare function resolveExternalPlaybackRequestTrigger(state: string, previousState: string, pending: boolean): ExternalPlaybackRequestTrigger | null;
14
+ /** A gate request is evaluated only while no SSB switch owns the route. */
15
+ export declare function shouldEvaluateExternalPlaybackRequest(ssbPhase: SsbSwitchPhase | null): boolean;
@@ -0,0 +1 @@
1
+ export function resolveSsbRestoreChange(e,t){return e?{previousDeliveryMode:"ssb",deliveryMode:t}:null}export function resolveStatusExternalPlaybackAllowed(e,t,n){if(void 0!==e)return"ssb"===n||e&&"active"!==t}export function resolveExternalPlaybackRequestTrigger(e,t,n){return"connecting"===e?"connecting":"active"!==e||"inactive"!==t||n?null:"active"}export function shouldEvaluateExternalPlaybackRequest(e){return null===e}
@@ -0,0 +1,28 @@
1
+ import { AppliedAssetParameters, AssetParameterMacroTable } from './assetParameterMacros';
2
+ /** The layers that apply to a static asset request, lowest precedence first. */
3
+ export interface StaticAssetParameterLayers {
4
+ /** `SessionConfig.assetParameters`. */
5
+ sessionConfigParameters?: Record<string, string> | undefined;
6
+ /** What `updateAssetParameters()` last set. */
7
+ updateParameters?: Record<string, string> | undefined;
8
+ /** The static asset's own manifest `assetParameters`. */
9
+ assetParameters?: Record<string, string> | undefined;
10
+ }
11
+ export interface StaticAssetUrl {
12
+ /** The URL to request; the input URL unchanged when no parameter is left to send. */
13
+ url: string;
14
+ /** The substituted parameters and the macros to diagnose. */
15
+ applied: AppliedAssetParameters;
16
+ }
17
+ /**
18
+ * The request URL of a static asset (resource URL, master playlist or MPD), as a pure
19
+ * function shared by behavioural contract with the Kotlin and Swift cores and locked
20
+ * by the `static-asset-parameters` conformance fixture.
21
+ *
22
+ * The application layers and the asset's own `assetParameters` are merged per key
23
+ * (asset wins); manifest session-level parameters (`vendorConfiguration` gam
24
+ * `sgai`/`ssai`) only target GAM and never apply here. Macros are substituted with
25
+ * the result is appended with {@link appendQueryParameters}.
26
+ * A `data:` URL is returned as-is without evaluating any macro.
27
+ */
28
+ export declare function buildStaticAssetUrl(url: string, layers: StaticAssetParameterLayers, macros: AssetParameterMacroTable): StaticAssetUrl;
@@ -0,0 +1 @@
1
+ import{appendQueryParameters as e,applyAssetParameterMacros as r}from"./assetParameterMacros";import{resolveAssetParameters as t}from"./resolveAssetParameters";const a={params:void 0,unresolved:[],omitted:[]};export function buildStaticAssetUrl(s,o,i){if(/^data:/i.test(s))return{url:s,applied:a};const m=t(o.sessionConfigParameters,o.updateParameters,void 0,o.assetParameters),p=r(m,i);return{url:e(s,p.params),applied:p}}
@@ -1,4 +1,5 @@
1
1
  import { Asset, Break, BreakFormat } from '../types/BreakManifest';
2
+ import type { SessionDeliveryMode } from '../types/OptiViewAdsConfig';
2
3
  import { AdBreakStatus, OptiViewAdsEventMap, OptiViewAdsEventType } from '../types/Events';
3
4
  import { BreakTransitionTimer } from '../services/BreakTransitionTimer';
4
5
  export type EmitEvent = <T extends OptiViewAdsEventType>(event: T, data: OptiViewAdsEventMap[T]) => void;
@@ -6,6 +7,10 @@ export interface AdBreakStatusTrackerDeps {
6
7
  emit: EmitEvent;
7
8
  effectiveFormatOf: (breakInfo: Break) => BreakFormat | undefined;
8
9
  secondsUntilBreak: (breakId: string) => number | undefined;
10
+ /** Whether AirPlay may start now; undefined when the SDK does not manage the gate. */
11
+ externalPlaybackAllowed: () => boolean | undefined;
12
+ /** The active session's delivery mode; undefined between sessions. */
13
+ deliveryMode: () => SessionDeliveryMode | undefined;
9
14
  breakTransitionTimer: () => BreakTransitionTimer | undefined;
10
15
  }
11
16
  /** Playback-gated `AdBreakStatus` state machine behind `getAdBreakStatus()` and the `adbreakstatus` event. */
@@ -26,9 +31,22 @@ export declare class AdBreakStatusTracker {
26
31
  currentAdDuration: number | null;
27
32
  activeBreakWaiting: boolean;
28
33
  activeBreakPlaying: boolean;
34
+ /** The `externalPlaybackAllowed` value last attached to a status; undefined before the first. */
35
+ private lastExternalPlaybackAllowed;
36
+ private lastDeliveryMode;
29
37
  constructor(deps: AdBreakStatusTrackerDeps);
30
38
  /** Current playback-gated ad break status; for polling when an `adbreakstatus` event was missed. */
31
39
  getAdBreakStatus(): AdBreakStatus;
40
+ /**
41
+ * Emits `adbreakstatus` when the AirPlay guard flips so the app can grey out its AirPlay button;
42
+ * the baseline before the first status is `true`. No-op while the SDK does not manage the gate.
43
+ */
44
+ refreshExternalPlayback(): void;
45
+ /** Emits `adbreakstatus` after a delivery-mode switch or restore so the app sees the new mode. */
46
+ refreshDeliveryMode(): void;
47
+ private liveAdBreakStatus;
48
+ /** Adds `externalPlaybackAllowed` when the SDK manages the AirPlay gate; an active break never allows it. */
49
+ private withExternalPlayback;
32
50
  /** Pre-break warning: emits `adbreakstatus` with `phase: 'upcoming'`. */
33
51
  onBreakWarning(breakInfo: Break, warningSec: number, secondsUntil: number): void;
34
52
  /** Promote a break to `phase: 'active'`; `ticking` starts `false` until playback events toggle it. */
@@ -1 +1 @@
1
- export class AdBreakStatusTracker{constructor(t){this.deps=t,this.adBreakStatus={phase:"idle",break:null,ticking:!1},this.activeBreak=null,this.activeBreakRemainingAtStart=null,this.activeBreakStartTimestamp=null,this.activeBreakElapsedSec=0,this.currentAdStartElapsedSec=0,this.currentAdAssetId=null,this.timelineAssetId=null,this.timelineStartElapsedSec=0,this.currentAdIndex=null,this.currentAdTotal=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakWaiting=!1,this.activeBreakPlaying=!1}getAdBreakStatus(){if("active"===this.adBreakStatus.phase)return{...this.adBreakStatus,breakRemainingSec:this.computeBreakRemainingSec()};if("upcoming"===this.adBreakStatus.phase&&this.activeBreak){const t=this.deps.secondsUntilBreak(this.activeBreak.id);return{...this.adBreakStatus,secondsUntilBreak:t}}return{...this.adBreakStatus}}onBreakWarning(t,e,i){this.activeBreak=t,this.emitAdBreakStatus({phase:"upcoming",break:t,format:this.deps.effectiveFormatOf(t),secondsUntilBreak:i,ticking:!1,warningSec:e})}setAdBreakStatusActive(t,e){this.activeBreak=t,this.activeBreakRemainingAtStart=e,this.activeBreakStartTimestamp=Date.now(),this.activeBreakElapsedSec=0,this.currentAdStartElapsedSec=0,this.currentAdAssetId=null,this.timelineAssetId=null,this.timelineStartElapsedSec=0,this.currentAdIndex=null,this.currentAdTotal=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakWaiting=!1,this.activeBreakPlaying=!1,this.emitAdBreakStatus({phase:"active",break:t,format:this.deps.effectiveFormatOf(t),breakRemainingSec:this.activeBreakRemainingAtStart,ticking:!1})}setAdBreakVisible(t){this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),ticking:!0})}onAdStarted(t,e,i,a){this.currentAdIndex=i,this.currentAdAssetId=e.id,this.currentAdTotal=a,this.currentAdTime=null,this.currentAdDuration=null,this.currentAdStartElapsedSec=this.currentBreakElapsedSec(),this.activeBreakElapsedSec=this.currentAdStartElapsedSec,this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.deps.breakTransitionTimer()?.settle("into-break","ad-to-ad","break-to-break"),this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),adIndex:i,totalAds:a,adsRemaining:a-i,ticking:!0})}onAdEnded(t,e,i){this.timelineAssetId===this.currentAdAssetId&&null!==this.currentAdDuration&&Number.isFinite(this.currentAdDuration)?this.activeBreakElapsedSec=this.timelineStartElapsedSec+this.currentAdDuration:null!==this.currentAdTime&&(this.activeBreakElapsedSec=this.timelineStartElapsedSec+this.currentAdTime),this.currentAdIndex=null,this.currentAdAssetId=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakPlaying=!1,this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),adsRemaining:Math.max(0,i-e-1),breakRemainingSec:this.computeBreakRemainingSec(),ticking:!1})}onAdTimeUpdated(t,e,i,a){e.id!==this.timelineAssetId&&(this.timelineAssetId=e.id,this.timelineStartElapsedSec=null===this.currentAdIndex?this.currentBreakElapsedSec():this.currentAdStartElapsedSec),this.currentAdTime=i,this.currentAdDuration=a,this.activeBreakElapsedSec=this.timelineStartElapsedSec+i,this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),breakRemainingSec:this.computeBreakRemainingSec(),ticking:!0})}onAdPlaybackWaiting(){this.activeBreakWaiting=!0,this.activeBreakPlaying=!1,this.activeBreak&&this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(this.activeBreak),ticking:!1})}onAdPlaybackPlaying(){this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.activeBreak&&this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(this.activeBreak),ticking:!0})}buildActiveAdBreakStatus(t){return{phase:"active",break:t,format:this.deps.effectiveFormatOf(t),adIndex:this.currentAdIndex??void 0,totalAds:this.currentAdTotal??void 0,adsRemaining:null!==this.currentAdIndex&&null!==this.currentAdTotal?this.currentAdTotal-this.currentAdIndex:void 0,breakRemainingSec:this.computeBreakRemainingSec(),ticking:this.activeBreakPlaying&&!this.activeBreakWaiting}}computeBreakRemainingSec(){if(null!==this.activeBreakRemainingAtStart)return Math.max(0,this.activeBreakRemainingAtStart-this.currentBreakElapsedSec())}currentBreakElapsedSec(){let t=this.activeBreakElapsedSec;return 0===t&&null!==this.activeBreakStartTimestamp&&null===this.currentAdTime&&(t=(Date.now()-this.activeBreakStartTimestamp)/1e3),t}resetAdBreakStatus(){this.activeBreak=null,this.activeBreakRemainingAtStart=null,this.activeBreakStartTimestamp=null,this.activeBreakElapsedSec=0,this.currentAdStartElapsedSec=0,this.currentAdAssetId=null,this.timelineAssetId=null,this.timelineStartElapsedSec=0,this.currentAdIndex=null,this.currentAdTotal=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakWaiting=!1,this.activeBreakPlaying=!1,this.emitAdBreakStatus({phase:"idle",break:null,ticking:!1})}emitAdBreakStatus(t){this.statusesEqual(this.adBreakStatus,t)||(this.adBreakStatus=t,this.deps.emit("adbreakstatus",{type:"adbreakstatus",timestamp:Date.now(),status:t}))}statusesEqual(t,e){return t.phase===e.phase&&t.format===e.format&&t.secondsUntilBreak===e.secondsUntilBreak&&t.breakRemainingSec===e.breakRemainingSec&&t.adIndex===e.adIndex&&t.totalAds===e.totalAds&&t.adsRemaining===e.adsRemaining&&t.ticking===e.ticking&&t.warningSec===e.warningSec&&t.break?.id===e.break?.id}}
1
+ import{resolveStatusExternalPlaybackAllowed as t}from"../services/ssbSwitchSignals";export class AdBreakStatusTracker{constructor(t){this.deps=t,this.adBreakStatus={phase:"idle",break:null,ticking:!1},this.activeBreak=null,this.activeBreakRemainingAtStart=null,this.activeBreakStartTimestamp=null,this.activeBreakElapsedSec=0,this.currentAdStartElapsedSec=0,this.currentAdAssetId=null,this.timelineAssetId=null,this.timelineStartElapsedSec=0,this.currentAdIndex=null,this.currentAdTotal=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakWaiting=!1,this.activeBreakPlaying=!1}getAdBreakStatus(){return this.withExternalPlayback(this.liveAdBreakStatus())}refreshExternalPlayback(){const e=t(this.deps.externalPlaybackAllowed(),this.adBreakStatus.phase,this.deps.deliveryMode());if(void 0===e||e===(this.lastExternalPlaybackAllowed??!0))return;const i=this.getAdBreakStatus();this.lastExternalPlaybackAllowed=i.externalPlaybackAllowed,this.deps.emit("adbreakstatus",{type:"adbreakstatus",timestamp:Date.now(),status:i})}refreshDeliveryMode(){const t=this.deps.deliveryMode();if(void 0===t||t===(this.lastDeliveryMode??"sgai"))return;const e=this.getAdBreakStatus();this.lastDeliveryMode=e.deliveryMode,this.deps.emit("adbreakstatus",{type:"adbreakstatus",timestamp:Date.now(),status:e})}liveAdBreakStatus(){if("active"===this.adBreakStatus.phase)return{...this.adBreakStatus,breakRemainingSec:this.computeBreakRemainingSec()};if("upcoming"===this.adBreakStatus.phase&&this.activeBreak){const t=this.deps.secondsUntilBreak(this.activeBreak.id);return{...this.adBreakStatus,secondsUntilBreak:t}}return{...this.adBreakStatus}}withExternalPlayback(e){const i=this.deps.deliveryMode(),a=void 0===i?e:{...e,deliveryMode:i},s=t(this.deps.externalPlaybackAllowed(),a.phase,i);return void 0===s?a:{...a,externalPlaybackAllowed:s}}onBreakWarning(t,e,i){this.activeBreak=t,this.emitAdBreakStatus({phase:"upcoming",break:t,format:this.deps.effectiveFormatOf(t),secondsUntilBreak:i,ticking:!1,warningSec:e})}setAdBreakStatusActive(t,e){this.activeBreak=t,this.activeBreakRemainingAtStart=e,this.activeBreakStartTimestamp=Date.now(),this.activeBreakElapsedSec=0,this.currentAdStartElapsedSec=0,this.currentAdAssetId=null,this.timelineAssetId=null,this.timelineStartElapsedSec=0,this.currentAdIndex=null,this.currentAdTotal=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakWaiting=!1,this.activeBreakPlaying=!1,this.emitAdBreakStatus({phase:"active",break:t,format:this.deps.effectiveFormatOf(t),breakRemainingSec:this.activeBreakRemainingAtStart,ticking:!1})}setAdBreakVisible(t){this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),ticking:!0})}onAdStarted(t,e,i,a){this.currentAdIndex=i,this.currentAdAssetId=e.id,this.currentAdTotal=a,this.currentAdTime=null,this.currentAdDuration=null,this.currentAdStartElapsedSec=this.currentBreakElapsedSec(),this.activeBreakElapsedSec=this.currentAdStartElapsedSec,this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.deps.breakTransitionTimer()?.settle("into-break","ad-to-ad","break-to-break"),this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),adIndex:i,totalAds:a,adsRemaining:a-i,ticking:!0})}onAdEnded(t,e,i){this.timelineAssetId===this.currentAdAssetId&&null!==this.currentAdDuration&&Number.isFinite(this.currentAdDuration)?this.activeBreakElapsedSec=this.timelineStartElapsedSec+this.currentAdDuration:null!==this.currentAdTime&&(this.activeBreakElapsedSec=this.timelineStartElapsedSec+this.currentAdTime),this.currentAdIndex=null,this.currentAdAssetId=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakPlaying=!1,this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),adsRemaining:Math.max(0,i-e-1),breakRemainingSec:this.computeBreakRemainingSec(),ticking:!1})}onAdTimeUpdated(t,e,i,a){e.id!==this.timelineAssetId&&(this.timelineAssetId=e.id,this.timelineStartElapsedSec=null===this.currentAdIndex?this.currentBreakElapsedSec():this.currentAdStartElapsedSec),this.currentAdTime=i,this.currentAdDuration=a,this.activeBreakElapsedSec=this.timelineStartElapsedSec+i,this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(t),breakRemainingSec:this.computeBreakRemainingSec(),ticking:!0})}onAdPlaybackWaiting(){this.activeBreakWaiting=!0,this.activeBreakPlaying=!1,this.activeBreak&&this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(this.activeBreak),ticking:!1})}onAdPlaybackPlaying(){this.activeBreakWaiting=!1,this.activeBreakPlaying=!0,this.activeBreak&&this.emitAdBreakStatus({...this.buildActiveAdBreakStatus(this.activeBreak),ticking:!0})}buildActiveAdBreakStatus(t){return{phase:"active",break:t,format:this.deps.effectiveFormatOf(t),adIndex:this.currentAdIndex??void 0,totalAds:this.currentAdTotal??void 0,adsRemaining:null!==this.currentAdIndex&&null!==this.currentAdTotal?this.currentAdTotal-this.currentAdIndex:void 0,breakRemainingSec:this.computeBreakRemainingSec(),ticking:this.activeBreakPlaying&&!this.activeBreakWaiting}}computeBreakRemainingSec(){if(null!==this.activeBreakRemainingAtStart)return Math.max(0,this.activeBreakRemainingAtStart-this.currentBreakElapsedSec())}currentBreakElapsedSec(){let t=this.activeBreakElapsedSec;return 0===t&&null!==this.activeBreakStartTimestamp&&null===this.currentAdTime&&(t=(Date.now()-this.activeBreakStartTimestamp)/1e3),t}resetAdBreakStatus(){this.activeBreak=null,this.activeBreakRemainingAtStart=null,this.activeBreakStartTimestamp=null,this.activeBreakElapsedSec=0,this.currentAdStartElapsedSec=0,this.currentAdAssetId=null,this.timelineAssetId=null,this.timelineStartElapsedSec=0,this.currentAdIndex=null,this.currentAdTotal=null,this.currentAdTime=null,this.currentAdDuration=null,this.activeBreakWaiting=!1,this.activeBreakPlaying=!1,this.lastExternalPlaybackAllowed=void 0,this.emitAdBreakStatus({phase:"idle",break:null,ticking:!1})}emitAdBreakStatus(t){if(this.statusesEqual(this.adBreakStatus,t))return;this.adBreakStatus=t;const e=this.withExternalPlayback(t);void 0!==e.externalPlaybackAllowed&&(this.lastExternalPlaybackAllowed=e.externalPlaybackAllowed),this.lastDeliveryMode=e.deliveryMode,this.deps.emit("adbreakstatus",{type:"adbreakstatus",timestamp:Date.now(),status:e})}statusesEqual(t,e){return t.phase===e.phase&&t.format===e.format&&t.secondsUntilBreak===e.secondsUntilBreak&&t.breakRemainingSec===e.breakRemainingSec&&t.adIndex===e.adIndex&&t.totalAds===e.totalAds&&t.adsRemaining===e.adsRemaining&&t.ticking===e.ticking&&t.warningSec===e.warningSec&&t.break?.id===e.break?.id}}
@@ -4,6 +4,7 @@ import { BreakScheduler, BreakStartContext } from '../services/BreakScheduler';
4
4
  import { ManifestService } from '../services/ManifestService';
5
5
  import { AdPlayerController } from '../services/AdPlayerController';
6
6
  import { BreakTransitionTimer } from '../services/BreakTransitionTimer';
7
+ import type { BreakChainRecorder } from '../services/BreakChainRecorder';
7
8
  import { CmcdAdReporter } from '../services/CmcdAdReporter';
8
9
  import { KnownDiagnosticCode } from '../diagnostics/errorCodes';
9
10
  import { AdBreakStatusTracker, EmitEvent } from './AdBreakStatusTracker';
@@ -15,6 +16,7 @@ export interface BreakLifecycleDeps {
15
16
  breakScheduler: BreakScheduler;
16
17
  manifestService: ManifestService;
17
18
  breakTransitionTimer: BreakTransitionTimer | undefined;
19
+ breakChainRecorder?: BreakChainRecorder | undefined;
18
20
  cmcdAdReporter: CmcdAdReporter;
19
21
  adBreakStatusTracker: AdBreakStatusTracker;
20
22
  emit: EmitEvent;
@@ -22,6 +24,8 @@ export interface BreakLifecycleDeps {
22
24
  log: Log;
23
25
  effectiveFormatOf: (breakInfo: Break) => BreakFormat | undefined;
24
26
  onBreakSettled?: () => void;
27
+ /** A break ended and `adbreakend` was emitted. */
28
+ onBreakEnded?: () => void;
25
29
  }
26
30
  /** Scheduler and ad-player callbacks for one SGAI break: event emission, status updates, teardown reporting. */
27
31
  export declare class BreakLifecycle {
@@ -30,6 +34,8 @@ export declare class BreakLifecycle {
30
34
  private breakBeginEmittedId;
31
35
  private pendingAdBreakBegin;
32
36
  constructor(state: SessionState, deps: BreakLifecycleDeps);
37
+ /** Feed the chain report: the break began, with the successor the scheduler knows right now. */
38
+ private recordBreakBegan;
33
39
  /**
34
40
  * Emit `adbreakbegin` at most once per break id. Routing every begin through here, plus a flush
35
41
  * before any terminal event, keeps the break bracketed even when the ad errors before its first frame.
@@ -1 +1 @@
1
- import{BreakFormat as e}from"../types/BreakManifest";import{playbackErrorContext as t}from"../diagnostics/playbackErrorContext";import{adErrorCode as a}from"../types/AdError";import{VendorAdError as i}from"../types/VendorAdError";export class BreakLifecycle{constructor(e,t){this.state=e,this.deps=t,this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null}emitAdBreakBegin(e,t){this.breakBeginEmittedId!==e.id&&(this.breakBeginEmittedId=e.id,this.pendingAdBreakBegin=null,this.deps.emit("adbreakbegin",{type:"adbreakbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),elapsedSec:t?.elapsedSec??0,remainingSec:t?.remainingSec??e.duration??0,...t?.tuneIn?{tuneIn:{elapsedSec:t.elapsedSec,remainingSec:t.remainingSec}}:{}}))}flushPendingAdBreakBegin(){this.pendingAdBreakBegin?.()}onBreakSkipped(e,t){this.deps.emit("adbreakskipped",{type:"adbreakskipped",timestamp:Date.now(),break:e,reason:t,format:this.deps.effectiveFormatOf(e)})}async onBreakStart(r,d){const s=this.state.sessionGeneration;if(this.state.snapbackSeekInFlight=!1,this.deps.adPlayerController.isAdPlaying()){if(this.deps.diagnose("DA-BREAK-SUPPRESSED-OVERLAP",`Break suppressed (ad already playing): ${r.id}`,{breakId:r.id}),s!==this.state.sessionGeneration)return;return this.deps.breakScheduler.completeBreak(r.id),this.onBreakSkipped(r,"overlap"),void this.deps.onBreakSettled?.()}this.deps.adPlayerController.diagnoseUnknownVariantFormats(r);const n=this.deps.adPlayerController.breakNothingToPlayReason(r);if(n){if(this.deps.log("Break not begun (no playable variant):",r.id,n),this.deps.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`break ${r.id} has no playable variant (${n}); it will not begin.`,{breakId:r.id,reason:n}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:r,format:this.deps.effectiveFormatOf(r),error:new Error(`Break ${r.id} has no playable variant (${n})`)}),s!==this.state.sessionGeneration)return;return this.deps.breakScheduler.completeBreak(r.id),this.onBreakSkipped(r,"unsupported"),void this.deps.onBreakSettled?.()}this.deps.log("Break starting:",r.id,d.tuneIn?"(tune-in)":""),this.deps.breakTransitionTimer?.mark("into-break",r.id),this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null;const o=this.state.heldImmediatePreRollId===r.id,p=this.deps.effectiveFormatOf(r),k=p===e.SINGLE||p===e.LSHAPE_AD,h=o&&k;o&&(this.state.heldImmediatePreRollId=null),this.deps.adBreakStatusTracker.setAdBreakStatusActive(r,void 0!==d.remainingSec?d.remainingSec:r.duration),h||this.emitAdBreakBegin(r,d),this.deps.manifestService.setActive(!0),this.deps.breakScheduler.claimChain(r.id);const l={onAdStart:(e,t,a,i,r)=>{this.deps.adBreakStatusTracker.onAdStarted(e,t,a,i),this.deps.emit("adbegin",{type:"adbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...r?.adId?{adId:r.adId}:{},...r?.creativeId?{creativeId:r.creativeId}:{},...void 0!==r?.podPosition?{podPosition:r.podPosition}:{}})},onAdEnd:(e,t,a,i,r)=>{this.deps.adBreakStatusTracker.onAdEnded(e,a,i),a+1<i&&this.deps.breakTransitionTimer?.mark("ad-to-ad",e.id),this.deps.emit("adend",{type:"adend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...r?.adId?{adId:r.adId}:{},...r?.creativeId?{creativeId:r.creativeId}:{}})},onAdError:(e,r,d)=>{this.flushPendingAdBreakBegin(),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",d.message,{breakId:e.id,assetId:r?.id,adErrorCode:a(d),...d instanceof i?{vendorCode:d.vendorCode}:{},...t(d)}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:e,format:this.deps.effectiveFormatOf(e),...r?{asset:r}:{},error:d})},onAdTimeUpdate:(e,t,a,i)=>{this.deps.adBreakStatusTracker.onAdTimeUpdated(e,t,a,i),this.deps.emit("adtimeupdate",{type:"adtimeupdate",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,currentTime:a,duration:i})},onAdWaiting:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackWaiting(),this.deps.emit("waiting",{type:"waiting",timestamp:Date.now(),source:"ad",break:e,asset:t})},onAdPlaying:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackPlaying(),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"ad",break:e,asset:t})},onBreakEnd:e=>{this.onBreakEnd(e)},onBreakCut:e=>{this.deps.cmcdAdReporter.onBreakCut(e.id)},onBreakEndedWithoutContentPause:()=>{this.onBreakEndedWithoutContentPause()},onChainedBreakBegin:e=>{this.onChainedBreakBegin(e)},onChainedBreakSkipped:e=>{s===this.state.sessionGeneration&&(this.deps.log("Chained break skipped (no playable variant):",e.id),this.deps.breakScheduler.completeBreak(e.id),this.onBreakSkipped(e,"unsupported"))},onContentResumeSeek:(e,t)=>{this.deps.breakScheduler.notifyResumeSeek(e,t)},onContentReloading:e=>{this.deps.breakScheduler.setContentReloading(e)}};h&&(this.pendingAdBreakBegin=()=>this.emitAdBreakBegin(r,d),l.onBreakVisible=e=>{this.deps.adBreakStatusTracker.setAdBreakVisible(e),this.flushPendingAdBreakBegin()}),await this.deps.adPlayerController.playBreak(r,l,{effectiveDurationSec:d.remainingSec,firstFrameGate:h})}onChainedBreakBegin(e){this.deps.log("Chained break starting:",e.id),this.deps.breakTransitionTimer?.mark("break-to-break",e.id),this.deps.breakScheduler.activateChainedBreak(e.id),this.deps.manifestService.setActive(!0),this.deps.adBreakStatusTracker.setAdBreakStatusActive(e,e.duration),this.emitAdBreakBegin(e)}onBreakEnd(e){this.deps.log("Break ended:",e.id),this.deps.breakTransitionTimer?.mark("out-of-break",e.id),this.deps.breakScheduler.completeBreak(e.id),this.deps.manifestService.setActive(!1),this.deps.adBreakStatusTracker.resetAdBreakStatus(),this.flushPendingAdBreakBegin(),this.deps.emit("adbreakend",{type:"adbreakend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e)}),this.deps.onBreakSettled?.()}onBreakEndedWithoutContentPause(){this.deps.breakTransitionTimer?.settle("out-of-break"),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"content"})}onBreakAborted(e){if(this.deps.adPlayerController.isAdPlaying()){const t=this.deps.adBreakStatusTracker.currentBreakElapsedSec();this.deps.log(`Early return — ending break '${e.id}' at ${t.toFixed(2)}s`),this.deps.cmcdAdReporter.onBreakAborted(e.id),this.deps.adPlayerController.abortBreak(t)}else this.deps.breakScheduler.completeBreak(e.id),this.deps.onBreakSettled?.()}reportAbandonedBreak(e){const t=this.deps.adBreakStatusTracker.activeBreak;if(!t)return;const a=this.deps.adBreakStatusTracker.currentAdIndex;this.deps.diagnose("DA-BREAK-ABANDONED",`Session torn down (${e}) while break ${t.id} was still in flight`+(null!==a?"; an ad began and will never end.":"."),{breakId:t.id,cause:e,phase:this.deps.adBreakStatusTracker.adBreakStatus.phase,unbalancedAdBegin:null!==a,...null!==a?{adIndex:a}:{},...null!==this.deps.adBreakStatusTracker.currentAdTotal?{totalAds:this.deps.adBreakStatusTracker.currentAdTotal}:{}})}}
1
+ import{BreakFormat as e}from"../types/BreakManifest";import{playbackErrorContext as t}from"../diagnostics/playbackErrorContext";import{adErrorCode as a}from"../types/AdError";import{VendorAdError as i}from"../types/VendorAdError";export class BreakLifecycle{constructor(e,t){this.state=e,this.deps=t,this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null,t.breakScheduler.setOnChainSuccessorChanged((e,a)=>{t.breakChainRecorder?.successorChanged(a?.id??null),t.adPlayerController.onChainSuccessorChanged()})}recordBreakBegan(e,t){const a=this.deps.breakScheduler.getChainedSuccessor(e.id);this.deps.breakChainRecorder?.breakBegan(e,t,a?.id??null)}emitAdBreakBegin(e,t){this.breakBeginEmittedId!==e.id&&(this.breakBeginEmittedId=e.id,this.pendingAdBreakBegin=null,this.deps.emit("adbreakbegin",{type:"adbreakbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),elapsedSec:t?.elapsedSec??0,remainingSec:t?.remainingSec??e.duration??0,...t?.tuneIn?{tuneIn:{elapsedSec:t.elapsedSec,remainingSec:t.remainingSec}}:{}}))}flushPendingAdBreakBegin(){this.pendingAdBreakBegin?.()}onBreakSkipped(e,t){this.deps.emit("adbreakskipped",{type:"adbreakskipped",timestamp:Date.now(),break:e,reason:t,format:this.deps.effectiveFormatOf(e)})}async onBreakStart(r,s){const d=this.state.sessionGeneration;if(this.state.snapbackSeekInFlight=!1,this.deps.adPlayerController.isAdPlaying()){if(this.deps.diagnose("DA-BREAK-SUPPRESSED-OVERLAP",`Break suppressed (ad already playing): ${r.id}`,{breakId:r.id}),d!==this.state.sessionGeneration)return;return this.deps.breakScheduler.completeBreak(r.id),this.onBreakSkipped(r,"overlap"),void this.deps.onBreakSettled?.()}this.deps.adPlayerController.diagnoseUnknownVariantFormats(r);const n=this.deps.adPlayerController.breakNothingToPlayReason(r);if(n){if(this.deps.log("Break not begun (no playable variant):",r.id,n),this.deps.diagnose("DA-BREAK-NO-PLAYABLE-ASSET",`break ${r.id} has no playable variant (${n}); it will not begin.`,{breakId:r.id,reason:n}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:r,format:this.deps.effectiveFormatOf(r),error:new Error(`Break ${r.id} has no playable variant (${n})`)}),d!==this.state.sessionGeneration)return;return this.deps.breakScheduler.completeBreak(r.id),this.onBreakSkipped(r,"unsupported"),void this.deps.onBreakSettled?.()}this.deps.log("Break starting:",r.id,s.tuneIn?"(tune-in)":""),this.deps.breakTransitionTimer?.mark("into-break",r.id),this.breakBeginEmittedId=null,this.pendingAdBreakBegin=null;const o=this.state.heldImmediatePreRollId===r.id,p=this.deps.effectiveFormatOf(r),k=p===e.SINGLE||p===e.LSHAPE_AD,h=o&&k;o&&(this.state.heldImmediatePreRollId=null),this.deps.adBreakStatusTracker.setAdBreakStatusActive(r,void 0!==s.remainingSec?s.remainingSec:r.duration),h||this.emitAdBreakBegin(r,s),this.deps.manifestService.setActive(!0),this.deps.breakScheduler.claimChain(r.id),this.recordBreakBegan(r,!1);const l={onAdStart:(e,t,a,i,r)=>{this.deps.adBreakStatusTracker.onAdStarted(e,t,a,i),this.deps.emit("adbegin",{type:"adbegin",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...r?.adId?{adId:r.adId}:{},...r?.creativeId?{creativeId:r.creativeId}:{},...void 0!==r?.podPosition?{podPosition:r.podPosition}:{}})},onAdEnd:(e,t,a,i,r)=>{this.deps.adBreakStatusTracker.onAdEnded(e,a,i),a+1<i?this.deps.breakTransitionTimer?.mark("ad-to-ad",e.id):this.deps.breakTransitionTimer?.noteOutgoingEnd(),this.deps.emit("adend",{type:"adend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,adIndex:a,totalAds:i,...r?.adId?{adId:r.adId}:{},...r?.creativeId?{creativeId:r.creativeId}:{}})},onAdError:(e,r,s)=>{this.flushPendingAdBreakBegin(),this.deps.diagnose("DA-AD-PLAYBACK-ERROR",s.message,{breakId:e.id,assetId:r?.id,adErrorCode:a(s),...s instanceof i?{vendorCode:s.vendorCode}:{},...t(s)}),this.deps.emit("aderror",{type:"aderror",timestamp:Date.now(),source:"ad",break:e,format:this.deps.effectiveFormatOf(e),...r?{asset:r}:{},error:s})},onAdTimeUpdate:(e,t,a,i)=>{this.deps.adBreakStatusTracker.onAdTimeUpdated(e,t,a,i),this.deps.emit("adtimeupdate",{type:"adtimeupdate",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e),asset:t,currentTime:a,duration:i})},onAdWaiting:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackWaiting(),this.deps.emit("waiting",{type:"waiting",timestamp:Date.now(),source:"ad",break:e,asset:t})},onAdPlaying:(e,t)=>{this.deps.adBreakStatusTracker.onAdPlaybackPlaying(),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"ad",break:e,asset:t})},onBreakEnd:e=>{this.onBreakEnd(e)},onBreakCut:e=>{this.deps.cmcdAdReporter.onBreakCut(e.id)},onBreakEndedWithoutContentPause:()=>{this.onBreakEndedWithoutContentPause()},onChainedBreakBegin:e=>{this.onChainedBreakBegin(e)},onSuccessorBuffered:(e,t)=>this.deps.breakTransitionTimer?.noteSuccessorBuffered(e,t),onSuccessorDropped:e=>this.deps.breakTransitionTimer?.noteSuccessorDropped(e),onChainedBreakSkipped:e=>{d===this.state.sessionGeneration&&(this.deps.log("Chained break skipped (no playable variant):",e.id),this.deps.breakScheduler.completeBreak(e.id),this.onBreakSkipped(e,"unsupported"))},onContentResumeSeek:(e,t)=>{this.deps.breakScheduler.notifyResumeSeek(e,t)},onContentReloading:e=>{this.deps.breakScheduler.setContentReloading(e)}};h&&(this.pendingAdBreakBegin=()=>this.emitAdBreakBegin(r,s),l.onBreakVisible=e=>{this.deps.adBreakStatusTracker.setAdBreakVisible(e),this.flushPendingAdBreakBegin()}),await this.deps.adPlayerController.playBreak(r,l,{effectiveDurationSec:s.remainingSec,firstFrameGate:h})}onChainedBreakBegin(e){this.deps.log("Chained break starting:",e.id),this.deps.breakTransitionTimer?.mark("break-to-break",e.id),this.deps.breakScheduler.activateChainedBreak(e.id),this.recordBreakBegan(e,!0),this.deps.manifestService.setActive(!0),this.deps.adBreakStatusTracker.setAdBreakStatusActive(e,e.duration),this.emitAdBreakBegin(e)}onBreakEnd(e){this.deps.log("Break ended:",e.id),this.deps.breakTransitionTimer?.mark("out-of-break",e.id),this.deps.breakChainRecorder?.breakEnded(e.id),this.deps.breakScheduler.completeBreak(e.id),this.deps.manifestService.setActive(!1),this.deps.adBreakStatusTracker.resetAdBreakStatus(),this.flushPendingAdBreakBegin(),this.deps.emit("adbreakend",{type:"adbreakend",timestamp:Date.now(),break:e,format:this.deps.effectiveFormatOf(e)}),this.deps.onBreakEnded?.(),this.deps.onBreakSettled?.()}onBreakEndedWithoutContentPause(){this.deps.breakTransitionTimer?.settle("out-of-break"),this.deps.emit("playing",{type:"playing",timestamp:Date.now(),source:"content"})}onBreakAborted(e){if(this.deps.adPlayerController.isAdPlaying()){const t=this.deps.adBreakStatusTracker.currentBreakElapsedSec();this.deps.log(`Early return — ending break '${e.id}' at ${t.toFixed(2)}s`),this.deps.cmcdAdReporter.onBreakAborted(e.id),this.deps.adPlayerController.abortBreak(t)}else this.deps.breakScheduler.completeBreak(e.id),this.deps.onBreakSettled?.()}reportAbandonedBreak(e){const t=this.deps.adBreakStatusTracker.activeBreak;if(!t)return;const a=this.deps.adBreakStatusTracker.currentAdIndex;this.deps.diagnose("DA-BREAK-ABANDONED",`Session torn down (${e}) while break ${t.id} was still in flight`+(null!==a?"; an ad began and will never end.":"."),{breakId:t.id,cause:e,phase:this.deps.adBreakStatusTracker.adBreakStatus.phase,unbalancedAdBegin:null!==a,...null!==a?{adIndex:a}:{},...null!==this.deps.adBreakStatusTracker.currentAdTotal?{totalAds:this.deps.adBreakStatusTracker.currentAdTotal}:{}})}}
@@ -1,42 +1,7 @@
1
- import { PlayerAdapter } from '../interfaces/PlayerAdapter';
2
1
  import { BreakManifest } from '../types/BreakManifest';
3
- import { ExternalVendorConfigurationTracker } from '../services/ExternalVendorConfigurationTracker';
4
2
  import { OptiViewAdsMode, SessionConfig, SsaiSessionConfig, StartSessionConfig } from '../types/OptiViewAdsConfig';
5
- import { ManifestService } from '../services/ManifestService';
6
- import { BreakScheduler } from '../services/BreakScheduler';
7
- import { AdPlayerController } from '../services/AdPlayerController';
8
- import { PauseAdController } from '../services/PauseAdController';
9
- import { LensReporter } from '../services/LensReporter';
10
- import { CmcdAdReporter } from '../services/CmcdAdReporter';
11
- import { AdBreakStatusTracker, EmitEvent } from './AdBreakStatusTracker';
12
- import { BreakLifecycle, Diagnose, Log } from './BreakLifecycle';
13
3
  import { SessionState } from './SessionState';
14
- export interface SessionStarterDeps {
15
- player: PlayerAdapter;
16
- debug: boolean;
17
- mode: OptiViewAdsMode;
18
- hasExplicitMode: boolean;
19
- continueContentDuringBreak: boolean;
20
- startLivePushback: () => void;
21
- manifestService: ManifestService | undefined;
22
- breakScheduler: BreakScheduler | undefined;
23
- adPlayerController: AdPlayerController | undefined;
24
- pauseAdController: PauseAdController | undefined;
25
- breakLifecycle: BreakLifecycle | undefined;
26
- cmcdAdReporter: CmcdAdReporter;
27
- lensReporter: LensReporter;
28
- adBreakStatusTracker: AdBreakStatusTracker;
29
- externalVendorConfigurationTracker: ExternalVendorConfigurationTracker;
30
- emit: EmitEvent;
31
- diagnose: Diagnose;
32
- log: Log;
33
- endSessionInternal: (cause: string) => void;
34
- notedManifestBreakIds: (manifest: {
35
- breaks: Array<{
36
- id: string;
37
- }>;
38
- }) => string[];
39
- }
4
+ import type { SessionStarterDeps } from './SessionStarterDeps';
40
5
  /** startSession() flow: validation, delivery-mode steering, GAM/SSAI start, scheduler and polling start. */
41
6
  export declare class SessionStarter {
42
7
  private readonly state;
@@ -47,6 +12,7 @@ export declare class SessionStarter {
47
12
  * GAM session initialization. Ends any previously active session first.
48
13
  */
49
14
  startSession(config: StartSessionConfig): Promise<void>;
15
+ discardPreparedGamSessionAfterSeek(): void;
50
16
  /**
51
17
  * Content already playing when the session starts emits no further `playing`; the pre-roll
52
18
  * anchor (spec `event: 'start'`) is then the session start itself.
@@ -72,7 +38,7 @@ export declare class SessionStarter {
72
38
  * rules are evaluated against the device type; an explicit constructor `mode` wins.
73
39
  * Absent/unmatched rules and unrecognized values resolve to sgai.
74
40
  */
75
- resolveSessionDeliveryMode(manifest: BreakManifest, source?: 'manifest' | 'channel-switch'): OptiViewAdsMode;
41
+ resolveSessionDeliveryMode(manifest: BreakManifest, source?: 'manifest' | 'channel-switch' | 'event-boundary'): OptiViewAdsMode;
76
42
  /**
77
43
  * Manifest-steered ssai: play the DAI stitched stream through the content player, keep
78
44
  * polling the break manifest, and schedule only event-triggered breaks client-side.
@@ -86,7 +52,7 @@ export declare class SessionStarter {
86
52
  startManifestPolling(): void;
87
53
  applyPendingChannelSwitch(): void;
88
54
  private applyManifestUpdate;
89
- private diagnoseIgnoredDeliveryModeChange;
55
+ private handleEventBoundaryTick;
90
56
  /**
91
57
  * A poll returned a manifest of another channel: re-align the ad setup after the current break
92
58
  * settles (the latest poll wins). Scheduler ledgers are rebuilt, GAM is re-opened when its
@@ -95,12 +61,9 @@ export declare class SessionStarter {
95
61
  * the new manifest's `adStartDelay` applies; afterwards, the delay in effect stays.
96
62
  */
97
63
  private switchChannel;
98
- private emitChannelChange;
99
- private adoptExternalVendorConfigurations;
100
- private diagnoseExternalVendorConfigurations;
101
64
  private applySameModeChannelSwitch;
102
- private switchToSteeredSsai;
103
- private switchToSgai;
65
+ switchToSteeredSsai(manifest: BreakManifest, config: SessionConfig, preserveSchedulerLedgers?: boolean): Promise<boolean>;
66
+ switchToSgai(manifest: BreakManifest, config: SessionConfig, generation: number, preserveSchedulerLedgers?: boolean): Promise<void>;
104
67
  /**
105
68
  * Resolve the GAM stream identity from the manifest and open the IMA/DAI session. GAM failure
106
69
  * is non-fatal: non-GAM breaks still schedule, only the GAM assets are skipped.
@@ -1 +1 @@
1
- import{getPreRollDelay as e}from"../types/BreakManifest";import{manifestHasGamAssets as s,resolveGamVendorConfiguration as t}from"../services/resolveGamVendorConfiguration";import{resolveExternalVendorConfigurations as i}from"../services/resolveExternalVendorConfigurations";import{SsaiController as a}from"../services/SsaiController";import{resolveUrlOrigin as r}from"../services/urlOrigin";import{filterSsaiClientBreaks as n,resolveStitchedStreamVendor as o,SsaiUnavailableError as d}from"../services/resolveDeliveryMode";import{detectDeviceProfile as l}from"../services/detectDeviceType";import{decideDeliveryMode as h,detectChannelSwitch as c,gamPodIdentityChanged as p}from"../services/channelSwitch";import{prepareWebRuntime as m}from"../services/webCompatibility";const S=1e4;function u(e){return"stitcherUrl"in e}function f(e){return"string"==typeof e&&""!==e.trim()}export class SessionStarter{constructor(e,s){this.state=e,this.deps=s}async startSession(s){if("ssai"===this.deps.mode){if(!u(s)||!f(s.stitcherUrl))throw new Error('[OptiViewAds] mode "ssai" requires a stitcherUrl in startSession().')}else if(u(s)||!f(s.manifestUrl))throw new Error("[OptiViewAds] startSession requires a manifestUrl (the full break-manifest URL).");this.state.sessionActive&&(this.deps.log("Ending previous session before starting new one"),this.deps.endSessionInternal("session-restart"));const t=this.state.sessionGeneration;this.state.sessionActive=!0,this.state.adStartDelayElapsed=!1;try{const e=m();e&&await e}catch(e){if(this.isStaleStart(t))return;throw this.state.sessionActive=!1,e}if(this.isStaleStart(t))return;if(this.deps.lensReporter.startSession(),u(s))return s.adInsertionType&&this.deps.diagnose("DA-INSERTION-TYPE-IGNORED-SSAI",`adInsertionType "${s.adInsertionType}" ignored in SSAI mode; the stitcher controls ad insertion.`,{adInsertionType:s.adInsertionType}),void await this.startSsaiSession(s,t);const i=s.manifestUrl;this.state.sessionConfig=s,this.state.manifestOrigin=r(i),this.deps.manifestService.setManifestUrl(i),this.deps.log("Session starting, manifest URL:",i);const a=Date.now(),n=e=>{this.deps.diagnose("DA-SESSION-STARTING",`Session start: ${e}`,{step:e,manifestUrl:i,elapsedMs:Date.now()-a})};n("begin");const o=s.assetParameters;let d;this.deps.adPlayerController.setSessionAssetParameters(o),this.deps.adPlayerController.setSessionAssetParameterMacros(s.assetParameterMacros),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!o}),n("manifest-fetch");try{d=await this.deps.manifestService.fetchManifest()}catch(e){if(this.isStaleStart(t))return;throw this.failSessionStart(e)}if(this.deps.cmcdAdReporter.onSessionStart(d),!this.isStaleStart(t))if(this.state.sessionManifest=d,this.adoptExternalVendorConfigurations(d),"ssai"!==this.resolveSessionDeliveryMode(d))try{if(await this.startGamForManifest(d,o,n,t,!0),this.isStaleStart(t))return;const a=s.adInsertionType;this.deps.adPlayerController.setBreakResumePolicy(a,d.timebase,this.deps.continueContentDuringBreak),this.deps.diagnose("DA-INSERTION-TYPE-RESOLVED",void 0===a?`Ad insertion type: unset — the manifest's resumeOffset drives the resume point (timebase: ${d.timebase})`:`Ad insertion type: ${a}, client-set (timebase: ${d.timebase})`,{adInsertionType:a??null,source:void 0===a?"unset":"client",timebase:d.timebase}),n("scheduling"),this.deps.breakScheduler.updateManifest(d),this.deps.pauseAdController.updateManifest(d),this.state.pauseAdTickInterval=setInterval(()=>this.deps.pauseAdController?.tick(),250);const r=(d.adStartDelay??0)>0?void 0:d.breaks.find(s=>0===e(s));r?(this.deps.adPlayerController.holdContentForPreRoll(r),this.state.heldImmediatePreRollId=r.id,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=Date.now(),this.contentIsReady()?this.armHeldImmediatePreRoll():this.state.heldPreRollArmInterval=setInterval(()=>{this.contentIsReady()?this.armHeldImmediatePreRoll():this.armHeldImmediatePreRollIfTimedOut()},250)):this.markPlaybackStartIfPlaying(),this.deps.startLivePushback(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`Session active for manifest ${i}`,{manifestUrl:i}),this.deps.log("Session active")}catch(e){if(this.isStaleStart(t))return;throw this.failSessionStart(e)}else await this.startSteeredSsaiSession(d,s,n)}markPlaybackStartIfPlaying(){this.deps.player.paused||this.deps.breakScheduler.markPlaybackStart()}contentIsReady(){return!Number.isNaN(this.deps.player.duration)||!this.deps.player.paused}clearHeldImmediatePreRollArmInterval(){this.state.heldPreRollArmInterval&&(clearInterval(this.state.heldPreRollArmInterval),this.state.heldPreRollArmInterval=null)}armHeldImmediatePreRoll(){this.state.heldImmediatePreRollId&&!this.state.heldPreRollArmed&&(this.clearHeldImmediatePreRollArmInterval(),this.state.heldPreRollArmed=!0,this.deps.breakScheduler.markPlaybackStart())}armHeldImmediatePreRollIfTimedOut(){if(!this.state.heldImmediatePreRollId||this.state.heldPreRollArmed)return;const e=this.state.heldPreRollWaitStartedAt;if(null===e||Date.now()-e<S)return;const s=this.state.heldImmediatePreRollId;this.armHeldImmediatePreRoll(),this.deps.diagnose("DA-PREROLL-CONTENT-WAIT-TIMEOUT","Pending pre-roll began after waiting 10000ms for content readiness.",{breakId:s,waitMs:S})}cancelHeldImmediatePreRollOnContentError(){const e=this.state.heldImmediatePreRollId;return!(!e||this.state.heldPreRollArmed)&&(this.clearHeldImmediatePreRollArmInterval(),this.deps.breakScheduler?.completeBreak(e),this.deps.adPlayerController?.releaseContentHold(),this.state.heldImmediatePreRollId=null,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=null,this.deps.diagnose("DA-PREROLL-CANCELLED-CONTENT-ERROR","A pending pre-roll was cancelled because the content failed to load before it could begin.",{breakId:e}),!0)}clearHeldImmediatePreRollArm(){this.clearHeldImmediatePreRollArmInterval(),this.state.heldImmediatePreRollId=null,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=null}failSessionStart(e){const s=e instanceof Error?e:new Error(String(e)),t=this.deps.manifestService?.takeLastFailure();return this.deps.diagnose("DA-MANIFEST-FETCH-FAILED",s.message,t?{...t}:void 0),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),s}resolveSessionDeliveryMode(e,s="manifest"){const t=this.state.sessionDeliveryMode,i=h(e,l(),this.deps.hasExplicitMode?this.deps.mode:null,s);null!==i.unknownMode&&this.deps.diagnose("DA-DELIVERY-MODE-UNKNOWN",`Delivery rule ${i.ruleIndex} carries unrecognized mode "${i.unknownMode}"; using sgai.`,{unknownMode:i.unknownMode,ruleIndex:i.ruleIndex});for(const e of i.targetingIssues)this.deps.diagnose(e.code,`Targeting ${e.field} was rejected.`,{field:e.field,...void 0===e.value?{}:{value:e.value}});return this.deps.hasExplicitMode&&i.manifestMode!==i.mode&&this.deps.diagnose("DA-DELIVERY-MODE-OVERRIDDEN",`Manifest delivery rules resolve "${i.manifestMode}" but the explicit config mode "${i.mode}" takes precedence.`,{manifestMode:i.manifestMode,configuredMode:i.mode}),this.state.sessionDeliveryMode=i.mode,this.deps.diagnose("DA-DELIVERY-MODE-RESOLVED",`Delivery mode: ${i.mode}`,{mode:i.mode,ruleIndex:i.ruleIndex,source:i.source,deviceProfile:l(),..."channel-switch"===s?{previousMode:t}:{}}),i.mode}async startSteeredSsaiSession(e,s,t){const i=o(e.vendorConfiguration);if(!i){const e="Manifest selected ssai delivery but no vendor configuration provides a stitched stream (no gam entry with an assetKey).";throw this.deps.diagnose("DA-SSAI-UNAVAILABLE",e),this.deps.manifestService.reset(),this.state.sessionActive=!1,new d(e)}try{t("ssai-live-stream"),this.state.steeredSsaiActive=!0,this.state.ssaiController=this.createSsaiController(i.networkCode),await this.state.ssaiController.startSteered(i,s.assetParameters,s.assetParameterMacros),t("scheduling");const a=n(e);this.deps.adPlayerController.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),this.deps.breakScheduler.updateManifest(a),this.deps.pauseAdController.updateManifest(a),this.state.pauseAdTickInterval=setInterval(()=>this.deps.pauseAdController?.tick(),250),this.markPlaybackStartIfPlaying(),this.deps.startLivePushback(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active (manifest-steered) for ${s.manifestUrl}`,{manifestUrl:s.manifestUrl}),this.deps.log("SSAI session active (manifest-steered)")}catch(e){const s=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",s.message),this.state.steeredSsaiActive=!1,this.deps.manifestService.reset(),this.state.sessionActive=!1,s}}createSsaiController(e){return new a(this.deps.player,{gam:this.state.steeredSsaiOptions?.streamActivityMonitorId?{streamActivityMonitorId:this.state.steeredSsaiOptions.streamActivityMonitorId}:{},ssai:{networkCode:e},maxBitrate:this.state.steeredSsaiOptions?.maxBitrate,autoplay:this.state.steeredSsaiOptions?.autoplay,debug:this.deps.debug},{emit:(e,s)=>this.deps.emit(e,s),diagnose:(e,s,t)=>this.deps.diagnose(e,s,t),log:(...e)=>this.deps.log(...e)})}startBreakScheduler(){this.deps.breakScheduler.start((e,s)=>{this.deps.breakLifecycle.onBreakStart(e,s)},e=>!this.deps.adPlayerController.isAdPlaying()&&(this.deps.adPlayerController.preload(e).catch(s=>{this.deps.diagnose("DA-PRELOAD-FAILED",s instanceof Error?s.message:String(s),{breakId:e.id})}),!0),(e,s,t)=>{this.deps.adBreakStatusTracker.onBreakWarning(e,s,t)},e=>{this.deps.breakLifecycle.onBreakAborted(e)},(e,s)=>this.deps.breakLifecycle.onBreakSkipped(e,s))}startManifestPolling(){this.deps.manifestService.startPolling(e=>{if(!this.state.sessionActive)return;const s=this.deps.notedManifestBreakIds(e);this.deps.diagnose("DA-MANIFEST-POLLED","Manifest polled",{breaks:e.breaks.length,newBreakIds:s,pollingIntervalSeconds:this.deps.manifestService.currentPollingIntervalSeconds});const t=this.state.sessionManifest,i=t?c(t,e):null;if(this.state.pendingChannelSwitch||i&&this.deps.breakScheduler.getActiveBreak())return this.state.pendingChannelSwitch=e,void this.deps.log("Channel switch deferred until the active break ends");i?this.switchChannel(e,i):this.applyManifestUpdate(e)},e=>{this.state.sessionActive&&this.deps.diagnose("DA-MANIFEST-POLL-FAILED",e.message)})}applyPendingChannelSwitch(){if(!this.state.sessionActive||!this.state.pendingChannelSwitch)return;const e=this.state.pendingChannelSwitch;this.state.pendingChannelSwitch=null;const s=c(this.state.sessionManifest,e);s?this.switchChannel(e,s):this.applyManifestUpdate(e)}applyManifestUpdate(e){if(!this.state.sessionActive)return;this.adoptExternalVendorConfigurations(e),this.diagnoseIgnoredDeliveryModeChange(e);const s=t(e);this.state.steeredSsaiActive?this.state.ssaiController?.setManifestAssetParameters(s.ssai?.assetParameters):this.deps.adPlayerController.setManifestAssetParameters(s.sgaiAssetParameters);const i=this.state.steeredSsaiActive?n(e):e;this.deps.breakScheduler.updateManifest(i),this.deps.pauseAdController.updateManifest(i)}diagnoseIgnoredDeliveryModeChange(e){if(!this.state.sessionDeliveryMode)return;const s=h(e,l(),null,"manifest");s.manifestMode!==this.state.sessionDeliveryMode&&this.deps.diagnose("DA-DELIVERY-MODE-CHANGE-IGNORED",`Polled manifest resolves delivery mode "${s.manifestMode}" but "${this.state.sessionDeliveryMode}" is pinned for this session.`,{polledMode:s.manifestMode,pinnedMode:this.state.sessionDeliveryMode})}async switchChannel(e,s){if(this.state.channelSwitchInFlight)return;const t=this.state.sessionConfig,i=this.state.sessionManifest,a={previousChannelId:s.previousChannelId,channelId:s.channelId,reason:s.reason,manifestUrl:t.manifestUrl};this.deps.diagnose("DA-MANIFEST-CHANNEL-CHANGED",`Manifest channel changed (${s.reason}): ${s.previousChannelId??"none"} -> ${s.channelId??"none"}`,a),this.state.sessionManifest=e,this.state.channelSwitchInFlight=!0;const r=this.state.sessionGeneration,n=this.state.sessionDeliveryMode??"sgai";try{let s=this.resolveSessionDeliveryMode(e,"channel-switch");if(this.deps.cmcdAdReporter.onSessionEnd(),this.deps.cmcdAdReporter.onSessionStart(e),this.deps.lensReporter.endSession(),this.deps.lensReporter.startSession(),"ssai"===s&&"sgai"===n){const a=await this.switchToSteeredSsai(e,t);if(this.isStaleStart(r))return;a||(s="sgai",this.state.sessionDeliveryMode="sgai",await this.applySameModeChannelSwitch(i,e,t,r))}else"sgai"===s&&"ssai"===n?await this.switchToSgai(e,t,r):await this.applySameModeChannelSwitch(i,e,t,r);if(this.isStaleStart(r))return}finally{this.state.channelSwitchInFlight=!1}this.adoptExternalVendorConfigurations(e),this.deps.startLivePushback(),this.emitChannelChange({...a,previousDeliveryMode:n,deliveryMode:this.state.sessionDeliveryMode??n})}emitChannelChange(e){this.deps.emit("adchannelchange",{type:"adchannelchange",timestamp:Date.now(),...e})}adoptExternalVendorConfigurations(e){this.deps.externalVendorConfigurationTracker.adopt(e)}diagnoseExternalVendorConfigurations(e){const s=i(e);s.invalidCount>0&&this.deps.diagnose("DA-EXTERNAL-VENDOR-INVALID",`Ignored ${s.invalidCount} invalid external vendorConfiguration `+(1===s.invalidCount?"entry":"entries")+".",{count:s.invalidCount}),s.duplicateProviders.length>0&&this.deps.diagnose("DA-EXTERNAL-VENDOR-DUPLICATE",`Ignored duplicate external vendorConfiguration entries for providers: ${s.duplicateProviders.join(", ")}.`,{providers:s.duplicateProviders})}async applySameModeChannelSwitch(e,s,i,a){const r=this.state.steeredSsaiActive,o=r?n(s):s;if(this.deps.breakScheduler.resetForChannelSwitch(o),this.deps.pauseAdController.updateManifest(o),this.deps.adPlayerController.setBreakResumePolicy(i.adInsertionType,s.timebase,this.deps.continueContentDuringBreak),!r&&p(e,s)){const e=this.deps.adPlayerController.updatedAssetParameters,t=this.deps.adPlayerController.sessionAssetParameterMacros;if(this.deps.adPlayerController.endGamSession(),this.deps.adPlayerController.setSessionAssetParameters(i.assetParameters),this.deps.adPlayerController.setSessionAssetParameterMacros(t),await this.startGamForManifest(s,i.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:i.manifestUrl})},a),this.isStaleStart(a))return;e&&this.deps.adPlayerController.updateAssetParameters(e)}else if(r){const e=t(s);this.state.ssaiController?.setManifestAssetParameters(e.ssai?.assetParameters)}else{const e=t(s);this.deps.adPlayerController.setManifestAssetParameters(e.sgaiAssetParameters)}}async switchToSteeredSsai(e,s){const t=o(e.vendorConfiguration);if(!t)return this.deps.diagnose("DA-SSAI-UNAVAILABLE","Channel switch resolved ssai delivery but no vendor configuration provides a stitched stream; the session stays in sgai.",{manifestUrl:s.manifestUrl,previousMode:"sgai"}),!1;const i=this.deps.adPlayerController,a=i.updatedAssetParameters,r=i.sessionAssetParameterMacros;i.stopActiveBreak(),i.releaseContentHold(),i.endGamSession(),i.setSessionAssetParameters(s.assetParameters),i.setSessionAssetParameterMacros(r),a&&i.updateAssetParameters(a);const d=this.createSsaiController(t.networkCode);this.state.ssaiController=d,this.state.steeredSsaiActive=!0,this.deps.diagnose("DA-SESSION-STARTING","Channel switch: ssai-live-stream",{step:"ssai-live-stream",manifestUrl:s.manifestUrl});try{await d.startSteered(t,s.assetParameters,r),a&&d.updateAssetParameters(a);const o=n(e);return this.deps.breakScheduler.resetForChannelSwitch(o),this.deps.pauseAdController.updateManifest(o),i.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),!0}catch(e){const s=e instanceof Error?e.message:String(e);return this.deps.diagnose("DA-SSAI-SESSION-FAILED",s),d.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,!1}}async switchToSgai(e,s,t){const i=this.state.ssaiController?.sessionAssetParameterMacros;this.state.ssaiController?.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,this.deps.breakScheduler.resetForChannelSwitch(e),this.deps.pauseAdController.updateManifest(e);const a=this.deps.adPlayerController;a.setBreakResumePolicy(s.adInsertionType,e.timebase,this.deps.continueContentDuringBreak);const r=a.updatedAssetParameters;a.endGamSession(),a.setSessionAssetParameters(s.assetParameters),a.setSessionAssetParameterMacros(i??s.assetParameterMacros),await this.startGamForManifest(e,s.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:s.manifestUrl})},t),this.isStaleStart(t)||r&&a.updateAssetParameters(r)}async startGamForManifest(e,i,a,r=this.state.sessionGeneration,n=!1){const o=t(e);if(this.deps.adPlayerController.setManifestAssetParameters(o.sgaiAssetParameters),o.unknownTypes.length>0&&this.deps.diagnose("DA-VENDOR-CONFIG-UNKNOWN-TYPE",`Ignored ${o.unknownTypes.length} vendorConfiguration `+(1===o.unknownTypes.length?"entry":"entries")+" with unrecognised type: "+`${o.unknownTypes.join(", ")}.`,{types:o.unknownTypes}),this.diagnoseExternalVendorConfigurations(e),!this.deps.adPlayerController.isGamEnabled())return void(s(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING","The break manifest carries GAM pod assets but GAM is disabled for this session (gam.enabled === false); those assets will be skipped.",{reason:"gam-disabled"}));if(!o.identity)return void(s(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING",'The break manifest carries GAM pod assets but no usable "gam" vendorConfiguration entry; those assets will be skipped.',{reason:o.reason}));this.deps.diagnose("DA-GAM-CONFIG-RESOLVED",`GAM session identity: network ${o.identity.networkCode}, custom asset ${o.identity.customAssetKey}`,{networkCode:o.identity.networkCode,customAssetKey:o.identity.customAssetKey,channelId:e.channelId,duplicateEntriesIgnored:o.duplicateGam});const d=e.adStartDelay??0;if(d>0&&!this.state.adStartDelayElapsed&&(n||this.deps.breakScheduler.isAdStartDelayActive()))return this.deps.breakScheduler.holdBreakStarts(),this.deps.diagnose("DA-GAM-START-DEFERRED",`GAM session start deferred until ${d} seconds of content have played.`,{adStartDelaySec:d}),void this.deps.breakScheduler.setOnAdStartDelayElapsed(()=>{if(this.isStaleStart(r)||!this.state.sessionActive)return;this.state.adStartDelayElapsed=!0;const s=this.state.sessionManifest??e,n=t(s).identity;n?(this.deps.adPlayerController.setManifestAssetParameters(t(s).sgaiAssetParameters),this.startResolvedGamSession(n,i,a,r,!0)):this.deps.breakScheduler.releaseBreakStarts()});this.state.adStartDelayElapsed=!0,await this.startResolvedGamSession(o.identity,i,a,r)}async startResolvedGamSession(e,s,t,i,a=!1){try{if(this.isStaleStart(i)||!this.state.sessionActive)return;t("gam-init"),await this.deps.adPlayerController.startGamSession(e,s),this.deps.log("GAM session started")}catch(e){this.deps.diagnose("DA-GAM-SESSION-FAILED",e instanceof Error?e.message:String(e))}finally{a&&!this.isStaleStart(i)&&this.state.sessionActive&&this.deps.breakScheduler.releaseBreakStarts()}}async startSsaiSession(e,s){try{if(this.state.stitcherOrigin=r(e.stitcherUrl),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!e.assetParameters}),await this.state.ssaiController.start(e),this.isStaleStart(s))return;this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active for ${e.stitcherUrl}`,{stitcherUrl:e.stitcherUrl}),this.deps.startLivePushback(),this.deps.log("SSAI session active")}catch(e){if(this.isStaleStart(s))return;const t=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",t.message),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),t}}isStaleStart(e){return e!==this.state.sessionGeneration&&(this.deps.log("Session start aborted — the session ended or was replaced while starting"),!0)}}
1
+ import{sessionTick as e}from"./sessionTick";import{getPreRollDelay as s}from"../types/BreakManifest";import{manifestHasGamAssets as t,resolveGamVendorConfiguration as a,resolveSsbStream as i}from"../services/resolveGamVendorConfiguration";import{SsaiController as r}from"../services/SsaiController";import{resolveUrlOrigin as n}from"../services/urlOrigin";import{filterSsaiClientBreaks as o,resolveStitchedStreamVendor as d,SsaiUnavailableError as l}from"../services/resolveDeliveryMode";import{detectChannelSwitch as h,gamPodIdentityChanged as c}from"../services/channelSwitch";import{prepareWebRuntime as p}from"../services/webCompatibility";import{adoptExternalVendorConfigurations as S,diagnoseExternalVendorConfigurations as m,diagnoseIgnoredDeliveryModeChange as f,emitChannelChange as u,handleEventBoundaryTick as A,isStaleSessionStart as y,isNonBlankString as g,isSsaiSessionConfig as v,rejectGamPodPreloadForBoundaryIdentity as P,resolveEventAtPosition as I,resolveSessionDeliveryMode as k}from"./SessionStarterHelpers";const C=1e4;export class SessionStarter{constructor(e,s){this.state=e,this.deps=s}async startSession(t){if("ssai"===this.deps.mode){if(!v(t)||!g(t.stitcherUrl))throw new Error('[OptiViewAds] mode "ssai" requires a stitcherUrl in startSession().')}else if(v(t)||!g(t.manifestUrl))throw new Error("[OptiViewAds] startSession requires a manifestUrl (the full break-manifest URL).");this.state.sessionActive&&(this.deps.log("Ending previous session before starting new one"),this.deps.endSessionInternal("session-restart"));const a=this.state.sessionGeneration;this.state.sessionActive=!0,this.state.adStartDelayElapsed=!1;try{const e=p();e&&await e}catch(e){if(this.isStaleStart(a))return;throw this.state.sessionActive=!1,e}if(this.isStaleStart(a))return;if(this.deps.lensReporter.startSession(),v(t))return t.adInsertionType&&this.deps.diagnose("DA-INSERTION-TYPE-IGNORED-SSAI",`adInsertionType "${t.adInsertionType}" ignored in SSAI mode; the stitcher controls ad insertion.`,{adInsertionType:t.adInsertionType}),void await this.startSsaiSession(t,a);const r=t.manifestUrl;this.state.sessionConfig=t,this.state.manifestOrigin=n(r),this.deps.manifestService.setManifestUrl(r),this.deps.log("Session starting, manifest URL:",r);const o=Date.now(),d=e=>{this.deps.diagnose("DA-SESSION-STARTING",`Session start: ${e}`,{step:e,manifestUrl:r,elapsedMs:Date.now()-o})};d("begin");const l=t.assetParameters;let h,c;this.deps.adPlayerController.setSessionAssetParameters(l),this.deps.adPlayerController.setSessionAssetParameterMacros(t.assetParameterMacros),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!l}),d("manifest-fetch");try{c=await this.deps.manifestService.fetchManifest();const e=I(c,Date.now(),this.deps.player);h=e.manifest,this.state.sessionManifest=c,this.state.effectiveSessionManifest=h,this.state.activeEventId=e.eventId}catch(e){if(this.isStaleStart(a))return;throw this.failSessionStart(e)}if(this.deps.cmcdAdReporter.onSessionStart(h),!this.isStaleStart(a))if(this.deps.externalPlayback.setManaged(null!==i(h.vendorConfiguration)),S(this.deps,h),"ssai"!==this.resolveSessionDeliveryMode(h))try{if(await this.startGamForManifest(h,l,d,a,!0),this.isStaleStart(a))return;const i=t.adInsertionType;this.deps.adPlayerController.setBreakResumePolicy(i,h.timebase,this.deps.continueContentDuringBreak),this.deps.diagnose("DA-INSERTION-TYPE-RESOLVED",void 0===i?`Ad insertion type: unset — the manifest's resumeOffset drives the resume point (timebase: ${h.timebase})`:`Ad insertion type: ${i}, client-set (timebase: ${h.timebase})`,{adInsertionType:i??null,source:void 0===i?"unset":"client",timebase:h.timebase}),d("scheduling"),this.deps.breakScheduler.updateManifest(h),this.deps.pauseAdController.updateManifest(h),this.state.pauseAdTickInterval=setInterval(e(this.deps),250);const n=(h.adStartDelay??0)>0?void 0:h.breaks.find(e=>0===s(e));n?(this.deps.adPlayerController.holdContentForPreRoll(n),this.state.heldImmediatePreRollId=n.id,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=Date.now(),this.contentIsReady()?this.armHeldImmediatePreRoll():this.state.heldPreRollArmInterval=setInterval(()=>{this.contentIsReady()?this.armHeldImmediatePreRoll():this.armHeldImmediatePreRollIfTimedOut()},250)):this.markPlaybackStartIfPlaying(),this.deps.startLivePushback(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`Session active for manifest ${r}`,{manifestUrl:r}),this.deps.log("Session active")}catch(e){if(this.isStaleStart(a))return;throw this.failSessionStart(e)}else await this.startSteeredSsaiSession(h,t,d)}discardPreparedGamSessionAfterSeek(){this.state.sessionActive&&(this.deps.adPlayerController?.gamPodBridge.discardPreparedGamSession(),this.state.eventSessionPreloadKeys.clear())}markPlaybackStartIfPlaying(){this.deps.player.paused||this.deps.breakScheduler.markPlaybackStart()}contentIsReady(){return!Number.isNaN(this.deps.player.duration)||!this.deps.player.paused}clearHeldImmediatePreRollArmInterval(){this.state.heldPreRollArmInterval&&(clearInterval(this.state.heldPreRollArmInterval),this.state.heldPreRollArmInterval=null)}armHeldImmediatePreRoll(){this.state.heldImmediatePreRollId&&!this.state.heldPreRollArmed&&(this.clearHeldImmediatePreRollArmInterval(),this.state.heldPreRollArmed=!0,this.deps.breakScheduler.markPlaybackStart())}armHeldImmediatePreRollIfTimedOut(){if(!this.state.heldImmediatePreRollId||this.state.heldPreRollArmed)return;const e=this.state.heldPreRollWaitStartedAt;if(null===e||Date.now()-e<C)return;const s=this.state.heldImmediatePreRollId;this.armHeldImmediatePreRoll(),this.deps.diagnose("DA-PREROLL-CONTENT-WAIT-TIMEOUT","Pending pre-roll began after waiting 10000ms for content readiness.",{breakId:s,waitMs:C})}cancelHeldImmediatePreRollOnContentError(){const e=this.state.heldImmediatePreRollId;return!(!e||this.state.heldPreRollArmed)&&(this.clearHeldImmediatePreRollArmInterval(),this.deps.breakScheduler?.completeBreak(e),this.deps.adPlayerController?.releaseContentHold(),this.state.heldImmediatePreRollId=null,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=null,this.deps.diagnose("DA-PREROLL-CANCELLED-CONTENT-ERROR","A pending pre-roll was cancelled because the content failed to load before it could begin.",{breakId:e}),!0)}clearHeldImmediatePreRollArm(){this.clearHeldImmediatePreRollArmInterval(),this.state.heldImmediatePreRollId=null,this.state.heldPreRollArmed=!1,this.state.heldPreRollWaitStartedAt=null}failSessionStart(e){const s=e instanceof Error?e:new Error(String(e)),t=this.deps.manifestService?.takeLastFailure();return this.deps.diagnose("DA-MANIFEST-FETCH-FAILED",s.message,t?{...t}:void 0),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),s}resolveSessionDeliveryMode(e,s="manifest"){return k(this.state,this.deps,e,s)}async startSteeredSsaiSession(s,t,a){const i=d(s.vendorConfiguration);if(!i){const e="Manifest selected ssai delivery but no vendor configuration provides a stitched stream (no gam entry with an assetKey).";throw this.deps.diagnose("DA-SSAI-UNAVAILABLE",e),this.deps.manifestService.reset(),this.state.sessionActive=!1,new l(e)}try{a("ssai-live-stream"),this.state.steeredSsaiActive=!0,this.state.ssaiController=this.createSsaiController(i.networkCode),await this.state.ssaiController.startSteered(i,t.assetParameters,t.assetParameterMacros),a("scheduling");const r=o(s);this.deps.adPlayerController.setBreakResumePolicy(t.adInsertionType,s.timebase,!1),this.deps.breakScheduler.updateManifest(r),this.deps.pauseAdController.updateManifest(r),this.state.pauseAdTickInterval=setInterval(e(this.deps),250),this.markPlaybackStartIfPlaying(),this.deps.startLivePushback(),this.startBreakScheduler(),this.startManifestPolling(),this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active (manifest-steered) for ${t.manifestUrl}`,{manifestUrl:t.manifestUrl}),this.deps.log("SSAI session active (manifest-steered)")}catch(e){const s=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",s.message),this.state.steeredSsaiActive=!1,this.deps.manifestService.reset(),this.state.sessionActive=!1,s}}createSsaiController(e){return new r(this.deps.player,{gam:this.state.steeredSsaiOptions?.streamActivityMonitorId?{streamActivityMonitorId:this.state.steeredSsaiOptions.streamActivityMonitorId}:{},ssai:{networkCode:e},maxBitrate:this.state.steeredSsaiOptions?.maxBitrate,autoplay:this.state.steeredSsaiOptions?.autoplay,debug:this.deps.debug},{emit:(e,s)=>this.deps.emit(e,s),diagnose:(e,s,t)=>this.deps.diagnose(e,s,t),log:(...e)=>this.deps.log(...e)})}startBreakScheduler(){this.deps.breakScheduler.setEventBoundaryCheck(e=>this.handleEventBoundaryTick(e)),this.deps.breakScheduler.start((e,s)=>{this.deps.breakLifecycle.onBreakStart(e,s)},e=>{if(this.deps.adPlayerController.isAdPlaying())return!1;if(this.deps.adPlayerController.gamPodBridge.shouldSkipGamPodPreload(e))return!1;const s=P(this.state,this.deps,this.deps.player,e);return"defer"!==s&&("mismatch"===s||this.deps.adPlayerController.preload(e).catch(s=>{this.deps.diagnose("DA-PRELOAD-FAILED",s instanceof Error?s.message:String(s),{breakId:e.id})}),!0)},(e,s,t)=>{this.deps.adBreakStatusTracker.onBreakWarning(e,s,t)},e=>{this.deps.breakLifecycle.onBreakAborted(e)},(e,s)=>this.deps.breakLifecycle.onBreakSkipped(e,s))}startManifestPolling(){this.deps.manifestService.startPolling(e=>{if(!this.state.sessionActive)return;const s=this.deps.notedManifestBreakIds(e);this.deps.diagnose("DA-MANIFEST-POLLED","Manifest polled",{breaks:e.breaks.length,newBreakIds:s,pollingIntervalSeconds:this.deps.manifestService.currentPollingIntervalSeconds});const t=this.state.sessionManifest,a=t?h(t,e):null;if(this.state.pendingChannelSwitch||a&&(this.deps.breakScheduler.getActiveBreak()||this.state.ssb))return this.state.pendingChannelSwitch=e,void this.deps.log("Channel switch deferred until the active break ends");if(a)return this.state.sessionManifest=e,this.state.pendingManifestUpdate=null,void this.switchChannel(e,a);const i="wallclock"===e.timebase&&!1!==this.deps.player.isLive&&Boolean(e.events?.length||t?.events?.length);this.state.sessionManifest=e,i?this.state.pendingManifestUpdate=e:(this.state.pendingManifestUpdate=null,this.state.effectiveSessionManifest=e,this.applyManifestUpdate(e))},e=>{this.state.sessionActive&&this.deps.diagnose("DA-MANIFEST-POLL-FAILED",e.message)})}applyPendingChannelSwitch(){if(!this.state.sessionActive||!this.state.pendingChannelSwitch||this.state.ssb)return;const e=this.state.pendingChannelSwitch;this.state.pendingChannelSwitch=null;const s=h(this.state.sessionManifest,e);s?this.switchChannel(e,s):this.applyManifestUpdate(e)}applyManifestUpdate(e){if(!this.state.sessionActive)return;S(this.deps,e),f(this.state,this.deps,e);const s=a(e);this.state.steeredSsaiActive?this.state.ssaiController?.setManifestAssetParameters(s.ssai?.assetParameters):this.deps.adPlayerController.setManifestAssetParameters(s.sgaiAssetParameters);const t=this.state.steeredSsaiActive?o(e):e;this.deps.breakScheduler.updateManifest(t),this.deps.pauseAdController.updateManifest(t)}handleEventBoundaryTick(e){return A(this.state,this.deps,e,{resolveDeliveryMode:e=>this.resolveSessionDeliveryMode(e,"event-boundary"),applyManifestUpdate:e=>this.applyManifestUpdate(e),switchChannel:(e,s,t,a)=>this.switchChannel(e,s,t,a)})}async switchChannel(e,s,t=!1,a=e){if(this.state.channelSwitchInFlight)return;const r=this.state.sessionConfig,n=this.state.effectiveSessionManifest??this.state.sessionManifest;if(!t){const s=this.deps.player.programDateTime?.getTime()??Date.now(),t=I(a,s,this.deps.player);e=t.manifest,this.state.activeEventId=t.eventId}const o=t?"event-boundary":s.reason,d={previousChannelId:s.previousChannelId,channelId:s.channelId,eventId:this.state.activeEventId,reason:o,manifestUrl:r.manifestUrl};this.deps.diagnose("DA-MANIFEST-CHANNEL-CHANGED",`Manifest channel changed (${o}): ${s.previousChannelId??"none"} -> ${s.channelId??"none"}`,d),this.state.sessionManifest=a,this.state.effectiveSessionManifest=e,this.state.pendingManifestUpdate=null,this.deps.externalPlayback.setManaged(null!==i(e.vendorConfiguration)),this.state.channelSwitchInFlight=!0;const l=this.state.sessionGeneration,h=this.state.sessionDeliveryMode??"sgai";try{let s=this.resolveSessionDeliveryMode(e,t?"event-boundary":"channel-switch");if(t?this.deps.cmcdAdReporter.updateEventId(this.state.activeEventId):(this.deps.cmcdAdReporter.onSessionEnd(),this.deps.cmcdAdReporter.onSessionStart(e),this.deps.lensReporter.endSession(),this.deps.lensReporter.startSession()),"ssai"===s&&"sgai"===h){const a=await this.switchToSteeredSsai(e,r,t);if(this.isStaleStart(l))return;a||(s="sgai",this.state.sessionDeliveryMode="sgai",await this.applySameModeChannelSwitch(n,e,r,l,t))}else"sgai"===s&&"ssai"===h?await this.switchToSgai(e,r,l,t):await this.applySameModeChannelSwitch(n,e,r,l,t);if(this.isStaleStart(l))return}finally{this.state.channelSwitchInFlight=!1,t&&this.deps.breakScheduler.releaseBreakStarts()}S(this.deps,e),this.deps.startLivePushback(),u(this.deps,{...d,previousDeliveryMode:h,deliveryMode:this.state.sessionDeliveryMode??h})}async applySameModeChannelSwitch(e,s,t,i,r=!1){const n=this.state.steeredSsaiActive,d=n?o(s):s;if(r?this.deps.breakScheduler.applyEventBoundary(d):this.deps.breakScheduler.resetForChannelSwitch(d),this.deps.pauseAdController.updateManifest(d),this.deps.adPlayerController.setBreakResumePolicy(t.adInsertionType,s.timebase,this.deps.continueContentDuringBreak),!n&&c(e,s)){const e=this.deps.adPlayerController,n=a(s);if(!(!r||!n.identity)&&await e.gamPodBridge.adoptPreparedGamSession(n.identity,t.assetParameters,n.sgaiAssetParameters))e.updatedAssetParameters&&e.updateAssetParameters(e.updatedAssetParameters);else{const a=e.updatedAssetParameters,r=e.sessionAssetParameterMacros;if(e.endGamSession(),e.setSessionAssetParameters(t.assetParameters),e.setSessionAssetParameterMacros(r),await this.startGamForManifest(s,t.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:t.manifestUrl})},i),this.isStaleStart(i))return;a&&e.updateAssetParameters(a)}}else if(n){const e=a(s);this.state.ssaiController?.setManifestAssetParameters(e.ssai?.assetParameters)}else{const e=a(s);this.deps.adPlayerController.setManifestAssetParameters(e.sgaiAssetParameters)}}async switchToSteeredSsai(e,s,t=!1){const a=d(e.vendorConfiguration);if(!a)return this.deps.diagnose("DA-SSAI-UNAVAILABLE","Channel switch resolved ssai delivery but no vendor configuration provides a stitched stream; the session stays in sgai.",{manifestUrl:s.manifestUrl,previousMode:"sgai"}),!1;const i=this.deps.adPlayerController,r=i.updatedAssetParameters,n=i.sessionAssetParameterMacros;i.stopActiveBreak(),i.releaseContentHold(),i.endGamSession(),i.setSessionAssetParameters(s.assetParameters),i.setSessionAssetParameterMacros(n),r&&i.updateAssetParameters(r);const l=this.createSsaiController(a.networkCode);this.state.ssaiController=l,this.state.steeredSsaiActive=!0,this.deps.diagnose("DA-SESSION-STARTING","Channel switch: ssai-live-stream",{step:"ssai-live-stream",manifestUrl:s.manifestUrl});try{await l.startSteered(a,s.assetParameters,n),r&&l.updateAssetParameters(r);const d=o(e);return t?this.deps.breakScheduler.applyEventBoundary(d):this.deps.breakScheduler.resetForChannelSwitch(d),this.deps.pauseAdController.updateManifest(d),i.setBreakResumePolicy(s.adInsertionType,e.timebase,!1),!0}catch(e){const s=e instanceof Error?e.message:String(e);return this.deps.diagnose("DA-SSAI-SESSION-FAILED",s),l.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,!1}}async switchToSgai(e,s,t,a=!1){const i=this.state.ssaiController?.sessionAssetParameterMacros;this.state.ssaiController?.stop(),delete this.state.ssaiController,this.state.steeredSsaiActive=!1,a?this.deps.breakScheduler.applyEventBoundary(e):this.deps.breakScheduler.resetForChannelSwitch(e),this.deps.pauseAdController.updateManifest(e);const r=this.deps.adPlayerController;r.setBreakResumePolicy(s.adInsertionType,e.timebase,this.deps.continueContentDuringBreak);const n=r.updatedAssetParameters;r.endGamSession(),r.setSessionAssetParameters(s.assetParameters),r.setSessionAssetParameterMacros(i??s.assetParameterMacros),await this.startGamForManifest(e,s.assetParameters,e=>{this.deps.diagnose("DA-SESSION-STARTING",`Channel switch: ${e}`,{step:e,manifestUrl:s.manifestUrl})},t),this.isStaleStart(t)||n&&r.updateAssetParameters(n)}async startGamForManifest(e,s,i,r=this.state.sessionGeneration,n=!1){const o=a(e);if(this.deps.adPlayerController.setManifestAssetParameters(o.sgaiAssetParameters),o.unknownTypes.length>0&&this.deps.diagnose("DA-VENDOR-CONFIG-UNKNOWN-TYPE",`Ignored ${o.unknownTypes.length} vendorConfiguration `+(1===o.unknownTypes.length?"entry":"entries")+" with unrecognised type: "+`${o.unknownTypes.join(", ")}.`,{types:o.unknownTypes}),m(this.deps,e),!this.deps.adPlayerController.isGamEnabled())return void(t(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING","The break manifest carries GAM pod assets but GAM is disabled for this session (gam.enabled === false); those assets will be skipped.",{reason:"gam-disabled"}));if(!o.identity)return void(t(e)&&this.deps.diagnose("DA-GAM-CONFIG-MISSING",'The break manifest carries GAM pod assets but no usable "gam" vendorConfiguration entry; those assets will be skipped.',{reason:o.reason}));this.deps.diagnose("DA-GAM-CONFIG-RESOLVED",`GAM session identity: network ${o.identity.networkCode}, custom asset ${o.identity.customAssetKey}`,{networkCode:o.identity.networkCode,customAssetKey:o.identity.customAssetKey,channelId:e.channelId,duplicateEntriesIgnored:o.duplicateGam});const d=e.adStartDelay??0;if(d>0&&!this.state.adStartDelayElapsed&&(n||this.deps.breakScheduler.isAdStartDelayActive()))return this.deps.breakScheduler.holdBreakStarts(),this.deps.diagnose("DA-GAM-START-DEFERRED",`GAM session start deferred until ${d} seconds of content have played.`,{adStartDelaySec:d}),void this.deps.breakScheduler.setOnAdStartDelayElapsed(()=>{if(this.isStaleStart(r)||!this.state.sessionActive)return;this.state.adStartDelayElapsed=!0;const t=this.state.sessionManifest??e,n=a(t).identity;n?(this.deps.adPlayerController.setManifestAssetParameters(a(t).sgaiAssetParameters),this.startResolvedGamSession(n,s,i,r,!0)):this.deps.breakScheduler.releaseBreakStarts()});this.state.adStartDelayElapsed=!0,await this.startResolvedGamSession(o.identity,s,i,r)}async startResolvedGamSession(e,s,t,a,i=!1){try{if(this.isStaleStart(a)||!this.state.sessionActive)return;t("gam-init"),await this.deps.adPlayerController.startGamSession(e,s),this.deps.log("GAM session started")}catch(e){this.deps.diagnose("DA-GAM-SESSION-FAILED",e instanceof Error?e.message:String(e))}finally{i&&!this.isStaleStart(a)&&this.state.sessionActive&&this.deps.breakScheduler.releaseBreakStarts()}}async startSsaiSession(e,s){try{if(this.state.stitcherOrigin=n(e.stitcherUrl),this.state.configSummary.gam&&(this.state.configSummary.gam={...this.state.configSummary.gam,hasAssetParameters:!!e.assetParameters}),await this.state.ssaiController.start(e),this.isStaleStart(s))return;this.deps.diagnose("DA-SESSION-STARTED",`SSAI session active for ${e.stitcherUrl}`,{stitcherUrl:e.stitcherUrl}),this.deps.startLivePushback(),this.deps.log("SSAI session active")}catch(e){if(this.isStaleStart(s))return;const t=e instanceof Error?e:new Error(String(e));throw this.deps.diagnose("DA-SSAI-SESSION-FAILED",t.message),this.state.sessionActive=!1,this.deps.cmcdAdReporter.onSessionEnd(),this.deps.lensReporter.endSession(),t}}isStaleStart(e){return y(this.state,this.deps,e)}}
@@ -0,0 +1,39 @@
1
+ import type { PlayerAdapter } from '../interfaces/PlayerAdapter';
2
+ import type { OptiViewAdsMode } from '../types/OptiViewAdsConfig';
3
+ import type { ManifestService } from '../services/ManifestService';
4
+ import type { BreakScheduler } from '../services/BreakScheduler';
5
+ import type { AdPlayerController } from '../services/AdPlayerController';
6
+ import type { PauseAdController } from '../services/PauseAdController';
7
+ import type { LensReporter } from '../services/LensReporter';
8
+ import type { CmcdAdReporter } from '../services/CmcdAdReporter';
9
+ import type { ExternalPlaybackGate } from '../services/ExternalPlaybackGate';
10
+ import type { ExternalVendorConfigurationTracker } from '../services/ExternalVendorConfigurationTracker';
11
+ import type { AdBreakStatusTracker, EmitEvent } from './AdBreakStatusTracker';
12
+ import type { BreakLifecycle, Diagnose, Log } from './BreakLifecycle';
13
+ export interface SessionStarterDeps {
14
+ externalPlayback: ExternalPlaybackGate;
15
+ player: PlayerAdapter;
16
+ debug: boolean;
17
+ mode: OptiViewAdsMode;
18
+ hasExplicitMode: boolean;
19
+ continueContentDuringBreak: boolean;
20
+ startLivePushback: () => void;
21
+ manifestService: ManifestService | undefined;
22
+ breakScheduler: BreakScheduler | undefined;
23
+ adPlayerController: AdPlayerController | undefined;
24
+ pauseAdController: PauseAdController | undefined;
25
+ breakLifecycle: BreakLifecycle | undefined;
26
+ cmcdAdReporter: CmcdAdReporter;
27
+ lensReporter: LensReporter;
28
+ adBreakStatusTracker: AdBreakStatusTracker;
29
+ externalVendorConfigurationTracker: ExternalVendorConfigurationTracker;
30
+ emit: EmitEvent;
31
+ diagnose: Diagnose;
32
+ log: Log;
33
+ endSessionInternal: (cause: string) => void;
34
+ notedManifestBreakIds: (manifest: {
35
+ breaks: Array<{
36
+ id: string;
37
+ }>;
38
+ }) => string[];
39
+ }
@@ -0,0 +1 @@
1
+ export{};
@@ -0,0 +1,36 @@
1
+ import type { PlayerAdapter } from '../interfaces/PlayerAdapter';
2
+ import type { Break, BreakManifest } from '../types/BreakManifest';
3
+ import type { OptiViewAdsMode, SessionDeliveryMode, SsaiSessionConfig, StartSessionConfig } from '../types/OptiViewAdsConfig';
4
+ import { type ChannelSwitch } from '../services/channelSwitch';
5
+ import type { SessionState } from './SessionState';
6
+ import type { SessionStarterDeps } from './SessionStarterDeps';
7
+ export declare const EVENT_SESSION_PRELOAD_LEAD_MS = 8000;
8
+ export declare function isSsaiSessionConfig(config: StartSessionConfig): config is SsaiSessionConfig;
9
+ export declare function isNonBlankString(value: unknown): value is string;
10
+ export declare function resolveEventAtPosition(manifest: BreakManifest, positionMs: number, player: PlayerAdapter): {
11
+ manifest: BreakManifest;
12
+ eventId: string | null;
13
+ };
14
+ export type GamPodPreloadBoundaryDecision = 'none' | 'defer' | 'mismatch';
15
+ export declare function rejectGamPodPreloadForBoundaryIdentity(state: SessionState, deps: SessionStarterDeps, player: PlayerAdapter, breakInfo: Break): GamPodPreloadBoundaryDecision;
16
+ export declare function diagnoseIgnoredDeliveryModeChange(state: SessionState, deps: SessionStarterDeps, manifest: BreakManifest): void;
17
+ export declare function resolveSessionDeliveryMode(state: SessionState, deps: SessionStarterDeps, manifest: BreakManifest, source?: 'manifest' | 'channel-switch' | 'event-boundary'): OptiViewAdsMode;
18
+ export declare function isStaleSessionStart(state: SessionState, deps: SessionStarterDeps, generation: number): boolean;
19
+ export declare function emitChannelChange(deps: SessionStarterDeps, context: {
20
+ previousChannelId: string | null;
21
+ channelId: string | null;
22
+ eventId: string | null;
23
+ reason: ChannelSwitch['reason'];
24
+ manifestUrl: string;
25
+ previousDeliveryMode: SessionDeliveryMode;
26
+ deliveryMode: SessionDeliveryMode;
27
+ }): void;
28
+ export declare function adoptExternalVendorConfigurations(deps: SessionStarterDeps, manifest: BreakManifest): void;
29
+ export declare function diagnoseExternalVendorConfigurations(deps: SessionStarterDeps, manifest: BreakManifest): void;
30
+ type EventBoundaryActions = {
31
+ resolveDeliveryMode: (manifest: BreakManifest) => OptiViewAdsMode;
32
+ applyManifestUpdate: (manifest: BreakManifest) => void;
33
+ switchChannel: (manifest: BreakManifest, change: ChannelSwitch, eventBoundary: boolean, rawManifest: BreakManifest) => Promise<void>;
34
+ };
35
+ export declare function handleEventBoundaryTick(state: SessionState, deps: SessionStarterDeps, positionMs: number | null, actions: EventBoundaryActions): boolean;
36
+ export {};
@@ -0,0 +1 @@
1
+ import{Timebase as e}from"../types/BreakManifest";import{resolveExternalVendorConfigurations as n}from"../services/resolveExternalVendorConfigurations";import{detectDeviceProfile as t}from"../services/detectDeviceType";import{isGamVendorParameters as s}from"../services/GamPodUrlBuilder";import{decideDeliveryMode as i,detectChannelSwitch as o,gamPodIdentityChanged as r}from"../services/channelSwitch";import{nextEventBoundaryMs as a,resolveEventManifest as d}from"../services/resolveChannelEvents";import{filterSsaiClientBreaks as l}from"../services/resolveDeliveryMode";import{gamPodIdentityMismatches as u,resolveGamVendorConfiguration as f}from"../services/resolveGamVendorConfiguration";export const EVENT_SESSION_PRELOAD_LEAD_MS=8e3;export function isSsaiSessionConfig(e){return"stitcherUrl"in e}export function isNonBlankString(e){return"string"==typeof e&&""!==e.trim()}export function resolveEventAtPosition(n,t,s){return n.timebase!==e.WALLCLOCK||!1===s.isLive?{manifest:n,eventId:n.eventId??null}:d(n,t)}export function rejectGamPodPreloadForBoundaryIdentity(n,t,i,o){const r=n.pendingManifestUpdate??n.sessionManifest;if(!r||r.timebase!==e.WALLCLOCK||!1===i.isLive||"string"!=typeof o.start)return"none";const a=Date.parse(o.start);if(!Number.isFinite(a))return"none";const l=d(r,a).manifest,c=f(l).identity;if(!c)return"none";const v=n.effectiveSessionManifest??r,m=f(v).identity;if(m&&c.networkCode===m.networkCode&&c.customAssetKey===m.customAssetKey)return"none";const p=(Array.isArray(o.variant)?o.variant:[o.variant]).flatMap(e=>e.assets).filter(e=>"vendor"===e.type&&"gam"===e.vendor&&s(e.vendorParameters));if(0===p.length)return"none";const y=p.find(e=>0===u(c,e.vendorParameters).length);if(y)return"defer";const g=p[0];if(!g)return"none";for(const e of u(c,g.vendorParameters))t.diagnose("DA-GAM-POD-IDENTITY-MISMATCH",`GAM asset ${g.id} skipped: vendorParameters.${e.field} "${e.assetValue}" does not match the stream session's "${e.sessionValue}".`,{assetId:g.id,field:e.field,assetValue:e.assetValue,sessionValue:e.sessionValue});return"mismatch"}export function diagnoseIgnoredDeliveryModeChange(e,n,s){if(!e.sessionDeliveryMode)return;const o="ssb"===e.sessionDeliveryMode?e.ssb?.previousMode:e.sessionDeliveryMode;if(!o)return;const r=i(s,t(),null,"manifest");r.manifestMode!==o&&n.diagnose("DA-DELIVERY-MODE-CHANGE-IGNORED",`Polled manifest resolves delivery mode "${r.manifestMode}" but "${o}" is pinned for this session.`,{polledMode:r.manifestMode,pinnedMode:o})}export function resolveSessionDeliveryMode(e,n,s,o="manifest"){const r=e.sessionDeliveryMode,a=i(s,t(),n.hasExplicitMode?n.mode:null,o);null!==a.unknownMode&&n.diagnose("DA-DELIVERY-MODE-UNKNOWN",`Delivery rule ${a.ruleIndex} carries unrecognized mode "${a.unknownMode}"; using sgai.`,{unknownMode:a.unknownMode,ruleIndex:a.ruleIndex});for(const e of a.targetingIssues)n.diagnose(e.code,`Targeting ${e.field} was rejected.`,{field:e.field,...void 0===e.value?{}:{value:e.value}});return n.hasExplicitMode&&a.manifestMode!==a.mode&&n.diagnose("DA-DELIVERY-MODE-OVERRIDDEN",`Manifest delivery rules resolve "${a.manifestMode}" but the explicit config mode "${a.mode}" takes precedence.`,{manifestMode:a.manifestMode,configuredMode:a.mode}),e.sessionDeliveryMode=a.mode,n.diagnose("DA-DELIVERY-MODE-RESOLVED",`Delivery mode: ${a.mode}`,{mode:a.mode,ruleIndex:a.ruleIndex,source:a.source,deviceProfile:t(),..."manifest"!==o?{previousMode:r}:{}}),a.mode}export function isStaleSessionStart(e,n,t){return t!==e.sessionGeneration&&(n.log("Session start aborted — the session ended or was replaced while starting"),!0)}export function emitChannelChange(e,n){e.emit("adchannelchange",{type:"adchannelchange",timestamp:Date.now(),...n})}export function adoptExternalVendorConfigurations(e,n){e.externalVendorConfigurationTracker.adopt(n)}export function diagnoseExternalVendorConfigurations(e,t){const s=n(t);s.invalidCount>0&&e.diagnose("DA-EXTERNAL-VENDOR-INVALID",`Ignored ${s.invalidCount} invalid external vendorConfiguration `+(1===s.invalidCount?"entry":"entries")+".",{count:s.invalidCount}),s.duplicateProviders.length>0&&e.diagnose("DA-EXTERNAL-VENDOR-DUPLICATE",`Ignored duplicate external vendorConfiguration entries for providers: ${s.duplicateProviders.join(", ")}.`,{providers:s.duplicateProviders})}export function handleEventBoundaryTick(n,s,d,u){const c=n.sessionManifest;if(!n.sessionActive||!c||c.timebase!==e.WALLCLOCK||!1===s.player.isLive||null===d||!Number.isFinite(d))return!0;const v=n.pendingManifestUpdate,m=v??c,p=resolveEventAtPosition(m,d,s.player),y=p.eventId!==n.activeEventId;if(y){if(n.channelSwitchInFlight)return!1;if(s.breakScheduler.getActiveBreak())return!0}if(!y)return v&&(n.pendingManifestUpdate=null,n.effectiveSessionManifest=p.manifest,u.applyManifestUpdate(p.manifest)),function(e,n,s,o){const d=a(s,o),l=null===d?null:d-o;if(null===d||null===l||l<0||l>8e3||e.steeredSsaiActive)return void n.adPlayerController.gamPodBridge.discardPreparedGamSession();const u=resolveEventAtPosition(s,o,n.player).manifest,c=resolveEventAtPosition(s,d,n.player).manifest,v=n.hasExplicitMode?n.mode:null,m=i(u,t(),v,"event-boundary").mode,p=i(c,t(),v,"event-boundary").mode,y=f(u),g=f(c);if("sgai"!==m||"sgai"!==p||!g.identity||y.identity?.customAssetKey===g.identity.customAssetKey||!r(u,c)||!function(e,n){return e.breaks.some(e=>{const t="string"==typeof e.start?Date.parse(e.start):Number.NaN,s=Array.isArray(e.variant)?e.variant:[e.variant];return Number.isFinite(t)&&t>=n&&t<=n+8e3&&s.some(e=>e.assets.some(e=>"vendor"===e.type&&"gam"===e.vendor))})}(c,d))return void n.adPlayerController.gamPodBridge.discardPreparedGamSession();const M=g.identity,h=`${d}:${M.networkCode}:${M.customAssetKey}`;if(e.eventSessionPreloadKeys.has(h))return;e.eventSessionPreloadKeys.add(h),n.adPlayerController.gamPodBridge.prepareNextGamSession(M,g.sgaiAssetParameters).then(e=>{e&&n.diagnose("DA-GAM-SESSION-PRELOADED","GAM session preloaded for the next event.",{boundaryMs:d,networkCode:M.networkCode,customAssetKey:M.customAssetKey})}).catch(e=>{n.log("Next-event GAM session preload failed",e)})}(n,s,m,d),!0;const g=n.effectiveSessionManifest??c,M=i(p.manifest,t(),s.hasExplicitMode?s.mode:null,"event-boundary").mode,h=o(g,p.manifest)??(M!==n.sessionDeliveryMode?{previousChannelId:g.channelId??null,channelId:p.manifest.channelId??g.channelId??null,reason:"event-boundary"}:null);if(v&&(n.pendingManifestUpdate=null),s.breakScheduler.holdBreakStarts(),n.activeEventId=p.eventId,h)return u.switchChannel(p.manifest,{...h,reason:"event-boundary"},!0,m),!1;n.effectiveSessionManifest=p.manifest;try{!function(e,n,t,s,i,o){const r=e.sessionDeliveryMode??"sgai",a=o.resolveDeliveryMode(s);adoptExternalVendorConfigurations(n,s);const d=f(s);e.steeredSsaiActive?e.ssaiController?.setManifestAssetParameters(d.ssai?.assetParameters):n.adPlayerController.setManifestAssetParameters(d.sgaiAssetParameters);const u=e.steeredSsaiActive?l(s):s;n.breakScheduler.applyEventBoundary(u),n.pauseAdController.updateManifest(u),n.cmcdAdReporter.updateEventId(i);const c=s.channelId??t.channelId??null;emitChannelChange(n,{previousChannelId:t.channelId??c,channelId:c,eventId:i,reason:"event-boundary",manifestUrl:e.sessionConfig.manifestUrl,previousDeliveryMode:r,deliveryMode:a})}(n,s,g,p.manifest,p.eventId,u)}finally{s.breakScheduler.releaseBreakStarts()}return!0}
@@ -1,7 +1,22 @@
1
- import { OptiViewAdsMode, SessionConfig } from '../types/OptiViewAdsConfig';
1
+ import { OptiViewAdsMode, SessionConfig, SessionDeliveryMode } from '../types/OptiViewAdsConfig';
2
2
  import { BreakManifest } from '../types/BreakManifest';
3
+ import type { ExternalPlaybackAllowedTrigger } from '../services/ExternalPlaybackGate';
4
+ import type { SsbSwitchPhase } from '../services/ssbSwitchSignals';
3
5
  import { SsaiController } from '../services/SsaiController';
4
6
  import { ConfigSummary } from '../diagnostics/DiagnosticEvent';
7
+ export interface SsbSwitchState {
8
+ previousMode: OptiViewAdsMode;
9
+ previousSource: string;
10
+ previousPosition: number;
11
+ previousLive: boolean;
12
+ switchedAt: number;
13
+ /** What opened the gate: the request itself or the retry after a break. */
14
+ trigger: ExternalPlaybackAllowedTrigger;
15
+ phase: SsbSwitchPhase;
16
+ activeAt: number | null;
17
+ /** Set once the `adchannelchange` into `ssb` went out; the restore emits its counterpart only then. */
18
+ announced: boolean;
19
+ }
5
20
  /** Mutable session state owned by `OptiViewAds` and shared by reference with the `session/` modules. */
6
21
  export declare class SessionState {
7
22
  sessionActive: boolean;
@@ -12,7 +27,8 @@ export declare class SessionState {
12
27
  */
13
28
  sessionGeneration: number;
14
29
  adStartDelayElapsed: boolean;
15
- sessionDeliveryMode: OptiViewAdsMode | null;
30
+ sessionDeliveryMode: SessionDeliveryMode | null;
31
+ ssb: SsbSwitchState | null;
16
32
  steeredSsaiActive: boolean;
17
33
  steeredSsaiOptions: {
18
34
  maxBitrate: number | undefined;
@@ -35,6 +51,10 @@ export declare class SessionState {
35
51
  snapbackSeekInFlight: boolean;
36
52
  sessionConfig: SessionConfig | null;
37
53
  sessionManifest: BreakManifest | null;
54
+ effectiveSessionManifest: BreakManifest | null;
55
+ activeEventId: string | null;
56
+ eventSessionPreloadKeys: Set<string>;
57
+ pendingManifestUpdate: BreakManifest | null;
38
58
  channelSwitchInFlight: boolean;
39
59
  pendingChannelSwitch: BreakManifest | null;
40
60
  clearSessionIdentity(): void;
@@ -1 +1 @@
1
- export class SessionState{constructor(){this.sessionActive=!1,this.sessionGeneration=0,this.adStartDelayElapsed=!1,this.sessionDeliveryMode=null,this.steeredSsaiActive=!1,this.steeredSsaiOptions=null,this.pauseAdTickInterval=null,this.heldPreRollArmInterval=null,this.manifestOrigin=null,this.stitcherOrigin=null,this.heldImmediatePreRollId=null,this.heldPreRollArmed=!1,this.heldPreRollWaitStartedAt=null,this.snapbackSeekInFlight=!1,this.sessionConfig=null,this.sessionManifest=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}clearSessionIdentity(){this.sessionDeliveryMode=null,this.sessionManifest=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}}
1
+ export class SessionState{constructor(){this.sessionActive=!1,this.sessionGeneration=0,this.adStartDelayElapsed=!1,this.sessionDeliveryMode=null,this.ssb=null,this.steeredSsaiActive=!1,this.steeredSsaiOptions=null,this.pauseAdTickInterval=null,this.heldPreRollArmInterval=null,this.manifestOrigin=null,this.stitcherOrigin=null,this.heldImmediatePreRollId=null,this.heldPreRollArmed=!1,this.heldPreRollWaitStartedAt=null,this.snapbackSeekInFlight=!1,this.sessionConfig=null,this.sessionManifest=null,this.effectiveSessionManifest=null,this.activeEventId=null,this.eventSessionPreloadKeys=new Set,this.pendingManifestUpdate=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}clearSessionIdentity(){this.sessionDeliveryMode=null,this.sessionManifest=null,this.effectiveSessionManifest=null,this.activeEventId=null,this.eventSessionPreloadKeys.clear(),this.pendingManifestUpdate=null,this.channelSwitchInFlight=!1,this.pendingChannelSwitch=null}}