浏览代码

Adds desktop streaming using Chrome extension. Does not flip local screen preview.

master
paweldomas 11 年前
父节点
当前提交
452704d6b3
共有 7 个文件被更改,包括 223 次插入78 次删除
  1. 99
    51
      app.js
  2. 2
    1
      config.js
  3. 1
    1
      css/main.css
  4. 104
    20
      desktopsharing.js
  5. 2
    2
      index.html
  6. 12
    3
      libs/strophe/strophe.jingle.adapter.js
  7. 3
    0
      libs/strophe/strophe.jingle.sessionbase.js

+ 99
- 51
app.js 查看文件

9
 var roomUrl = null;
9
 var roomUrl = null;
10
 var ssrc2jid = {};
10
 var ssrc2jid = {};
11
 var localVideoSrc = null;
11
 var localVideoSrc = null;
12
+var flipXLocalVideo = true;
12
 var preziPlayer = null;
13
 var preziPlayer = null;
13
 
14
 
14
 /* window.onbeforeunload = closePageWarning; */
15
 /* window.onbeforeunload = closePageWarning; */
71
 
72
 
72
 function videoStreamReady(stream) {
73
 function videoStreamReady(stream) {
73
 
74
 
74
-    change_local_video(stream);
75
+    change_local_video(stream, true);
75
 
76
 
76
     doJoin();
77
     doJoin();
77
 }
78
 }
134
     document.getElementById('localAudio').volume = 0;
135
     document.getElementById('localAudio').volume = 0;
135
 }
136
 }
136
 
137
 
137
-function change_local_video(stream) {
138
+function change_local_video(stream, flipX) {
138
 
139
 
139
     connection.jingle.localVideo = stream;
140
     connection.jingle.localVideo = stream;
140
-    RTC.attachMediaStream($('#localVideo'), stream);
141
-    document.getElementById('localVideo').autoplay = true;
142
-    document.getElementById('localVideo').volume = 0;
143
 
141
 
144
-    localVideoSrc = document.getElementById('localVideo').src;
145
-    updateLargeVideo(localVideoSrc, true, 0);
146
-
147
-    $('#localVideo').click(function () {
148
-        $(document).trigger("video.selected", [false]);
149
-        updateLargeVideo($(this).attr('src'), true, 0);
142
+    var localVideo = document.createElement('video');
143
+    localVideo.id = 'localVideo_'+stream.id;
144
+    localVideo.autoplay = true;
145
+    localVideo.volume = 0; // is it required if audio is separated ?
146
+    localVideo.oncontextmenu = function () { return false; };
147
+
148
+    var localVideoContainer = document.getElementById('localVideoContainer');
149
+    localVideoContainer.appendChild(localVideo);
150
+
151
+    var localVideoSelector = $('#' + localVideo.id);
152
+    // Add click handler
153
+    localVideoSelector.click(function () { handleVideoThumbClicked(localVideo.src); } );
154
+    // Add stream ended handler
155
+    stream.onended = function () {
156
+        localVideoContainer.removeChild(localVideo);
157
+        checkChangeLargeVideo(localVideo.src);
158
+    };
159
+    // Flip video x axis if needed
160
+    flipXLocalVideo = flipX;
161
+    if(flipX) {
162
+        localVideoSelector.addClass("flipVideoX");
163
+    }
164
+    // Attach WebRTC stream
165
+    RTC.attachMediaStream(localVideoSelector, stream);
150
 
166
 
151
-        $('video').each(function (idx, el) {
152
-            if (el.id.indexOf('mixedmslabel') !== -1) {
153
-                el.volume = 0;
154
-                el.volume = 1;
155
-            }
156
-        });
157
-    });
167
+    localVideoSrc = localVideo.src;
168
+    updateLargeVideo(localVideoSrc, 0);
158
 }
169
 }
159
 
170
 
160
 $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
171
 $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
243
 
254
 
