@100mslive/hls-player 0.4.3-alpha.3 → 0.4.3-alpha.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@100mslive/hls-player",
3
- "version": "0.4.3-alpha.3",
3
+ "version": "0.4.3-alpha.4",
4
4
  "description": "HLS client library which uses HTML5 Video element and Media Source Extension for playback",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.js",
@@ -11,7 +11,6 @@
11
11
  "directory": "packages/hls-player"
12
12
  },
13
13
  "files": [
14
- "src",
15
14
  "dist"
16
15
  ],
17
16
  "exports": {
@@ -36,7 +35,7 @@
36
35
  "author": "100ms",
37
36
  "license": "MIT",
38
37
  "dependencies": {
39
- "@100mslive/hls-stats": "0.5.3-alpha.3",
38
+ "@100mslive/hls-stats": "0.5.3-alpha.4",
40
39
  "eventemitter2": "^6.4.9",
41
40
  "hls.js": "1.4.12"
42
41
  },
@@ -48,5 +47,5 @@
48
47
  "conferencing",
49
48
  "100ms"
50
49
  ],
51
- "gitHead": "43ae4fb9ef619c492c010d185fe7200bd1e3fa45"
50
+ "gitHead": "f4feef2572dd196edc136c16a0b0debbfb00f39a"
52
51
  }
