@flashphoner/sfusdk-examples 2.0.221 → 2.0.226
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 +3 -2
- package/src/client/main.js +23 -7
- package/src/commons/js/display.js +285 -20
- package/src/commons/js/stats.js +46 -0
- package/src/player/player.js +28 -69
- package/src/sfu.ts +9 -2
- package/src/two-way-streaming/two-way-streaming.js +41 -78
- package/src/webrtc-abr-player/player.html +1 -0
- package/src/webrtc-abr-player/player.js +65 -95
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flashphoner/sfusdk-examples",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.226",
|
|
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
|
}
|
package/src/client/main.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
390
|
-
qualityDivs
|
|
391
|
-
qualityDiv.
|
|
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 ===
|
|
438
|
+
if (qualityDiv.style.color === QUALITY_COLORS.UNAVAILABLE) {
|
|
397
439
|
return;
|
|
398
440
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
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
|
-
|
|
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
|
+
}
|
package/src/player/player.js
CHANGED
|
@@ -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()
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
229
|
+
await state.room.join(state.pc);
|
|
275
230
|
} catch(e) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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()
|
|
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
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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.
|
|
320
|
+
await state.room.join(state.pc, null, config);
|
|
366
321
|
}
|
|
367
322
|
} catch(e) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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.
|
|
350
|
+
await state.room.join(state.pc);
|
|
392
351
|
} catch(e) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
|
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()
|
|
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(
|
|
132
|
-
setStatus(
|
|
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(
|
|
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(
|
|
97
|
+
setStatus(playStatus, "FAILED", "red");
|
|
147
98
|
if (e.status && e.statusText) {
|
|
148
|
-
setStatus(
|
|
99
|
+
setStatus(playErrorInfo, e.status + " " + e.statusText, "red");
|
|
149
100
|
} else if (e.type && e.info) {
|
|
150
|
-
setStatus(
|
|
101
|
+
setStatus(playErrorInfo, e.type + ": " + e.info, "red");
|
|
151
102
|
}
|
|
152
103
|
});
|
|
153
104
|
// Connected successfully
|
|
154
|
-
state
|
|
155
|
-
|
|
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(
|
|
161
|
-
setStatus(
|
|
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(
|
|
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(
|
|
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(
|
|
134
|
+
setStatus(playErrorInfo, e, "red");
|
|
185
135
|
}).on(constants.SFU_ROOM_EVENT.OPERATION_FAILED, function (e) {
|
|
186
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
242
|
+
setStatus(playErrorInfo, "Fields cannot be empty", "red");
|
|
273
243
|
} else {
|
|
274
244
|
removeHighlight($(this));
|
|
275
|
-
setStatus(
|
|
245
|
+
setStatus(playErrorInfo, "");
|
|
276
246
|
}
|
|
277
247
|
});
|
|
278
248
|
return valid;
|