@flashphoner/sfusdk-examples 2.0.221 → 2.0.228

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": "@flashphoner/sfusdk-examples",
3
- "version": "2.0.221",
3
+ "version": "2.0.228",
4
4
  "description": "Official Flashphoner WebCallServer SFU SDK usage examples",
5
5
  "main": "dist/sfu.js",
6
6
  "types": "src/sfu.ts",
@@ -27,6 +27,7 @@
27
27
  "webpack-cli": "^4.9.2"
28
28
  },
29
29
  "dependencies": {
30
- "@flashphoner/sfusdk": "^2.0.193"
30
+ "@flashphoner/sfusdk": "^2.0.193",
31
+ "kalmanjs": "^1.1.0"
31
32
  }
32
33
  }
@@ -115,9 +115,28 @@ async function connect() {
115
115
  }
116
116
  }
117
117
 
118
+ /**
119
+ * Display an error message on operation failure
120
+ *
121
+ * @param prefix
122
+ * @param event
123
+ */
124
+ const onOperationFailed = function(prefix, event) {
125
+ let reason = "reason unknown";
126
+ if (event.operation && event.error) {
127
+ reason = event.operation + " failed: " + event.error;
128
+ } else if (event.text) {
129
+ reason = event.text;
130
+ } else {
131
+ reason = JSON.stringify(event);
132
+ }
133
+ console.error(prefix + ": " + reason);
134
+ displayError(reason);
135
+ }
136
+
118
137
 
