Browse Source

Creates simulcast module

master
hristoterezov 10 years ago
parent
commit
faaf24d3c4

+ 1
- 1
index.html View File

@@ -11,7 +11,6 @@
11 11
     <meta itemprop="image" content="/images/jitsilogo.png"/>
12 12
     <script src="libs/jquery-2.1.1.min.js"></script>
13 13
     <script src="config.js?v=5"></script><!-- adapt to your needs, i.e. set hosts and bosh path -->
14
-    <script src="simulcast.js?v=8"></script><!-- simulcast handling -->
15 14
     <script src="libs/strophe/strophe.jingle.adapter.js?v=4"></script><!-- strophe.jingle bundles -->
16 15
     <script src="libs/strophe/strophe.min.js?v=1"></script>
17 16
     <script src="libs/strophe/strophe.disco.min.js?v=1"></script>
@@ -30,6 +29,7 @@
30 29
     <script src="service/RTC/RTCBrowserType.js?v=1"></script>
31 30
     <script src="service/RTC/StreamEventTypes.js?v=1"></script>
32 31
     <script src="service/RTC/MediaStreamTypes.js?v=1"></script>
32
+    <script src="libs/modules/simulcast.bundle.js?v=1"></script>
33 33
     <script src="libs/modules/connectionquality.bundle.js?v=1"></script>
34 34
     <script src="libs/modules/UI.bundle.js?v=2"></script>
35 35
     <script src="libs/modules/statistics.bundle.js?v=1"></script>

simulcast.js → libs/modules/simulcast.bundle.js View File

@@ -1,235 +1,39 @@
1
-/*jslint plusplus: true */
2
-/*jslint nomen: true*/
3
-
1
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.simulcast=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
4 2
 /**
5 3
  *
6 4
  * @constructor
7 5
  */
8
-function SimulcastUtils() {
9
-    this.logger = new SimulcastLogger("SimulcastUtils", 1);
6
+function SimulcastLogger(name, lvl) {
7
+    this.name = name;
8
+    this.lvl = lvl;
10 9
 }
11 10
 
12
-/**
13
- *
14
- * @type {{}}
15
- * @private
16
- */
17
-SimulcastUtils.prototype._emptyCompoundIndex = {};
18
-
19
-/**
20
- *
21
- * @param lines
22
- * @param videoSources
23
- * @private
24
- */
25
-SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
26
-    var i, inVideo = false, index = -1, howMany = 0;
27
-
28
-    this.logger.info('Replacing video sources...');
29
-
30
-    for (i = 0; i < lines.length; i++) {
31
-        if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
32
-            // Out of video.
33
-            break;
34
-        }
35
-
36
-        if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
37
-            // In video.
38
-            inVideo = true;
39
-        }
40
-
41
-        if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
42
-            || lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
43
-
44
-            if (index === -1) {
45
-                index = i;
46
-            }
47
-
48
-            howMany++;
49
-        }
50
-    }
51
-
52
-    //  efficiency baby ;)
53
-    lines.splice.apply(lines,
54
-        [index, howMany].concat(videoSources));
55
-
56
-};
57
-
58
-SimulcastUtils.prototype.isValidDescription = function (desc)
59
-{
60
-    return desc && desc != null
61
-        && desc.type && desc.type != ''
62
-        && desc.sdp && desc.sdp != '';
63
-};
64
-
65
-SimulcastUtils.prototype._getVideoSources = function (lines) {
66
-    var i, inVideo = false, sb = [];
67
-
68
-    this.logger.info('Getting video sources...');
69
-
70
-    for (i = 0; i < lines.length; i++) {
71
-        if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
72
-            // Out of video.
73
-            break;
74
-        }
75
-
76
-        if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
77
-            // In video.
78
-            inVideo = true;
79
-        }
80
-
81
-        if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
82
-            // In SSRC.
83
-            sb.push(lines[i]);
84
-        }
85
-
86
-        if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
87
-            sb.push(lines[i]);
88
-        }
11
+SimulcastLogger.prototype.log = function (text) {
12
+    if (this.lvl) {
13
+        console.log(text);
89 14
     }
90
-
91
-    return sb;
92 15
 };
93 16
 
94
-SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
95
-    var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
96
-        ssrc_attribute, group, semantics, skip = true;
97
-
98
-    this.logger.info('Parsing media sources...');
99
-
100
-    for (i = 0; i < lines.length; i++) {
101
-        if (lines[i].substring(0, 'm='.length) === 'm=') {
102
-
103
-            type = lines[i]
104
-                .substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
105
-            skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
106
-
107
-            if (!skip) {
108
-                cur_media = {
109
-                    'type': type,
110
-                    'sources': {},
111
-                    'groups': []
112
-                };
113
-
114
-                res.push(cur_media);
115
-            }
116
-
117
-        } else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
118
-
119
-            idx = lines[i].indexOf(' ');
120
-            ssrc = lines[i].substring('a=ssrc:'.length, idx);
121
-            if (cur_media.sources[ssrc] === undefined) {
122
-                cur_ssrc = {'ssrc': ssrc};
123
-                cur_media.sources[ssrc] = cur_ssrc;
124
-            }
125
-
126
-            ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
127
-            cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
128
-
129
-            if (cur_media.base === undefined) {
130
-                cur_media.base = cur_ssrc;
131
-            }
132
-
133
-        } else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
134
-            idx = lines[i].indexOf(' ');
135
-            semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
136
-            ssrcs = lines[i].substr(idx).trim().split(' ');
137
-            group = {
138
-                'semantics': semantics,
139
-                'ssrcs': ssrcs
140
-            };
141
-            cur_media.groups.push(group);
142
-        } else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
143
-            lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
144
-            lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
145
-            lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
146
-
147
-            cur_media.direction = lines[i].substring('a='.length);
148
-        }
17
+SimulcastLogger.prototype.info = function (text) {
18
+    if (this.lvl > 1) {
19
+        console.info(text);
149 20
     }
150
-
151
-    return res;
152 21
 };
153 22
 
154
-/**
155
- * The _indexOfArray() method returns the first a CompoundIndex at which a
156
- * given element can be found in the array, or _emptyCompoundIndex if it is
157
- * not present.
158
- *
159
- * Example:
160
- *
161
- * _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
162
- *
163
- * returns {row: 2, column: 14}
164
- *
165
- * @param needle
166
- * @param haystack
167
- * @param start
168
- * @returns {}
169
- * @private
170
- */
171
-SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
172
-    var length = haystack.length, idx, i;
173
-
174
-    if (!start) {
175
-        start = 0;
176
-    }
177
-
178
-    for (i = start; i < length; i++) {
179
-        idx = haystack[i].indexOf(needle);
180
-        if (idx !== -1) {
181
-            return {row: i, column: idx};
182
-        }
23
+SimulcastLogger.prototype.fine = function (text) {
24
+    if (this.lvl > 2) {
25
+        console.log(text);
183 26
     }
184
-    return this._emptyCompoundIndex;
185 27
 };
186 28
 
187
-SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
188
-    var i;
189
-
190
-    for (i = lines.length - 1; i >= 0; i--) {
191
-        if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
192
-            lines.splice(i, 1);
193
-        }
194
-    }
29
+SimulcastLogger.prototype.error = function (text) {
30
+    console.error(text);
195 31
 };
196 32
 
