浏览代码

Implements first version of adaptive simulcast.

j8
George Politis 10 年前
父节点
当前提交
36af4da83d
共有 7 个文件被更改,包括 278 次插入44 次删除
  1. 16
    1
      app.js
  2. 14
    0
      data_channels.js
  3. 1
    1
      libs/colibri/colibri.focus.js
  4. 6
    2
      libs/strophe/strophe.jingle.adapter.js
  5. 2
    2
      libs/strophe/strophe.jingle.session.js
  6. 211
    35
      simulcast.js
  7. 28
    3
      videolayout.js

+ 16
- 1
app.js 查看文件

@@ -279,7 +279,10 @@ function waitForPresence(data, sid) {
279 279
         var ssrclines
280 280
             = SDPUtil.find_lines(sess.peerconnection.remoteDescription.sdp, 'a=ssrc:');
281 281
         ssrclines = ssrclines.filter(function (line) {
282
-            return line.indexOf('mslabel:' + data.stream.label) !== -1;
282
+            // NOTE(gp) previously we filtered on the mslabel, but that property
283
+            // is not always present.
284
+            // return line.indexOf('mslabel:' + data.stream.label) !== -1;
285
+            return line.indexOf('msid:' + data.stream.id) !== -1;
283 286
         });
284 287
         if (ssrclines.length) {
285 288
             thessrc = ssrclines[0].substring(7).split(' ')[0];
@@ -292,6 +295,7 @@ function waitForPresence(data, sid) {
292 295
             // presence to arrive.
293 296
 
294 297
             if (!ssrc2jid[thessrc]) {
298
+                // TODO(gp) limit wait duration to 1 sec.
295 299
                 setTimeout(function(d, s) {
296 300
                     return function() {
297 301
                             waitForPresence(d, s);
@@ -1418,6 +1422,17 @@ $(document).bind('fatalError.jingle',
1418 1422
     }
1419 1423
 );
1420 1424
 
1425
+$(document).bind("video.selected", function(event, isPresentation, userJid) {
1426
+    if (!isPresentation && _dataChannels && _dataChannels.length != 0) {
1427
+        _dataChannels[0].send(JSON.stringify({
1428
+            'colibriClass': 'SelectedEndpointChangedEvent',
1429
+            'selectedEndpoint': (isPresentation || !userJid)
1430
+                // TODO(gp) hmm.. I wonder which one of the Strophe methods to use..
1431
+                ? null : userJid.split('/')[1]
1432
+        }));
1433
+    }
1434
+});
1435
+
1421 1436
 function callSipButtonClicked()
1422 1437
 {
1423 1438
     $.prompt('<h2>Enter SIP number</h2>' +

+ 14
- 0
data_channels.js 查看文件

@@ -23,6 +23,10 @@ function onDataChannel(event)
23 23
         //dataChannel.send("Hello bridge!");
24 24
         // Sends 12 bytes binary message to the bridge
25 25
         //dataChannel.send(new ArrayBuffer(12));
26
+
27
+        // TODO(gp) we are supposed to tell the bridge about video selections
28
+        // so that it can do adaptive simulcast, What if a video selection has
29
+        // been made while the data channels are down or broken?
26 30
     };
27 31
 
28 32
     dataChannel.onerror = function (error)
@@ -89,6 +93,16 @@ function onDataChannel(event)
89 93
                 var endpointSimulcastLayers = obj.endpointSimulcastLayers;
90 94
                 $(document).trigger('simulcastlayerschanged', [endpointSimulcastLayers]);
91 95
             }
96
+            else if ("StartSimulcastLayerEvent" === colibriClass)
97
+            {
98
+                var simulcastLayer = obj.simulcastLayer;
99
+                $(document).trigger('startsimulcastlayer', simulcastLayer);
100
+            }
101
+            else if ("StopSimulcastLayerEvent" === colibriClass)
102
+            {
103
+                var simulcastLayer = obj.simulcastLayer;
104
+                $(document).trigger('stopsimulcastlayer', simulcastLayer);
105
+            }
92 106
             else
93 107
             {
94 108
                 console.debug("Data channel JSON-formatted message: ", obj);

+ 1
- 1
libs/colibri/colibri.focus.js 查看文件

@@ -530,7 +530,7 @@ ColibriFocus.prototype.createdConference = function (result) {
530 530
     bridgeSDP.raw = bridgeSDP.session + bridgeSDP.media.join('');
531 531
     var bridgeDesc = new RTCSessionDescription({type: 'offer', sdp: bridgeSDP.raw});
532 532
     var simulcast = new Simulcast();
533
-    var bridgeDesc = simulcast.transformBridgeDescription(bridgeDesc);
533
+    var bridgeDesc = simulcast.transformRemoteDescription(bridgeDesc);
534 534
 
535 535
     this.peerconnection.setRemoteDescription(bridgeDesc,
536 536
         function () {

+ 6
- 2
libs/strophe/strophe.jingle.adapter.js 查看文件

@@ -130,10 +130,14 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
130 130
     TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
131 131
     TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() {
132 132
         var simulcast = new Simulcast();
133
-        var publicLocalDescription = simulcast.makeLocalDescriptionPublic(this.peerconnection.localDescription);
133
+        var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
134 134
         return publicLocalDescription;
135 135
     });
136
-    TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() { return this.peerconnection.remoteDescription; });
136
+    TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
137
+        var simulcast = new Simulcast();
138
+        var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
139
+        return publicRemoteDescription;
140
+    });
137 141
 }
138 142
 
139 143
 TraceablePeerConnection.prototype.addStream = function (stream) {

+ 2
- 2
libs/strophe/strophe.jingle.session.js 查看文件

@@ -120,7 +120,7 @@ JingleSession.prototype.accept = function () {
120 120
         pranswer.sdp = pranswer.sdp.replace('a=inactive', 'a=sendrecv');
121 121
     }
122 122
     var simulcast = new Simulcast();
123
-    pranswer = simulcast.makeLocalDescriptionPublic(pranswer);
123
+    pranswer = simulcast.reverseTransformLocalDescription(pranswer);
124 124
     var prsdp = new SDP(pranswer.sdp);
125 125
     var accept = $iq({to: this.peerjid,
126 126
         type: 'set'})
@@ -568,7 +568,7 @@ JingleSession.prototype.createdAnswer = function (sdp, provisional) {
568 568
                     responder: this.responder,
569 569
                     sid: this.sid });
570 570
             var simulcast = new Simulcast();
571
-            var publicLocalDesc = simulcast.makeLocalDescriptionPublic(sdp);
571
+            var publicLocalDesc = simulcast.reverseTransformLocalDescription(sdp);
572 572
             var publicLocalSDP = new SDP(publicLocalDesc.sdp);
573 573
             publicLocalSDP.toJingle(accept, this.initiator == this.me ? 'initiator' : 'responder');
574 574
             this.connection.sendIQ(accept,

+ 211
- 35
simulcast.js 查看文件

@@ -15,7 +15,7 @@ function Simulcast() {
15 15
     "use strict";
16 16
     // global state for all transformers.
17 17
     var localExplosionMap = {}, localVideoSourceCache, emptyCompoundIndex,
18
-        remoteMaps = {
18
+        remoteVideoSourceCache, remoteMaps = {
19 19
             msid2Quality: {},
20 20
             ssrc2Msid: {},
21 21
             receivingVideoStreams: {}
@@ -45,6 +45,14 @@ function Simulcast() {
45 45
         this._replaceVideoSources(lines, localVideoSourceCache);
46 46
     };
47 47
 
48
+    Simulcast.prototype._cacheRemoteVideoSources = function (lines) {
49
+        remoteVideoSourceCache = this._getVideoSources(lines);
50
+    };
51
+
52
+    Simulcast.prototype._restoreRemoteVideoSources = function (lines) {
53
+        this._replaceVideoSources(lines, remoteVideoSourceCache);
54
+    };
55
+
48 56
     Simulcast.prototype._replaceVideoSources = function (lines, videoSources) {
49 57
 
50 58
         var i, inVideo = false, index = -1, howMany = 0;
@@ -216,6 +224,12 @@ function Simulcast() {
216 224
         }
217 225
     };
218 226
 
227
+    /**
228
+     * Produces a single stream with multiple tracks for local video sources.
229
+     *
230
+     * @param lines
231
+     * @private
232
+     */
219 233
     Simulcast.prototype._explodeLocalSimulcastSources = function (lines) {
220 234
         var sb, msid, sid, tid, videoSources, self;
221 235
 
@@ -259,6 +273,12 @@ function Simulcast() {
259 273
         this._replaceVideoSources(lines, sb);
260 274
     };
261 275
 
276
+    /**
277
+     * Groups local video sources together in the ssrc-group:SIM group.
278
+     *
279
+     * @param lines
280
+     * @private
281
+     */
262 282
     Simulcast.prototype._groupLocalVideoSources = function (lines) {
263 283
         var sb, videoSources, ssrcs = [], ssrc;
264 284
 
@@ -401,6 +421,13 @@ function Simulcast() {
401 421
         return sb;
402 422
     };
403 423
 
424
+    /**
425
+     * Ensures that the simulcast group is present in the answer, _if_ native
426
+     * simulcast is enabled,
427
+     *
428
+     * @param desc
429
+     * @returns {*}
430
+     */
404 431
     Simulcast.prototype.transformAnswer = function (desc) {
405 432
         if (config.enableSimulcast && config.useNativeSimulcast) {
406 433
 
@@ -429,11 +456,53 @@ function Simulcast() {
429 456
         return desc;
430 457
     };
431 458
 
432
-    Simulcast.prototype.makeLocalDescriptionPublic = function (desc) {
459
+    Simulcast.prototype._restoreSimulcastGroups = function (sb) {
460
+        this._restoreRemoteVideoSources(sb);
461
+    };
462
+
463
+    /**
464
+     * Restores the simulcast groups of the remote description. In
465
+     * transformRemoteDescription we remove those in order for the set remote
466
+     * description to succeed. The focus needs the signal the groups to new
467
+     * participants.
468
+     *
469
+     * @param desc
470
+     * @returns {*}
471
+     */
472
+    Simulcast.prototype.reverseTransformRemoteDescription = function (desc) {
433 473
         var sb;
434 474
 
435
-        if (!desc || desc == null)
475
+        if (!desc || desc == null) {
436 476
             return desc;
477
+        }
478
+
479
+        if (config.enableSimulcast) {
480
+            sb = desc.sdp.split('\r\n');
481
+
482
+            this._restoreSimulcastGroups(sb);
483
+
484
+            desc = new RTCSessionDescription({
485
+                type: desc.type,
486
+                sdp: sb.join('\r\n')
487
+            });
488
+        }
489
+
490
+        return desc;
491
+    };
492
+
493
+    /**
494
+     * Prepares the local description for public usage (i.e. to be signaled
495
+     * through Jingle to the focus).
496
+     *
497
+     * @param desc
498
+     * @returns {RTCSessionDescription}
499
+     */
500
+    Simulcast.prototype.reverseTransformLocalDescription = function (desc) {
501
+        var sb;
502
+
503
+        if (!desc || desc == null) {
504
+            return desc;
505
+        }
437 506
 
438 507
         if (config.enableSimulcast) {
439 508
 
@@ -480,30 +549,16 @@ function Simulcast() {
480 549
         this._replaceVideoSources(lines, sb);
481 550
     };
482 551
 
483
-    Simulcast.prototype.transformBridgeDescription = function (desc) {
484
-        if (config.enableSimulcast && config.useNativeSimulcast) {
485
-
486
-            var sb = desc.sdp.split('\r\n');
487
-
488
-            this._ensureGoogConference(sb);
489
-
490
-            desc = new RTCSessionDescription({
491
-                type: desc.type,
492
-                sdp: sb.join('\r\n')
493
-            });
494
-
495
-            if (this.debugLvl && this.debugLvl > 1) {
496
-                console.info('Transformed bridge description');
497
-                console.info(desc.sdp);
498
-            }
499
-        }
500
-
501
-        return desc;
502
-    };
503
-
504 552
     Simulcast.prototype._updateRemoteMaps = function (lines) {
505 553
         var remoteVideoSources = this._parseMedia(lines, ['video'])[0], videoSource, quality;
506 554
 
555
+        // (re) initialize the remote maps.
556
+        remoteMaps = {
557
+            msid2Quality: {},
558
+            ssrc2Msid: {},
559
+            receivingVideoStreams: {}
560
+        };
561
+
507 562
         if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
508 563
             remoteVideoSources.groups.forEach(function (group) {
509 564
                 if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
@@ -518,6 +573,12 @@ function Simulcast() {
518 573
         }
519 574
     };
520 575
 
576
+    /**
577
+     *
578
+     *
579
+     * @param desc
580
+     * @returns {*}
581
+     */
521 582
     Simulcast.prototype.transformLocalDescription = function (desc) {
522 583
         if (config.enableSimulcast && !config.useNativeSimulcast) {
523 584
 
@@ -539,14 +600,28 @@ function Simulcast() {
539 600
         return desc;
540 601
     };
541 602
 
603
+    /**
604
+     * Removes the ssrc-group:SIM from the remote description bacause Chrome
605
+     * either gets confused and thinks this is an FID group or, if an FID group
606
+     * is already present, it fails to set the remote description.
607
+     *
608
+     * @param desc
609
+     * @returns {*}
610
+     */
542 611
     Simulcast.prototype.transformRemoteDescription = function (desc) {
543 612
         if (config.enableSimulcast) {
544 613
 
545 614
             var sb = desc.sdp.split('\r\n');
546 615
 
547 616
             this._updateRemoteMaps(sb);
548
-            this._removeSimulcastGroup(sb); // NOTE(gp) this needs to be called after updateRemoteMaps!
549
-            this._ensureGoogConference(sb);
617
+            this._cacheRemoteVideoSources(sb);
618
+            this._removeSimulcastGroup(sb); // NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
619
+
620
+            if (config.useNativeSimulcast) {
621
+                // We don't need the goog conference flag if we're not doing
622
+                // native simulcast.
623
+                this._ensureGoogConference(sb);
624
+            }
550 625
 
551 626
             desc = new RTCSessionDescription({
552 627
                 type: desc.type,
@@ -562,20 +637,28 @@ function Simulcast() {
562 637
         return desc;
563 638
     };
564 639
 
565
-    Simulcast.prototype.setReceivingVideoStream = function (ssrc) {
640
+    Simulcast.prototype._setReceivingVideoStream = function (ssrc) {
566 641
         var receivingTrack = remoteMaps.ssrc2Msid[ssrc],
567 642
             msidParts = receivingTrack.split(' ');
568 643
 
569 644
         remoteMaps.receivingVideoStreams[msidParts[0]] = msidParts[1];
570 645
     };
571 646
 
647
+    /**
648
+     * Returns a stream with single video track, the one currently being
649
+     * received by this endpoint.
650
+     *
651
+     * @param stream the remote simulcast stream.
652
+     * @returns {webkitMediaStream}
653
+     */
572 654
     Simulcast.prototype.getReceivingVideoStream = function (stream) {
573
-        var tracks, track, i, electedTrack, msid, quality = 1, receivingTrackId;
655
+        var tracks, i, electedTrack, msid, quality = 1, receivingTrackId;
574 656
 
575 657
         if (config.enableSimulcast) {
576 658
 
577 659
             if (remoteMaps.receivingVideoStreams[stream.id])
578 660
             {
661
+                // the bridge has signaled us to receive a specific track.
579 662
                 receivingTrackId = remoteMaps.receivingVideoStreams[stream.id];
580 663
                 tracks = stream.getVideoTracks();
581 664
                 for (i = 0; i < tracks.length; i++) {
@@ -587,15 +670,18 @@ function Simulcast() {
587 670
             }
588 671
 
589 672
             if (!electedTrack) {
673
+                // we don't have an elected track, choose by initial quality.
590 674
                 tracks = stream.getVideoTracks();
591 675
                 for (i = 0; i < tracks.length; i++) {
592
-                    track = tracks[i];
593
-                    msid = [stream.id, track.id].join(' ');
676
+                    msid = [stream.id, tracks[i].id].join(' ');
594 677
                     if (remoteMaps.msid2Quality[msid] === quality) {
595
-                        electedTrack = track;
678
+                        electedTrack = tracks[i];
596 679
                         break;
597 680
                     }
598 681
                 }
682
+
683
+                // TODO(gp) if the initialQuality could not be satisfied, lower
684
+                // the requirement and try again.
599 685
             }
600 686
         }
601 687
 
@@ -604,6 +690,15 @@ function Simulcast() {
604 690
             : stream;
605 691
     };
606 692
 
693
+    var stream;
694
+
695
+    /**
696
+     * GUM for simulcast.
697
+     *
698
+     * @param constraints
699
+     * @param success
700
+     * @param err
701
+     */
607 702
     Simulcast.prototype.getUserMedia = function (constraints, success, err) {
608 703
 
609 704
         // TODO(gp) what if we request a resolution not supported by the hardware?
@@ -620,7 +715,10 @@ function Simulcast() {
620 715
 
621 716
         if (config.enableSimulcast && !config.useNativeSimulcast) {
622 717
 
623
-            // NOTE(gp) if we request the lq stream first webkitGetUserMedia fails randomly. Tested with Chrome 37.
718
+            // NOTE(gp) if we request the lq stream first webkitGetUserMedia
719
+            // fails randomly. Tested with Chrome 37. As fippo suggested, the
720
+            // reason appears to be that Chrome only acquires the cam once and
721
+            // then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
624 722
 
625 723
             navigator.webkitGetUserMedia(constraints, function (hqStream) {
626 724
 
@@ -641,6 +739,7 @@ function Simulcast() {
641 739
                     localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
642 740
 
643 741
                     hqStream.addTrack(lqStream.getVideoTracks()[0]);
742
+                    stream = hqStream;
644 743
                     success(hqStream);
645 744
                 }, err);
646 745
             }, err);
@@ -656,18 +755,95 @@ function Simulcast() {
656 755
 
657 756
                 // add hq stream to local map
658 757
                 localMaps.msids.push(hqStream.getVideoTracks()[0].id);
659
-
758
+                stream = hqStream;
660 759
                 success(hqStream);
661 760
             }, err);
662 761
         }
663 762
     };
664 763
 
665
-    Simulcast.prototype.getRemoteVideoStreamIdBySSRC = function (primarySSRC) {
666
-        return remoteMaps.ssrc2Msid[primarySSRC];
764
+    /**
765
+     * Gets the fully qualified msid (stream.id + track.id) associated to the
766
+     * SSRC.
767
+     *
768
+     * @param ssrc
769
+     * @returns {*}
770
+     */
771
+    Simulcast.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
772
+        return remoteMaps.ssrc2Msid[ssrc];
667 773
     };
668 774
 
669 775
     Simulcast.prototype.parseMedia = function (desc, mediatypes) {
670 776
         var lines = desc.sdp.split('\r\n');
671 777
         return this._parseMedia(lines, mediatypes);
672 778
     };
779
+
780
+    Simulcast.prototype._startLocalVideoStream = function (ssrc) {
781
+        var trackid;
782
+
783
+        Object.keys(localMaps.msid2ssrc).some(function (tid) {
784
+            if (localMaps.msid2ssrc[tid] == ssrc)
785
+            {
786
+                trackid = tid;
787
+                return true;
788
+            }
789
+        });
790
+
791
+        stream.getVideoTracks().some(function(track) {
792
+            if (track.id === trackid) {
793
+                track.enabled = true;
794
+                return true;
795
+            }
796
+        });
797
+    };
798
+
799
+    Simulcast.prototype._stopLocalVideoStream = function (ssrc) {
800
+        var trackid;
801
+
802
+        Object.keys(localMaps.msid2ssrc).some(function (tid) {
803
+            if (localMaps.msid2ssrc[tid] == ssrc)
804
+            {
805
+                trackid = tid;
806
+                return true;
807
+            }
808
+        });
809
+
810
+        stream.getVideoTracks().some(function(track) {
811
+            if (track.id === trackid) {
812
+                track.enabled = false;
813
+                return true;
814
+            }
815
+        });
816
+    };
817
+
818
+    Simulcast.prototype.getLocalVideoStream = function() {
819
+        var track;
820
+
821
+        stream.getVideoTracks().some(function(t) {
822
+            if ((track = t).enabled) {
823
+                return true;
824
+            }
825
+        });
826
+
827
+        return new webkitMediaStream([track]);
828
+    };
829
+
830
+    $(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
831
+        endpointSimulcastLayers.forEach(function (esl) {
832
+            var ssrc = esl.simulcastLayer.primarySSRC;
833
+            var simulcast = new Simulcast();
834
+            simulcast._setReceivingVideoStream(ssrc);
835
+        });
836
+    });
837
+
838
+    $(document).bind('startsimulcastlayer', function(event, simulcastLayer) {
839
+        var ssrc = simulcastLayer.primarySSRC;
840
+        var simulcast = new Simulcast();
841
+        simulcast._startLocalVideoStream(ssrc);
842
+    });
843
+
844
+    $(document).bind('stopsimulcastlayer', function(event, simulcastLayer) {
845
+        var ssrc = simulcastLayer.primarySSRC;
846
+        var simulcast = new Simulcast();
847
+        simulcast._stopLocalVideoStream(ssrc);
848
+    });
673 849
 }());

+ 28
- 3
videolayout.js 查看文件

@@ -116,6 +116,10 @@ var VideoLayout = (function (my) {
116 116
 
117 117
             var isVisible = $('#largeVideo').is(':visible');
118 118
 
119
+            // we need this here because after the fade the videoSrc may have
120
+            // changed.
121
+            var isDesktop = isVideoSrcDesktop(newSrc);
122
+
119 123
             $('#largeVideo').fadeOut(300, function () {
120 124
                 var oldSrc = $(this).attr('src');
121 125
 
@@ -137,7 +141,7 @@ var VideoLayout = (function (my) {
137 141
                 }
138 142
 
139 143
                 // Change the way we'll be measuring and positioning large video
140
-                var isDesktop = isVideoSrcDesktop(newSrc);
144
+
141 145
                 getVideoSize = isDesktop
142 146
                                 ? getDesktopVideoSize
143 147
                                 : getCameraVideoSize;
@@ -209,7 +213,7 @@ var VideoLayout = (function (my) {
209 213
 
210 214
         // Triggers a "video.selected" event. The "false" parameter indicates
211 215
         // this isn't a prezi.
212
-        $(document).trigger("video.selected", [false]);
216
+        $(document).trigger("video.selected", [false, userJid]);
213 217
 
214 218
         VideoLayout.updateLargeVideo(videoSrc, 1);
215 219
 
@@ -1294,6 +1298,28 @@ var VideoLayout = (function (my) {
1294 1298
         }
1295 1299
     });
1296 1300
 
1301
+    $(document).bind('startsimulcastlayer', function(event, simulcastLayer) {
1302
+        var localVideoSelector = $('#' + 'localVideo_' + connection.jingle.localVideo.id);
1303
+        var simulcast = new Simulcast();
1304
+        var stream = simulcast.getLocalVideoStream();
1305
+
1306
+        // Attach WebRTC stream
1307
+        RTC.attachMediaStream(localVideoSelector, stream);
1308
+
1309
+        localVideoSrc = $(localVideoSelector).attr('src');
1310
+    });
1311
+
1312
+    $(document).bind('stopsimulcastlayer', function(event, simulcastLayer) {
1313
+        var localVideoSelector = $('#' + 'localVideo_' + connection.jingle.localVideo.id);
1314
+        var simulcast = new Simulcast();
1315
+        var stream = simulcast.getLocalVideoStream();
1316
+
1317
+        // Attach WebRTC stream
1318
+        RTC.attachMediaStream(localVideoSelector, stream);
1319
+
1320
+        localVideoSrc = $(localVideoSelector).attr('src');
1321
+    });
1322
+
1297 1323
     /**
1298 1324
      * On simulcast layers changed event.
1299 1325
      */
@@ -1302,7 +1328,6 @@ var VideoLayout = (function (my) {
1302 1328
         endpointSimulcastLayers.forEach(function (esl) {
1303 1329
 
1304 1330
             var primarySSRC = esl.simulcastLayer.primarySSRC;
1305
-            simulcast.setReceivingVideoStream(primarySSRC);
1306 1331
             var msid = simulcast.getRemoteVideoStreamIdBySSRC(primarySSRC);
1307 1332
 
1308 1333
             // Get session and stream from msid.

正在加载...
取消
保存