244
     data.stream.onended = function () {
255
     data.stream.onended = function () {
245
         console.log('stream ended', this.id);
256
         console.log('stream ended', this.id);
246
-        if (sel.attr('src') === $('#largeVideo').attr('src')) {
247
-            // this is currently displayed as large
248
-            // pick the last visible video in the row
249
-            // if nobody else is left, this picks the local video
250
-            var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0);
251
-            // mute if localvideo
252
-            var isLocalVideo = false;
253
-            if (pick) {
254
-                 if (pick.src === localVideoSrc)
255
-                 isLocalVideo = true;
256
-
257
-                 updateLargeVideo(pick.src, isLocalVideo, pick.volume);
258
-            }
259
-        }
257
+
260
         // Mark video as removed to cancel waiting loop(if video is removed before has started)
258
         // Mark video as removed to cancel waiting loop(if video is removed before has started)
261
         sel.removed = true;
259
         sel.removed = true;
260
+        sel.remove();
262
 
261
 
263
-        var userContainer = sel.parent();
264
-        if(userContainer.children().length === 0) {
262
+        var audioCount = $('#'+container.id+'>audio').length;
263
+        var videoCount = $('#'+container.id+'>video').length;
264
+        if(!audioCount && !videoCount) {
265
             console.log("Remove whole user");
265
             console.log("Remove whole user");
266
             // Remove whole container
266
             // Remove whole container
267
-            userContainer.remove();
267
+            container.remove();
268
             Util.playSoundNotification('userLeft');
268
             Util.playSoundNotification('userLeft');
269
             resizeThumbnails();
269
             resizeThumbnails();
270
-        } else {
271
-            // Remove only stream holder
272
-            sel.remove();
273
-            console.log("Remove stream only", sel);
274
         }
270
         }
271
+
272
+        checkChangeLargeVideo(vid.src);
275
     };
273
     };
276
-    sel.click(
277
-        function () {
278
-            $(document).trigger("video.selected", [false]);
279
-            updateLargeVideo($(this).attr('src'), false, 1);
280
-        }
281
-    );
274
+
275
+    // Add click handler
276
+    sel.click(function () { handleVideoThumbClicked(vid.src); });
277
+
282
     // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
278
     // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
283
     if (isVideo
279
     if (isVideo
284
         && data.peerjid && sess.peerjid === data.peerjid &&
280
         && data.peerjid && sess.peerjid === data.peerjid &&
290
     }
286
     }
291
 });
287
 });
292
 
288
 
289
+function handleVideoThumbClicked(videoSrc) {
290
+
291
+    $(document).trigger("video.selected", [false]);
292
+
293
+    updateLargeVideo(videoSrc, 1);
294
+
295
+    $('audio').each(function (idx, el) {
296
+        // We no longer mix so we check for local audio now
297
+        if(el.id != 'localAudio') {
298
+            el.volume = 0;
299
+            el.volume = 1;
300
+        }
301
+    });
302
+}
303
+
304
+/**
305
+ * Checks if removed video is currently displayed and tries to display another one instead.
306
+ * @param removedVideoSrc src stream identifier of the video.
307
+ */
308
+function checkChangeLargeVideo(removedVideoSrc){
309
+    if (removedVideoSrc === $('#largeVideo').attr('src')) {
310
+        // this is currently displayed as large
311
+        // pick the last visible video in the row
312
+        // if nobody else is left, this picks the local video
313
+        var pick = $('#remoteVideos>span[id!="mixedstream"]:visible:last>video').get(0);
314
+
315
+        if(!pick) {
316
+            console.info("Last visible video no longer exists");
317
+            pick = $('#remoteVideos>span[id!="mixedstream"]>video').get(0);
318
+        }
319
+
320
+        // mute if localvideo
321
+        if (pick) {
322
+            updateLargeVideo(pick.src, pick.volume);
323
+        } else {
324
+            console.warn("Failed to elect large video");
325
+        }
326
+    }
327
+}
328
+
293
 // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
329
 // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