197
-SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
198
-    var sb = [], ssrc, addedSSRCs = [];
199
-
200
-    this.logger.info('Compiling video sources...');
201
-
202
-    // Add the groups
203
-    if (videoSources.groups && videoSources.groups.length !== 0) {
204
-        videoSources.groups.forEach(function (group) {
205
-            if (group.ssrcs && group.ssrcs.length !== 0) {
206
-                sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
207
-
208
-                // if (group.semantics !== 'SIM') {
209
-                group.ssrcs.forEach(function (ssrc) {
210
-                    addedSSRCs.push(ssrc);
211
-                    sb.splice.apply(sb, [sb.length, 0].concat([
212
-                        ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
213
-                        ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
214
-                });
215
-                //}
216
-            }
217
-        });
218
-    }
219
-
220
-    // Then add any free sources.
221
-    if (videoSources.sources) {
222
-        for (ssrc in videoSources.sources) {
223
-            if (addedSSRCs.indexOf(ssrc) === -1) {
224
-                sb.splice.apply(sb, [sb.length, 0].concat([
225
-                    ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
226
-                    ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
227
-            }
228
-        }
229
-    }
230
-
231
-    return sb;
232
-};
33
+module.exports = SimulcastLogger;
34
+},{}],2:[function(require,module,exports){
35
+var SimulcastLogger = require("./SimulcastLogger");
36
+var SimulcastUtils = require("./SimulcastUtils");
233 37
 
234 38
 function SimulcastReceiver() {
235 39
     this.simulcastUtils = new SimulcastUtils();
@@ -492,23 +296,65 @@ SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
492 296
     return this._remoteMaps.ssrc2Msid[ssrc];
493 297
 };
494 298
 
495
-function SimulcastSender() {
496
-    this.simulcastUtils = new SimulcastUtils();
497
-    this.logger = new SimulcastLogger('SimulcastSender', 1);
498
-}
299
+/**
300
+ * Removes the ssrc-group:SIM from the remote description bacause Chrome
301
+ * either gets confused and thinks this is an FID group or, if an FID group
302
+ * is already present, it fails to set the remote description.
303
+ *
304
+ * @param desc
305
+ * @returns {*}
306
+ */
307
+SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
499 308
 
500
-SimulcastSender.prototype.displayedLocalVideoStream = null;
309
+    if (desc && desc.sdp) {
310
+        var sb = desc.sdp.split('\r\n');
501 311
 
502
-SimulcastSender.prototype._generateGuid = (function () {
503
-    function s4() {
504
-        return Math.floor((1 + Math.random()) * 0x10000)
505
-            .toString(16)
506
-            .substring(1);
507
-    }
312
+        this._updateRemoteMaps(sb);
313
+        this._cacheRemoteVideoSources(sb);
508 314
 
509
-    return function () {
510
-        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
511
-            s4() + '-' + s4() + s4() + s4();
315
+        // NOTE(gp) this needs to be called after updateRemoteMaps because we
316
+        // need the simulcast group in the _updateRemoteMaps() method.
317
+        this.simulcastUtils._removeSimulcastGroup(sb);
318
+
319
+        if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
320
+            // We don't need the goog conference flag if we're not doing
321
+            // simulcast.
322
+            this._ensureGoogConference(sb);
323
+        }
324
+
325
+        desc = new RTCSessionDescription({
326
+            type: desc.type,
327
+            sdp: sb.join('\r\n')
328
+        });
329
+
330
+        this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
331
+    }
332
+
333
+    return desc;
334
+};
335
+
336
+module.exports = SimulcastReceiver;
337
+},{"./SimulcastLogger":1,"./SimulcastUtils":4}],3:[function(require,module,exports){
338
+var SimulcastLogger = require("./SimulcastLogger");
339
+var SimulcastUtils = require("./SimulcastUtils");
340
+
341
+function SimulcastSender() {
342
+    this.simulcastUtils = new SimulcastUtils();
343
+    this.logger = new SimulcastLogger('SimulcastSender', 1);
344
+}
345
+
346
+SimulcastSender.prototype.displayedLocalVideoStream = null;
347
+
348
+SimulcastSender.prototype._generateGuid = (function () {
349
+    function s4() {
350
+        return Math.floor((1 + Math.random()) * 0x10000)
351
+            .toString(16)
352
+            .substring(1);
353
+    }
354
+
355
+    return function () {
356
+        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
357
+            s4() + '-' + s4() + s4() + s4();
512 358
     };
513 359
 }());
514 360
 
@@ -735,43 +581,6 @@ NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
735 581
     return desc;
736 582
 };
737 583
 
738
-/**
739
- * Removes the ssrc-group:SIM from the remote description bacause Chrome
740
- * either gets confused and thinks this is an FID group or, if an FID group
741
- * is already present, it fails to set the remote description.
742
- *
743
- * @param desc
744
- * @returns {*}
745
- */
746
-SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
747
-
748
-    if (desc && desc.sdp) {
749
-        var sb = desc.sdp.split('\r\n');
750
-
751
-        this._updateRemoteMaps(sb);
752
-        this._cacheRemoteVideoSources(sb);
753
-
754
-        // NOTE(gp) this needs to be called after updateRemoteMaps because we
755
-        // need the simulcast group in the _updateRemoteMaps() method.
756
-        this.simulcastUtils._removeSimulcastGroup(sb);
757
-
758
-        if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
759
-            // We don't need the goog conference flag if we're not doing
760
-            // simulcast.
761
-            this._ensureGoogConference(sb);
762
-        }
763
-
764
-        desc = new RTCSessionDescription({
765
-            type: desc.type,
766
-            sdp: sb.join('\r\n')
767
-        });
768
-
769
-        this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
770
-    }
771
-
772
-    return desc;
773
-};
774
-
775 584
 NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
776 585
     // Nothing to do here, native simulcast does that auto-magically.
777 586
 };
@@ -1041,6 +850,256 @@ NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enable
1041 850
 
1042 851
 NoSimulcastSender.prototype.constructor = NoSimulcastSender;
1043 852
 
853
+module.exports = {
854
+    "native": NativeSimulcastSender,
855
+    "no": NoSimulcastSender
856
+}
857
+
858
+},{"./SimulcastLogger":1,"./SimulcastUtils":4}],4:[function(require,module,exports){
859
+var SimulcastLogger = require("./SimulcastLogger");
860
+
861
+/**
862
+ *
863
+ * @constructor
864
+ */
865
+function SimulcastUtils() {
866
+    this.logger = new SimulcastLogger("SimulcastUtils", 1);
867
+}
868
+
869
+/**
870
+ *
871
+ * @type {{}}
872
+ * @private
873
+ */
874
+SimulcastUtils.prototype._emptyCompoundIndex = {};
875
+
876
+/**
877
+ *
878
+ * @param lines
879
+ * @param videoSources
880
+ * @private
881
+ */
882
+SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
883
+    var i, inVideo = false, index = -1, howMany = 0;
884
+
885
+    this.logger.info('Replacing video sources...');
886
+
887
+    for (i = 0; i < lines.length; i++) {
888
+        if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
889
+            // Out of video.
890
+            break;
891
+        }
892
+
893
+        if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
894
+            // In video.
895
+            inVideo = true;
896
+        }
897
+
898
+        if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
899
+            || lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
900
+
901
+            if (index === -1) {
902
+                index = i;
903
+            }
904
+
905
+            howMany++;
906
+        }
907
+    }
908
+
909
+    //  efficiency baby ;)
910
+    lines.splice.apply(lines,
911
+        [index, howMany].concat(videoSources));
912
+
913
+};
914
+
915
+SimulcastUtils.prototype.isValidDescription = function (desc)
916
+{
917
+    return desc && desc != null
918
+        && desc.type && desc.type != ''
919
+        && desc.sdp && desc.sdp != '';
920
+};
921
+
922
+SimulcastUtils.prototype._getVideoSources = function (lines) {
923
+    var i, inVideo = false, sb = [];
924
+
925
+    this.logger.info('Getting video sources...');
926
+
927
+    for (i = 0; i < lines.length; i++) {
928
+        if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
929
+            // Out of video.
930
+            break;
931
+        }
932
+
933
+        if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
934
+            // In video.
935
+            inVideo = true;
936
+        }
937
+
938
+        if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
939
+            // In SSRC.
940
+            sb.push(lines[i]);
941
+        }
942
+
943
+        if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
944
+            sb.push(lines[i]);
945
+        }
946
+    }
947
+
948
+    return sb;
949
+};
950
+
951
+SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
952
+    var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
953
+        ssrc_attribute, group, semantics, skip = true;
954
+
955
+    this.logger.info('Parsing media sources...');
956
+
957
+    for (i = 0; i < lines.length; i++) {
958
+        if (lines[i].substring(0, 'm='.length) === 'm=') {
959
+
960
+            type = lines[i]
961
+                .substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
962
+            skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
963
+
964
+            if (!skip) {
965
+                cur_media = {
966
+                    'type': type,
967
+                    'sources': {},
968
+                    'groups': []
969
+                };
970
+
971
+                res.push(cur_media);
972
+            }
973
+
974
+        } else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
975
+
976
+            idx = lines[i].indexOf(' ');
977
+            ssrc = lines[i].substring('a=ssrc:'.length, idx);
978
+            if (cur_media.sources[ssrc] === undefined) {
979
+                cur_ssrc = {'ssrc': ssrc};
980
+                cur_media.sources[ssrc] = cur_ssrc;
981
+            }
982
+
983
+            ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
984
+            cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
985
+
986
+            if (cur_media.base === undefined) {
987
+                cur_media.base = cur_ssrc;
988
+            }
989
+
990
+        } else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
991
+            idx = lines[i].indexOf(' ');
992
+            semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
993
+            ssrcs = lines[i].substr(idx).trim().split(' ');
994
+            group = {
995
+                'semantics': semantics,
996
+                'ssrcs': ssrcs
997
+            };
998
+            cur_media.groups.push(group);
999
+        } else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
1000
+            lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
1001
+            lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
1002
+            lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
1003
+
1004
+            cur_media.direction = lines[i].substring('a='.length);
1005
+        }
1006
+    }
1007
+
1008
+    return res;
1009
+};
1010
+
1011
+/**
1012
+ * The _indexOfArray() method returns the first a CompoundIndex at which a
1013
+ * given element can be found in the array, or _emptyCompoundIndex if it is
1014
+ * not present.
1015
+ *
1016
+ * Example:
1017
+ *
1018
+ * _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
1019
+ *
1020
+ * returns {row: 2, column: 14}
1021
+ *
1022
+ * @param needle
1023
+ * @param haystack
1024
+ * @param start
1025
+ * @returns {}
1026
+ * @private
1027
+ */
1028
+SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
1029
+    var length = haystack.length, idx, i;
1030
+
1031
+    if (!start) {
1032
+        start = 0;
1033
+    }
1034
+
1035
+    for (i = start; i < length; i++) {
1036
+        idx = haystack[i].indexOf(needle);
1037
+        if (idx !== -1) {
1038
+            return {row: i, column: idx};
1039
+        }
1040
+    }
1041
+    return this._emptyCompoundIndex;
1042
+};
1043
+
1044
+SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
1045
+    var i;
1046
+
1047
+    for (i = lines.length - 1; i >= 0; i--) {
1048
+        if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
1049
+            lines.splice(i, 1);
1050
+        }
1051
+    }
1052
+};
1053
+
1054
+SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
1055
+    var sb = [], ssrc, addedSSRCs = [];
1056
+
1057
+    this.logger.info('Compiling video sources...');
1058
+
1059
+    // Add the groups
1060
+    if (videoSources.groups && videoSources.groups.length !== 0) {
1061
+        videoSources.groups.forEach(function (group) {
1062
+            if (group.ssrcs && group.ssrcs.length !== 0) {
1063
+                sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
1064
+
1065
+                // if (group.semantics !== 'SIM') {
1066
+                group.ssrcs.forEach(function (ssrc) {
1067
+                    addedSSRCs.push(ssrc);
1068
+                    sb.splice.apply(sb, [sb.length, 0].concat([
1069
+                        ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
1070
+                        ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
1071
+                });
1072
+                //}
1073
+            }
1074
+        });
1075
+    }
1076
+
1077
+    // Then add any free sources.
1078
+    if (videoSources.sources) {
1079
+        for (ssrc in videoSources.sources) {
1080
+            if (addedSSRCs.indexOf(ssrc) === -1) {
1081
+                sb.splice.apply(sb, [sb.length, 0].concat([
1082
+                    ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
1083
+                    ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
1084
+            }
1085
+        }
1086
+    }
1087
+
1088
+    return sb;
1089
+};
1090
+
1091
+module.exports = SimulcastUtils;
1092
+},{"./SimulcastLogger":1}],5:[function(require,module,exports){
1093
+/*jslint plusplus: true */
1094
+/*jslint nomen: true*/
1095
+
1096
+var SimulcastSender = require("./SimulcastSender");
1097
+var NoSimulcastSender = SimulcastSender["no"];
1098
+var NativeSimulcastSender = SimulcastSender["native"];
1099
+var SimulcastReceiver = require("./SimulcastReceiver");
1100
+var SimulcastUtils = require("./SimulcastUtils");
1101
+
1102
+
1044 1103
 /**
1045 1104
  *
1046 1105
  * @constructor
@@ -1213,39 +1272,6 @@ SimulcastManager.prototype.resetSender = function() {
1213 1272
     }
1214 1273
 };
1215 1274
 
1216
-/**
1217
- *
1218
- * @constructor
1219
- */
1220
-function SimulcastLogger(name, lvl) {
1221
-    this.name = name;
1222
-    this.lvl = lvl;
1223
-}
1224
-
1225
-SimulcastLogger.prototype.log = function (text) {
1226
-    if (this.lvl) {
1227
-        console.log(text);
1228
-    }
1229
-};
1230
-
1231
-SimulcastLogger.prototype.info = function (text) {
1232
-    if (this.lvl > 1) {
1233
-        console.info(text);
1234
-    }
1235
-};
1236
-
1237
-SimulcastLogger.prototype.fine = function (text) {
1238
-    if (this.lvl > 2) {
1239
-        console.log(text);
1240
-    }
1241
-};
1242
-
1243
-SimulcastLogger.prototype.error = function (text) {
1244
-    console.error(text);
1245
-};
1246
-
1247
-var simulcast = new SimulcastManager();
1248
-
1249 1275
 $(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
1250 1276
     endpointSimulcastLayers.forEach(function (esl) {
1251 1277
         var ssrc = esl.simulcastLayer.primarySSRC;
@@ -1262,3 +1288,10 @@ $(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
1262 1288
     var ssrc = simulcastLayer.primarySSRC;
1263 1289
     simulcast._setLocalVideoStreamEnabled(ssrc, false);
1264 1290
 });
1291
+
1292
+
1293
+var simulcast = new SimulcastManager();
1294
+
1295
+module.exports = simulcast;
1296
+},{"./SimulcastReceiver":2,"./SimulcastSender":3,"./SimulcastUtils":4}]},{},[5])(5)
1297
+});

+ 32
- 0
modules/simulcast/SimulcastLogger.js View File

@@ -0,0 +1,32 @@
1
+/**
2
+ *
3
+ * @constructor
4
+ */
5
+function SimulcastLogger(name, lvl) {
6
+    this.name = name;
7
+    this.lvl = lvl;
8
+}
9
+
10
+SimulcastLogger.prototype.log = function (text) {
11
+    if (this.lvl) {
12
+        console.log(text);
13
+    }
14
+};
15
+
16
+SimulcastLogger.prototype.info = function (text) {
17
+    if (this.lvl > 1) {
18
+        console.info(text);
19
+    }
20
+};
21
+
22
+SimulcastLogger.prototype.fine = function (text) {
23
+    if (this.lvl > 2) {
24
+        console.log(text);
25
+    }
26
+};
27
+
28
+SimulcastLogger.prototype.error = function (text) {
29
+    console.error(text);
30
+};
31
+
32
+module.exports = SimulcastLogger;

+ 302
- 0
modules/simulcast/SimulcastReceiver.js View File

@@ -0,0 +1,302 @@
1
+var SimulcastLogger = require("./SimulcastLogger");
2
+var SimulcastUtils = require("./SimulcastUtils");
3
+
4
+function SimulcastReceiver() {
5
+    this.simulcastUtils = new SimulcastUtils();
6
+    this.logger = new SimulcastLogger('SimulcastReceiver', 1);
7
+}
8
+
9
+SimulcastReceiver.prototype._remoteVideoSourceCache = '';
10
+SimulcastReceiver.prototype._remoteMaps = {
11
+    msid2Quality: {},
12
+    ssrc2Msid: {},
13
+    msid2ssrc: {},
14
+    receivingVideoStreams: {}
15
+};
16
+
17
+SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
18
+    this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
19
+};
20
+
21
+SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
22
+    this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
23
+};
24
+
25
+SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
26
+    var sb;
27
+
28
+    this.logger.info('Ensuring x-google-conference flag...')
29
+
30
+    if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
31
+        // TODO(gp) do that for the audio as well as suggested by fippo.
32
+        // Add the google conference flag
33
+        sb = this.simulcastUtils._getVideoSources(lines);
34
+        sb = ['a=x-google-flag:conference'].concat(sb);
35
+        this.simulcastUtils._replaceVideoSources(lines, sb);
36
+    }
37
+};
38
+
39
+SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
40
+    this._restoreRemoteVideoSources(sb);
41
+};
42
+
43
+/**
44
+ * Restores the simulcast groups of the remote description. In
45
+ * transformRemoteDescription we remove those in order for the set remote
46
+ * description to succeed. The focus needs the signal the groups to new
47
+ * participants.
48
+ *
49
+ * @param desc
50
+ * @returns {*}
51
+ */
52
+SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
53
+    var sb;
54
+
55
+    if (!this.simulcastUtils.isValidDescription(desc)) {
56
+        return desc;
57
+    }
58
+
59
+    if (config.enableSimulcast) {
60
+        sb = desc.sdp.split('\r\n');
61
+
62
+        this._restoreSimulcastGroups(sb);
63
+
64
+        desc = new RTCSessionDescription({
65
+            type: desc.type,
66
+            sdp: sb.join('\r\n')
67
+        });
68
+    }
69
+
70
+    return desc;
71
+};
72
+
73
+SimulcastUtils.prototype._ensureOrder = function (lines) {
74
+    var videoSources, sb;
75
+
76
+    videoSources = this.parseMedia(lines, ['video'])[0];
77
+    sb = this._compileVideoSources(videoSources);
78
+
79
+    this._replaceVideoSources(lines, sb);
80
+};
81
+
82
+SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
83
+    var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
84
+        videoSource, quality;
85
+
86
+    // (re) initialize the remote maps.
87
+    this._remoteMaps.msid2Quality = {};
88
+    this._remoteMaps.ssrc2Msid = {};
89
+    this._remoteMaps.msid2ssrc = {};
90
+
91
+    var self = this;
92
+    if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
93
+        remoteVideoSources.groups.forEach(function (group) {
94
+            if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
95
+                quality = 0;
96
+                group.ssrcs.forEach(function (ssrc) {
97
+                    videoSource = remoteVideoSources.sources[ssrc];
98
+                    self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
99
+                    self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
100
+                    self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
101
+                });
102
+            }
103
+        });
104
+    }
105
+};
106
+
107
+SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
108
+    this._remoteMaps.receivingVideoStreams[resource] = ssrc;
109
+};
110
+
111
+/**
112
+ * Returns a stream with single video track, the one currently being
113
+ * received by this endpoint.
114
+ *
115
+ * @param stream the remote simulcast stream.
116
+ * @returns {webkitMediaStream}
117
+ */
118
+SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
119
+    var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
120
+
121
+    var self = this;
122
+    if (config.enableSimulcast) {
123
+
124
+        stream.getVideoTracks().some(function (track) {
125
+            return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
126
+                var ssrc = self._remoteMaps.receivingVideoStreams[resource];
127
+                var msid = self._remoteMaps.ssrc2Msid[ssrc];
128
+                if (msid == [stream.id, track.id].join(' ')) {
129
+                    electedTrack = track;
130
+                    return true;
131
+                }
132
+            });
133
+        });
134
+
135
+        if (!electedTrack) {
136
+            // we don't have an elected track, choose by initial quality.
137
+            tracks = stream.getVideoTracks();
138
+            for (i = 0; i < tracks.length; i++) {
139
+                msid = [stream.id, tracks[i].id].join(' ');
140
+                if (this._remoteMaps.msid2Quality[msid] === quality) {
141
+                    electedTrack = tracks[i];
142
+                    break;
143
+                }
144
+            }
145
+
146
+            // TODO(gp) if the initialQuality could not be satisfied, lower
147
+            // the requirement and try again.
148
+        }
149
+    }
150
+
151
+    return (electedTrack)
152
+        ? new webkitMediaStream([electedTrack])
153
+        : stream;
154
+};
155
+
156
+SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
157
+    var resource = Strophe.getResourceFromJid(jid);
158
+    var ssrc = this._remoteMaps.receivingVideoStreams[resource];
159
+
160
+    // If we haven't receiving a "changed" event yet, then we must be receiving
161
+    // low quality (that the sender always streams).
162
+    if (!ssrc && connection.jingle) {
163
+        var session;
164
+        var i, j, k;
165
+
166
+        var keys = Object.keys(connection.jingle.sessions);
167
+        for (i = 0; i < keys.length; i++) {
168
+            var sid = keys[i];
169
+
170
+            if (ssrc) {
171
+                // stream found, stop.
172
+                break;
173
+            }
174
+
175
+            session = connection.jingle.sessions[sid];
176
+            if (session.remoteStreams) {
177
+                for (j = 0; j < session.remoteStreams.length; j++) {
178
+                    var remoteStream = session.remoteStreams[j];
179
+
180
+                    if (ssrc) {
181
+                        // stream found, stop.
182
+                        break;
183
+                    }
184
+                    var tracks = remoteStream.getVideoTracks();
185
+                    if (tracks) {
186
+                        for (k = 0; k < tracks.length; k++) {
187
+                            var track = tracks[k];
188
+                            var msid = [remoteStream.id, track.id].join(' ');
189
+                            var _ssrc = this._remoteMaps.msid2ssrc[msid];
190
+                            var _jid = ssrc2jid[_ssrc];
191
+                            var quality = this._remoteMaps.msid2Quality[msid];
192
+                            if (jid == _jid && quality == 0) {
193
+                                ssrc = _ssrc;
194
+                                // stream found, stop.
195
+                                break;
196
+                            }
197
+                        }
198
+                    }
199
+                }
200
+            }
201
+        }
202
+    }
203
+
204
+    return ssrc;
205
+};
206
+
207
+SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
208
+{
209
+    var session, electedStream;
210
+    var i, j, k;
211
+    if (connection.jingle) {
212
+        var keys = Object.keys(connection.jingle.sessions);
213
+        for (i = 0; i < keys.length; i++) {
214
+            var sid = keys[i];
215
+
216
+            if (electedStream) {
217
+                // stream found, stop.
218
+                break;
219
+            }
220
+
221
+            session = connection.jingle.sessions[sid];
222
+            if (session.remoteStreams) {
223
+                for (j = 0; j < session.remoteStreams.length; j++) {
224
+                    var remoteStream = session.remoteStreams[j];
225
+
226
+                    if (electedStream) {
227
+                        // stream found, stop.
228
+                        break;
229
+                    }
230
+                    var tracks = remoteStream.getVideoTracks();
231
+                    if (tracks) {
232
+                        for (k = 0; k < tracks.length; k++) {
233
+                            var track = tracks[k];
234
+                            var msid = [remoteStream.id, track.id].join(' ');
235
+                            var tmp = this._remoteMaps.msid2ssrc[msid];
236
+                            if (tmp == ssrc) {
237
+                                electedStream = new webkitMediaStream([track]);
238
+                                // stream found, stop.
239
+                                break;
240
+                            }
241
+                        }
242
+                    }
243
+                }
244
+            }
245
+        }
246
+    }
247
+
248
+    return {
249
+        session: session,
250
+        stream: electedStream
251
+    };
252
+};
253
+
254
+/**
255
+ * Gets the fully qualified msid (stream.id + track.id) associated to the
256
+ * SSRC.
257
+ *
258
+ * @param ssrc
259
+ * @returns {*}
260
+ */
261
+SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
262
+    return this._remoteMaps.ssrc2Msid[ssrc];
263
+};
264
+
265
+/**
266
+ * Removes the ssrc-group:SIM from the remote description bacause Chrome
267
+ * either gets confused and thinks this is an FID group or, if an FID group
268
+ * is already present, it fails to set the remote description.
269
+ *
270
+ * @param desc
271
+ * @returns {*}
272
+ */
273
+SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
274
+
275
+    if (desc && desc.sdp) {
276
+        var sb = desc.sdp.split('\r\n');
277
+
278
+        this._updateRemoteMaps(sb);
279
+        this._cacheRemoteVideoSources(sb);
280
+
281
+        // NOTE(gp) this needs to be called after updateRemoteMaps because we
282
+        // need the simulcast group in the _updateRemoteMaps() method.
283
+        this.simulcastUtils._removeSimulcastGroup(sb);
284
+
285
+        if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
286
+            // We don't need the goog conference flag if we're not doing
287
+            // simulcast.
288
+            this._ensureGoogConference(sb);
289
+        }
290
+
291
+        desc = new RTCSessionDescription({
292
+            type: desc.type,
293
+            sdp: sb.join('\r\n')
294
+        });
295
+
296
+        this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
297
+    }
298
+
299
+    return desc;
300
+};
301
+
302
+module.exports = SimulcastReceiver;

