@dolby-optiview/ads-sdk-core 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/OptiViewAds.js +1 -1
- package/dist/generated/diagnosticCodes.d.ts +15 -0
- package/dist/generated/diagnosticCodes.js +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/services/AdPlayerController.js +1 -1
- package/dist/services/BreakScheduler.d.ts +9 -17
- package/dist/services/BreakScheduler.js +1 -1
- package/dist/services/BreakSchedulerTypes.d.ts +4 -0
- package/dist/services/SchedulerSnapback.d.ts +32 -0
- package/dist/services/SchedulerSnapback.js +1 -0
- package/dist/services/SnapbackGuard.d.ts +36 -0
- package/dist/services/SnapbackGuard.js +1 -0
- package/dist/services/adPlayer/AdPlayerCallbacks.d.ts +2 -0
- package/dist/services/adPlayer/AdPlayerState.d.ts +1 -0
- package/dist/services/adPlayer/AdPlayerState.js +1 -1
- package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -1
- package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
- package/dist/services/checkEventBreak.d.ts +5 -0
- package/dist/services/checkEventBreak.js +1 -0
- package/dist/services/resolveResumePoint.d.ts +3 -1
- package/dist/services/resolveResumePoint.js +1 -1
- package/dist/session/BreakLifecycle.js +1 -1
- package/dist/session/SessionStarter.js +1 -1
- package/dist/session/contentSnapback.d.ts +5 -1
- package/dist/session/contentSnapback.js +1 -1
- package/dist/types/BreakManifest.d.ts +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
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)}}
|
|
1
|
+
import{sessionTick as e}from"./sessionTick";import{configureVodSnapback as s}from"./contentSnapback";import{getPreRollDelay as t}from"../types/BreakManifest";import{manifestHasGamAssets as a,resolveGamVendorConfiguration as i,resolveSsbStream as r}from"../services/resolveGamVendorConfiguration";import{SsaiController as n}from"../services/SsaiController";import{resolveUrlOrigin as o}from"../services/urlOrigin";import{filterSsaiClientBreaks as d,resolveStitchedStreamVendor as l,SsaiUnavailableError as h}from"../services/resolveDeliveryMode";import{detectChannelSwitch as c,gamPodIdentityChanged as p}from"../services/channelSwitch";import{prepareWebRuntime as S}from"../services/webCompatibility";import{adoptExternalVendorConfigurations as m,diagnoseExternalVendorConfigurations as f,diagnoseIgnoredDeliveryModeChange as u,emitChannelChange as A,handleEventBoundaryTick as y,isStaleSessionStart as g,isNonBlankString as v,isSsaiSessionConfig as P,rejectGamPodPreloadForBoundaryIdentity as I,resolveEventAtPosition as k,resolveSessionDeliveryMode as C}from"./SessionStarterHelpers";const M=1e4;export class SessionStarter{constructor(e,s){this.state=e,this.deps=s}async startSession(s){if("ssai"===this.deps.mode){if(!P(s)||!v(s.stitcherUrl))throw new Error('[OptiViewAds] mode "ssai" requires a stitcherUrl in startSession().')}else if(P(s)||!v(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 a=this.state.sessionGeneration;this.state.sessionActive=!0,this.state.adStartDelayElapsed=!1;try{const e=S();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(),P(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,a);const i=s.manifestUrl;this.state.sessionConfig=s,this.state.manifestOrigin=o(i),this.deps.manifestService.setManifestUrl(i),this.deps.log("Session starting, manifest URL:",i);const n=Date.now(),d=e=>{this.deps.diagnose("DA-SESSION-STARTING",`Session start: ${e}`,{step:e,manifestUrl:i,elapsedMs:Date.now()-n})};d("begin");const l=s.assetParameters;let h,c;this.deps.adPlayerController.setSessionAssetParameters(l),this.deps.adPlayerController.setSessionAssetParameterMacros(s.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=k(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!==r(h.vendorConfiguration)),m(this.deps,h),"ssai"!==this.resolveSessionDeliveryMode(h))try{if(await this.startGamForManifest(h,l,d,a,!0),this.isStaleStart(a))return;const r=s.adInsertionType;this.deps.adPlayerController.setBreakResumePolicy(r,h.timebase,this.deps.continueContentDuringBreak),this.deps.diagnose("DA-INSERTION-TYPE-RESOLVED",void 0===r?`Ad insertion type: unset — the manifest's resumeOffset drives the resume point (timebase: ${h.timebase})`:`Ad insertion type: ${r}, client-set (timebase: ${h.timebase})`,{adInsertionType:r??null,source:void 0===r?"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===t(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 ${i}`,{manifestUrl:i}),this.deps.log("Session active")}catch(e){if(this.isStaleStart(a))return;throw this.failSessionStart(e)}else await this.startSteeredSsaiSession(h,s,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<M)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:M})}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 C(this.state,this.deps,e,s)}async startSteeredSsaiSession(s,t,a){const i=l(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 h(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=d(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 n(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(){s(this.deps.breakScheduler,this.deps.player,e=>"ssai"!==this.state.sessionDeliveryMode&&!this.state.ssb&&null===this.deps.adPlayerController.breakNothingToPlayReason(e)),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=I(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?c(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=c(this.state.sessionManifest,e);s?this.switchChannel(e,s):this.applyManifestUpdate(e)}applyManifestUpdate(e){if(!this.state.sessionActive)return;m(this.deps,e),u(this.state,this.deps,e);const s=i(e);this.state.steeredSsaiActive?this.state.ssaiController?.setManifestAssetParameters(s.ssai?.assetParameters):this.deps.adPlayerController.setManifestAssetParameters(s.sgaiAssetParameters);const t=this.state.steeredSsaiActive?d(e):e;this.deps.breakScheduler.updateManifest(t),this.deps.pauseAdController.updateManifest(t)}handleEventBoundaryTick(e){return y(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 i=this.state.sessionConfig,n=this.state.effectiveSessionManifest??this.state.sessionManifest;if(!t){const s=this.deps.player.programDateTime?.getTime()??Date.now(),t=k(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:i.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!==r(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,i,t);if(this.isStaleStart(l))return;a||(s="sgai",this.state.sessionDeliveryMode="sgai",await this.applySameModeChannelSwitch(n,e,i,l,t))}else"sgai"===s&&"ssai"===h?await this.switchToSgai(e,i,l,t):await this.applySameModeChannelSwitch(n,e,i,l,t);if(this.isStaleStart(l))return}finally{this.state.channelSwitchInFlight=!1,t&&this.deps.breakScheduler.releaseBreakStarts()}m(this.deps,e),this.deps.startLivePushback(),A(this.deps,{...d,previousDeliveryMode:h,deliveryMode:this.state.sessionDeliveryMode??h})}async applySameModeChannelSwitch(e,s,t,a,r=!1){const n=this.state.steeredSsaiActive,o=n?d(s):s;if(r?this.deps.breakScheduler.applyEventBoundary(o):this.deps.breakScheduler.resetForChannelSwitch(o),this.deps.pauseAdController.updateManifest(o),this.deps.adPlayerController.setBreakResumePolicy(t.adInsertionType,s.timebase,this.deps.continueContentDuringBreak),!n&&p(e,s)){const e=this.deps.adPlayerController,n=i(s);if(!(!r||!n.identity)&&await e.gamPodBridge.adoptPreparedGamSession(n.identity,t.assetParameters,n.sgaiAssetParameters))e.updatedAssetParameters&&e.updateAssetParameters(e.updatedAssetParameters);else{const i=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})},a),this.isStaleStart(a))return;i&&e.updateAssetParameters(i)}}else if(n){const e=i(s);this.state.ssaiController?.setManifestAssetParameters(e.ssai?.assetParameters)}else{const e=i(s);this.deps.adPlayerController.setManifestAssetParameters(e.sgaiAssetParameters)}}async switchToSteeredSsai(e,s,t=!1){const a=l(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 o=this.createSsaiController(a.networkCode);this.state.ssaiController=o,this.state.steeredSsaiActive=!0,this.deps.diagnose("DA-SESSION-STARTING","Channel switch: ssai-live-stream",{step:"ssai-live-stream",manifestUrl:s.manifestUrl});try{await o.startSteered(a,s.assetParameters,n),r&&o.updateAssetParameters(r);const l=d(e);return t?this.deps.breakScheduler.applyEventBoundary(l):this.deps.breakScheduler.resetForChannelSwitch(l),this.deps.pauseAdController.updateManifest(l),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),o.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,t,r=this.state.sessionGeneration,n=!1){const o=i(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}),f(this.deps,e),!this.deps.adPlayerController.isGamEnabled())return void(a(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(a(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 a=this.state.sessionManifest??e,n=i(a).identity;n?(this.deps.adPlayerController.setManifestAssetParameters(i(a).sgaiAssetParameters),this.startResolvedGamSession(n,s,t,r,!0)):this.deps.breakScheduler.releaseBreakStarts()});this.state.adStartDelayElapsed=!0,await this.startResolvedGamSession(o.identity,s,t,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=o(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 g(this.state,this.deps,e)}}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { PlayerAdapter } from '../interfaces/PlayerAdapter';
|
|
2
|
-
import type
|
|
2
|
+
import { type Break } from '../types/BreakManifest';
|
|
3
3
|
import type { SessionState } from './SessionState';
|
|
4
|
+
import type { BreakScheduler } from '../services/BreakScheduler';
|
|
5
|
+
/** VOD snapback is unavailable on TV browsers, including React Native web TV shells. */
|
|
6
|
+
export declare function configureVodSnapback(scheduler: BreakScheduler, player: PlayerAdapter, canPlay: (brk: Break) => boolean): void;
|
|
4
7
|
export interface SnapbackDeps {
|
|
5
8
|
player: PlayerAdapter;
|
|
6
9
|
state: Pick<SessionState, 'snapbackSeekInFlight'>;
|
|
7
10
|
activeBreak(): Break | null | undefined;
|
|
8
11
|
/** A viewer seek (not our own corrective one) landed. */
|
|
9
12
|
onViewerSeek(): void;
|
|
13
|
+
onSeeked?(): void;
|
|
10
14
|
log(...args: unknown[]): void;
|
|
11
15
|
}
|
|
12
16
|
/** The content `seeked` handler: snaps a viewer seek back to the break start while the break locks it. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function createSnapbackHandler(e){return()=>{if(e.state.snapbackSeekInFlight)return void(e.state.snapbackSeekInFlight=!1);e.onViewerSeek();const t=e.activeBreak();if(!t?.controls?.snapback)return;const a="number"==typeof t.start?t.start:e.player.currentTime;Math.abs(e.player.currentTime-a)<=.5||(e.log("Snapback: seek detected during locked break, snapping back to break start"),e.state.snapbackSeekInFlight=!0,e.player.seek(a))}}
|
|
1
|
+
import{DeviceType as e}from"../types/BreakManifest";import{detectDeviceProfile as t}from"../services/detectDeviceType";export function configureVodSnapback(a,n,r){a.setSnapbackOnSeek(t().deviceType!==e.TV,r),a.setOnSnapback(e=>n.seek(e.target))}export function createSnapbackHandler(e){return()=>{if(e.state.snapbackSeekInFlight)return void(e.state.snapbackSeekInFlight=!1);e.onViewerSeek(),e.onSeeked?.();const t=e.activeBreak();if(!t?.controls?.snapback)return;const a="number"==typeof t.start?t.start:e.player.currentTime;Math.abs(e.player.currentTime-a)<=.5||(e.log("Snapback: seek detected during locked break, snapping back to break start"),e.state.snapbackSeekInFlight=!0,e.player.seek(a))}}
|
|
@@ -291,7 +291,7 @@ export interface OverlaySize {
|
|
|
291
291
|
width: number;
|
|
292
292
|
height: number;
|
|
293
293
|
}
|
|
294
|
-
/** Double-box variant — ad
|
|
294
|
+
/** Double-box variant — ad alongside continuing content, with a companion backdrop. @category Break Manifests & Ad Formats */
|
|
295
295
|
export interface DoubleBreakVariant extends BaseBreakVariant {
|
|
296
296
|
format: BreakFormat.DOUBLE;
|
|
297
297
|
/** Paired ad + companion assets. */
|