294
 function sendKeyframe(pc) {
330
 function sendKeyframe(pc) {
295
     console.log('sendkeyframe', pc.iceConnectionState);
331
     console.log('sendkeyframe', pc.iceConnectionState);
404
         videoelem.show();
440
         videoelem.show();
405
         resizeThumbnails();
441
         resizeThumbnails();
406
 
442
 
407
-        updateLargeVideo(videoelem.attr('src'), false, 1);
443
+        updateLargeVideo(videoelem.attr('src'), 1);
408
 
444
 
409
         showFocusIndicator();
445
         showFocusIndicator();
410
     }
446
     }
566
                 break;
602
                 break;
567
             case 'recvonly':
603
             case 'recvonly':
568
                 el.hide();
604
                 el.hide();
605
+                // FIXME: Check if we have to change large video
606
+                //checkChangeLargeVideo(el);
569
                 break;
607
                 break;
570
             }
608
             }
571
         }
609
         }
751
 /**
789
 /**
752
  * Updates the large video with the given new video source.
790
  * Updates the large video with the given new video source.
753
  */
791
  */
754
-function updateLargeVideo(newSrc, localVideo, vol) {
792
+function updateLargeVideo(newSrc, vol) {
755
     console.log('hover in', newSrc);
793
     console.log('hover in', newSrc);
756
 
794
 
757
     setPresentationVisible(false);
795
     setPresentationVisible(false);
758
 
796
 
759
     if ($('#largeVideo').attr('src') !== newSrc) {
797
     if ($('#largeVideo').attr('src') !== newSrc) {
760
 
798
 
761
-        document.getElementById('largeVideo').volume = vol;
799
+        // FIXME: is it still required ? audio is separated
800
+        //document.getElementById('largeVideo').volume = vol;
762
 
801
 
763
         $('#largeVideo').fadeOut(300, function () {
802
         $('#largeVideo').fadeOut(300, function () {
764
             $(this).attr('src', newSrc);
803
             $(this).attr('src', newSrc);
765
 
804
 
805
+            // Screen stream is already rotated
806
+            var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
807
+
766
             var videoTransform = document.getElementById('largeVideo').style.webkitTransform;
808
             var videoTransform = document.getElementById('largeVideo').style.webkitTransform;
767
-            if (localVideo && videoTransform !== 'scaleX(-1)') {
809
+            if (flipX && videoTransform !== 'scaleX(-1)') {
768
                 document.getElementById('largeVideo').style.webkitTransform = "scaleX(-1)";
810
                 document.getElementById('largeVideo').style.webkitTransform = "scaleX(-1)";
769
             }
811
             }
770
-            else if (!localVideo && videoTransform === 'scaleX(-1)') {
812
+            else if (!flipX && videoTransform === 'scaleX(-1)') {
771
                 document.getElementById('largeVideo').style.webkitTransform = "none";
813
                 document.getElementById('largeVideo').style.webkitTransform = "none";
772
             }
814
             }
773
 
815
 
1203
 //        TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
1245
 //        TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
1204
 //        $('#settingsButton').css({visibility:"visible"});
1246
 //        $('#settingsButton').css({visibility:"visible"});
1205
     }
1247
     }
1248
+    showDesktopSharingButton();
1249
+}
1250
+
1251
+function showDesktopSharingButton() {
1206
     if(isDesktopSharingEnabled()) {
1252
     if(isDesktopSharingEnabled()) {
1207
-        $('#desktopsharing').css({display:"inline"});
1253
+        $('#desktopsharing').css( {display:"inline"} );
1254
+    } else {
1255
+        $('#desktopsharing').css( {display:"none"} );
1208
     }
1256
     }
1209
 }
1257
 }
1210
 
1258
 

+ 2
- 1
config.js 查看文件

9
 //  useIPv6: true, // ipv6 support. use at your own risk
9
 //  useIPv6: true, // ipv6 support. use at your own risk
10
     useNicks: false,
10
     useNicks: false,
11
     bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
11
     bosh: '//lambada.jitsi.net/http-bind', // FIXME: use xep-0156 for that
12
-    chromeDesktopSharing: false // Desktop sharing is disabled by default
12
+    desktopSharing: false, // Desktop sharing is disabled by default(call setDesktopSharing in the console to enable)
13
+    chromeExtensionId: 'nhkhigmiepmkogopmkfipjlfkeablnch' // Id of Jitsi Desktop Streamer chrome extension
13
 };
14
 };

+ 1
- 1
css/main.css 查看文件

49
     font-size: 10pt;
49
     font-size: 10pt;
50
 }
50
 }
