@dolby-optiview/ads-sdk-core 1.0.0 → 1.1.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/README.md +68 -0
- package/dist/OptiViewAds.d.ts +1 -0
- package/dist/OptiViewAds.js +1 -1
- package/dist/diagnostics/recordEventTimeline.d.ts +3 -0
- package/dist/diagnostics/recordEventTimeline.js +1 -0
- package/dist/generated/diagnosticCodes.d.ts +142 -2
- package/dist/generated/diagnosticCodes.js +1 -1
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/interfaces/PlayerAdapter.d.ts +33 -0
- package/dist/internal/index.d.ts +1 -1
- package/dist/internal/index.js +1 -1
- package/dist/services/AdPlayerController.d.ts +9 -1
- package/dist/services/AdPlayerController.js +1 -1
- package/dist/services/BreakScheduler.d.ts +5 -2
- package/dist/services/BreakScheduler.js +1 -1
- package/dist/services/CmcdAdReporter.d.ts +10 -0
- package/dist/services/CmcdAdReporter.js +1 -1
- package/dist/services/GamPodAdTracker.d.ts +12 -1
- package/dist/services/GamPodAdTracker.js +1 -1
- package/dist/services/LensReporter.d.ts +3 -1
- package/dist/services/LensReporter.js +1 -1
- package/dist/services/LiveStartPushbackController.d.ts +36 -0
- package/dist/services/LiveStartPushbackController.js +1 -0
- package/dist/services/MetadataCueTracker.d.ts +42 -1
- package/dist/services/MetadataCueTracker.js +1 -1
- package/dist/services/VastAdManager.js +1 -1
- package/dist/services/adPlayer/AdPlayerState.d.ts +11 -0
- package/dist/services/adPlayer/AdPlayerState.js +1 -1
- package/dist/services/adPlayer/AdPreloader.d.ts +9 -1
- package/dist/services/adPlayer/AdPreloader.js +1 -1
- package/dist/services/adPlayer/AdStage.d.ts +5 -0
- package/dist/services/adPlayer/AdStage.js +1 -1
- package/dist/services/adPlayer/AssetPlayback.d.ts +15 -1
- package/dist/services/adPlayer/AssetPlayback.js +1 -1
- package/dist/services/adPlayer/ContentRecovery.js +1 -1
- package/dist/services/adPlayer/ContentResumeCoordinator.d.ts +81 -2
- package/dist/services/adPlayer/ContentResumeCoordinator.js +1 -1
- package/dist/services/adPlayer/GamPodBridge.d.ts +7 -0
- package/dist/services/adPlayer/GamPodBridge.js +1 -1
- package/dist/services/adPlayer/PreparedPresentation.d.ts +32 -0
- package/dist/services/adPlayer/PreparedPresentation.js +1 -0
- package/dist/services/adPlayer/SharedElementBreakPlayer.d.ts +8 -0
- package/dist/services/adPlayer/SharedElementBreakPlayer.js +1 -1
- package/dist/services/adPlayer/classifyMediaError.d.ts +9 -0
- package/dist/services/adPlayer/classifyMediaError.js +1 -0
- package/dist/services/adPlayer/liveEdge.d.ts +29 -0
- package/dist/services/adPlayer/liveEdge.js +1 -1
- package/dist/services/adPlayer/streamClockMapping.d.ts +65 -0
- package/dist/services/adPlayer/streamClockMapping.js +1 -0
- package/dist/services/adPlayer/streamClockResume.d.ts +94 -0
- package/dist/services/adPlayer/streamClockResume.js +1 -0
- package/dist/services/anvatoCueRuntime.d.ts +19 -0
- package/dist/services/anvatoCueRuntime.js +1 -1
- package/dist/services/breakTiming.d.ts +7 -0
- package/dist/services/breakTiming.js +1 -1
- package/dist/services/classifyImaAdError.d.ts +6 -0
- package/dist/services/classifyImaAdError.js +1 -0
- package/dist/services/html5ImaErrorNames.d.ts +2 -0
- package/dist/services/html5ImaErrorNames.js +1 -0
- package/dist/services/lensBackend.d.ts +1 -1
- package/dist/services/resolveLiveStartPushback.d.ts +1 -0
- package/dist/services/resolveLiveStartPushback.js +1 -0
- package/dist/session/AdBreakStatusTracker.js +1 -1
- package/dist/session/BreakLifecycle.js +1 -1
- package/dist/session/SessionStarter.d.ts +1 -0
- package/dist/session/SessionStarter.js +1 -1
- package/dist/types/AdError.d.ts +12 -0
- package/dist/types/AdError.js +1 -0
- package/dist/types/OptiViewAdsConfig.d.ts +17 -0
- package/dist/types/VendorAdError.d.ts +6 -5
- package/dist/types/VendorAdError.js +1 -1
- package/dist/vendor/lens-sdk/THIRD-PARTY-NOTICES.txt +1 -1
- package/dist/vendor/lens-sdk/index.d.ts +1 -1
- package/dist/vendor/lens-sdk/index.js +1 -1
- package/dist/vendor/lens-sdk/lens-core-uGZfZpQy.js +1 -0
- package/dist/vendor/lens-sdk/lens.d.ts +2 -0
- package/dist/vendor/lens-sdk/teardown.d.ts +13 -0
- package/dist/vendor/lens-sdk/transport.d.ts +11 -2
- package/dist/vendor/lens-sdk/types.d.ts +56 -5
- package/dist/vendor/lens-sdk/validation.d.ts +4 -1
- package/package.json +1 -1
- package/dist/vendor/lens-sdk/lens-core-Q5lbQGMC.js +0 -1
|
@@ -11,6 +11,20 @@ export declare const ID3_IN_EMSG_SCHEME_ID_URIS: readonly string[];
|
|
|
11
11
|
export declare function isId3InEmsgScheme(schemeIdUri: string | undefined): boolean;
|
|
12
12
|
/** Tolerance (seconds) matching a manifest `start` PTS to a cue `pts`; encoder/backend rounding differs sub-millisecond. */
|
|
13
13
|
export declare const PTS_MATCH_TOLERANCE_SECONDS = 0.005;
|
|
14
|
+
/**
|
|
15
|
+
* Tolerance (seconds) between a break's duration and its stream-signalled length (`cue_end` pts −
|
|
16
|
+
* break start pts). NFL Channel signalled 150.166–150.293s for 150s breaks (splice segments).
|
|
17
|
+
*/
|
|
18
|
+
export declare const BREAK_END_LENGTH_TOLERANCE_SECONDS = 2;
|
|
19
|
+
/** The stream's own end of a break: the `type=cue_end` cue matched to it on the stream clock. */
|
|
20
|
+
export interface BreakEndMatch {
|
|
21
|
+
/** Media time (seconds) the `cue_end` was reported at, in the player's mapping at delivery. */
|
|
22
|
+
mediaTime: number;
|
|
23
|
+
/** Break length on the stream clock, `cue_end` pts − break start pts, in seconds. */
|
|
24
|
+
lengthSec: number;
|
|
25
|
+
/** `lengthSec` is within {@link BREAK_END_LENGTH_TOLERANCE_SECONDS} of the break duration. */
|
|
26
|
+
accepted: boolean;
|
|
27
|
+
}
|
|
14
28
|
/** Anvato marker: GEOB with description "Anvatos" (MIME `application/json` when surfaced) or emsg with the Anvato scheme. */
|
|
15
29
|
export declare function isAnvatoCue(cue: TimedMetadataCue): boolean;
|
|
16
30
|
/**
|
|
@@ -23,15 +37,42 @@ export declare function anvatoPayloadFromCue(cue: TimedMetadataCue): string | nu
|
|
|
23
37
|
* split on `&`, then on the FIRST `=`, `+` as space, percent-decode, raw text on failure, first duplicate wins.
|
|
24
38
|
*/
|
|
25
39
|
export declare function parseAnvatoPayload(payload: string): Record<string, string>;
|
|
40
|
+
/** How far before its `startTime` a `ptsOffset`-placed cue may land (frame rounding between the two clocks). */
|
|
41
|
+
export declare const PTS_OFFSET_MIN_LEAD_SECONDS = 0.1;
|
|
42
|
+
/** How far after its `startTime` (the carrying segment's start) a `ptsOffset`-placed cue may land. */
|
|
43
|
+
export declare const PTS_OFFSET_MAX_LAG_SECONDS = 15;
|
|
44
|
+
/** Structured-diagnostic sink for the tracker. Optional; absent means silent. */
|
|
45
|
+
export type CueTrackerDiagnose = (code: 'DA-ANVATO-CUE-SEGMENT-ANCHORED' | 'DA-ANVATO-CUE-OFFSET-REJECTED', message: string, context: Record<string, unknown>) => void;
|
|
26
46
|
/**
|
|
27
47
|
* Maps a break's manifest `pts` to the media time its Anvato cue was observed at. With
|
|
28
48
|
* `ptsSource: 'anvatoCue'` a PTS break is only schedulable once its cue was seen. Portable brain logic.
|
|
49
|
+
*
|
|
50
|
+
* A cue's `startTime` is only the cue's own PTS for ID3. A DASH emsg v0 with a zero delta reports
|
|
51
|
+
* the start of its carrying segment, so the adapter supplies `ptsOffset` and the cue is placed at
|
|
52
|
+
* `pts + ptsOffset`, provided that lands inside the carrying segment's plausible window.
|
|
29
53
|
*/
|
|
30
54
|
export declare class MetadataCueTracker {
|
|
55
|
+
private readonly diagnose?;
|
|
31
56
|
/** Observed cue `pts` parameter (seconds) → cue media time (seconds). */
|
|
32
57
|
private mediaTimeByPts;
|
|
33
|
-
/**
|
|
58
|
+
/** Observed `type=cue_end` `pts` → media time; the latest delivery wins. */
|
|
59
|
+
private endMediaTimeByPts;
|
|
60
|
+
private segmentAnchoredDiagnosed;
|
|
61
|
+
private offsetRejectedPts;
|
|
62
|
+
constructor(diagnose?: CueTrackerDiagnose | undefined);
|
|
63
|
+
/**
|
|
64
|
+
* Ignores non-Anvato cues, payloads other than `type=cue` / `type=cue_end`, and non-finite `pts`.
|
|
65
|
+
* True when a cue was recorded or refreshed.
|
|
66
|
+
*/
|
|
34
67
|
handleCue(cue: TimedMetadataCue): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* The `cue_end` closest to ending the break that starts at `startPts` and lasts `durationSec`,
|
|
70
|
+
* judged on the stream clock so a player remap cannot move it. The pts difference is read as
|
|
71
|
+
* seconds and as milliseconds; only a length between half and twice the duration is a candidate.
|
|
72
|
+
* Ties resolve to the lower `cue_end` pts. Null when there is no candidate.
|
|
73
|
+
*/
|
|
74
|
+
breakEnd(startPts: number, durationSec: number): BreakEndMatch | null;
|
|
75
|
+
private resolveMediaTime;
|
|
35
76
|
/**
|
|
36
77
|
* Media time of the cue matching `pts` within {@link PTS_MATCH_TOLERANCE_SECONDS}, or null.
|
|
37
78
|
* Closest wins; ties resolve to the lower cue PTS so the result is observation-order independent.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{decodeUtf8 as e,parseId3GeobFrames as t}from"../id3/parseId3Geob";export const ANVATO_ID3_DESCRIPTION="Anvatos";export const ANVATO_ID3_MIME_TYPE="application/json";export const ANVATO_EMSG_SCHEME_ID_URI="urn:anvato:es1:052016";export const ID3_IN_EMSG_SCHEME_ID_URIS=["https://aomedia.org/emsg/ID3","https://developer.apple.com/streaming/emsg-id3"];export function isId3InEmsgScheme(e){return void 0!==e&&ID3_IN_EMSG_SCHEME_ID_URIS.includes(e)}export const PTS_MATCH_TOLERANCE_SECONDS=.005;export function isAnvatoCue(e){return"id3"===e.type?"Anvatos"===e.description&&(void 0===e.mimeType||"application/json"===e.mimeType):"emsg"===e.type&&"urn:anvato:es1:052016"===e.schemeIdUri}export function anvatoPayloadFromCue(
|
|
1
|
+
import{decodeUtf8 as e,parseId3GeobFrames as t}from"../id3/parseId3Geob";export const ANVATO_ID3_DESCRIPTION="Anvatos";export const ANVATO_ID3_MIME_TYPE="application/json";export const ANVATO_EMSG_SCHEME_ID_URI="urn:anvato:es1:052016";export const ID3_IN_EMSG_SCHEME_ID_URIS=["https://aomedia.org/emsg/ID3","https://developer.apple.com/streaming/emsg-id3"];export function isId3InEmsgScheme(e){return void 0!==e&&ID3_IN_EMSG_SCHEME_ID_URIS.includes(e)}export const PTS_MATCH_TOLERANCE_SECONDS=.005;export const BREAK_END_LENGTH_TOLERANCE_SECONDS=2;export function isAnvatoCue(e){return"id3"===e.type?"Anvatos"===e.description&&(void 0===e.mimeType||"application/json"===e.mimeType):"emsg"===e.type&&"urn:anvato:es1:052016"===e.schemeIdUri}export function anvatoPayloadFromCue(s){if(isAnvatoCue(s))return s.messageData??null;if("emsg"===s.type&&isId3InEmsgScheme(s.schemeIdUri)&&s.raw)for(const i of t(s.raw))if("Anvatos"===i.description&&"application/json"===i.mimeType)return e(i.data);return null}export function parseAnvatoPayload(e){const t={};for(const i of e.split("&")){if(""===i)continue;const e=i.indexOf("="),n=-1===e?i:i.slice(0,e),o=-1===e?"":i.slice(e+1),r=s(n);r in t||(t[r]=s(o))}return t}function s(e){const t=e.replace(/\+/g," ");try{return decodeURIComponent(t)}catch{return t}}export const PTS_OFFSET_MIN_LEAD_SECONDS=.1;export const PTS_OFFSET_MAX_LAG_SECONDS=15;export class MetadataCueTracker{constructor(e){this.diagnose=e,this.mediaTimeByPts=new Map,this.endMediaTimeByPts=new Map,this.segmentAnchoredDiagnosed=!1,this.offsetRejectedPts=new Set}handleCue(e){if(!Number.isFinite(e.startTime))return!1;const t=anvatoPayloadFromCue(e);if(!t)return!1;const s=parseAnvatoPayload(t),i=s.type;if("cue"!==i&&"cue_end"!==i)return!1;const n=Number(s.pts);if(!Number.isFinite(n))return!1;return("cue"===i?this.mediaTimeByPts:this.endMediaTimeByPts).set(n,this.resolveMediaTime(e,n)),!0}breakEnd(e,t){if(!(Number.isFinite(e)&&t>0))return null;let s=null;for(const[i,n]of this.endMediaTimeByPts){const o=i-e;for(const e of[o,o/1e3]){if(e<t/2||e>2*t)continue;const o=Math.abs(e-t);if(null===s||o<s.distance||o===s.distance&&i<s.endPts){s={endPts:i,distance:o,match:{mediaTime:n,lengthSec:e,accepted:o<=2}}}}}return null===s?null:s.match}resolveMediaTime(e,t){const s=e.ptsOffset;if("number"!=typeof s||!Number.isFinite(s))return"urn:anvato:es1:052016"!==e.schemeIdUri||this.segmentAnchoredDiagnosed||(this.segmentAnchoredDiagnosed=!0,this.diagnose?.("DA-ANVATO-CUE-SEGMENT-ANCHORED",`Anvato emsg cue pts=${t} has no ptsOffset; scheduling it at its segment start ${e.startTime}s, which can be up to one segment early.`,{pts:t,mediaTime:e.startTime})),e.startTime;const i=t=>t>=e.startTime-.1&&t<=e.startTime+15,n=t+s;if(i(n))return n;const o=t/1e3+s;return i(o)?o:(this.offsetRejectedPts.has(t)||(this.offsetRejectedPts.add(t),this.diagnose?.("DA-ANVATO-CUE-OFFSET-REJECTED",`Anvato cue pts=${t} maps to ${n}s with ptsOffset ${s}, outside its segment window from ${e.startTime}s; using the segment start.`,{pts:t,startTime:e.startTime,ptsOffset:s,mappedTime:n})),e.startTime)}mediaTimeForPts(e){let t=null,s=1/0;for(const i of this.mediaTimeByPts.keys()){const n=Math.abs(i-e);n>.005||(n<s||n===s&&i<t)&&(t=i,s=n)}return null===t?null:this.mediaTimeByPts.get(t)}get size(){return this.mediaTimeByPts.size}clear(){this.mediaTimeByPts.clear(),this.endMediaTimeByPts.clear(),this.segmentAnchoredDiagnosed=!1,this.offsetRejectedPts.clear()}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{debugLog as e}from"../diagnostics/debugLog";import{
|
|
1
|
+
import{debugLog as e}from"../diagnostics/debugLog";import{AdError as t}from"../types/AdError";import{VendorAdError as s}from"../types/VendorAdError";import{classifyImaAdError as a}from"./classifyImaAdError";import{html5ImaErrorName as r}from"./html5ImaErrorNames";export function isImaCsaiSdkAvailable(){return"undefined"!=typeof google&&void 0!==google.ima&&void 0!==google.ima.AdsLoader&&void 0!==google.ima.AdDisplayContainer}export class VastAdManager{constructor(e,t,s=!1){this.adContainer=e,this.adVideoElement=t,this.adDisplayContainer=null,this.adsLoader=null,this.adsManager=null,this.callbacks={},this.preloadResolve=null,this.preloadReject=null,this.startResolve=null,this.startReject=null,this.pendingMuted=!1,this.settled=!1,this.preloaded=!1,this.started=!1,this.currentAdDurationSec=null,this.debug=s}setCallbacks(e){this.callbacks=e}resize(){this.adsManager&&(this.adsManager.resize(this.slotWidth(),this.slotHeight(),google.ima.ViewMode.NORMAL),this.log("VAST AdsManager resized to",this.slotWidth(),"x",this.slotHeight()))}preloadVast(e,s){return this.reset(),isImaCsaiSdkAvailable()?new Promise((t,a)=>{this.preloadResolve=t,this.preloadReject=a;try{this.setupContainerAndLoader();const t=new google.ima.AdsRequest;t.adTagUrl=e,t.linearAdSlotWidth=this.slotWidth(),t.linearAdSlotHeight=this.slotHeight(),t.setAdWillAutoPlay(!0),this.pendingMuted=s?.muted??!1,t.setAdWillPlayMuted(this.pendingMuted),this.log("Preloading VAST ads:",e),this.adsLoader.requestAds(t)}catch(e){this.rejectPreload(e instanceof Error?e:new Error(String(e)))}}):Promise.reject(new t('Google IMA client-side SDK not found. Include <script src="https://imasdk.googleapis.com/js/sdkloader/ima3.js"><\/script> in your page.',"DA-VAST-IMA-SDK-MISSING"))}startPreloaded(){return new Promise((e,t)=>{if(this.adsManager&&this.preloaded&&!this.started){this.started=!0,this.settled=!1,this.startResolve=e,this.startReject=t;try{this.resize(),this.adsManager.start(),this.log("VAST AdsManager started")}catch(e){this.failStart(e instanceof Error?e:new Error(String(e)))}}else t(new Error("No preloaded VAST AdsManager; call preloadVast() first"))})}hasPreloadedManager(){return null!==this.adsManager&&this.preloaded&&!this.started&&!this.settled}setMuted(e){this.pendingMuted=e,this.adsManager?.setVolume(e?0:1)}setVolume(e){this.pendingMuted=e<=0,this.adsManager?.setVolume(e)}playVast(e,t){return this.preloadVast(e,t).then(()=>this.startPreloaded())}setupContainerAndLoader(){this.adDisplayContainer=new google.ima.AdDisplayContainer(this.adContainer,this.adVideoElement),this.adDisplayContainer.initialize(),this.adsLoader=new google.ima.AdsLoader(this.adDisplayContainer),this.adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED,e=>this.onAdsManagerLoaded(e)),this.adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,e=>this.onAdError(e))}onAdsManagerLoaded(e){try{const t=new google.ima.AdsRenderingSettings;t.restoreCustomPlaybackStateOnAdBreakComplete=!0,t.enablePreloading=!0;const s=e.getAdsManager(this.adVideoElement,t);this.adsManager=s,this.wireManagerEvents(s),this.pendingMuted&&s.setVolume(0),s.init(this.slotWidth(),this.slotHeight(),google.ima.ViewMode.NORMAL),this.preloaded=!0,this.resolvePreload(),this.log("VAST AdsManager initialized and ready")}catch(e){this.rejectPreload(e instanceof Error?e:new Error(String(e)))}}wireManagerEvents(e){const t=google.ima.AdEvent.Type;e.addEventListener(t.STARTED,e=>{this.resize();const t=e.getAd?.(),s=t?.getDuration?.();this.currentAdDurationSec="number"==typeof s&&Number.isFinite(s)&&s>0?s:null;const a=t?.getAdPodInfo?.()?.getAdPosition?.();this.callbacks.onAdStarted?.("number"==typeof a&&Number.isFinite(a)&&a>=1?Math.floor(a):void 0)}),e.addEventListener(t.AD_PROGRESS,()=>this.emitAdProgress()),e.addEventListener(t.FIRST_QUARTILE,()=>this.callbacks.onAdFirstQuartile?.()),e.addEventListener(t.MIDPOINT,()=>this.callbacks.onAdMidpoint?.()),e.addEventListener(t.THIRD_QUARTILE,()=>this.callbacks.onAdThirdQuartile?.()),e.addEventListener(t.CONTENT_PAUSE_REQUESTED,()=>this.callbacks.onContentPauseRequested?.()),e.addEventListener(t.CONTENT_RESUME_REQUESTED,()=>this.callbacks.onContentResumeRequested?.()),e.addEventListener(t.ALL_ADS_COMPLETED,()=>this.succeedStart()),e.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR,e=>this.onAdError(e))}emitAdProgress(){if(!this.callbacks.onAdProgress||!this.adsManager)return;const e=this.currentAdDurationSec;if(null==e)return;const t=this.adsManager.getRemainingTime();if(!Number.isFinite(t)||t<0)return;const s=Math.max(0,Math.min(e,e-t));this.callbacks.onAdProgress(s,e)}onAdError(e){const i=e?.getError?.(),o=i?.getMessage?.()??"Unknown IMA CSAI error",d=i?.getErrorCode?.();this.log("VAST IMA error:",o);const n="number"==typeof d?new s(o,String(d),a(r(d))):new t(o,"DA-VAST-IMA-ERROR");this.startReject?this.failStart(n):this.preloadReject&&this.rejectPreload(n)}succeedStart(){this.settled||(this.settled=!0,this.log("VAST playout complete"),this.startResolve?.(),this.clearStartPromises(),this.teardownManager())}failStart(e){this.settled||(this.settled=!0,this.startReject?.(e),this.clearStartPromises(),this.teardownManager())}resolvePreload(){this.preloadResolve?.(),this.clearPreloadPromises()}rejectPreload(e){this.preloadReject?.(e),this.clearPreloadPromises(),this.teardownManager()}clearPreloadPromises(){this.preloadResolve=null,this.preloadReject=null}clearStartPromises(){this.startResolve=null,this.startReject=null}slotWidth(){return this.adVideoElement.clientWidth||640}slotHeight(){return this.adVideoElement.clientHeight||360}teardownManager(){this.adsManager&&(this.adsManager.destroy(),this.adsManager=null),this.adsLoader&&(this.adsLoader.destroy(),this.adsLoader=null),this.adDisplayContainer&&(this.adDisplayContainer.destroy(),this.adDisplayContainer=null)}reset(){this.teardownManager(),this.settled=!1,this.preloaded=!1,this.started=!1,this.pendingMuted=!1,this.currentAdDurationSec=null,this.clearPreloadPromises(),this.clearStartPromises()}destroy(){this.reset(),this.callbacks={}}log(...t){this.debug&&e("[VastAdManager]",...t)}}
|
|
@@ -58,6 +58,17 @@ export interface AdPlayerState {
|
|
|
58
58
|
savedContentEdgeDistance: number | null;
|
|
59
59
|
earlyReturnResumeOffsetSec: number | null;
|
|
60
60
|
contentPositionAtBreakStart: number;
|
|
61
|
+
/**
|
|
62
|
+
* Whether this break's anchor is the in-stream cue rather than the pause position — i.e.
|
|
63
|
+
* whether the content under the ad is known slate of exactly the advertised duration.
|
|
64
|
+
*/
|
|
65
|
+
resumeAnchorIsCue: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* The seekable end as the engine reported it at break start, captured with the anchor so the
|
|
68
|
+
* two are comparable. Their difference is the cushion the resume has to work with: a playhead
|
|
69
|
+
* riding its engine's seekable end has none, and any under-published second then lands short.
|
|
70
|
+
*/
|
|
71
|
+
resumeAnchorSeekableEnd: number | null;
|
|
61
72
|
resumePrepositionedTo: number | null;
|
|
62
73
|
resumePrepositionSettle: ReturnType<typeof setTimeout> | null;
|
|
63
74
|
resumeCatchupActive: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function invalidateMediaPreload(e){return e.preparedMedia=null,++e.preloadGeneration}export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,startPlaybackCutoff:null,effectiveBreakDurationSec:null,adInsertionType:void 0,breakTimebase:null,adBreakCutSafetyMarginSec:0,detectedDeviceType:void 0,sessionAssetParameters:void 0,updatedAssetParameters:void 0,manifestAssetParameters:void 0,sessionAssetParameterMacros:{},pushedAssetParameters:null,gamStreamManager:null,gamPodMode:"none",gamPodTracker:null,gamPodAsset:null,earlyGamPodAdStart:null,withheldPodAdBegin:null,gamConfig:void 0,gamInitPromise:null,currentGamIdentity:void 0,playerContainer:null,stage:null,contentVideoElement:null,adContainer:null,adMediaContainer:null,adPlayer:null,companionElement:null,overlayImageEl:null,isContentFullscreen:!1,trackedContentVideo:null,fullscreenEnterHandler:null,fullscreenExitHandler:null,contentPictureInPicture:!1,pipTransferredToAd:!1,pipAdoptedForBreak:!1,sharedAdClaimActive:!1,savedContentSrc:null,savedContentTime:0,savedContentLive:!1,savedContentEdgeDistance:null,earlyReturnResumeOffsetSec:null,contentPositionAtBreakStart:0,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,resumeCatchupRan:!1,lastResumeAlignRemainingSec:Number.POSITIVE_INFINITY,resumeAlignBlockedDiagnosed:!1,lastResumeSafeEnd:Number.NEGATIVE_INFINITY,resumeSawSeekDone:!1,resumeBestSawMarginSec:Number.NEGATIVE_INFINITY,resumeAlignTicks:0,resumeSawSteps:[],continueContentDuringBreak:!1,continueQualityOverrideApplied:!1,continueContentDuringBreakActive:!1,keepContentPlayingHandler:null,continuePlayoutStartWallMs:0,continuePlayoutTicker:null,continueRateWritten:null,continueRateWrittenAtMs:0,continueRateHonoured:!0,continueMaxAbsDriftSec:0,continueCorrections:[],continueDriftUnrecoverable:!1,continueRefusalReported:!1,contentMutedBeforeCatchup:null,resumeAlignCorrectionsMs:[],resumeLandedProbe:null,adSecondsPlayedThisBreak:0,currentAssetElapsedSec:0,preparedMedia:null,preloadGeneration:0,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
|
|
1
|
+
export function invalidateMediaPreload(e){return e.preparedMedia=null,++e.preloadGeneration}export function createAdPlayerState(){return{currentBreak:null,currentAsset:null,isPlaying:!1,contentPaused:!1,callbacks:null,durationTimer:null,startPlaybackCutoff:null,effectiveBreakDurationSec:null,adInsertionType:void 0,breakTimebase:null,adBreakCutSafetyMarginSec:0,detectedDeviceType:void 0,sessionAssetParameters:void 0,updatedAssetParameters:void 0,manifestAssetParameters:void 0,sessionAssetParameterMacros:{},pushedAssetParameters:null,gamStreamManager:null,gamPodMode:"none",gamPodTracker:null,gamPodAsset:null,earlyGamPodAdStart:null,withheldPodAdBegin:null,gamConfig:void 0,gamInitPromise:null,currentGamIdentity:void 0,playerContainer:null,stage:null,contentVideoElement:null,adContainer:null,adMediaContainer:null,adPlayer:null,companionElement:null,overlayImageEl:null,isContentFullscreen:!1,trackedContentVideo:null,fullscreenEnterHandler:null,fullscreenExitHandler:null,contentPictureInPicture:!1,pipTransferredToAd:!1,pipAdoptedForBreak:!1,sharedAdClaimActive:!1,savedContentSrc:null,savedContentTime:0,savedContentLive:!1,savedContentEdgeDistance:null,earlyReturnResumeOffsetSec:null,contentPositionAtBreakStart:0,resumeAnchorIsCue:!1,resumeAnchorSeekableEnd:null,resumePrepositionedTo:null,resumePrepositionSettle:null,resumeCatchupActive:!1,resumeCatchupRan:!1,lastResumeAlignRemainingSec:Number.POSITIVE_INFINITY,resumeAlignBlockedDiagnosed:!1,lastResumeSafeEnd:Number.NEGATIVE_INFINITY,resumeSawSeekDone:!1,resumeBestSawMarginSec:Number.NEGATIVE_INFINITY,resumeAlignTicks:0,resumeSawSteps:[],continueContentDuringBreak:!1,continueQualityOverrideApplied:!1,continueContentDuringBreakActive:!1,keepContentPlayingHandler:null,continuePlayoutStartWallMs:0,continuePlayoutTicker:null,continueRateWritten:null,continueRateWrittenAtMs:0,continueRateHonoured:!0,continueMaxAbsDriftSec:0,continueCorrections:[],continueDriftUnrecoverable:!1,continueRefusalReported:!1,contentMutedBeforeCatchup:null,resumeAlignCorrectionsMs:[],resumeLandedProbe:null,adSecondsPlayedThisBreak:0,currentAssetElapsedSec:0,preparedMedia:null,preloadGeneration:0,firstFrameGateActive:!1,firstFrameGateShown:!1,firstFrameResolver:null,firstFrameRejector:null,firstFrameTimeout:null,adStartDeadlineMs:null,adStartBudgetMs:0,vastAdManager:null}}
|
|
@@ -3,6 +3,10 @@ import { Asset, Break, BreakVariant, StaticAsset, VastAsset } from '../../types/
|
|
|
3
3
|
import type { ResolvedPreloadMode } from '../detectPreloadMode';
|
|
4
4
|
import { type AdPlayerState } from './AdPlayerState';
|
|
5
5
|
import type { Clock } from '../BreakScheduler';
|
|
6
|
+
/** A stalled ad load gets one second load() this far into the ad-start budget. */
|
|
7
|
+
export declare const AD_LOAD_RETRY_AFTER_MS = 2000;
|
|
8
|
+
/** True when the ad element has produced nothing since load() — no metadata and no buffered media. */
|
|
9
|
+
export declare function adLoadShowsNoProgress(video: HTMLVideoElement | null | undefined): boolean;
|
|
6
10
|
export interface AdPreloaderDeps {
|
|
7
11
|
state: AdPlayerState;
|
|
8
12
|
clock: Clock;
|
|
@@ -46,7 +50,11 @@ export declare class AdPreloader {
|
|
|
46
50
|
revealFirstFrame(): void;
|
|
47
51
|
armAdStartBudget(asset: Asset): void;
|
|
48
52
|
remainingAdStartBudgetMs(): number;
|
|
49
|
-
loadWithinBudget(
|
|
53
|
+
loadWithinBudget(startLoad: () => Promise<void>, context: {
|
|
54
|
+
breakId: string;
|
|
55
|
+
assetId: string;
|
|
56
|
+
uri: string;
|
|
57
|
+
}): Promise<boolean>;
|
|
50
58
|
waitForFirstFrame(): Promise<void>;
|
|
51
59
|
clearFirstFrame(reason?: string): void;
|
|
52
60
|
clearFirstFrameResolver(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AssetType as e,BreakFormat as t,MediaType as s,Vendor as
|
|
1
|
+
import{AssetType as e,BreakFormat as t,MediaType as s,Vendor as r,isVastAsset as a,isVendorAsset as i}from"../../types/BreakManifest";import{AUX_FETCH_TIMEOUT_MS as d,fetchWithTimeout as o}from"../fetchWithTimeout";import{resolveVastAdmission as n}from"../resolveVastAdmission";import{isImaCsaiSdkAvailable as l,VastAdManager as p}from"../VastAdManager";import{invalidateMediaPreload as h}from"./AdPlayerState";import{AdError as m}from"../../types/AdError";export const AD_LOAD_RETRY_AFTER_MS=2e3;export function adLoadShowsNoProgress(e){return!e||0===e.readyState&&0===e.buffered.length}export class AdPreloader{constructor(e){this.deps=e,this.warmedBreakIds=new Set}classifyCreative(e){const t=(e.split(/[?#]/)[0]??"").toLowerCase();return t.endsWith(".m3u8")||t.endsWith(".mpd")?"stream":/\.(mp4|m4v|m4a|webm|mov|ogg|ogv|mp3)$/.test(t)?"progressive":"unknown"}diagnosePreloadPath(e,t,s){const r=this.classifyCreative(t);this.deps.diagnose("DA-PRELOAD-PATH",`Ad preload path: ${s} (${r} creative)`,{breakId:e,adPreload:this.deps.preloadMode,path:s,creative:r})}async preload(r){if(this.deps.isPlaying())return void this.deps.log("Preload skipped: ad already playing");const i=h(this.deps.state),d=this.deps.getAdPlayer();if("none"===this.deps.preloadMode){const t=this.deps.getAssets(r)[0],s=t&&t.type===e.STATIC?this.deps.resolveUri(t.uri)??"":"";return this.diagnosePreloadPath(r.id,s,"none"),void this.deps.log('Preload skipped: adPreload "none"')}if("shared-element"===this.deps.effectiveInsertionMode()){const s=this.deps.getVariant(r);if(s&&s.format!==t.LSHAPE_CONTENT){const t=this.deps.getAssets(r)[0];if(t&&t.type===e.STATIC){const e=this.deps.resolveUri(t.uri);e&&await this.warmCache([e])}}return}const o=this.deps.getAssets(r);if(0===o.length)return void this.deps.log("No assets to preload");this.preloadCompanion(r);const n=this.deps.getVariant(r);if(n?.format===t.LSHAPE_CONTENT)return;if(o[0].mediaType===s.IMAGE){const e=await this.deps.resolveAssetUri(o[0],r.duration);if(e){(new Image).src=e,this.deps.log("Preloading overlay image:",e)}return}if(a(o[0]))return void await this.preloadVastAsset(o[0],r);const l=await this.deps.resolveAssetUri(o[0],r.duration);if(l){if(d&&this.ownsPreload(i,d))if("single-decoder"!==this.deps.preloadMode){this.deps.log("Preloading ad (parallel):",l),this.diagnosePreloadPath(r.id,l,"parallel");try{if(await d.load(l),!this.ownsPreload(i,d))return;this.deps.state.preparedMedia={breakId:r.id,assetId:o[0].id,uri:l,player:d},this.deps.log("Preload complete for break:",r.id)}catch(e){this.deps.log("Preload failed (will load on demand):",e)}}else{if(this.deps.log("Preloading ad (single-decoder, detached):",l),this.diagnosePreloadPath(r.id,l,"single-decoder"),await this.warmCache([l]),!this.ownsPreload(i,d))return;try{await(d.preload?.(l))}catch(e){this.deps.log("Detached prefetch failed (will load on demand):",e)}}}else this.deps.log("No valid URI for preload")}ownsPreload(e,t){return e===this.deps.state.preloadGeneration&&!this.deps.isPlaying()&&t===this.deps.getAdPlayer()}async preloadVastAsset(e,s){const r=this.deps.getVariant(s),a=r?.format??t.SINGLE;if(!n({format:a,ssai:!1}).admitted)return void this.deps.log("VAST preload skipped: format not supported",a);if(!l())return void this.deps.log("VAST preload skipped: IMA client-side SDK unavailable");const i=this.deps.getAdPlayer(),d=this.deps.getAdContainer();if(!i||!d)return void this.deps.log("VAST preload skipped: ad player not initialized");const o=i.videoElement;if(!o)return void this.deps.log("VAST preload skipped: no ad video element");const h=this.deps.buildVastTagUrl(e,s);if(h){this.deps.state.vastAdManager||(this.deps.state.vastAdManager=new p(d,o,this.deps.debug)),this.deps.log("Preloading VAST asset via IMA CSAI:",h);try{await this.deps.state.vastAdManager.preloadVast(h,{muted:i.muted}),this.deps.log("VAST preload complete for break:",s.id)}catch(t){const r=t instanceof Error?t:new Error(String(t));this.deps.log("VAST preload failed (will load on demand):",r.message),this.deps.diagnose("DA-PRELOAD-FAILED",r.message,{breakId:s.id,assetId:e.id})}}else this.deps.log("No valid VAST tag URL for preload")}preloadChainedSuccessor(e){const t=this.deps.resolveChainedSuccessor(e);t&&this.preloadNext(t)}clearWarmedBreaks(){this.warmedBreakIds.clear()}async preloadNext(s){if(this.warmedBreakIds.has(s.id))return;this.preloadCompanion(s);const r=this.deps.getVariant(s);if(!r||r.format===t.LSHAPE_CONTENT)return;const a=this.deps.getAssets(s)[0];if(!a||a.type!==e.STATIC)return void this.deps.log("Chained successor first asset not static; skipping media warm:",s.id);const i=this.deps.resolveUri(a.uri);if(i){this.warmedBreakIds.add(s.id),this.deps.log("Warming chained successor media:",s.id,i),await this.warmCache([i]);try{await(this.deps.getAdPlayer()?.preload?.(i))}catch(e){this.deps.log("Chained prefetch failed (will load on demand):",e)}}}revealFirstFrame(){if(!this.deps.state.firstFrameGateActive||this.deps.state.firstFrameGateShown)return;const e=this.deps.getCurrentBreak();if(!e)return;this.deps.state.firstFrameGateShown=!0,this.deps.show();const t=this.deps.getVariant(e);t&&this.deps.reapplyAdContainerLayout(t),this.deps.onBreakVisible(e)}armAdStartBudget(e){const t=1e3*((this.deps.state.effectiveBreakDurationSec??this.deps.state.currentBreak?.duration??0)+this.deps.state.adBreakCutSafetyMarginSec),s=i(e)&&e.vendor===r.GAM?this.deps.gamPodAdStartBudgetMs:this.deps.adStartBudgetMs,a=Math.max(Math.min(t-100,s),this.deps.minAdStartBudgetMs);this.deps.state.adStartBudgetMs=a,this.deps.state.adStartDeadlineMs=this.deps.clock.now()+a}remainingAdStartBudgetMs(){return Math.max(0,(this.deps.state.adStartDeadlineMs??this.deps.clock.now())-this.deps.clock.now())}loadWithinBudget(e,t){const s=this.deps.state.preloadGeneration,r=this.remainingAdStartBudgetMs(),a=this.deps.clock.now();return new Promise((i,d)=>{let o=!1,n=!1;const l=e=>{o||(o=!0,clearTimeout(p),null!==h&&clearTimeout(h),e.ok?i(!0):d(e.error instanceof Error?e.error:new Error(String(e.error))))},p=setTimeout(()=>{s===this.deps.state.preloadGeneration&&this.deps.onAdError(new m("Ad load timeout","DA-AD-START-TIMEOUT")),o||(o=!0,null!==h&&clearTimeout(h),i(!1))},r);let h=null;r>2e3&&(h=setTimeout(()=>{if(o||s!==this.deps.state.preloadGeneration)return;const r=this.deps.clock.now()-a,i=this.deps.getAdPlayer()?.videoElement;adLoadShowsNoProgress(i)?(n=!0,this.deps.log(`Ad load for ${t.assetId} stalled after ${r}ms; retrying once`),this.deps.diagnose("DA-AD-LOAD-RETRIED",`Ad load for ${t.assetId} in break ${t.breakId} had not settled after ${r}ms; issued a second load()`,{breakId:t.breakId,assetId:t.assetId,uri:t.uri,elapsedMs:r,remainingBudgetMs:this.remainingAdStartBudgetMs()}),e().then(()=>l({ok:!0}),e=>l({ok:!1,error:e}))):this.deps.log(`Ad load for ${t.assetId} still pending after ${r}ms but the element is progressing (readyState=${i?.readyState}, buffered=${i?.buffered.length}); no retry`)},2e3)),e().then(()=>l({ok:!0}),e=>{n||l({ok:!1,error:e})})})}async waitForFirstFrame(){return new Promise((e,t)=>{this.deps.state.firstFrameResolver=()=>{this.clearFirstFrameResolver(),e()},this.deps.state.firstFrameRejector=e=>{this.clearFirstFrameResolver(),t(e)},this.deps.state.firstFrameTimeout=setTimeout(()=>{this.deps.onAdError(new m("First frame timeout","DA-AD-START-TIMEOUT"))},this.remainingAdStartBudgetMs())})}clearFirstFrame(e="Break ended before first frame"){if(this.deps.state.adStartDeadlineMs=null,this.deps.state.adStartBudgetMs=0,this.deps.state.firstFrameTimeout&&(clearTimeout(this.deps.state.firstFrameTimeout),this.deps.state.firstFrameTimeout=null),this.deps.state.firstFrameRejector){const t=this.deps.state.firstFrameRejector;this.deps.state.firstFrameResolver=null,this.deps.state.firstFrameRejector=null,t(new Error(e))}}clearFirstFrameResolver(){this.deps.state.adStartDeadlineMs=null,this.deps.state.adStartBudgetMs=0,this.deps.state.firstFrameTimeout&&(clearTimeout(this.deps.state.firstFrameTimeout),this.deps.state.firstFrameTimeout=null),this.deps.state.firstFrameResolver=null,this.deps.state.firstFrameRejector=null}preloadCompanion(s){const r=this.deps.getCompanionVariant(s);if(!r)return;if(r.format===t.LSHAPE_CONTENT){const t=r.assets[0];if(!t||t.type!==e.STATIC)return;const s=this.deps.resolveUri(t.uri);if(!s)return;return(new Image).src=s,void this.deps.log("Preloading lshape_content backdrop:",s)}if(r.format!==t.DOUBLE&&r.format!==t.LSHAPE_AD)return;const a=r.assets[0]?.companion;if(!a||a.type!==e.STATIC)return;const i=this.deps.resolveUri(a.uri);if(!i)return;(new Image).src=i,this.deps.log("Preloading companion:",i)}async warmCache(e){"function"==typeof fetch&&await Promise.allSettled(e.map(e=>o(e,{mode:"no-cors"},d).catch(()=>{})))}}
|
|
@@ -40,8 +40,11 @@ export declare class AdStage {
|
|
|
40
40
|
private resizePoll;
|
|
41
41
|
private companionRemoveTimer;
|
|
42
42
|
private adFadeTimer;
|
|
43
|
+
private preparedLease;
|
|
44
|
+
private unsubscribeGeometry;
|
|
43
45
|
constructor(deps: AdStageDeps);
|
|
44
46
|
setupOverlay(): void;
|
|
47
|
+
private setupMediaLayer;
|
|
45
48
|
private refreshStageGeometry;
|
|
46
49
|
private rebindContentVideoGeometryListener;
|
|
47
50
|
private updateStageGeometry;
|
|
@@ -54,5 +57,7 @@ export declare class AdStage {
|
|
|
54
57
|
clearOverlayImage(): void;
|
|
55
58
|
show(): void;
|
|
56
59
|
hide(): void;
|
|
60
|
+
restoreContent(createdBySDK: boolean, originalStyle: string): boolean;
|
|
61
|
+
private releasePreparedPresentation;
|
|
57
62
|
destroy(): void;
|
|
58
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AssetType as e,BreakFormat as t}from"../../types/BreakManifest";const s=16/9;export class AdStage{constructor(e){this.deps=e,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.resizePoll=null,this.companionRemoveTimer=null,this.adFadeTimer=null}setupOverlay(){const e=window.getComputedStyle(this.deps.container);"static"!==e.position&&""!==e.position||(this.deps.container.style.position="relative"),this.deps.setStage(document.createElement("div"));const t=this.deps.getStage();if(!t)return;if(t.className="dolby-stage",t.style.cssText="position:absolute;overflow:hidden;",this.deps.container.appendChild(t),this.contentVideoGeometryHandler=()=>this.refreshStageGeometry(),"undefined"!=typeof MutationObserver&&(this.contentVideoMutationObserver=new MutationObserver(()=>{(this.deps.contentPlayer.videoElement??null)!==this.deps.getContentVideo()&&this.refreshStageGeometry()}),this.contentVideoMutationObserver.observe(this.deps.container,{childList:!0,subtree:!0})),"undefined"!=typeof ResizeObserver)this.resizeObserver=new ResizeObserver(()=>this.refreshStageGeometry()),this.resizeObserver.observe(this.deps.container);else{let e=this.deps.container.getBoundingClientRect();this.resizePoll=setInterval(()=>{const t=this.deps.container.getBoundingClientRect();t.width===e.width&&t.height===e.height||(e=t,this.refreshStageGeometry())},250)}if(this.refreshStageGeometry(),this.deps.getPlayerContainer()){const e=this.deps.getPlayerContainer();if(!e)return;t.appendChild(e)}else{const e=document.createElement("div"),s=Array.from(this.deps.container.childNodes).filter(e=>e!==t);for(const t of s)e.appendChild(t);t.appendChild(e),this.deps.setPlayerContainer(e),this.deps.onPlayerContainerCreated()}const s=this.deps.getPlayerContainer();if(!s)return;this.deps.setPlayerContainerOriginalStyle(s.style.cssText),s.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;",this.deps.setAdContainer(document.createElement("div"));const i=this.deps.getAdContainer();if(!i)return;i.className="dolby-ad-container",i.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;",this.deps.setAdMediaContainer(document.createElement("div"));const o=this.deps.getAdMediaContainer();o&&(o.style.cssText="display:block;width:100%;height:100%;",i.appendChild(o),t.appendChild(i),this.deps.setAdPlayer(this.deps.createAdAdapter(o)),this.deps.setupEventListeners(),this.deps.log("Ad player overlay initialized"))}refreshStageGeometry(){this.rebindContentVideoGeometryListener(),this.updateStageGeometry(),this.deps.resizeVast()}rebindContentVideoGeometryListener(){const e=this.deps.contentPlayer.videoElement??null,t=e&&"function"==typeof e.addEventListener&&"function"==typeof e.removeEventListener?e:null;t!==this.deps.getContentVideo()&&(this.deps.getContentVideo()&&this.contentVideoGeometryHandler&&(this.deps.getContentVideo()?.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),this.deps.getContentVideo()?.removeEventListener("resize",this.contentVideoGeometryHandler)),this.deps.setContentVideo(t),t&&this.contentVideoGeometryHandler&&(t.addEventListener("loadedmetadata",this.contentVideoGeometryHandler),t.addEventListener("resize",this.contentVideoGeometryHandler)))}updateStageGeometry(){const e=this.deps.getStage();if(!e)return;const{width:t,height:s}=this.deps.container.getBoundingClientRect();if(0===t||0===s)return;const i=this.getContentAspectRatio();let o,n;t/s>i?(n=s,o=s*i):(o=t,n=t/i);const r=(s-n)/2,a=(t-o)/2;e.style.width=`${o}px`,e.style.height=`${n}px`,e.style.top=`${r}px`,e.style.left=`${a}px`}getContentAspectRatio(){const e=this.deps.contentPlayer.videoElement;if(!e)return s;const{videoWidth:t,videoHeight:i}=e;return!Number.isFinite(t)||!Number.isFinite(i)||t<=0||i<=0?s:t/i}renderLshapeContentBackdrop(t){const s=t.assets[0];if(!s||s.type!==e.STATIC)return;const i=this.deps.resolveUri(s.uri);if(!i)return;const o=document.createElement("img");o.className="dolby-companion",o.src=i,o.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;";const n=this.deps.getStage();n&&(n.appendChild(o),this.deps.setCompanion(o),requestAnimationFrame(()=>{const e=this.deps.getCompanion();e&&(e.style.opacity="1")}),this.deps.log("Rendered lshape_content backdrop:",i))}reapplyAdContainerLayout(e){const s=this.deps.getAdContainer();if(s){switch(e.format){case t.LSHAPE_AD:s.style.position="absolute",s.style.top="5%",s.style.left="5%",s.style.right="30%",s.style.bottom="30%",s.style.width="",s.style.height="",s.style.zIndex="100";break;case t.DOUBLE:s.style.position="absolute",s.style.top="26.04%",s.style.left="51.04%",s.style.right="1.04%",s.style.bottom="26.04%",s.style.width="",s.style.height="",s.style.zIndex="100"}this.deps.log("reapplyAdContainerLayout: re-stamped adContainer CSS for format:",e.format)}}applyLayout(e){const s=this.deps.getAdContainer(),i=this.deps.getPlayerContainer();if(!s||!i)return;const o="transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;";switch(e.format){case t.SINGLE:s.style.cssText="display:none;opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;";break;case t.DOUBLE:i.style.cssText=`position:absolute;top:26.04%;left:1.04%;right:51.04%;bottom:26.04%;z-index:101;${o}`,s.style.cssText="display:none;opacity:0;position:absolute;top:26.04%;left:51.04%;right:1.04%;bottom:26.04%;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;");break;case t.LSHAPE_AD:i.style.cssText=`position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:101;opacity:0;${o}`,s.style.cssText="display:none;opacity:0;position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;");break;case t.LSHAPE_CONTENT:i.style.top="5%",i.style.left="5%",i.style.right="30%",i.style.bottom="30%",i.style.zIndex="101";break;case t.OVERLAY:{const t=e,i=e=>void 0!==e?100*e+"%":"auto",o=i(t.position.top),n=i(t.position.bottom),r=i(t.position.left),a=i(t.position.right),d=100*t.size.width+"%",l=100*t.size.height+"%",p=t.opacity??1;s.style.cssText=`display:none;position:absolute;top:${o};bottom:${n};left:${r};right:${a};width:${d};height:${l};z-index:100;opacity:${p};`;break}default:s.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;"}}restoreLayout(e){const s=e===t.DOUBLE||e===t.LSHAPE_AD||e===t.LSHAPE_CONTENT,i=this.deps.getPlayerContainer();if(!i)return;i.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;"+(s?"z-index:101;":"")+"transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;",this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer);const o=this.deps.getCompanion();o&&(o.style.opacity="0"),this.companionRemoveTimer=setTimeout(()=>{const e=this.deps.getCompanion();e?.parentElement&&(e.parentElement.removeChild(e),this.deps.setCompanion(null)),this.companionRemoveTimer=null;const t=this.deps.getPlayerContainer();s&&t&&(t.style.zIndex="")},300)}renderCompanion(t,s){const i=t[0]?.companion;if(!i)return;const o=i.type===e.STATIC?this.deps.resolveUri(i.uri):null;if(!o)return;const n=document.createElement("img");n.className="dolby-companion",n.src=o,n.style.cssText=s;const r=this.deps.getStage();r&&(r.appendChild(n),this.deps.setCompanion(n),requestAnimationFrame(()=>{const e=this.deps.getCompanion();e&&(e.style.opacity="1")}))}clearOverlayImage(){const e=this.deps.getImageAssetTimer();e&&(clearTimeout(e),this.deps.setImageAssetTimer(null));const t=this.deps.getOverlayImage();t?.parentElement&&t.parentElement.removeChild(t),this.deps.setOverlayImage(null)}show(){const e=this.deps.getAdContainer();if(!e)return;this.adFadeTimer&&(clearTimeout(this.adFadeTimer),this.adFadeTimer=null);const s=this.deps.getCurrentBreak(),i=s?this.deps.getVariant(s):null;e.style.display="block",i?.format!==t.OVERLAY&&(this.deps.isChaining()?e.style.opacity="1":(e.style.opacity="0",requestAnimationFrame(()=>{const e=this.deps.getAdContainer();e&&(e.style.opacity="1")})))}hide(){const e=this.deps.getAdContainer();if(!e)return;const s=this.deps.getCurrentBreak(),i=s?this.deps.getVariant(s):null;i?.format!==t.OVERLAY?(e.style.opacity="0",this.adFadeTimer=setTimeout(()=>{const e=this.deps.getAdContainer();e&&(e.style.display="none"),this.adFadeTimer=null},300)):e.style.display="none"}destroy(){const e=this.deps.getContentVideo();e&&this.contentVideoGeometryHandler&&(e.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),e.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoMutationObserver?.disconnect(),this.resizeObserver?.disconnect(),null!==this.resizePoll&&clearInterval(this.resizePoll),this.resizePoll=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.contentVideoGeometryHandler=null,this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer),this.adFadeTimer&&clearTimeout(this.adFadeTimer)}}
|
|
1
|
+
import{getPreparedPresentation as e}from"./PreparedPresentation";import{AssetType as t,BreakFormat as s}from"../../types/BreakManifest";const i=16/9;export class AdStage{constructor(e){this.deps=e,this.contentVideoGeometryHandler=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.resizePoll=null,this.companionRemoveTimer=null,this.adFadeTimer=null,this.preparedLease=null,this.unsubscribeGeometry=null}setupOverlay(){const t=e(this.deps.container);if(t){const e=this.deps.getPlayerContainer();if(e&&e!==t.playerContainer)throw new Error("The prepared content container cannot be replaced");this.preparedLease=t.acquire(),this.deps.setStage(this.preparedLease.stage),this.deps.setPlayerContainer(this.preparedLease.playerContainer),this.unsubscribeGeometry=this.preparedLease.subscribe(()=>this.deps.resizeVast());try{this.setupMediaLayer()}catch(e){const t=this.deps.getAdContainer();throw t?.parentNode?.removeChild(t),this.deps.setAdContainer(null),this.deps.setAdMediaContainer(null),this.unsubscribeGeometry?.(),this.preparedLease.release(),e}return}const s=window.getComputedStyle(this.deps.container);"static"!==s.position&&""!==s.position||(this.deps.container.style.position="relative"),this.deps.setStage(document.createElement("div"));const i=this.deps.getStage();if(!i)return;if(i.className="dolby-stage",i.style.cssText="position:absolute;overflow:hidden;",this.deps.container.appendChild(i),this.contentVideoGeometryHandler=()=>this.refreshStageGeometry(),"undefined"!=typeof MutationObserver&&(this.contentVideoMutationObserver=new MutationObserver(()=>{(this.deps.contentPlayer.videoElement??null)!==this.deps.getContentVideo()&&this.refreshStageGeometry()}),this.contentVideoMutationObserver.observe(this.deps.container,{childList:!0,subtree:!0})),"undefined"!=typeof ResizeObserver)this.resizeObserver=new ResizeObserver(()=>this.refreshStageGeometry()),this.resizeObserver.observe(this.deps.container);else{let e=this.deps.container.getBoundingClientRect();this.resizePoll=setInterval(()=>{const t=this.deps.container.getBoundingClientRect();t.width===e.width&&t.height===e.height||(e=t,this.refreshStageGeometry())},250)}if(this.refreshStageGeometry(),this.deps.getPlayerContainer()){const e=this.deps.getPlayerContainer();if(!e)return;i.appendChild(e)}else{const e=document.createElement("div"),t=Array.from(this.deps.container.childNodes).filter(e=>e!==i);for(const s of t)e.appendChild(s);i.appendChild(e),this.deps.setPlayerContainer(e),this.deps.onPlayerContainerCreated()}const o=this.deps.getPlayerContainer();o&&(this.deps.setPlayerContainerOriginalStyle(o.style.cssText),o.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;",this.setupMediaLayer())}setupMediaLayer(){const e=this.deps.getStage();if(!e)throw new Error("An ads presentation stage is required");this.deps.setAdContainer(document.createElement("div"));const t=this.deps.getAdContainer();if(!t)return;t.className="dolby-ad-container",t.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;",this.deps.setAdMediaContainer(document.createElement("div"));const s=this.deps.getAdMediaContainer();s&&(s.style.cssText="display:block;width:100%;height:100%;",t.appendChild(s),e.appendChild(t),this.deps.setAdPlayer(this.deps.createAdAdapter(s)),this.deps.setupEventListeners(),this.deps.log("Ad player overlay initialized"))}refreshStageGeometry(){this.rebindContentVideoGeometryListener(),this.updateStageGeometry(),this.deps.resizeVast()}rebindContentVideoGeometryListener(){const e=this.deps.contentPlayer.videoElement??null,t=e&&"function"==typeof e.addEventListener&&"function"==typeof e.removeEventListener?e:null;t!==this.deps.getContentVideo()&&(this.deps.getContentVideo()&&this.contentVideoGeometryHandler&&(this.deps.getContentVideo()?.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),this.deps.getContentVideo()?.removeEventListener("resize",this.contentVideoGeometryHandler)),this.deps.setContentVideo(t),t&&this.contentVideoGeometryHandler&&(t.addEventListener("loadedmetadata",this.contentVideoGeometryHandler),t.addEventListener("resize",this.contentVideoGeometryHandler)))}updateStageGeometry(){const e=this.deps.getStage();if(!e)return;const{width:t,height:s}=this.deps.container.getBoundingClientRect();if(0===t||0===s)return;const i=this.getContentAspectRatio();let o,n;t/s>i?(n=s,o=s*i):(o=t,n=t/i);const r=(s-n)/2,a=(t-o)/2;e.style.width=`${o}px`,e.style.height=`${n}px`,e.style.top=`${r}px`,e.style.left=`${a}px`}getContentAspectRatio(){const e=this.deps.contentPlayer.videoElement;if(!e)return i;const{videoWidth:t,videoHeight:s}=e;return!Number.isFinite(t)||!Number.isFinite(s)||t<=0||s<=0?i:t/s}renderLshapeContentBackdrop(e){const s=e.assets[0];if(!s||s.type!==t.STATIC)return;const i=this.deps.resolveUri(s.uri);if(!i)return;const o=document.createElement("img");o.className="dolby-companion",o.src=i,o.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;";const n=this.deps.getStage();n&&(n.appendChild(o),this.deps.setCompanion(o),requestAnimationFrame(()=>{const e=this.deps.getCompanion();e&&(e.style.opacity="1")}),this.deps.log("Rendered lshape_content backdrop:",i))}reapplyAdContainerLayout(e){const t=this.deps.getAdContainer();if(t){switch(e.format){case s.LSHAPE_AD:t.style.position="absolute",t.style.top="5%",t.style.left="5%",t.style.right="30%",t.style.bottom="30%",t.style.width="",t.style.height="",t.style.zIndex="100";break;case s.DOUBLE:t.style.position="absolute",t.style.top="26.04%",t.style.left="51.04%",t.style.right="1.04%",t.style.bottom="26.04%",t.style.width="",t.style.height="",t.style.zIndex="100"}this.deps.log("reapplyAdContainerLayout: re-stamped adContainer CSS for format:",e.format)}}applyLayout(e){const t=this.deps.getAdContainer(),i=this.deps.getPlayerContainer();if(!t||!i)return;const o="transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;";switch(e.format){case s.SINGLE:t.style.cssText="display:none;opacity:0;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;";break;case s.DOUBLE:i.style.cssText=`position:absolute;top:26.04%;left:1.04%;right:51.04%;bottom:26.04%;z-index:101;${o}`,t.style.cssText="display:none;opacity:0;position:absolute;top:26.04%;left:51.04%;right:1.04%;bottom:26.04%;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;");break;case s.LSHAPE_AD:i.style.cssText=`position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:101;opacity:0;${o}`,t.style.cssText="display:none;opacity:0;position:absolute;top:5%;left:5%;right:30%;bottom:30%;z-index:100;background:#000;transition:opacity 0.3s ease-in-out;",this.renderCompanion(e.assets,"position:absolute;top:0;left:0;right:0;bottom:0;z-index:99;object-fit:cover;width:100%;height:100%;opacity:0;transition:opacity 0.3s ease-in-out;");break;case s.LSHAPE_CONTENT:i.style.top="5%",i.style.left="5%",i.style.right="30%",i.style.bottom="30%",i.style.zIndex="101";break;case s.OVERLAY:{const s=e,i=e=>void 0!==e?100*e+"%":"auto",o=i(s.position.top),n=i(s.position.bottom),r=i(s.position.left),a=i(s.position.right),d=100*s.size.width+"%",l=100*s.size.height+"%",p=s.opacity??1;t.style.cssText=`display:none;position:absolute;top:${o};bottom:${n};left:${r};right:${a};width:${d};height:${l};z-index:100;opacity:${p};`;break}default:t.style.cssText="display:none;position:absolute;top:0;left:0;right:0;bottom:0;z-index:100;background:#000;"}}restoreLayout(e){if(this.preparedLease&&!this.preparedLease.isActive())return;const t=e===s.DOUBLE||e===s.LSHAPE_AD||e===s.LSHAPE_CONTENT,i=this.deps.getPlayerContainer();if(!i)return;i.style.cssText="position:absolute;top:0;left:0;right:0;bottom:0;"+(t?"z-index:101;":"")+"transition:top 0.3s ease-in-out,left 0.3s ease-in-out,right 0.3s ease-in-out,bottom 0.3s ease-in-out,opacity 0.3s ease-in-out;",this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer);const o=this.deps.getCompanion();o&&(o.style.opacity="0"),this.companionRemoveTimer=setTimeout(()=>{const e=this.deps.getCompanion();e?.parentElement&&(e.parentElement.removeChild(e),this.deps.setCompanion(null)),this.companionRemoveTimer=null;const s=this.deps.getPlayerContainer();t&&s&&(s.style.zIndex="")},300)}renderCompanion(e,s){const i=e[0]?.companion;if(!i)return;const o=i.type===t.STATIC?this.deps.resolveUri(i.uri):null;if(!o)return;const n=document.createElement("img");n.className="dolby-companion",n.src=o,n.style.cssText=s;const r=this.deps.getStage();r&&(r.appendChild(n),this.deps.setCompanion(n),requestAnimationFrame(()=>{const e=this.deps.getCompanion();e&&(e.style.opacity="1")}))}clearOverlayImage(){const e=this.deps.getImageAssetTimer();e&&(clearTimeout(e),this.deps.setImageAssetTimer(null));const t=this.deps.getOverlayImage();t?.parentElement&&t.parentElement.removeChild(t),this.deps.setOverlayImage(null)}show(){const e=this.deps.getAdContainer();if(!e)return;this.adFadeTimer&&(clearTimeout(this.adFadeTimer),this.adFadeTimer=null);const t=this.deps.getCurrentBreak(),i=t?this.deps.getVariant(t):null;e.style.display="block",i?.format!==s.OVERLAY&&(this.deps.isChaining()?e.style.opacity="1":(e.style.opacity="0",requestAnimationFrame(()=>{const e=this.deps.getAdContainer();e&&(e.style.opacity="1")})))}hide(){const e=this.deps.getAdContainer();if(!e)return;const t=this.deps.getCurrentBreak(),i=t?this.deps.getVariant(t):null;i?.format!==s.OVERLAY?(e.style.opacity="0",this.adFadeTimer=setTimeout(()=>{const e=this.deps.getAdContainer();e&&(e.style.display="none"),this.adFadeTimer=null},300)):e.style.display="none"}restoreContent(e,t){if(this.releasePreparedPresentation())return!0;const s=this.deps.getPlayerContainer();if(!s)return!1;if(e)for(;s.firstChild;)this.deps.container.appendChild(s.firstChild);else this.deps.container.appendChild(s),s.style.cssText=t;return!1}releasePreparedPresentation(){return!!this.preparedLease&&(this.unsubscribeGeometry?.(),this.unsubscribeGeometry=null,this.preparedLease.release(),!0)}destroy(){this.unsubscribeGeometry?.(),this.unsubscribeGeometry=null;const e=this.deps.getContentVideo();e&&this.contentVideoGeometryHandler&&(e.removeEventListener("loadedmetadata",this.contentVideoGeometryHandler),e.removeEventListener("resize",this.contentVideoGeometryHandler)),this.contentVideoMutationObserver?.disconnect(),this.resizeObserver?.disconnect(),null!==this.resizePoll&&clearInterval(this.resizePoll),this.resizePoll=null,this.contentVideoMutationObserver=null,this.resizeObserver=null,this.contentVideoGeometryHandler=null,this.companionRemoveTimer&&clearTimeout(this.companionRemoveTimer),this.adFadeTimer&&clearTimeout(this.adFadeTimer)}}
|
|
@@ -25,12 +25,18 @@ export interface AssetPlaybackDeps {
|
|
|
25
25
|
revealFirstFrame: () => void;
|
|
26
26
|
transferPictureInPictureToAd: () => Promise<void>;
|
|
27
27
|
isGamPodAsset: (asset: Asset) => boolean;
|
|
28
|
+
/** Whether the GAM pod asset was skipped because its identity mismatches the stream session. */
|
|
29
|
+
hasGamIdentityMismatch: (asset: Asset) => boolean;
|
|
28
30
|
beginGamPodTracking: (asset: Asset) => void;
|
|
29
31
|
recordAdProgress: (currentTime: number) => void;
|
|
30
32
|
reportPhase: (name: string) => void;
|
|
31
33
|
armAdStartBudget: (asset: Asset) => void;
|
|
32
34
|
remainingAdStartBudgetMs: () => number;
|
|
33
|
-
loadWithinBudget: (
|
|
35
|
+
loadWithinBudget: (startLoad: () => Promise<void>, context: {
|
|
36
|
+
breakId: string;
|
|
37
|
+
assetId: string;
|
|
38
|
+
uri: string;
|
|
39
|
+
}) => Promise<boolean>;
|
|
34
40
|
waitForFirstFrame: () => Promise<void>;
|
|
35
41
|
getOverlayImage: () => HTMLImageElement | null;
|
|
36
42
|
setOverlayImage: (value: HTMLImageElement | null) => void;
|
|
@@ -47,6 +53,13 @@ export declare class AssetPlayback {
|
|
|
47
53
|
constructor(state: AdPlayerState, deps: AssetPlaybackDeps);
|
|
48
54
|
private get currentBreak();
|
|
49
55
|
playAsset(asset: Asset, index: number, total: number): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Publish the prepared ad media's own duration (ADS-619).
|
|
58
|
+
*
|
|
59
|
+
* Silent when the adapter cannot name one: a live or still-loading source reports `Infinity` or
|
|
60
|
+
* `NaN`, and a guessed number here would be read as a measurement of the break.
|
|
61
|
+
*/
|
|
62
|
+
private reportPreparedDuration;
|
|
50
63
|
/**
|
|
51
64
|
* `play()` with one retry when the browser itself pauses the element while the request is
|
|
52
65
|
* pending (Chromium: "The play() request was interrupted because the media paused", an
|
|
@@ -68,4 +81,5 @@ export declare class AssetPlayback {
|
|
|
68
81
|
revealFirstFrame(): void;
|
|
69
82
|
/** Surfaces the MediaError's code/message so consumers can diagnose ad playback failures. */
|
|
70
83
|
toAdError(mediaError?: unknown): Error;
|
|
84
|
+
private missingUriError;
|
|
71
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as
|
|
1
|
+
import{AssetType as e,BreakFormat as t,Vendor as s,MediaType as r,isVastAsset as i}from"../../types/BreakManifest";import{AdError as a}from"../../types/AdError";import{resolveVastAdmission as d}from"../resolveVastAdmission";import{classifyMediaError as n,toClassifiedAdError as o}from"./classifyMediaError";import{VastAdManager as h,isImaCsaiSdkAvailable as p}from"../VastAdManager";import{invalidateMediaPreload as c}from"./AdPlayerState";export class AssetPlayback{constructor(e,t){this.state=e,this.deps=t}get currentBreak(){return this.state.currentBreak}async playAsset(e,t,s){const d=this.currentBreak,n=this.deps.getAdPlayer();if(!d||!n)return;const h=this.state.preparedMedia,p=c(this.state),l=()=>this.state.preloadGeneration===p&&this.currentBreak===d&&this.deps.getAdPlayer()===n;if(this.state.currentAsset=e,this.state.currentAssetElapsedSec=0,this.deps.clearStaticQuartiles(),this.deps.clearOverlayImage(),this.deps.log("Playing asset:",e.id,"type:",e.type,"index:",t),i(e))return void this.playVastAsset(e,t,s);if(e.mediaType===r.IMAGE)return void await this.playImageAsset(e,t,s);const u=await this.resolveAssetUri(e,this.state.effectiveBreakDurationSec??d.duration);if(!l())return;if(!u)return void this.deps.onAdError(this.missingUriError(e));this.deps.log("Resolved URI:",u),this.deps.reportPhase("uri-resolved");const A=d.id;try{this.deps.armAdStartBudget(e);if(0===t&&h?.breakId===A&&h.assetId===e.id&&h.uri===u&&h.player===n)this.deps.reportPhase("load-skipped-preloaded");else{if(this.deps.reportPhase("load-start"),!await this.deps.loadWithinBudget(()=>n.load(u),{breakId:A,assetId:e.id,uri:u})||!l())return;this.deps.reportPhase("load-resolved")}this.reportPreparedDuration(n,d,e,t,s);const r=n.videoElement;if(r&&(r.style.cssText="display:block;width:100%;height:100%;object-fit:contain;"),this.deps.reportPhase("play-called"),this.state.firstFrameGateActive&&!this.state.firstFrameGateShown){const e=this.deps.waitForFirstFrame();await this.playRetryingInterruption(n,A),await e}else{let e=null;const t=await Promise.race([this.playRetryingInterruption(n,A).then(()=>!0),new Promise(t=>{e=setTimeout(()=>t(!1),this.deps.remainingAdStartBudgetMs())})]);if(e&&clearTimeout(e),!l())return;if(!t)return void this.deps.onAdError(new a(`Ad did not start within ${this.state.adStartBudgetMs}ms ad-start budget`,"DA-AD-START-TIMEOUT"))}if(!l())return;this.state.adStartDeadlineMs=null,this.state.adStartBudgetMs=0,this.deps.transferPictureInPictureToAd();const i=this.deps.getSequencer();i?(this.deps.isGamPodAsset(e)&&this.deps.beginGamPodTracking(e),await this.deps.drive(i.onOutcome({kind:"asset-started"}))):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s)}catch(e){if(!l())return;if(this.state.firstFrameGateActive&&null===this.state.firstFrameResolver)return;this.deps.onAdError(o(e))}}reportPreparedDuration(e,t,s,r,i){const a=e.duration;if(!Number.isFinite(a)||a<=0)return;const d=s.duration;this.deps.diagnose("DA-AD-DURATION-PREPARED",`Ad media for break ${t.id} prepared: ${a}s of playable ad`,{breakId:t.id,assetId:s.id,index:r,total:i,preparedDurationSec:a,..."number"==typeof d?{declaredDurationSec:d}:{},breakDurationSec:this.state.effectiveBreakDurationSec??t.duration})}async playRetryingInterruption(e,t){const s=this.state.preloadGeneration;try{await e.play()}catch(r){if(s!==this.state.preloadGeneration||!function(e){return"AbortError"===e?.name}(r)||!this.isStartingBreak(t))throw r;this.deps.diagnose("DA-AD-START-INTERRUPTED","Ad play() was interrupted by a browser-initiated pause before the ad started; play() re-issued once.",{breakId:t,error:r instanceof Error?r.message:String(r)}),this.deps.reportPhase("play-retried"),await e.play()}}isStartingBreak(e){return this.state.isPlaying&&this.state.currentBreak?.id===e}playVastAsset(e,s,r){if(!this.currentBreak||!this.deps.getAdPlayer())return;const i=this.deps.getVariant(this.currentBreak)?.format??t.SINGLE,n=d({format:i,ssai:!1});if(!n.admitted)return this.deps.diagnose(n.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT",`VAST asset '${e.id}' is not supported in '${i}' breaks (single/double/lshape_ad only); skipping.`,{breakId:this.currentBreak.id,assetId:e.id,format:i}),void this.deps.onAdError(new a(`VAST not supported in ${i} format`,n.diagnostic??"DA-VAST-UNSUPPORTED-FORMAT"));if(!p())return this.deps.diagnose("DA-VAST-IMA-SDK-MISSING","Google IMA client-side SDK (ima3.js) is unavailable; cannot play VAST ad.",{breakId:this.currentBreak.id,assetId:e.id}),void this.deps.onAdError(new a("IMA client-side SDK (ima3.js) not available","DA-VAST-IMA-SDK-MISSING"));const o=this.deps.buildVastTagUrl(e,this.currentBreak);if(!o)return void this.deps.onAdError(new a("No valid VAST tag URL for asset","DA-AD-URI-MISSING"));const c=this.deps.getAdPlayer(),l=c?.videoElement,u=this.deps.getAdContainer();if(!u||!l||!c)return void this.deps.onAdError(new a("VAST playout requires an ad container and ad video element","DA-AD-URI-MISSING"));this.deps.revealFirstFrame(),l.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.state.vastAdManager||(this.state.vastAdManager=new h(u,l,this.deps.debug)),this.state.vastAdManager.setCallbacks({onAdStarted:t=>{if(!this.currentBreak)return;this.state.startPlaybackCutoff?.(),this.deps.setVastPodPosition(t);const i=this.deps.getSequencer();i?this.deps.drive(i.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,s,r,{podPosition:t})},onAdProgress:(t,s)=>{this.currentBreak&&(this.deps.recordAdProgress(t),this.state.callbacks?.onAdTimeUpdate(this.currentBreak,e,t,s))},onAdFirstQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdFirstQuartile?.(this.currentBreak,e)},onAdMidpoint:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdMidpoint?.(this.currentBreak,e)},onAdThirdQuartile:()=>{this.currentBreak&&this.deps.getGamEventCallbacks().onAdThirdQuartile?.(this.currentBreak,e)}}),this.deps.log("Playing VAST asset via IMA CSAI:",o);(this.state.vastAdManager.hasPreloadedManager()?(this.state.vastAdManager.setMuted(c.muted),this.state.vastAdManager.startPreloaded()):this.state.vastAdManager.playVast(o,{muted:c.muted})).then(()=>{this.deps.onAdEnded()},t=>{const s=t instanceof Error?t.message:String(t),r=/ima3\.js|client-side SDK/i.test(s)?"DA-VAST-IMA-SDK-MISSING":"DA-VAST-IMA-ERROR",i=t instanceof a?t:new a(s,r);this.deps.diagnose(r,s,{...this.currentBreak?{breakId:this.currentBreak.id}:{},assetId:e.id}),this.deps.onAdError(i)})}async playImageAsset(e,t,s){const r=this.currentBreak,i=this.deps.getAdMediaContainer();if(!r||!i)return;const d=this.state.effectiveBreakDurationSec??r.duration,n=await this.resolveAssetUri(e,d);if(!n)return void this.deps.onAdError(this.missingUriError(e));const o=document.createElement("img");o.className="dolby-overlay-image",o.style.cssText="display:block;width:100%;height:100%;object-fit:contain;",this.deps.setOverlayImage(o);const h=e.duration??d;o.onload=()=>{if(this.deps.getOverlayImage()!==o||!this.currentBreak)return;this.state.startPlaybackCutoff?.();const r=this.deps.getSequencer();r?this.deps.drive(r.onOutcome({kind:"asset-started"})):this.state.callbacks?.onAdStart(this.currentBreak,e,t,s),h>0?this.deps.setImageAssetTimer(setTimeout(()=>this.onImageAssetEnded(),1e3*h)):this.onImageAssetEnded()},o.onerror=()=>{this.deps.getOverlayImage()===o&&this.deps.onAdError(new a(`Overlay image failed to load: ${n}`,"DA-IMAGE-LOAD-FAILED"))},o.src=n,i.appendChild(o)}onImageAssetEnded(){const e=this.deps.getSequencer();if(!this.currentBreak||!this.state.isPlaying||!e)return;const t=this.deps.getImageAssetTimer();t&&(clearTimeout(t),this.deps.setImageAssetTimer(null)),this.deps.drive(e.onOutcome({kind:"asset-ended"}))}async resolveAssetUri(t,r){switch(t.type){case e.STATIC:return this.deps.resolveUri(t.uri);case e.VENDOR:{const e=t;return e.vendor===s.GAM?await this.deps.buildGamUrl(e,r):(this.deps.log("Unsupported vendor:",e.vendor),null)}default:return this.deps.log("Unsupported asset type:",t.type),null}}adPlayerStreamRemainingSec(){const e=this.deps.getAdPlayer();if(!e)return 0;const{duration:t,currentTime:s}=e;if(!Number.isFinite(t)||t<=0)return 0;if(!Number.isFinite(s)||s<0)return 0;const r=t-s;return r>0?r:0}revealFirstFrame(){this.deps.revealFirstFrame()}toAdError(e){const t=e,s=n(e);if(t&&("number"==typeof t.code||t.message)){const e="number"==typeof t.code?` (code ${t.code})`:"",r=t.message?`: ${t.message}`:"";return new a(`Ad playback error${e}${r}`,s)}return new a("Ad playback error",s)}missingUriError(e){return this.deps.hasGamIdentityMismatch(e)?new a("GAM pod asset identity does not match the stream session","DA-GAM-POD-IDENTITY-MISMATCH"):new a("No valid URI for asset","DA-AD-URI-MISSING")}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export async function resumeContentAfterBreak(e,t){const{breakInfo:
|
|
1
|
+
export async function resumeContentAfterBreak(e,t){const{breakInfo:n,earlyReturnOffsetSec:r,isLiveResume:a,released:i,isCurrent:o}=t,{contentPlayer:s,preloadMode:d,contentRecovery:l,resumeCoordinator:c,log:u}=e;if(i&&(await Promise.resolve(i).catch(e=>{u("Ad media release failed:",e)}),!o()))return;const m=()=>{a&&setTimeout(()=>e.getCallbacks()?.onContentReloading?.(!1),2e3)},p=async()=>{await s.play().catch(e=>{u("Content resume failed:",e)}).finally(m)};if("recreate"===l){if("function"==typeof s.recreateMediaPipeline)return void await async function(e,t,n){const{breakInfo:r,earlyReturnOffsetSec:a,isLiveResume:i,isCurrent:o}=t,{contentPlayer:s,resumeCoordinator:d,diagnose:l,preloadMode:c}=e,u=s.recreateMediaPipeline;if(!u)return;if("single-decoder"===c&&(await new Promise(e=>setTimeout(e,300)),!o()))return;i&&e.getCallbacks()?.onContentReloading?.(!0);const m=d.resolveResumeTarget(r,a)??s.currentTime;d.noteContentReload();const p=()=>"undefined"!=typeof performance?performance.now():Date.now(),f=p(),C=u.call(s,{startTime:m});let E,T=null;try{await Promise.race([C,new Promise((e,t)=>{E=setTimeout(()=>t(new Error("recreateMediaPipeline timed out")),1e4)})])}catch(e){T=e}finally{void 0!==E&&clearTimeout(E)}if(!o())return;if(null===T){const e=Math.round(p()-f);l("DA-CONTENT-PIPELINE-RECREATED",`Content media pipeline rebuilt at ${m}s for break ${r.id} in ${e}ms`,{breakId:r.id,startTime:m,durationMs:e}),d.probeResumeLanding(r.id,m)}else{const e=T instanceof Error?T.message:String(T);l("DA-CONTENT-PIPELINE-RECREATE-FAILED",`Content media pipeline rebuild for break ${r.id} failed (${e}); resuming in place`,{breakId:r.id,startTime:m,reason:e}),d.probeResumeLanding(r.id,m),s.seek(m)}await n()}(e,t,p);u('Content recovery "recreate" requested but the content adapter does not implement recreateMediaPipeline(); resuming in place with seek + play'),e.diagnose("DA-CONTENT-PIPELINE-RECREATE-FAILED",'Content recovery "recreate" requested but the content adapter does not implement recreateMediaPipeline(); resuming in place',{breakId:n.id,startTime:s.currentTime,reason:"unsupported-adapter"})}const f=()=>{o()&&(a&&e.getCallbacks()?.onContentReloading?.(!0),c.applyResumeSeek(n,r),p())};"single-decoder"===d?setTimeout(f,300):f()}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import type { PlayerAdapter } from '../../interfaces/PlayerAdapter';
|
|
1
|
+
import type { PlayerAdapter, TimedMetadataCue } from '../../interfaces/PlayerAdapter';
|
|
2
2
|
import type { Break } from '../../types/BreakManifest';
|
|
3
3
|
import type { Clock } from '../BreakScheduler';
|
|
4
4
|
import type { AdPlayerState } from './AdPlayerState';
|
|
5
|
+
import { type BreakEndOnStreamClock } from './streamClockResume';
|
|
6
|
+
export declare const START_CLAMP_MAX_WINDOW_FRACTION = 0.5;
|
|
5
7
|
export interface ContentResumeCoordinatorDeps {
|
|
6
8
|
contentPlayer: PlayerAdapter;
|
|
7
9
|
clock: Clock;
|
|
@@ -16,19 +18,91 @@ export declare class ContentResumeCoordinator {
|
|
|
16
18
|
private readonly state;
|
|
17
19
|
private readonly deps;
|
|
18
20
|
private readonly continuePlayout;
|
|
21
|
+
/**
|
|
22
|
+
* Resolves a break's frame-accurate in-stream cue position, in seconds of content; null when
|
|
23
|
+
* the SDK has no cue tracker (every timebase except `ptsSource: 'anvatoCue'`).
|
|
24
|
+
*/
|
|
25
|
+
private cueAnchorResolver;
|
|
26
|
+
/** The last resolved resume point before the live-window clamp; null when no seek was resolved. */
|
|
27
|
+
private resumePolicyTarget;
|
|
28
|
+
/** The break's matched Anvato `cue_end`; null without a cue tracker or before it is matched. */
|
|
29
|
+
private cueEndResolver;
|
|
30
|
+
private readonly streamClock;
|
|
19
31
|
constructor(state: AdPlayerState, deps: ContentResumeCoordinatorDeps);
|
|
32
|
+
/**
|
|
33
|
+
* Supply the break's matched Anvato `cue_end` (`ptsSource: 'anvatoCue'`): the stream's own end
|
|
34
|
+
* of the break, used as the resume point in place of the cue plus the duration.
|
|
35
|
+
*/
|
|
36
|
+
setCueEndResolver(resolver: ((breakInfo: Break) => BreakEndOnStreamClock | null) | null): void;
|
|
37
|
+
/** Feed a timed-metadata cue; Anvato beacons map currentTime to the stream clock. */
|
|
38
|
+
ingestStreamClockCue(cue: TimedMetadataCue): void;
|
|
39
|
+
/** The content pipeline was rebuilt: its timeline has to be mapped to the stream clock again. */
|
|
40
|
+
noteContentReload(): void;
|
|
41
|
+
/** Stops the stream-clock landing measurement (controller teardown). */
|
|
42
|
+
dispose(): void;
|
|
43
|
+
/**
|
|
44
|
+
* Supply the in-stream cue position used to anchor the resume in place of the pause position.
|
|
45
|
+
*
|
|
46
|
+
* `null`, or a resolver returning `null`, keeps the pause position, so a stream without
|
|
47
|
+
* in-stream cues is unaffected.
|
|
48
|
+
*/
|
|
49
|
+
setCueAnchorResolver(resolver: ((breakInfo: Break) => number | null) | null): void;
|
|
50
|
+
/**
|
|
51
|
+
* Fix the anchor every resume offset for `breakInfo` is measured from, and reset the per-break
|
|
52
|
+
* resume state around it. Also the drift reference for `continueContentDuringBreak`.
|
|
53
|
+
*
|
|
54
|
+
* Prefers the in-stream cue over the pause position where one matched this break: the slate the
|
|
55
|
+
* ad replaces begins at the cue, and the pause lands after it, so anchoring on the pause carries
|
|
56
|
+
* that latency into the resume target and lands content late by the same amount.
|
|
57
|
+
*/
|
|
58
|
+
captureBreakAnchor(breakInfo: Break): void;
|
|
59
|
+
/**
|
|
60
|
+
* Replace the anchor part-way through a break, when content kept playing until now
|
|
61
|
+
* (picture-in-picture adoption).
|
|
62
|
+
*
|
|
63
|
+
* Clears the cue flag with it: the replacement is a pause position by construction, so the
|
|
64
|
+
* content under the ad is no longer known slate of the advertised length and the ad-played cap
|
|
65
|
+
* has to apply again. Leaving the flag set would resume at `pausedMidBreak + fullDuration`.
|
|
66
|
+
*/
|
|
67
|
+
reanchorMidBreak(position: number): void;
|
|
20
68
|
liveEdgeDistanceSec(): number | null;
|
|
21
69
|
liveSafeSeekEnd(): number | null;
|
|
70
|
+
liveSafeSeekStart(): number | null;
|
|
71
|
+
liveSeekableWindowSec(): number | null;
|
|
22
72
|
contentSeekableEnd(): number | null;
|
|
73
|
+
/**
|
|
74
|
+
* Seconds of published media the anchor sat below the engine's seekable end at break start, or
|
|
75
|
+
* null when that end was unreadable. Positive is slack the resume can spend; zero or negative
|
|
76
|
+
* means the playhead was riding the engine's limit and the resume cannot absorb anything.
|
|
77
|
+
*/
|
|
78
|
+
resumeAnchorCushionSec(): number | null;
|
|
23
79
|
isPlausibleSeekEnd(end: number): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Where the run-up steers content: the stream-clock target once beacons since the run-up's last
|
|
82
|
+
* seek map it, else the matched `cue_end`, else the policy's resume point.
|
|
83
|
+
*/
|
|
24
84
|
plannedResumeTarget(breakInfo: Break): number | null;
|
|
85
|
+
/** The policy's resume point from the anchor, ignoring the stream's own clock. */
|
|
86
|
+
computedResumeTarget(breakInfo: Break): number | null;
|
|
87
|
+
/** Replacement resumes at the break end: the only policy the stream's end of the break can serve. */
|
|
88
|
+
resumesAtBreakEnd(breakInfo: Break): boolean;
|
|
89
|
+
private resolvePolicy;
|
|
90
|
+
/** The matched `cue_end`'s currentTime, when the policy resumes at the break end. */
|
|
91
|
+
private cueEndTarget;
|
|
25
92
|
maybePrepositionContentForResume(): void;
|
|
26
93
|
maybeCatchUpContentForResume(breakInfo: Break): void;
|
|
27
94
|
endResumeCatchup(keepPlaying?: boolean): void;
|
|
28
95
|
get resumeHandoverInMotion(): boolean;
|
|
29
96
|
resumeAlignSummary(): Record<string, unknown>;
|
|
30
97
|
cancelResumeLandedProbe(): void;
|
|
31
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Reports where the resume seek landed (`DA-RESUME-LANDED`) on the next `seeked` event.
|
|
100
|
+
*
|
|
101
|
+
* @param target - The position the player was asked to seek to (after any live-window clamp).
|
|
102
|
+
* @param policyTarget - The resume point before the clamp; the landing is also measured against
|
|
103
|
+
* it, because an exact landing on a clamped seek still leaves the viewer short.
|
|
104
|
+
*/
|
|
105
|
+
probeResumeLanding(breakId: string, target: number, policyTarget?: number | null): void;
|
|
32
106
|
waitForLiveEdge(isCurrent?: () => boolean): Promise<number | null>;
|
|
33
107
|
resetBreakResumeState(): void;
|
|
34
108
|
beginContinueContentPlayout(): void;
|
|
@@ -47,5 +121,10 @@ export declare class ContentResumeCoordinator {
|
|
|
47
121
|
* seek is needed (policy says resume in place, or content was already pre-positioned there).
|
|
48
122
|
*/
|
|
49
123
|
resolveResumeTarget(breakInfo: Break, earlyReturnOffsetSec?: number | null): number | null;
|
|
124
|
+
/**
|
|
125
|
+
* DA-RESUME-CUE-END at the hand-back, when a matched `cue_end` decides the resume without the
|
|
126
|
+
* stream clock (no beacons, or none since the run-up's last seek). Returns its target, or null.
|
|
127
|
+
*/
|
|
128
|
+
private reportCueEnd;
|
|
50
129
|
applyResumeSeek(breakInfo: Break, earlyReturnOffsetSec?: number | null): void;
|
|
51
130
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{Timebase as e,isPostRollBreak as t}from"../../types/BreakManifest";import{resolveResumePoint as s}from"../resolveResumePoint";import{ContinueContentPlayout as n}from"./ContinueContentPlayout";import{contentSeekableEnd as i,isPlausibleSeekEnd as a,liveEdgeDistanceSec as r,liveSafeSeekEnd as o}from"./liveEdge";const u=.04;export class ContentResumeCoordinator{constructor(e,t){this.state=e,this.deps=t,this.continuePlayout=new n(this.state,{contentPlayer:this.deps.contentPlayer,clock:this.deps.clock,gateContentForBreak:()=>this.deps.gateContentForBreak(),distanceToLiveEdgeSec:()=>this.distanceToLiveEdgeSec(),plannedResumeTarget:e=>this.plannedResumeTarget(e),diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),log:(...e)=>this.deps.log(...e)})}liveEdgeDistanceSec(){return r(this.deps.contentPlayer)}liveSafeSeekEnd(){return o(this.deps.contentPlayer)}contentSeekableEnd(){return i(this.deps.contentPlayer)}isPlausibleSeekEnd(e){return a(this.deps.contentPlayer,e)}plannedResumeTarget(e){if(t(e))return null;const n=s({anchor:this.state.contentPositionAtBreakStart,duration:e.duration,adDuration:e.duration,...this.state.adInsertionType?{insertionType:this.state.adInsertionType}:{},..."number"==typeof e.resumeOffset?{resumeOffset:e.resumeOffset}:{}});return n.seek?n.target:null}maybePrepositionContentForResume(){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||null!==this.state.resumePrepositionedTo)return;if(null!==this.state.resumePrepositionSettle)return;if(this.state.resumeCatchupActive)return;if(Number.isFinite(this.deps.contentPlayer.duration))return;const e=this.state.currentBreak;if(!e)return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.contentSeekableEnd();if(null===s)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;let a;if(t<=s)a=t;else{if(!(i<=3))return;a=s}this.deps.log(a===t?`Resume pre-positioning: seeking content to ${t}s during the break (window reaches ${s}s)`:`Resume pre-positioning: window only reaches ${s}s and the ad ends in ${i}s — moving content as close to ${t}s as exists`),this.deps.contentPlayer.seek(a);const r=setTimeout(()=>{this.state.resumePrepositionSettle=null;const e=this.deps.contentPlayer.currentTime;Math.abs(e-a)<=u?(this.state.resumePrepositionedTo=e,this.deps.log(`Resume pre-positioned: content is at ${e}s`)):this.deps.log(`Resume pre-position rejected: asked ${a}s, landed ${e}s — window is short, will retry`)},250);this.state.resumePrepositionSettle=r}maybeCatchUpContentForResume(e){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||Number.isFinite(this.deps.contentPlayer.duration))return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.deps.contentPlayer.currentTime;if(this.state.resumeCatchupActive){const n=(e.duration??0)-(this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec);if(s>t+1)return this.state.resumeCatchupActive=!1,this.deps.contentPlayer.pause(),this.state.resumePrepositionedTo=this.deps.contentPlayer.currentTime,void this.deps.log(`Resume catch-up halted: content reached ${this.deps.contentPlayer.currentTime}s, past the ${t}s target`);if(n<=30&&n>=1){this.state.resumeAlignTicks+=1;const e=s+n,i=e-t;if(Math.abs(i)>u){const a=s-i,r=this.liveSafeSeekEnd();if(null!==r){const e=r>this.state.lastResumeSafeEnd+.25;e&&Number.isFinite(this.state.lastResumeSafeEnd)&&this.state.resumeSawSteps.push(Math.round(100*(r-this.state.lastResumeSafeEnd))/100),r>this.state.lastResumeSafeEnd&&(this.state.lastResumeSafeEnd=r);const s=r-a;if(s>this.state.resumeBestSawMarginSec&&(this.state.resumeBestSawMarginSec=s),a>r)return void(this.state.resumeAlignBlockedDiagnosed||(this.state.resumeAlignBlockedDiagnosed=!0,this.deps.log(`Resume align waiting: ${a}s is past the published edge ${r}s (${Math.round(1e3*(a-r))}ms short) — rolling until a manifest step brings it in reach`),this.deps.diagnose?.("DA-RESUME-ALIGN-BLOCKED",`Resume alignment cannot reach ${t}s yet: the published edge is ${r}s`,{target:t,requested:a,safeEnd:r,shortfallSec:Math.round(1e3*(a-r))/1e3,remainingSec:n})));if(!e&&this.state.resumeSawSeekDone)return;this.state.resumeSawSeekDone=!0}if(this.state.lastResumeAlignRemainingSec-n<.5)return;this.state.lastResumeAlignRemainingSec=n,this.deps.log(`Resume align: landing ${e}s vs target ${t}s (${Math.round(1e3*i)}ms) — content to ${a}s, ${n}s of ad left`),this.state.resumeAlignCorrectionsMs.push(Math.round(1e3*i)),this.deps.contentPlayer.seek(a)}}return}if(t-s<=u)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;if(i<=0)return;if(i>30)return;const a=this.contentSeekableEnd();if(null===a)return;const r=t-i+.3,o=Math.min(r,a);this.state.resumeCatchupActive=!0,this.state.resumeCatchupRan=!0,this.state.contentMutedBeforeCatchup=this.deps.contentPlayer.muted,this.deps.contentPlayer.muted=!0,this.deps.log(`Resume catch-up: content to ${o}s, playing toward ${t}s with ${i}s of ad left`),this.deps.diagnose?.("DA-RESUME-CATCHUP",`Break ${e.id}: playing content from ${o}s toward ${t}s with ${i}s of ad left`,{breakId:e.id,startPos:o,target:t,remainingAdSec:Math.round(1e3*i)/1e3,publishedEnd:a,clampedStart:o<r}),Math.abs(s-o)>u&&this.deps.contentPlayer.seek(o),this.deps.contentPlayer.play()}endResumeCatchup(e=!1){this.state.resumeCatchupActive&&(this.state.resumeCatchupActive=!1,e||this.deps.contentPlayer.pause()),null!==this.state.contentMutedBeforeCatchup&&(this.deps.contentPlayer.muted=this.state.contentMutedBeforeCatchup,this.state.contentMutedBeforeCatchup=null)}get resumeHandoverInMotion(){return this.state.resumeCatchupActive}resumeAlignSummary(){const e=this.state.resumeAlignCorrectionsMs;return{catchUpRan:this.state.resumeCatchupRan||this.state.resumeCatchupActive||e.length>0||null!==this.state.resumePrepositionedTo,alignCorrections:e.length,placedOnStep:this.state.resumeSawSeekDone,alignTicks:this.state.resumeAlignTicks,sawSteps:this.state.resumeSawSteps.slice(0,12),sawStepCount:this.state.resumeSawSteps.length,bestSawMarginSec:Number.isFinite(this.state.resumeBestSawMarginSec)?Math.round(1e3*this.state.resumeBestSawMarginSec)/1e3:null,alignCorrectionsMs:e.slice(0,12),alignLastErrorMs:e.length?e[e.length-1]:null}}cancelResumeLandedProbe(){const e=this.state.resumeLandedProbe;this.state.resumeLandedProbe=null,e?.()}probeResumeLanding(e,t){this.cancelResumeLandedProbe();let s=!1;const n=(n,i)=>{if(s)return;s=!0,this.cancelResumeLandedProbe();const a=null===n?null:Math.round(1e3*(n-t))/1e3;this.deps.log(null===n?`Resume landing unknown for break ${e} (${i})`:`Resume landed at ${n}s, ${a}s from the ${t}s asked for (break ${e})`),this.deps.diagnose?.("DA-RESUME-LANDED",null===n?`Resume landing for break ${e} could not be measured (${i})`:`Resume for break ${e} landed at ${n}s, ${a}s from the ${t}s requested`,{breakId:e,target:t,landed:n,errorSec:a,...i?{reason:i}:{}})},i=()=>n(this.deps.contentPlayer.currentTime),a=setTimeout(()=>n(null,"no seeked event"),2e3);this.state.resumeLandedProbe=()=>{clearTimeout(a),this.deps.contentPlayer.off("seeked",i)},this.deps.contentPlayer.on("seeked",i)}async waitForLiveEdge(e=()=>!0){for(let t=0;t<20;t++){if(!e())return null;const t=this.deps.contentPlayer.videoElement,s=t?.seekable;if(s&&s.length>0){const e=s.end(s.length-1);if(Number.isFinite(e)&&e>0)return e}if(await new Promise(e=>setTimeout(e,100)),!e())return null}return this.deps.log("shared-element: no seekable range after restore; staying at the live edge"),null}resetBreakResumeState(){this.state.resumePrepositionedTo=null,this.state.resumeCatchupActive=!1,this.state.resumeCatchupRan=!1,this.state.contentMutedBeforeCatchup=null,this.state.resumeAlignCorrectionsMs=[],this.state.lastResumeAlignRemainingSec=Number.POSITIVE_INFINITY,this.state.resumeAlignBlockedDiagnosed=!1,this.state.lastResumeSafeEnd=Number.NEGATIVE_INFINITY,this.state.resumeSawSeekDone=!1,this.state.resumeBestSawMarginSec=Number.NEGATIVE_INFINITY,this.state.resumeAlignTicks=0,this.state.resumeSawSteps=[],this.state.resumePrepositionSettle&&(clearTimeout(this.state.resumePrepositionSettle),this.state.resumePrepositionSettle=null),this.state.adSecondsPlayedThisBreak=0,this.state.currentAssetElapsedSec=0}beginContinueContentPlayout(){this.continuePlayout.beginContinueContentPlayout()}distanceToLiveEdgeSec(){const e=this.contentSeekableEnd();return null===e?null:(t=e-this.deps.contentPlayer.currentTime,Math.round(1e3*t)/1e3);var t}secondsUntilCut(e){return this.continuePlayout.secondsUntilCut(e)}restoreContinueQuality(e){this.continuePlayout.restoreContinueQuality(e)}currentContinueDriftSec(){return this.continuePlayout.currentContinueDriftSec()}abandonContinueContentPlayout(e){this.continuePlayout.abandonContinueContentPlayout(e)}endContinueContentPlayout(){this.continuePlayout.endContinueContentPlayout()}setContinueRate(e){this.continuePlayout.setContinueRate(e)}tickContinueContentPlayout(){this.continuePlayout.tickContinueContentPlayout()}resolveResumeTarget(t,n=null){const i=null!==n,a=i?n:this.state.adSecondsPlayedThisBreak,r=s({anchor:this.state.contentPositionAtBreakStart,duration:t.duration,adDuration:a,...i?{}:{insertionType:this.state.adInsertionType},...i?{resumeOffset:n}:"number"==typeof t.resumeOffset?{resumeOffset:t.resumeOffset}:{}});if(r.clamped&&this.deps.diagnose?.("DA-RESUME-OFFSET-CLAMPED",`Resume offset shortened to the ${r.offset}s of ad actually played for break ${t.id}`,{breakId:t.id,offset:r.offset,source:r.source}),!r.seek)return this.deps.log(`Resume: no seek for break ${t.id} (offset 0, ${r.source})`),null;this.deps.log(`Resume-seek to ${r.target}s (offset ${r.offset}s, ${r.source}) for break ${t.id}`);const o=this.resumeAlignSummary();this.deps.diagnose?.("DA-RESUME-SEEK",`Resume-seek to ${r.target}s (offset ${r.offset}s, ${r.source}) for break ${t.id}`,{breakId:t.id,target:r.target,offset:r.offset,source:r.source,...o.catchUpRan?o:{}});(this.state.breakTimebase===e.PTS||this.state.breakTimebase===e.MEDIATIME)&&"number"==typeof t.start&&this.state.callbacks?.onContentResumeSeek?.(r.target,t.start+t.duration);const l=this.liveSafeSeekEnd();let d=r.target,h=0;if(null!==l&&d>l&&(h=Math.round(1e3*(d-l))/1e3,d=l),null!==this.state.resumePrepositionedTo&&Math.abs(this.deps.contentPlayer.currentTime-d)<=u){const e=this.deps.contentPlayer.currentTime;return this.deps.log(`Resume: content already at ${e}s for break ${t.id} (pre-positioned during the break)`+(h>0?` — ${h}s short of policy, those frames are not published yet`:"")+" — no seek"),this.deps.diagnose?.("DA-RESUME-PREPOSITIONED",`Break ${t.id} resumed without a seek: content was moved to ${e}s during the break`+(h>0?` (${h}s short of the ${r.target}s policy target)`:""),{breakId:t.id,target:r.target,prepositionedTo:this.state.resumePrepositionedTo,landed:e,shortfallSec:h,exact:0===h,...this.resumeAlignSummary()}),null}return h>0&&(this.deps.log(`Resume-seek clamped: ${r.target}s is beyond what the stream has published; seeking ${d}s instead (${h}s short)`),this.deps.diagnose?.("DA-RESUME-TARGET-CLAMPED",`Resume target ${r.target}s for break ${t.id} is beyond the published window; clamped to ${d}s`,{breakId:t.id,requested:r.target,clamped:d,shortfallSec:h,...this.resumeAlignSummary()})),d}applyResumeSeek(e,t=null){const s=this.resolveResumeTarget(e,t);null!==s&&(this.probeResumeLanding(e.id,s),this.deps.contentPlayer.seek(s))}}
|
|
1
|
+
import{Timebase as e,isPostRollBreak as t}from"../../types/BreakManifest";import{resolveResumePoint as s}from"../resolveResumePoint";import{ContinueContentPlayout as n}from"./ContinueContentPlayout";import{StreamClockResume as i}from"./streamClockResume";import{contentSeekableEnd as r,isPlausibleSeekEnd as a,liveEdgeDistanceSec as o,liveSafeSeekEnd as u,liveSafeSeekStart as l,liveSeekableWindowSec as d,unreachableEdgeSec as h}from"./liveEdge";export const START_CLAMP_MAX_WINDOW_FRACTION=.5;const c=.04,m=e=>Math.round(1e3*e)/1e3;export class ContentResumeCoordinator{constructor(e,t){this.state=e,this.deps=t,this.cueAnchorResolver=null,this.resumePolicyTarget=null,this.cueEndResolver=null,this.continuePlayout=new n(this.state,{contentPlayer:this.deps.contentPlayer,clock:this.deps.clock,gateContentForBreak:()=>this.deps.gateContentForBreak(),distanceToLiveEdgeSec:()=>this.distanceToLiveEdgeSec(),plannedResumeTarget:e=>this.plannedResumeTarget(e),diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),log:(...e)=>this.deps.log(...e)}),this.streamClock=new i({contentPlayer:this.deps.contentPlayer,diagnose:(e,t,s)=>this.deps.diagnose(e,t,s),activeBreak:()=>this.state.currentBreak,anchorIsCue:()=>this.state.resumeAnchorIsCue,breakEnd:e=>this.cueEndResolver?.(e)??null,computedTarget:e=>this.computedResumeTarget(e),resumesAtBreakEnd:e=>this.resumesAtBreakEnd(e),now:()=>this.deps.clock.now()})}setCueEndResolver(e){this.cueEndResolver=e}ingestStreamClockCue(e){this.streamClock.ingestCue(e)}noteContentReload(){this.streamClock.noteReload()}dispose(){this.streamClock.cancelLanding()}setCueAnchorResolver(e){this.cueAnchorResolver=e}captureBreakAnchor(e){this.resetBreakResumeState();const t=this.cueAnchorResolver?.(e)??null,s=null!==t&&Number.isFinite(t);this.state.contentPositionAtBreakStart=s?t:this.deps.contentPlayer.currentTime,this.state.resumeAnchorIsCue=s,this.state.resumeAnchorSeekableEnd=this.contentSeekableEnd(),this.streamClock.beginBreak()}reanchorMidBreak(e){this.state.contentPositionAtBreakStart=e,this.state.resumeAnchorIsCue=!1,this.state.resumeAnchorSeekableEnd=this.contentSeekableEnd()}liveEdgeDistanceSec(){return o(this.deps.contentPlayer)}liveSafeSeekEnd(){return u(this.deps.contentPlayer)}liveSafeSeekStart(){return l(this.deps.contentPlayer)}liveSeekableWindowSec(){return d(this.deps.contentPlayer)}contentSeekableEnd(){return r(this.deps.contentPlayer)}resumeAnchorCushionSec(){const e=this.state.resumeAnchorSeekableEnd;return null!==e&&Number.isFinite(e)?m(e-this.state.contentPositionAtBreakStart):null}isPlausibleSeekEnd(e){return a(this.deps.contentPlayer,e)}plannedResumeTarget(e){const t=this.computedResumeTarget(e);return null===t?null:this.streamClock.target(e)??this.cueEndTarget(e)??t}computedResumeTarget(e){if(t(e))return null;const s=this.resolvePolicy(e,this.state.contentPositionAtBreakStart);return s.seek?s.target:null}resumesAtBreakEnd(e){if(t(e))return!1;const s=this.resolvePolicy(e,0);return s.seek&&Math.abs(s.offset-(e.duration??0))<.001}resolvePolicy(e,t){return s({anchor:t,duration:e.duration,adDuration:e.duration,...this.state.adInsertionType?{insertionType:this.state.adInsertionType}:{},..."number"==typeof e.resumeOffset?{resumeOffset:e.resumeOffset}:{}})}cueEndTarget(e){if(!this.state.resumeAnchorIsCue||!this.resumesAtBreakEnd(e))return null;const t=this.cueEndResolver?.(e)?.mediaTime;return"number"==typeof t&&Number.isFinite(t)?t:null}maybePrepositionContentForResume(){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||null!==this.state.resumePrepositionedTo)return;if(null!==this.state.resumePrepositionSettle)return;if(this.state.resumeCatchupActive)return;if(Number.isFinite(this.deps.contentPlayer.duration))return;const e=this.state.currentBreak;if(!e)return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.contentSeekableEnd();if(null===s)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;let r;if(t<=s)r=t;else{if(!(i<=3))return;r=s}this.deps.log(r===t?`Resume pre-positioning: seeking content to ${t}s during the break (window reaches ${s}s)`:`Resume pre-positioning: window only reaches ${s}s and the ad ends in ${i}s — moving content as close to ${t}s as exists`),this.streamClock.noteSeek(r),this.streamClock.arm(),this.deps.contentPlayer.seek(r);const a=setTimeout(()=>{this.state.resumePrepositionSettle=null;const e=this.deps.contentPlayer.currentTime;Math.abs(e-r)<=c?(this.state.resumePrepositionedTo=e,this.deps.log(`Resume pre-positioned: content is at ${e}s`)):this.deps.log(`Resume pre-position rejected: asked ${r}s, landed ${e}s — window is short, will retry`)},250);this.state.resumePrepositionSettle=a}maybeCatchUpContentForResume(e){if("shared-element"===this.deps.getInsertionMode())return;if(!this.state.contentPaused||Number.isFinite(this.deps.contentPlayer.duration))return;const t=this.plannedResumeTarget(e);if(null===t)return;const s=this.deps.contentPlayer.currentTime;if(this.state.resumeCatchupActive){const n=(e.duration??0)-(this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec);if(s>t+1)return this.state.resumeCatchupActive=!1,this.deps.contentPlayer.pause(),this.state.resumePrepositionedTo=this.deps.contentPlayer.currentTime,void this.deps.log(`Resume catch-up halted: content reached ${this.deps.contentPlayer.currentTime}s, past the ${t}s target`);if(n<=30&&n>=1){this.state.resumeAlignTicks+=1;const e=s+n,i=e-t;if(Math.abs(i)>c){const r=s-i,a=this.liveSafeSeekEnd();if(null!==a){const e=a>this.state.lastResumeSafeEnd+.25;e&&Number.isFinite(this.state.lastResumeSafeEnd)&&this.state.resumeSawSteps.push(Math.round(100*(a-this.state.lastResumeSafeEnd))/100),a>this.state.lastResumeSafeEnd&&(this.state.lastResumeSafeEnd=a);const s=a-r;if(s>this.state.resumeBestSawMarginSec&&(this.state.resumeBestSawMarginSec=s),r>a)return void(this.state.resumeAlignBlockedDiagnosed||(this.state.resumeAlignBlockedDiagnosed=!0,this.deps.log(`Resume align waiting: ${r}s is past the published edge ${a}s (${Math.round(1e3*(r-a))}ms short) — rolling until a manifest step brings it in reach`),this.deps.diagnose?.("DA-RESUME-ALIGN-BLOCKED",`Resume alignment cannot reach ${t}s yet: the published edge is ${a}s`,{target:t,requested:r,safeEnd:a,shortfallSec:Math.round(1e3*(r-a))/1e3,remainingSec:n})));if(!e&&this.state.resumeSawSeekDone)return;this.state.resumeSawSeekDone=!0}if(this.state.lastResumeAlignRemainingSec-n<.5)return;this.state.lastResumeAlignRemainingSec=n,this.deps.log(`Resume align: landing ${e}s vs target ${t}s (${Math.round(1e3*i)}ms) — content to ${r}s, ${n}s of ad left`),this.state.resumeAlignCorrectionsMs.push(Math.round(1e3*i)),this.streamClock.noteSeek(r),this.deps.contentPlayer.seek(r)}}return}if(t-s<=c)return;const n=this.state.adSecondsPlayedThisBreak+this.state.currentAssetElapsedSec,i=(e.duration??0)-n;if(i<=0)return;if(i>30)return;const r=this.contentSeekableEnd();if(null===r)return;const a=t-i+.3,o=Math.min(a,r);this.state.resumeCatchupActive=!0,this.state.resumeCatchupRan=!0,this.state.contentMutedBeforeCatchup=this.deps.contentPlayer.muted,this.deps.contentPlayer.muted=!0,this.deps.log(`Resume catch-up: content to ${o}s, playing toward ${t}s with ${i}s of ad left`),this.deps.diagnose?.("DA-RESUME-CATCHUP",`Break ${e.id}: playing content from ${o}s toward ${t}s with ${i}s of ad left`,{breakId:e.id,startPos:o,target:t,remainingAdSec:Math.round(1e3*i)/1e3,publishedEnd:r,clampedStart:o<a}),Math.abs(s-o)>c&&(this.streamClock.noteSeek(o),this.deps.contentPlayer.seek(o)),this.streamClock.arm(),this.deps.contentPlayer.play()}endResumeCatchup(e=!1){this.state.resumeCatchupActive&&(this.state.resumeCatchupActive=!1,e||this.deps.contentPlayer.pause()),null!==this.state.contentMutedBeforeCatchup&&(this.deps.contentPlayer.muted=this.state.contentMutedBeforeCatchup,this.state.contentMutedBeforeCatchup=null)}get resumeHandoverInMotion(){return this.state.resumeCatchupActive}resumeAlignSummary(){const e=this.state.resumeAlignCorrectionsMs;return{catchUpRan:this.state.resumeCatchupRan||this.state.resumeCatchupActive||e.length>0||null!==this.state.resumePrepositionedTo,alignCorrections:e.length,placedOnStep:this.state.resumeSawSeekDone,alignTicks:this.state.resumeAlignTicks,sawSteps:this.state.resumeSawSteps.slice(0,12),sawStepCount:this.state.resumeSawSteps.length,bestSawMarginSec:Number.isFinite(this.state.resumeBestSawMarginSec)?Math.round(1e3*this.state.resumeBestSawMarginSec)/1e3:null,alignCorrectionsMs:e.slice(0,12),alignLastErrorMs:e.length?e[e.length-1]:null}}cancelResumeLandedProbe(){const e=this.state.resumeLandedProbe;this.state.resumeLandedProbe=null,e?.()}probeResumeLanding(e,t,s=null){this.cancelResumeLandedProbe();let n=!1;const i=(i,r)=>{if(n)return;n=!0,this.cancelResumeLandedProbe();const a=null===i?null:Math.round(1e3*(i-t))/1e3,o=null===i||null===s?null:Math.round(1e3*(i-s))/1e3,u=null!==s&&Math.abs(s-t)>=5e-4&&null!==o?`; ${Math.abs(o)}s ${o<0?"before":"after"} the ${s}s resume point`:"";this.deps.log(null===i?`Resume landing unknown for break ${e} (${r})`:`Resume landed at ${i}s, ${a}s from the ${t}s asked for${u} (break ${e})`),this.deps.diagnose?.("DA-RESUME-LANDED",null===i?`Resume landing for break ${e} could not be measured (${r})`:`Resume for break ${e} landed at ${i}s, ${a}s from the ${t}s requested${u}`,{breakId:e,target:t,landed:i,errorSec:a,...null===s?{}:{policyTarget:s,policyErrorSec:o},...r?{reason:r}:{}})},r=()=>{this.streamClock.updateLanding(e,this.deps.contentPlayer.currentTime),i(this.deps.contentPlayer.currentTime)},a=setTimeout(()=>i(null,"no seeked event"),2e3);this.state.resumeLandedProbe=()=>{clearTimeout(a),this.deps.contentPlayer.off("seeked",r)},this.deps.contentPlayer.on("seeked",r)}async waitForLiveEdge(e=()=>!0){for(let t=0;t<20;t++){if(!e())return null;const t=this.deps.contentPlayer.videoElement,s=t?.seekable;if(s&&s.length>0){const e=s.end(s.length-1);if(Number.isFinite(e)&&e>0)return e}if(await new Promise(e=>setTimeout(e,100)),!e())return null}return this.deps.log("shared-element: no seekable range after restore; staying at the live edge"),null}resetBreakResumeState(){this.state.resumeAnchorIsCue=!1,this.state.resumeAnchorSeekableEnd=null,this.state.resumePrepositionedTo=null,this.state.resumeCatchupActive=!1,this.state.resumeCatchupRan=!1,this.state.contentMutedBeforeCatchup=null,this.state.resumeAlignCorrectionsMs=[],this.state.lastResumeAlignRemainingSec=Number.POSITIVE_INFINITY,this.state.resumeAlignBlockedDiagnosed=!1,this.state.lastResumeSafeEnd=Number.NEGATIVE_INFINITY,this.state.resumeSawSeekDone=!1,this.state.resumeBestSawMarginSec=Number.NEGATIVE_INFINITY,this.state.resumeAlignTicks=0,this.state.resumeSawSteps=[],this.state.resumePrepositionSettle&&(clearTimeout(this.state.resumePrepositionSettle),this.state.resumePrepositionSettle=null),this.state.adSecondsPlayedThisBreak=0,this.state.currentAssetElapsedSec=0}beginContinueContentPlayout(){this.continuePlayout.beginContinueContentPlayout()}distanceToLiveEdgeSec(){const e=this.contentSeekableEnd();return null===e?null:m(e-this.deps.contentPlayer.currentTime)}secondsUntilCut(e){return this.continuePlayout.secondsUntilCut(e)}restoreContinueQuality(e){this.continuePlayout.restoreContinueQuality(e)}currentContinueDriftSec(){return this.continuePlayout.currentContinueDriftSec()}abandonContinueContentPlayout(e){this.continuePlayout.abandonContinueContentPlayout(e)}endContinueContentPlayout(){this.continuePlayout.endContinueContentPlayout()}setContinueRate(e){this.continuePlayout.setContinueRate(e)}tickContinueContentPlayout(){this.continuePlayout.tickContinueContentPlayout()}resolveResumeTarget(t,n=null){this.resumePolicyTarget=null;const i=null!==n,r=this.state.resumeAnchorIsCue&&!i,a=i?n:r?t.duration:this.state.adSecondsPlayedThisBreak,o=s({anchor:this.state.contentPositionAtBreakStart,duration:t.duration,adDuration:a,...i?{}:{insertionType:this.state.adInsertionType},...i?{resumeOffset:n}:"number"==typeof t.resumeOffset?{resumeOffset:t.resumeOffset}:{}});if(o.clamped&&this.deps.diagnose?.("DA-RESUME-OFFSET-CLAMPED",`Resume offset shortened to the ${o.offset}s of ad actually played for break ${t.id}`,{breakId:t.id,offset:o.offset,source:o.source}),!o.seek)return this.deps.log(`Resume: no seek for break ${t.id} (offset 0, ${o.source})`),null;const u=this.streamClock.handBackTarget(t,i),l=null!==u||i?null:this.reportCueEnd(t,o.target),d=u??l??o.target;this.resumePolicyTarget=d;const m=this.streamClock.seekBasis(t)??(null!==l?"cue_end":`offset ${o.offset}s, ${o.source}`);this.deps.log(`Resume-seek to ${d}s (${m}) for break ${t.id}`);const p=this.resumeAlignSummary(),g=this.liveSafeSeekEnd();this.deps.diagnose?.("DA-RESUME-SEEK",`Resume-seek to ${d}s (${m}) for break ${t.id}`,{breakId:t.id,target:d,offset:o.offset,source:o.source,...d===o.target?{}:{computedTarget:o.target},...null===l?{}:{cueEndTarget:l},...this.streamClock.seekContext(t,d,o.target),anchorPosition:this.state.contentPositionAtBreakStart,anchorIsCue:this.state.resumeAnchorIsCue,anchorSeekableEnd:this.state.resumeAnchorSeekableEnd,anchorCushionSec:this.resumeAnchorCushionSec(),safeEnd:g,seekableEndNow:this.contentSeekableEnd(),liveEdge:this.deps.contentPlayer.liveEdge??null,unreachableEdgeSec:h(this.deps.contentPlayer),...p.catchUpRan?p:{}}),this.streamClock.reportCorrection(t,d,o.target,i);(this.state.breakTimebase===e.PTS||this.state.breakTimebase===e.MEDIATIME)&&"number"==typeof t.start&&this.state.callbacks?.onContentResumeSeek?.(d,t.start+t.duration);let k=d,S=0;null!==g&&k>g&&(S=Math.round(1e3*(k-g))/1e3,k=g);let f=S>0?"end":null;const P=this.liveSafeSeekStart();if(null!==P&&k<P){const e=Math.round(1e3*(P-k))/1e3,s=this.liveSeekableWindowSec(),n=null===s?null:.5*s;null!==n&&e<=n?(S=e,k=P,f="start"):(this.deps.log(`Resume-seek: ${d}s is ${e}s behind the element's live window start (window ${s}s); not clamping`),this.deps.diagnose?.("DA-RESUME-START-CLAMP-SKIPPED",`Resume target ${d}s for break ${t.id} is ${e}s behind the element's seekable start but the gap exceeds half the ${s}s window; left unchanged`,{breakId:t.id,requested:d,seekableStart:P,behindSec:e,windowSec:s,maxBehindSec:n,...this.resumeAlignSummary()}))}if(null!==this.state.resumePrepositionedTo&&Math.abs(this.deps.contentPlayer.currentTime-k)<=c){const e=this.deps.contentPlayer.currentTime;return this.deps.log(`Resume: content already at ${e}s for break ${t.id} (pre-positioned during the break)`+(S>0?` — ${S}s short of policy, those frames are not published yet`:"")+" — no seek"),this.deps.diagnose?.("DA-RESUME-PREPOSITIONED",`Break ${t.id} resumed without a seek: content was moved to ${e}s during the break`+(S>0?` (${S}s short of the ${d}s policy target)`:""),{breakId:t.id,target:d,prepositionedTo:this.state.resumePrepositionedTo,landed:e,shortfallSec:S,exact:0===S,...this.resumeAlignSummary()}),this.streamClock.armLanding(t,e),null}return S>0&&(this.deps.log("start"===f?`Resume-seek clamped: ${d}s is behind the live window start (already evicted); seeking ${k}s instead (${S}s early)`:`Resume-seek clamped: ${d}s is beyond what the stream has published; seeking ${k}s instead (${S}s short)`),this.deps.diagnose?.("DA-RESUME-TARGET-CLAMPED","start"===f?`Resume target ${d}s for break ${t.id} is behind the live window start (already evicted); clamped to ${k}s`:`Resume target ${d}s for break ${t.id} is beyond the published window; clamped to ${k}s`,{breakId:t.id,requested:d,clamped:k,shortfallSec:S,bound:f,...this.resumeAlignSummary()})),this.streamClock.armLanding(t,k),k}reportCueEnd(e,t){const s=this.state.resumeAnchorIsCue?this.cueEndResolver?.(e):null;if(!s||!Number.isFinite(s.mediaTime))return null;const n=this.resumesAtBreakEnd(e),i=m(s.mediaTime-t);return this.deps.diagnose("DA-RESUME-CUE-END",n?`Break ${e.id}: resuming on the stream's cue_end at ${s.mediaTime}s (${i}s from the computed ${t}s)`:`Break ${e.id}: cue_end at ${s.mediaTime}s not used; the resume policy does not resume at the break end`,{breakId:e.id,computedTarget:t,cueEndTarget:s.mediaTime,deltaSec:i,applied:n,stage:"hand-back",corrected:!1}),n?s.mediaTime:null}applyResumeSeek(e,t=null){const s=this.resolveResumeTarget(e,t);null!==s&&(this.probeResumeLanding(e.id,s,this.resumePolicyTarget),this.streamClock.noteSeek(s),this.deps.contentPlayer.seek(s))}}
|
|
@@ -57,10 +57,17 @@ export declare class GamPodBridge {
|
|
|
57
57
|
resetGamPodTracking(): void;
|
|
58
58
|
onGamPodAdProgress(currentTime: number): void;
|
|
59
59
|
onGamPodAdStarted(info?: GamPodAdInfo): void;
|
|
60
|
+
private logRestartedPodPosition;
|
|
61
|
+
private onAdBreakEndedWithPodLeft;
|
|
60
62
|
emitGamPodSteps(steps: GamPodAdStep[]): void;
|
|
61
63
|
emitGamQuartile(callback: ((breakInfo: Break, asset: Asset, adIndex?: number, totalAds?: number) => void) | undefined): void;
|
|
62
64
|
buildGamUrl(asset: VendorAsset, breakDuration: number): Promise<string | null>;
|
|
63
65
|
processTimedMetadata(cue: TimedMetadataCue): void;
|
|
64
66
|
destroy(): void;
|
|
67
|
+
/** Whether the last pod URL build for `asset` was refused for a session identity mismatch. */
|
|
68
|
+
hasIdentityMismatch(asset: Asset): boolean;
|
|
69
|
+
private identityMismatchAssetId;
|
|
70
|
+
/** Last ad position before a mid-pod `AD_BREAK_ENDED`, to spot IMA restarting positions. */
|
|
71
|
+
private midBreakEndPosition;
|
|
65
72
|
private callbacks;
|
|
66
73
|
}
|