Browse Source

feat(screenshare): support remote wireless screensharing (#3809)

* feat(screenshare): support remote wireless screensharing

- Pass events to the ProxyConnectionService so it can
  handle establishing a peer connection so a remote
  participant, not in the conference, can send a
  video stream to the local participant to use as a
  local desktop stream.
- Modify the existing start screensharing flow to accept
  a desktop stream instead of always trying to create one.

* adjust ProxyConnectionService for lib review changes
master
virtuacoplenny 6 years ago
parent
commit
6241172af8
No account linked to committer's email address
3 changed files with 156 additions and 36 deletions
  1. 119
    36
      conference.js
  2. 17
    0
      modules/API/API.js
  3. 20
    0
      modules/API/external/external_api.js

+ 119
- 36
conference.js View File

@@ -1400,6 +1400,8 @@ export default {
1400 1400
             receiver.stop();
1401 1401
         }
1402 1402
 
1403
+        this._stopProxyConnection();
1404
+
1403 1405
         let promise = null;
1404 1406
 
1405 1407
         if (didHaveVideo) {
@@ -1475,9 +1477,12 @@ export default {
1475 1477
 
1476 1478
     /**
1477 1479
      * Creates desktop (screensharing) {@link JitsiLocalTrack}
1480
+     *
1478 1481
      * @param {Object} [options] - Screen sharing options that will be passed to
1479 1482
      * createLocalTracks.
1480
-     *
1483
+     * @param {Object} [options.desktopSharing]
1484
+     * @param {Object} [options.desktopStream] - An existing desktop stream to
1485
+     * use instead of creating a new desktop stream.
1481 1486
      * @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
1482 1487
      * {@link JitsiLocalTrack} for the screensharing or rejected with
1483 1488
      * {@link JitsiTrackError}.
@@ -1490,49 +1495,52 @@ export default {
1490 1495
         const didHaveVideo = Boolean(this.localVideo);
1491 1496
         const wasVideoMuted = this.isLocalVideoMuted();
1492 1497
 
1493
-        return createLocalTracksF({
1494
-            desktopSharingSourceDevice: options.desktopSharingSources
1495
-                ? null : config._desktopSharingSourceDevice,
1496
-            desktopSharingSources: options.desktopSharingSources,
1497
-            devices: [ 'desktop' ],
1498
-            desktopSharingExtensionExternalInstallation: {
1499
-                interval: 500,
1500
-                checkAgain: () => DSExternalInstallationInProgress,
1501
-                listener: (status, url) => {
1502
-                    switch (status) {
1503
-                    case 'waitingForExtension': {
1504
-                        DSExternalInstallationInProgress = true;
1505
-                        externalInstallation = true;
1506
-                        const listener = () => {
1507
-                            // Wait a little bit more just to be sure that we
1508
-                            // won't miss the extension installation
1509
-                            setTimeout(
1510
-                                () => {
1498
+        const getDesktopStreamPromise = options.desktopStream
1499
+            ? Promise.resolve([ options.desktopStream ])
1500
+            : createLocalTracksF({
1501
+                desktopSharingSourceDevice: options.desktopSharingSources
1502
+                    ? null : config._desktopSharingSourceDevice,
1503
+                desktopSharingSources: options.desktopSharingSources,
1504
+                devices: [ 'desktop' ],
1505
+                desktopSharingExtensionExternalInstallation: {
1506
+                    interval: 500,
1507
+                    checkAgain: () => DSExternalInstallationInProgress,
1508
+                    listener: (status, url) => {
1509
+                        switch (status) {
1510
+                        case 'waitingForExtension': {
1511
+                            DSExternalInstallationInProgress = true;
1512
+                            externalInstallation = true;
1513
+                            const listener = () => {
1514
+                                // Wait a little bit more just to be sure that
1515
+                                // we won't miss the extension installation
1516
+                                setTimeout(() => {
1511 1517
                                     DSExternalInstallationInProgress = false;
1512 1518
                                 },
1513 1519
                                 500);
1514
-                            APP.UI.removeListener(
1520
+                                APP.UI.removeListener(
1521
+                                    UIEvents.EXTERNAL_INSTALLATION_CANCELED,
1522
+                                    listener);
1523
+                            };
1524
+
1525
+                            APP.UI.addListener(
1515 1526
                                 UIEvents.EXTERNAL_INSTALLATION_CANCELED,
1516 1527
                                 listener);
1517
-                        };
1518
-
1519
-                        APP.UI.addListener(
1520
-                            UIEvents.EXTERNAL_INSTALLATION_CANCELED,
1521
-                            listener);
1522
-                        APP.UI.showExtensionExternalInstallationDialog(url);
1523
-                        break;
1524
-                    }
1525
-                    case 'extensionFound':
1526
-                        // Close the dialog.
1527
-                        externalInstallation && $.prompt.close();
1528
-                        break;
1529
-                    default:
1528
+                            APP.UI.showExtensionExternalInstallationDialog(url);
1529
+                            break;
1530
+                        }
1531
+                        case 'extensionFound':
1532
+                            // Close the dialog.
1533
+                            externalInstallation && $.prompt.close();
1534
+                            break;
1535
+                        default:
1530 1536
 
1531
-                        // Unknown status
1537
+                            // Unknown status
1538
+                        }
1532 1539
                     }
1533 1540
                 }
1534
-            }
1535
-        }).then(([ desktopStream ]) => {
1541
+            });
1542
+
1543
+        return getDesktopStreamPromise.then(([ desktopStream ]) => {
1536 1544
             // Stores the "untoggle" handler which remembers whether was
1537 1545
             // there any video before and whether was it muted.
1538 1546
             this._untoggleScreenSharing
@@ -2477,6 +2485,8 @@ export default {
2477 2485
     hangup(requestFeedback = false) {
2478 2486
         eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP);
2479 2487
 
2488
+        this._stopProxyConnection();
2489
+
2480 2490
         APP.store.dispatch(destroyLocalTracks());
2481 2491
         this._localTracksInitialized = false;
2482 2492
         this.localVideo = null;
@@ -2694,6 +2704,65 @@ export default {
2694 2704
         return this.localVideo.sourceType;
2695 2705
     },
2696 2706
 
2707
+    /**
2708
+     * Callback invoked by the external api create or update a direct connection
2709
+     * from the local client to an external client.
2710
+     *
2711
+     * @param {Object} event - The object containing information that should be
2712
+     * passed to the {@code ProxyConnectionService}.
2713
+     * @returns {void}
2714
+     */
2715
+    onProxyConnectionEvent(event) {
2716
+        if (!this._proxyConnection) {
2717
+            this._proxyConnection = new JitsiMeetJS.ProxyConnectionService({
2718
+                /**
2719
+                 * The proxy connection feature is currently tailored towards
2720
+                 * taking a proxied video stream and showing it as a local
2721
+                 * desktop screen.
2722
+                 */
2723
+                convertVideoToDesktop: true,
2724
+
2725
+                /**
2726
+                 * Callback invoked to pass messages from the local client back
2727
+                 * out to the external client.
2728
+                 *
2729
+                 * @param {string} peerJid - The jid of the intended recipient
2730
+                 * of the message.
2731
+                 * @param {Object} data - The message that should be sent. For
2732
+                 * screensharing this is an iq.
2733
+                 * @returns {void}
2734
+                 */
2735
+                onSendMessage: (peerJid, data) =>
2736
+                    APP.API.sendProxyConnectionEvent({
2737
+                        data,
2738
+                        to: peerJid
2739
+                    }),
2740
+
2741
+                /**
2742
+                 * Callback invoked when the remote peer of the proxy connection
2743
+                 * has provided a video stream, intended to be used as a local
2744
+                 * desktop stream.
2745
+                 *
2746
+                 * @param {JitsiLocalTrack} remoteProxyStream - The media
2747
+                 * stream to use as a local desktop stream.
2748
+                 * @returns {void}
2749
+                 */
2750
+                onRemoteStream: desktopStream => {
2751
+                    if (desktopStream.videoType !== 'desktop') {
2752
+                        logger.warn('Received a non-desktop stream to proxy.');
2753
+                        desktopStream.dispose();
2754
+
2755
+                        return;
2756
+                    }
2757
+
2758
+                    this.toggleScreenSharing(undefined, { desktopStream });
2759
+                }
2760
+            });
2761
+        }
2762
+
2763
+        this._proxyConnection.processMessage(event);
2764
+    },
2765
+
2697 2766
     /**
2698 2767
      * Sets the video muted status.
2699 2768
      *
@@ -2728,5 +2797,19 @@ export default {
2728 2797
         if (score === -1 || (score >= 1 && score <= 5)) {
2729 2798
             APP.store.dispatch(submitFeedback(score, message, room));
2730 2799
         }
2800
+    },
2801
+
2802
+    /**
2803
+     * Terminates any proxy screensharing connection that is active.
2804
+     *
2805
+     * @private
2806
+     * @returns {void}
2807
+     */
2808
+    _stopProxyConnection() {
2809
+        if (this._proxyConnection) {
2810
+            this._proxyConnection.stop();
2811
+        }
2812
+
2813
+        this._proxyConnection = null;
2731 2814
     }
2732 2815
 };

+ 17
- 0
modules/API/API.js View File

@@ -60,6 +60,9 @@ function initCommands() {
60 60
             sendAnalytics(createApiEvent('display.name.changed'));
61 61
             APP.conference.changeLocalDisplayName(displayName);
62 62
         },
63
+        'proxy-connection-event': event => {
64
+            APP.conference.onProxyConnectionEvent(event);
65
+        },
63 66
         'submit-feedback': feedback => {
64 67
             sendAnalytics(createApiEvent('submit.feedback'));
65 68
             APP.conference.submitFeedback(feedback.score, feedback.message);
@@ -260,6 +263,20 @@ class API {
260 263
         });
261 264
     }
262 265
 
266
+    /**
267
+     * Notifies the external application (spot) that the local jitsi-participant
268
+     * has a status update.
269
+     *
270
+     * @param {Object} event - The message to pass onto spot.
271
+     * @returns {void}
272
+     */
273
+    sendProxyConnectionEvent(event: Object) {
274
+        this._sendEvent({
275
+            name: 'proxy-connection-event',
276
+            ...event
277
+        });
278
+    }
279
+
263 280
     /**
264 281
      * Sends event to the external application.
265 282
      *

+ 20
- 0
modules/API/external/external_api.js View File

@@ -46,6 +46,7 @@ const events = {
46 46
     'outgoing-message': 'outgoingMessage',
47 47
     'participant-joined': 'participantJoined',
48 48
     'participant-left': 'participantLeft',
49
+    'proxy-connection-event': 'proxyConnectionEvent',
49 50
     'video-ready-to-close': 'readyToClose',
50 51
     'video-conference-joined': 'videoConferenceJoined',
51 52
     'video-conference-left': 'videoConferenceLeft',
@@ -743,6 +744,25 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
743 744
         eventList.forEach(event => this.removeEventListener(event));
744 745
     }
745 746
 
747
+    /**
748
+     * Passes an event along to the local conference participant to establish
749
+     * or update a direct peer connection. This is currently used for developing
750
+     * wireless screensharing with room integration and it is advised against to
751
+     * use as its api may change.
752
+     *
753
+     * @param {Object} event - An object with information to pass along.
754
+     * @param {Object} event.data - The payload of the event.
755
+     * @param {string} event.from - The jid of the sender of the event. Needed
756
+     * when a reply is to be sent regarding the event.
757
+     * @returns {void}
758
+     */
759
+    sendProxyConnectionEvent(event) {
760
+        this._transport.sendEvent({
761
+            data: [ event ],
762
+            name: 'proxy-connection-event'
763
+        });
764
+    }
765
+
746 766
     /**
747 767
      * Returns the configuration for electron for the windows that are open
748 768
      * from Jitsi Meet.

Loading…
Cancel
Save