51
 
51
 
52
-#localVideo {
52
+.flipVideoX {
53
     -moz-transform: scaleX(-1);
53
     -moz-transform: scaleX(-1);
54
     -webkit-transform: scaleX(-1);
54
     -webkit-transform: scaleX(-1);
55
     -o-transform: scaleX(-1);
55
     -o-transform: scaleX(-1);

+ 104
- 20
desktopsharing.js 查看文件

9
  */
9
  */
10
 var switchInProgress = false;
10
 var switchInProgress = false;
11
 
11
 
12
+/**
13
+ * Method used to get screen sharing stream.
14
+ *
15
+ * @type {function(stream_callback, failure_callback}
16
+ */
17
+var obtainDesktopStream = obtainScreenFromExtension;
18
+
19
+/**
20
+ * Desktop sharing must be enabled in config and works on chrome only.
21
+ */
22
+var desktopSharingEnabled = config.desktopSharing;
23
+
12
 /**
24
 /**
13
  * @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
25
  * @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
14
  */
26
  */
15
 function isDesktopSharingEnabled() {
27
 function isDesktopSharingEnabled() {
16
-    // Desktop sharing must be enabled in config and works on chrome only.
17
-    // Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
18
-    return config.chromeDesktopSharing && RTC.browser == 'chrome';
28
+    return desktopSharingEnabled;
29
+}
30
+
31
+/**
32
+ * Call this method to toggle desktop sharing feature.
33
+ * @param method pass "ext" to use chrome extension for desktop capture(chrome extension required),
34
+ *        pass "webrtc" to use WebRTC "screen" desktop source('chrome://flags/#enable-usermedia-screen-capture'
35
+ *        must be enabled), pass any other string or nothing in order to disable this feature completely.
36
+ */
37
+function setDesktopSharing(method) {
38
+    if(method == "ext") {
39
+        obtainDesktopStream = obtainScreenFromExtension;
40
+        desktopSharingEnabled = true;
41
+    } else if(method == "webrtc") {
42
+        obtainDesktopStream = obtainWebRTCScreen;
43
+        desktopSharingEnabled = true;
44
+    } else {
45
+        obtainDesktopStream = null;
46
+        desktopSharingEnabled = false;
47
+    }
48
+    showDesktopSharingButton();
19
 }
49
 }
20
 
50
 
21
 /*
51
 /*
25
     if (!(connection && connection.connected
55
     if (!(connection && connection.connected
26
         && !switchInProgress
56
         && !switchInProgress
27
         && getConferenceHandler().peerconnection.signalingState == 'stable'
57
         && getConferenceHandler().peerconnection.signalingState == 'stable'
28
-        && getConferenceHandler().peerconnection.iceConnectionState == 'connected')) {
58
+        && getConferenceHandler().peerconnection.iceConnectionState == 'connected'
59
+        && obtainDesktopStream )) {
29
         return;
60
         return;
30
     }
61
     }
31
     switchInProgress = true;
62
     switchInProgress = true;
33
     // Only the focus is able to set a shared key.
64
     // Only the focus is able to set a shared key.
34
     if(!isUsingScreenStream)
65
     if(!isUsingScreenStream)
35
     {
66
     {
36
-        // Enable screen stream
37
-        getUserMediaWithConstraints(
38
-            ['screen'],
39
-            function(stream){
67
+        obtainDesktopStream(
68
+            function(stream) {
69
+                // We now use screen stream
40
                 isUsingScreenStream = true;
70
                 isUsingScreenStream = true;
41
-                gotScreenStream(stream);
71
+                // Hook 'ended' event to restore camera when screen stream stops
72
+                stream.addEventListener('ended',
73
+                    function(e) {
74
+                        if(!switchInProgress) {
75
+                            toggleScreenSharing();
76
+                        }
77
+                    }
78
+                );
79
+                newStreamCreated(stream);
42
             },
80
             },
43
-            getSwitchStreamFailed
44
-        );
81
+            getSwitchStreamFailed );
45
     } else {
82
     } else {
46
         // Disable screen stream
83
         // Disable screen stream
47
         getUserMediaWithConstraints(
84
         getUserMediaWithConstraints(
48
             ['video'],
85
             ['video'],
49
             function(stream) {
86
             function(stream) {
87
+                // We are now using camera stream
50
                 isUsingScreenStream = false;
88
                 isUsingScreenStream = false;
51
-                gotScreenStream(stream);
89
+                newStreamCreated(stream);
52
             },
90
             },
53
             getSwitchStreamFailed, config.resolution || '360'
91
             getSwitchStreamFailed, config.resolution || '360'
54
         );
92
         );
60
     switchInProgress = false;
98
     switchInProgress = false;
61
 }
99
 }
62
 
100
 
63
-function gotScreenStream(stream) {
101
+function newStreamCreated(stream) {
102
+
64
     var oldStream = connection.jingle.localVideo;
103
     var oldStream = connection.jingle.localVideo;
65
 
104
 
66
-    change_local_video(stream);
105
+    change_local_video(stream, !isUsingScreenStream);
67
 
106
 
68
     // FIXME: will block switchInProgress on true value in case of exception
107
     // FIXME: will block switchInProgress on true value in case of exception
69
-    getConferenceHandler().switchStreams(stream, oldStream, onDesktopStreamEnabled);
108
+    getConferenceHandler().switchStreams(
109
+        stream, oldStream,
110
+        function() {
111
+            // Switch operation has finished
112
+            switchInProgress = false;
113
+        });
114
+}
115
+
116
+/**
117
+ * Method obtains desktop stream from WebRTC 'screen' source.
118
+ * Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled.
119
+ */
120
+function obtainWebRTCScreen(streamCallback, failCallback) {
121
+    getUserMediaWithConstraints(
122
+        ['screen'],
123
+        streamCallback,
124
+        failCallback
125
+    );
70
 }
126
 }
