@dolby-optiview/ads-sdk 1.0.0-beta.6 → 1.0.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.
@@ -52,6 +52,9 @@ of the public contract and are never renamed.
52
52
  | `DA-VENDOR-CONFIG-UNKNOWN-TYPE` | manifest | info | One or more vendorConfiguration entries declare a type this SDK version does not recognise and were ignored. |
53
53
  | `DA-CONTENT-PLAYBACK-ERROR` | playback | error | The content player reported a playback error. |
54
54
  | `DA-CONTENT-RESUME-STALLED` | playback | warn | Content did not resume within the watchdog window after a break that paused it ended; the SDK re-issued play() once. iOS only for now. |
55
+ | `DA-CONTENT-RECOVERY-MODE-RESOLVED` | playback | info | The resolved post-break content recovery mode for the session (recreate or none); context carries configured and resolved. |
56
+ | `DA-CONTENT-PIPELINE-RECREATED` | playback | info | The content player's media pipeline was rebuilt at break end before content resumed (recovery mode recreate); context carries breakId, startTime and durationMs. |
57
+ | `DA-CONTENT-PIPELINE-RECREATE-FAILED` | playback | warn | Rebuilding the content media pipeline at break end failed or timed out — or the adapter does not implement recreateMediaPipeline() (reason 'unsupported-adapter', e.g. a wrapper adapter that does not forward it); the SDK fell back to resuming in place with seek + play; context carries breakId, startTime, reason. |
55
58
  | `DA-AD-PLAYBACK-ERROR` | playback | error | The ad player reported a playback error during a break. |
56
59
  | `DA-AD-START-INTERRUPTED` | playback | warn | The ad element's play() was interrupted by a browser-initiated pause before the ad started (Chromium AbortError, seen on Samsung Tizen); the SDK re-issued play() once. context: breakId, error. |
57
60
  | `DA-DELIVERY-MODE-RESOLVED` | session | info | Delivery mode (sgai/ssai) resolved for the session at start and again on an ads-channel switch from the manifest's delivery rules (or the explicit client config). |
@@ -439,6 +439,34 @@ pre-buffers into a second MediaSource on such TVs: a second attached
439
439
  MediaSource wedges their pipeline (PLAYG-319), and MSE makes a detached
440
440
  pre-fill impossible (see `docs/ctv-preload-findings.md`).
441
441
 
442
+ ## DA-CONTENT-RECOVERY-MODE-RESOLVED
443
+
444
+ **Cause:** Informational, emitted once at construction. States the post-break
445
+ content recovery mode in force for the session: `context.configured` is what
446
+ the integrator set (or the default) and `context.resolved` is what
447
+ `resolveContentRecovery` decided (`auto` resolving to `recreate` on VIZIO
448
+ SmartCast User-Agents, `none` elsewhere). **Fix:** None — use it to confirm
449
+ which recovery strategy a session/report actually ran with.
450
+
451
+ ## DA-CONTENT-PIPELINE-RECREATED
452
+
453
+ **Cause:** Informational, emitted when `adBreakContentRecovery` resolved to
454
+ `recreate` and the content player's media pipeline was rebuilt at break end
455
+ before content resumed. `context` carries `breakId`, `startTime` (the resume
456
+ position the pipeline was seeded at) and `durationMs` (the rebuild cost).
457
+ **Fix:** None — use `durationMs` to measure what the rebuild costs on a panel;
458
+ expect roughly a second of re-buffering instead of an instant resume.
459
+
460
+ ## DA-CONTENT-PIPELINE-RECREATE-FAILED
461
+
462
+ **Cause:** In `recreate` recovery mode, the adapter's `recreateMediaPipeline`
463
+ rejected or exceeded its 10s budget; the SDK fell back to resuming in place
464
+ with seek + play. `context` carries `breakId`, `startTime` and `reason`.
465
+ **Fix:** Read `reason` — adapter-side engine errors vs a timeout. Frequent
466
+ failures on a platform mean the rebuild path is not viable there; consider
467
+ `adBreakContentRecovery: 'none'` for it, or fix the adapter's rebuild. The
468
+ session still resumes — the diagnostic marks the fallback, not a stall.
469
+
442
470
  ## DA-BREAK-WINDOW-ESCAPED
443
471
 
444
472
  **Cause:** An active wallclock break was still rendering after its window
