diff --git a/modules/desktopsharing/ScreenObtainer.js b/modules/desktopsharing/ScreenObtainer.js new file mode 100644 index 000000000..a9ce0562f --- /dev/null +++ b/modules/desktopsharing/ScreenObtainer.js @@ -0,0 +1,265 @@ +/* global config, APP, chrome, $, alert */ +/* jshint -W003 */ +var RTCBrowserType = require("../RTC/RTCBrowserType"); +var AdapterJS = require("../RTC/adapter.screenshare"); + +/** + * Indicates whether the Chrome desktop sharing extension is installed. + * @type {boolean} + */ +var chromeExtInstalled = false; +/** + * Indicates whether an update of the Chrome desktop sharing extension is + * required. + * @type {boolean} + */ +var chromeExtUpdateRequired = false; + +/** + * Handles obtaining a stream from a screen capture on different browsers. + */ +function ScreenObtainer(){ +} + +/** + * Initializes the function used to obtain a screen capture (this.obtainStream). + * + * If the browser is Chrome, it uses the value of 'config.desktopSharing' to + * decide whether to use the a Chrome extension (if the value is 'ext'), use + * the "screen" media source (if the value is 'webrtc'), or disable screen + * capture (if the value is other). + * Note that for the "screen" media source to work the + * 'chrome://flags/#enable-usermedia-screen-capture' flag must be set. + */ +ScreenObtainer.prototype.init = function() { + var obtainDesktopStream = null; + + // When TemasysWebRTC plugin is used we always use getUserMedia, so we don't + // care about the value of config.desktopSharing. + if (RTCBrowserType.isTemasysPluginUsed()) { + if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) { + console.info("Screensharing not supported by this plugin version"); + } else if (!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) { + console.info( + "Screensharing not available with Temasys plugin on this site"); + } else { + obtainDesktopStream = obtainWebRTCScreen; + console.info("Using Temasys plugin for desktop sharing"); + } + } else if (RTCBrowserType.isChrome()) { + if (config.desktopSharing == "ext") { + if (RTCBrowserType.getChromeVersion() >= 34) { + obtainDesktopStream = obtainScreenFromExtension; + console.info("Using Chrome extension for desktop sharing"); + initChromeExtension(); + } else { + console.info("Chrome extension not supported until ver 34"); + } + } else if (config.desktopSharing == "webrtc") { + obtainDesktopStream = obtainWebRTCScreen; + console.info("Using Chrome WebRTC for desktop sharing"); + } + } else if (RTCBrowserType.isFirefox()) { + obtainDesktopStream = obtainWebRTCScreen; + } + + if (!obtainDesktopStream) { + console.info("Desktop sharing disabled"); + } + + ScreenObtainer.prototype.obtainStream = obtainDesktopStream.bind(this); +}; + +ScreenObtainer.prototype.obtainStream = null; + +/** + * Checks whether obtaining a screen capture is supported in the current + * environment. + * @returns {boolean} + */ +ScreenObtainer.prototype.isSupported = function() { + return !!this.obtainStream; +}; + +/** + * Obtains a desktop stream using getUserMedia. + * For this to work on Chrome, the + * 'chrome://flags/#enable-usermedia-screen-capture' flag must be enabled. + * + * On firefox, the document's domain must be white-listed in the + * 'media.getusermedia.screensharing.allowed_domains' preference in + * 'about:config'. + */ +function obtainWebRTCScreen(streamCallback, failCallback) { + APP.RTC.getUserMediaWithConstraints( + ['screen'], + streamCallback, + failCallback + ); +} + +/** + * Constructs inline install URL for Chrome desktop streaming extension. + * The 'chromeExtensionId' must be defined in config.js. + * @returns {string} + */ +function getWebStoreInstallUrl() +{ + return "https://chrome.google.com/webstore/detail/" + + config.chromeExtensionId; +} + +/** + * Checks whether an update of the Chrome extension is required. + * @param minVersion minimal required version + * @param extVersion current extension version + * @returns {boolean} + */ +function isUpdateRequired(minVersion, extVersion) { + try { + var s1 = minVersion.split('.'); + var s2 = extVersion.split('.'); + + var len = Math.max(s1.length, s2.length); + for (var i = 0; i < len; i++) { + var n1 = 0, + n2 = 0; + + if (i < s1.length) + n1 = parseInt(s1[i]); + if (i < s2.length) + n2 = parseInt(s2[i]); + + if (isNaN(n1) || isNaN(n2)) { + return true; + } else if (n1 !== n2) { + return n1 > n2; + } + } + + // will happen if both versions have identical numbers in + // their components (even if one of them is longer, has more components) + return false; + } + catch (e) { + console.error("Failed to parse extension version", e); + APP.UI.messageHandler.showError("dialog.error", + "dialog.detectext"); + return true; + } +} + +function checkChromeExtInstalled(callback) { + if (!chrome || !chrome.runtime) { + // No API, so no extension for sure + callback(false, false); + return; + } + chrome.runtime.sendMessage( + config.chromeExtensionId, + { getVersion: true }, + function (response) { + if (!response || !response.version) { + // Communication failure - assume that no endpoint exists + console.warn( + "Extension not installed?: ", chrome.runtime.lastError); + callback(false, false); + return; + } + // Check installed extension version + var extVersion = response.version; + console.log('Extension version is: ' + extVersion); + var updateRequired + = isUpdateRequired(config.minChromeExtVersion, extVersion); + callback(!updateRequired, updateRequired); + } + ); +} + +function doGetStreamFromExtension(streamCallback, failCallback) { + // Sends 'getStream' msg to the extension. + // Extension id must be defined in the config. + chrome.runtime.sendMessage( + config.chromeExtensionId, + { getStream: true, sources: config.desktopSharingSources }, + function (response) { + if (!response) { + failCallback(chrome.runtime.lastError); + return; + } + console.log("Response from extension: " + response); + if (response.streamId) { + APP.RTC.getUserMediaWithConstraints( + ['desktop'], + function (stream) { + streamCallback(stream); + }, + failCallback, + null, null, null, + response.streamId); + } else { + failCallback("Extension failed to get the stream"); + } + } + ); +} + +/** + * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop' + * stream for returned stream token. + */ +function obtainScreenFromExtension(streamCallback, failCallback) { + if (chromeExtInstalled) { + doGetStreamFromExtension(streamCallback, failCallback); + } else { + if (chromeExtUpdateRequired) { + alert( + 'Jitsi Desktop Streamer requires update. ' + + 'Changes will take effect after next Chrome restart.'); + } + + chrome.webstore.install( + getWebStoreInstallUrl(), + function (arg) { + console.log("Extension installed successfully", arg); + chromeExtInstalled = true; + // We need to give a moment for the endpoint to become available + window.setTimeout(function () { + doGetStreamFromExtension(streamCallback, failCallback); + }, 500); + }, + function (arg) { + console.log("Failed to install the extension", arg); + failCallback(arg); + APP.UI.messageHandler.showError("dialog.error", + "dialog.failtoinstall"); + } + ); + } +} + +/** + * Initializes with extension id set in + * config.js to support inline installs. Host site must be selected as main + * website of published extension. + */ +function initInlineInstalls() +{ + $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl()); +} + +function initChromeExtension() { + // Initialize Chrome extension inline installs + initInlineInstalls(); + // Check if extension is installed + checkChromeExtInstalled(function (installed, updateRequired) { + chromeExtInstalled = installed; + chromeExtUpdateRequired = updateRequired; + console.info( + "Chrome extension installed: " + chromeExtInstalled + + " updateRequired: " + chromeExtUpdateRequired); + }); +} + + +module.exports = ScreenObtainer; diff --git a/modules/desktopsharing/desktopsharing.js b/modules/desktopsharing/desktopsharing.js index 835e27a4e..44fab7796 100644 --- a/modules/desktopsharing/desktopsharing.js +++ b/modules/desktopsharing/desktopsharing.js @@ -1,17 +1,17 @@ -/* global $, alert, APP, changeLocalVideo, chrome, config, getConferenceHandler, - getUserMediaWithConstraints */ -var AdapterJS = require("../RTC/adapter.screenshare"); +/* global APP, config */ var EventEmitter = require("events"); var DesktopSharingEventTypes = require("../../service/desktopsharing/DesktopSharingEventTypes"); var RTCBrowserType = require("../RTC/RTCBrowserType"); var RTCEvents = require("../../service/RTC/RTCEvents"); +var ScreenObtainer = require("./ScreenObtainer"); /** - * Indicates that desktop stream is currently in use(for toggle purpose). + * Indicates that desktop stream is currently in use (for toggle purpose). * @type {boolean} */ var isUsingScreenStream = false; + /** * Indicates that switch stream operation is in progress and prevent from * triggering new events. @@ -20,251 +20,22 @@ var isUsingScreenStream = false; var switchInProgress = false; /** - * Method used to get screen sharing stream. - * - * @type {function (stream_callback, failure_callback} + * Used to obtain the screen sharing stream from the browser. */ -var obtainDesktopStream = null; - -/** - * Indicates whether desktop sharing extension is installed. - * @type {boolean} - */ -var extInstalled = false; - -/** - * Indicates whether update of desktop sharing extension is required. - * @type {boolean} - */ -var extUpdateRequired = false; +var screenObtainer = new ScreenObtainer(); var eventEmitter = new EventEmitter(); -/** - * Method obtains desktop stream from WebRTC 'screen' source. - * Flag 'chrome://flags/#enable-usermedia-screen-capture' must be enabled. - */ -function obtainWebRTCScreen(streamCallback, failCallback) { - APP.RTC.getUserMediaWithConstraints( - ['screen'], - streamCallback, - failCallback - ); +function streamSwitchDone() { + switchInProgress = false; + eventEmitter.emit( + DesktopSharingEventTypes.SWITCHING_DONE, + isUsingScreenStream); } -/** - * Constructs inline install URL for Chrome desktop streaming extension. - * The 'chromeExtensionId' must be defined in config.js. - * @returns {string} - */ -function getWebStoreInstallUrl() -{ - return "https://chrome.google.com/webstore/detail/" + - config.chromeExtensionId; -} - -/** - * Checks whether extension update is required. - * @param minVersion minimal required version - * @param extVersion current extension version - * @returns {boolean} - */ -function isUpdateRequired(minVersion, extVersion) -{ - try - { - var s1 = minVersion.split('.'); - var s2 = extVersion.split('.'); - - var len = Math.max(s1.length, s2.length); - for (var i = 0; i < len; i++) - { - var n1 = 0, - n2 = 0; - - if (i < s1.length) - n1 = parseInt(s1[i]); - if (i < s2.length) - n2 = parseInt(s2[i]); - - if (isNaN(n1) || isNaN(n2)) - { - return true; - } - else if (n1 !== n2) - { - return n1 > n2; - } - } - - // will happen if boths version has identical numbers in - // their components (even if one of them is longer, has more components) - return false; - } - catch (e) - { - console.error("Failed to parse extension version", e); - APP.UI.messageHandler.showError("dialog.error", - "dialog.detectext"); - return true; - } -} - -function checkChromeExtInstalled(callback) { - if (!chrome.runtime) { - // No API, so no extension for sure - callback(false, false); - return; - } - chrome.runtime.sendMessage( - config.chromeExtensionId, - { getVersion: true }, - function (response) { - if (!response || !response.version) { - // Communication failure - assume that no endpoint exists - console.warn( - "Extension not installed?: ", chrome.runtime.lastError); - callback(false, false); - return; - } - // Check installed extension version - var extVersion = response.version; - console.log('Extension version is: ' + extVersion); - var updateRequired - = isUpdateRequired(config.minChromeExtVersion, extVersion); - callback(!updateRequired, updateRequired); - } - ); -} - -function doGetStreamFromExtension(streamCallback, failCallback) { - // Sends 'getStream' msg to the extension. - // Extension id must be defined in the config. - chrome.runtime.sendMessage( - config.chromeExtensionId, - { getStream: true, sources: config.desktopSharingSources }, - function (response) { - if (!response) { - failCallback(chrome.runtime.lastError); - return; - } - console.log("Response from extension: " + response); - if (response.streamId) { - APP.RTC.getUserMediaWithConstraints( - ['desktop'], - function (stream) { - streamCallback(stream); - }, - failCallback, - null, null, null, - response.streamId); - } else { - failCallback("Extension failed to get the stream"); - } - } - ); -} -/** - * Asks Chrome extension to call chooseDesktopMedia and gets chrome 'desktop' - * stream for returned stream token. - */ -function obtainScreenFromExtension(streamCallback, failCallback) { - if (extInstalled) { - doGetStreamFromExtension(streamCallback, failCallback); - } else { - if (extUpdateRequired) { - alert( - 'Jitsi Desktop Streamer requires update. ' + - 'Changes will take effect after next Chrome restart.'); - } - - chrome.webstore.install( - getWebStoreInstallUrl(), - function (arg) { - console.log("Extension installed successfully", arg); - extInstalled = true; - // We need to give a moment for the endpoint to become available - window.setTimeout(function () { - doGetStreamFromExtension(streamCallback, failCallback); - }, 500); - }, - function (arg) { - console.log("Failed to install the extension", arg); - failCallback(arg); - APP.UI.messageHandler.showError("dialog.error", - "dialog.failtoinstall"); - } - ); - } -} - -/** - * Call this method to toggle desktop sharing feature. - * @param method pass "ext" to use chrome extension for desktop capture(chrome - * extension required), pass "webrtc" to use WebRTC "screen" desktop - * source('chrome://flags/#enable-usermedia-screen-capture' must be - * enabled), pass any other string or nothing in order to disable this - * feature completely. - */ -function setDesktopSharing(method) { - - obtainDesktopStream = null; - - // When TemasysWebRTC plugin is used we always use getUserMedia, so we don't - // care about 'method' parameter - if (RTCBrowserType.isTemasysPluginUsed()) { - if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) { - console.info("Screensharing not supported by this plugin version"); - } else if (!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) { - console.info( - "Screensharing not available with Temasys plugin on this site"); - } else { - obtainDesktopStream = obtainWebRTCScreen; - console.info("Using Temasys plugin for desktop sharing"); - } - } else if (RTCBrowserType.isChrome()) { - if (method == "ext") { - if (RTCBrowserType.getChromeVersion() >= 34) { - obtainDesktopStream = obtainScreenFromExtension; - console.info("Using Chrome extension for desktop sharing"); - initChromeExtension(); - } else { - console.info("Chrome extension not supported until ver 34"); - } - } else if (method == "webrtc") { - obtainDesktopStream = obtainWebRTCScreen; - console.info("Using Chrome WebRTC for desktop sharing"); - } - } else if (RTCBrowserType.isFirefox()) { - obtainDesktopStream = obtainWebRTCScreen; - } - - if (!obtainDesktopStream) { - console.info("Desktop sharing disabled"); - } -} - -/** - * Initializes with extension id set in - * config.js to support inline installs. Host site must be selected as main - * website of published extension. - */ -function initInlineInstalls() -{ - $("link[rel=chrome-webstore-item]").attr("href", getWebStoreInstallUrl()); -} - -function initChromeExtension() { - // Initialize Chrome extension inline installs - initInlineInstalls(); - // Check if extension is installed - checkChromeExtInstalled(function (installed, updateRequired) { - extInstalled = installed; - extUpdateRequired = updateRequired; - console.info( - "Chrome extension installed: " + extInstalled + - " updateRequired: " + extUpdateRequired); - }); +function newStreamCreated(stream) { + eventEmitter.emit(DesktopSharingEventTypes.NEW_STREAM_CREATED, + stream, isUsingScreenStream, streamSwitchDone); } function getVideoStreamFailed(error) { @@ -279,19 +50,6 @@ function getDesktopStreamFailed(error) { switchInProgress = false; } -function streamSwitchDone() { - switchInProgress = false; - eventEmitter.emit( - DesktopSharingEventTypes.SWITCHING_DONE, - isUsingScreenStream); -} - -function newStreamCreated(stream) -{ - eventEmitter.emit(DesktopSharingEventTypes.NEW_STREAM_CREATED, - stream, isUsingScreenStream, streamSwitchDone); -} - function onEndedHandler(stream) { if (!switchInProgress && isUsingScreenStream) { APP.desktopsharing.toggleScreenSharing(); @@ -306,9 +64,7 @@ function onEndedHandler(stream) { // Called when RTC finishes initialization function onWebRtcReady() { - - setDesktopSharing(config.desktopSharing); - + screenObtainer.init(); eventEmitter.emit(DesktopSharingEventTypes.INIT); } @@ -321,7 +77,9 @@ module.exports = { * @returns {boolean} true if desktop sharing feature is available * and enabled. */ - isDesktopSharingEnabled: function () { return !!obtainDesktopStream; }, + isDesktopSharingEnabled: function () { + return screenObtainer.isSupported(); + }, init: function () { APP.RTC.addListener(RTCEvents.RTC_READY, onWebRtcReady); @@ -339,16 +97,18 @@ module.exports = { * Toggles screen sharing. */ toggleScreenSharing: function () { - if (switchInProgress || !obtainDesktopStream) { - console.warn("Switch in progress or no method defined"); + if (switchInProgress) { + console.warn("Switch in progress."); + return; + } else if (!screenObtainer.isSupported()) { + console.warn("Cannot toggle screen sharing: not supported."); return; } switchInProgress = true; - if (!isUsingScreenStream) - { + if (!isUsingScreenStream) { // Switch to desktop stream - obtainDesktopStream( + screenObtainer.obtainStream( function (stream) { // We now use screen stream isUsingScreenStream = true;