@@ -1,399 +0,0 @@
1
- import { HlsPlayerStats, HlsStats } from '@100mslive/hls-stats';
2
- import Hls, { ErrorData, HlsConfig, Level, LevelParsed } from 'hls.js';
3
- import { HMSHLSTimedMetadata } from './HMSHLSTimedMetadata';
4
- import { HMSHLSErrorFactory } from '../error/HMSHLSErrorFactory';
5
- import { HMSHLSException } from '../error/HMSHLSException';
6
- import { HMSHLSPlayerEventEmitter, HMSHLSPlayerListeners, IHMSHLSPlayerEventEmitter } from '../interfaces/events';
7
- import { HMSHLSLayer } from '../interfaces/IHMSHLSLayer';
8
- import IHMSHLSPlayer from '../interfaces/IHMSHLSPlayer';
9
- import { HLS_DEFAULT_ALLOWED_MAX_LATENCY_DELAY, HLSPlaybackState, HMSHLSPlayerEvents } from '../utilies/constants';
10
- import { mapLayer, mapLayers } from '../utilies/utils';
11
-
12
- export class HMSHLSPlayer implements IHMSHLSPlayer, IHMSHLSPlayerEventEmitter {
13
- private _hls: Hls;
14
- private _hlsUrl: string;
15
- private _hlsStats: HlsStats;
16
- private _videoEl: HTMLVideoElement;
17
- private _emitter: HMSHLSPlayerEventEmitter;
18
- private _subscribeHlsStats?: (() => void) | null = null;
19
- private _isLive: boolean;
20
- private _volume: number;
21
- private _metaData: HMSHLSTimedMetadata;
22
- private readonly TAG = '[HMSHLSPlayer]';
23
- /**
24
- * Initiliaze the player with hlsUrl and video element
25
- * @remarks If video element is not passed, we will create one and call a method getVideoElement get element
26
- * @param hlsUrl required - Pass hls url to
27
- * @param videoEl optional field - HTML video element
28
- */
29
- constructor(hlsUrl: string, videoEl?: HTMLVideoElement) {
30
- this._hls = new Hls(this.getPlayerConfig());
31
- this._emitter = new HMSHLSPlayerEventEmitter();
32
- this._hlsUrl = hlsUrl;
33
- this._videoEl = videoEl || this.createVideoElement();
34
- try {
35
- const url = new URL(hlsUrl);
36
- if (!url.pathname.endsWith('m3u8')) {
37
- throw HMSHLSErrorFactory.HLSMediaError.hlsURLNotFound('Invalid URL, pass m3u8 url');
38
- }
39
- } catch (e) {
40
- throw HMSHLSErrorFactory.HLSMediaError.hlsURLNotFound();
41
- }
42
- this._hls.loadSource(hlsUrl);
43
- this._hls.attachMedia(this._videoEl);
44
- this._isLive = true;
45
- this._volume = this._videoEl.volume * 100;
46
- this._hlsStats = new HlsStats(this._hls, this._videoEl);
47
- this.listenHLSEvent();
48
- this._metaData = new HMSHLSTimedMetadata(this._hls, this._videoEl, this.emitEvent);
49
- this.seekToLivePosition();
50
- }
51
-
52
- /**
53
- * @remarks It will create a video element with playiniline true.
54
- * @returns HTML video element
55
- */
56
- private createVideoElement(): HTMLVideoElement {
57
- if (this._videoEl) {
58
- return this._videoEl;
59
- }
60
- const video: HTMLVideoElement = document.createElement('video');
61
- video.playsInline = true;
62
- video.controls = false;
63
- video.autoplay = true;
64
- return video;
65
- }
66
-
67
- /**
68
- * @returns get html video element
69
- */
70
- getVideoElement(): HTMLVideoElement {
71
- return this._videoEl;
72
- }
73
- /**
74
- * Subscribe to hls stats
75
- */
76
- private subscribeStats = (interval = 2000) => {
77
- this._subscribeHlsStats = this._hlsStats.subscribe((state: HlsPlayerStats) => {
78
- this.emitEvent(HMSHLSPlayerEvents.STATS, state);
79
- }, interval);
80
- };
81
- /**
82
- * Unsubscribe to hls stats
83
- */
84
- private unsubscribeStats = () => {
85
- if (this._subscribeHlsStats) {
86
- this._subscribeHlsStats();
87
- }
88
- };
89
- // reset the controller
90
- reset() {
91
- if (this._hls && this._hls.media) {
92
- this._hls.detachMedia();
93
- this.unsubscribeStats();
94
- }
95
- if (this._metaData) {
96
- this._metaData.unregisterListener();
97
- }
98
- if (Hls.isSupported()) {
99
- this._hls.off(Hls.Events.MANIFEST_LOADED, this.manifestLoadedHandler);
100
- this._hls.off(Hls.Events.LEVEL_UPDATED, this.levelUpdatedHandler);
101
- this._hls.off(Hls.Events.ERROR, this.handleHLSException);
102
- }
103
- if (this._videoEl) {
104
- this._videoEl.removeEventListener('play', this.playEventHandler);
105
- this._videoEl.removeEventListener('pause', this.pauseEventHandler);
106
- this._videoEl.removeEventListener('timeupdate', this.handleTimeUpdateListener);
107
- this._videoEl.removeEventListener('volumechange', this.volumeEventHandler);
108
- }
109
- this.removeAllListeners();
110
- }
111
-
112
- on = <E extends HMSHLSPlayerEvents>(eventName: E, listener: HMSHLSPlayerListeners<E>) => {
113
- this._emitter.on(eventName, listener);
114
- };
115
-
116
- off = <E extends HMSHLSPlayerEvents>(eventName: E, listener: HMSHLSPlayerListeners<E>) => {
117
- this._emitter.off(eventName, listener);
118
- };
119
-
120
- emitEvent = <E extends HMSHLSPlayerEvents>(
121
- eventName: E,
122
- eventObject: Parameters<HMSHLSPlayerListeners<E>>[0],
123
- ): boolean => {
124
- if (eventName === HMSHLSPlayerEvents.ERROR) {
125
- const hlsError = eventObject as HMSHLSException;
126
- if (hlsError?.isTerminal) {
127
- // send analytics event
128
- window?.__hms?.sdk?.sendHLSAnalytics(hlsError);
129
- }
130
- }
131
- return this._emitter.emitEvent(eventName, eventObject);
132
- };
133
-
134
- private removeAllListeners = <E extends HMSHLSPlayerEvents>(eventName?: E): void => {
135
- this._emitter.removeAllListeners(eventName);
136
- };
137
-
138
- public get volume(): number {
139
- return this._volume;
140
- }
141
-
142
- setVolume(volume: number) {
143
- this._videoEl.volume = volume / 100;
144
- this._volume = volume;
145
- }
146
-
147
- getLayer(): HMSHLSLayer | null {
148
- if (this._hls && this._hls.currentLevel !== -1) {
149
- const currentLevel = this._hls?.levels.at(this._hls?.currentLevel);
150
- return currentLevel ? mapLayer(currentLevel) : null;
151
- }
152
- return null;
153
- }
154
-
155
- setLayer(layer: HMSHLSLayer): void {
156
- if (this._hls) {
157
- const current = this._hls.levels.findIndex((level: Level) => {
158
- return level?.attrs?.RESOLUTION === layer?.resolution;
159
- });
160
- this._hls.nextLevel = current;
161
- }
162
- return;
163
- }
164
- /**
165
- * set current stream to Live
166
- */
167
- async seekToLivePosition() {
168
- let end = 0;
169
- if (this._videoEl.buffered.length > 0) {
170
- end = this._videoEl.buffered.end(this._videoEl.buffered.length - 1);
171
- }
172
- this._videoEl.currentTime = this._hls.liveSyncPosition || end;
173
- if (this._videoEl.paused) {
174
- try {
175
- await this.playVideo();
176
- } catch (err) {
177
- console.error(this.TAG, 'Attempt to jump to live position Failed.', err);
178
- }
179
- }
180
- }
181
- /**
182
- * Play stream
183
- */
184
- play = async () => {
185
- await this.playVideo();
186
- };
187
- /**
188
- * Pause stream
189
- */
190
- pause = () => {
191
- this.pauseVideo();
192
- };
193
- /**
194
- * It will update the video element current time
195
- * @param seekValue Pass currentTime in second
196
- */
197
- seekTo = (seekValue: number) => {
198
- this._videoEl.currentTime = seekValue;
199
- };
200
-
201
- hasCaptions = () => {
202
- return this._hls.subtitleTracks.length > 0;
203
- };
204
-
205
- toggleCaption = () => {
206
- // no subtitles, do nothing
207
- if (!this.hasCaptions()) {
208
- return;
209
- }
210
- this._hls.subtitleDisplay = !this._hls.subtitleDisplay;
211
- this.emitEvent(HMSHLSPlayerEvents.CAPTION_ENABLED, this._hls.subtitleDisplay);
212
- };
213
-
214
- private playVideo = async () => {
215
- try {
216
- if (this._videoEl.paused) {
217
- await this._videoEl.play();
218
- }
219
- } catch (error) {
220
- console.debug(this.TAG, 'Play failed with error', (error as Error).message);
221
- if ((error as Error).name === 'NotAllowedError') {
222
- this.emitEvent(HMSHLSPlayerEvents.AUTOPLAY_BLOCKED, HMSHLSErrorFactory.HLSMediaError.autoplayFailed());
223
- }
224
- }
225
- };
226
- private pauseVideo = () => {
227
- if (!this._videoEl.paused) {
228
- this._videoEl.pause();
229
- }
230
- };
231
- private playEventHandler = () => {
232
- this.emitEvent(HMSHLSPlayerEvents.PLAYBACK_STATE, {
233
- state: HLSPlaybackState.playing,
234
- });
235
- };
236
- private pauseEventHandler = () => {
237
- this.emitEvent(HMSHLSPlayerEvents.PLAYBACK_STATE, {
238
- state: HLSPlaybackState.paused,
239
- });
240
- };
241
- private volumeEventHandler = () => {
242
- this._volume = Math.round(this._videoEl.volume * 100);
243
- };
244
-
245
- private reConnectToStream = () => {
246
- window.addEventListener(
247
- 'online',
248
- () => {
249
- this._hls.startLoad();
250
- },
251
- {
252
- once: true,
253
- },
254
- );
255
- };
256
- // eslint-disable-next-line complexity
257
- private handleHLSException = (_: any, data: ErrorData) => {
258
- console.error(this.TAG, `error type ${data.type} with details ${data.details} is fatal ${data.fatal}`);
259
- const details = data.error?.message || data.err?.message || '';
260
- const detail = {
261
- details: details,
262
- fatal: data.fatal,
263
- };
264
- if (!detail.fatal) {
265
- return;
266
- }
267
- switch (data.details) {
268
- case Hls.ErrorDetails.MANIFEST_INCOMPATIBLE_CODECS_ERROR: {
269
- const error = HMSHLSErrorFactory.HLSMediaError.manifestIncompatibleCodecsError(detail);
270
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
271
- break;
272
- }
273
- case Hls.ErrorDetails.FRAG_DECRYPT_ERROR: {
274
- const error = HMSHLSErrorFactory.HLSMediaError.fragDecryptError(detail);
275
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
276
- break;
277
- }
278
- case Hls.ErrorDetails.BUFFER_INCOMPATIBLE_CODECS_ERROR: {
279
- const error = HMSHLSErrorFactory.HLSMediaError.bufferIncompatibleCodecsError(detail);
280
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
281
- break;
282
- }
283
- // Below ones are network related errors
284
- case Hls.ErrorDetails.MANIFEST_LOAD_ERROR: {
285
- const error = HMSHLSErrorFactory.HLSNetworkError.manifestLoadError(detail);
286
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
287
- break;
288
- }
289
- case Hls.ErrorDetails.MANIFEST_PARSING_ERROR: {
290
- const error = HMSHLSErrorFactory.HLSNetworkError.manifestParsingError(detail);
291
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
292
- break;
293
- }
294
- case Hls.ErrorDetails.LEVEL_LOAD_ERROR: {
295
- const error = HMSHLSErrorFactory.HLSNetworkError.layerLoadError(detail);
296
- if (!navigator.onLine) {
297
- this.reConnectToStream();
298
- } else {
299
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
300
- }
301
- break;
302
- }
303
- default: {
304
- const error = HMSHLSErrorFactory.HLSError(detail, data.type, data.details);
305
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
306
- break;
307
- }
308
- }
309
- };
310
- private manifestLoadedHandler = (_: any, { levels }: { levels: LevelParsed[] }) => {
311
- const layers: HMSHLSLayer[] = mapLayers(this.removeAudioLevels(levels));
312
- this.emitEvent(HMSHLSPlayerEvents.MANIFEST_LOADED, {
313
- layers,
314
- });
315
- };
316
- private levelUpdatedHandler = (_: any, { level }: { level: number }) => {
317
- const qualityLayer: HMSHLSLayer = mapLayer(this._hls.levels[level]);
318
- this.emitEvent(HMSHLSPlayerEvents.LAYER_UPDATED, {
319
- layer: qualityLayer,
320
- });
321
- };
322
-
323
- private handleTimeUpdateListener = (_: Event) => {
324
- this.emitEvent(HMSHLSPlayerEvents.CURRENT_TIME, this._videoEl.currentTime);
325
- const live = this._hls.liveSyncPosition
326
- ? this._hls.liveSyncPosition - this._videoEl.currentTime <= HLS_DEFAULT_ALLOWED_MAX_LATENCY_DELAY
327
- : false;
328
- if (this._isLive !== live) {
329
- this._isLive = live;
330
- this.emitEvent(HMSHLSPlayerEvents.SEEK_POS_BEHIND_LIVE_EDGE, {
331
- isLive: this._isLive,
332
- });
333
- }
334
- };
335
- /**
336
- * Listen to hlsjs and video related events
337
- */
338
- private listenHLSEvent() {
339
- if (Hls.isSupported()) {
340
- this._hls.on(Hls.Events.MANIFEST_LOADED, this.manifestLoadedHandler);
341
- this._hls.on(Hls.Events.LEVEL_UPDATED, this.levelUpdatedHandler);
342
- this._hls.on(Hls.Events.ERROR, this.handleHLSException);
343
- this.subscribeStats();
344
- } else if (this._videoEl.canPlayType('application/vnd.apple.mpegurl')) {
345
- // code for ios safari, mseNot Supported.
346
- this._videoEl.src = this._hlsUrl;
347
- }
348
- this._videoEl.addEventListener('timeupdate', this.handleTimeUpdateListener);
349
- this._videoEl.addEventListener('play', this.playEventHandler);
350
- this._videoEl.addEventListener('pause', this.pauseEventHandler);
351
- this._videoEl.addEventListener('volumechange', this.volumeEventHandler);
352
- }
353
- /**
354
- * 1 min retries before user came online, reason room automatically disconnected if user is offline for more than 1mins
355
- * Retries logic will run exponential like (1, 2, 4, 8, 8, 8, 8, 8, 8, 8secs)
356
- * there will be total 10 retries
357
- */
358
- private getPlayerConfig(): Partial<HlsConfig> {
359
- return {
360
- enableWorker: true,
361
- maxBufferLength: 20,
362
- backBufferLength: 10,
363
- abrBandWidthUpFactor: 1,
364
- playlistLoadPolicy: {
365
- default: {
366
- maxTimeToFirstByteMs: 8000,
367
- maxLoadTimeMs: 20000,
368
- timeoutRetry: {
369
- maxNumRetry: 10,
370
- retryDelayMs: 1000,
371
- maxRetryDelayMs: 8000,
372
- backoff: 'exponential',
373
- },
374
- errorRetry: {
375
- maxNumRetry: 10,
376
- retryDelayMs: 1000,
377
- maxRetryDelayMs: 8000,
378
- backoff: 'exponential',
379
- },
380
- },
381
- },
382
- };
383
- }
384
-
385
- /**
386
- * @param {Array} levels array
387
- * @returns a new array with only video levels.
388
- */
389
- private removeAudioLevels(levels: LevelParsed[]) {
390
- return levels.filter(({ videoCodec, width, height }) => !!videoCodec || !!(width && height));
391
- }
392
-
393
- /**
394
- * @returns true if HLS player is supported in the browser
395
- */
396
- public static isSupported(): boolean {
397
- return Hls.isSupported();
398
- }
399
- }
@@ -1,119 +0,0 @@
1
- import Hls, { Fragment } from 'hls.js';
2
- import { HMSHLSErrorFactory } from '../error/HMSHLSErrorFactory';
3
- import { HMSHLSPlayerListeners } from '../interfaces/events';
4
- import { HMSHLSPlayerEvents } from '../utilies/constants';
5
- import { metadataPayloadParser } from '../utilies/utils';
6
-
7
- export class HMSHLSTimedMetadata {
8
- private hls: Hls;
9
- constructor(
10
- hls: Hls,
11
- private videoEl: HTMLVideoElement,
12
- private emitEvent: <E extends HMSHLSPlayerEvents>(
13
- eventName: E,
14
- eventObject: Parameters<HMSHLSPlayerListeners<E>>[0],
15
- ) => boolean,
16
- ) {
17
- this.hls = hls;
18
- this.registerListner();
19
- }
20
- extractMetaTextTrack = (): TextTrack | null => {
21
- const textTrackListCount = this.videoEl.textTracks.length || 0;
22
- for (let trackIndex = 0; trackIndex < textTrackListCount; trackIndex++) {
23
- const textTrack = this.videoEl.textTracks[trackIndex];
24
- if (textTrack?.kind !== 'metadata') {
25
- continue;
26
- }
27
- textTrack.mode = 'showing';
28
- return textTrack;
29
- }
30
- return null;
31
- };
32
-
33
- // sync time with cue and trigger event
34
- fireCues = (currentAbsTime: number, tolerance: number) => {
35
- const cues = this.extractMetaTextTrack()?.cues;
36
- if (!cues) {
37
- return;
38
- }
39
- const cuesLength = cues.length;
40
- let cueIndex = 0;
41
- while (cueIndex < cuesLength) {
42
- const cue = cues[cueIndex] as TextTrackCue & {
43
- queued: boolean;
44
- value: { data: string };
45
- };
46
- if (cue.queued) {
47
- cueIndex++;
48
- continue;
49
- }
50
- // here we are converting base64 to actual data.
51
- const data: Record<string, any> = metadataPayloadParser(cue.value.data);
52
- const startDate = data.start_date;
53
- const endDate = data.end_date;
54
- const timeDiff = new Date(startDate).getTime() - currentAbsTime;
55
- const duration = new Date(endDate).getTime() - new Date(startDate).getTime();
56
- if (timeDiff <= tolerance) {
57
- setTimeout(() => {
58
- this.emitEvent(HMSHLSPlayerEvents.TIMED_METADATA_LOADED, {
59
- id: cue?.id,
60
- payload: data.payload,
61
- duration: duration,
62
- startDate: new Date(startDate),
63
- endDate: new Date(endDate),
64
- });
65
- }, timeDiff);
66
- cue.queued = true;
67
- }
68
- cueIndex++;
69
- }
70
- };
71
-
72
- // handle time update listener
73
- handleTimeUpdateListener = () => {
74
- // extract timed metadata text track
75
- const metaTextTrack: TextTrack | null = this.extractMetaTextTrack();
76
- if (!metaTextTrack || !metaTextTrack.cues) {
77
- return;
78
- }
79
- // @ts-ignore
80
- const firstFragProgramDateTime = this.videoEl?.getStartDate() || 0;
81
- const currentAbsTime = new Date(firstFragProgramDateTime).getTime() + (this.videoEl.currentTime || 0) * 1000;
82
- // fire cue for timed meta data extract
83
- this.fireCues(currentAbsTime, 0.25);
84
- };
85
- /**
86
- * Metadata are automatically parsed and added to the video element's
87
- * textTrack cue by hlsjs as they come through the stream.
88
- * in FRAG_CHANGED, we read the cues and emitEvent HLS_METADATA_LOADED
89
- * when the current fragment has a metadata to play.
90
- */
91
- fragChangeHandler = (_: any, { frag }: { frag: Fragment }) => {
92
- if (!this.videoEl) {
93
- const error = HMSHLSErrorFactory.HLSMediaError.videoElementNotFound();
94
- this.emitEvent(HMSHLSPlayerEvents.ERROR, error);
95
- }
96
- try {
97
- if (this.videoEl.textTracks.length === 0) {
98
- return;
99
- }
100
- const fragStartTime = frag.programDateTime || 0;
101
- const fragmentDuration = frag.end - frag.start;
102
- this.fireCues(fragStartTime, fragmentDuration);
103
- } catch (e) {
104
- console.error('FRAG_CHANGED event error', e);
105
- }
106
- };
107
- private registerListner = () => {
108
- if (Hls.isSupported()) {
109
- this.hls.on(Hls.Events.FRAG_CHANGED, this.fragChangeHandler);
110
- } else if (this.videoEl.canPlayType('application/vnd.apple.mpegurl')) {
111
- this.videoEl.addEventListener('timeupdate', this.handleTimeUpdateListener);
112
- }
113
- };
114
-
115
- unregisterListener = () => {
116
- this.hls.off(Hls.Events.FRAG_CHANGED, this.fragChangeHandler);
117
- this.videoEl.removeEventListener('timeupdate', this.handleTimeUpdateListener);
118
- };
119
- }
@@ -1,92 +0,0 @@
1
- import { HMSHLSException } from './HMSHLSException';
2
- import { HMSHLSExceptionEvents } from '../utilies/constants';
3
-
4
- export type HMSHLSErrorDetails = {
5
- details: string;
6
- fatal?: boolean;
7
- };
8
- export const HMSHLSErrorFactory = {
9
- HLSNetworkError: {
10
- manifestLoadError(data: HMSHLSErrorDetails): HMSHLSException {
11
- return new HMSHLSException(
12
- HMSHLSExceptionEvents.MANIFEST_LOAD_ERROR,
13
- data.details,
14
- 'Unable to load manifest file',
15
- data.fatal,
16
- );
17
- },
18
- manifestParsingError(data: HMSHLSErrorDetails): HMSHLSException {
19
- return new HMSHLSException(
20
- HMSHLSExceptionEvents.MANIFEST_PARSING_ERROR,
21
- data.details,
22
- 'Unable to parse manifest file',
23
- data.fatal,
24
- );
25
- },
26
- layerLoadError(data: HMSHLSErrorDetails): HMSHLSException {
27
- return new HMSHLSException(
28
- HMSHLSExceptionEvents.LAYER_LOAD_ERROR,
29
- data.details,
30
- 'Unable to load quality layers',
31
- data.fatal,
32
- );
33
- },
34
- },
35
- HLSMediaError: {
36
- manifestIncompatibleCodecsError(data: HMSHLSErrorDetails): HMSHLSException {
37
- return new HMSHLSException(
38
- HMSHLSExceptionEvents.MANIFEST_INCOMPATIBLE_CODECS_ERROR,
39
- data.details,
40
- 'Incompatible manifest codecs',
41
- data.fatal,
42
- );
43
- },
44
- fragDecryptError(data: HMSHLSErrorDetails): HMSHLSException {
45
- return new HMSHLSException(
46
- HMSHLSExceptionEvents.FRAG_DECRYPT_ERROR,
47
- data.details,
48
- 'Unable to decrypt fragment',
49
- data.fatal,
50
- );
51
- },
52
- bufferIncompatibleCodecsError(data: HMSHLSErrorDetails): HMSHLSException {
53
- return new HMSHLSException(
54
- HMSHLSExceptionEvents.BUFFER_INCOMPATIBLE_CODECS_ERROR,
55
- data.details,
56
- 'Incompatible buffer codecs',
57
- data.fatal,
58
- );
59
- },
60
- videoElementNotFound(): HMSHLSException {
61
- return new HMSHLSException(
62
- HMSHLSExceptionEvents.VIDEO_ELEMENT_NOT_FOUND,
63
- 'Video element not found',
64
- 'Video element not found',
65
- false,
66
- );
67
- },
68
- autoplayFailed(): HMSHLSException {
69
- return new HMSHLSException(
70
- HMSHLSExceptionEvents.HLS_AUTOPLAY_FAILED,
71
- 'Failed to autoplay',
72
- 'Failed to autoplay',
73
- false,
74
- );
75
- },
76
- hlsURLNotFound(msg?: string): HMSHLSException {
77
- return new HMSHLSException(
78
- HMSHLSExceptionEvents.HLS_URL_NOT_FOUND,
79
- msg || 'hls url not found',
80
- msg || 'hls url not found',
81
- true,
82
- );
83
- },
84
- },
85
- HLSError: (
86
- data: HMSHLSErrorDetails,
87
- name: string | HMSHLSExceptionEvents = HMSHLSExceptionEvents.HLS_ERROR,
88
- description = 'Hls error',
89
- ): HMSHLSException => {
90
- return new HMSHLSException(name, data.details, description, data.fatal);
91
- },
92
- };
@@ -1,38 +0,0 @@
1
- export class HMSHLSException extends Error {
2
- nativeError?: Error;
3
-
4
- constructor(
5
- public name: string,
6
- public message: string,
7
- public description: string,
8
- public isTerminal: boolean = false,
9
- ) {
10
- super(message);
11
-
12
- // Ref: https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work
13
- Object.setPrototypeOf(this, HMSHLSException.prototype);
14
- }
15
-
16
- toAnalyticsProperties() {
17
- return {
18
- error_name: this.name,
19
- error_message: this.message,
20
- error_description: this.description,
21
- is_terminal: this.isTerminal,
22
- };
23
- }
24
-
25
- addNativeError(error: Error) {
26
- this.nativeError = error;
27
- }
28
-
29
- toString() {
30
- return `{
31
- name: ${this.name};
32
- message: ${this.message};
33
- description: ${this.description};
34
- isTerminal: ${this.isTerminal};
35
- nativeError: ${this.nativeError?.message};
36
- }`;
37
- }
38
- }
package/src/index.ts DELETED
@@ -1,7 +0,0 @@
1
- import { HlsPlayerStats } from '@100mslive/hls-stats';
2
- import { HMSHLSPlayer } from './controllers/HMSHLSPlayer';
3
- import { HMSHLSException } from './error/HMSHLSException';
4
- import { HMSHLSLayer } from './interfaces/IHMSHLSLayer';
5
- import { HLSPlaybackState, HMSHLSPlayerEvents } from './utilies/constants';
6
- export type { HMSHLSLayer, HMSHLSException, HlsPlayerStats };
7
- export { HMSHLSPlayer, HLSPlaybackState, HMSHLSPlayerEvents };
@@ -1,10 +0,0 @@
1
- import { LevelParsed } from 'hls.js';
2
-
3
- export declare interface HMSHLSLayer extends Partial<LevelParsed> {
4
- readonly bitrate: number;
5
- readonly height?: number;
6
- readonly id?: number;
7
- readonly width?: number;
8
- url: string;
9
- resolution?: string;
10
- }
@@ -1,46 +0,0 @@
1
- import { HMSHLSLayer } from './IHMSHLSLayer';
2
- interface IHMSHLSPlayer {
3
- /**
4
- * @returns get html video element
5
- */
6
- getVideoElement(): HTMLVideoElement;
7
-
8
- /**
9
- * set video volumne
10
- * @param { volume } - in range [0,100]
11
- */
12
- setVolume(volume: number): void;
13
- /**
14
- *
15
- * @returns returns HMSHLSLayer which represents current
16
- * quality.
17
- */
18
- getLayer(): HMSHLSLayer | null;
19
- /**
20
- *
21
- * @param { HMSHLSLayer } layer - layer we want to set the stream to.
22
- * set { height: auto } to set the layer to auto
23
- */
24
- setLayer(layer: HMSHLSLayer): void;
25
- /**
26
- * move the video to Live
27
- */
28
- seekToLivePosition(): Promise<void>;
29
- /**
30
- * play stream
31
- * call this when autoplay error is received
32
- */
33
- play(): Promise<void>;
34
- /**
35
- * pause stream
36
- */
37
- pause(): void;
38
-
39
- /**
40
- * It will update the video element current time
41
- * @param seekValue Pass currentTime in second
42
- */
43
- seekTo(seekValue: number): void;
44
- }
45
-
46
- export default IHMSHLSPlayer;
@@ -1,70 +0,0 @@
1
- import { HlsPlayerStats } from '@100mslive/hls-stats';
2
- import { EventEmitter2 as EventEmitter } from 'eventemitter2';
3
- import { HMSHLSLayer } from './IHMSHLSLayer';
4
- import { HMSHLSException } from '../error/HMSHLSException';
5
- import { HLSPlaybackState, HMSHLSPlayerEvents } from '../utilies/constants';
6
-
7
- type HMSHLSListenerDataMapping = {
8
- [HMSHLSPlayerEvents.SEEK_POS_BEHIND_LIVE_EDGE]: HMSHLSStreamLive;
9
- [HMSHLSPlayerEvents.TIMED_METADATA_LOADED]: HMSHLSCue;
10
- [HMSHLSPlayerEvents.STATS]: HlsPlayerStats;
11
- [HMSHLSPlayerEvents.PLAYBACK_STATE]: HMSHLSPlaybackState;
12
- [HMSHLSPlayerEvents.CAPTION_ENABLED]: boolean;
13
-
14
- [HMSHLSPlayerEvents.ERROR]: HMSHLSException;
15
- [HMSHLSPlayerEvents.CURRENT_TIME]: number;
16
- [HMSHLSPlayerEvents.AUTOPLAY_BLOCKED]: HMSHLSException;
17
-
18
- [HMSHLSPlayerEvents.MANIFEST_LOADED]: HMSHLSManifestLoaded;
19
- [HMSHLSPlayerEvents.LAYER_UPDATED]: HMSHLSLayerUpdated;
20
- };
21
-
22
- export type HMSHLSPlayerListeners<E extends HMSHLSPlayerEvents> = (data: HMSHLSListenerDataMapping[E], name: E) => void;
23
-
24
- export interface HMSHLSStreamLive {
25
- isLive: boolean;
26
- }
27
- export interface HMSHLSPlaybackState {
28
- state: HLSPlaybackState;
29
- }
30
- export interface HMSHLSCue {
31
- id?: string;
32
- payload: string;
33
- duration: number;
34
- startDate: Date;
35
- endDate?: Date;
36
- }
37
-
38
- export interface HMSHLSManifestLoaded {
39
- layers: HMSHLSLayer[];
40
- }
41
- export interface HMSHLSLayerUpdated {
42
- layer: HMSHLSLayer;
43
- }
44
- export interface IHMSHLSPlayerEventEmitter {
45
- on<E extends HMSHLSPlayerEvents>(eventName: E, listener: HMSHLSPlayerListeners<E>): void;
46
-
47
- off<E extends HMSHLSPlayerEvents>(eventName: E, listener?: HMSHLSPlayerListeners<E>): void;
48
- }
49
-
50
- export class HMSHLSPlayerEventEmitter implements IHMSHLSPlayerEventEmitter {
51
- private eventEmitter: EventEmitter;
52
- constructor() {
53
- this.eventEmitter = new EventEmitter();
54
- }
55
- on<E extends HMSHLSPlayerEvents>(eventName: E, listener: HMSHLSPlayerListeners<E>): void {
56
- this.eventEmitter.on(eventName, listener);
57
- }
58
-
59
- off<E extends HMSHLSPlayerEvents>(eventName: E, listener: HMSHLSPlayerListeners<E>) {
60
- this.eventEmitter.off(eventName, listener);
61
- }
62
-
63
- emitEvent<E extends HMSHLSPlayerEvents>(eventName: E, eventObject: Parameters<HMSHLSPlayerListeners<E>>[0]): boolean {
64
- return this.eventEmitter.emit(eventName, eventObject, eventName);
65
- }
66
-
67
- removeAllListeners<E extends HMSHLSPlayerEvents>(eventName?: E): void {
68
- this.eventEmitter.removeAllListeners(eventName);
69
- }
70
- }
@@ -1,37 +0,0 @@
1
- export const HLS_DEFAULT_ALLOWED_MAX_LATENCY_DELAY = 5;
2
-
3
- export enum HMSHLSPlayerEvents {
4
- TIMED_METADATA_LOADED = 'timed-metadata',
5
- SEEK_POS_BEHIND_LIVE_EDGE = 'seek-pos-behind-live-edge',
6
-
7
- CURRENT_TIME = 'current-time',
8
- AUTOPLAY_BLOCKED = 'autoplay-blocked',
9
-
10
- MANIFEST_LOADED = 'manifest-loaded',
11
- LAYER_UPDATED = 'layer-updated',
12
- CAPTION_ENABLED = 'caption-enabled',
13
-
14
- ERROR = 'error',
15
- PLAYBACK_STATE = 'playback-state',
16
- STATS = 'stats',
17
- }
18
-
19
- export enum HMSHLSExceptionEvents {
20
- MANIFEST_LOAD_ERROR = 'manifest-load-error',
21
- MANIFEST_PARSING_ERROR = 'manifest-parsing-error',
22
- LAYER_LOAD_ERROR = 'layer-load-error',
23
-
24
- MANIFEST_INCOMPATIBLE_CODECS_ERROR = 'manifest-incompatible-codecs-error',
25
- FRAG_DECRYPT_ERROR = 'frag-decrypt-error',
26
- BUFFER_INCOMPATIBLE_CODECS_ERROR = 'buffer-incompatible-codecs-error',
27
-
28
- VIDEO_ELEMENT_NOT_FOUND = 'video-element-not-found',
29
- HLS_AUTOPLAY_FAILED = 'hls-autoplay-failed',
30
- HLS_URL_NOT_FOUND = 'hls-url-not-found',
31
- HLS_ERROR = 'hls-error',
32
- }
33
-
34
- export enum HLSPlaybackState {
35
- playing,
36
- paused,
37
- }
@@ -1,15 +0,0 @@
1
- import { HMSHLSException } from '../error/HMSHLSException';
2
-
3
- declare global {
4
- interface Window {
5
- __hms:
6
- | {
7
- sdk:
8
- | {
9
- sendHLSAnalytics: (error: HMSHLSException) => void;
10
- }
11
- | undefined;
12
- }
13
- | undefined;
14
- }
15
- }
@@ -1,38 +0,0 @@
1
- import { Level, LevelParsed } from 'hls.js';
2
- import { HMSHLSLayer } from '../interfaces/IHMSHLSLayer';
3
-
4
- /**
5
- *
6
- * @param payload a base64 string coming from backend
7
- * @returns a parsed data which contains payload, start_date, end_date, version
8
- */
9
- export const metadataPayloadParser = (payload: string): Record<string, any> => {
10
- try {
11
- const data = window?.atob(payload);
12
- const parsedData = JSON.parse(data);
13
- return parsedData;
14
- } catch (e) {
15
- return { payload };
16
- }
17
- };
18
-
19
- /**
20
- * map Level[] to HMSHLSLayer[]
21
- */
22
- export const mapLayers = (levels: Level[] | LevelParsed[]): HMSHLSLayer[] => {
23
- return levels.map((level: Level | LevelParsed) => mapLayer(level));
24
- };
25
-
26
- /**
27
- * map Level[] to HMSHLSLayer[]
28
- */
29
- export const mapLayer = (quality: Level | LevelParsed): HMSHLSLayer => {
30
- return {
31
- resolution: quality.attrs?.RESOLUTION,
32
- bitrate: quality.bitrate,
33
- height: quality.height,
34
- id: quality.id,
35
- url: quality.url[0],
36
- width: quality.width,
37
- };
38
- };