@dolby-optiview/ads-sdk 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -9,6 +9,11 @@ of the public contract and are never renamed.
|
|
|
9
9
|
|
|
10
10
|
| Code | Category | Level | Meaning |
|
|
11
11
|
|---|---|---|---|
|
|
12
|
+
| `DA-BOOKMARK-START` | break | info | VOD bookmark start skipped earlier breaks. context: position, skippedBreakIds. |
|
|
13
|
+
| `DA-CONTENT-END-INFERRED` | playback | info | Content end inferred from the VOD playhead. context: currentTime, duration, trigger (past-end or stalled-at-end). |
|
|
14
|
+
| `DA-SNAPBACK-APPLIED` | break | info | Viewer seek corrected to an unplayed VOD snapback break. context: breakId, target, resumePosition (seek-over of a content-pausing format only). |
|
|
15
|
+
| `DA-SNAPBACK-ABANDONED` | break | warn | VOD snapback correction abandoned after one retry or because the break became ineligible. context: breakId, reason. |
|
|
16
|
+
| `DA-SNAPBACK-NOT-APPLIED` | break | debug | Viewer seek was not eligible for VOD snapback. context: reason, breakId (when known). |
|
|
12
17
|
| `DA-SESSION-STARTED` | session | info | Monetization session started; manifest polling began. context: manifestUrl (stitcherUrl in SSAI mode), reported verbatim. |
|
|
13
18
|
| `DA-SESSION-ENDED` | session | info | Monetization session ended; polling and scheduling stopped. |
|
|
14
19
|
| `DA-ASSET-PARAMETERS-UPDATED` | session | info | The break manifest's session-level asset parameters changed; the merged parameters were pushed to the active GAM stream. context: source ('manifest'), keys (parameter names only, never values). |
|
|
@@ -19,6 +24,7 @@ of the public contract and are never renamed.
|
|
|
19
24
|
| `DA-MANIFEST-REQUEST-INTERCEPT-FAILED` | manifest | warn | The customer manifest-request interceptor threw; the normal network fetch of the original URL was used instead. |
|
|
20
25
|
| `DA-MANIFEST-SIGNATURE-INVALID` | manifest | error | The break-manifest response failed detached-JWS signature verification (missing/malformed X-Manifest-Signature, unsupported alg, unknown kid, unknown critical param, or signature mismatch); the manifest was rejected and no breaks were scheduled. context: url, reason. |
|
|
21
26
|
| `DA-MANIFEST-EVENT-INVALID` | manifest | warn | A channel event was ignored because its time window or configuration is invalid, or because events are unsupported for the manifest timebase. context: eventId and index when available. |
|
|
27
|
+
| `DA-SLATE-INVALID` | manifest | warn | The manifest's slate was ignored because it is unusable: not an object, an invalid minDuration, no assets, or an invalid first asset (the only one the player plays). The breaks of that manifest are unaffected and play without a slate. Reported once per distinct fault. context: reason (not-object, invalid-min-duration, no-assets, invalid-asset), assetId when the first asset has one. |
|
|
22
28
|
| `DA-PDT-MISSING` | playback | warn | No EXT-X-PROGRAM-DATE-TIME once content is playing (or after the startup backstop window); wallclock-timebase breaks fall back to the system clock. |
|
|
23
29
|
| `DA-ANVATO-CUE-PENDING` | break | info | ptsSource: 'anvatoCue' — no Anvato in-stream cue observed yet for a PTS break; the break stays pending until its cue arrives. |
|
|
24
30
|
| `DA-ANVATO-CUE-MATCHED` | break | info | ptsSource: 'anvatoCue' — a PTS break's start was matched to an observed Anvato cue and scheduled at the cue's media time. |
|
|
@@ -29,6 +35,7 @@ of the public contract and are never renamed.
|
|
|
29
35
|
| `DA-ANVATO-CUE-END-MATCHED` | break | info | ptsSource: 'anvatoCue' — the active break's Anvato type=cue_end was observed and its length on the stream clock (cue_end pts minus the break start pts) is within 2s of the break duration. Emitted once per break; context: breakId, mediaTime (where the player reported the cue_end), lengthSec, durationSec. |
|
|
30
36
|
| `DA-ANVATO-CUE-END-REJECTED` | break | warn | ptsSource: 'anvatoCue' — an Anvato type=cue_end was observed for the active break but its stream-clock length differs from the break duration by more than 2s, so it is ignored and the resume keeps its computed target. Emitted once per break; context: breakId, mediaTime, lengthSec, durationSec. |
|
|
31
37
|
| `DA-PRELOAD-FAILED` | preload | warn | Ad preload failed; the asset will be loaded on demand at break start. |
|
|
38
|
+
| `DA-SLATE-PRELOADED` | preload | info | The manifest's slate asset was fetched ahead of its first use, on its first appearance in the manifest or when its URL changed, so a break that needs it starts it warm. Best-effort: a failed warm-up never fails a break. context: slateAssetId, mediaType, path (the preload path taken). |
|
|
32
39
|
| `DA-PRELOAD-MODE-RESOLVED` | preload | info | The configured ad preload mode was resolved for this session; context carries both the configured and the resolved mode. |
|
|
33
40
|
| `DA-PRELOAD-PATH` | preload | info | The preload path actually taken for a break's ad (parallel buffer / detached prefetch / first-frame warm-up on the ad player / none) and the creative container (stream vs progressive). |
|
|
34
41
|
| `DA-BREAK-EARLY-RETURN` | break | info | The active break was removed from a polled manifest (early return); the break ends immediately and content resumes at break start plus the seconds already played. |
|
|
@@ -44,7 +51,9 @@ of the public contract and are never renamed.
|
|
|
44
51
|
| `DA-PREROLL-CANCELLED-CONTENT-ERROR` | break | warn | A pending pre-roll was cancelled because the content failed to load before it could begin. |
|
|
45
52
|
| `DA-SHARED-ELEMENT-UNSUPPORTED` | break | warn | Configured shared-element insertion is unsupported on this platform (non-iPhone/iPod); fell back to overlay insertion. |
|
|
46
53
|
| `DA-SHARED-ELEMENT-RESTORE-FAILED` | playback | error | Content did not resume after a shared-element break: the reload/seek/play restore failed twice; the viewer is left paused on the handed-back element. |
|
|
47
|
-
| `DA-IMAGE-LOAD-FAILED` | break | warn | An image (companion, backdrop or image ad) could not be fetched or decoded within the timeout; the break continues without it. |
|
|
54
|
+
| `DA-IMAGE-LOAD-FAILED` | break | warn | An image (companion, backdrop or image ad) could not be fetched or decoded within the timeout, or exceeded the size limit; the break continues without it. |
|
|
55
|
+
| `DA-IMAGE-ANIMATED` | break | debug | An animated image (GIF or animated WebP) started animating on a companion, backdrop, image ad or pause image. Native renderers only: browsers animate the image element themselves. context: surface ('companion', 'backdrop', 'image-ad' or 'pause'), uri, frameCount (iOS/tvOS; null on Android). |
|
|
56
|
+
| `DA-IMAGE-ANIMATION-UNSUPPORTED` | break | warn | An image is animated but this device cannot animate it, so its first frame is shown. context: surface, uri, reason ('api-below-28' on Android below API 28; 'canvas-too-large' on iOS/tvOS when the animation exceeds the decode budget). |
|
|
48
57
|
| `DA-PIP-TRANSFER-FAILED` | break | warn | The picture-in-picture window could not be moved between the content and ad elements; the break continues but the viewer may not see the ad in the PiP window. |
|
|
49
58
|
| `DA-PIP-AUTODETECT-UNAVAILABLE` | break | warn | No Activity could be resolved from the renderer's Context or the overlay's view tree, so picture-in-picture cannot be detected automatically; the host must call OptiViewAds.setPictureInPicture() from Activity.onPictureInPictureModeChanged or breaks will be laid out for a fullscreen surface. Android only. |
|
|
50
59
|
| `DA-CONTENT-ELEMENT-SWAPPED` | break | info | The content player replaced its <video> element; the SDK re-bound its presentation listeners to the new one. |
|
|
@@ -68,7 +77,13 @@ of the public contract and are never renamed.
|
|
|
68
77
|
| `DA-EXTERNAL-VENDOR-DUPLICATE` | manifest | warn | External vendorConfiguration entries repeated a provider; the first valid entry was used. context: providers. |
|
|
69
78
|
| `DA-CONTENT-PLAYBACK-ERROR` | playback | error | The content player reported a playback error. |
|
|
70
79
|
| `DA-EXTERNAL-PLAYBACK-CHANGED` | playback | info | The content player's external (AirPlay) playback state changed; context carries the new state and the target. |
|
|
71
|
-
| `DA-EXTERNAL-PLAYBACK-BLOCKED` | playback | warn | AirPlay was requested while an ad break is active or about to start; the content player stays on the device and the request is retried when the break ends. |
|
|
80
|
+
| `DA-EXTERNAL-PLAYBACK-BLOCKED` | playback | warn | AirPlay was requested while an ad break is active or about to start; the content player stays on the device and the request is retried when the break ends. On iOS the running ad plays on the receiver meanwhile. |
|
|
81
|
+
| `DA-AD-ROUTE-CHANGED` | playback | info | iOS: the audio route changed while an ad break played; context carries the route change reason, the current and previous outputs and a snapshot of the ad player (time control status, waiting reason, rate, buffer flags, external playback, ad and content state). |
|
|
82
|
+
| `DA-AD-TIMECONTROL` | playback | info | iOS: the ad player's timeControlStatus or reasonForWaitingToPlay changed during an ad break; context.field names which, with the ad player snapshot. |
|
|
83
|
+
| `DA-AD-ITEM-STALLED` | playback | warn | iOS: the ad player's current item reported a playback stall during an ad break; context carries the ad player snapshot. |
|
|
84
|
+
| `DA-AD-AUDIO-INTERRUPTION` | playback | warn | iOS: an audio-session interruption began or ended during an ad break (context.type, options, wasSuspended) with the ad player snapshot. |
|
|
85
|
+
| `DA-AD-INTERRUPTION-RESUMED` | playback | warn | iOS: an ad paused by an audio-session interruption was restarted after the audio session was reactivated; context.trigger is interruption-ended, route-changed, player-paused or retry, stalledSec how long the ad was stopped. |
|
|
86
|
+
| `DA-AD-AUDIO-SESSION-REACTIVATE-FAILED` | playback | warn | iOS: reactivating the audio session before restarting an interrupted ad failed (context.errorCode, errorDomain, trigger); the SDK retries once a second, up to five times. |
|
|
72
87
|
| `DA-SSB-SWITCH-FAILED` | playback | warn | Switching the content player to the SSB stream for AirPlay failed (context.reason: no-restore-source, load-error, load-timeout, external-not-active or receiver-paused); the SDK restores the previous content. |
|
|
73
88
|
| `DA-SSB-RESTORE-FAILED` | playback | error | The previous content source could not be reloaded after the SSB stream (AirPlay ended or the switch failed); the session stays in ssb delivery until the app reloads its content. |
|
|
74
89
|
| `DA-EXTERNAL-PLAYBACK-ALLOWED` | playback | info | An AirPlay request passed the ad-break guard; context.trigger says whether it was the request itself or the retry after a break. |
|
|
@@ -108,6 +123,7 @@ of the public contract and are never renamed.
|
|
|
108
123
|
| `DA-VAST-REQUEST-FAILED` | gam | error | The VAST ad request failed (network error, invalid tag URL, wrapper error or too many redirects); reported as CMCD adec. |
|
|
109
124
|
| `DA-VAST-CREATIVE-FAILED` | gam | error | The VAST creative could not be played (no compatible media file, playback error or trafficking error); reported as CMCD adec. |
|
|
110
125
|
| `DA-VAST-IMA-SDK-MISSING` | gam | error | The Google IMA client-side SDK (ima3.js) is unavailable; VAST (CSAI) ads cannot be played. |
|
|
126
|
+
| `DA-PAUSE-AD-ROTATION-EXHAUSTED` | break | info | No unused pause breaks remain in the rotation; nothing was shown. |
|
|
111
127
|
| `DA-PAUSE-AD-SHOWN` | break | info | A pause ad (full-screen image or video) was shown while content is paused. |
|
|
112
128
|
| `DA-PAUSE-AD-DISMISSED` | break | info | A pause ad was dismissed (content resumed, or the duration cap elapsed). |
|
|
113
129
|
| `DA-PAUSE-AD-RESUME-ALLOWED` | break | info | The viewer may now resume content over the shown pause ad: the break's controls.skipOffset elapsed, or (no skipOffset) the ad was watched fully: a video creative ended, an image reached its duration cap or has none. context: breakId. |
|
|
@@ -151,8 +167,13 @@ of the public contract and are never renamed.
|
|
|
151
167
|
| `DA-EVENT` | event | info | An SDK event, recorded into the diagnostic ring buffer so exportDiagnostics() carries one unified timeline of diagnostics and playback events. Raised to error for aderror. Buffer-only: not dispatched to onDiagnostic handlers, which would duplicate the event stream they already receive. |
|
|
152
168
|
| `DA-BREAK-CONTENT-MASK` | break | warn | The host's content surface cannot follow view transforms (punch-through SurfaceView, e.g. react-native-video's default), so the two-box format masks the backdrop with a transparent hole over the content box instead of reframing the content view. context: format. |
|
|
153
169
|
| `DA-BREAK-FORMAT-DEGRADED` | break | warn | Split break reframing degraded: the host supplied no content view or sibling container for the ad. |
|
|
154
|
-
| `DA-
|
|
170
|
+
| `DA-SLATE-STARTED` | break | info | The slate began playing in a break because no ad covered the rest of it; it plays until the break's scheduled end (adbreakbegin plus the effective duration) and then the break ends as any other. No adbegin/adend is emitted for it. context: breakId, slateAssetId, reason (ads-ended: the last ad ended before the break end; no-ad: no ad could play), durationSec (the real time left to the scheduled end, so time lost to failed ad attempts and start-up shortens it), adsPlayedSec, effectiveDurationSec, minDurationSec. |
|
|
171
|
+
| `DA-SLATE-NOT-STARTED` | break | info | A break with a slate left part of its time uncovered but the slate did not start: less than the slate's minDuration remained (below-min-duration), or the stitched stream fills the break (not-rendered: a time-based break under ssai delivery, or while an SSB stream plays). The break ends as without a slate. context: breakId, reason, remainingSec, minDurationSec. |
|
|
172
|
+
| `DA-SLATE-ENDED` | break | info | The slate stopped playing. context: breakId, slateAssetId, plannedSec (the real time left to the break's scheduled end when the slate started), filledSec (seconds the slate was on screen: start-up, stalls and loop restarts shorten it, never the break), loops (completed plays of a video slate), interruptedSec (time it was not presenting), breakOverrunSec (the slate's end minus the scheduled end, signed: about 0 at break-end, negative when ended early), reason (break-end: the scheduled end was reached; skipped, removed, resumed: ended early). |
|
|
173
|
+
| `DA-SLATE-FAILED` | playback | error | The slate could not load or play (load error, or it did not present within the ad-start budget); the break ends as without a slate and a public aderror carries the slate asset. context: breakId, slateAssetId, reason (load, stalled), error. |
|
|
174
|
+
| `DA-BREAK-NO-PLAYABLE-ASSET` | break | warn | A break had nothing to play, so it never began: no adbreakbegin/adbreakend was emitted, content kept playing, and a public aderror (source ad, carrying the break) reports it. When the manifest carries a slate that fills the break (reason no-assets only), the break does begin and the slate covers it: the aderror and this diagnostic are still reported, then DA-SLATE-STARTED follows. context.reason says why: no-variant (none declared), no-qualifying-variant (variant selection - device targeting + format playability - matched nothing and no downgrade applied), no-assets (the selected variant was empty), or lshape-content-skipped (shared-element insertion cannot render a backdrop). This is the BREAK-level outcome; when a particular variant was passed over for an unrecognised format, DA-BREAK-VARIANT-FORMAT-UNKNOWN names that format alongside it. context: breakId, reason, declaredAssetCount, format. |
|
|
155
175
|
| `DA-BREAK-VARIANT-FORMAT-UNKNOWN` | break | warn | A break variant declares a format this SDK version does not recognise, so it was passed over during variant selection rather than guessed at - an unknown format is never playable, because rendering it as a fullscreen single would be the most invasive possible misreading. Raised once per skipped variant: a break that still has a playable variant plays it and reports this too, so the skip is never silent, and a break left with nothing also reports DA-BREAK-NO-PLAYABLE-ASSET with reason no-qualifying-variant. An ABSENT format is treated identically to an unrecognised one. context: breakId, format (the raw manifest string; empty when the field was absent). |
|
|
176
|
+
| `DA-BREAK-LSHAPE-ANCHOR-UNKNOWN` | break | warn | An lshape_ad or lshape_content variant declares an anchor this SDK version does not recognise (manifest spec 1.11.0 knows top_left, top_right, bottom_left and bottom_right), so the break still plays with the video box in the default top_left corner. Raised once per break begin on the selected variant. context: breakId, format, anchor (the raw manifest string). |
|
|
156
177
|
| `DA-AD-STALLED` | playback | warn | An ad began playing and then stopped advancing: the playhead did not move for the stall threshold while the ad was meant to be playing. context: assetId, breakId, playbackState, stalledForMs, positionMs, bufferedAheadMs, reason. |
|
|
157
178
|
| `DA-AD-FOREGROUND-RESUMED` | playback | info | An in-flight ad resumed after the app returned to the foreground; context carries the suspended duration and ad position at resume. |
|
|
158
179
|
| `DA-AD-FOCUS-LOST` | playback | warn | The overlay ad lost permanent audio focus during an active break; the host player must not reclaim focus while the SDK ad is on screen. context: breakId, transient, reason. |
|
|
@@ -162,3 +183,5 @@ of the public contract and are never renamed.
|
|
|
162
183
|
| `DA-BREAK-ABANDONED` | lifecycle | warn | A break was still in flight when the session was torn down (endSession, destroy, or a startSession over an active session). context: breakId, cause, phase, unbalancedAdBegin, adIndex, totalAds, assetId. |
|
|
163
184
|
| `DA-RENDERER-UNAVAILABLE` | lifecycle | error | The renderer could not render a break because a required piece of it was not attached (e.g. no ad player), after the SDK had already emitted adbreakbegin — so the break began and will never end. context: breakId, missing. |
|
|
164
185
|
| `DA-SESSION-STARTING` | session | info | A step of startSession() was entered: begin, manifest-fetch, gam-init or scheduling. Emitted on entry, so the last one recorded names the step a hung startSession never finished. context: step, manifestUrl, elapsedMs. The URL is reported verbatim; the redacted config summary carries only its origin (config.manifestOrigin). |
|
|
186
|
+
| `DA-MANIFEST-URL-DISCOVERED` | session | info | startSession() was called without a manifestUrl and the SDK started the session with the ads manifest URL the content player discovered (e.g. THEOlive discovery). context: manifestUrl (reported verbatim), source ('player-discovery'). |
|
|
187
|
+
| `DA-MANIFEST-URL-UNAVAILABLE` | session | error | startSession() was called without a manifestUrl and no ads manifest URL could be resolved from the player. context: reason ('disabled': the manifestUrlDiscovery config flag is off, 'unsupported-adapter': the adapter cannot discover one, 'no-ads': the player reported none, 'timeout': still pending after the discovery wait). |
|
|
@@ -12,6 +12,22 @@ the sequence leading up to a failure.
|
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
15
|
+
## DA-BOOKMARK-START
|
|
16
|
+
|
|
17
|
+
Expected saved-position behavior. Compare `position` and `skippedBreakIds` with the intended start position. See [Start VOD from a bookmark](../../../demo/docs/07-session.md#vod-bookmark-start) for player setup and subsequent seek behavior.
|
|
18
|
+
|
|
19
|
+
## DA-SNAPBACK-APPLIED
|
|
20
|
+
|
|
21
|
+
Expected VOD seek protection: the last crossed snapback break was selected; an already-played or unplayable last break never falls back to an earlier crossed break. Compare `target` with the break start. `resumePosition` is set only for seek-over of content-pausing Single or L-Shape Ad, including fullscreen fallbacks at break start. It overrides insertion policy and `resumeOffset`. Double Box, L-Shape Content and Overlay omit it: content keeps playing and plays on without a seek, using normal chaining. Confirm the landing and balanced break lifecycle; a correction request alone does not prove playback succeeded. Backward seeks into an unplayed window use normal resume policy.
|
|
22
|
+
|
|
23
|
+
## DA-SNAPBACK-ABANDONED
|
|
24
|
+
|
|
25
|
+
The content player did not land after one corrective retry, or the selected break became ineligible. Check the adapter's seek implementation, seekable range and competing host seeks. The SDK releases the correction instead of trapping playback in a loop.
|
|
26
|
+
|
|
27
|
+
## DA-SNAPBACK-NOT-APPLIED
|
|
28
|
+
|
|
29
|
+
Check the diagnostic `reason`, VOD mediatime scheduling, `controls.snapback`, playable variants and whether the break already started in this session. Live, event-triggered, SSAI/SSB and excluded single-decoder web platforms are unchanged.
|
|
30
|
+
|
|
15
31
|
## DA-SESSION-STARTED
|
|
16
32
|
|
|
17
33
|
Informational. The session started and manifest polling began. Use it to confirm
|
|
@@ -31,6 +47,20 @@ the session does start, `context.elapsedMs` on each marker gives a per-step cost
|
|
|
31
47
|
breakdown, so a _slow_ start is attributable without adding instrumentation.
|
|
32
48
|
`DA-SESSION-STARTED` marks completion.
|
|
33
49
|
|
|
50
|
+
## DA-CONTENT-END-INFERRED
|
|
51
|
+
|
|
52
|
+
**Cause:** Informational. The SDK inferred VOD content end from the playhead
|
|
53
|
+
to arm a pending post-roll when the player did not fire `ended`. Inference requires
|
|
54
|
+
an adapter reporting VOD (`isLive: false`); unknown live status, including native
|
|
55
|
+
video web adapters that omit it, relies on the real `ended` event. Context carries
|
|
56
|
+
`currentTime`, `duration` and `trigger`: `past-end` means within 0.25 seconds (0.05 seconds while paused) of
|
|
57
|
+
duration or beyond it; `stalled-at-end` means no meaningful progress for two
|
|
58
|
+
seconds within the final second while not paused. **Fix:** No action needed if
|
|
59
|
+
content has ended. Otherwise verify the adapter's `duration`, `currentTime` and
|
|
60
|
+
`isLive` values. A later `ended` does not trigger another post-roll. A pending
|
|
61
|
+
post-roll is cancelled only after a backward replay beyond one second from the
|
|
62
|
+
recorded end position, or on playback start when that position is unknown.
|
|
63
|
+
|
|
34
64
|
## DA-SESSION-ENDED
|
|
35
65
|
|
|
36
66
|
Informational. Polling and scheduling stopped (via `endSession()`/`destroy()`).
|
|
@@ -121,6 +151,41 @@ nothing (or the unchanged request) when no modification is needed. Use a
|
|
|
121
151
|
`ManifestMockResponse` (raw body) to short-circuit the network for tests. See the
|
|
122
152
|
`interceptManifestRequest` configuration docs.
|
|
123
153
|
|
|
154
|
+
## DA-MANIFEST-URL-DISCOVERED
|
|
155
|
+
|
|
156
|
+
**Cause:** Informational — `startSession()` was called without a `manifestUrl`
|
|
157
|
+
and the SDK started the session on the ads manifest URL the content player
|
|
158
|
+
discovered (`context.source: 'player-discovery'`; `context.manifestUrl` is the
|
|
159
|
+
URL verbatim). Typical source: THEOlive discovery via the THEOplayer adapter.
|
|
160
|
+
**Fix:** No action needed; verify the reported URL is the ads manifest you
|
|
161
|
+
expect for the loaded channel.
|
|
162
|
+
|
|
163
|
+
## DA-MANIFEST-URL-UNAVAILABLE
|
|
164
|
+
|
|
165
|
+
**Cause:** `startSession()` was called without a `manifestUrl` and no ads
|
|
166
|
+
manifest URL could be used, so no session started. `context.reason` names the
|
|
167
|
+
cause: `disabled` — the `manifestUrlDiscovery` config flag is off;
|
|
168
|
+
`unsupported-adapter` — the player adapter does not expose manifest
|
|
169
|
+
discovery; `no-ads` — the player's discovery completed but reported no ads
|
|
170
|
+
manifest URL; `timeout` — discovery stayed pending past the wait window.
|
|
171
|
+
**Fix:** For `disabled`, set `manifestUrlDiscovery: true` or pass `manifestUrl`
|
|
172
|
+
explicitly. For `unsupported-adapter`, pass `manifestUrl` explicitly or use an
|
|
173
|
+
adapter/player combination that discovers one (e.g. THEOplayer with a THEOlive
|
|
174
|
+
source). For `no-ads`, check the channel's upstream ads configuration. For
|
|
175
|
+
`timeout`, retry once the source has loaded or pass `manifestUrl` explicitly.
|
|
176
|
+
|
|
177
|
+
## DA-SLATE-INVALID
|
|
178
|
+
|
|
179
|
+
**Cause:** The manifest's `slate` was ignored because it is unusable, and its
|
|
180
|
+
breaks play without one; the breaks themselves are unaffected. `context.reason`
|
|
181
|
+
says why: `not-object` (the slate must be an object), `invalid-min-duration`
|
|
182
|
+
(`minDuration` must be a number >= 0), `no-assets` (`assets` must be a non-empty
|
|
183
|
+
array) or `invalid-asset` (the FIRST asset, the only one the player uses, must be
|
|
184
|
+
a `static` video or image with an `id`, a `mediaType` and a `uri`; `context.assetId`
|
|
185
|
+
names it). **Fix:** Correct the slate in the manifest. A slate asset carries no
|
|
186
|
+
`duration`: the slate fills whatever the ads left of the break. Reported once per
|
|
187
|
+
distinct fault, not once per poll. Non-fatal.
|
|
188
|
+
|
|
124
189
|
## DA-PDT-MISSING
|
|
125
190
|
|
|
126
191
|
**Cause:** The stream exposed no `EXT-X-PROGRAM-DATE-TIME` once content was
|
|
@@ -701,6 +766,56 @@ or single-decoder prefetch limits. **Fix:** Verify the ad asset URL is reachable
|
|
|
701
766
|
with correct CORS. On older single-decoder TVs confirm `adPreload: 'single-decoder'`
|
|
702
767
|
or `'auto'`. Non-fatal — playback still proceeds.
|
|
703
768
|
|
|
769
|
+
## DA-SLATE-PRELOADED
|
|
770
|
+
|
|
771
|
+
**Cause:** Informational. The manifest's slate asset was fetched ahead of its first use,
|
|
772
|
+
on its first appearance in the manifest or when its URL changed, so a break that needs it
|
|
773
|
+
starts it warm. `context` carries `slateAssetId`, `mediaType` and the preload `path`
|
|
774
|
+
taken. **Fix:** None. Best-effort: a failed warm-up never fails a break, and
|
|
775
|
+
`adPreload: 'none'` skips it.
|
|
776
|
+
|
|
777
|
+
## DA-SLATE-STARTED
|
|
778
|
+
|
|
779
|
+
**Cause:** Informational. The slate began because no ad covered the rest of the break.
|
|
780
|
+
`context.reason` is `ads-ended` (the last ad ended before the break end; `adsPlayedSec`
|
|
781
|
+
of `effectiveDurationSec`) or `no-ad` (nothing could play). `durationSec` is the real time
|
|
782
|
+
left to the break's scheduled end, so time lost to an ad that never played or to ad start-up
|
|
783
|
+
shortens it. The slate plays until the scheduled end and then the break ends as any other; it
|
|
784
|
+
emits no `adbegin`/`adend`, so ad counts and ad analytics are unaffected. **Fix:** None.
|
|
785
|
+
If you expected a slate and see none, look for `DA-SLATE-NOT-STARTED` or
|
|
786
|
+
`DA-SLATE-INVALID`.
|
|
787
|
+
|
|
788
|
+
## DA-SLATE-NOT-STARTED
|
|
789
|
+
|
|
790
|
+
**Cause:** Informational. The manifest has a slate and part of the break was uncovered,
|
|
791
|
+
but the slate did not start. `below-min-duration`: less than the slate's `minDuration`
|
|
792
|
+
(default 5 seconds) remained (`context.remainingSec`), so the break returned to content
|
|
793
|
+
as it always did. `not-rendered`: the stitched stream fills the break (a time-based
|
|
794
|
+
break under `ssai` delivery, or while an SSB stream plays), so a client slate would
|
|
795
|
+
cover real content. **Fix:** To fill shorter gaps lower `minDuration` in the manifest
|
|
796
|
+
(`0` starts the slate for any remaining time).
|
|
797
|
+
|
|
798
|
+
## DA-SLATE-ENDED
|
|
799
|
+
|
|
800
|
+
**Cause:** Informational. The slate stopped. `plannedSec` is the real time that was left to
|
|
801
|
+
the break's scheduled end when the slate started; `filledSec` is how long it was on screen.
|
|
802
|
+
`breakOverrunSec` is the slate's end minus the scheduled end: about 0 with `reason:
|
|
803
|
+
break-end` means the break ended on schedule, negative with `skipped`, `removed` or
|
|
804
|
+
`resumed` means it was ended early on purpose. `interruptedSec` is time the slate was not
|
|
805
|
+
presenting (start-up, stalls, loop seams, app in the background); it shortens the slate
|
|
806
|
+
and never moves the break end. `loops` is how many times a video slate completed.
|
|
807
|
+
**Fix:** None. A very short slate asset loops often: prefer one a few seconds long or
|
|
808
|
+
longer.
|
|
809
|
+
|
|
810
|
+
## DA-SLATE-FAILED
|
|
811
|
+
|
|
812
|
+
**Cause:** The slate could not load or play, so the break ended as it would without a
|
|
813
|
+
slate (the viewer returns to content early) and a public `aderror` carrying the slate
|
|
814
|
+
asset was emitted. `context.reason` is `load` (the player rejected the asset) or
|
|
815
|
+
`stalled` (it did not present within the ad-start budget). **Fix:** Check the slate URL
|
|
816
|
+
and CORS, and that an HLS or DASH slate is an on-demand presentation (`EXT-X-ENDLIST`, or
|
|
817
|
+
an MPD of `type="static"`). A slate failure never fails the break itself.
|
|
818
|
+
|
|
704
819
|
## DA-PRELOAD-MODE-RESOLVED
|
|
705
820
|
|
|
706
821
|
**Cause:** Informational, emitted once at construction. States the ad preload
|
|
@@ -754,10 +869,54 @@ for AirPlay while a break was active (`context.reason: break-active`) or within
|
|
|
754
869
|
`externalPlayback.blockBeforeBreakSec` seconds of the next one
|
|
755
870
|
(`break-imminent`). `context` also carries `breakId`, `secondsUntilBreak` and
|
|
756
871
|
`trigger`.
|
|
757
|
-
**Fix:** None needed; the video stays on the device
|
|
872
|
+
**Fix:** None needed; the content video stays on the device (on iOS the running
|
|
873
|
+
ad plays on the receiver instead) and the SDK retries when
|
|
758
874
|
the break ends if the route is still on AirPlay. Shorten the window with
|
|
759
875
|
`externalPlayback.blockBeforeBreakSec` (`0` blocks only during a break).
|
|
760
876
|
|
|
877
|
+
## DA-AD-ROUTE-CHANGED
|
|
878
|
+
|
|
879
|
+
**Cause:** iOS only. The audio route changed while an ad break played (an
|
|
880
|
+
AirPlay or Bluetooth output appeared or went away). `context` carries `reason`,
|
|
881
|
+
`outputs`, `previousOutputs` and the ad player snapshot.
|
|
882
|
+
**Fix:** None needed; diagnostic. Read it next to `DA-AD-TIMECONTROL` and
|
|
883
|
+
`DA-AD-AUDIO-INTERRUPTION` to explain an ad stall right after a route change.
|
|
884
|
+
|
|
885
|
+
## DA-AD-TIMECONTROL
|
|
886
|
+
|
|
887
|
+
**Cause:** iOS only. The ad player's `timeControlStatus` or
|
|
888
|
+
`reasonForWaitingToPlay` changed during a break (`context.field` names which).
|
|
889
|
+
**Fix:** None needed; diagnostic. A pause with no viewer action points at an
|
|
890
|
+
audio-session interruption (`DA-AD-AUDIO-INTERRUPTION`).
|
|
891
|
+
|
|
892
|
+
## DA-AD-ITEM-STALLED
|
|
893
|
+
|
|
894
|
+
**Cause:** iOS only. The ad item reported a playback stall during a break.
|
|
895
|
+
**Fix:** Check `context` buffer flags and outputs; a stall right after a route
|
|
896
|
+
change means the app audio moved to an audio-only AirPlay route.
|
|
897
|
+
|
|
898
|
+
## DA-AD-AUDIO-INTERRUPTION
|
|
899
|
+
|
|
900
|
+
**Cause:** iOS only. An audio-session interruption began or ended during a
|
|
901
|
+
break (a call, or another app taking audio focus). `context.type` is `began` or
|
|
902
|
+
`ended`.
|
|
903
|
+
**Fix:** None needed while an ad is meant to play: the SDK reactivates the
|
|
904
|
+
session and restarts it (`DA-AD-INTERRUPTION-RESUMED`).
|
|
905
|
+
|
|
906
|
+
## DA-AD-INTERRUPTION-RESUMED
|
|
907
|
+
|
|
908
|
+
**Cause:** iOS only. An ad paused by an audio-session interruption was
|
|
909
|
+
restarted. `context.trigger` is `interruption-ended`, `route-changed`,
|
|
910
|
+
`player-paused` or `retry`; `stalledSec` is how long it was stopped.
|
|
911
|
+
**Fix:** None needed; informational. The break cut deadline is not extended.
|
|
912
|
+
|
|
913
|
+
## DA-AD-AUDIO-SESSION-REACTIVATE-FAILED
|
|
914
|
+
|
|
915
|
+
**Cause:** iOS only. Reactivating the audio session before restarting an
|
|
916
|
+
interrupted ad failed (`context.errorCode`, `errorDomain`).
|
|
917
|
+
**Fix:** The SDK retries once a second, up to five times; if it keeps failing,
|
|
918
|
+
another app or call still holds the audio session.
|
|
919
|
+
|
|
761
920
|
## DA-EXTERNAL-PLAYBACK-ALLOWED
|
|
762
921
|
|
|
763
922
|
**Cause:** An AirPlay request passed the ad-break guard. `context.trigger` is
|
|
@@ -913,6 +1072,17 @@ SDK version too. Skipping is deliberate: an earlier SDK coerced an unrecognised
|
|
|
913
1072
|
format to `single`, which rendered a future format as a full-screen content
|
|
914
1073
|
takeover — the most invasive possible reading of a value it did not understand.
|
|
915
1074
|
|
|
1075
|
+
## DA-BREAK-LSHAPE-ANCHOR-UNKNOWN
|
|
1076
|
+
|
|
1077
|
+
**Cause:** An `lshape_ad` or `lshape_content` variant declared an `anchor` string
|
|
1078
|
+
this SDK version does not recognise. `context.anchor` is the raw manifest value.
|
|
1079
|
+
The break **still played**, with the video box in the default `top_left` corner:
|
|
1080
|
+
the manifest spec (1.11.0) says an unknown anchor must not drop the break. Raised
|
|
1081
|
+
once per break begin on the selected variant. **Fix:** Check the value against the
|
|
1082
|
+
spec enum (`top_left`, `top_right`, `bottom_left`, `bottom_right`); the usual
|
|
1083
|
+
cause is a typo or a casing mistake such as `TOP_RIGHT`. A corner newer than the
|
|
1084
|
+
SDK build looks identical here, so compare the SDK version too.
|
|
1085
|
+
|
|
916
1086
|
## DA-BREAK-FORMAT-DEGRADED
|
|
917
1087
|
|
|
918
1088
|
**Cause:** A split break format (`double`, `lshape_ad`, `lshape_content`) has to
|
|
@@ -1196,11 +1366,31 @@ flip, and defers the exit hand-back past the close morph. `reachedEnd: true` fol
|
|
|
1196
1366
|
is a forced break cut and intentionally keeps the ad on the layer until break end.
|
|
1197
1367
|
Non-fatal.
|
|
1198
1368
|
|
|
1369
|
+
## DA-IMAGE-ANIMATED
|
|
1370
|
+
|
|
1371
|
+
**Cause:** An animated image (GIF or animated WebP) used as a `double`/`lshape_ad`
|
|
1372
|
+
companion, an `lshape_content` backdrop, an image ad or a pause image started animating.
|
|
1373
|
+
Only the Android and iOS/tvOS renderers emit it, because browsers animate the image element
|
|
1374
|
+
themselves. `context.surface` is `companion`, `backdrop`, `image-ad` or `pause`, `context.uri`
|
|
1375
|
+
the image, and
|
|
1376
|
+
`context.frameCount` the frame count (iOS/tvOS; null on Android). **Fix:** None — it
|
|
1377
|
+
confirms the animation path ran. If the creative still looks frozen, check that the file
|
|
1378
|
+
really has more than one frame and a loop count other than 1. Debug.
|
|
1379
|
+
|
|
1380
|
+
## DA-IMAGE-ANIMATION-UNSUPPORTED
|
|
1381
|
+
|
|
1382
|
+
**Cause:** The image is animated but this device cannot animate it, so the first frame is
|
|
1383
|
+
shown and the break continues. `context.reason` is `api-below-28` on Android below API 28
|
|
1384
|
+
(no animated image decoder) or `canvas-too-large` on iOS/tvOS when the animation canvas is
|
|
1385
|
+
above the decode budget. **Fix:** Serve an animation of 1080p or less, or a still image for
|
|
1386
|
+
these devices. Non-fatal.
|
|
1387
|
+
|
|
1199
1388
|
## DA-IMAGE-LOAD-FAILED
|
|
1200
1389
|
|
|
1201
1390
|
**Cause:** An image the renderer needed — a `double`/`lshape_ad` companion, an
|
|
1202
1391
|
`lshape_content` backdrop, or an image ad — could not be fetched or decoded within the
|
|
1203
|
-
timeout,
|
|
1392
|
+
timeout, or was larger than the 8 MB size limit (`context.error` is `too-large`), so the
|
|
1393
|
+
break continued without it. `context.uri` is the URL that failed. **Fix:**
|
|
1204
1394
|
Check the URL is reachable from the device with correct CORS headers. The case the timeout
|
|
1205
1395
|
exists for is a socket that CONNECTS and then goes quiet: `HttpURLConnection` defaults both
|
|
1206
1396
|
its connect and read timeouts to infinite, so before this such a fetch could hang an entire
|
|
@@ -1648,6 +1838,14 @@ resume policy has no effect. **Fix:** Remove `adInsertionType` for SSAI sessions
|
|
|
1648
1838
|
or switch to SGAI (`mode: 'sgai'`) if you need client-controlled DAR/DAI resume
|
|
1649
1839
|
behaviour.
|
|
1650
1840
|
|
|
1841
|
+
## DA-PAUSE-AD-ROTATION-EXHAUSTED
|
|
1842
|
+
|
|
1843
|
+
**Cause:** Informational. No unused pause breaks remain at or beyond the
|
|
1844
|
+
session's rotation position, so this pause shows no ad. Ordinary manifest
|
|
1845
|
+
updates preserve that position, including shorter or empty lists.
|
|
1846
|
+
**Fix:** No action needed; content can resume normally. To extend the rotation,
|
|
1847
|
+
append pause breaks beyond the consumed positions instead of repeating them.
|
|
1848
|
+
|
|
1651
1849
|
## DA-PAUSE-AD-SHOWN
|
|
1652
1850
|
|
|
1653
1851
|
**Cause:** Informational. A pause ad (full-screen image or video) was shown while
|
|
@@ -16,6 +16,8 @@ and creates the matching native adapter. See `react-native-overview.md`.
|
|
|
16
16
|
|
|
17
17
|
## Events
|
|
18
18
|
|
|
19
|
+
For VOD snapback, prefer the SDK's `seek()` entry point: it plans the correction before moving the player. External content seeks use `seeked`, or position jumps greater than two seconds when a host has no direct notification (AVPlayer host controls). Corrective seeks must land at the break start: Media3 uses exact seek parameters and AVPlayer uses zero tolerances. SDK corrections are suppressed as new viewer intent. Only playable mediatime-scheduled client-side VOD breaks participate; TV web, live, PTS/wallclock, event-triggered, SSAI and SSB are excluded. After seek-over, content-pausing Single and L-Shape Ad return to the requested target, overriding insertion policy and `resumeOffset` and bypassing chaining. Double Box, L-Shape Content and Overlay play on without a seek; normal chaining applies. Fullscreen fallbacks at break start count as Single. Seek-into uses normal resume policy.
|
|
20
|
+
|
|
19
21
|
`PlayerAdapterEvent` is exactly these seven strings. An adapter MUST forward all
|
|
20
22
|
of them to handlers registered via `on()`:
|
|
21
23
|
|
|
@@ -33,6 +33,8 @@ export declare class RoutingAdAdapter implements PlayerAdapter, AdSourceQueue {
|
|
|
33
33
|
private segmentEnd;
|
|
34
34
|
private stopBoundaryWatch;
|
|
35
35
|
private boundaryCrossed;
|
|
36
|
+
/** The hls.js engine came from a queued ad: it is configured for that hand-over only. */
|
|
37
|
+
private engineFromQueue;
|
|
36
38
|
constructor(video: HTMLVideoElement, hlsSupported?: boolean);
|
|
37
39
|
get currentTime(): number;
|
|
38
40
|
get duration(): number;
|
package/dist/RoutingAdAdapter.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"hls.js";import{HlsJsAdapter as t}from"@dolby-optiview/ads-sdk-adapter-hlsjs";import{HlsAdQueue as s}from"./HlsAdQueue";import{NativeVideoAdapter as i}from"./NativeVideoAdapter";import{selectAdTech as r}from"./selectAdTech";export class RoutingAdAdapter{constructor(t,s=e.isSupported()){this.inner=null,this.currentTech=null,this.ownedSrc=null,this.hls=null,this.eventListeners=new Map,this.queue=null,this.orphan=null,this.segmentStart=0,this.segmentEnd=null,this.stopBoundaryWatch=null,this.boundaryCrossed=!1,this.video=t,this.hlsSupported=s}get currentTime(){const e=this.inner?this.inner.currentTime:this.video.currentTime;return Math.max(0,e-this.segmentStart)}get duration(){return null!==this.segmentEnd?this.segmentEnd-this.segmentStart:(this.inner?this.inner.duration:this.video.duration)-this.segmentStart}get paused(){return this.inner?this.inner.paused:this.video.paused}get muted(){return this.video.muted}set muted(e){this.video.muted=e}get volume(){return this.video.volume}set volume(e){this.video.volume=e}get programDateTime(){return this.inner?this.inner.programDateTime:null}get videoElement(){return this.video}get supportsParallelBuffering(){return this.inner?.supportsParallelBuffering??this.hlsSupported}pause(){this.inner?this.inner.pause():this.video.pause()}async play(){if(this.inner)return this.inner.play();await this.video.play()}seek(e){this.inner?this.inner.seek(e+this.segmentStart):this.video.currentTime=e+this.segmentStart}load(e){this.resetQueue();const t=r(e),s=t=>{const s=this.ensureInner(t).load(e);return this.ownedSrc=this.video.getAttribute("src"),s};return"string"==typeof t?s(t):t.then(s)}preload(e){const t=r(e),s=t=>{if(this.inner&&this.currentTech!==(this.hlsSupported?t:"native"))return Promise.resolve();const s=this.ensureInner(t);return s.preload?s.preload(e):Promise.resolve()};return"string"==typeof t?s(t):t.then(s)}async queueNext(e,t){this.disposeQueue();const i=this.hls;if(!i||"hls"!==this.currentTech||i.media!==this.video)return!1;if("function"!=typeof i.transferMedia)return!1;const n=r(e);if("hls"!==("string"==typeof n?n:await n)||this.hls!==i||this.queue)return!1;const h=i.latestLevelDetails?.totalduration;if("number"!=typeof h||!Number.isFinite(h)||h<=0)return!1;const o=new s({url:e,video:this.video,current:i,boundary:this.segmentStart+h});this.queue=o;const u=this.segmentEnd;this.segmentEnd=o.boundary;const d=await o.start();return this.queue===o&&(d?(o.setOnFatal(()=>{this.queue===o&&(this.queue=null,this.dropTransferred(o),t())}),this.watchBoundary(o.boundary,"ended"),!0):(this.disposeQueue(),o.transferred||(this.segmentEnd=u),!1))}takeQueued(e){const s=this.queue;if(!s||s.url!==e||!s.transferred||this.video.ended||!s.isPlayable())return this.disposeQueue(),!1;this.queue=null;const i=this.boundaryCrossed;this.stopWatch(),!i&&this.video.currentTime<s.boundary&&(this.video.currentTime=s.boundary),this.inner?.destroy(),this.hls?.destroy(),this.hls=s.next,this.inner=new t(s.next,this.video);for(const[e,t]of this.eventListeners)for(const s of t)this.inner.on(e,s);return this.segmentStart=s.boundary,this.segmentEnd=s.boundary+s.duration,i?queueMicrotask(()=>this.emit("playing")):this.watchBoundary(s.boundary,"playing"),!0}clearQueued(){this.disposeQueue()}on(e,t){let s=this.eventListeners.get(e);s||(s=new Set,this.eventListeners.set(e,s)),s.add(t),this.inner?.on(e,t)}off(e,t){this.eventListeners.get(e)?.delete(t),this.inner?.off(e,t)}unload(){this.resetQueue(),this.teardownInner()}releaseMediaElement(){if(this.resetQueue(),this.inner){if(this.inner.destroy(),this.hls){try{this.hls.stopLoad()}catch{}this.video.getAttribute("src")===this.ownedSrc&&this.video.removeAttribute("src");for(const e of Array.from(this.video.querySelectorAll("source")))e.remove();try{this.hls.destroy()}catch{}this.hls=null}this.ownedSrc=null,this.inner=null,this.currentTech=null}}destroy(){this.resetQueue(),this.teardownInner(),this.eventListeners.clear()}resetQueue(){const e=this.queue;this.queue=null,this.stopWatch(),e?.destroy(),this.orphan?.destroy(),this.orphan=null,this.segmentStart=0,this.segmentEnd=null,this.boundaryCrossed=!1}disposeQueue(){const e=this.queue;this.queue=null,this.stopWatch(),e&&(e.transferred?this.dropTransferred(e):e.destroy())}dropTransferred(e){this.stopWatch(),e.drop(),this.orphan?.destroy(),this.orphan=e}watchBoundary(e,t){this.stopWatch(),this.boundaryCrossed=!1;const s=this.video;let i=null;const r=()=>{s.removeEventListener("timeupdate",h),null!==i&&s.cancelVideoFrameCallback?.(i)},n=()=>{this.stopBoundaryWatch===r&&(this.stopWatch(),this.boundaryCrossed=!0,this.emit(t))},h=()=>{s.currentTime>=e&&n()},o=(t,h)=>{h.mediaTime>=e?n():this.stopBoundaryWatch===r&&(i=s.requestVideoFrameCallback?.(o)??null)};this.stopBoundaryWatch=r,s.addEventListener("timeupdate",h),i=s.requestVideoFrameCallback?.(o)??null}stopWatch(){const e=this.stopBoundaryWatch;this.stopBoundaryWatch=null,e?.()}emit(e){for(const t of this.eventListeners.get(e)??[])t()}ensureInner(e){const t=this.hlsSupported?e:"native";if(this.inner&&this.currentTech===t)return this.inner;this.teardownInner(),this.inner=this.buildInner(t),this.currentTech=t;for(const[e,t]of this.eventListeners)for(const s of t)this.inner.on(e,s);return this.inner}buildInner(s){return"native"===s?new i(this.video):(this.hls=new e,new t(this.hls,this.video))}teardownInner(){if(this.inner?.destroy(),this.hls){const e=this.video.getAttribute("src");if(e&&e!==this.ownedSrc)for(const e of Array.from(this.video.querySelectorAll("source")))e.remove();try{this.hls.destroy()}catch{}this.hls=null}const e=this.video.getAttribute("src");e&&e!==this.ownedSrc||(this.video.removeAttribute("src"),"native"!==this.currentTech||null===this.ownedSrc||this.video.srcObject||this.video.load()),this.ownedSrc=null,this.inner=null,this.currentTech=null}}
|
|
1
|
+
import e from"hls.js";import{HlsJsAdapter as t}from"@dolby-optiview/ads-sdk-adapter-hlsjs";import{HlsAdQueue as s}from"./HlsAdQueue";import{NativeVideoAdapter as i}from"./NativeVideoAdapter";import{selectAdTech as r}from"./selectAdTech";export class RoutingAdAdapter{constructor(t,s=e.isSupported()){this.inner=null,this.currentTech=null,this.ownedSrc=null,this.hls=null,this.eventListeners=new Map,this.queue=null,this.orphan=null,this.segmentStart=0,this.segmentEnd=null,this.stopBoundaryWatch=null,this.boundaryCrossed=!1,this.engineFromQueue=!1,this.video=t,this.hlsSupported=s}get currentTime(){const e=this.inner?this.inner.currentTime:this.video.currentTime;return Math.max(0,e-this.segmentStart)}get duration(){return null!==this.segmentEnd?this.segmentEnd-this.segmentStart:(this.inner?this.inner.duration:this.video.duration)-this.segmentStart}get paused(){return this.inner?this.inner.paused:this.video.paused}get muted(){return this.video.muted}set muted(e){this.video.muted=e}get volume(){return this.video.volume}set volume(e){this.video.volume=e}get programDateTime(){return this.inner?this.inner.programDateTime:null}get videoElement(){return this.video}get supportsParallelBuffering(){return this.inner?.supportsParallelBuffering??this.hlsSupported}pause(){this.inner?this.inner.pause():this.video.pause()}async play(){if(this.inner)return this.inner.play();await this.video.play()}seek(e){this.inner?this.inner.seek(e+this.segmentStart):this.video.currentTime=e+this.segmentStart}load(e){this.resetQueue(),this.engineFromQueue&&this.teardownInner();const t=r(e),s=t=>{const s=this.ensureInner(t).load(e);return this.ownedSrc=this.video.getAttribute("src"),s};return"string"==typeof t?s(t):t.then(s)}preload(e){const t=r(e),s=t=>{if(this.inner&&this.currentTech!==(this.hlsSupported?t:"native"))return Promise.resolve();const s=this.ensureInner(t);return s.preload?s.preload(e):Promise.resolve()};return"string"==typeof t?s(t):t.then(s)}async queueNext(e,t){this.disposeQueue();const i=this.hls;if(!i||"hls"!==this.currentTech||i.media!==this.video)return!1;if("function"!=typeof i.transferMedia)return!1;const n=r(e);if("hls"!==("string"==typeof n?n:await n)||this.hls!==i||this.queue)return!1;const h=i.latestLevelDetails?.totalduration;if("number"!=typeof h||!Number.isFinite(h)||h<=0)return!1;const o=new s({url:e,video:this.video,current:i,boundary:this.segmentStart+h});this.queue=o;const u=this.segmentEnd;this.segmentEnd=o.boundary;const d=await o.start();return this.queue===o&&(d?(o.setOnFatal(()=>{this.queue===o&&(this.queue=null,this.dropTransferred(o),t())}),this.watchBoundary(o.boundary,"ended"),!0):(this.disposeQueue(),o.transferred||(this.segmentEnd=u),!1))}takeQueued(e){const s=this.queue;if(!s||s.url!==e||!s.transferred||this.video.ended||!s.isPlayable())return this.disposeQueue(),!1;this.queue=null;const i=this.boundaryCrossed;this.stopWatch(),!i&&this.video.currentTime<s.boundary&&(this.video.currentTime=s.boundary),this.inner?.destroy(),this.hls?.destroy(),this.hls=s.next,this.inner=new t(s.next,this.video),this.engineFromQueue=!0;for(const[e,t]of this.eventListeners)for(const s of t)this.inner.on(e,s);return this.segmentStart=s.boundary,this.segmentEnd=s.boundary+s.duration,i?queueMicrotask(()=>this.emit("playing")):this.watchBoundary(s.boundary,"playing"),!0}clearQueued(){this.disposeQueue()}on(e,t){let s=this.eventListeners.get(e);s||(s=new Set,this.eventListeners.set(e,s)),s.add(t),this.inner?.on(e,t)}off(e,t){this.eventListeners.get(e)?.delete(t),this.inner?.off(e,t)}unload(){this.resetQueue(),this.teardownInner()}releaseMediaElement(){if(this.resetQueue(),this.inner){if(this.inner.destroy(),this.hls){try{this.hls.stopLoad()}catch{}this.video.getAttribute("src")===this.ownedSrc&&this.video.removeAttribute("src");for(const e of Array.from(this.video.querySelectorAll("source")))e.remove();try{this.hls.destroy()}catch{}this.hls=null}this.ownedSrc=null,this.inner=null,this.currentTech=null,this.engineFromQueue=!1}}destroy(){this.resetQueue(),this.teardownInner(),this.eventListeners.clear()}resetQueue(){const e=this.queue;this.queue=null,this.stopWatch(),e?.destroy(),this.orphan?.destroy(),this.orphan=null,this.segmentStart=0,this.segmentEnd=null,this.boundaryCrossed=!1}disposeQueue(){const e=this.queue;this.queue=null,this.stopWatch(),e&&(e.transferred?this.dropTransferred(e):e.destroy())}dropTransferred(e){this.stopWatch(),e.drop(),this.orphan?.destroy(),this.orphan=e}watchBoundary(e,t){this.stopWatch(),this.boundaryCrossed=!1;const s=this.video;let i=null;const r=()=>{s.removeEventListener("timeupdate",h),null!==i&&s.cancelVideoFrameCallback?.(i)},n=()=>{this.stopBoundaryWatch===r&&(this.stopWatch(),this.boundaryCrossed=!0,this.emit(t))},h=()=>{s.currentTime>=e&&n()},o=(t,h)=>{h.mediaTime>=e?n():this.stopBoundaryWatch===r&&(i=s.requestVideoFrameCallback?.(o)??null)};this.stopBoundaryWatch=r,s.addEventListener("timeupdate",h),i=s.requestVideoFrameCallback?.(o)??null}stopWatch(){const e=this.stopBoundaryWatch;this.stopBoundaryWatch=null,e?.()}emit(e){for(const t of this.eventListeners.get(e)??[])t()}ensureInner(e){const t=this.hlsSupported?e:"native";if(this.inner&&this.currentTech===t)return this.inner;this.teardownInner(),this.inner=this.buildInner(t),this.currentTech=t;for(const[e,t]of this.eventListeners)for(const s of t)this.inner.on(e,s);return this.inner}buildInner(s){return"native"===s?new i(this.video):(this.hls=new e,new t(this.hls,this.video))}teardownInner(){if(this.inner?.destroy(),this.hls){const e=this.video.getAttribute("src");if(e&&e!==this.ownedSrc)for(const e of Array.from(this.video.querySelectorAll("source")))e.remove();try{this.hls.destroy()}catch{}this.hls=null}const e=this.video.getAttribute("src");e&&e!==this.ownedSrc||(this.video.removeAttribute("src"),"native"!==this.currentTech||null===this.ownedSrc||this.video.srcObject||this.video.load()),this.ownedSrc=null,this.inner=null,this.currentTech=null,this.engineFromQueue=!1}}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dolby-optiview/ads-sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "OptiView Ads SDK entry package - OptiViewAds with a default HLS.js ad player (native fallback)",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
},
|
|
45
45
|
"sideEffects": false,
|
|
46
46
|
"dependencies": {
|
|
47
|
-
"@dolby-optiview/ads-sdk-adapter-hlsjs": "^1.
|
|
48
|
-
"@dolby-optiview/ads-sdk-core": "^1.
|
|
47
|
+
"@dolby-optiview/ads-sdk-adapter-hlsjs": "^1.6.0",
|
|
48
|
+
"@dolby-optiview/ads-sdk-core": "^1.6.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"hls.js": "^1.0.0"
|