71
 
127
 
72
-function onDesktopStreamEnabled() {
73
-    // Wait a moment before enabling the button
74
-    window.setTimeout(function() {
75
-        switchInProgress = false;
76
-    }, 3000);
128
+/**
129
+ * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop' stream for returned stream token.
130
+ */
131
+function obtainScreenFromExtension(streamCallback, failCallback) {
132
+    // Check for extension API
133
+    if(!chrome || !chrome.runtime) {
134
+        failCallback("Failed to communicate with extension - no API available");
135
+        return;
136
+    }
137
+    // Sends 'getStream' msg to the extension. Extension id must be defined in the config.
138
+    chrome.runtime.sendMessage(
139
+        config.chromeExtensionId,
140
+        { getStream: true},
141
+        function(response) {
142
+            if(!response) {
143
+                failCallback(chrome.runtime.lastError);
144
+                return;
145
+            }
146
+            console.log("Response from extension: "+response);
147
+            if(response.streamId) {
148
+                getUserMediaWithConstraints(
149
+                    ['desktop'],
150
+                    function(stream) {
151
+                        streamCallback(stream);
152
+                    },
153
+                    failCallback,
154
+                    null, null, null,
155
+                    response.streamId);
156
+            } else {
157
+                failCallback("Extension failed to get the stream");
158
+            }
159
+        }
160
+    );
77
 }
161
 }

+ 2
- 2
index.html 查看文件

14
     <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
14
     <script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
15
     <script src="muc.js?v=9"></script><!-- simple MUC library -->
15
     <script src="muc.js?v=9"></script><!-- simple MUC library -->
16
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
16
     <script src="estos_log.js?v=2"></script><!-- simple stanza logger -->
17
+    <script src="desktopsharing.js?v=1"></script><!-- desktop sharing -->
17
     <script src="app.js?v=23"></script><!-- application logic -->
