@antmedia/web_player 2.8.0-SNAPSHOT

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.
Files changed (41) hide show
  1. package/.project +17 -0
  2. package/.settings/.jsdtscope +7 -0
  3. package/.settings/org.eclipse.wst.jsdt.ui.superType.container +1 -0
  4. package/.settings/org.eclipse.wst.jsdt.ui.superType.name +1 -0
  5. package/LICENSE +201 -0
  6. package/README.md +1 -0
  7. package/api-extractor.json +38 -0
  8. package/codecov.yml +6 -0
  9. package/dist/_commonjsHelpers-ed042b00.js +10 -0
  10. package/dist/aframe-master-42bb78a9.js +7139 -0
  11. package/dist/browser/web_player.js +976 -0
  12. package/dist/dash.all.min-84806d51.js +36 -0
  13. package/dist/es/_commonjsHelpers-7d1333e8.js +7 -0
  14. package/dist/es/aframe-master-a6146619.js +7137 -0
  15. package/dist/es/dash.all.min-4a2772b6.js +34 -0
  16. package/dist/es/index.d.ts +227 -0
  17. package/dist/es/index.js +1 -0
  18. package/dist/es/video-js.min-8b4dfe88.js +3 -0
  19. package/dist/es/video.es-22056625.js +31061 -0
  20. package/dist/es/videojs-contrib-quality-levels.es-5f5b5f23.js +287 -0
  21. package/dist/es/videojs-hls-quality-selector.es-3c54e1cd.js +391 -0
  22. package/dist/es/videojs-webrtc-plugin-b9e4da27.js +3 -0
  23. package/dist/es/videojs-webrtc-plugin.es-f41400f7.js +7649 -0
  24. package/dist/es/web_player.js +1262 -0
  25. package/dist/index.d.ts +227 -0
  26. package/dist/index.js +7 -0
  27. package/dist/video-js.min-7e4ae47a.js +5 -0
  28. package/dist/video.es-72122d04.js +31067 -0
  29. package/dist/videojs-contrib-quality-levels.es-ef3cec9e.js +289 -0
  30. package/dist/videojs-hls-quality-selector.es-562309df.js +393 -0
  31. package/dist/videojs-webrtc-plugin-d30c3e7a.js +5 -0
  32. package/dist/videojs-webrtc-plugin.es-ac81d249.js +7651 -0
  33. package/dist/web_player.js +1265 -0
  34. package/karma.conf.cjs +74 -0
  35. package/package.json +68 -0
  36. package/rollup.config.browser.cjs +16 -0
  37. package/rollup.config.module.cjs +42 -0
  38. package/src/index.js +1 -0
  39. package/src/web_player.js +1133 -0
  40. package/test/embedded-player.test.js +864 -0
  41. package/tsconfig.json +24 -0
