diff --git a/conference.js b/conference.js index 09914ac0c..99d01c9be 100644 --- a/conference.js +++ b/conference.js @@ -1400,6 +1400,8 @@ export default { receiver.stop(); } + this._stopProxyConnection(); + let promise = null; if (didHaveVideo) { @@ -1475,9 +1477,12 @@ export default { /** * Creates desktop (screensharing) {@link JitsiLocalTrack} + * * @param {Object} [options] - Screen sharing options that will be passed to * createLocalTracks. - * + * @param {Object} [options.desktopSharing] + * @param {Object} [options.desktopStream] - An existing desktop stream to + * use instead of creating a new desktop stream. * @return {Promise.} - A Promise resolved with * {@link JitsiLocalTrack} for the screensharing or rejected with * {@link JitsiTrackError}. @@ -1490,49 +1495,52 @@ export default { const didHaveVideo = Boolean(this.localVideo); const wasVideoMuted = this.isLocalVideoMuted(); - return createLocalTracksF({ - desktopSharingSourceDevice: options.desktopSharingSources - ? null : config._desktopSharingSourceDevice, - desktopSharingSources: options.desktopSharingSources, - devices: [ 'desktop' ], - desktopSharingExtensionExternalInstallation: { - interval: 500, - checkAgain: () => DSExternalInstallationInProgress, - listener: (status, url) => { - switch (status) { - case 'waitingForExtension': { - DSExternalInstallationInProgress = true; - externalInstallation = true; - const listener = () => { - // Wait a little bit more just to be sure that we - // won't miss the extension installation - setTimeout( - () => { + const getDesktopStreamPromise = options.desktopStream + ? Promise.resolve([ options.desktopStream ]) + : createLocalTracksF({ + desktopSharingSourceDevice: options.desktopSharingSources + ? null : config._desktopSharingSourceDevice, + desktopSharingSources: options.desktopSharingSources, + devices: [ 'desktop' ], + desktopSharingExtensionExternalInstallation: { + interval: 500, + checkAgain: () => DSExternalInstallationInProgress, + listener: (status, url) => { + switch (status) { + case 'waitingForExtension': { + DSExternalInstallationInProgress = true; + externalInstallation = true; + const listener = () => { + // Wait a little bit more just to be sure that + // we won't miss the extension installation + setTimeout(() => { DSExternalInstallationInProgress = false; }, 500); - APP.UI.removeListener( + APP.UI.removeListener( + UIEvents.EXTERNAL_INSTALLATION_CANCELED, + listener); + }; + + APP.UI.addListener( UIEvents.EXTERNAL_INSTALLATION_CANCELED, listener); - }; + APP.UI.showExtensionExternalInstallationDialog(url); + break; + } + case 'extensionFound': + // Close the dialog. + externalInstallation && $.prompt.close(); + break; + default: - APP.UI.addListener( - UIEvents.EXTERNAL_INSTALLATION_CANCELED, - listener); - APP.UI.showExtensionExternalInstallationDialog(url); - break; - } - case 'extensionFound': - // Close the dialog. - externalInstallation && $.prompt.close(); - break; - default: - - // Unknown status + // Unknown status + } } } - } - }).then(([ desktopStream ]) => { + }); + + return getDesktopStreamPromise.then(([ desktopStream ]) => { // Stores the "untoggle" handler which remembers whether was // there any video before and whether was it muted. this._untoggleScreenSharing @@ -2477,6 +2485,8 @@ export default { hangup(requestFeedback = false) { eventEmitter.emit(JitsiMeetConferenceEvents.BEFORE_HANGUP); + this._stopProxyConnection(); + APP.store.dispatch(destroyLocalTracks()); this._localTracksInitialized = false; this.localVideo = null; @@ -2694,6 +2704,65 @@ export default { return this.localVideo.sourceType; }, + /** + * Callback invoked by the external api create or update a direct connection + * from the local client to an external client. + * + * @param {Object} event - The object containing information that should be + * passed to the {@code ProxyConnectionService}. + * @returns {void} + */ + onProxyConnectionEvent(event) { + if (!this._proxyConnection) { + this._proxyConnection = new JitsiMeetJS.ProxyConnectionService({ + /** + * The proxy connection feature is currently tailored towards + * taking a proxied video stream and showing it as a local + * desktop screen. + */ + convertVideoToDesktop: true, + + /** + * Callback invoked to pass messages from the local client back + * out to the external client. + * + * @param {string} peerJid - The jid of the intended recipient + * of the message. + * @param {Object} data - The message that should be sent. For + * screensharing this is an iq. + * @returns {void} + */ + onSendMessage: (peerJid, data) => + APP.API.sendProxyConnectionEvent({ + data, + to: peerJid + }), + + /** + * Callback invoked when the remote peer of the proxy connection + * has provided a video stream, intended to be used as a local + * desktop stream. + * + * @param {JitsiLocalTrack} remoteProxyStream - The media + * stream to use as a local desktop stream. + * @returns {void} + */ + onRemoteStream: desktopStream => { + if (desktopStream.videoType !== 'desktop') { + logger.warn('Received a non-desktop stream to proxy.'); + desktopStream.dispose(); + + return; + } + + this.toggleScreenSharing(undefined, { desktopStream }); + } + }); + } + + this._proxyConnection.processMessage(event); + }, + /** * Sets the video muted status. * @@ -2728,5 +2797,19 @@ export default { if (score === -1 || (score >= 1 && score <= 5)) { APP.store.dispatch(submitFeedback(score, message, room)); } + }, + + /** + * Terminates any proxy screensharing connection that is active. + * + * @private + * @returns {void} + */ + _stopProxyConnection() { + if (this._proxyConnection) { + this._proxyConnection.stop(); + } + + this._proxyConnection = null; } }; diff --git a/modules/API/API.js b/modules/API/API.js index 60907761e..4740dc026 100644 --- a/modules/API/API.js +++ b/modules/API/API.js @@ -60,6 +60,9 @@ function initCommands() { sendAnalytics(createApiEvent('display.name.changed')); APP.conference.changeLocalDisplayName(displayName); }, + 'proxy-connection-event': event => { + APP.conference.onProxyConnectionEvent(event); + }, 'submit-feedback': feedback => { sendAnalytics(createApiEvent('submit.feedback')); APP.conference.submitFeedback(feedback.score, feedback.message); @@ -260,6 +263,20 @@ class API { }); } + /** + * Notifies the external application (spot) that the local jitsi-participant + * has a status update. + * + * @param {Object} event - The message to pass onto spot. + * @returns {void} + */ + sendProxyConnectionEvent(event: Object) { + this._sendEvent({ + name: 'proxy-connection-event', + ...event + }); + } + /** * Sends event to the external application. * diff --git a/modules/API/external/external_api.js b/modules/API/external/external_api.js index c1934fc9a..92e17626e 100644 --- a/modules/API/external/external_api.js +++ b/modules/API/external/external_api.js @@ -46,6 +46,7 @@ const events = { 'outgoing-message': 'outgoingMessage', 'participant-joined': 'participantJoined', 'participant-left': 'participantLeft', + 'proxy-connection-event': 'proxyConnectionEvent', 'video-ready-to-close': 'readyToClose', 'video-conference-joined': 'videoConferenceJoined', 'video-conference-left': 'videoConferenceLeft', @@ -743,6 +744,25 @@ export default class JitsiMeetExternalAPI extends EventEmitter { eventList.forEach(event => this.removeEventListener(event)); } + /** + * Passes an event along to the local conference participant to establish + * or update a direct peer connection. This is currently used for developing + * wireless screensharing with room integration and it is advised against to + * use as its api may change. + * + * @param {Object} event - An object with information to pass along. + * @param {Object} event.data - The payload of the event. + * @param {string} event.from - The jid of the sender of the event. Needed + * when a reply is to be sent regarding the event. + * @returns {void} + */ + sendProxyConnectionEvent(event) { + this._transport.sendEvent({ + data: [ event ], + name: 'proxy-connection-event' + }); + } + /** * Returns the configuration for electron for the windows that are open * from Jitsi Meet.