18
     <script src="app.js?v=23"></script><!-- application logic -->
18
     <script src="chat.js?v=3"></script><!-- chat logic -->
19
     <script src="chat.js?v=3"></script><!-- chat logic -->
19
-    <script src="desktopsharing.js?v=1"></script><!-- desktop sharing logic -->
20
     <script src="util.js?v=2"></script><!-- utility functions -->
20
     <script src="util.js?v=2"></script><!-- utility functions -->
21
     <script src="etherpad.js?v=5"></script><!-- etherpad plugin -->
21
     <script src="etherpad.js?v=5"></script><!-- etherpad plugin -->
22
     <script src="smileys.js?v=1"></script><!-- smiley images -->
22
     <script src="smileys.js?v=1"></script><!-- smiley images -->
85
         <div id="remoteVideos">
85
         <div id="remoteVideos">
86
             <span id="localVideoContainer" class="videocontainer">
86
             <span id="localVideoContainer" class="videocontainer">
87
                 <span id="localNick"></span>
87
                 <span id="localNick"></span>
88
-                <video id="localVideo" autoplay oncontextmenu="return false;" muted></video>
88
+                <!--<video id="localVideo" autoplay oncontextmenu="return false;" muted></video> - is now per stream generated -->
89
                 <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
89
                 <audio id="localAudio" autoplay oncontextmenu="return false;" muted></audio>
90
                 <span class="focusindicator"></span>
90
                 <span class="focusindicator"></span>
91
             </span>
91
             </span>

+ 12
- 3
libs/strophe/strophe.jingle.adapter.js 查看文件

337
                             switch(self.pendingop) {
337
                             switch(self.pendingop) {
338
                                 case 'mute':
338
                                 case 'mute':
339
                                     sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
339
                                     sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
340
-                                    console.error("MUTE");
341
                                     break;
340
                                     break;
342
                                 case 'unmute':
341
                                 case 'unmute':
343
                                     sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
342
                                     sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
344
-                                    console.error("UNMUTE");
345
                                     break;
343
                                     break;
346
                             }
344
                             }
347
                             sdp.raw = sdp.session + sdp.media.join('');
345
                             sdp.raw = sdp.session + sdp.media.join('');
502
     return RTC;
500
     return RTC;
503
 }
501
 }
504
 
502
 
505
-function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps) {
503
+function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
506
     var constraints = {audio: false, video: false};
504
     var constraints = {audio: false, video: false};
507
 
505
 
508
     if (um.indexOf('video') >= 0) {
506
     if (um.indexOf('video') >= 0) {
521
             }
519
             }
522
         };
520
         };
523
     }
521
     }
522
+    if (um.indexOf('desktop') >= 0) {
523
+        constraints.video = {
524
+            mandatory: {
525
+                chromeMediaSource: "desktop",
526
+                chromeMediaSourceId: desktopStream,
527
+                maxWidth: window.screen.width,
528
+                maxHeight: window.screen.height,
529
+                maxFrameRate: 3
530
+            }
531
+        }
532
+    }
524
 
533
 
525
     if (resolution && !constraints.video) {
534
     if (resolution && !constraints.video) {
526
         constraints.video = {mandatory: {}};// same behaviour as true
535
         constraints.video = {mandatory: {}};// same behaviour as true

+ 3
- 0
libs/strophe/strophe.jingle.sessionbase.js 查看文件

51
     // Remember SDP to figure out added/removed SSRCs
51
     // Remember SDP to figure out added/removed SSRCs
52
     var oldSdp = new SDP(self.peerconnection.localDescription.sdp);
52
     var oldSdp = new SDP(self.peerconnection.localDescription.sdp);
53
 
53
 
54
+    // Stop the stream to trigger onended event for old stream
55
+    oldStream.stop();
56
+
54
     self.peerconnection.removeStream(oldStream);
57
     self.peerconnection.removeStream(oldStream);
55
 
58
 
56
     self.connection.jingle.localVideo = new_stream;
59
     self.connection.jingle.localVideo = new_stream;

正在加载...
取消
保存