@@ -0,0 +1,1133 @@
1
+
2
+ import { getUrlParameter } from "@antmedia/webrtc_adaptor/dist/fetch.stream";
3
+ import { Logger } from "@antmedia/webrtc_adaptor/dist/loglevel.min";
4
+
5
+ export const STATIC_VIDEO_HTML = "<video id='video-player' class='video-js vjs-default-skin vjs-big-play-centered' controls playsinline></video>";
6
+
7
+
8
+ export class WebPlayer {
9
+
10
+ static DEFAULT_PLAY_ORDER = ["webrtc", "hls"];
11
+
12
+ static DEFAULT_PLAY_TYPE = ["mp4", "webm"];
13
+
14
+ static HLS_EXTENSION = "m3u8";
15
+
16
+ static WEBRTC_EXTENSION = "webrtc";
17
+
18
+ static DASH_EXTENSION = "mpd";
19
+
20
+ /**
21
+ * streamsFolder: streams folder. Optional. Default value is "streams"
22
+ */
23
+ static STREAMS_FOLDER = "streams";
24
+
25
+
26
+ /**
27
+ * Video HTML content. It's by default STATIC_VIDEO_HTML
28
+ */
29
+ videoHTMLContent;
30
+
31
+ /**
32
+ * video player Id. It's by default "video-player"
33
+ */
34
+ videoPlayerId;
35
+
36
+ /**
37
+ * "playOrder": the order which technologies is used in playing. Optional. Default value is "webrtc,hls".
38
+ * possible values are "hls,webrtc","webrtc","hls","vod","dash"
39
+ * It will be taken from url parameter "playOrder".
40
+ */
41
+ playOrder;
42
+
43
+ /**
44
+ * currentPlayType: current play type in playOrder
45
+ */
46
+ currentPlayType;
47
+
48
+ /**
49
+ * "is360": if true, player will be 360 degree player. Optional. Default value is false.
50
+ * It will be taken from url parameter "is360".
51
+ */
52
+ is360 = false;
53
+
54
+ /**
55
+ * "streamId": stream id. Mandatory. If it is not set, it will be taken from url parameter "id".
56
+ * It will be taken from url parameter "id".
57
+ */
58
+ streamId;
59
+
60
+ /**
61
+ * "playType": play type. Optional. It's used for vod. Default value is "mp4,webm".
62
+ * It can be "mp4,webm","webm,mp4","mp4","webm","mov" and it's used for vod.
63
+ * It will be taken from url parameter "playType".
64
+ */
65
+ playType;
66
+
67
+ /**
68
+ * "token": token. It's required when stream security for playback is enabled .
69
+ * It will be taken from url parameter "token".
70
+ */
71
+ token;
72
+
73
+ /**
74
+ * autoplay: if true, player will be started automatically. Optional. Default value is true.
75
+ * autoplay is false by default for mobile devices because of mobile browser's autoplay policy.
76
+ * It will be taken from url parameter "autoplay".
77
+ */
78
+ autoPlay = true;
79
+
80
+ /**
81
+ * mute: if true, player will be started muted. Optional. Default value is true.
82
+ * default value is true because of browser's autoplay policy.
83
+ * It will be taken from url parameter "mute".
84
+ */
85
+ mute = true;
86
+
87
+ /**
88
+ * targetLatency: target latency in seconds. Optional. Default value is 3.
89
+ * It will be taken from url parameter "targetLatency".
90
+ * It's used for dash(cmaf) playback.
91
+ */
92
+ targetLatency = 3;
93
+
94
+ /**
95
+ * subscriberId: subscriber id. Optional. It will be taken from url parameter "subscriberId".
96
+ */
97
+ subscriberId;
98
+
99
+ /**
100
+ * subscriberCode: subscriber code. Optional. It will be taken from url parameter "subscriberCode".
101
+ */
102
+ subscriberCode;
103
+
104
+ /**
105
+ * window: window object
106
+ */
107
+ window;
108
+
109
+ /**
110
+ * video player container element
111
+ */
112
+ containerElement;
113
+
114
+ /**
115
+ * player placeholder element
116
+ */
117
+ placeHolderElement;
118
+
119
+ /**
120
+ * videojs player
121
+ */
122
+ videojsPlayer;
123
+
124
+ /**
125
+ * dash player
126
+ */
127
+ dashPlayer;
128
+
129
+ /**
130
+ * Ice servers for webrtc
131
+ */
132
+ iceServers;
133
+
134
+ /**
135
+ * ice connection state
136
+ */
137
+ iceConnected;
138
+
139
+ /**
140
+ * flag to check if error callback is called
141
+ */
142
+ errorCalled;
143
+
144
+ /**
145
+ * scene for 360 degree player
146
+ */
147
+ aScene;
148
+
149
+ /**
150
+ * player listener
151
+ */
152
+ playerListener
153
+
154
+ /**
155
+ * webRTCDataListener
156
+ */
157
+ webRTCDataListener;
158
+
159
+ /**
160
+ * Field to keep if tryNextMethod is already called
161
+ */
162
+ tryNextTechTimer;
163
+
164
+ constructor(configOrWindow, containerElement, placeHolderElement) {
165
+
166
+ WebPlayer.DEFAULT_PLAY_ORDER = ["webrtc", "hls"];;
167
+
168
+ WebPlayer.DEFAULT_PLAY_TYPE = ["mp4", "webm"];
169
+
170
+ WebPlayer.HLS_EXTENSION = "m3u8";
171
+
172
+ WebPlayer.WEBRTC_EXTENSION = "webrtc";
173
+
174
+ WebPlayer.DASH_EXTENSION = "mpd";
175
+
176
+ /**
177
+ * streamsFolder: streams folder. Optional. Default value is "streams"
178
+ */
179
+ WebPlayer.STREAMS_FOLDER = "streams";
180
+
181
+ WebPlayer.VIDEO_PLAYER_ID = "video-player";
182
+
183
+ // Initialize default values
184
+ this.setDefaults();
185
+
186
+
187
+ // Check if the first argument is a config object or a Window object
188
+ if (!this.isWindow(configOrWindow)) {
189
+ // New config object mode
190
+ Logger.info("config object mode");
191
+ Object.assign(this, configOrWindow);
192
+ this.window = window;
193
+ }
194
+ else {
195
+ // Backward compatibility mode
196
+ Logger.info("getting from url mode");
197
+ this.window = configOrWindow;
198
+
199
+ // Use getUrlParameter for backward compatibility
200
+ this.initializeFromUrlParams();
201
+ }
202
+
203
+ this.containerElement = containerElement;
204
+ this.placeHolderElement = placeHolderElement;
205
+
206
+ if (this.streamId == null) {
207
+ var message = "Stream id is not set.Please add your stream id to the url as a query parameter such as ?id={STREAM_ID} to the url"
208
+ Logger.error(message);
209
+ //TODO: we may need to show this message on directly page
210
+ alert(message);
211
+ throw new Error(message);
212
+ }
213
+
214
+ if (!this.httpBaseURL)
215
+ {
216
+ let appName = this.window.location.pathname.substring(0, this.window.location.pathname.lastIndexOf("/") + 1);
217
+ let path = this.window.location.hostname + ":" + this.window.location.port + appName + this.streamId + ".webrtc";
218
+ this.websocketURL = "ws://" + path;
219
+
220
+ if (location.protocol.startsWith("https")) {
221
+ this.websocketURL = "wss://" + path;
222
+ }
223
+
224
+ this.httpBaseURL = location.protocol + "//" + this.window.location.hostname + ":" + this.window.location.port + appName;
225
+ }
226
+ else if (!this.websocketURL)
227
+ {
228
+ this.websocketURL = this.httpBaseURL.replace("http", "ws");
229
+
230
+ if (!this.websocketURL.endsWith("/")) {
231
+ this.websocketURL += "/";
232
+ }
233
+
234
+ this.websocketURL += this.streamId + ".webrtc";
235
+ }
236
+
237
+ this.dom = this.window.document;
238
+
239
+ this.containerElement.innerHTML = this.videoHTMLContent;
240
+
241
+ this.setPlayerVisible(false);
242
+ }
243
+
244
+ isWindow(configOrWindow) {
245
+ //accept that it's a window if it's a Window instance or it has location.href
246
+ //location.href is used in test environment
247
+ return configOrWindow instanceof Window || (configOrWindow.location && configOrWindow.location.href);
248
+ }
249
+
250
+ initialize()
251
+ {
252
+ return this.loadVideoJSComponents()
253
+ .then(() => {
254
+ return this.loadDashScript();
255
+ })
256
+ .then(() => {
257
+ if (this.is360 && !window.AFRAME) {
258
+
259
+ return import('aframe');
260
+ }
261
+ })
262
+ .catch((e) => {
263
+ Logger.error("Scripts are not loaded. The error is " + e);
264
+ throw e;
265
+ });
266
+ };
267
+
268
+ loadDashScript() {
269
+ if (this.playOrder.includes("dash") && !this.dashjsLoaded) {
270
+
271
+ return import('dashjs/dist/dash.all.min.js').then((dashjs) =>
272
+ {
273
+ window.dashjs = dashjs.default;
274
+ this.dashjsLoaded = true;
275
+ console.log("dash.all.min.js is loaded");
276
+ })
277
+ }
278
+ else {
279
+ return Promise.resolve();
280
+ }
281
+ }
282
+
283
+ setDefaults() {
284
+ this.playOrder = WebPlayer.DEFAULT_PLAY_ORDER;
285
+ this.currentPlayType = null;
286
+ this.is360 = false;
287
+ this.streamId = null;
288
+ this.playType = WebPlayer.DEFAULT_PLAY_TYPE;
289
+ this.token = null;
290
+ this.autoPlay = true;
291
+ this.mute = true;
292
+ this.targetLatency = 3;
293
+ this.subscriberId = null;
294
+ this.subscriberCode = null;
295
+ this.window = null;
296
+ this.containerElement = null;
297
+ this.placeHolderElement = null;
298
+ this.videojsPlayer = null;
299
+ this.dashPlayer = null;
300
+ this.iceServers = '[ { "urls": "stun:stun1.l.google.com:19302" } ]';
301
+ this.iceConnected = false;
302
+ this.errorCalled = false;
303
+ this.tryNextTechTimer = -1;
304
+ this.aScene = null;
305
+ this.playerListener = null;
306
+ this.webRTCDataListener = null;
307
+ this.websocketURL = null;
308
+ this.httpBaseURL = null;
309
+ this.videoHTMLContent = STATIC_VIDEO_HTML;
310
+ this.videoPlayerId = "video-player";
311
+ this.videojsLoaded = false;
312
+ this.dashjsLoaded = false;
313
+ }
314
+
315
+ initializeFromUrlParams() {
316
+ // Fetch parameters from URL and set to class properties
317
+ this.streamId = getUrlParameter("id", this.window.location.search) || this.streamId;
318
+
319
+ if (this.streamId == null) {
320
+ //check name variable for compatibility with older versions
321
+
322
+ this.streamId = getUrlParameter("name", this.window.location.search) || this.streamId;
323
+ if (this.streamId == null) {
324
+ Logger.warn("Please use id parameter instead of name parameter.");
325
+ }
326
+ }
327
+
328
+ this.is360 = (getUrlParameter("is360", this.window.location.search) === "true") || this.is360;
329
+
330
+ this.playType = getUrlParameter("playType", this.window.location.search)?.split(',') || this.playType;
331
+ this.token = getUrlParameter("token", this.window.location.search) || this.token;
332
+ let autoPlayLocal = getUrlParameter("autoplay", this.window.location.search);
333
+ if (autoPlayLocal === "false") {
334
+ this.autoPlay = false;
335
+ }
336
+ else {
337
+ this.autoPlay = true;
338
+ }
339
+
340
+ let muteLocal = getUrlParameter("mute", this.window.location.search);
341
+ if (muteLocal === "false") {
342
+ this.mute = false;
343
+ }
344
+ else {
345
+ this.mute = true;
346
+ }
347
+
348
+ let localTargetLatency = getUrlParameter("targetLatency", this.window.location.search);
349
+ if (localTargetLatency != null) {
350
+ let latencyInNumber = Number(localTargetLatency);
351
+ if (!isNaN(latencyInNumber)) {
352
+ this.targetLatency = latencyInNumber;
353
+ } else {
354
+ Logger.warn("targetLatency parameter is not a number. It will be ignored.");
355
+ this.targetLatency = this.targetLatency || 3; // Default value or existing value
356
+ }
357
+ }
358
+ this.subscriberId = getUrlParameter("subscriberId", this.window.location.search) || this.subscriberId;
359
+ this.subscriberCode = getUrlParameter("subscriberCode", this.window.location.search) || this.subscriberCode;
360
+ let playOrder = getUrlParameter("playOrder", this.window.location.search);
361
+ this.playOrder = playOrder ? playOrder.split(',') : this.playOrder;
362
+
363
+
364
+ }
365
+
366
+ loadWebRTCComponents() {
367
+ if (this.playOrder.includes("webrtc"))
368
+ {
369
+ return import('@antmedia/videojs-webrtc-plugin/dist/videojs-webrtc-plugin.css').then((css) =>
370
+ {
371
+ Logger.info("videojs-webrtc-plugin.css is loaded");
372
+ const styleElement = this.dom.createElement('style');
373
+ styleElement.textContent = css.default.toString(); // Assuming css module exports a string
374
+ this.dom.head.appendChild(styleElement);
375
+
376
+ return import('@antmedia/videojs-webrtc-plugin').then((videojsWebrtcPluginLocal) =>
377
+ {
378
+ Logger.info("videojs-webrtc-plugin is loaded");
379
+
380
+ });
381
+ });
382
+ }
383
+ else {
384
+
385
+ return Promise.resolve();
386
+ }
387
+ }
388
+ /**
389
+ * load scripts dynamically
390
+ */
391
+ loadVideoJSComponents() {
392
+ if (this.playOrder.includes("hls") || this.playOrder.includes("vod") || this.playOrder.includes("webrtc")) {
393
+ //it means we're going to use videojs
394
+ //load videojs css
395
+ if (!this.videojsLoaded)
396
+ {
397
+ return import('video.js/dist/video-js.min.css').then((css) => {
398
+ const styleElement = this.dom.createElement('style');
399
+ styleElement.textContent = css.default.toString(); // Assuming css module exports a string
400
+ this.dom.head.appendChild(styleElement);
401
+ })
402
+ .then(() => { return import("video.js") })
403
+ .then((videojs) =>
404
+ {
405
+ window.videojs = videojs.default;
406
+ this.videojsLoaded = true;
407
+ })
408
+ .then(() => { return import('videojs-contrib-quality-levels') } )
409
+ .then(() => { return import('videojs-hls-quality-selector') } )
410
+ .then(() => { return this.loadWebRTCComponents(); });
411
+ }
412
+ else {
413
+ return Promise.resolve();
414
+ }
415
+
416
+ }
417
+ else {
418
+ return Promise.resolve();
419
+ }
420
+ }
421
+
422
+ /**
423
+ * enable 360 player
424
+ */
425
+ enable360Player() {
426
+ this.aScene = this.dom.createElement("a-scene");
427
+ var elementId = this.dom.getElementsByTagName("video")[0].id;
428
+ this.aScene.innerHTML = "<a-videosphere src=\"#"+elementId+"\" rotation=\"0 180 0\" style=\"background-color: antiquewhite\"></a-videosphere>";
429
+ this.dom.body.appendChild(this.aScene);
430
+ }
431
+
432
+ /**
433
+ * set player visibility
434
+ * @param {boolean} visible
435
+ */
436
+ setPlayerVisible(visible) {
437
+ this.containerElement.style.display = visible ? "block" : "none";
438
+ if (this.placeHolderElement) {
439
+ this.placeHolderElement.style.display = visible ? "none" : "block";
440
+ }
441
+
442
+ if (this.is360) {
443
+ if (visible) {
444
+ this.enable360Player();
445
+ }
446
+ else if (this.aScene != null) {
447
+ var elements = this.dom.getElementsByTagName("a-scene");
448
+ while (elements.length > 0) {
449
+ this.dom.body.removeChild(elements[0]);
450
+ elements = this.dom.getElementsByTagName("a-scene");
451
+ }
452
+ this.aScene = null;
453
+ }
454
+ }
455
+ }
456
+
457
+
458
+ handleWebRTCInfoMessages(infos) {
459
+ if (infos["info"] == "ice_connection_state_changed") {
460
+ Logger.debug("ice connection state changed to " + infos["obj"].state);
461
+ if (infos["obj"].state == "completed" || infos["obj"].state == "connected") {
462
+ this.iceConnected = true;
463
+ }
464
+ else if (infos["obj"].state == "failed" || infos["obj"].state == "disconnected" || infos["obj"].state == "closed") {
465
+ //
466
+ Logger.warn("Ice connection is not connected. tryNextTech to replay");
467
+ this.tryNextTech();
468
+ }
469
+
470
+ }
471
+ else if (infos["info"] == "closed") {
472
+ //this means websocket is closed and it stops the playback - tryNextTech
473
+ Logger.warn("Websocket is closed. tryNextTech to replay");
474
+ this.tryNextTech();
475
+ }
476
+ else if (infos["info"] == "resolutionChangeInfo")
477
+ {
478
+ Logger.info("Resolution is changing");
479
+ this.videojsPlayer.pause();
480
+ setTimeout(() => {
481
+ this.videojsPlayer.play();
482
+ }, 1000);
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Play the stream via videojs
488
+ * @param {*} streamUrl
489
+ * @param {*} extension
490
+ * @returns
491
+ */
492
+ playWithVideoJS(streamUrl, extension) {
493
+ var type;
494
+ if (extension == "mp4") {
495
+ type = "video/mp4";
496
+ }
497
+ else if (extension == "webm") {
498
+ type = "video/webm";
499
+ }
500
+ else if (extension == "mov") {
501
+ type = "video/mp4";
502
+ alert("Browsers do not support to play mov format");
503
+ }
504
+ else if (extension == "avi") {
505
+ type = "video/mp4";
506
+ alert("Browsers do not support to play avi format");
507
+ }
508
+ else if (extension == "m3u8") {
509
+ type = "application/x-mpegURL";
510
+ }
511
+ else if (extension == "mpd") {
512
+ type = "application/dash+xml";
513
+ }
514
+ else if (extension == "webrtc") {
515
+ type = "video/webrtc";
516
+ }
517
+ else {
518
+ Logger.warn("Unknown extension: " + extension);
519
+ return;
520
+ }
521
+
522
+ var preview = this.streamId;
523
+ if (this.streamId.endsWith("_adaptive")) {
524
+ preview = streamId.substring(0, streamId.indexOf("_adaptive"));
525
+ }
526
+
527
+ //same videojs is being use for hls, vod and webrtc streams
528
+ this.videojsPlayer = videojs(this.videoPlayerId, {
529
+ poster: "previews/" + preview + ".png",
530
+ liveui: extension == "m3u8" ? true : false,
531
+ liveTracker: {
532
+ trackingThreshold: 0
533
+ },
534
+ html5: {
535
+ vhs: {
536
+ limitRenditionByPlayerDimensions: false
537
+ }
538
+ },
539
+ controls: true,
540
+ class: 'video-js vjs-default-skin vjs-big-play-centered',
541
+ muted: this.mute,
542
+ preload: "auto",
543
+ autoplay: this.autoPlay
544
+
545
+ });
546
+
547
+ this.videojsPlayer.on('error', (e) => {
548
+ Logger.warn("There is an error in playback: " + e);
549
+ // We need to add this kind of check. If we don't add this kind of checkpoint, it will create an infinite loop
550
+ if (!this.errorCalled) {
551
+ this.errorCalled = true;
552
+ setTimeout(() => {
553
+ this.tryNextTech();
554
+ this.errorCalled = false;
555
+ }, 2500)
556
+ }
557
+ });
558
+
559
+ //webrtc specific events
560
+ if (extension == "webrtc") {
561
+
562
+ this.videojsPlayer.on('webrtc-info', (event, infos) => {
563
+
564
+ //Logger.warn("info callback: " + JSON.stringify(infos));
565
+ this.handleWebRTCInfoMessages(infos);
566
+ });
567
+
568
+
569
+ this.videojsPlayer.on('webrtc-error', (event, errors) => {
570
+ //some of the possible errors, NotFoundError, SecurityError,PermissionDeniedError
571
+ Logger.warn("error callback: " + JSON.stringify(errors));
572
+
573
+ if (errors["error"] == "no_stream_exist" || errors["error"] == "WebSocketNotConnected"
574
+ || errors["error"] == "not_initialized_yet" || errors["error"] == "data_store_not_available"
575
+ || errors["error"] == "highResourceUsage" || errors["error"] == "unauthorized_access"
576
+ || errors["error"] == "user_blocked") {
577
+
578
+ //handle high resource usage and not authroized errors && websocket disconnected
579
+ //Even if webrtc adaptor has auto reconnect scenario, we dispose the videojs immediately in tryNextTech
580
+ // so that reconnect scenario is managed here
581
+
582
+ this.tryNextTech();
583
+ }
584
+ else if (errors["error"] == "notSetRemoteDescription") {
585
+ /*
586
+ * If getting codec incompatible or remote description error, it will redirect HLS player.
587
+ */
588
+ Logger.warn("notSetRemoteDescription error. Redirecting to HLS player.");
589
+ this.playIfExists("hls");
590
+ }
591
+ });
592
+
593
+ this.videojsPlayer.on("webrtc-data-received", (event, obj) => {
594
+ Logger.warn("webrtc-data-received: " + JSON.stringify(obj));
595
+ if (this.webRTCDataListener != null) {
596
+ this.webRTCDataListener(obj);
597
+ }
598
+ });
599
+ }
600
+
601
+ //hls specific calls
602
+ if (extension == "m3u8") {
603
+ videojs.Vhs.xhr.beforeRequest = (options) => {
604
+
605
+ let securityParams = this.getSecurityQueryParams();
606
+ if (!options.uri.includes(securityParams))
607
+ {
608
+ if (!options.uri.endsWith("?"))
609
+ {
610
+ options.uri = options.uri + "?";
611
+ }
612
+ options.uri += securityParams;
613
+ }
614
+
615
+ Logger.debug("hls request: " + options.uri);
616
+ return options;
617
+ };
618
+
619
+
620
+ this.videojsPlayer.ready(() => {
621
+
622
+ // If it's already added to player, no need to add again
623
+ if (typeof this.videojsPlayer.hlsQualitySelector === "function") {
624
+ this.videojsPlayer.hlsQualitySelector({
625
+ displayCurrentQuality: true,
626
+ });
627
+ }
628
+
629
+ // If there is no adaptive option in m3u8 no need to show quality selector
630
+ let qualityLevels = this.videojsPlayer.qualityLevels();
631
+ qualityLevels.on('addqualitylevel', function (event) {
632
+ let qualityLevel = event.qualityLevel;
633
+ if (qualityLevel.height) {
634
+ qualityLevel.enabled = true;
635
+ } else {
636
+ qualityLevels.removeQualityLevel(qualityLevel);
637
+ qualityLevel.enabled = false;
638
+ }
639
+ });
640
+ });
641
+ }
642
+
643
+ //videojs is being used to play mp4, webm, m3u8 and webrtc
644
+ //make the videoJS visible when ready is called except for webrtc
645
+ //webrtc fires ready event all cases so we use "play" event to make the player visible
646
+
647
+ //this setting is critical to play in mobile
648
+ if (extension == "mp4" || extension == "webm" || extension == "m3u8") {
649
+ this.makeVideoJSVisibleWhenReady();
650
+ }
651
+
652
+ this.videojsPlayer.on('ended', () => {
653
+ //reinit to play after it ends
654
+ Logger.warn("stream is ended")
655
+ this.setPlayerVisible(false);
656
+ //for webrtc, this event can be called by two reasons
657
+ //1. ice connection is not established, it means that there is a networking issug
658
+ //2. stream is ended
659
+ if (this.currentPlayType != "vod") {
660
+ //if it's vod, it means that stream is ended and no need to replay
661
+
662
+ if (this.iceConnected) {
663
+ //if iceConnected is true, it means that stream is really ended for webrtc
664
+
665
+ //initialize to play again if the publishing starts again
666
+ this.playIfExists(this.playOrder[0]);
667
+ }
668
+ else if (this.currentPlayType == "hls") {
669
+ //if it's hls, it means that stream is ended
670
+
671
+ this.setPlayerVisible(false);
672
+ if (this.playOrder[0] = "hls")
673
+ {
674
+ //do not play again if it's hls because it play last seconds again, let the server clear it
675
+ setTimeout(() => {
676
+ this.playIfExists(this.playOrder[0]);
677
+ }, 10000);
678
+ }
679
+ else
680
+ {
681
+ this.playIfExists(this.playOrder[0]);
682
+ }
683
+ //TODO: what if the stream is hls vod then it always re-play
684
+ }
685
+ else {
686
+ //if iceConnected is false, it means that there is a networking issue for webrtc
687
+ this.tryNextTech();
688
+ }
689
+ }
690
+ if (this.playerListener != null) {
691
+ this.playerListener("ended");
692
+ }
693
+
694
+ });
695
+
696
+ //webrtc plugin sends play event. On the other hand, webrtc plugin sends ready event for every scenario.
697
+ //so no need to trust ready event for webrt play
698
+ this.videojsPlayer.on("play", () => {
699
+ this.setPlayerVisible(true);
700
+ if (this.playerListener != null) {
701
+ this.playerListener("play");
702
+ }
703
+ });
704
+ this.iceConnected = false;
705
+
706
+ this.videojsPlayer.src({
707
+ src: streamUrl,
708
+ type: type,
709
+ withCredentials: true,
710
+ iceServers: this.iceServers,
711
+ reconnect: false, //webrtc adaptor has auto reconnect scenario, just disable it, we manage it here
712
+
713
+ });
714
+
715
+ if (this.autoPlay) {
716
+ this.videojsPlayer.play().catch((e) => {
717
+ Logger.warn("Problem in playback. The error is " + e);
718
+ });
719
+ }
720
+ }
721
+
722
+
723
+ makeVideoJSVisibleWhenReady() {
724
+ this.videojsPlayer.ready(() => {
725
+ this.setPlayerVisible(true);
726
+ });
727
+ }
728
+
729
+ /**
730
+ * check if stream exists via http
731
+ * @param {*} streamsfolder
732
+ * @param {*} streamId
733
+ * @param {*} extension
734
+ * @returns
735
+ */
736
+ checkStreamExistsViaHttp(streamsfolder, streamId, extension) {
737
+
738
+ var streamPath = this.httpBaseURL;
739
+ if (!streamId.startsWith(streamsfolder)) {
740
+ streamPath += streamsfolder + "/";
741
+ }
742
+ streamPath += streamId;
743
+
744
+ if (extension != null && extension != "") {
745
+ //if there is extension, add it and try if _adaptive exists
746
+ streamPath += "_adaptive" + "." + extension;
747
+ }
748
+
749
+ streamPath = this.addSecurityParams(streamPath);
750
+
751
+ return fetch(streamPath, { method: 'HEAD' })
752
+ .then((response) => {
753
+ if (response.status == 200) {
754
+ // adaptive m3u8 & mpd exists,play it
755
+ return new Promise(function (resolve, reject) {
756
+ resolve(streamPath);
757
+ });
758
+ } else {
759
+ //adaptive not exists, try mpd or m3u8 exists.
760
+ streamPath = this.httpBaseURL + streamsfolder + "/" + streamId + "." + extension;
761
+ streamPath = this.addSecurityParams(streamPath);
762
+
763
+ return fetch(streamPath, { method: 'HEAD' })
764
+ .then((response) => {
765
+ if (response.status == 200) {
766
+ return new Promise(function (resolve, reject) {
767
+ resolve(streamPath);
768
+ });
769
+ }
770
+ else {
771
+ Logger.warn("No stream found");
772
+ return new Promise(function (resolve, reject) {
773
+ reject("resource_is_not_available");
774
+ });
775
+ }
776
+ });
777
+ }
778
+ });
779
+ }
780
+
781
+ addSecurityParams(streamPath) {
782
+ var securityParams = this.getSecurityQueryParams();
783
+ if (securityParams != null && securityParams != "") {
784
+ streamPath += "?" + securityParams;
785
+ }
786
+ return streamPath;
787
+ }
788
+
789
+ /**
790
+ * try next tech if current tech is not working
791
+ */
792
+ tryNextTech() {
793
+ if (this.tryNextTechTimer == -1)
794
+ {
795
+ this.destroyDashPlayer();
796
+ this.destroyVideoJSPlayer();
797
+ this.setPlayerVisible(false);
798
+ var index = this.playOrder.indexOf(this.currentPlayType);
799
+ if (index == -1 || index == (this.playOrder.length - 1)) {
800
+ index = 0;
801
+ }
802
+ else {
803
+ index++;
804
+ }
805
+
806
+ this.tryNextTechTimer = setTimeout(() => {
807
+ this.tryNextTechTimer = -1;
808
+ this.playIfExists(this.playOrder[index]);
809
+ }, 3000);
810
+ }
811
+ else
812
+ {
813
+ Logger.debug("tryNextTech is already scheduled no need to schedule again");
814
+ }
815
+ }
816
+
817
+ /**
818
+ * play stream throgugh dash player
819
+ * @param {string"} streamUrl
820
+ */
821
+ playViaDash(streamUrl) {
822
+ this.destroyDashPlayer();
823
+ this.dashPlayer = dashjs.MediaPlayer().create();
824
+ this.dashPlayer.extend("RequestModifier", () => {
825
+ return {
826
+ modifyRequestHeader: function (xhr, { url }) {
827
+ return xhr;
828
+ },
829
+ modifyRequestURL: (url) => {
830
+ var modifiedUrl = ""
831
+
832
+ var securityParams = this.getSecurityQueryParams();
833
+ if (!url.includes(securityParams))
834
+ {
835
+ if (!url.endsWith("?"))
836
+ {
837
+ url += "?";
838
+ }
839
+ modifiedUrl = url + securityParams;
840
+ Logger.warn(modifiedUrl);
841
+ return modifiedUrl
842
+ }
843
+
844
+ return url;
845
+ },
846
+ modifyRequest(request) {
847
+
848
+ },
849
+ };
850
+ });
851
+
852
+ this.dashPlayer.updateSettings({
853
+ streaming: {
854
+ delay: {
855
+ liveDelay: this.targetLatency
856
+ },
857
+ liveCatchup: {
858
+ maxDrift: 0.5,
859
+ playbackRate: 0.5,
860
+ latencyThreshold: 60
861
+ }
862
+ }
863
+ });
864
+
865
+ this.dashPlayer.initialize(this.containerElement.firstChild, streamUrl, this.autoPlay);
866
+
867
+ this.dashPlayer.setMute(this.mute);
868
+
869
+ this.dashLatencyTimer = setInterval(() => {
870
+ Logger.warn("live latency: " + this.dashPlayer.getCurrentLiveLatency());
871
+ }, 2000);
872
+
873
+
874
+ this.makeDashPlayerVisibleWhenInitialized();
875
+
876
+ this.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_PLAYING, (event) => {
877
+ Logger.warn("playback started");
878
+ this.setPlayerVisible(true);
879
+ if (this.playerListener != null) {
880
+ this.playerListener("play");
881
+ }
882
+ });
883
+ this.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ENDED, () => {
884
+ Logger.warn("playback ended");
885
+ this.destroyDashPlayer();
886
+ this.setPlayerVisible(false);
887
+ //streaming can be started again so try to play again with preferred tech
888
+ if (this.playOrder[0] = "dash")
889
+ {
890
+ //do not play again if it's dash because it play last seconds again, let the server clear it
891
+ setTimeout(() => {
892
+ this.playIfExists(this.playOrder[0]);
893
+ }, 10000);
894
+ }
895
+ else {
896
+ this.playIfExists(this.playOrder[0]);
897
+ }
898
+ if (this.playerListener != null) {
899
+ this.playerListener("ended");
900
+ }
901
+ });
902
+ this.dashPlayer.on(dashjs.MediaPlayer.events.PLAYBACK_ERROR, (event) => {
903
+ this.tryNextTech();
904
+ });
905
+ this.dashPlayer.on(dashjs.MediaPlayer.events.ERROR, (event) => {
906
+ this.tryNextTech();
907
+ });
908
+ }
909
+
910
+ makeDashPlayerVisibleWhenInitialized() {
911
+ this.dashPlayer.on(dashjs.MediaPlayer.events.STREAM_INITIALIZED, (event) => {
912
+ Logger.warn("Stream initialized");
913
+ //make the player visible in mobile devices
914
+ this.setPlayerVisible(true);
915
+ });
916
+ }
917
+
918
+ /**
919
+ * destroy the dash player
920
+ */
921
+ destroyDashPlayer() {
922
+ if (this.dashPlayer) {
923
+ this.dashPlayer.destroy();
924
+ this.dashPlayer = null;
925
+ clearInterval(this.dashLatencyTimer);
926
+ }
927
+ }
928
+
929
+ /**
930
+ * destroy the videojs player
931
+ */
932
+ destroyVideoJSPlayer() {
933
+ if (this.videojsPlayer) {
934
+ this.videojsPlayer.dispose();
935
+ this.videojsPlayer = null;
936
+ }
937
+ }
938
+
939
+ /**
940
+ * Destory the player
941
+ */
942
+ destroy() {
943
+ this.destroyVideoJSPlayer();
944
+ this.destroyDashPlayer();
945
+ }
946
+
947
+ /**
948
+ * play the stream with the given tech
949
+ * @param {string} tech
950
+ */
951
+ async playIfExists(tech) {
952
+ this.currentPlayType = tech;
953
+ this.destroyVideoJSPlayer();
954
+ this.destroyDashPlayer();
955
+ this.setPlayerVisible(false);
956
+
957
+ this.containerElement.innerHTML = this.videoHTMLContent;
958
+
959
+ Logger.warn("Try to play the stream " + this.streamId + " with " + this.currentPlayType);
960
+ switch (this.currentPlayType) {
961
+ case "hls":
962
+ //TODO: Test case for hls
963
+ //1. Play stream with adaptive m3u8 for live and VoD
964
+ //2. Play stream with m3u8 for live and VoD
965
+ //3. if files are not available check nextTech is being called
966
+ return this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId, WebPlayer.HLS_EXTENSION).then((streamPath) => {
967
+
968
+ this.playWithVideoJS(streamPath, WebPlayer.HLS_EXTENSION);
969
+ Logger.warn("incoming stream path: " + streamPath);
970
+
971
+ }).catch((error) => {
972
+
973
+ Logger.warn("HLS stream resource not available for stream:" + this.streamId + " error is " + error + ". Try next play tech");
974
+ this.tryNextTech();
975
+ });
976
+ case "dash":
977
+ return this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId + "/" + this.streamId, WebPlayer.DASH_EXTENSION).then((streamPath) => {
978
+ this.playViaDash(streamPath);
979
+ }).catch((error) => {
980
+ Logger.warn("DASH stream resource not available for stream:" + this.streamId + " error is " + error + ". Try next play tech");
981
+ this.tryNextTech();
982
+ });
983
+
984
+ case "webrtc":
985
+
986
+
987
+ return this.playWithVideoJS(this.addSecurityParams(this.websocketURL), WebPlayer.WEBRTC_EXTENSION);
988
+ case "vod":
989
+ //TODO: Test case for vod
990
+ //1. Play stream with mp4 for VoD
991
+ //2. Play stream with webm for VoD
992
+ //3. Play stream with playOrder type
993
+
994
+ var lastIndexOfDot = this.streamId.lastIndexOf(".");
995
+ var extension;
996
+ if (lastIndexOfDot != -1)
997
+ {
998
+ //if there is a dot in the streamId, it means that this is extension, use it. make the extension empty
999
+ this.playType[0] = "";
1000
+ extension = this.streamId.substring(lastIndexOfDot + 1);
1001
+ }
1002
+ else {
1003
+ //we need to give extension to playWithVideoJS
1004
+ extension = this.playType[0];
1005
+ }
1006
+
1007
+ return this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId, this.playType[0]).then((streamPath) => {
1008
+
1009
+ //we need to give extension to playWithVideoJS
1010
+ this.playWithVideoJS(streamPath, extension);
1011
+
1012
+ }).catch((error) => {
1013
+ Logger.warn("VOD stream resource not available for stream:" + this.streamId + " and play type " + this.playType[0] + ". Error is " + error);
1014
+ if (this.playType.length > 1) {
1015
+ Logger.warn("Try next play type which is " + this.playType[1] + ".")
1016
+ this.checkStreamExistsViaHttp(WebPlayer.STREAMS_FOLDER, this.streamId, this.playType[1]).then((streamPath) => {
1017
+ this.playWithVideoJS(streamPath, this.playType[1]);
1018
+ }).catch((error) => {
1019
+ Logger.warn("VOD stream resource not available for stream:" + this.streamId + " and play type error is " + error);
1020
+ });
1021
+ }
1022
+
1023
+ });
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ *
1029
+ * @returns {String} query string for security
1030
+ */
1031
+ getSecurityQueryParams() {
1032
+ var queryString = "";
1033
+ if (this.token != null) {
1034
+ queryString += "&token=" + this.token;
1035
+ }
1036
+ if (this.subscriberId != null) {
1037
+ queryString += "&subscriberId=" + this.subscriberId;
1038
+ }
1039
+ if (this.subscriberCode != null) {
1040
+ queryString += "&subscriberCode=" + this.subscriberCode;
1041
+ }
1042
+ return queryString;
1043
+ }
1044
+
1045
+ /**
1046
+ * play the stream with videojs player or dash player
1047
+ */
1048
+ play() {
1049
+ if (this.streamId.startsWith(WebPlayer.STREAMS_FOLDER)) {
1050
+
1051
+ //start videojs player because it directly try to play stream from streams folder
1052
+ var lastIndexOfDot = this.streamId.lastIndexOf(".");
1053
+ var extension = this.streamId.substring(lastIndexOfDot + 1);
1054
+
1055
+ this.playOrder= ["vod"];
1056
+
1057
+ if (!this.httpBaseURL.endsWith("/")) {
1058
+ this.httpBaseURL += "/";
1059
+ }
1060
+ this.containerElement.innerHTML = this.videoHTMLContent;
1061
+
1062
+ if (extension == WebPlayer.DASH_EXTENSION)
1063
+ {
1064
+ this.playViaDash(this.httpBaseURL + this.addSecurityParams(this.streamId), extension);
1065
+ }
1066
+ else {
1067
+ this.playWithVideoJS(this.httpBaseURL + this.addSecurityParams(this.streamId), extension);
1068
+ }
1069
+ }
1070
+ else {
1071
+ this.playIfExists(this.playOrder[0]);
1072
+ }
1073
+ }
1074
+
1075
+ /**
1076
+ * mute or unmute the player
1077
+ * @param {boolean} mutestatus true to mute the player
1078
+ */
1079
+ mutePlayer(mutestatus)
1080
+ {
1081
+ this.mute = mutestatus;
1082
+ if (this.videojsPlayer) {
1083
+ this.videojsPlayer.muted(mutestatus);
1084
+ }
1085
+ if (this.dashPlayer) {
1086
+ this.dashPlayer.setMute(mutestatus);
1087
+ }
1088
+ }
1089
+
1090
+ /**
1091
+ *
1092
+ * @returns {boolean} true if player is muted
1093
+ */
1094
+ isMuted() {
1095
+ return this.mute;
1096
+ }
1097
+
1098
+ addPlayerListener(playerListener) {
1099
+ this.playerListener = playerListener;
1100
+ }
1101
+
1102
+ /**
1103
+ * WebRTC data listener
1104
+ * @param {*} webRTCDataListener
1105
+ */
1106
+ addWebRTCDataListener(webRTCDataListener) {
1107
+ this.webRTCDataListener = webRTCDataListener
1108
+ }
1109
+
1110
+ /**
1111
+ *
1112
+ * @param {*} data
1113
+ */
1114
+ sendWebRTCData(data) {
1115
+ try {
1116
+ if (this.videojsPlayer && this.currentPlayType == "webrtc") {
1117
+ this.videojsPlayer.sendDataViaWebRTC(data);
1118
+ return true;
1119
+ }
1120
+ else {
1121
+ Logger.warn("Player is not ready or playType is not WebRTC");
1122
+ }
1123
+ } catch (error) {
1124
+ // Handle the error here
1125
+ Logger.error("An error occurred while sending WebRTC data: ", error);
1126
+ }
1127
+ return false;
1128
+ }
1129
+
1130
+
1131
+
1132
+
1133
+ }