+ 519
- 0
modules/simulcast/SimulcastSender.js View File

@@ -0,0 +1,519 @@
1
+var SimulcastLogger = require("./SimulcastLogger");
2
+var SimulcastUtils = require("./SimulcastUtils");
3
+
4
+function SimulcastSender() {
5
+    this.simulcastUtils = new SimulcastUtils();
6
+    this.logger = new SimulcastLogger('SimulcastSender', 1);
7
+}
8
+
9
+SimulcastSender.prototype.displayedLocalVideoStream = null;
10
+
11
+SimulcastSender.prototype._generateGuid = (function () {
12
+    function s4() {
13
+        return Math.floor((1 + Math.random()) * 0x10000)
14
+            .toString(16)
15
+            .substring(1);
16
+    }
17
+
18
+    return function () {
19
+        return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
20
+            s4() + '-' + s4() + s4() + s4();
21
+    };
22
+}());
23
+
24
+// Returns a random integer between min (included) and max (excluded)
25
+// Using Math.round() gives a non-uniform distribution!
26
+SimulcastSender.prototype._generateRandomSSRC = function () {
27
+    var min = 0, max = 0xffffffff;
28
+    return Math.floor(Math.random() * (max - min)) + min;
29
+};
30
+
31
+SimulcastSender.prototype.getLocalVideoStream = function () {
32
+    return (this.displayedLocalVideoStream != null)
33
+        ? this.displayedLocalVideoStream
34
+        // in case we have no simulcast at all, i.e. we didn't perform the GUM
35
+        : connection.jingle.localVideo;
36
+};
37
+
38
+function NativeSimulcastSender() {
39
+    SimulcastSender.call(this); // call the super constructor.
40
+}
41
+
42
+NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
43
+
44
+NativeSimulcastSender.prototype._localExplosionMap = {};
45
+NativeSimulcastSender.prototype._isUsingScreenStream = false;
46
+NativeSimulcastSender.prototype._localVideoSourceCache = '';
47
+
48
+NativeSimulcastSender.prototype.reset = function () {
49
+    this._localExplosionMap = {};
50
+    this._isUsingScreenStream = isUsingScreenStream;
51
+};
52
+
53
+NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
54
+    this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
55
+};
56
+
57
+NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
58
+    this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
59
+};
60
+
61
+NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
62
+    var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
63
+
64
+    this.logger.info('Appending simulcast group...');
65
+
66
+    // Get the primary SSRC information.
67
+    videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
68
+
69
+    // Start building the SIM SSRC group.
70
+    ssrcGroup = ['a=ssrc-group:SIM'];
71
+
72
+    // The video source buffer.
73
+    sb = [];
74
+
75
+    // Create the simulcast sub-streams.
76
+    for (i = 0; i < numOfSubs; i++) {
77
+        // TODO(gp) prevent SSRC collision.
78
+        simSSRC = this._generateRandomSSRC();
79
+        ssrcGroup.push(simSSRC);
80
+
81
+        sb.splice.apply(sb, [sb.length, 0].concat(
82
+            [["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
83
+                ["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
84
+        ));
85
+
86
+        this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));
87
+
88
+    }
89
+
90
+    // Add the group sim layers.
91
+    sb.splice(0, 0, ssrcGroup.join(' '))
92
+
93
+    this.simulcastUtils._replaceVideoSources(lines, sb);
94
+};
95
+
96
+// Does the actual patching.
97
+NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
98
+
99
+    this.logger.info('Ensuring simulcast group...');
100
+
101
+    if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
102
+        this._appendSimulcastGroup(lines);
103
+        this._cacheLocalVideoSources(lines);
104
+    } else {
105
+        // verify that the ssrcs participating in the SIM group are present
106
+        // in the SDP (needed for presence).
107
+        this._restoreLocalVideoSources(lines);
108
+    }
109
+};
110
+
111
+/**
112
+ * Produces a single stream with multiple tracks for local video sources.
113
+ *
114
+ * @param lines
115
+ * @private
116
+ */
117
+NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
118
+    var sb, msid, sid, tid, videoSources, self;
119
+
120
+    this.logger.info('Exploding local video sources...');
121
+
122
+    videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
123
+
124
+    self = this;
125
+    if (videoSources.groups && videoSources.groups.length !== 0) {
126
+        videoSources.groups.forEach(function (group) {
127
+            if (group.semantics === 'SIM') {
128
+                group.ssrcs.forEach(function (ssrc) {
129
+
130
+                    // Get the msid for this ssrc..
131
+                    if (self._localExplosionMap[ssrc]) {
132
+                        // .. either from the explosion map..
133
+                        msid = self._localExplosionMap[ssrc];
134
+                    } else {
135
+                        // .. or generate a new one (msid).
136
+                        sid = videoSources.sources[ssrc].msid
137
+                            .substring(0, videoSources.sources[ssrc].msid.indexOf(' '));
138
+
139
+                        tid = self._generateGuid();
140
+                        msid = [sid, tid].join(' ');
141
+                        self._localExplosionMap[ssrc] = msid;
142
+                    }
143
+
144
+                    // Assign it to the source object.
145
+                    videoSources.sources[ssrc].msid = msid;
146
+
147
+                    // TODO(gp) Change the msid of associated sources.
148
+                });
149
+            }
150
+        });
151
+    }
152
+
153
+    sb = this.simulcastUtils._compileVideoSources(videoSources);
154
+
155
+    this.simulcastUtils._replaceVideoSources(lines, sb);
156
+};
157
+
158
+/**
159
+ * GUM for simulcast.
160
+ *
161
+ * @param constraints
162
+ * @param success
163
+ * @param err
164
+ */
165
+NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
166
+
167
+    // There's nothing special to do for native simulcast, so just do a normal GUM.
168
+    navigator.webkitGetUserMedia(constraints, function (hqStream) {
169
+        success(hqStream);
170
+    }, err);
171
+};
172
+
173
+/**
174
+ * Prepares the local description for public usage (i.e. to be signaled
175
+ * through Jingle to the focus).
176
+ *
177
+ * @param desc
178
+ * @returns {RTCSessionDescription}
179
+ */
180
+NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
181
+    var sb;
182
+
183
+    if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
184
+        return desc;
185
+    }
186
+
187
+
188
+    sb = desc.sdp.split('\r\n');
189
+
190
+    this._explodeSimulcastSenderSources(sb);
191
+
192
+    desc = new RTCSessionDescription({
193
+        type: desc.type,
194
+        sdp: sb.join('\r\n')
195
+    });
196
+
197
+    this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));
198
+
199
+    return desc;
200
+};
201
+
202
+/**
203
+ * Ensures that the simulcast group is present in the answer, _if_ native
204
+ * simulcast is enabled,
205
+ *
206
+ * @param desc
207
+ * @returns {*}
208
+ */
209
+NativeSimulcastSender.prototype.transformAnswer = function (desc) {
210
+
211
+    if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
212
+        return desc;
213
+    }
214
+
215
+    var sb = desc.sdp.split('\r\n');
216
+
217
+    // Even if we have enabled native simulcasting previously
218
+    // (with a call to SLD with an appropriate SDP, for example),
219
+    // createAnswer seems to consistently generate incomplete SDP
220
+    // with missing SSRCS.
221
+    //
222
+    // So, subsequent calls to SLD will have missing SSRCS and presence
223
+    // won't have the complete list of SRCs.
224
+    this._ensureSimulcastGroup(sb);
225
+
226
+    desc = new RTCSessionDescription({
227
+        type: desc.type,
228
+        sdp: sb.join('\r\n')
229
+    });
230
+
231
+    this.logger.fine(['Transformed answer', desc.sdp].join(' '));
232
+
233
+    return desc;
234
+};
235
+
236
+
237
+/**
238
+ *
239
+ *
240
+ * @param desc
241
+ * @returns {*}
242
+ */
243
+NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
244
+    return desc;
245
+};
246
+
247
+NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
248
+    // Nothing to do here, native simulcast does that auto-magically.
249
+};
250
+
251
+NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;
252
+
253
+function SimpleSimulcastSender() {
254
+    SimulcastSender.call(this);
255
+}
256
+
257
+SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
258
+
259
+SimpleSimulcastSender.prototype.localStream = null;
260
+SimpleSimulcastSender.prototype._localMaps = {
261
+    msids: [],
262
+    msid2ssrc: {}
263
+};
264
+
265
+/**
266
+ * Groups local video sources together in the ssrc-group:SIM group.
267
+ *
268
+ * @param lines
269
+ * @private
270
+ */
271
+SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
272
+    var sb, videoSources, ssrcs = [], ssrc;
273
+
274
+    this.logger.info('Grouping local video sources...');
275
+
276
+    videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
277
+
278
+    for (ssrc in videoSources.sources) {
279
+        // jitsi-meet destroys/creates streams at various places causing
280
+        // the original local stream ids to change. The only thing that
281
+        // remains unchanged is the trackid.
282
+        this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
283
+    }
284
+
285
+    var self = this;
286
+    // TODO(gp) add only "free" sources.
287
+    this._localMaps.msids.forEach(function (msid) {
288
+        ssrcs.push(self._localMaps.msid2ssrc[msid]);
289
+    });
290
+
291
+    if (!videoSources.groups) {
292
+        videoSources.groups = [];
293
+    }
294
+
295
+    videoSources.groups.push({
296
+        'semantics': 'SIM',
297
+        'ssrcs': ssrcs
298
+    });
299
+
300
+    sb = this.simulcastUtils._compileVideoSources(videoSources);
301
+
302
+    this.simulcastUtils._replaceVideoSources(lines, sb);
303
+};
304
+
305
+/**
306
+ * GUM for simulcast.
307
+ *
308
+ * @param constraints
309
+ * @param success
310
+ * @param err
311
+ */
312
+SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
313
+
314
+    // TODO(gp) what if we request a resolution not supported by the hardware?
315
+    // TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
316
+    var lqConstraints = {
317
+        audio: false,
318
+        video: {
319
+            mandatory: {
320
+                maxWidth: 320,
321
+                maxHeight: 180,
322
+                maxFrameRate: 15
323
+            }
324
+        }
325
+    };
326
+
327
+    this.logger.info('HQ constraints: ', constraints);
328
+    this.logger.info('LQ constraints: ', lqConstraints);
329
+
330
+
331
+    // NOTE(gp) if we request the lq stream first webkitGetUserMedia
332
+    // fails randomly. Tested with Chrome 37. As fippo suggested, the
333
+    // reason appears to be that Chrome only acquires the cam once and
334
+    // then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
335
+
336
+    var self = this;
337
+    navigator.webkitGetUserMedia(constraints, function (hqStream) {
338
+
339
+        self.localStream = hqStream;
340
+
341
+        // reset local maps.
342
+        self._localMaps.msids = [];
343
+        self._localMaps.msid2ssrc = {};
344
+
345
+        // add hq trackid to local map
346
+        self._localMaps.msids.push(hqStream.getVideoTracks()[0].id);
347
+
348
+        navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
349
+
350
+            self.displayedLocalVideoStream = lqStream;
351
+
352
+            // NOTE(gp) The specification says Array.forEach() will visit
353
+            // the array elements in numeric order, and that it doesn't
354
+            // visit elements that don't exist.
355
+
356
+            // add lq trackid to local map
357
+            self._localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
358
+
359
+            self.localStream.addTrack(lqStream.getVideoTracks()[0]);
360
+            success(self.localStream);
361
+        }, err);
362
+    }, err);
363
+};
364
+
365
+/**
366
+ * Prepares the local description for public usage (i.e. to be signaled
367
+ * through Jingle to the focus).
368
+ *
369
+ * @param desc
370
+ * @returns {RTCSessionDescription}
371
+ */
372
+SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
373
+    var sb;
374
+
375
+    if (!this.simulcastUtils.isValidDescription(desc)) {
376
+        return desc;
377
+    }
378
+
379
+    sb = desc.sdp.split('\r\n');
380
+
381
+    this._groupLocalVideoSources(sb);
382
+
383
+    desc = new RTCSessionDescription({
384
+        type: desc.type,
385
+        sdp: sb.join('\r\n')
386
+    });
387
+
388
+    this.logger.fine('Grouped local video sources');
389
+    this.logger.fine(desc.sdp);
390
+
391
+    return desc;
392
+};
393
+
394
+/**
395
+ * Ensures that the simulcast group is present in the answer, _if_ native
396
+ * simulcast is enabled,
397
+ *
398
+ * @param desc
399
+ * @returns {*}
400
+ */
401
+SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
402
+    return desc;
403
+};
404
+
405
+
406
+/**
407
+ *
408
+ *
409
+ * @param desc
410
+ * @returns {*}
411
+ */
412
+SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {
413
+
414
+    var sb = desc.sdp.split('\r\n');
415
+
416
+    this.simulcastUtils._removeSimulcastGroup(sb);
417
+
418
+    desc = new RTCSessionDescription({
419
+        type: desc.type,
420
+        sdp: sb.join('\r\n')
421
+    });
422
+
423
+    this.logger.fine('Transformed local description');
424
+    this.logger.fine(desc.sdp);
425
+
426
+    return desc;
427
+};
428
+
429
+SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
430
+    var trackid;
431
+
432
+    var self = this;
433
+    this.logger.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
434
+    if (Object.keys(this._localMaps.msid2ssrc).some(function (tid) {
435
+        // Search for the track id that corresponds to the ssrc
436
+        if (self._localMaps.msid2ssrc[tid] == ssrc) {
437
+            trackid = tid;
438
+            return true;
439
+        }
440
+    }) && self.localStream.getVideoTracks().some(function (track) {
441
+        // Start/stop the track that corresponds to the track id
442
+        if (track.id === trackid) {
443
+            track.enabled = enabled;
444
+            return true;
445
+        }
446
+    })) {
447
+        this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
448
+        $(document).trigger(enabled
449
+            ? 'simulcastlayerstarted'
450
+            : 'simulcastlayerstopped');
451
+    } else {
452
+        this.logger.error("I don't have a local stream with SSRC " + ssrc);
453
+    }
454
+};
455
+
456
+SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;
457
+
458
+function NoSimulcastSender() {
459
+    SimulcastSender.call(this);
460
+}
461
+
462
+NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
463
+
464
+/**
465
+ * GUM for simulcast.
466
+ *
467
+ * @param constraints
468
+ * @param success
469
+ * @param err
470
+ */
471
+NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
472
+    navigator.webkitGetUserMedia(constraints, function (hqStream) {
473
+        success(hqStream);
474
+    }, err);
475
+};
476
+
477
+/**
478
+ * Prepares the local description for public usage (i.e. to be signaled
479
+ * through Jingle to the focus).
480
+ *
481
+ * @param desc
482
+ * @returns {RTCSessionDescription}
483
+ */
484
+NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
485
+    return desc;
486
+};
487
+
488
+/**
489
+ * Ensures that the simulcast group is present in the answer, _if_ native
490
+ * simulcast is enabled,
491
+ *
492
+ * @param desc
493
+ * @returns {*}
494
+ */
495
+NoSimulcastSender.prototype.transformAnswer = function (desc) {
496
+    return desc;
497
+};
498
+
499
+
500
+/**
501
+ *
502
+ *
503
+ * @param desc
504
+ * @returns {*}
505
+ */
506
+NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
507
+    return desc;
508
+};
509
+
510
+NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
511
+
512
+};
513
+
514
+NoSimulcastSender.prototype.constructor = NoSimulcastSender;
515
+
516
+module.exports = {
517
+    "native": NativeSimulcastSender,
518
+    "no": NoSimulcastSender
519
+}