@@ -63,6 +63,19 @@ export declare class NativeVideoAdapter implements PlayerAdapter {
63
63
  */
64
64
  get sourceUrl(): string | null;
65
65
  load(url: string): Promise<void>;
66
+ /**
67
+ * Release the element's media at break end — remove `src` and force the load
68
+ * algorithm to run so the resource is fully released.
69
+ */
70
+ unload(): void;
71
+ /**
72
+ * Rebuild the pipeline for the CURRENT source positioned at `startTime` — see
73
+ * `PlayerAdapter.recreateMediaPipeline`. Resolves on `loadedmetadata`, rejects on
74
+ * the element's `error`; the resume position is applied once metadata is known.
75
+ */
76
+ recreateMediaPipeline(options: {
77
+ startTime: number;
78
+ }): Promise<void>;
66
79
  /**
67
80
  * Best-effort detached prefetch: warm the HTTP cache using a throwaway,
68
81
  * un-attached video element set to preload metadata. Never rejects.
@@ -1 +1 @@
1
- export class NativeVideoAdapter{constructor(e){this.boundHandlers=new Map,this.eventListeners=new Map,this.cueTracks=[],this.emittedCues=new WeakSet,this.video=e,this.setupVideoListeners(),this.setupMetadataListener()}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get paused(){return 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 videoElement(){return this.video}get programDateTime(){const e=this.video.getStartDate;if("function"!=typeof e)return null;if(this.video.readyState<HTMLMediaElement.HAVE_METADATA)return null;const t=e.call(this.video);return t instanceof Date&&t.getTime()>0?new Date(t.getTime()+1e3*this.video.currentTime):null}get supportsParallelBuffering(){return!0}pause(){this.video.pause()}async play(){await this.video.play()}seek(e){this.video.currentTime=e}get sourceUrl(){return this.video.currentSrc||this.video.src||null}async load(e){return new Promise((t,r)=>{const i=()=>{n(),t()},s=()=>{n(),r(new Error("Native video load error"))},n=()=>{this.video.removeEventListener("loadedmetadata",i),this.video.removeEventListener("error",s)};this.video.addEventListener("loadedmetadata",i),this.video.addEventListener("error",s),this.video.preload="auto",this.video.src=e,this.video.load()})}async preload(e){if("undefined"!=typeof document)return new Promise(t=>{const r=document.createElement("video");r.preload="metadata",r.muted=!0;let i=!1;const s=()=>{i||(i=!0,clearTimeout(n),r.removeAttribute("src"),r.load(),t())},n=setTimeout(s,8e3);r.addEventListener("loadedmetadata",s,{once:!0}),r.addEventListener("error",s,{once:!0}),r.src=e})}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t)}off(e,t){this.eventListeners.get(e)?.delete(t)}destroy(){for(const[e,t]of this.boundHandlers)this.video.removeEventListener(e,t);this.boundHandlers.clear(),this.teardownMetadataListener(),this.eventListeners.clear()}setupMetadataListener(){const e=this.video.textTracks;if(!e||"function"!=typeof e.addEventListener)return;const t=e=>{this.emittedCues.has(e)||(this.emittedCues.add(e),this.emit("timedmetadata",this.toCue(e)))},r=e=>{const r=e.cues;if(r)for(let e=0;e<r.length;e++)t(r[e])},i=e=>{if("metadata"!==e.kind)return;e.mode="hidden";const i=()=>{const r=e.activeCues;if(r)for(let e=0;e<r.length;e++)t(r[e])};e.addEventListener("cuechange",i),this.cueTracks.push({track:e,handler:i}),r(e)},s=e=>{const t=e.track;t&&i(t)};this.addTrackHandler=s,e.addEventListener("addtrack",s);const n=()=>{for(const{track:e}of this.cueTracks)r(e)};this.cueScanHandler=n,this.video.addEventListener("timeupdate",n);for(let t=0;t<e.length;t++)i(e[t])}teardownMetadataListener(){const e=this.video.textTracks;this.addTrackHandler&&e?.removeEventListener&&(e.removeEventListener("addtrack",this.addTrackHandler),this.addTrackHandler=void 0),this.cueScanHandler&&(this.video.removeEventListener("timeupdate",this.cueScanHandler),this.cueScanHandler=void 0);for(const{track:e,handler:t}of this.cueTracks)e.removeEventListener("cuechange",t);this.cueTracks=[],this.emittedCues=new WeakSet}toCue(e){const t={type:"id3",startTime:e.startTime,endTime:e.endTime},r=e.value;if(r?.key){const e="string"==typeof(i=r.data)?i:null==i?"":"[object ArrayBuffer]"===Object.prototype.toString.call(i)?(new TextDecoder).decode(new Uint8Array(i)):ArrayBuffer.isView(i)?(new TextDecoder).decode(new Uint8Array(i.buffer,i.byteOffset,i.byteLength)):String(i);t.frames={[r.key]:e},"string"==typeof r.info&&r.info&&(t.description=r.info),e&&(t.messageData=e)}var i;return t}setupVideoListeners(){const e=["timeupdate","ended","error","seeked","volumechange","waiting","playing","pause"];for(const t of e){const e=()=>{this.emit(t,"error"===t?this.video.error:void 0)};this.video.addEventListener(t,e),this.boundHandlers.set(t,e)}}emit(e,t){const r=this.eventListeners.get(e);if(r)for(const e of r)try{e(t)}catch(e){console.error("[NativeVideoAdapter] Event handler error:",e)}}}
1
+ export class NativeVideoAdapter{constructor(e){this.boundHandlers=new Map,this.eventListeners=new Map,this.cueTracks=[],this.emittedCues=new WeakSet,this.video=e,this.setupVideoListeners(),this.setupMetadataListener()}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get paused(){return 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 videoElement(){return this.video}get programDateTime(){const e=this.video.getStartDate;if("function"!=typeof e)return null;if(this.video.readyState<HTMLMediaElement.HAVE_METADATA)return null;const t=e.call(this.video);return t instanceof Date&&t.getTime()>0?new Date(t.getTime()+1e3*this.video.currentTime):null}get supportsParallelBuffering(){return!0}pause(){this.video.pause()}async play(){await this.video.play()}seek(e){this.video.currentTime=e}get sourceUrl(){return this.video.currentSrc||this.video.src||null}async load(e){return new Promise((t,r)=>{const i=()=>{n(),t()},s=()=>{n(),r(new Error("Native video load error"))},n=()=>{this.video.removeEventListener("loadedmetadata",i),this.video.removeEventListener("error",s)};this.video.addEventListener("loadedmetadata",i),this.video.addEventListener("error",s),this.video.preload="auto",this.video.src=e,this.video.load()})}unload(){this.video.removeAttribute("src"),this.video.load()}recreateMediaPipeline(e){const t=this.sourceUrl;return t?new Promise((r,i)=>{const s=()=>{o(),this.video.currentTime=e.startTime,r()},n=()=>{o(),i(new Error("Native pipeline recreate error"))},o=()=>{this.video.removeEventListener("loadedmetadata",s),this.video.removeEventListener("error",n)};this.video.addEventListener("loadedmetadata",s),this.video.addEventListener("error",n),this.video.removeAttribute("src"),this.video.load(),this.video.src=t,this.video.load()}):Promise.reject(new Error("Native pipeline recreate: no source URL"))}async preload(e){if("undefined"!=typeof document)return new Promise(t=>{const r=document.createElement("video");r.preload="metadata",r.muted=!0;let i=!1;const s=()=>{i||(i=!0,clearTimeout(n),r.removeAttribute("src"),r.load(),t())},n=setTimeout(s,8e3);r.addEventListener("loadedmetadata",s,{once:!0}),r.addEventListener("error",s,{once:!0}),r.src=e})}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t)}off(e,t){this.eventListeners.get(e)?.delete(t)}destroy(){for(const[e,t]of this.boundHandlers)this.video.removeEventListener(e,t);this.boundHandlers.clear(),this.teardownMetadataListener(),this.eventListeners.clear()}setupMetadataListener(){const e=this.video.textTracks;if(!e||"function"!=typeof e.addEventListener)return;const t=e=>{this.emittedCues.has(e)||(this.emittedCues.add(e),this.emit("timedmetadata",this.toCue(e)))},r=e=>{const r=e.cues;if(r)for(let e=0;e<r.length;e++)t(r[e])},i=e=>{if("metadata"!==e.kind)return;e.mode="hidden";const i=()=>{const r=e.activeCues;if(r)for(let e=0;e<r.length;e++)t(r[e])};e.addEventListener("cuechange",i),this.cueTracks.push({track:e,handler:i}),r(e)},s=e=>{const t=e.track;t&&i(t)};this.addTrackHandler=s,e.addEventListener("addtrack",s);const n=()=>{for(const{track:e}of this.cueTracks)r(e)};this.cueScanHandler=n,this.video.addEventListener("timeupdate",n);for(let t=0;t<e.length;t++)i(e[t])}teardownMetadataListener(){const e=this.video.textTracks;this.addTrackHandler&&e?.removeEventListener&&(e.removeEventListener("addtrack",this.addTrackHandler),this.addTrackHandler=void 0),this.cueScanHandler&&(this.video.removeEventListener("timeupdate",this.cueScanHandler),this.cueScanHandler=void 0);for(const{track:e,handler:t}of this.cueTracks)e.removeEventListener("cuechange",t);this.cueTracks=[],this.emittedCues=new WeakSet}toCue(e){const t={type:"id3",startTime:e.startTime,endTime:e.endTime},r=e.value;if(r?.key){const e="string"==typeof(i=r.data)?i:null==i?"":"[object ArrayBuffer]"===Object.prototype.toString.call(i)?(new TextDecoder).decode(new Uint8Array(i)):ArrayBuffer.isView(i)?(new TextDecoder).decode(new Uint8Array(i.buffer,i.byteOffset,i.byteLength)):String(i);t.frames={[r.key]:e},"string"==typeof r.info&&r.info&&(t.description=r.info),e&&(t.messageData=e)}var i;return t}setupVideoListeners(){const e=["timeupdate","ended","error","seeked","volumechange","waiting","playing","pause"];for(const t of e){const e=()=>{this.emit(t,"error"===t?this.video.error:void 0)};this.video.addEventListener(t,e),this.boundHandlers.set(t,e)}}emit(e,t){const r=this.eventListeners.get(e);if(r)for(const e of r)try{e(t)}catch(e){console.error("[NativeVideoAdapter] Event handler error:",e)}}}
@@ -1 +1 @@
1
- import e from"hls.js";import{HlsJsAdapter as t}from"@dolby-optiview/ads-sdk-adapter-hlsjs";import{NativeVideoAdapter as n}from"./NativeVideoAdapter";import{selectAdTech as i}from"./selectAdTech";export class RoutingAdAdapter{constructor(t,n=e.isSupported()){this.inner=null,this.currentTech=null,this.ownedSrc=null,this.hls=null,this.eventListeners=new Map,this.video=t,this.hlsSupported=n}get currentTime(){return this.inner?this.inner.currentTime:this.video.currentTime}get duration(){return this.inner?this.inner.duration:this.video.duration}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.video.currentTime=e}load(e){const t=i(e),n=t=>{const n=this.ensureInner(t).load(e);return this.ownedSrc=this.video.getAttribute("src"),n};return"string"==typeof t?n(t):t.then(n)}preload(e){const t=i(e),n=t=>{const n=this.ensureInner(t);return n.preload?n.preload(e):Promise.resolve()};return"string"==typeof t?n(t):t.then(n)}on(e,t){let n=this.eventListeners.get(e);n||(n=new Set,this.eventListeners.set(e,n)),n.add(t),this.inner?.on(e,t)}off(e,t){this.eventListeners.get(e)?.delete(t),this.inner?.off(e,t)}unload(){this.teardownInner()}destroy(){this.teardownInner(),this.eventListeners.clear()}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 n of t)this.inner.on(e,n);return this.inner}buildInner(i){return"native"===i?new n(this.video):(this.hls=new e,new t(this.hls,this.video))}teardownInner(){if(this.inner?.destroy(),this.hls){try{this.hls.destroy()}catch{}this.hls=null}const e=this.video.getAttribute("src");e&&e!==this.ownedSrc||this.video.removeAttribute("src"),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{NativeVideoAdapter as i}from"./NativeVideoAdapter";import{selectAdTech as n}from"./selectAdTech";export class RoutingAdAdapter{constructor(t,i=e.isSupported()){this.inner=null,this.currentTech=null,this.ownedSrc=null,this.hls=null,this.eventListeners=new Map,this.video=t,this.hlsSupported=i}get currentTime(){return this.inner?this.inner.currentTime:this.video.currentTime}get duration(){return this.inner?this.inner.duration:this.video.duration}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.video.currentTime=e}load(e){const t=n(e),i=t=>{const i=this.ensureInner(t).load(e);return this.ownedSrc=this.video.getAttribute("src"),i};return"string"==typeof t?i(t):t.then(i)}preload(e){const t=n(e),i=t=>{const i=this.ensureInner(t);return i.preload?i.preload(e):Promise.resolve()};return"string"==typeof t?i(t):t.then(i)}on(e,t){let i=this.eventListeners.get(e);i||(i=new Set,this.eventListeners.set(e,i)),i.add(t),this.inner?.on(e,t)}off(e,t){this.eventListeners.get(e)?.delete(t),this.inner?.off(e,t)}unload(){this.teardownInner()}destroy(){this.teardownInner(),this.eventListeners.clear()}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 i of t)this.inner.on(e,i);return this.inner}buildInner(n){return"native"===n?new i(this.video):(this.hls=new e,new t(this.hls,this.video))}teardownInner(){if(this.inner?.destroy(),this.hls){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}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolby-optiview/ads-sdk",
3
- "version": "1.0.0-beta.6",
3
+ "version": "1.0.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.0.0-beta.6",
48
- "@dolby-optiview/ads-sdk-core": "1.0.0-beta.6"
47
+ "@dolby-optiview/ads-sdk-adapter-hlsjs": "^1.0.0",
48
+ "@dolby-optiview/ads-sdk-core": "^1.0.0"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "hls.js": "^1.0.0"