@dolby-optiview/ads-sdk-adapter-theoplayer 1.0.0 → 1.1.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.
- package/README.md +55 -0
- package/dist/THEOplayerAdapter.d.ts +41 -0
- package/dist/THEOplayerAdapter.js +1 -1
- package/dist/cueBytes.d.ts +10 -0
- package/dist/cueBytes.js +1 -0
- package/dist/dashPtsOffset.d.ts +47 -0
- package/dist/dashPtsOffset.js +1 -0
- package/dist/theoErrorToMediaError.d.ts +6 -0
- package/dist/theoErrorToMediaError.js +1 -0
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# @dolby-optiview/ads-sdk-adapter-theoplayer
|
|
2
|
+
|
|
3
|
+
THEOplayer Web SDK `PlayerAdapter` for the OptiView Ads SDK. It connects a
|
|
4
|
+
`ChromelessPlayer` (or full `Player`) and its mount element to the SDK
|
|
5
|
+
core, either as the 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-theoplayer theoplayer
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`theoplayer` 9.0.0 or newer is a peer dependency. The adapter is typed against
|
|
14
|
+
the structural `THEOplayerLike` interface, so it also works with a THEOplayer
|
|
15
|
+
build loaded from a script tag. A THEOplayer license is required.
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { ChromelessPlayer } from 'theoplayer';
|
|
21
|
+
import { OptiViewAds } from '@dolby-optiview/ads-sdk-core';
|
|
22
|
+
import { THEOplayerAdapter } from '@dolby-optiview/ads-sdk-adapter-theoplayer';
|
|
23
|
+
|
|
24
|
+
const config = { license: LICENSE, libraryLocation: LIB, mutedAutoplay: 'all' };
|
|
25
|
+
|
|
26
|
+
const playerDiv = document.getElementById('player') as HTMLElement;
|
|
27
|
+
const player = new ChromelessPlayer(playerDiv, config);
|
|
28
|
+
|
|
29
|
+
const sdk = new OptiViewAds({
|
|
30
|
+
player: new THEOplayerAdapter(player, playerDiv),
|
|
31
|
+
container: document.getElementById('container')!,
|
|
32
|
+
createAdAdapter: (adContainer) => {
|
|
33
|
+
const adPlayer = new ChromelessPlayer(adContainer, config);
|
|
34
|
+
return new THEOplayerAdapter(adPlayer, adContainer);
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
await sdk.startSession({
|
|
39
|
+
manifestUrl: 'https://manifest.example.com/v1/your-org/channels/your-channel',
|
|
40
|
+
});
|
|
41
|
+
player.source = { sources: [{ src: 'https://example.com/live.m3u8' }] };
|
|
42
|
+
player.play();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
For React Native apps using `react-native-theoplayer`, use
|
|
46
|
+
`@dolby-optiview/ads-sdk-react-native` instead of this package.
|
|
47
|
+
|
|
48
|
+
## Documentation
|
|
49
|
+
|
|
50
|
+
- [OptiView Ads SDK documentation](https://optiview.dolby.com/docs/ads/player-integration/optiview-ads-sdk/)
|
|
51
|
+
- Entry package: `@dolby-optiview/ads-sdk`
|
|
52
|
+
|
|
53
|
+
## License
|
|
54
|
+
|
|
55
|
+
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 THEONetworkLike } from './dashPtsOffset';
|
|
2
3
|
/** Minimal surface of a THEOplayer metadata `TextTrackCue`. */
|
|
3
4
|
export interface THEOTextTrackCueLike {
|
|
4
5
|
startTime?: number;
|
|
@@ -112,10 +113,16 @@ export interface THEOplayerLike {
|
|
|
112
113
|
src?: string;
|
|
113
114
|
/** The MIME type, e.g. `application/x-mpegurl`. */
|
|
114
115
|
type?: string;
|
|
116
|
+
/** Seconds behind the end of the manifest where THEOplayer places its live point. */
|
|
117
|
+
liveOffset?: number;
|
|
115
118
|
}>;
|
|
119
|
+
/** Description-wide live offset; a typed source's own value takes precedence. */
|
|
120
|
+
liveOffset?: number;
|
|
116
121
|
} | null | undefined;
|
|
117
122
|
/** Text-track list; metadata tracks carry in-stream ID3/emsg cues. Optional. */
|
|
118
123
|
readonly textTracks?: THEOTextTracksLike;
|
|
124
|
+
/** Network interceptors; the adapter reads DASH manifests to place emsg cues by PTS. Optional. */
|
|
125
|
+
readonly network?: THEONetworkLike;
|
|
119
126
|
/** Live-latency controller; absent on builds or sources without live playback. */
|
|
120
127
|
readonly latencyManager?: THEOLatencyManagerLike;
|
|
121
128
|
/**
|
|
@@ -127,6 +134,8 @@ export interface THEOplayerLike {
|
|
|
127
134
|
readonly length: number;
|
|
128
135
|
/** The end of the seekable range at `index`, in seconds. */
|
|
129
136
|
end(index: number): number;
|
|
137
|
+
/** The start of the seekable range at index, in seconds, when supported. */
|
|
138
|
+
start?(index: number): number;
|
|
130
139
|
};
|
|
131
140
|
/** Video rendition ladder; absent on builds or sources without selectable qualities. */
|
|
132
141
|
readonly videoTracks?: ArrayLike<THEOMediaTrackLike>;
|
|
@@ -152,6 +161,12 @@ export interface THEOplayerLike {
|
|
|
152
161
|
* GAM pod URLs with query strings — default to HLS.
|
|
153
162
|
*/
|
|
154
163
|
export declare function inferSourceType(url: string): string;
|
|
164
|
+
type THEOSourceDescription = NonNullable<THEOplayerLike['source']>;
|
|
165
|
+
/**
|
|
166
|
+
* `source` with every typed source carrying `offset` as its `liveOffset`, or null when the sources
|
|
167
|
+
* are not plain objects in an array (a copy could lose fields) or already declare a live offset.
|
|
168
|
+
*/
|
|
169
|
+
export declare function withLiveOffset(source: THEOSourceDescription, offset: number): THEOSourceDescription | null;
|
|
155
170
|
/**
|
|
156
171
|
* The {@link PlayerAdapter} for THEOplayer, from the `@dolby-optiview/ads-sdk-adapter-theoplayer`
|
|
157
172
|
* package (peer dependency: `theoplayer` 9 or later).
|
|
@@ -190,6 +205,7 @@ export declare class THEOplayerAdapter implements PlayerAdapter {
|
|
|
190
205
|
private eventListeners;
|
|
191
206
|
private boundHandlers;
|
|
192
207
|
private addTrackHandler?;
|
|
208
|
+
private metadataTrackList?;
|
|
193
209
|
/**
|
|
194
210
|
* The track whose `targetQuality` the pin changed, and what it held before — so a restore
|
|
195
211
|
* puts back an application's own selection rather than assuming "automatic".
|
|
@@ -198,9 +214,16 @@ export declare class THEOplayerAdapter implements PlayerAdapter {
|
|
|
198
214
|
private pinnedTrack;
|
|
199
215
|
private targetQualityBeforePin;
|
|
200
216
|
private cueTracks;
|
|
217
|
+
/** Pending live start distance in seconds; null once withdrawn. */
|
|
218
|
+
private liveStartOffset;
|
|
219
|
+
/** `loadeddata` fired for the current source, so a frame may already be on screen. */
|
|
220
|
+
private frameLoaded;
|
|
221
|
+
/** The source this adapter last re-set with a live offset, so its own `sourcechange` is not rewritten again. */
|
|
222
|
+
private rewrittenSource;
|
|
201
223
|
/** Per-cue `enter` listeners, released in teardown — a live stream adds one per cue. */
|
|
202
224
|
private cueEnters;
|
|
203
225
|
private seenCues;
|
|
226
|
+
private readonly dashPtsOffsets;
|
|
204
227
|
/**
|
|
205
228
|
* @param player The THEOplayer instance (a `ChromelessPlayer` or anything satisfying {@link THEOplayerLike}).
|
|
206
229
|
* @param container The element the player was created in; the SDK locates the `<video>` inside it.
|
|
@@ -229,6 +252,21 @@ export declare class THEOplayerAdapter implements PlayerAdapter {
|
|
|
229
252
|
* it does not know rather than a position. `player.seekable` carries the real window.
|
|
230
253
|
*/
|
|
231
254
|
get seekableEnd(): number | null;
|
|
255
|
+
get seekableStart(): number | null;
|
|
256
|
+
/**
|
|
257
|
+
* The published live edge. THEOplayer ends `seekable` at its live point, documented as the end
|
|
258
|
+
* of the manifest minus the source's `liveOffset`; without a declared offset the seekable end
|
|
259
|
+
* is reported.
|
|
260
|
+
*/
|
|
261
|
+
get liveEdge(): number | null;
|
|
262
|
+
/**
|
|
263
|
+
* Makes THEOplayer itself begin a live source `targetDurations` × `fallbackTargetDuration` behind
|
|
264
|
+
* live, by re-setting the host's source with that `liveOffset` before its first frame loads.
|
|
265
|
+
* THEOplayer exposes no playlist target duration, so the fallback is required. Returns false once
|
|
266
|
+
* a frame loaded; a source that cannot be copied losslessly is left alone and the SDK seeks.
|
|
267
|
+
*/
|
|
268
|
+
requestLiveStartOffset(targetDurations: number | null, fallbackTargetDuration: number | null): boolean;
|
|
269
|
+
private rewriteSourceForLiveStart;
|
|
232
270
|
/**
|
|
233
271
|
* Playback rate, `1` being normal speed.
|
|
234
272
|
*
|
|
@@ -259,6 +297,8 @@ export declare class THEOplayerAdapter implements PlayerAdapter {
|
|
|
259
297
|
*/
|
|
260
298
|
get isLive(): boolean | null;
|
|
261
299
|
get paused(): boolean;
|
|
300
|
+
/** Content is ready and its initial engine seek has settled. */
|
|
301
|
+
get playbackReady(): boolean;
|
|
262
302
|
get muted(): boolean;
|
|
263
303
|
set muted(value: boolean);
|
|
264
304
|
get volume(): number;
|
|
@@ -367,3 +407,4 @@ export declare class THEOplayerAdapter implements PlayerAdapter {
|
|
|
367
407
|
private setupNativeListeners;
|
|
368
408
|
private teardownNativeListeners;
|
|
369
409
|
}
|
|
410
|
+
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export function inferSourceType(e){const t=e.split(/[?#]/,1)[0].toLowerCase();return t.endsWith(".mp4")||t.endsWith(".m4v")?"video/mp4":t.endsWith(".webm")?"video/webm":"application/x-mpegurl"}function
|
|
1
|
+
import{toMediaErrorPayload as e}from"./theoErrorToMediaError";import{decodeGeobData as t,toUint8Array as r}from"./cueBytes";import{DashPtsOffsetTracker as s}from"./dashPtsOffset";export function inferSourceType(e){const t=e.split(/[?#]/,1)[0].toLowerCase();return t.endsWith(".mp4")||t.endsWith(".m4v")?"video/mp4":t.endsWith(".webm")?"video/webm":"application/x-mpegurl"}export function withLiveOffset(e,t){const r=e.sources,s=e=>"object"==typeof e&&null!==e&&Object.getPrototypeOf(e)===Object.prototype;return s(e)&&Array.isArray(r)&&0!==r.length&&void 0===e.liveOffset&&r.every(e=>s(e)&&void 0===e.liveOffset)?{...e,sources:r.map(e=>({...e,liveOffset:t}))}:null}export class THEOplayerAdapter{constructor(e,t){this.endedSuppressedUntilStart=!1,this.pendingSeekTime=null,this.liveRemapCleanup=null,this.eventListeners=new Map,this.boundHandlers=new Map,this.pinnedTrack=null,this.cueTracks=[],this.liveStartOffset=null,this.frameLoaded=!1,this.rewrittenSource=null,this.cueEnters=[],this.seenCues=new WeakSet,this.player=e,this.container=t,this.dashPtsOffsets=new s(e.network),this.setupNativeListeners(),this.setupMetadataListener()}get seekableEnd(){const e=this.player.seekable;if(!e||0===e.length)return null;const t=e.end(e.length-1);return"number"==typeof t&&Number.isFinite(t)?t:null}get seekableStart(){const e=this.player.seekable;if(!e?.length)return null;const t=e.start?.(e.length-1);return"number"==typeof t&&Number.isFinite(t)?t:null}get liveEdge(){const e=this.seekableEnd;if(null===e)return null;const t=this.player.source,r=t?.sources?.[0]?.liveOffset??t?.liveOffset;return"number"==typeof r&&Number.isFinite(r)&&r>=0?e+r:e}requestLiveStartOffset(e,t){const r=null!==e&&null!==t?e*t:NaN;return!Number.isFinite(r)||r<=0?(this.liveStartOffset=null,!1):(this.liveStartOffset=r,this.rewriteSourceForLiveStart(),!this.frameLoaded)}rewriteSourceForLiveStart(){const e=this.liveStartOffset,t=this.player.source;if(null===e||this.frameLoaded||!t||t===this.rewrittenSource)return;const r=withLiveOffset(t,e);if(!r)return;this.rewrittenSource=r;const s=!this.player.paused;this.player.source=r,s&&this.player.play()}get playbackRate(){return this.player.playbackRate??1}set playbackRate(e){this.player.playbackRate=e}setVideoQuality(e){if(null===e){const e=this.pinnedTrack;if(!e)return!1;const t=this.targetQualityBeforePin;try{return e.targetQuality=t,e.targetQuality!==t?!1:(this.pinnedTrack=null,this.targetQualityBeforePin=void 0,!0)}catch{return!1}}const t=this.player.videoTracks;if(!t||0===t.length)return!1;const r=t[0],s=r?.qualities;if(!r||!s||0===s.length)return!1;const i=[];for(let e=0;e<s.length;e++){const t=s[e];if(!t||!1===t.available||!1===t.enabled)continue;const r=t.bandwidth;"number"==typeof r&&Number.isFinite(r)&&r>0&&i.push(t)}if(i.length<2)return!1;let n=i[0];for(let e=1;e<i.length;e++)i[e].bandwidth<n.bandwidth&&(n=i[e]);const a=!this.pinnedTrack;let l;a&&(this.pinnedTrack=r,this.targetQualityBeforePin=r.targetQuality);try{r.targetQuality=n,l=r.targetQuality===n}catch{l=!1}return!!l||(a&&(this.pinnedTrack=null,this.targetQualityBeforePin=void 0),!1)}get maxLiveSeekPosition(){const e=this.player.latencyManager;if(!e?.monitoringLivePlayback)return null;const t=e.currentLatency,r=e.currentConfiguration?.minimumOffset;if("number"!=typeof t||!Number.isFinite(t))return null;if("number"!=typeof r||!Number.isFinite(r))return null;const s=this.player.currentTime+t-r;return Number.isFinite(s)?s:null}get currentTime(){return this.player.currentTime}set currentTime(e){this.player.currentTime=e}get duration(){return this.player.duration}get isLive(){const e=this.player.duration;return"number"==typeof e?!Number.isFinite(e):null}get paused(){return this.player.paused}get playbackReady(){const e=this.videoElement;return!0!==this.player.seeking&&!!e&&e.readyState>=HTMLMediaElement.HAVE_CURRENT_DATA&&!e.seeking}get muted(){return this.player.muted}set muted(e){this.player.muted=e}get volume(){return this.player.volume}set volume(e){this.player.volume=e}get videoElement(){return this.container.querySelector("video")}get programDateTime(){return this.player.currentProgramDateTime}pause(){this.player.pause()}play(){return this.player.play(),Promise.resolve()}seek(e){!0!==this.player.seeking?(this.pendingSeekTime=null,this.player.currentTime=e):this.pendingSeekTime=e}get sourceUrl(){return this.player.source?.sources?.[0]?.src??null}load(e){return this.pendingSeekTime=null,this.player.source={sources:[{src:e,type:inferSourceType(e)}]},this.endedSuppressedUntilStart=!0,Promise.resolve()}async releaseMediaElement(){this.player.pause(),this.player.source=null;const e=this.videoElement;e&&await new Promise(t=>{let r=!1;const s=()=>{r||(r=!0,e.removeEventListener("emptied",s),clearTimeout(i),t())},i=setTimeout(s,250);e.addEventListener("emptied",s,{once:!0})})}async unload(){this.pendingSeekTime=null,await this.releaseMediaElement()}async recreateMediaPipeline(e){const{startTime:t}=e,r=this.player.source;if(!r?.sources?.length)throw new Error("THEOplayer pipeline recreate: no source description");const s=!0===this.isLive,i=this.player.currentProgramDateTime,n=this.player.currentTime,a=i&&Number.isFinite(i.getTime())?i.getTime()+1e3*(t-n):null,l=this.seekableEnd,o=s&&null!==l?Math.max(0,l-t):null;if(await this.releaseMediaElement(),this.endedSuppressedUntilStart=!0,this.player.source=r,this.player.currentTime=t,s){const e=()=>{this.player.removeEventListener("loadedmetadata",t),this.player.removeEventListener("error",e),this.liveRemapCleanup=null},t=()=>{e();const t=this.player.currentProgramDateTime;let r=null;null!==a&&t&&Number.isFinite(t.getTime())?r=this.player.currentTime+(a-t.getTime())/1e3:null!==o&&null!==this.seekableEnd&&(r=this.seekableEnd-o),null!==r&&Math.abs(r-this.player.currentTime)>.5&&(this.player.currentTime=r)};this.liveRemapCleanup=e,this.player.addEventListener("loadedmetadata",t),this.player.addEventListener("error",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(){this.pendingSeekTime=null,this.liveRemapCleanup?.(),this.liveRemapCleanup=null,this.teardownNativeListeners(),this.teardownMetadataListener(),this.dashPtsOffsets.destroy(),this.eventListeners.clear(),this.player.destroy()}emit(e,t){const r=this.eventListeners.get(e);if(r)for(const e of r)e(t)}setupMetadataListener(){const e=this.player.textTracks;if(!e)return;const t=e=>{e.track&&this.subscribeMetadataTrack(e.track)};this.addTrackHandler=t,this.metadataTrackList=e,e.addEventListener("addtrack",t);for(let t=0;t<(e.length??0);t++){const r=e[t];r&&this.subscribeMetadataTrack(r)}}deferMetadataCue(e){"function"==typeof queueMicrotask?queueMicrotask(e):Promise.resolve().then(e)}subscribeMetadataTrack(e){if("metadata"!==(e.kind??e.type))return;if(this.cueTracks.some(({track:t})=>t===e))return;let t=!1;const r=s=>{if(!this.cueTracks.some(({track:t})=>t===e))return;if(t)return void this.deferMetadataCue(()=>r(s));const i=s.cue;if(i&&!this.seenCues.has(i)&&(this.seenCues.add(i),this.emit("timedmetadata",{...this.toCue(i),phase:"parsed"}),"function"==typeof i.addEventListener)){const e=()=>{this.emit("timedmetadata",{...this.toCue(i),phase:"presented"})};i.addEventListener("enter",e),this.cueEnters.push({cue:i,handler:e})}},s="disabled"===e.mode;this.cueTracks.push({track:e,handler:r,enabledByAdapter:s}),e.addEventListener("addcue",r),s&&(t=!0,e.mode="hidden",t=!1);const i=e.cues;if(i&&i.length>0){const t=()=>{if(this.cueTracks.some(({track:t})=>t===e))for(let e=0;e<i.length;e++){const t=i[e];t&&r({cue:t})}};this.deferMetadataCue(t)}}teardownMetadataListener(){this.addTrackHandler&&this.metadataTrackList&&this.metadataTrackList.removeEventListener("addtrack",this.addTrackHandler),this.addTrackHandler=void 0,this.metadataTrackList=void 0;for(const{track:e,handler:t,enabledByAdapter:r}of this.cueTracks)e.removeEventListener("addcue",t),r&&"hidden"===e.mode&&(e.mode="disabled");this.cueTracks=[];for(const{cue:e,handler:t}of this.cueEnters)e.removeEventListener?.("enter",t);this.cueEnters=[],this.seenCues=new WeakSet}toCue(e){if("string"==typeof e.schemeIDURI){const t={type:"emsg",startTime:e.startTime??0,endTime:e.endTime,schemeIdUri:e.schemeIDURI},s=this.dashPtsOffsets.offsetAt(t.startTime);void 0!==s&&(t.ptsOffset=s);const i=r(e.content);return i?(t.raw=i,t.messageData=new TextDecoder("utf-8").decode(i)):"string"==typeof e.content&&(t.messageData=e.content),t}const s={type:"id3",startTime:e.startTime??0,endTime:e.endTime},i=e.content;if(i&&"object"==typeof i){const e=i.id;if("GEOB"===e||"GEO"===e){"string"==typeof i.description&&(s.description=i.description),"string"==typeof i.mimeType&&(s.mimeType=i.mimeType);const e=t(i.data);return void 0!==e&&(s.messageData=e),s}const r=i.text??i.data??i.description;e&&void 0!==r?s.frames={[e]:String(r)}:s.messageData=JSON.stringify(i)}else"string"==typeof i&&(s.messageData=i);return s}setupNativeListeners(){const t=["timeupdate","ended","error","seeked","volumechange","waiting","playing","pause"];for(const r of t){const t=t=>{if("ended"===r&&this.endedSuppressedUntilStart)return;"playing"===r&&!1===this.player.paused&&(this.endedSuppressedUntilStart=!1);const s=this.eventListeners.get(r);if(s)for(const i of s)"error"===r?i(e(t,this.videoElement)):i();if("seeked"===r&&null!==this.pendingSeekTime){const e=this.pendingSeekTime;this.pendingSeekTime=null,this.player.currentTime=e}};this.boundHandlers.set(r,t),this.player.addEventListener(r,t)}const r=()=>{this.frameLoaded=!1,setTimeout(()=>this.rewriteSourceForLiveStart(),0)},s=()=>{this.frameLoaded=!0};this.boundHandlers.set("sourcechange",r),this.player.addEventListener("sourcechange",r),this.boundHandlers.set("loadeddata",s),this.player.addEventListener("loadeddata",s)}teardownNativeListeners(){for(const[e,t]of this.boundHandlers)this.player.removeEventListener(e,t);this.boundHandlers.clear()}}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Byte helpers for THEOplayer cue and response payloads. */
|
|
2
|
+
/**
|
|
3
|
+
* Decode a GEOB frame's object data as UTF-8. THEOplayer types it as an
|
|
4
|
+
* `ArrayBuffer`; typed-array views and strings are accepted defensively.
|
|
5
|
+
*/
|
|
6
|
+
export declare function decodeGeobData(data: unknown): string | undefined;
|
|
7
|
+
/** Normalize an emsg cue's content to bytes; THEOplayer types it `Uint8Array`. */
|
|
8
|
+
export declare function toUint8Array(data: unknown): Uint8Array | undefined;
|
|
9
|
+
/** Realm-safe ArrayBuffer check (`instanceof` fails across window/worker realms). */
|
|
10
|
+
export declare function isArrayBuffer(value: unknown): value is ArrayBuffer;
|
package/dist/cueBytes.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function decodeGeobData(e){return"string"==typeof e?e:isArrayBuffer(e)?new TextDecoder("utf-8").decode(e):ArrayBuffer.isView(e)?new TextDecoder("utf-8").decode(new Uint8Array(e.buffer,e.byteOffset,e.byteLength)):void 0}export function toUint8Array(e){return isArrayBuffer(e)?new Uint8Array(e):ArrayBuffer.isView(e)?new Uint8Array(e.buffer,e.byteOffset,e.byteLength):void 0}export function isArrayBuffer(e){return e instanceof ArrayBuffer||"[object ArrayBuffer]"===Object.prototype.toString.call(e)}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PTS-clock offsets of a DASH presentation, read from its MPD.
|
|
3
|
+
*
|
|
4
|
+
* A DASH emsg whose `presentation_time_delta` is 0 surfaces as a cue at the start of its carrying
|
|
5
|
+
* segment, not at the PTS its payload names (Anvato `type=cue&pts=...`). Player time for a media
|
|
6
|
+
* time `t` in a Period is `Period@start + (t - presentationTimeOffset) / timescale`, so
|
|
7
|
+
* `ptsOffset = Period@start - presentationTimeOffset / timescale` lets the core place the cue exactly.
|
|
8
|
+
*/
|
|
9
|
+
/** One Period's start on the player timeline and its PTS-clock offset, in seconds. */
|
|
10
|
+
export interface DashPeriodOffset {
|
|
11
|
+
start: number;
|
|
12
|
+
ptsOffset: number;
|
|
13
|
+
}
|
|
14
|
+
/** Seconds in an `xs:duration` such as `PT1791467290S` or `PT1H2.5S`; null when unparseable. */
|
|
15
|
+
export declare function parseIsoDuration(value: string): number | null;
|
|
16
|
+
/**
|
|
17
|
+
* Period offsets of an MPD, in document order. A Period without `start` follows the previous one
|
|
18
|
+
* when that one declares a `duration`; otherwise it is skipped rather than guessed.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseDashPeriodOffsets(mpd: string): DashPeriodOffset[];
|
|
21
|
+
/** Offset of the last Period starting at or before `time`; undefined when none does. */
|
|
22
|
+
export declare function ptsOffsetAt(periods: readonly DashPeriodOffset[], time: number): number | undefined;
|
|
23
|
+
/** The slice of THEOplayer's `player.network` this module needs. */
|
|
24
|
+
export interface THEONetworkLike {
|
|
25
|
+
addResponseInterceptor(interceptor: (response: THEOResponseLike) => void): void;
|
|
26
|
+
removeResponseInterceptor(interceptor: (response: THEOResponseLike) => void): void;
|
|
27
|
+
}
|
|
28
|
+
/** The slice of a THEOplayer `InterceptableResponse` this module reads. */
|
|
29
|
+
export interface THEOResponseLike {
|
|
30
|
+
readonly request?: {
|
|
31
|
+
readonly type?: string;
|
|
32
|
+
};
|
|
33
|
+
readonly body?: unknown;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Keeps the current MPD's Period offsets by watching manifest responses. A non-DASH manifest
|
|
37
|
+
* clears them, so an HLS source never inherits a previous DASH source's offsets.
|
|
38
|
+
*/
|
|
39
|
+
export declare class DashPtsOffsetTracker {
|
|
40
|
+
private readonly network;
|
|
41
|
+
private periods;
|
|
42
|
+
private readonly interceptor;
|
|
43
|
+
constructor(network: THEONetworkLike | undefined);
|
|
44
|
+
/** The PTS-clock offset for a cue at player time `time`, when the current MPD gives one. */
|
|
45
|
+
offsetAt(time: number): number | undefined;
|
|
46
|
+
destroy(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isArrayBuffer as t}from"./cueBytes";const e=/^P(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;export function parseIsoDuration(t){const r=e.exec(t.trim());if(!r||"P"===t.trim()||"PT"===t.trim())return null;const[,s,o,n,i]=r;return 86400*Number(s??0)+3600*Number(o??0)+60*Number(n??0)+Number(i??0)}function r(t,e){return new RegExp(`\\b${e}="([^"]*)"`).exec(t)?.[1]}function s(t){const e=/<Segment(?:Template|List|Base)\b([^>]*)>/.exec(t)?.[1]??"";return{pto:Number(r(e,"presentationTimeOffset")??0),timescale:Number(r(e,"timescale")??1)}}export function parseDashPeriodOffsets(t){const e=[];let o=0;for(const n of t.matchAll(/<Period\b([^>]*)>([\s\S]*?)<\/Period>/g)){const[,t="",i=""]=n,u=r(t,"start"),c=void 0!==u?parseIsoDuration(u):o,f=r(t,"duration"),p=void 0!==f?parseIsoDuration(f):null;if(o=null!==c&&null!==p?c+p:null,null===c)continue;const{pto:a,timescale:d}=s(i);!Number.isFinite(a)||!Number.isFinite(d)||d<=0||e.push({start:c,ptsOffset:c-a/d})}return e}export function ptsOffsetAt(t,e){let r;for(const s of t)s.start<=e&&(!r||s.start>=r.start)&&(r=s);return r?.ptsOffset}export class DashPtsOffsetTracker{constructor(e){this.network=e,this.periods=[],this.interceptor=e=>{if("manifest"!==e.request?.type)return;const r="string"==typeof(s=e.body)?s:t(s)?new TextDecoder("utf-8").decode(s):null;var s;null!==r&&(this.periods=r.includes("<MPD")?parseDashPeriodOffsets(r):[])},e?.addResponseInterceptor(this.interceptor)}offsetAt(t){return ptsOffsetAt(this.periods,t)}destroy(){this.network?.removeResponseInterceptor(this.interceptor),this.periods=[]}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const e={3001:4,4e3:2,4001:2,4002:4,5e3:4,5001:2,5002:3,5004:1,6e3:2,6001:2},o=e=>"object"==typeof e&&null!==e;export function toMediaErrorPayload(r,t){const n=o(r)?r:void 0,d=n?.errorObject,i=o(d)?d:void 0,a=i?.code,c=t?.error?.code,s=("number"==typeof a?e[a]:void 0)??(6===i?.category?2:void 0)??("number"==typeof c&&c>=1&&c<=4?c:void 0),y=i?.message,g=n?.error,p={message:"string"==typeof y&&y.length>0?y:"string"==typeof g&&g.length>0?g:"THEOplayer error"};return void 0!==s&&(p.code=s),"string"==typeof i?.name&&(p.name=i.name),void 0!==d&&(p.cause=d),p}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dolby-optiview/ads-sdk-adapter-theoplayer",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "THEOplayer adapter for OptiView Ads SDK",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
},
|
|
38
38
|
"sideEffects": false,
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@dolby-optiview/ads-sdk-core": "^1.
|
|
40
|
+
"@dolby-optiview/ads-sdk-core": "^1.1.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"theoplayer": ">=9.0.0"
|