+ 233
- 0
modules/simulcast/SimulcastUtils.js View File

@@ -0,0 +1,233 @@
1
+var SimulcastLogger = require("./SimulcastLogger");
2
+
3
+/**
4
+ *
5
+ * @constructor
6
+ */
7
+function SimulcastUtils() {
8
+    this.logger = new SimulcastLogger("SimulcastUtils", 1);
9
+}
10
+
11
+/**
12
+ *
13
+ * @type {{}}
14
+ * @private
15
+ */
16
+SimulcastUtils.prototype._emptyCompoundIndex = {};
17
+
18
+/**
19
+ *
20
+ * @param lines
21
+ * @param videoSources
22
+ * @private
23
+ */
24
+SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
25
+    var i, inVideo = false, index = -1, howMany = 0;
26
+
27
+    this.logger.info('Replacing video sources...');
28
+
29
+    for (i = 0; i < lines.length; i++) {
30
+        if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
31
+            // Out of video.
32
+            break;
33
+        }
34
+
35
+        if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
36
+            // In video.
37
+            inVideo = true;
38
+        }
39
+
40
+        if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
41
+            || lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
42
+
43
+            if (index === -1) {
44
+                index = i;
45
+            }
46
+
47
+            howMany++;
48
+        }
49
+    }
50
+
51
+    //  efficiency baby ;)
52
+    lines.splice.apply(lines,
53
+        [index, howMany].concat(videoSources));
54
+
55
+};
56
+
57
+SimulcastUtils.prototype.isValidDescription = function (desc)
58
+{
59
+    return desc && desc != null
60
+        && desc.type && desc.type != ''
61
+        && desc.sdp && desc.sdp != '';
62
+};
63
+
64
+SimulcastUtils.prototype._getVideoSources = function (lines) {
65
+    var i, inVideo = false, sb = [];
66
+
67
+    this.logger.info('Getting video sources...');
68
+
69
+    for (i = 0; i < lines.length; i++) {
70
+        if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
71
+            // Out of video.
72
+            break;
73
+        }
74
+
75
+        if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
76
+            // In video.
77
+            inVideo = true;
78
+        }
79
+
80
+        if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
81
+            // In SSRC.
82
+            sb.push(lines[i]);
83
+        }
84
+
85
+        if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
86
+            sb.push(lines[i]);
87
+        }
88
+    }
89
+
90
+    return sb;
91
+};
92
+
93
+SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
94
+    var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
95
+        ssrc_attribute, group, semantics, skip = true;
96
+
97
+    this.logger.info('Parsing media sources...');
98
+
99
+    for (i = 0; i < lines.length; i++) {
100
+        if (lines[i].substring(0, 'm='.length) === 'm=') {
101
+
102
+            type = lines[i]
103
+                .substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
104
+            skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
105
+
106
+            if (!skip) {
107
+                cur_media = {
108
+                    'type': type,
109
+                    'sources': {},
110
+                    'groups': []
111
+                };
112
+
113
+                res.push(cur_media);
114
+            }
115
+
116
+        } else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
117
+
118
+            idx = lines[i].indexOf(' ');
119
+            ssrc = lines[i].substring('a=ssrc:'.length, idx);
120
+            if (cur_media.sources[ssrc] === undefined) {
121
+                cur_ssrc = {'ssrc': ssrc};
122
+                cur_media.sources[ssrc] = cur_ssrc;
123
+            }
124
+
125
+            ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
126
+            cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
127
+
128
+            if (cur_media.base === undefined) {
129
+                cur_media.base = cur_ssrc;
130
+            }
131
+
132
+        } else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
133
+            idx = lines[i].indexOf(' ');
134
+            semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
135
+            ssrcs = lines[i].substr(idx).trim().split(' ');
136
+            group = {
137
+                'semantics': semantics,
138
+                'ssrcs': ssrcs
139
+            };
140
+            cur_media.groups.push(group);
141
+        } else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
142
+            lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
143
+            lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
144
+            lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
145
+
146
+            cur_media.direction = lines[i].substring('a='.length);
147
+        }
148
+    }
149
+
150
+    return res;
151
+};
152
+
153
+/**
154
+ * The _indexOfArray() method returns the first a CompoundIndex at which a
155
+ * given element can be found in the array, or _emptyCompoundIndex if it is
156
+ * not present.
157
+ *
158
+ * Example:
159
+ *
160
+ * _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
161
+ *
162
+ * returns {row: 2, column: 14}
163
+ *
164
+ * @param needle
165
+ * @param haystack
166
+ * @param start
167
+ * @returns {}
168
+ * @private
169
+ */
170
+SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
171
+    var length = haystack.length, idx, i;
172
+
173
+    if (!start) {
174
+        start = 0;
175
+    }
176
+
177
+    for (i = start; i < length; i++) {
178
+        idx = haystack[i].indexOf(needle);
179
+        if (idx !== -1) {
180
+            return {row: i, column: idx};
181
+        }
182
+    }
183
+    return this._emptyCompoundIndex;
184
+};
185
+
186
+SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
187
+    var i;
188
+
189
+    for (i = lines.length - 1; i >= 0; i--) {
190
+        if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
191
+            lines.splice(i, 1);
192
+        }
193
+    }
194
+};
195
+
196
+SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
197
+    var sb = [], ssrc, addedSSRCs = [];
198
+
199
+    this.logger.info('Compiling video sources...');
200
+
201
+    // Add the groups
202
+    if (videoSources.groups && videoSources.groups.length !== 0) {
203
+        videoSources.groups.forEach(function (group) {
204
+            if (group.ssrcs && group.ssrcs.length !== 0) {
205
+                sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
206
+
207
+                // if (group.semantics !== 'SIM') {
208
+                group.ssrcs.forEach(function (ssrc) {
209
+                    addedSSRCs.push(ssrc);
210
+                    sb.splice.apply(sb, [sb.length, 0].concat([
211
+                        ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
212
+                        ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
213
+                });
214
+                //}
215
+            }
216
+        });
217
+    }
218
+
219
+    // Then add any free sources.
220
+    if (videoSources.sources) {
221
+        for (ssrc in videoSources.sources) {
222
+            if (addedSSRCs.indexOf(ssrc) === -1) {
223
+                sb.splice.apply(sb, [sb.length, 0].concat([
224
+                    ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
225
+                    ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
226
+            }
227
+        }
228
+    }
229
+
230
+    return sb;
231
+};
232
+
233
+module.exports = SimulcastUtils;

+ 203
- 0
modules/simulcast/simulcast.js View File

@@ -0,0 +1,203 @@
1
+/*jslint plusplus: true */
2
+/*jslint nomen: true*/
3
+
4
+var SimulcastSender = require("./SimulcastSender");
5
+var NoSimulcastSender = SimulcastSender["no"];
6
+var NativeSimulcastSender = SimulcastSender["native"];
7
+var SimulcastReceiver = require("./SimulcastReceiver");
8
+var SimulcastUtils = require("./SimulcastUtils");
9
+
10
+
11
+/**
12
+ *
13
+ * @constructor
14
+ */
15
+function SimulcastManager() {
16
+
17
+    // Create the simulcast utilities.
18
+    this.simulcastUtils = new SimulcastUtils();
19
+
20
+    // Create remote simulcast.
21
+    this.simulcastReceiver = new SimulcastReceiver();
22
+
23
+    // Initialize local simulcast.
24
+
25
+    // TODO(gp) move into SimulcastManager.prototype.getUserMedia and take into
26
+    // account constraints.
27
+    if (!config.enableSimulcast) {
28
+        this.simulcastSender = new NoSimulcastSender();
29
+    } else {
30
+
31
+        var isChromium = window.chrome,
32
+            vendorName = window.navigator.vendor;
33
+        if(isChromium !== null && isChromium !== undefined
34
+            /* skip opera */
35
+            && vendorName === "Google Inc."
36
+            /* skip Chromium as suggested by fippo */
37
+            && !window.navigator.appVersion.match(/Chromium\//) ) {
38
+            var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
39
+            if (ver > 37) {
40
+                this.simulcastSender = new NativeSimulcastSender();
41
+            } else {
42
+                this.simulcastSender = new NoSimulcastSender();
43
+            }
44
+        } else {
45
+            this.simulcastSender = new NoSimulcastSender();
46
+        }
47
+
48
+    }
49
+}
50
+
51
+/**
52
+ * Restores the simulcast groups of the remote description. In
53
+ * transformRemoteDescription we remove those in order for the set remote
54
+ * description to succeed. The focus needs the signal the groups to new
55
+ * participants.
56
+ *
57
+ * @param desc
58
+ * @returns {*}
59
+ */
60
+SimulcastManager.prototype.reverseTransformRemoteDescription = function (desc) {
61
+    return this.simulcastReceiver.reverseTransformRemoteDescription(desc);
62
+};
63
+
64
+/**
65
+ * Removes the ssrc-group:SIM from the remote description bacause Chrome
66
+ * either gets confused and thinks this is an FID group or, if an FID group
67
+ * is already present, it fails to set the remote description.
68
+ *
69
+ * @param desc
70
+ * @returns {*}
71
+ */
72
+SimulcastManager.prototype.transformRemoteDescription = function (desc) {
73
+    return this.simulcastReceiver.transformRemoteDescription(desc);
74
+};
75
+
76
+/**
77
+ * Gets the fully qualified msid (stream.id + track.id) associated to the
78
+ * SSRC.
79
+ *
80
+ * @param ssrc
81
+ * @returns {*}
82
+ */
83
+SimulcastManager.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
84
+    return this.simulcastReceiver.getRemoteVideoStreamIdBySSRC(ssrc);
85
+};
86
+
87
+/**
88
+ * Returns a stream with single video track, the one currently being
89
+ * received by this endpoint.
90
+ *
91
+ * @param stream the remote simulcast stream.
92
+ * @returns {webkitMediaStream}
93
+ */
94
+SimulcastManager.prototype.getReceivingVideoStream = function (stream) {
95
+    return this.simulcastReceiver.getReceivingVideoStream(stream);
96
+};
97
+
98
+/**
99
+ *
100
+ *
101
+ * @param desc
102
+ * @returns {*}
103
+ */
104
+SimulcastManager.prototype.transformLocalDescription = function (desc) {
105
+    return this.simulcastSender.transformLocalDescription(desc);
106
+};
107
+
108
+/**
109
+ *
110
+ * @returns {*}
111
+ */
112
+SimulcastManager.prototype.getLocalVideoStream = function() {
113
+    return this.simulcastSender.getLocalVideoStream();
114
+};
115
+
116
+/**
117
+ * GUM for simulcast.
118
+ *
119
+ * @param constraints
120
+ * @param success
121
+ * @param err
122
+ */
123
+SimulcastManager.prototype.getUserMedia = function (constraints, success, err) {
124
+
125
+    this.simulcastSender.getUserMedia(constraints, success, err);
126
+};
127
+
128
+/**
129
+ * Prepares the local description for public usage (i.e. to be signaled
130
+ * through Jingle to the focus).
131
+ *
132
+ * @param desc
133
+ * @returns {RTCSessionDescription}
134
+ */
135
+SimulcastManager.prototype.reverseTransformLocalDescription = function (desc) {
136
+    return this.simulcastSender.reverseTransformLocalDescription(desc);
137
+};
138
+
139
+/**
140
+ * Ensures that the simulcast group is present in the answer, _if_ native
141
+ * simulcast is enabled,
142
+ *
143
+ * @param desc
144
+ * @returns {*}
145
+ */
146
+SimulcastManager.prototype.transformAnswer = function (desc) {
147
+    return this.simulcastSender.transformAnswer(desc);
148
+};
149
+
150
+SimulcastManager.prototype.getReceivingSSRC = function (jid) {
151
+    return this.simulcastReceiver.getReceivingSSRC(jid);
152
+};
153
+
154
+SimulcastManager.prototype.getReceivingVideoStreamBySSRC = function (msid) {
155
+    return this.simulcastReceiver.getReceivingVideoStreamBySSRC(msid);
156
+};
157
+
158
+/**
159
+ *
160
+ * @param lines
161
+ * @param mediatypes
162
+ * @returns {*}
163
+ */
164
+SimulcastManager.prototype.parseMedia = function(lines, mediatypes) {
165
+    var sb = lines.sdp.split('\r\n');
166
+    return this.simulcastUtils.parseMedia(sb, mediatypes);
167
+};
168
+
169
+SimulcastManager.prototype._setReceivingVideoStream = function(resource, ssrc) {
170
+    this.simulcastReceiver._setReceivingVideoStream(resource, ssrc);
171
+};
172
+
173
+SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled) {
174
+    this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
175
+};
176
+
177
+SimulcastManager.prototype.resetSender = function() {
178
+    if (typeof this.simulcastSender.reset === 'function'){
179
+        this.simulcastSender.reset();
180
+    }
181
+};
182
+
183
+$(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
184
+    endpointSimulcastLayers.forEach(function (esl) {
185
+        var ssrc = esl.simulcastLayer.primarySSRC;
186
+        simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
187
+    });
188
+});
189
+
190
+$(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
191
+    var ssrc = simulcastLayer.primarySSRC;
192
+    simulcast._setLocalVideoStreamEnabled(ssrc, true);
193
+});
194
+
195
+$(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
196
+    var ssrc = simulcastLayer.primarySSRC;
197
+    simulcast._setLocalVideoStreamEnabled(ssrc, false);
198
+});
199
+
200
+
201
+var simulcast = new SimulcastManager();
202
+
203
+module.exports = simulcast;

Loading…
Cancel
Save