119
138
  /**
120
- * Publish streams after entyering room according to configuration file
139
+ * Publish streams after entering room according to configuration file
121
140
  *
122
141
  * @param {*} room
123
142
  * @param {*} pc
@@ -146,8 +165,7 @@ const publishPreconfiguredStreams = async function(room, pc, streams) {
146
165
  $('#' + s.stream.id + "-button").prop('disabled', false);
147
166
  });
148
167
  } catch(e) {
149
- console.error("Failed to publish a preconfigured streams: " + e);
150
- displayError(e);
168
+ onOperationFailed("Failed to publish a preconfigured streams", e);
151
169
  // Enable Delete button for each preconfigured stream #WCS-3689
152
170
  streams.forEach(function (s) {
153
171
  $('#' + s.stream.id + "-button").prop('disabled', false);
@@ -182,8 +200,7 @@ const publishNewTrack = async function(room, pc, media) {
182
200
  // Enable Delete button for a new stream #WCS-3689
183
201
  $('#' + media.stream.id + "-button").prop('disabled', false);
184
202
  } catch(e) {
185
- console.error("Failed to publish a new track: " + e);
186
- displayError(e);
203
+ onOperationFailed("Failed to publish a new track", e);
187
204
  // Enable Delete button for a new stream #WCS-3689
188
205
  $('#' + media.stream.id + "-button").prop('disabled', false);
189
206
  }
@@ -216,8 +233,7 @@ const subscribeTrackToEndedEvent = function(room, track, pc) {
216
233
  await room.updateState();
217
234
  }
218
235
  } catch(e) {
219
- displayError(e);
220
- console.error("Failed to update room state: " + e);
236
+ onOperationFailed("Failed to update room state", e);
221
237
  }
222
238
  });
223
239
  }
@@ -1,3 +1,13 @@
1
+ const ABR_QUALITY_CHECK_PERIOD = 1000;
2
+ const ABR_KEEP_ON_QUALITY = 20000;
3
+ const ABR_TRY_UPPER_QUALITY = 20000;
4
+ const QUALITY_COLORS = {
5
+ NONE: "",
6
+ AVAILABLE: "gray",
7
+ UNAVAILABLE: "red",
8
+ SELECTED: "blue"
9
+ };
10
+
1
11
  const initLocalDisplay = function(localDisplayElement){
2
12
  const localDisplayDiv = localDisplayElement;
3
13
  const localDisplays = {};
@@ -272,6 +282,23 @@ const initRemoteDisplay = function(options) {
272
282
  let publisherNameDisplay;
273
283
  let currentQualityDisplay;
274
284
  let videoTypeDisplay;
285
+ let abrQualityCheckPeriod = ABR_QUALITY_CHECK_PERIOD;
286
+ let abrKeepOnGoodQuality = ABR_KEEP_ON_QUALITY;
287
+ let abrTryForUpperQuality = ABR_TRY_UPPER_QUALITY;
288
+ if (displayOptions.abrQualityCheckPeriod !== undefined) {
289
+ abrQualityCheckPeriod = displayOptions.abrQualityCheckPeriod;
290
+ }
291
+ if (displayOptions.abrKeepOnGoodQuality !== undefined) {
292
+ abrKeepOnGoodQuality = displayOptions.abrKeepOnGoodQuality;
293
+ }
294
+ if (displayOptions.abrTryForUpperQuality !== undefined) {
295
+ abrTryForUpperQuality = displayOptions.abrTryForUpperQuality;
296
+ }
297
+ if (!displayOptions.abr) {
298
+ abrQualityCheckPeriod = 0;
299
+ abrKeepOnGoodQuality = 0;
300
+ abrTryForUpperQuality = 0;
301
+ }
275
302
  if (displayOptions.publisher) {
276
303
  publisherNameDisplay = createInfoDisplay(cell, "Published by: " + name);
277
304
  }
@@ -302,8 +329,16 @@ const initRemoteDisplay = function(options) {
302
329
 
303
330
  let audio = null;
304
331
  let video = null;
332
+
333
+ const abr = ABR(abrQualityCheckPeriod, [
334
+ {parameter: "nackCount", maxLeap: 10},
335
+ {parameter: "freezeCount", maxLeap: 10},
336
+ {parameter: "packetsLost", maxLeap: 10}
337
+ ], abrKeepOnGoodQuality, abrTryForUpperQuality);
338
+
305
339
  return {
306
340
  dispose: function() {
341
+ abr.stop();
307
342
  cell.remove();
308
343
  },
309
344
  hide: function(value) {
@@ -380,31 +415,34 @@ const initRemoteDisplay = function(options) {
380
415
  streamDisplay.appendChild(video);
381
416
  video.srcObject = stream;
382
417
  this.setResizeHandler(video);
418
+ abr.start();
383
419
  },
384
420
  setTrackInfo: function(trackInfo) {
385
421
  if (trackInfo) {
386
422
  if (trackInfo.quality) {
387
423
  showItem(qualitySwitchDisplay);
424
+ if (abr.isEnabled()) {
425
+ const autoDiv = createQualityButton("Auto", qualityDivs, qualitySwitchDisplay);
426
+ autoDiv.style.color = QUALITY_COLORS.SELECTED;
427
+ autoDiv.addEventListener('click', function() {
428
+ setQualityButtonsColor(qualityDivs);
429
+ autoDiv.style.color = QUALITY_COLORS.SELECTED;
430
+ abr.setAuto();
431
+ });
432
+ }
388
433
  for (let i = 0; i < trackInfo.quality.length; i++) {
389
- const qualityDiv = document.createElement("button");
390
- qualityDivs.push(qualityDiv);
391
- qualityDiv.innerText = trackInfo.quality[i];
392
- qualityDiv.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
393
- qualityDiv.style.color = "red";
394
- qualityDiv.addEventListener('click', async function(){
434
+ abr.addQuality(trackInfo.quality[i]);
435
+ const qualityDiv = createQualityButton(trackInfo.quality[i], qualityDivs, qualitySwitchDisplay);
436
+ qualityDiv.addEventListener('click', function() {
395
437
  console.log("Clicked on quality " + trackInfo.quality[i] + " trackId " + trackInfo.id);
396
- if (qualityDiv.style.color === "red") {
438
+ if (qualityDiv.style.color === QUALITY_COLORS.UNAVAILABLE) {
397
439
  return;
398
440
  }
399
- for (let c = 0; c < qualityDivs.length; c++) {
400
- if (qualityDivs[c].style.color !== "red") {
401
- qualityDivs[c].style.color = "gray";
402
- }
403
- }
404
- qualityDiv.style.color = "blue";
405
- await room.changeQuality(trackInfo.id, trackInfo.quality[i]);
441
+ setQualityButtonsColor(qualityDivs);
442
+ qualityDiv.style.color = QUALITY_COLORS.SELECTED;
443
+ abr.setManual();
444
+ abr.setQuality(trackInfo.quality[i]);
406
445
  });
407
- qualitySwitchDisplay.appendChild(qualityDiv);
408
446
  }
409
447
  } else {
410
448
  hideItem(qualitySwitchDisplay);
@@ -424,16 +462,17 @@ const initRemoteDisplay = function(options) {
424
462
  updateQualityInfo: function(videoQuality) {
425
463
  showItem(qualitySwitchDisplay);
426
464
  for (const qualityInfo of videoQuality) {
465
+ let qualityColor = QUALITY_COLORS.UNAVAILABLE;
466
+ if (qualityInfo.available === true) {
467
+ qualityColor = QUALITY_COLORS.AVAILABLE;
468
+ }
427
469
  for (const qualityDiv of qualityDivs) {
428
470
  if (qualityDiv.innerText === qualityInfo.quality){
429
- if (qualityInfo.available === true) {
430
- qualityDiv.style.color = "gray";
431
- } else {
432
- qualityDiv.style.color = "red";
433
- }
471
+ qualityDiv.style.color = qualityColor;
434
472
  break;
435
473
  }
436
474
  }
475
+ abr.setQualityAvailable(qualityInfo.quality, qualityInfo.available);
437
476
  }
438
477
  },
439
478
  hasVideo: function() {
@@ -450,6 +489,10 @@ const initRemoteDisplay = function(options) {
450
489
  currentQualityDisplay.innerHTML = video.videoWidth + "x" + video.videoHeight;
451
490
  }
452
491
  resizeVideo(event.target);
492
+ // Received a new quality, resume ABR is enabled
493
+ if (abr.isAuto()) {
494
+ abr.resume();
495
+ }
453
496
  });
454
497
  },
455
498
  setEventHandlers: function(video) {
@@ -482,6 +525,9 @@ const initRemoteDisplay = function(options) {
482
525
  isFullscreen = false;
483
526
  });
484
527
  },
528
+ setVideoABRTrack: function(track) {
529
+ abr.setTrack(track);
530
+ },
485
531
  audioMid: undefined,
486
532
  videoMid: undefined
487
533
  };
@@ -517,6 +563,7 @@ const initRemoteDisplay = function(options) {
517
563
  if (display.videoMid === transceiver.mid) {
518
564
  let stream = new MediaStream();
519
565
  stream.addTrack(transceiver.receiver.track);
566
+ display.setVideoABRTrack(transceiver.receiver.track);
520
567
  display.setVideo(stream);
521
568
  break;
522
569
  }
@@ -580,6 +627,202 @@ const initRemoteDisplay = function(options) {
580
627
  return button;
581
628
  }
582
629
 
630
+ const ABR = function(interval, thresholds, keepGoodTimeout, tryUpperTimeout) {
631
+ let abr = {
632
+ track: null,
633
+ interval: interval,
634
+ thresholds: thresholds,
635
+ qualities: [],
636
+ currentQualityName: null,
637
+ statTimer: null,
638
+ paused: false,
639
+ manual: false,
640
+ keepGoodTimeout: keepGoodTimeout,
641
+ keepGoodTimer: null,
642
+ tryUpperTimeout: tryUpperTimeout,
643
+ tryUpperTimer: null,
644
+ start: function() {
645
+ if (abr.interval) {
646
+ const thresholds = Thresholds();
647
+ for (const threshold of abr.thresholds) {
648
+ thresholds.add(threshold.parameter, threshold.maxLeap);
649
+ }
650
+ abr.statsTimer = setInterval(() => {
651
+ if (abr.track) {
652
+ room.getStats(abr.track, constants.SFU_RTC_STATS_TYPE.INBOUND, (stats) => {
653
+ if (thresholds.isReached(stats)) {
654
+ abr.shiftDown();
655
+ } else {
656
+ abr.useGoodQuality();
657
+ }
658
+ });
659
+ }
660
+ }, abr.interval);
661
+ }
662
+ },
663
+ stop: function() {
664
+ abr.stopKeeping();
665
+ abr.stopTrying();
666
+ if (abr.statsTimer) {
667
+ clearInterval(abr.statsTimer);
668
+ abr.statsTimer = null;
669
+ }
670
+ },
671
+ isEnabled: function () {
672
+ return (abr.interval > 0);
673
+ },
674
+ pause: function() {
675
+ abr.paused = true;
676
+ },
677
+ resume: function() {
678
+ abr.paused = false;
679
+ },
680
+ setAuto: function() {
681
+ abr.manual = false;
682
+ abr.resume();
683
+ },
684
+ setManual: function() {
685
+ abr.manual = true;
686
+ abr.pause();
687
+ },
688
+ isAuto: function() {
689
+ return !abr.manual;
690
+ },
691
+ setTrack: function(track) {
692
+ abr.track = track;
693
+ },
694
+ setQualitiesList: function(qualities) {
695
+ abr.qualities = qualities;
696
+ },
697
+ addQuality: function(name) {
698
+ abr.qualities.push({name: name, available: false, good: true});
699
+ },
700
+ setQualityAvailable: function(name, available) {
701
+ for (let i = 0; i < abr.qualities.length; i++) {
702
+ if (name === abr.qualities[i].name) {
703
+ abr.qualities[i].available = available;
704
+ }
705
+ }
706
+ },
707
+ setQualityGood: function(name, good) {
708
+ if (name) {
709
+ for (let i = 0; i < abr.qualities.length; i++) {
710
+ if (name === abr.qualities[i].name) {
711
+ abr.qualities[i].good = good;
712
+ }
713
+ }
714
+ }
715
+ },
716
+ getFirstAvailableQuality: function() {
717
+ for (let i = 0; i < abr.qualities.length; i++) {
718
+ if (abr.qualities[i].available) {
719
+ return abr.qualities[i];
720
+ }
721
+ }
722
+ return null;
723
+ },
724
+ getLowerQuality: function(name) {
725
+ let quality = null;
726
+ if (!name) {
727
+ // There were no switching yet, return a first available quality
728
+ return abr.getFirstAvailableQuality();
729
+ }
730
+ let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
731
+ for (let i = 0; i < currentIndex; i++) {
732
+ if (abr.qualities[i].available) {
733
+ quality = abr.qualities[i];
734
+ }
735
+ }
736
+ return quality;
737
+ },
738
+ getUpperQuality: function(name) {
739
+ let quality = null;
740
+ if (!name) {
741
+ // There were no switching yet, return a first available quality
742
+ return abr.getFirstAvailableQuality();
743
+ }
744
+ let currentIndex = abr.qualities.map(item => item.name).indexOf(name);
745
+ for (let i = currentIndex + 1; i < abr.qualities.length; i++) {
746
+ if (abr.qualities[i].available) {
747
+ quality = abr.qualities[i];
748
+ break;
749
+ }
750
+ }
751
+ return quality;
752
+ },
753
+ shiftDown: function() {
754
+ if (!abr.manual && !abr.paused) {
755
+ abr.stopKeeping();
756
+ abr.setQualityGood(abr.currentQualityName, false);
757
+ let quality = abr.getLowerQuality(abr.currentQualityName);
758
+ if (quality) {
759
+ console.log("Switching down to " + quality.name + " quality");
760
+ abr.setQuality(quality.name);
761
+ }
762
+ }
763
+ },
764
+ shiftUp: function() {
765
+ if (!abr.manual && !abr.paused) {
766
+ let quality = abr.getUpperQuality(abr.currentQualityName);
767
+ if (quality) {
768
+ if (quality.good) {
769
+ console.log("Switching up to " + quality.name + " quality");
770
+ abr.setQuality(quality.name);
771
+ } else {
772
+ abr.tryUpper();
773
+ }
774
+ }
775
+ }
776
+ },
777
+ useGoodQuality: function() {
778
+ if (!abr.manual && !abr.paused) {
779
+ if (!abr.currentQualityName) {
780
+ let quality = abr.getFirstAvailableQuality();
781
+ abr.currentQualityName = quality.name;
782
+ }
783
+ abr.setQualityGood(abr.currentQualityName, true);
784
+ abr.keepGoodQuality();
785
+ }
786
+ },
787
+ keepGoodQuality: function() {
788
+ if (abr.keepGoodTimeout && !abr.keepGoodTimer && abr.getUpperQuality(abr.currentQualityName)) {
789
+ abr.keepGoodTimer = setTimeout(() => {
790
+ abr.shiftUp();
791
+ abr.stopKeeping();
792
+ }, abr.keepGoodTimeout);
793
+ }
794
+ },
795
+ stopKeeping: function() {
796
+ if (abr.keepGoodTimer) {
797
+ clearTimeout(abr.keepGoodTimer);
798
+ abr.keepGoodTimer = null;
799
+ }
800
+ },
801
+ tryUpper: function() {
802
+ let quality = abr.getUpperQuality(abr.currentQualityName);
803
+ if (abr.tryUpperTimeout && !abr.tryUpperTimer && quality) {
804
+ abr.tryUpperTimer = setTimeout(() => {
805
+ abr.setQualityGood(quality.name, true);
806
+ abr.stopTrying();
807
+ }, abr.tryUpperTimeout);
808
+ }
809
+ },
810
+ stopTrying: function() {
811
+ if (abr.tryUpperTimer) {
812
+ clearTimeout(abr.tryUpperTimer);
813
+ abr.tryUpperTimer = null;
814
+ }
815
+ },
816
+ setQuality: async function(name) {
817
+ // Pause switching until a new quality is received
818
+ abr.pause();
819
+ abr.currentQualityName = name;
820
+ await room.changeQuality(abr.track.id, abr.currentQualityName);
821
+ }
822
+ }
823
+ return abr;
824
+ }
825
+
583
826
  return {
584
827
  stop: stop
585
828
  }
@@ -656,6 +899,28 @@ const createContainer = function(parent) {
656
899
  return div;
657
900
  }
658
901
 
902
+ const createQualityButton = function(qualityName, buttonsList, parent) {
903
+ const div = document.createElement("button");
904
+ div.innerText = qualityName;
905
+ div.setAttribute("style", "display:inline-block; border: solid; border-width: 1px");
906
+ div.style.color = QUALITY_COLORS.UNAVAILABLE;
907
+ if (buttonsList) {
908
+ buttonsList.push(div);
909
+ }
910
+ if (parent) {
911
+ parent.appendChild(div);
912
+ }
913
+ return div;
914
+ }
915
+
916
+ const setQualityButtonsColor = function(qualityDivs) {
917
+ for (let c = 0; c < qualityDivs.length; c++) {
918
+ if (qualityDivs[c].style.color !== QUALITY_COLORS.UNAVAILABLE) {
919
+ qualityDivs[c].style.color = QUALITY_COLORS.AVAILABLE;
920
+ }
921
+ }
922
+ }
923
+
659
924
  // Helper functions to display/hide an element
660
925
  const showItem = function(tag) {
661
926
  if (tag) {
@@ -0,0 +1,46 @@
1
+ const Threshold = function(parameter, maxLeap) {
2
+ const threshold = {
3
+ parameter: parameter,
4
+ maxLeap: maxLeap,
5
+ filter: SFU.createFilter(),
6
+ previousValue: -1,
7
+ isReached: function(stats) {
8
+ let hasLeap = false;
9
+ if (stats && parameter in stats) {
10
+ let value = threshold.filter.filter(stats[parameter]);
11
+ if (threshold.previousValue > -1) {
12
+ if (Math.round(Math.abs(value - threshold.previousValue)) > maxLeap) {
13
+ hasLeap = true;
14
+ }
15
+ }
16
+ threshold.previousValue = value;
17
+ }
18
+ return hasLeap;
19
+ }
20
+ }
21
+ return threshold;
22
+ }
23
+
24
+ const Thresholds = function () {
25
+ const thresholds = {
26
+ thresholds: {},
27
+ add: function(parameter, maxLeap) {
28
+ if (!thresholds.thresholds[parameter]) {
29
+ thresholds.thresholds[parameter] = new Threshold(parameter, maxLeap);
30
+ }
31
+ },
32
+ remove: function(parameter) {
33
+ if (thresholds.thresholds[parameter]) {
34
+ delete thresholds.thresholds[parameter];
35
+ }
36
+ },
37
+ isReached: function(stats) {
38
+ let result = false;
39
+ Object.keys(thresholds.thresholds).forEach((key) => {
40
+ result = result || thresholds.thresholds[key].isReached(stats);
41
+ });
42
+ return result;
43
+ }
44
+ }
45
+ return thresholds;
46
+ }
@@ -6,7 +6,6 @@ let playState;
6
6
  const PLAY = "play";
7
7
  const STOP = "stop";
8
8
  const PRELOADER_URL="../commons/media/silence.mp3";
9
- const MAX_AWAIT_MS=5000;
10
9
 
11
10
 
12
11
  /**
@@ -31,27 +30,17 @@ const CurrentState = function(prefix) {
31
30
  session: null,
32
31
  room: null,
33
32
  roomEnded: false,
34
- timeout: null,
35
- timer: null,
36
- promise: null,
37
33
  set: function(pc, session, room) {
38
34
  state.pc = pc;
39
35
  state.session = session;
40
36
  state.room = room;
41
37
  state.roomEnded = false;
42
- state.timeout = null;
43
- state.timer = null;
44
- state.promise = null;
45
38
  },
46
39
  clear: function() {
47
- state.stopWaiting();
48
40
  state.room = null;
49
41
  state.session = null;
50
42
  state.pc = null;
51
43
  state.roomEnded = false;
52
- state.timeout = null;
53
- state.timer = null;
54
- state.promise = null;
55
44
  },
56
45
  setRoomEnded: function() {
57
46
  state.roomEnded = true;
@@ -81,47 +70,10 @@ const CurrentState = function(prefix) {
81
70
  return (state.room && !state.roomEnded && state.pc);
82
71
  },
83
72
  isConnected: function() {
84
- return (state.session && state.session.state() == constants.SFU_STATE.CONNECTED);
73
+ return (state.session && state.session.state() === constants.SFU_STATE.CONNECTED);
85
74
  },
86
75
  isRoomEnded: function() {
87
76
  return state.roomEnded;
88
- },
89
- waitFor: async function(promise, ms) {
90
- // Create a promise that rejects in <ms> milliseconds
91
- state.promise = promise;
92
- state.timeout = new Promise((resolve, reject) => {
93
- state.resolve = resolve;
94
- state.timer = setTimeout(() => {
95
- clearTimeout(state.timer);
96
- state.timer = null;
97
- state.promise = null;
98
- state.timeout = null;
99
- reject('Operation timed out in '+ ms + ' ms.')
100
- }, ms)
101
- });
102
-
103
- // Returns a race between our timeout and the passed in promise
104
- Promise.race([
105
- state.promise,
106
- state.timeout
107
- ]).then(() => {
108
- state.stopWaiting();
109
- }).catch((e) => {
110
- setStatus(state.errInfoId(), e, "red");
111
- });
112
- },
113
- stopWaiting: function() {
114
- if (state.timer) {
115
- clearTimeout(state.timer);
116
- state.timer = null;
117
- }
118
- if (state.timeout) {
119
- state.resolve();
120
- state.timeout = null;
121
- }
122
- if (state.promise) {
123
- state.promise = null;
124
- }
125
77
  }
126
78
  };
127
79
  return state;
@@ -172,12 +124,10 @@ const connect = async function(state) {
172
124
  // Set up session ending events
173
125
  session.on(constants.SFU_EVENT.DISCONNECTED, function() {
174
126
  onStopClick(state);
175
- state.clear();
176
127
  onDisconnected(state);
177
128
  setStatus(state.statusId(), "DISCONNECTED", "green");
178
129
  }).on(constants.SFU_EVENT.FAILED, function(e) {
179
130
  onStopClick(state);
180
- state.clear();
181
131
  onDisconnected(state);
182
132
  setStatus(state.statusId(), "FAILED", "red");
183
133
  if (e.status && e.statusText) {
@@ -187,18 +137,17 @@ const connect = async function(state) {
187
137
  }
188
138
  });
189
139
  // Connected successfully
190
- state.set(pc, session, session.room());
191
- onConnected(state);
140
+ onConnected(state, pc, session);
192
141
  setStatus(state.statusId(), "ESTABLISHED", "green");
193
142
  } catch(e) {
194
- state.clear();
195
143
  onDisconnected(state);
196
144
  setStatus(state.statusId(), "FAILED", "red");
197
145
  setStatus(state.errInfoId(), e, "red");
198
146
  }
199
147
  }
200
148
 
201
- const onConnected = async function(state) {
149
+ const onConnected = async function(state, pc, session) {
150
+ state.set(pc, session, session.room());
202
151
  $("#" + state.buttonId()).text("Stop").off('click').click(function () {
203
152
  onStopClick(state);
204
153
  });
@@ -209,22 +158,16 @@ const onConnected = async function(state) {
209
158
  state.room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
210
159
  setStatus(state.errInfoId(), e, "red");
211
160
  state.setRoomEnded();
212
- state.stopWaiting();
213
161
  onStopClick(state);
214
162
  }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
215
- setStatus(state.errInfoId(), e.operation + " failed: " + e.error, "red");
216
- state.setRoomEnded();
217
- state.stopWaiting();
218
- onStopClick(state);
163
+ onOperationFailed(state, e);
219
164
  }).on(constants.SFU_ROOM_EVENT.ENDED, function (e) {
220
165
  setStatus(state.errInfoId(), "Room "+state.room.name()+" has ended", "red");
221
166
  state.setRoomEnded();
222
- state.stopWaiting();
223
167
  onStopClick(state);
224
168
  }).on(constants.SFU_ROOM_EVENT.DROPPED, function (e) {
225
169
  setStatus(state.errInfoId(), "Dropped from the room "+state.room.name()+" due to network issues", "red");
226
170
  state.setRoomEnded();
227
- state.stopWaiting();
228
171
  onStopClick(state);
229
172
  });
230
173
  await playStreams(state);
@@ -233,6 +176,7 @@ const onConnected = async function(state) {
233
176
  }
234
177
 
235
178
  const onDisconnected = function(state) {
179
+ state.clear();
236
180
  $("#" + state.buttonId()).text(state.buttonText()).off('click').click(function () {
237
181
  onStartClick(state);
238
182
  }).prop('disabled', false);
@@ -255,11 +199,22 @@ const onStartClick = function(state) {
255
199
  }
256
200
 
257
201
  const onStopClick = async function(state) {
258
- $("#" + state.buttonId()).prop('disabled', true);
259
202
  stopStreams(state);
260
203
  if (state.isConnected()) {
261
- state.waitFor(state.session.disconnect(), MAX_AWAIT_MS);
204
+ $("#" + state.buttonId()).prop('disabled', true);
205
+ await state.session.disconnect();
206
+ onDisconnected(state);
207
+ }
208
+ }
209
+
210
+ const onOperationFailed = function(state, event) {
211
+ if (event.operation && event.error) {
212
+ setStatus(state.errInfoId(), event.operation + " failed: " + event.error, "red");
213
+ } else {
214
+ setStatus(state.errInfoId(), event, "red");
262
215
  }
216
+ state.setRoomEnded();
217
+ onStopClick(state);
263
218
  }
264
219
 
265
220
  const playStreams = async function(state) {
@@ -271,11 +226,15 @@ const playStreams = async function(state) {
271
226
  peerConnection: state.pc
272
227
  });
273
228
  // Start WebRTC negotiation
274
- state.waitFor(state.room.join(state.pc), MAX_AWAIT_MS);
229
+ await state.room.join(state.pc);
275
230
  } catch(e) {
276
- console.error("Failed to play streams: " + e);
277
- setStatus(state.errInfoId(), e.name, "red");
278
- onStopClick(state);
231
+ if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
232
+ onOperationFailed(state, e);
233
+ } else {
234
+ console.error("Failed to play streams: " + e);
235
+ setStatus(state.errInfoId(), e.name, "red");
236
+ onStopClick(state);
237
+ }
279
238
  }
280
239
  }
281
240
 
@@ -292,7 +251,7 @@ const setStatus = function (status, text, color) {
292
251
  }
293
252
 
294
253
  const validateForm = function (formId) {
295
- var valid = true;
254
+ let valid = true;
296
255
  $('#' + formId + ' :text').each(function () {
297
256
  if (!$(this).val()) {
298
257
  highlightInput($(this));
package/src/sfu.ts CHANGED
@@ -1,8 +1,10 @@
1
+ import KalmanFilter from 'kalmanjs';
1
2
  import {
2
3
  Sfu,
3
4
  RoomEvent,
4
5
  SfuEvent,
5
- State
6
+ State,
7
+ StatsType
6
8
  } from "@flashphoner/sfusdk";
7
9
 
8
10
  export async function createRoom(options: {
@@ -35,5 +37,10 @@ export async function createRoom(options: {
35
37
  export const constants = {
36
38
  SFU_EVENT: SfuEvent,
37
39
  SFU_ROOM_EVENT: RoomEvent,
38
- SFU_STATE: State
40
+ SFU_STATE: State,
41
+ SFU_RTC_STATS_TYPE: StatsType
39
42
  }
43
+
44
+ export function createFilter() : KalmanFilter {
45
+ return new KalmanFilter();
46
+ }
@@ -9,7 +9,6 @@ const PUBLISH = "publish";
9
9
  const PLAY = "play";
10
10
  const STOP = "stop";
11
11
  const PRELOADER_URL="../commons/media/silence.mp3";
12
- const MAX_AWAIT_MS=5000;
13
12
 
14
13
 
15
14
  /**
@@ -58,28 +57,18 @@ const CurrentState = function(prefix) {
58
57
  session: null,
59
58
  room: null,
60
59
  roomEnded: false,
61
- timeout: null,
62
- timer: null,
63
- promise: null,
64
60
  starting: false,
65
61
  set: function(pc, session, room) {
66
62
  state.pc = pc;
67
63
  state.session = session;
68
64
  state.room = room;
69
65
  state.roomEnded = false;
70
- state.timeout = null;
71
- state.timer = null;
72
- state.promise = null;
73
66
  },
74
67
  clear: function() {
75
- state.stopWaiting();
76
68
  state.room = null;
77
69
  state.session = null;
78
70
  state.pc = null;
79
71
  state.roomEnded = false;
80
- state.timeout = null;
81
- state.timer = null;
82
- state.promise = null;
83
72
  },
84
73
  setRoomEnded: function() {
85
74
  state.roomEnded = true;
@@ -109,48 +98,11 @@ const CurrentState = function(prefix) {
109
98
  return (state.room && !state.roomEnded && state.pc);
110
99
  },
111
100
  isConnected: function() {
112
- return (state.session && state.session.state() == constants.SFU_STATE.CONNECTED);
101
+ return (state.session && state.session.state() === constants.SFU_STATE.CONNECTED);
113
102
  },
114
103
  isRoomEnded: function() {
115
104
  return state.roomEnded;
116
105
  },
117
- waitFor: async function(promise, ms) {
118
- // Create a promise that rejects in <ms> milliseconds
119
- state.promise = promise;
120
- state.timeout = new Promise((resolve, reject) => {
121
- state.resolve = resolve;
122
- state.timer = setTimeout(() => {
123
- clearTimeout(state.timer);
124
- state.timer = null;
125
- state.promise = null;
126
- state.timeout = null;
127
- reject('Operation timed out in '+ ms + ' ms.')
128
- }, ms)
129
- });
130
-
131
- // Returns a race between our timeout and the passed in promise
132
- Promise.race([
133
- state.promise,
134
- state.timeout
135
- ]).then(() => {
136
- state.stopWaiting();
137
- }).catch((e) => {
138
- setStatus(state.errInfoId(), e, "red");
139
- });
140
- },
141
- stopWaiting: function() {
142
- if (state.timer) {
143
- clearTimeout(state.timer);
144
- state.timer = null;
145
- }
146
- if (state.timeout) {
147
- state.resolve();
148
- state.timeout = null;
149
- }
150
- if (state.promise) {
151
- state.promise = null;
152
- }
153
- },
154
106
  setStarting: function(value) {
155
107
  state.starting = value;
156
108
  },
@@ -212,12 +164,10 @@ const connect = async function(state) {
212
164
  // Set up session ending events
213
165
  session.on(constants.SFU_EVENT.DISCONNECTED, function() {
214
166
  onStopClick(state);
215
- state.clear();
216
167
  onDisconnected(state);
217
168
  setStatus(state.statusId(), "DISCONNECTED", "green");
218
169
  }).on(constants.SFU_EVENT.FAILED, function(e) {
219
170
  onStopClick(state);
220
- state.clear();
221
171
  onDisconnected(state);
222
172
  setStatus(state.statusId(), "FAILED", "red");
223
173
  if (e.status && e.statusText) {
@@ -227,17 +177,16 @@ const connect = async function(state) {
227
177
  }
228
178
  });
229
179
  // Connected successfully
230
- state.set(pc, session, session.room());
231
- onConnected(state);
180
+ onConnected(state, pc, session);
232
181
  setStatus(state.statusId(), "ESTABLISHED", "green");
233
182
  } catch(e) {
234
- state.clear();
235
183
  onDisconnected(state);
236
184
  setStatus(state.statusId(), "FAILED", "red");
237
185
  setStatus(state.errInfoId(), e, "red");
238
186
  }}
239
187
 
240
- const onConnected = function(state) {
188
+ const onConnected = function(state, pc, session) {
189
+ state.set(pc, session, session.room());
241
190
  $("#" + state.buttonId()).text("Stop").off('click').click(function () {
242
191
  onStopClick(state);
243
192
  });
@@ -248,28 +197,23 @@ const onConnected = function(state) {
248
197
  state.room.on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
249
198
  setStatus(state.errInfoId(), e, "red");
250
199
  state.setRoomEnded();
251
- state.stopWaiting();
252
200
  onStopClick(state);
253
201
  }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
254
- setStatus(state.errInfoId(), e.operation + " failed: " + e.error, "red");
255
- state.setRoomEnded();
256
- state.stopWaiting();
257
- onStopClick(state);
202
+ onOperationFailed(state, e);
258
203
  }).on(constants.SFU_ROOM_EVENT.ENDED, function () {
259
204
  setStatus(state.errInfoId(), "Room "+state.room.name()+" has ended", "red");
260
205
  state.setRoomEnded();
261
- state.stopWaiting();
262
206
  onStopClick(state);
263
207
  }).on(constants.SFU_ROOM_EVENT.DROPPED, function () {
264
208
  setStatus(state.errInfoId(), "Dropped from the room "+state.room.name()+" due to network issues", "red");
265
209
  state.setRoomEnded();
266
- state.stopWaiting();
267
210
  onStopClick(state);
268
211
  });
269
212
  startStreaming(state);
270
213
  }
271
214
 
272
215
  const onDisconnected = function(state) {
216
+ state.clear();
273
217
  $("#" + state.buttonId()).text(state.buttonText()).off('click').click(function () {
274
218
  onStartClick(state);
275
219
  }).prop('disabled', false);
@@ -305,10 +249,24 @@ const onStartClick = function(state) {
305
249
  }
306
250
  }
307
251
 
308
- const onStopClick = function(state) {
252
+ const onOperationFailed = function(state, event) {
253
+ if (event.operation && event.error) {
254
+ setStatus(state.errInfoId(), event.operation + " failed: " + event.error, "red");
255
+ } else {
256
+ setStatus(state.errInfoId(), event, "red");
257
+ }
258
+ state.setRoomEnded();
259
+ onStopClick(state);
260
+ }
261
+
262
+ const onStopClick = async function(state) {
309
263
  state.setStarting(false);
310
- $("#" + state.buttonId()).prop('disabled', true);
311
264
  stopStreaming(state);
265
+ if (state.isConnected()) {
266
+ $("#" + state.buttonId()).prop('disabled', true);
267
+ await state.session.disconnect();
268
+ onDisconnected(state);
269
+ }
312
270
  }
313
271
 
314
272
  const startStreaming = async function(state) {
@@ -326,15 +284,12 @@ const startStreaming = async function(state) {
326
284
  }
327
285
  }
328
286
 
329
- const stopStreaming = async function(state) {
287
+ const stopStreaming = function(state) {
330
288
  if (state.is(PUBLISH)) {
331
289
  unPublishStreams(state);
332
290
  } else if (state.is(PLAY)) {
333
291
  stopStreams(state);
334
292
  }
335
- if (state.isConnected()) {
336
- state.waitFor(state.session.disconnect(), MAX_AWAIT_MS);
337
- }
338
293
  }
339
294
 
340
295
  const publishStreams = async function(state) {
@@ -362,12 +317,16 @@ const publishStreams = async function(state) {
362
317
  });
363
318
  });
364
319
  //start WebRTC negotiation
365
- state.waitFor(state.room.join(state.pc, null, config), MAX_AWAIT_MS);
320
+ await state.room.join(state.pc, null, config);
366
321
  }
367
322
  } catch(e) {
368
- console.error("Failed to capture streams: " + e);
369
- setStatus(state.errInfoId(), e.name, "red");
370
- onStopClick(state);
323
+ if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
324
+ onOperationFailed(state, e);
325
+ } else {
326
+ console.error("Failed to capture streams: " + e);
327
+ setStatus(state.errInfoId(), e.name, "red");
328
+ onStopClick(state);
329
+ }
371
330
  }
372
331
  }
373
332
  }
@@ -388,11 +347,15 @@ const playStreams = async function(state) {
388
347
  peerConnection: state.pc
389
348
  });
390
349
  //start WebRTC negotiation
391
- state.waitFor(state.room.join(state.pc), MAX_AWAIT_MS);
350
+ await state.room.join(state.pc);
392
351
  } catch(e) {
393
- console.error("Failed to play streams: " + e);
394
- setStatus(state.errInfoId(), e.name, "red");
395
- onStopClick(state);
352
+ if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
353
+ onOperationFailed(state, e);
354
+ } else {
355
+ console.error("Failed to play streams: " + e);
356
+ setStatus(state.errInfoId(), e.name, "red");
357
+ onStopClick(state);
358
+ }
396
359
  }
397
360
  }
398
361
  }
@@ -404,7 +367,7 @@ const stopStreams = function(state) {
404
367
  }
405
368
 
406
369
  const subscribeTrackToEndedEvent = function(room, track, pc) {
407
- track.addEventListener("ended", function() {
370
+ track.addEventListener("ended", async function() {
408
371
  //track ended, see if we need to cleanup
409
372
  let negotiate = false;
410
373
  for (const sender of pc.getSenders()) {
@@ -417,7 +380,7 @@ const subscribeTrackToEndedEvent = function(room, track, pc) {
417
380
  }
418
381
  if (negotiate) {
419
382
  //kickoff renegotiation
420
- room.updateState();
383
+ await room.updateState();
421
384
  }
422
385
  });
423
386
  };
@@ -15,6 +15,7 @@
15
15
  <script type="text/javascript" src="../sfu.js"></script>
16
16
  <script type="text/javascript" src="../commons/js/util.js"></script>
17
17
  <script type="text/javascript" src="../commons/js/config.js"></script>
18
+ <script type="text/javascript" src="../commons/js/stats.js"></script>
18
19
  <script type="text/javascript" src="../commons/js/display.js"></script>
19
20
  <script type="text/javascript" src="player.js"></script>
20
21
  </head>
@@ -1,7 +1,8 @@
1
1
  const constants = SFU.constants;
2
2
  const sfu = SFU;
3
3
  const PRELOADER_URL="../commons/media/silence.mp3";
4
- const MAX_AWAIT_MS=5000;
4
+ const playStatus = "playStatus";
5
+ const playErrorInfo = "playErrorInfo";
5
6
 
6
7
 
7
8
  /**
@@ -14,27 +15,17 @@ const CurrentState = function() {
14
15
  room: null,
15
16
  remoteDisplay: null,
16
17
  roomEnded: false,
17
- timeout: null,
18
- timer: null,
19
- promise: null,
20
18
  set: function(pc, session, room) {
21
19
  state.pc = pc;
22
20
  state.session = session;
23
21
  state.room = room;
24
22
  state.roomEnded = false;
25
- state.timeout = null;
26
- state.timer = null;
27
- state.promise = null;
28
23
  },
29
24
  clear: function() {
30
- state.stopWaiting();
31
25
  state.room = null;
32
26
  state.session = null;
33
27
  state.pc = null;
34
28
  state.roomEnded = false;
35
- state.timeout = null;
36
- state.timer = null;
37
- state.promise = null;
38
29
  },
39
30
  setRoomEnded: function() {
40
31
  state.roomEnded = true;
@@ -43,7 +34,7 @@ const CurrentState = function() {
43
34
  return state.roomEnded;
44
35
  },
45
36
  isConnected: function() {
46
- return (state.session && state.session.state() == constants.SFU_STATE.CONNECTED);
37
+ return (state.session && state.session.state() === constants.SFU_STATE.CONNECTED);
47
38
  },
48
39
  isActive: function() {
49
40
  return (state.room && !state.roomEnded && state.pc);
@@ -56,45 +47,7 @@ const CurrentState = function() {
56
47
  state.remoteDisplay.stop();
57
48
  state.remoteDisplay = null;
58
49
  }
59
- },
60
- waitFor: async function(promise, ms) {
61
- // Create a promise that rejects in <ms> milliseconds
62
- state.promise = promise;
63
- state.timeout = new Promise((resolve, reject) => {
64
- state.resolve = resolve;
65
- state.timer = setTimeout(() => {
66
- clearTimeout(state.timer);
67
- state.timer = null;
68
- state.promise = null;
69
- state.timeout = null;
70
- reject('Operation timed out in '+ ms + ' ms.')
71
- }, ms)
72
- });
73
-
74
- // Returns a race between our timeout and the passed in promise
75
- Promise.race([
76
- state.promise,
77
- state.timeout
78
- ]).then(() => {
79
- state.stopWaiting();
80
- }).catch((e) => {
81
- setStatus("playStatus", e, "red");
82
- });
83
- },
84
- stopWaiting: function() {
85
- if (state.timer) {
86
- clearTimeout(state.timer);
87
- state.timer = null;
88
- }
89
- if (state.timeout) {
90
- state.resolve();
91
- state.timeout = null;
92
- }
93
- if (state.promise) {
94
- state.promise = null;
95
- }
96
50
  }
97
-
98
51
  };
99
52
  return state;
100
53
  }
@@ -128,41 +81,38 @@ const connect = async function(state) {
128
81
  pin: 123456
129
82
  }
130
83
  // Clean state display items
131
- setStatus("playStatus", "");
132
- setStatus("playErrorInfo", "");
84
+ setStatus(playStatus, "");
85
+ setStatus(playErrorInfo, "");
133
86
  try {
134
87
  // Connect to the server (room should already exist)
135
88
  const session = await sfu.createRoom(roomConfig);
136
89
  // Set up session ending events
137
90
  session.on(constants.SFU_EVENT.DISCONNECTED, function() {
138
91
  onStopClick(state);
139
- state.clear();
140
92
  onDisconnected(state);
141
- setStatus("playStatus", "DISCONNECTED", "green");
93
+ setStatus(playStatus, "DISCONNECTED", "green");
142
94
  }).on(constants.SFU_EVENT.FAILED, function(e) {
143
95
  onStopClick(state);
144
- state.clear();
145
96
  onDisconnected(state);
146
- setStatus("playStatus", "FAILED", "red");
97
+ setStatus(playStatus, "FAILED", "red");
147
98
  if (e.status && e.statusText) {
148
- setStatus("playErrorInfo", e.status + " " + e.statusText, "red");
99
+ setStatus(playErrorInfo, e.status + " " + e.statusText, "red");
149
100
  } else if (e.type && e.info) {
150
- setStatus("playErrorInfo", e.type + ": " + e.info, "red");
101
+ setStatus(playErrorInfo, e.type + ": " + e.info, "red");
151
102
  }
152
103
  });
153
104
  // Connected successfully
154
- state.set(pc, session, session.room());
155
- onConnected(state);
156
- setStatus("playStatus", "CONNECTING...", "black");
105
+ onConnected(state, pc, session);
106
+ setStatus(playStatus, "CONNECTING...", "black");
157
107
  } catch(e) {
158
- state.clear();
159
108
  onDisconnected(state);
160
- setStatus("playStatus", "FAILED", "red");
161
- setStatus("playErrorInfo", e, "red");
109
+ setStatus(playStatus, "FAILED", "red");
110
+ setStatus(playErrorInfo, e, "red");
162
111
  }
163
112
  }
164
113
 
165
- const onConnected = async function(state) {
114
+ const onConnected = async function(state, pc, session) {
115
+ state.set(pc, session, session.room());
166
116
  $("#playBtn").text("Stop").off('click').click(function () {
167
117
  onStopClick(state);
168
118
  });
@@ -171,34 +121,28 @@ const onConnected = async function(state) {
171
121
  // Add room event handling
172
122
  state.room.on(constants.SFU_ROOM_EVENT.PARTICIPANT_LIST, function(e) {
173
123
  // If the room is empty, the stream is not published yet
174
- if(!e.participants || e.participants.length === 0) {
175
- setStatus("playErrorInfo", "ABR stream is not published", "red");
124
+ if (!e.participants || e.participants.length === 0) {
125
+ setStatus(playErrorInfo, "ABR stream is not published", "red");
176
126
  onStopClick(state);
177
127
  }
178
128
  else {
179
- setStatus("playStatus", "ESTABLISHED", "green");
129
+ setStatus(playStatus, "ESTABLISHED", "green");
180
130
  $("#placeholder").hide();
181
131
  }
182
132
  }).on(constants.SFU_ROOM_EVENT.FAILED, function(e) {
183
133
  // Display error state
184
- setStatus("playErrorInfo", e, "red");
134
+ setStatus(playErrorInfo, e, "red");
185
135
  }).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
186
- // Display the operation failed
187
- setStatus("playErrorInfo", e.operation + " failed: " + e.error, "red");
188
- state.setRoomEnded();
189
- state.stopWaiting();
190
- onStopClick(state);
136
+ onOperationFailed(state);
191
137
  }).on(constants.SFU_ROOM_EVENT.ENDED, function () {
192
138
  // Publishing is stopped, dispose playback and close connection
193
- setStatus("playErrorInfo", "ABR stream is stopped", "red");
139
+ setStatus(playErrorInfo, "ABR stream is stopped", "red");
194
140
  state.setRoomEnded();
195
- state.stopWaiting();
196
141
  onStopClick(state);
197
142
  }).on(constants.SFU_ROOM_EVENT.DROPPED, function () {
198
143
  // Client dropped from the room, dispose playback and close connection
199
- setStatus("playErrorInfo", "Playback is dropped due to network issues", "red");
144
+ setStatus(playErrorInfo, "Playback is dropped due to network issues", "red");
200
145
  state.setRoomEnded();
201
- state.stopWaiting();
202
146
  onStopClick(state);
203
147
  });
204
148
  await playStreams(state);
@@ -207,6 +151,7 @@ const onConnected = async function(state) {
207
151
  }
208
152
 
209
153
  const onDisconnected = function(state) {
154
+ state.clear();
210
155
  $("#placeholder").show();
211
156
  $("#playBtn").text("Play").off('click').click(function () {
212
157
  onStartClick(state);
@@ -229,27 +174,52 @@ const onStartClick = function(state) {
229
174
  }
230
175
 
231
176
  const onStopClick = async function(state) {
232
- $("#playBtn").prop('disabled', true);
233
177
  stopStreams(state);
234
178
  if (state.isConnected()) {
235
- state.waitFor(state.session.disconnect(), MAX_AWAIT_MS);
179
+ $("#playBtn").prop('disabled', true);
180
+ await state.session.disconnect();
181
+ onDisconnected(state);
182
+ }
183
+ }
184
+
185
+ const onOperationFailed = function(state, event) {
186
+ if (event.operation && event.error) {
187
+ setStatus(playErrorInfo, e.operation + " failed: " + e.error, "red");
188
+ } else {
189
+ setStatus(playErrorInfo, event, "red");
236
190
  }
191
+ state.setRoomEnded();
192
+ onStopClick(state);
237
193
  }
238
194
 
239
195
  const playStreams = async function(state) {
240
- // Create remote display item to show remote streams
241
- state.setDisplay(initRemoteDisplay({
242
- div: document.getElementById("remoteVideo"),
243
- room: state.room,
244
- peerConnection: state.pc,
245
- displayOptions: {
246
- publisher: false,
247
- quality: true,
248
- type: false
196
+ try {
197
+ // Create remote display item to show remote streams
198
+ state.setDisplay(initRemoteDisplay({
199
+ div: document.getElementById("remoteVideo"),
200
+ room: state.room,
201
+ peerConnection: state.pc,
202
+ displayOptions: {
203
+ publisher: false,
204
+ quality: true,
205
+ type: false,
206
+ abr: true,
207
+ abrKeepOnGoodQuality: 20000,
208
+ abrTryForUpperQuality: 30000
209
+ }
210
+ }));
211
+ // Start WebRTC negotiation
212
+ await state.room.join(state.pc);
213
+ } catch(e) {
214
+ if (e.type === constants.SFU_ROOM_EVENT.OPERATION_FAILED) {
215
+ onOperationFailed(state, e);
216
+ } else {
217
+ console.error("Failed to play streams: " + e);
218
+ setStatus(playErrorInfo, e.name, "red");
219
+ onStopClick(state);
249
220
  }
250
- }));
251
- // Start WebRTC negotiation
252
- state.waitFor(state.room.join(state.pc), MAX_AWAIT_MS);
221
+ }
222
+
253
223
  }
254
224
 
255
225
  const stopStreams = function(state) {
@@ -264,15 +234,15 @@ const setStatus = function (status, text, color) {
264
234
  }
265
235
 
266
236
  const validateForm = function (formId) {
267
- var valid = true;
237
+ let valid = true;
268
238
  $('#' + formId + ' :text').each(function () {
269
239
  if (!$(this).val()) {
270
240
  highlightInput($(this));
271
241
  valid = false;
272
- setStatus("playErrorInfo", "Fields cannot be empty", "red");
242
+ setStatus(playErrorInfo, "Fields cannot be empty", "red");
273
243
  } else {
274
244
  removeHighlight($(this));
275
- setStatus("playErrorInfo", "");
245
+ setStatus(playErrorInfo, "");
276
246
  }
277
247
  });
278
248
  return valid;