@dolby-optiview/ads-sdk-adapter-shaka 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 ADDED
@@ -0,0 +1,57 @@
1
+ # @dolby-optiview/ads-sdk-adapter-shaka
2
+
3
+ Shaka Player `PlayerAdapter` for the OptiView Ads SDK. It connects a
4
+ `shaka.Player` and its `<video>` element to the SDK core, either as the
5
+ content player or as the ad player.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @dolby-optiview/ads-sdk-core @dolby-optiview/ads-sdk-adapter-shaka shaka-player
11
+ ```
12
+
13
+ `shaka-player` 4.x or 5.x is a peer dependency. The adapter is typed against
14
+ the structural `ShakaPlayerLike` interface, so it does not import
15
+ `shaka-player` itself.
16
+
17
+ ## Usage
18
+
19
+ ```typescript
20
+ import shaka from 'shaka-player';
21
+ import { OptiViewAds } from '@dolby-optiview/ads-sdk-core';
22
+ import { ShakaAdapter } from '@dolby-optiview/ads-sdk-adapter-shaka';
23
+
24
+ shaka.polyfill.installAll();
25
+
26
+ const video = document.getElementById('video') as HTMLVideoElement;
27
+ const player = new shaka.Player(video);
28
+
29
+ const sdk = new OptiViewAds({
30
+ player: new ShakaAdapter(player, video),
31
+ container: document.getElementById('container')!,
32
+ createAdAdapter: (adContainer) => {
33
+ const adVideo = document.createElement('video');
34
+ adContainer.appendChild(adVideo);
35
+ // Create the ad player without a video element; the adapter attaches it.
36
+ return new ShakaAdapter(new shaka.Player(), adVideo);
37
+ },
38
+ });
39
+
40
+ await sdk.startSession({
41
+ manifestUrl: 'https://manifest.example.com/v1/your-org/channels/your-channel',
42
+ });
43
+ await player.load('https://example.com/live.m3u8');
44
+ video.play();
45
+ ```
46
+
47
+ Call `sdk.destroy()` before `player.destroy()` when tearing the player down;
48
+ the SDK destroys the ad player it created.
49
+
50
+ ## Documentation
51
+
52
+ - [OptiView Ads SDK documentation](https://optiview.dolby.com/docs/ads/player-integration/optiview-ads-sdk/)
53
+ - Entry package: `@dolby-optiview/ads-sdk`
54
+
55
+ ## License
56
+
57
+ See <https://optiview.dolby.com/policies/terms-of-service/>.
@@ -1,4 +1,5 @@
1
1
  import type { PlayerAdapter, PlayerAdapterEvent, PlayerAdapterEventHandler, VideoQuality } from '@dolby-optiview/ads-sdk-core';
2
+ import { type ShakaPtsOffsetManifestLike } from './shakaPtsOffset';
2
3
  /**
3
4
  * Minimal surface of a Shaka variant track. `bandwidth` is bits/s; `active` marks the one
4
5
  * currently playing, which is what a restore has to put back when the application had ABR off.
@@ -62,6 +63,11 @@ export interface ShakaPlayerLike {
62
63
  targetLatencyTolerance?: number;
63
64
  };
64
65
  };
66
+ /** Manifest parsing settings. */
67
+ manifest?: {
68
+ /** Seconds behind the live edge where Shaka ends its seek range and starts live playback. */
69
+ defaultPresentationDelay?: number;
70
+ };
65
71
  /** Whether adaptive selection is on — snapshotted so a quality pin can be undone exactly. */
66
72
  abr?: {
67
73
  /** Whether ABR is enabled. */
@@ -138,10 +144,12 @@ export interface ShakaErrorEvent {
138
144
  message?: string;
139
145
  };
140
146
  }
141
- interface ShakaManifestLike {
147
+ interface ShakaManifestLike extends ShakaPtsOffsetManifestLike {
142
148
  presentationTimeline: {
143
149
  /** Wallclock anchor of presentation time 0 read from the manifest; null when it carries none. */
144
150
  getInitialProgramDateTime(): number | null;
151
+ /** End of the newest segment Shaka has parsed, in presentation seconds. Optional on older builds. */
152
+ getMaxSegmentEndTime?(): number | null;
145
153
  };
146
154
  }
147
155
  /**
@@ -183,6 +191,8 @@ export declare class ShakaAdapter implements PlayerAdapter {
183
191
  private shakaErrorHandler?;
184
192
  private originalPlayerLoad;
185
193
  private loadingViaAdapter;
194
+ /** Live start distance in seconds to apply as the presentation delay at the next load. */
195
+ private pendingPresentationDelay;
186
196
  /**
187
197
  * What was in force before {@link pinLowestQuality}: whether ABR was on, and which variant
188
198
  * was active if it was not. `null` when nothing is pinned.
@@ -221,6 +231,8 @@ export declare class ShakaAdapter implements PlayerAdapter {
221
231
  * Whether the player is currently paused.
222
232
  */
223
233
  get paused(): boolean;
234
+ /** Content is ready and its initial engine seek has settled. */
235
+ get playbackReady(): boolean;
224
236
  /**
225
237
  * Whether the player is muted.
226
238
  */
@@ -350,6 +362,14 @@ export declare class ShakaAdapter implements PlayerAdapter {
350
362
  * buffered content has played. Missing optional APIs degrade to no quality change.
351
363
  */
352
364
  setVideoQuality(quality: VideoQuality | null): boolean;
365
+ /**
366
+ * Starts a live stream `targetDurations` × `fallbackTargetDuration` behind the edge through
367
+ * `manifest.defaultPresentationDelay`, read when Shaka parses the manifest at the next load; it
368
+ * stays configured. Shaka exposes no target duration, so the fallback is required.
369
+ */
370
+ requestLiveStartOffset(targetDurations: number | null, fallbackTargetDuration: number | null): boolean;
371
+ /** The newest parsed segment's end; `seekRange()` trails by the delay and follows the wall clock. */
372
+ get liveEdge(): number | null;
353
373
  get maxLiveSeekPosition(): number | null;
354
374
  /**
355
375
  * Subscribe to Shaka's timed-metadata events and re-emit each marker as a
@@ -1 +1 @@
1
- import{decodeUtf8 as e,parseId3GeobBody as t}from"@dolby-optiview/ads-sdk-core/id3";function i(e){if(null!=e)return"string"==typeof e?e:a(e)?new TextDecoder("utf-8").decode(e):ArrayBuffer.isView(e)?new TextDecoder("utf-8").decode(new Uint8Array(e.buffer,e.byteOffset,e.byteLength)):String(e)}function a(e){return e instanceof ArrayBuffer||"[object ArrayBuffer]"===Object.prototype.toString.call(e)}function r(e){if(a(e))return new Uint8Array(e);const t=e;return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}export class ShakaAdapter{constructor(e,t){this.eventListeners=new Map,this.boundHandlers=new Map,this.deferredSeekTime=null,this.loadingViaAdapter=!1,this.abrEnabledBeforePin=null,this.variantBeforePin=null,this.emittedCueKeys=new Set,this.player=e,this.video=t;const i=e.load;this.originalPlayerLoad=i,e.load=async(...t)=>{try{return await i.apply(e,t)}catch(e){throw this.loadingViaAdapter||function(e){return"object"==typeof e&&null!==e&&7e3===e.code}(e)||this.emit("error",e),e}},this.setupVideoListeners(),this.setupMetadataListener()}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get isLive(){return this.player.isLive?.()??null}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(){try{const e=this.player.getManifest();if(!e)return null;const t=e.presentationTimeline.getInitialProgramDateTime();return null==t?null:new Date(1e3*(t+this.video.currentTime))}catch{return null}}pause(){this.video.pause()}async play(){await this.video.play()}seek(e){this.video.seeking?this.deferredSeekTime=e:this.video.currentTime=e}get sourceUrl(){return this.player.getAssetUri?.()??null}async load(e){this.player.getMediaElement?.()!==this.video&&await this.player.attach(this.video,!1),this.loadingViaAdapter=!0;try{await this.player.load(e)}finally{this.loadingViaAdapter=!1}}async releaseMediaElement(){await(this.player.detach?.())}async unload(){"function"==typeof this.player.unload?await this.player.unload(!1):await(this.player.detach?.())}async recreateMediaPipeline(e){const t=this.sourceUrl;if(!t)throw new Error("Shaka pipeline recreate: no asset URI");this.deferredSeekTime=null;const i=e.startTime,a=!0===this.isLive;let r;try{r=this.player.getManifest()?.presentationTimeline.getInitialProgramDateTime()??null}catch{r=null}const s=a&&null!==r?1e3*(r+i):null,n=this.player.seekRange?.()?.end,d=a&&void 0!==n&&Number.isFinite(n)?Math.max(0,n-i):null;await this.unload(),this.player.getMediaElement?.()!==this.video&&await this.player.attach(this.video,!1),this.loadingViaAdapter=!0;try{if(a)if(null!==s)await this.player.load(t,new Date(s));else if(null!==d){await this.player.load(t,null);const e=this.player.seekRange?.()?.end;if(void 0!==e&&Number.isFinite(e)){const t=e-d;Math.abs(t-this.video.currentTime)>.5&&(this.video.currentTime=t)}}else await this.player.load(t,i);else await this.player.load(t,i)}finally{this.loadingViaAdapter=!1}}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t)}off(e,t){const i=this.eventListeners.get(e);i&&i.delete(t)}destroy(){for(const[e,t]of this.boundHandlers)this.video.removeEventListener(e,t);this.boundHandlers.clear(),this.deferredSeekTime=null,this.player.removeEventListener&&(this.presentedMetadataHandler&&(this.player.removeEventListener("metadata",this.presentedMetadataHandler),this.presentedMetadataHandler=void 0),this.parsedMetadataHandler&&(this.player.removeEventListener("metadataadded",this.parsedMetadataHandler),this.parsedMetadataHandler=void 0)),this.emsgHandler&&this.player.removeEventListener&&(this.player.removeEventListener("emsg",this.emsgHandler),this.emsgHandler=void 0),this.shakaErrorHandler&&this.player.removeEventListener&&(this.player.removeEventListener("error",this.shakaErrorHandler),this.shakaErrorHandler=void 0),this.originalPlayerLoad&&(this.player.load=this.originalPlayerLoad,this.originalPlayerLoad=void 0),this.emittedCueKeys.clear(),this.eventListeners.clear(),this.player.destroy()}get playbackRate(){return this.video.playbackRate}set playbackRate(e){this.video.playbackRate=e}setVideoQuality(e){if(null===e){if(null===this.abrEnabledBeforePin)return!1;const e=this.abrEnabledBeforePin,t=this.variantBeforePin;if(e){if(!this.player.configure)return!1;this.player.configure("abr.enabled",!0)}else{if(!t)return!1;if(!this.player.selectVariantTrack)return!1;this.player.selectVariantTrack(t,!1)}return this.abrEnabledBeforePin=null,this.variantBeforePin=null,!0}const t=this.player.getVariantTracks?.();if(!t||!this.player.selectVariantTrack)return!1;const i=t.filter(e=>{const t=e?.bandwidth;return"number"==typeof t&&Number.isFinite(t)&&t>0});if(i.length<2)return!1;let a=i[0];for(let e=1;e<i.length;e++)i[e].bandwidth<a.bandwidth&&(a=i[e]);if(null!==this.abrEnabledBeforePin)return!0;const r=this.player.getConfiguration?.()?.abr?.enabled??!0,s=t.find(e=>e?.active)??null;if(r&&!this.player.configure||!r&&!s)return!1;this.abrEnabledBeforePin=r,this.variantBeforePin=s;try{return this.player.configure?.("abr.enabled",!1),this.player.selectVariantTrack(a,!1),!0}catch{if(r)try{this.player.configure?.("abr.enabled",!0)}catch{this.variantBeforePin=null}return this.abrEnabledBeforePin=null,this.variantBeforePin=null,!1}}get maxLiveSeekPosition(){if(!this.player.isLive?.())return null;const e=this.player.seekRange?.(),t=e?.end;if("number"!=typeof t||!Number.isFinite(t))return null;const i=this.player.getConfiguration?.()?.streaming,a=i?.liveSync;let r;if(a?.enabled){const e=a.targetLatency,t=a.targetLatencyTolerance??0;"number"==typeof e&&(r=e-t)}if(void 0===r&&(r=i?.safeSeekEndOffset),"number"!=typeof r||!Number.isFinite(r))return null;const s=t-r;return Number.isFinite(s)?s:null}setupMetadataListener(){if(!this.player.addEventListener)return;const e=e=>t=>{const i={...this.toCue(t),phase:e};this.isDuplicateCue(i)||this.emit("timedmetadata",i)};this.parsedMetadataHandler=e("parsed"),this.presentedMetadataHandler=e("presented"),this.player.addEventListener("metadata",this.presentedMetadataHandler),this.player.addEventListener("metadataadded",this.parsedMetadataHandler);const t=e=>{const t=e.detail;t&&this.emit("timedmetadata",this.emsgToCue(t))};this.emsgHandler=t,this.player.addEventListener("emsg",t);const i=e=>{2===e.detail?.severity&&this.emit("error",e.detail)};this.shakaErrorHandler=i,this.player.addEventListener("error",i)}isDuplicateCue(e){const t=[e.startTime,e.description??"",e.mimeType??"",e.messageData??"",e.frames?JSON.stringify(e.frames):"",e.phase??""].join("\0");return!!this.emittedCueKeys.has(t)||(this.emittedCueKeys.add(t),this.emittedCueKeys.size>256&&this.emittedCueKeys.delete(this.emittedCueKeys.values().next().value),!1)}toCue(s){const n=(s.metadataType??"").toLowerCase(),d=n.includes("emsg")?"emsg":n.includes("daterange")?"daterange":"id3",l=s.payload?.key??s.key,o=s.payload?.data??s.payload?.value??s.data,h={type:d,startTime:s.startTime??s.cueTime??0,endTime:s.endTime};if(void 0!==s.schemeIdUri&&(h.schemeIdUri=s.schemeIdUri),"emsg"===d){const e=null==o?null:r(o);e&&(h.raw=e)}if("id3"===d&&"GEOB"===l){const n=function(e,t){return(void 0===e||""===e)&&(a(t)||ArrayBuffer.isView(t))}(s.payload?.description,o)?t(r(o)):null;if(n)return h.description=n.description,n.mimeType&&(h.mimeType=n.mimeType),h.messageData=e(n.data),h;void 0!==s.payload?.description&&(h.description=s.payload.description),null!=s.payload?.mimeType&&(h.mimeType=s.payload.mimeType);const d=i(o);return void 0!==d&&(h.messageData=d),h}const u="id3"===d?null==o?void 0:String(o):i(o);return l&&void 0!==u?"id3"===d?h.frames={[l]:u}:h.messageData=u:void 0!==u&&(h.messageData=u),h}emsgToCue(e){const t={type:"emsg",startTime:e.startTime??0,endTime:e.endTime};if(void 0!==e.schemeIdUri&&(t.schemeIdUri=e.schemeIdUri),void 0!==e.id&&(t.id=String(e.id)),null!=e.messageData){const i=r(e.messageData);i&&(t.raw=i)}const a=i(e.messageData);return void 0!==a&&(t.messageData=a),t}setupVideoListeners(){const e=()=>{this.emit("timeupdate")},t=()=>{this.emit("ended")},i=()=>{this.emit("error",this.video.error)},a=()=>{if(this.emit("seeked"),null!==this.deferredSeekTime){const e=this.deferredSeekTime;this.deferredSeekTime=null,this.seek(e)}},r=()=>{this.emit("volumechange")},s=()=>{this.emit("waiting")},n=()=>{this.emit("playing")},d=()=>{this.emit("pause")};this.video.addEventListener("timeupdate",e),this.video.addEventListener("ended",t),this.video.addEventListener("error",i),this.video.addEventListener("seeked",a),this.video.addEventListener("volumechange",r),this.video.addEventListener("waiting",s),this.video.addEventListener("playing",n),this.video.addEventListener("pause",d),this.boundHandlers.set("timeupdate",e),this.boundHandlers.set("ended",t),this.boundHandlers.set("error",i),this.boundHandlers.set("seeked",a),this.boundHandlers.set("volumechange",r),this.boundHandlers.set("waiting",s),this.boundHandlers.set("playing",n),this.boundHandlers.set("pause",d)}emit(e,t){const i=this.eventListeners.get(e);if(i)for(const e of i)try{e(t)}catch(e){console.error("[ShakaAdapter] Event handler error:",e)}}}
1
+ import{decodeUtf8 as e,parseId3GeobBody as t}from"@dolby-optiview/ads-sdk-core/id3";import{shakaPtsOffsetAt as i}from"./shakaPtsOffset";function a(e){if(null!=e)return"string"==typeof e?e:r(e)?new TextDecoder("utf-8").decode(e):ArrayBuffer.isView(e)?new TextDecoder("utf-8").decode(new Uint8Array(e.buffer,e.byteOffset,e.byteLength)):String(e)}function r(e){return e instanceof ArrayBuffer||"[object ArrayBuffer]"===Object.prototype.toString.call(e)}function s(e){if(r(e))return new Uint8Array(e);const t=e;return new Uint8Array(t.buffer,t.byteOffset,t.byteLength)}export class ShakaAdapter{constructor(e,t){this.eventListeners=new Map,this.boundHandlers=new Map,this.deferredSeekTime=null,this.loadingViaAdapter=!1,this.pendingPresentationDelay=null,this.abrEnabledBeforePin=null,this.variantBeforePin=null,this.emittedCueKeys=new Set,this.player=e,this.video=t;const i=e.load;this.originalPlayerLoad=i,e.load=async(...t)=>{null!==this.pendingPresentationDelay&&e.configure?.("manifest.defaultPresentationDelay",this.pendingPresentationDelay),this.pendingPresentationDelay=null;try{return await i.apply(e,t)}catch(e){throw this.loadingViaAdapter||function(e){return"object"==typeof e&&null!==e&&7e3===e.code}(e)||this.emit("error",e),e}},this.setupVideoListeners(),this.setupMetadataListener()}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get isLive(){return this.player.isLive?.()??null}get paused(){return this.video.paused}get playbackReady(){return this.video.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA&&!this.video.seeking}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(){try{const e=this.player.getManifest();if(!e)return null;const t=e.presentationTimeline.getInitialProgramDateTime();return null==t?null:new Date(1e3*(t+this.video.currentTime))}catch{return null}}pause(){this.video.pause()}async play(){await this.video.play()}seek(e){this.video.seeking?this.deferredSeekTime=e:this.video.currentTime=e}get sourceUrl(){return this.player.getAssetUri?.()??null}async load(e){this.player.getMediaElement?.()!==this.video&&await this.player.attach(this.video,!1),this.loadingViaAdapter=!0;try{await this.player.load(e)}finally{this.loadingViaAdapter=!1}}async releaseMediaElement(){await(this.player.detach?.())}async unload(){"function"==typeof this.player.unload?await this.player.unload(!1):await(this.player.detach?.())}async recreateMediaPipeline(e){const t=this.sourceUrl;if(!t)throw new Error("Shaka pipeline recreate: no asset URI");this.deferredSeekTime=null;const i=e.startTime,a=!0===this.isLive;let r;try{r=this.player.getManifest()?.presentationTimeline.getInitialProgramDateTime()??null}catch{r=null}const s=a&&null!==r?1e3*(r+i):null,n=this.player.seekRange?.()?.end,d=a&&void 0!==n&&Number.isFinite(n)?Math.max(0,n-i):null;await this.unload(),this.player.getMediaElement?.()!==this.video&&await this.player.attach(this.video,!1),this.loadingViaAdapter=!0;try{if(a)if(null!==s)await this.player.load(t,new Date(s));else if(null!==d){await this.player.load(t,null);const e=this.player.seekRange?.()?.end;if(void 0!==e&&Number.isFinite(e)){const t=e-d;Math.abs(t-this.video.currentTime)>.5&&(this.video.currentTime=t)}}else await this.player.load(t,i);else await this.player.load(t,i)}finally{this.loadingViaAdapter=!1}}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t)}off(e,t){const i=this.eventListeners.get(e);i&&i.delete(t)}destroy(){for(const[e,t]of this.boundHandlers)this.video.removeEventListener(e,t);this.boundHandlers.clear(),this.deferredSeekTime=null,this.player.removeEventListener&&(this.presentedMetadataHandler&&(this.player.removeEventListener("metadata",this.presentedMetadataHandler),this.presentedMetadataHandler=void 0),this.parsedMetadataHandler&&(this.player.removeEventListener("metadataadded",this.parsedMetadataHandler),this.parsedMetadataHandler=void 0)),this.emsgHandler&&this.player.removeEventListener&&(this.player.removeEventListener("emsg",this.emsgHandler),this.emsgHandler=void 0),this.shakaErrorHandler&&this.player.removeEventListener&&(this.player.removeEventListener("error",this.shakaErrorHandler),this.shakaErrorHandler=void 0),this.originalPlayerLoad&&(this.player.load=this.originalPlayerLoad,this.originalPlayerLoad=void 0),this.emittedCueKeys.clear(),this.eventListeners.clear(),this.player.destroy()}get playbackRate(){return this.video.playbackRate}set playbackRate(e){this.video.playbackRate=e}setVideoQuality(e){if(null===e){if(null===this.abrEnabledBeforePin)return!1;const e=this.abrEnabledBeforePin,t=this.variantBeforePin;if(e){if(!this.player.configure)return!1;this.player.configure("abr.enabled",!0)}else{if(!t)return!1;if(!this.player.selectVariantTrack)return!1;this.player.selectVariantTrack(t,!1)}return this.abrEnabledBeforePin=null,this.variantBeforePin=null,!0}const t=this.player.getVariantTracks?.();if(!t||!this.player.selectVariantTrack)return!1;const i=t.filter(e=>{const t=e?.bandwidth;return"number"==typeof t&&Number.isFinite(t)&&t>0});if(i.length<2)return!1;let a=i[0];for(let e=1;e<i.length;e++)i[e].bandwidth<a.bandwidth&&(a=i[e]);if(null!==this.abrEnabledBeforePin)return!0;const r=this.player.getConfiguration?.()?.abr?.enabled??!0,s=t.find(e=>e?.active)??null;if(r&&!this.player.configure||!r&&!s)return!1;this.abrEnabledBeforePin=r,this.variantBeforePin=s;try{return this.player.configure?.("abr.enabled",!1),this.player.selectVariantTrack(a,!1),!0}catch{if(r)try{this.player.configure?.("abr.enabled",!0)}catch{this.variantBeforePin=null}return this.abrEnabledBeforePin=null,this.variantBeforePin=null,!1}}requestLiveStartOffset(e,t){const i=(e??NaN)*(t??NaN),a=Number.isFinite(i)&&i>0&&!!this.player.configure&&!this.player.getManifest();return this.pendingPresentationDelay=a?i:null,a}get liveEdge(){const e=this.player.isLive?.()?this.player.getManifest()?.presentationTimeline.getMaxSegmentEndTime?.():null;return"number"==typeof e&&Number.isFinite(e)?e:null}get maxLiveSeekPosition(){if(!this.player.isLive?.())return null;const e=this.player.seekRange?.(),t=e?.end;if("number"!=typeof t||!Number.isFinite(t))return null;const i=this.player.getConfiguration?.()?.streaming,a=i?.liveSync;let r;if(a?.enabled){const e=a.targetLatency,t=a.targetLatencyTolerance??0;"number"==typeof e&&(r=e-t)}if(void 0===r&&(r=i?.safeSeekEndOffset),"number"!=typeof r||!Number.isFinite(r))return null;const s=t-r;return Number.isFinite(s)?s:null}setupMetadataListener(){if(!this.player.addEventListener)return;const e=e=>t=>{const i={...this.toCue(t),phase:e};this.isDuplicateCue(i)||this.emit("timedmetadata",i)};this.parsedMetadataHandler=e("parsed"),this.presentedMetadataHandler=e("presented"),this.player.addEventListener("metadata",this.presentedMetadataHandler),this.player.addEventListener("metadataadded",this.parsedMetadataHandler);const t=e=>{const t=e.detail;t&&this.emit("timedmetadata",this.emsgToCue(t))};this.emsgHandler=t,this.player.addEventListener("emsg",t);const i=e=>{2===e.detail?.severity&&this.emit("error",e.detail)};this.shakaErrorHandler=i,this.player.addEventListener("error",i)}isDuplicateCue(e){const t=[e.startTime,e.description??"",e.mimeType??"",e.messageData??"",e.frames?JSON.stringify(e.frames):"",e.phase??""].join("\0");return!!this.emittedCueKeys.has(t)||(this.emittedCueKeys.add(t),this.emittedCueKeys.size>256&&this.emittedCueKeys.delete(this.emittedCueKeys.values().next().value),!1)}toCue(i){const n=(i.metadataType??"").toLowerCase(),d=n.includes("emsg")?"emsg":n.includes("daterange")?"daterange":"id3",l=i.payload?.key??i.key,o=i.payload?.data??i.payload?.value??i.data,h={type:d,startTime:i.startTime??i.cueTime??0,endTime:i.endTime};if(void 0!==i.schemeIdUri&&(h.schemeIdUri=i.schemeIdUri),"emsg"===d){const e=null==o?null:s(o);e&&(h.raw=e)}if("id3"===d&&"GEOB"===l){const n=function(e,t){return(void 0===e||""===e)&&(r(t)||ArrayBuffer.isView(t))}(i.payload?.description,o)?t(s(o)):null;if(n)return h.description=n.description,n.mimeType&&(h.mimeType=n.mimeType),h.messageData=e(n.data),h;void 0!==i.payload?.description&&(h.description=i.payload.description),null!=i.payload?.mimeType&&(h.mimeType=i.payload.mimeType);const d=a(o);return void 0!==d&&(h.messageData=d),h}const u="id3"===d?null==o?void 0:String(o):a(o);return l&&void 0!==u?"id3"===d?h.frames={[l]:u}:h.messageData=u:void 0!==u&&(h.messageData=u),h}emsgToCue(e){const t={type:"emsg",startTime:e.startTime??0,endTime:e.endTime};void 0!==e.schemeIdUri&&(t.schemeIdUri=e.schemeIdUri),void 0!==e.id&&(t.id=String(e.id));const r=i(this.player.getManifest(),t.startTime);if(void 0!==r&&(t.ptsOffset=r),null!=e.messageData){const i=s(e.messageData);i&&(t.raw=i)}const n=a(e.messageData);return void 0!==n&&(t.messageData=n),t}setupVideoListeners(){const e=()=>{this.emit("timeupdate")},t=()=>{this.emit("ended")},i=()=>{this.emit("error",this.video.error)},a=()=>{if(this.emit("seeked"),null!==this.deferredSeekTime){const e=this.deferredSeekTime;this.deferredSeekTime=null,this.seek(e)}},r=()=>{this.emit("volumechange")},s=()=>{this.emit("waiting")},n=()=>{this.emit("playing")},d=()=>{this.emit("pause")};this.video.addEventListener("timeupdate",e),this.video.addEventListener("ended",t),this.video.addEventListener("error",i),this.video.addEventListener("seeked",a),this.video.addEventListener("volumechange",r),this.video.addEventListener("waiting",s),this.video.addEventListener("playing",n),this.video.addEventListener("pause",d),this.boundHandlers.set("timeupdate",e),this.boundHandlers.set("ended",t),this.boundHandlers.set("error",i),this.boundHandlers.set("seeked",a),this.boundHandlers.set("volumechange",r),this.boundHandlers.set("waiting",s),this.boundHandlers.set("playing",n),this.boundHandlers.set("pause",d)}emit(e,t){const i=this.eventListeners.get(e);if(i)for(const e of i)try{e(t)}catch(e){console.error("[ShakaAdapter] Event handler error:",e)}}}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * PTS-clock offset of the DASH segment that carries a cue, read from Shaka's manifest model.
3
+ *
4
+ * Shaka reports a DASH emsg at `segment start + presentation_time_delta`, so an emsg v0 with a zero
5
+ * delta lands on its segment start rather than the PTS its payload names (Anvato `type=cue&pts=...`).
6
+ * A `SegmentReference.timestampOffset` is `Period@start - presentationTimeOffset / timescale`: the
7
+ * amount added to media timestamps to reach presentation time, which is the core's `ptsOffset`.
8
+ */
9
+ /** The slice of `shaka.media.SegmentIndex` this module reads. */
10
+ export interface ShakaSegmentIndexLike {
11
+ find(time: number): number | null;
12
+ get(position: number): {
13
+ readonly timestampOffset?: number | undefined;
14
+ } | null;
15
+ }
16
+ /** The slice of a `shaka.extern.Stream` this module reads. */
17
+ export interface ShakaStreamLike {
18
+ readonly segmentIndex?: ShakaSegmentIndexLike | null;
19
+ }
20
+ /** The slice of `shaka.extern.Manifest` this module reads. */
21
+ export interface ShakaPtsOffsetManifestLike {
22
+ readonly variants?: ReadonlyArray<{
23
+ readonly video?: ShakaStreamLike | null;
24
+ readonly audio?: ShakaStreamLike | null;
25
+ }>;
26
+ }
27
+ /** `ptsOffset` for a cue at presentation time `time`; undefined when Shaka has no segment there. */
28
+ export declare function shakaPtsOffsetAt(manifest: ShakaPtsOffsetManifestLike | null | undefined, time: number): number | undefined;
@@ -0,0 +1 @@
1
+ function t(t,n){const e=t?.segmentIndex,i=e?.find(n);if(!e||null==i)return;const o=e.get(i)?.timestampOffset;return"number"==typeof o&&Number.isFinite(o)?o:void 0}export function shakaPtsOffsetAt(n,e){for(const i of n?.variants??[]){const n=t(i.video,e)??t(i.audio,e);if(void 0!==n)return n}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolby-optiview/ads-sdk-adapter-shaka",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Shaka Player adapter for OptiView Ads SDK",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "sideEffects": false,
40
40
  "dependencies": {
41
- "@dolby-optiview/ads-sdk-core": "^1.0.0"
41
+ "@dolby-optiview/ads-sdk-core": "^1.1.1"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "shaka-player": "^4.0.0 || ^5.0.0"