Bläddra i källkod

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

j8
paweldomas 11 år sedan
förälder
incheckning
452704d6b3
7 ändrade filer med 223 tillägg och 78 borttagningar
  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 Visa fil

@@ -9,6 +9,7 @@ var sharedKey = '';
9 9
 var roomUrl = null;
10 10
 var ssrc2jid = {};
11 11
 var localVideoSrc = null;
12
+var flipXLocalVideo = true;
12 13
 var preziPlayer = null;
13 14
 
14 15
 /* window.onbeforeunload = closePageWarning; */
@@ -71,7 +72,7 @@ function audioStreamReady(stream) {
71 72
 
72 73
 function videoStreamReady(stream) {
73 74
 
74
-    change_local_video(stream);
75
+    change_local_video(stream, true);
75 76
 
76 77
     doJoin();
77 78
 }
@@ -134,27 +135,37 @@ function change_local_audio(stream) {
134 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 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 171
 $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
@@ -243,42 +254,27 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
243 254
 
244 255
     data.stream.onended = function () {
245 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 258
         // Mark video as removed to cancel waiting loop(if video is removed before has started)
261 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 265
             console.log("Remove whole user");
266 266
             // Remove whole container
267
-            userContainer.remove();
267
+            container.remove();
268 268
             Util.playSoundNotification('userLeft');
269 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 278
     // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
283 279
     if (isVideo
284 280
         && data.peerjid && sess.peerjid === data.peerjid &&
@@ -290,6 +286,46 @@ $(document).bind('remotestreamadded.jingle', function (event, data, sid) {
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 329
 // an attempt to work around https://github.com/jitsi/jitmeet/issues/32
294 330
 function sendKeyframe(pc) {
295 331
     console.log('sendkeyframe', pc.iceConnectionState);
@@ -404,7 +440,7 @@ $(document).bind('callactive.jingle', function (event, videoelem, sid) {
404 440
         videoelem.show();
405 441
         resizeThumbnails();
406 442
 
407
-        updateLargeVideo(videoelem.attr('src'), false, 1);
443
+        updateLargeVideo(videoelem.attr('src'), 1);
408 444
 
409 445
         showFocusIndicator();
410 446
     }
@@ -566,6 +602,8 @@ $(document).bind('presence.muc', function (event, jid, info, pres) {
566 602
                 break;
567 603
             case 'recvonly':
568 604
                 el.hide();
605
+                // FIXME: Check if we have to change large video
606
+                //checkChangeLargeVideo(el);
569 607
                 break;
570 608
             }
571 609
         }
@@ -751,23 +789,27 @@ function isPresentationVisible() {
751 789
 /**
752 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 793
     console.log('hover in', newSrc);
756 794
 
757 795
     setPresentationVisible(false);
758 796
 
759 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 802
         $('#largeVideo').fadeOut(300, function () {
764 803
             $(this).attr('src', newSrc);
765 804
 
805
+            // Screen stream is already rotated
806
+            var flipX = (newSrc === localVideoSrc) && flipXLocalVideo;
807
+
766 808
             var videoTransform = document.getElementById('largeVideo').style.webkitTransform;
767
-            if (localVideo && videoTransform !== 'scaleX(-1)') {
809
+            if (flipX && videoTransform !== 'scaleX(-1)') {
768 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 813
                 document.getElementById('largeVideo').style.webkitTransform = "none";
772 814
             }
773 815
 
@@ -1203,8 +1245,14 @@ function showToolbar() {
1203 1245
 //        TODO: Enable settings functionality. Need to uncomment the settings button in index.html.
1204 1246
 //        $('#settingsButton').css({visibility:"visible"});
1205 1247
     }
1248
+    showDesktopSharingButton();
1249
+}
1250
+
1251
+function showDesktopSharingButton() {
1206 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 Visa fil

@@ -9,5 +9,6 @@ var config = {
9 9
 //  useIPv6: true, // ipv6 support. use at your own risk
10 10
     useNicks: false,
11 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 Visa fil

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

+ 104
- 20
desktopsharing.js Visa fil

@@ -9,13 +9,43 @@ var isUsingScreenStream = false;
9 9
  */
10 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 25
  * @returns {boolean} <tt>true</tt> if desktop sharing feature is available and enabled.
14 26
  */
15 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,7 +55,8 @@ function toggleScreenSharing() {
25 55
     if (!(connection && connection.connected
26 56
         && !switchInProgress
27 57
         && getConferenceHandler().peerconnection.signalingState == 'stable'
28
-        && getConferenceHandler().peerconnection.iceConnectionState == 'connected')) {
58
+        && getConferenceHandler().peerconnection.iceConnectionState == 'connected'
59
+        && obtainDesktopStream )) {
29 60
         return;
30 61
     }
31 62
     switchInProgress = true;
@@ -33,22 +64,29 @@ function toggleScreenSharing() {
33 64
     // Only the focus is able to set a shared key.
34 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 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 82
     } else {
46 83
         // Disable screen stream
47 84
         getUserMediaWithConstraints(
48 85
             ['video'],
49 86
             function(stream) {
87
+                // We are now using camera stream
50 88
                 isUsingScreenStream = false;
51
-                gotScreenStream(stream);
89
+                newStreamCreated(stream);
52 90
             },
53 91
             getSwitchStreamFailed, config.resolution || '360'
54 92
         );
@@ -60,18 +98,64 @@ function getSwitchStreamFailed(error) {
60 98
     switchInProgress = false;
61 99
 }
62 100
 
63
-function gotScreenStream(stream) {
101
+function newStreamCreated(stream) {
102
+
64 103
     var oldStream = connection.jingle.localVideo;
65 104
 
66
-    change_local_video(stream);
105
+    change_local_video(stream, !isUsingScreenStream);
67 106
 
68 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 Visa fil

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

+ 12
- 3
libs/strophe/strophe.jingle.adapter.js Visa fil

@@ -337,11 +337,9 @@ TraceablePeerConnection.prototype.modifySources = function(successCallback) {
337 337
                             switch(self.pendingop) {
338 338
                                 case 'mute':
339 339
                                     sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
340
-                                    console.error("MUTE");
341 340
                                     break;
342 341
                                 case 'unmute':
343 342
                                     sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
344
-                                    console.error("UNMUTE");
345 343
                                     break;
346 344
                             }
347 345
                             sdp.raw = sdp.session + sdp.media.join('');
@@ -502,7 +500,7 @@ function setupRTC() {
502 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 504
     var constraints = {audio: false, video: false};
507 505
 
508 506
     if (um.indexOf('video') >= 0) {
@@ -521,6 +519,17 @@ function getUserMediaWithConstraints(um, success_callback, failure_callback, res
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 534
     if (resolution && !constraints.video) {
526 535
         constraints.video = {mandatory: {}};// same behaviour as true

+ 3
- 0
libs/strophe/strophe.jingle.sessionbase.js Visa fil

@@ -51,6 +51,9 @@ SessionBase.prototype.switchStreams = function (new_stream, oldStream, success_c
51 51
     // Remember SDP to figure out added/removed SSRCs
52 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 57
     self.peerconnection.removeStream(oldStream);
55 58
 
56 59
     self.connection.jingle.localVideo = new_stream;

Laddar…
Avbryt
Spara