@dolby-optiview/ads-sdk-adapter-hlsjs 1.1.0 → 1.2.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.
@@ -36,6 +36,8 @@ export declare class HlsJsAdapter implements PlayerAdapter {
36
36
  private deferredSeekTime;
37
37
  /** `ended` already emitted for the current playback (element event and hls.js MEDIA_ENDED can both arrive). */
38
38
  private endedEmitted;
39
+ /** The `load()` / `recreateMediaPipeline()` in flight, which owns fatal errors until it settles. */
40
+ private activeOperation;
39
41
  /**
40
42
  * The level selection in force before {@link pinLowestQuality}, or `null` when nothing is
41
43
  * pinned. `-1` is hls.js's own value for automatic, so it is a meaningful snapshot rather
@@ -226,6 +228,8 @@ export declare class HlsJsAdapter implements PlayerAdapter {
226
228
  * Clean up resources.
227
229
  */
228
230
  destroy(): void;
231
+ private beginOperation;
232
+ private endOperation;
229
233
  /**
230
234
  * Set up HLS.js event listeners for program date time.
231
235
  */
@@ -1 +1 @@
1
- import e from"hls.js";import{decodeUtf8 as t,parseId3GeobFrames as s}from"@dolby-optiview/ads-sdk-core/id3";export class HlsJsAdapter{constructor(e,t){this.eventListeners=new Map,this.boundHandlers=new Map,this._programDateTime=null,this.deferredSeekTime=null,this.endedEmitted=!1,this.levelBeforePin=null,this.parsedCues=new Map,this.presentedPts=new Set,this.id3TrackListener=null,this.hls=e,this.video=t,this.setupHlsListeners(),this.setupVideoListeners()}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get isLive(){const e=this.hls?.latestLevelDetails;return"boolean"==typeof e?.live?e.live: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 playbackRate(){return this.video.playbackRate}set playbackRate(e){this.video.playbackRate=e}get videoElement(){return this.video}get supportsParallelBuffering(){return e.isSupported()}get programDateTime(){return this._programDateTime}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.hls.url??null}async load(t){return this.endedEmitted=!1,new Promise((s,i)=>{const r=()=>{this.hls.off(e.Events.MANIFEST_PARSED,r),this.hls.off(e.Events.ERROR,n),s()},n=(t,s)=>{s.fatal&&(this.hls.off(e.Events.MANIFEST_PARSED,r),this.hls.off(e.Events.ERROR,n),i(new Error(`HLS load error: ${s.details}`)))};this.hls.on(e.Events.MANIFEST_PARSED,r),this.hls.on(e.Events.ERROR,n),this.hls.loadSource(t),this.hls.attachMedia(this.video)})}unload(){this.deferredSeekTime=null,this.hls.stopLoad(),this.hls.detachMedia()}recreateMediaPipeline(t){return this.deferredSeekTime=null,new Promise((s,i)=>{const r=()=>{this.video.removeEventListener("loadedmetadata",n),this.hls.off(e.Events.FRAG_BUFFERED,a),this.hls.off(e.Events.ERROR,o)},n=()=>{d()},a=()=>{d()},d=()=>{Math.abs(this.video.currentTime-t.startTime)>.5&&(this.video.currentTime=t.startTime),r(),s()},o=(e,t)=>{t.fatal&&(r(),i(new Error(`HLS pipeline recreate error: ${t.details}`)))};this.video.addEventListener("loadedmetadata",n),this.hls.on(e.Events.FRAG_BUFFERED,a),this.hls.on(e.Events.ERROR,o),this.hls.detachMedia(),this.hls.attachMedia(this.video);try{this.video.currentTime=t.startTime}catch{}this.hls.startLoad(t.startTime)})}async preload(t){if(e.isSupported())return new Promise(s=>{const i=new e({startFragPrefetch:!0,autoStartLoad:!0});let r=!1;const n=()=>{if(!r){r=!0,clearTimeout(o),i.off(e.Events.FRAG_LOADED,a),i.off(e.Events.ERROR,d);try{i.destroy()}catch{}s()}},a=()=>n(),d=(e,t)=>{t.fatal&&n()},o=setTimeout(n,8e3);i.on(e.Events.FRAG_LOADED,a),i.on(e.Events.ERROR,d),i.loadSource(t)})}get maxLiveSeekPosition(){const e=this.hls.latestLevelDetails;if(!e?.live)return null;const t=this.hls.liveSyncPosition;return"number"==typeof t&&Number.isFinite(t)?t:null}get targetDuration(){const e=this.hls.latestLevelDetails,t=e?.targetduration;return e?.live&&"number"==typeof t&&Number.isFinite(t)&&t>0?t:null}requestLiveStartOffset(e,t){const s=e;if(null===s||!Number.isFinite(s)||s<=0)return!1;if(this.hls.latestLevelDetails)return!1;const i=this.hls.config;return!(void 0===i.liveMaxLatencyDuration&&i.liveMaxLatencyDurationCount<=s)&&(delete i.liveSyncDuration,i.liveSyncDurationCount=s,this.hls.userConfig.liveSyncDurationCount=s,!0)}setVideoQuality(e){if(null===e){if(null===this.levelBeforePin)return!1;const e=this.levelBeforePin;try{return this.hls.nextLevel=e,this.hls.nextLevel!==e?!1:(this.levelBeforePin=null,!0)}catch{return!1}}const t=this.hls.levels;if(!t||t.length<2)return!1;let s=0;for(let e=1;e<t.length;e++)t[e].bitrate<t[s].bitrate&&(s=e);const i=t[s].bitrate;if("number"!=typeof i||!Number.isFinite(i)||i<=0)return!1;const r=null===this.levelBeforePin;let n;r&&(this.levelBeforePin=this.hls.manualLevel);try{this.hls.nextLevel=s,n=this.hls.nextLevel===s}catch{n=!1}return!!n||(r&&(this.levelBeforePin=null),!1)}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t)}off(e,t){const s=this.eventListeners.get(e);s&&s.delete(t)}destroy(){for(const[e,t]of this.boundHandlers)this.video.removeEventListener(e,t);this.boundHandlers.clear(),this.eventListeners.clear(),this.deferredSeekTime=null,this.id3TrackListener&&(this.id3TrackListener.track.removeEventListener("cuechange",this.id3TrackListener.handler),this.id3TrackListener=null),this.parsedCues.clear(),this.presentedPts.clear()}setupHlsListeners(){this.hls.on(e.Events.MEDIA_ENDED,()=>{this.emitEndedOnce()}),this.hls.on(e.Events.FRAG_CHANGED,(e,t)=>{t.frag.programDateTime&&this.anchorProgramDateTime(t.frag.programDateTime,t.frag.start)}),this.hls.on(e.Events.LEVEL_LOADED,(e,t)=>{if(t.details.hasProgramDateTime&&t.details.fragments.length>0){const e=t.details.fragments[0];e.programDateTime&&this.anchorProgramDateTime(e.programDateTime,e.start)}}),this.hls.on(e.Events.FRAG_PARSING_METADATA,(e,i)=>{for(const e of i.samples??[]){const i=s(e.data);if(0===i.length){const t={type:"id3",startTime:e.pts,raw:e.data,phase:"parsed"};this.emit("timedmetadata",t);continue}const r=[];for(const s of i){const i={type:"id3",startTime:e.pts,description:s.description,mimeType:s.mimeType,messageData:t(s.data),raw:e.data,phase:"parsed"};r.push(i),this.emit("timedmetadata",i)}this.parsedCues.set(e.pts,r),this.pruneParsedCues()}this.attachId3CueEnterListener()})}attachId3CueEnterListener(){if(this.id3TrackListener)return;const e=this.video.textTracks;if(!e)return;let t;for(let s=0;s<e.length;s++){const i=e[s];if("metadata"===i.kind&&"id3"===i.label){t=i;break}}if(!t||"function"!=typeof t.addEventListener)return;"disabled"===t.mode&&(t.mode="hidden");const s=()=>{const e=t?.activeCues;if(e)for(let t=0;t<e.length;t++){const s=e[t].startTime;if(this.presentedPts.has(s))continue;const i=this.parsedCues.get(s);if(i){this.presentedPts.add(s);for(const e of i)this.emit("timedmetadata",{...e,phase:"presented"})}}};t.addEventListener("cuechange",s),this.id3TrackListener={track:t,handler:s}}pruneParsedCues(){const e=this.video.currentTime-120;for(const t of this.parsedCues.keys())t<e&&(this.parsedCues.delete(t),this.presentedPts.delete(t))}setupVideoListeners(){const e=()=>{this.updateProgramDateTime(),this.emit("timeupdate")},t=()=>{this.emitEndedOnce()},s=()=>{this.emit("error",this.video.error)},i=()=>{if(this.emit("seeked"),null!==this.deferredSeekTime){const e=this.deferredSeekTime;this.deferredSeekTime=null,this.seek(e)}},r=()=>{this.emit("volumechange")},n=()=>{this.emit("waiting")},a=()=>{this.endedEmitted=!1,this.emit("playing")},d=()=>{this.emit("pause")};this.video.addEventListener("timeupdate",e),this.video.addEventListener("ended",t),this.video.addEventListener("error",s),this.video.addEventListener("seeked",i),this.video.addEventListener("volumechange",r),this.video.addEventListener("waiting",n),this.video.addEventListener("playing",a),this.video.addEventListener("pause",d),this.boundHandlers.set("timeupdate",e),this.boundHandlers.set("ended",t),this.boundHandlers.set("error",s),this.boundHandlers.set("seeked",i),this.boundHandlers.set("volumechange",r),this.boundHandlers.set("waiting",n),this.boundHandlers.set("playing",a),this.boundHandlers.set("pause",d)}emitEndedOnce(){this.endedEmitted||(this.endedEmitted=!0,this.emit("ended"))}updateProgramDateTime(){const e=this.hls.levels,t=this.hls.currentLevel;if(t>=0&&e[t]?.details){const s=e[t].details;if(s?.hasProgramDateTime&&s.fragments.length>0){const e=s.fragments[0];e.programDateTime&&this.anchorProgramDateTime(e.programDateTime,e.start)}}}anchorProgramDateTime(e,t){this._programDateTime=new Date(e+1e3*(this.video.currentTime-t))}emit(e,t){const s=this.eventListeners.get(e);if(s)for(const e of s)try{e(t)}catch(e){console.error("[HlsJsAdapter] Event handler error:",e)}}}
1
+ import e from"hls.js";import{decodeUtf8 as t,parseId3GeobFrames as s}from"@dolby-optiview/ads-sdk-core/id3";import{HlsMediaError as i}from"./hlsErrorToMediaError";export class HlsJsAdapter{constructor(e,t){this.eventListeners=new Map,this.boundHandlers=new Map,this._programDateTime=null,this.deferredSeekTime=null,this.endedEmitted=!1,this.activeOperation=null,this.levelBeforePin=null,this.parsedCues=new Map,this.presentedPts=new Set,this.id3TrackListener=null,this.hls=e,this.video=t,this.setupHlsListeners(),this.setupVideoListeners()}get currentTime(){return this.video.currentTime}get duration(){return this.video.duration}get isLive(){const e=this.hls?.latestLevelDetails;return"boolean"==typeof e?.live?e.live: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 playbackRate(){return this.video.playbackRate}set playbackRate(e){this.video.playbackRate=e}get videoElement(){return this.video}get supportsParallelBuffering(){return e.isSupported()}get programDateTime(){return this._programDateTime}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.hls.url??null}async load(t){this.endedEmitted=!1;const s=this.beginOperation();return new Promise((r,n)=>{const a=()=>{this.hls.off(e.Events.MANIFEST_PARSED,a),this.hls.off(e.Events.ERROR,o),this.endOperation(s),r()},o=(t,r)=>{r.fatal&&(this.hls.off(e.Events.MANIFEST_PARSED,a),this.hls.off(e.Events.ERROR,o),this.endOperation(s),n(new i("HLS load error",r)))};this.hls.on(e.Events.MANIFEST_PARSED,a),this.hls.on(e.Events.ERROR,o),this.hls.loadSource(t),this.hls.attachMedia(this.video)})}unload(){this.deferredSeekTime=null,this.activeOperation=null,this.hls.stopLoad(),this.hls.detachMedia()}recreateMediaPipeline(t){this.deferredSeekTime=null;const s=this.beginOperation();return new Promise((r,n)=>{const a=()=>{this.video.removeEventListener("loadedmetadata",o),this.hls.off(e.Events.FRAG_BUFFERED,h),this.hls.off(e.Events.ERROR,l),this.endOperation(s)},o=()=>{d()},h=()=>{d()},d=()=>{Math.abs(this.video.currentTime-t.startTime)>.5&&(this.video.currentTime=t.startTime),a(),r()},l=(e,t)=>{t.fatal&&(a(),n(new i("HLS pipeline recreate error",t)))};this.video.addEventListener("loadedmetadata",o),this.hls.on(e.Events.FRAG_BUFFERED,h),this.hls.on(e.Events.ERROR,l),this.hls.detachMedia(),this.hls.attachMedia(this.video);try{this.video.currentTime=t.startTime}catch{}this.hls.startLoad(t.startTime)})}async preload(t){if(e.isSupported())return new Promise(s=>{const i=new e({startFragPrefetch:!0,autoStartLoad:!0});let r=!1;const n=()=>{if(!r){r=!0,clearTimeout(h),i.off(e.Events.FRAG_LOADED,a),i.off(e.Events.ERROR,o);try{i.destroy()}catch{}s()}},a=()=>n(),o=(e,t)=>{t.fatal&&n()},h=setTimeout(n,8e3);i.on(e.Events.FRAG_LOADED,a),i.on(e.Events.ERROR,o),i.loadSource(t)})}get maxLiveSeekPosition(){const e=this.hls.latestLevelDetails;if(!e?.live)return null;const t=this.hls.liveSyncPosition;return"number"==typeof t&&Number.isFinite(t)?t:null}get targetDuration(){const e=this.hls.latestLevelDetails,t=e?.targetduration;return e?.live&&"number"==typeof t&&Number.isFinite(t)&&t>0?t:null}requestLiveStartOffset(e,t){const s=e;if(null===s||!Number.isFinite(s)||s<=0)return!1;if(this.hls.latestLevelDetails)return!1;const i=this.hls.config;return!(void 0===i.liveMaxLatencyDuration&&i.liveMaxLatencyDurationCount<=s)&&(delete i.liveSyncDuration,i.liveSyncDurationCount=s,this.hls.userConfig.liveSyncDurationCount=s,!0)}setVideoQuality(e){if(null===e){if(null===this.levelBeforePin)return!1;const e=this.levelBeforePin;try{return this.hls.nextLevel=e,this.hls.nextLevel!==e?!1:(this.levelBeforePin=null,!0)}catch{return!1}}const t=this.hls.levels;if(!t||t.length<2)return!1;let s=0;for(let e=1;e<t.length;e++)t[e].bitrate<t[s].bitrate&&(s=e);const i=t[s].bitrate;if("number"!=typeof i||!Number.isFinite(i)||i<=0)return!1;const r=null===this.levelBeforePin;let n;r&&(this.levelBeforePin=this.hls.manualLevel);try{this.hls.nextLevel=s,n=this.hls.nextLevel===s}catch{n=!1}return!!n||(r&&(this.levelBeforePin=null),!1)}on(e,t){this.eventListeners.has(e)||this.eventListeners.set(e,new Set),this.eventListeners.get(e).add(t)}off(e,t){const s=this.eventListeners.get(e);s&&s.delete(t)}destroy(){for(const[e,t]of this.boundHandlers)this.video.removeEventListener(e,t);this.boundHandlers.clear(),this.eventListeners.clear(),this.deferredSeekTime=null,this.id3TrackListener&&(this.id3TrackListener.track.removeEventListener("cuechange",this.id3TrackListener.handler),this.id3TrackListener=null),this.parsedCues.clear(),this.presentedPts.clear()}beginOperation(){const e=Symbol("hls-operation");return this.activeOperation=e,e}endOperation(e){this.activeOperation===e&&(this.activeOperation=null)}setupHlsListeners(){this.hls.on(e.Events.ERROR,(e,t)=>{t.fatal&&null===this.activeOperation&&this.emit("error",new i("HLS playback error",t))}),this.hls.on(e.Events.MEDIA_ENDED,()=>{this.emitEndedOnce()}),this.hls.on(e.Events.FRAG_CHANGED,(e,t)=>{t.frag.programDateTime&&this.anchorProgramDateTime(t.frag.programDateTime,t.frag.start)}),this.hls.on(e.Events.LEVEL_LOADED,(e,t)=>{if(t.details.hasProgramDateTime&&t.details.fragments.length>0){const e=t.details.fragments[0];e.programDateTime&&this.anchorProgramDateTime(e.programDateTime,e.start)}}),this.hls.on(e.Events.FRAG_PARSING_METADATA,(e,i)=>{for(const e of i.samples??[]){const i=s(e.data);if(0===i.length){const t={type:"id3",startTime:e.pts,raw:e.data,phase:"parsed"};this.emit("timedmetadata",t);continue}const r=[];for(const s of i){const i={type:"id3",startTime:e.pts,description:s.description,mimeType:s.mimeType,messageData:t(s.data),raw:e.data,phase:"parsed"};r.push(i),this.emit("timedmetadata",i)}this.parsedCues.set(e.pts,r),this.pruneParsedCues()}this.attachId3CueEnterListener()})}attachId3CueEnterListener(){if(this.id3TrackListener)return;const e=this.video.textTracks;if(!e)return;let t;for(let s=0;s<e.length;s++){const i=e[s];if("metadata"===i.kind&&"id3"===i.label){t=i;break}}if(!t||"function"!=typeof t.addEventListener)return;"disabled"===t.mode&&(t.mode="hidden");const s=()=>{const e=t?.activeCues;if(e)for(let t=0;t<e.length;t++){const s=e[t].startTime;if(this.presentedPts.has(s))continue;const i=this.parsedCues.get(s);if(i){this.presentedPts.add(s);for(const e of i)this.emit("timedmetadata",{...e,phase:"presented"})}}};t.addEventListener("cuechange",s),this.id3TrackListener={track:t,handler:s}}pruneParsedCues(){const e=this.video.currentTime-120;for(const t of this.parsedCues.keys())t<e&&(this.parsedCues.delete(t),this.presentedPts.delete(t))}setupVideoListeners(){const e=()=>{this.updateProgramDateTime(),this.emit("timeupdate")},t=()=>{this.emitEndedOnce()},s=()=>{this.emit("error",this.video.error)},i=()=>{if(this.emit("seeked"),null!==this.deferredSeekTime){const e=this.deferredSeekTime;this.deferredSeekTime=null,this.seek(e)}},r=()=>{this.emit("volumechange")},n=()=>{this.emit("waiting")},a=()=>{this.endedEmitted=!1,this.emit("playing")},o=()=>{this.emit("pause")};this.video.addEventListener("timeupdate",e),this.video.addEventListener("ended",t),this.video.addEventListener("error",s),this.video.addEventListener("seeked",i),this.video.addEventListener("volumechange",r),this.video.addEventListener("waiting",n),this.video.addEventListener("playing",a),this.video.addEventListener("pause",o),this.boundHandlers.set("timeupdate",e),this.boundHandlers.set("ended",t),this.boundHandlers.set("error",s),this.boundHandlers.set("seeked",i),this.boundHandlers.set("volumechange",r),this.boundHandlers.set("waiting",n),this.boundHandlers.set("playing",a),this.boundHandlers.set("pause",o)}emitEndedOnce(){this.endedEmitted||(this.endedEmitted=!0,this.emit("ended"))}updateProgramDateTime(){const e=this.hls.levels,t=this.hls.currentLevel;if(t>=0&&e[t]?.details){const s=e[t].details;if(s?.hasProgramDateTime&&s.fragments.length>0){const e=s.fragments[0];e.programDateTime&&this.anchorProgramDateTime(e.programDateTime,e.start)}}}anchorProgramDateTime(e,t){this._programDateTime=new Date(e+1e3*(this.video.currentTime-t))}emit(e,t){const s=this.eventListeners.get(e);if(s)for(const e of s)try{e(t)}catch(e){console.error("[HlsJsAdapter] Event handler error:",e)}}}
@@ -0,0 +1,25 @@
1
+ /** The subset of an hls.js `ErrorData` the mapping reads. */
2
+ export interface HlsErrorData {
3
+ type?: string;
4
+ details?: string;
5
+ fatal?: boolean;
6
+ response?: {
7
+ code?: number;
8
+ } | undefined;
9
+ }
10
+ /**
11
+ * A fatal hls.js error with an `HTMLMediaElement` `MediaError`-style `code` (2 network,
12
+ * 3 decode, 4 unsupported) so core classifies it like every other player. `code` is
13
+ * `undefined` when hls.js gives no usable cause.
14
+ */
15
+ export declare class HlsMediaError extends Error {
16
+ readonly code: number | undefined;
17
+ readonly details: string;
18
+ readonly httpStatus: number | undefined;
19
+ constructor(prefix: string, data: HlsErrorData);
20
+ }
21
+ /**
22
+ * The `MediaError` code for an hls.js error. Mapped on `details` first: hls.js raises a
23
+ * playlist that is not M3U8 (`manifestParsingError`) with type `networkError`.
24
+ */
25
+ export declare function hlsErrorToMediaErrorCode(data: HlsErrorData): number | undefined;
@@ -0,0 +1 @@
1
+ const r=new Set(["manifestLoadError","manifestLoadTimeOut","levelLoadError","levelLoadTimeOut","audioTrackLoadError","audioTrackLoadTimeOut","subtitleTrackLoadError","subtitleTrackLoadTimeOut","fragLoadError","fragLoadTimeOut","keyLoadError","keyLoadTimeOut"]),e=new Set(["manifestParsingError","manifestIncompatibleCodecsError","levelEmptyError","levelParsingError","fragParsingError","bufferAddCodecError","bufferIncompatibleCodecsError"]),o=new Set(["bufferAppendError","fragDecryptError"]);export class HlsMediaError extends Error{constructor(r,e){const o=e.details??"unknown",t=e.response?.code,a="number"==typeof t&&t>0?t:void 0;super(`${r}: ${o}${void 0!==a?` (HTTP ${a})`:""}`),this.name="HlsMediaError",this.code=hlsErrorToMediaErrorCode(e),this.details=o,this.httpStatus=a}}export function hlsErrorToMediaErrorCode(t){const a=t.details??"";if(e.has(a))return 4;if(r.has(a))return 2;if(o.has(a))return 3;switch(t.type){case"networkError":return 2;case"mediaError":return 3;case"muxError":return 4;default:return}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dolby-optiview/ads-sdk-adapter-hlsjs",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "HLS.js 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.1.0"
41
+ "@dolby-optiview/ads-sdk-core": "^1.2.0"
42
42
  },
43
43
  "peerDependencies": {
44
44
  "hls.js": "^1.0.0"