Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

ScreenObtainer.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /* global chrome, $, alert */
  2. /* jshint -W003 */
  3. var logger = require("jitsi-meet-logger").getLogger(__filename);
  4. var RTCBrowserType = require("./RTCBrowserType");
  5. var AdapterJS = require("./adapter.screenshare");
  6. var JitsiTrackErrors = require("../../JitsiTrackErrors");
  7. /**
  8. * Indicates whether the Chrome desktop sharing extension is installed.
  9. * @type {boolean}
  10. */
  11. var chromeExtInstalled = false;
  12. /**
  13. * Indicates whether an update of the Chrome desktop sharing extension is
  14. * required.
  15. * @type {boolean}
  16. */
  17. var chromeExtUpdateRequired = false;
  18. /**
  19. * Whether the jidesha extension for firefox is installed for the domain on
  20. * which we are running. Null designates an unknown value.
  21. * @type {null}
  22. */
  23. var firefoxExtInstalled = null;
  24. /**
  25. * If set to true, detection of an installed firefox extension will be started
  26. * again the next time obtainScreenOnFirefox is called (e.g. next time the
  27. * user tries to enable screen sharing).
  28. */
  29. var reDetectFirefoxExtension = false;
  30. var GUM = null;
  31. /**
  32. * Handles obtaining a stream from a screen capture on different browsers.
  33. */
  34. var ScreenObtainer = {
  35. obtainStream: null,
  36. /**
  37. * Initializes the function used to obtain a screen capture
  38. * (this.obtainStream).
  39. *
  40. * If the browser is Chrome, it uses the value of
  41. * 'options.desktopSharingChromeMethod' (or 'options.desktopSharing') to
  42. * decide whether to use the a Chrome extension (if the value is 'ext'),
  43. * use the "screen" media source (if the value is 'webrtc'),
  44. * or disable screen capture (if the value is other).
  45. * Note that for the "screen" media source to work the
  46. * 'chrome://flags/#enable-usermedia-screen-capture' flag must be set.
  47. */
  48. init: function(options, gum) {
  49. var obtainDesktopStream = null;
  50. this.options = options = options || {};
  51. GUM = gum;
  52. if (RTCBrowserType.isFirefox())
  53. initFirefoxExtensionDetection(options);
  54. // TODO remove this, options.desktopSharing is deprecated.
  55. var chromeMethod =
  56. (options.desktopSharingChromeMethod || options.desktopSharing);
  57. if (RTCBrowserType.isTemasysPluginUsed()) {
  58. if (!AdapterJS.WebRTCPlugin.plugin.HasScreensharingFeature) {
  59. logger.info("Screensharing not supported by this plugin " +
  60. "version");
  61. } else if(!AdapterJS.WebRTCPlugin.plugin.isScreensharingAvailable) {
  62. logger.info(
  63. "Screensharing not available with Temasys plugin on" +
  64. " this site");
  65. } else {
  66. obtainDesktopStream = obtainWebRTCScreen;
  67. logger.info("Using Temasys plugin for desktop sharing");
  68. }
  69. } else if (RTCBrowserType.isChrome()) {
  70. if (chromeMethod == "ext") {
  71. if (RTCBrowserType.getChromeVersion() >= 34) {
  72. obtainDesktopStream =
  73. this.obtainScreenFromExtension;
  74. logger.info("Using Chrome extension for desktop sharing");
  75. initChromeExtension(options);
  76. } else {
  77. logger.info("Chrome extension not supported until ver 34");
  78. }
  79. } else if (chromeMethod == "webrtc") {
  80. obtainDesktopStream = obtainWebRTCScreen;
  81. logger.info("Using Chrome WebRTC for desktop sharing");
  82. }
  83. } else if (RTCBrowserType.isFirefox()) {
  84. if (options.desktopSharingFirefoxDisabled) {
  85. obtainDesktopStream = null;
  86. } else if (window.location.protocol === "http:"){
  87. logger.log("Screen sharing is not supported over HTTP. " +
  88. "Use of HTTPS is required.");
  89. obtainDesktopStream = null;
  90. } else {
  91. obtainDesktopStream = this.obtainScreenOnFirefox;
  92. }
  93. }
  94. if (!obtainDesktopStream) {
  95. logger.info("Desktop sharing disabled");
  96. }
  97. this.obtainStream = obtainDesktopStream;
  98. },
  99. /**
  100. * Checks whether obtaining a screen capture is supported in the current
  101. * environment.
  102. * @returns {boolean}
  103. */
  104. isSupported: function() {
  105. return !!this.obtainStream;
  106. },
  107. /**
  108. * Obtains a screen capture stream on Firefox.
  109. * @param callback
  110. * @param errorCallback
  111. */
  112. obtainScreenOnFirefox:
  113. function (callback, errorCallback) {
  114. var self = this;
  115. var extensionRequired = false;
  116. if (this.options.desktopSharingFirefoxMaxVersionExtRequired === -1 ||
  117. (this.options.desktopSharingFirefoxMaxVersionExtRequired >= 0 &&
  118. RTCBrowserType.getFirefoxVersion() <=
  119. this.options.desktopSharingFirefoxMaxVersionExtRequired)) {
  120. extensionRequired = true;
  121. logger.log("Jidesha extension required on firefox version " +
  122. RTCBrowserType.getFirefoxVersion());
  123. }
  124. if (!extensionRequired || firefoxExtInstalled === true) {
  125. obtainWebRTCScreen(callback, errorCallback);
  126. return;
  127. }
  128. if (reDetectFirefoxExtension) {
  129. reDetectFirefoxExtension = false;
  130. initFirefoxExtensionDetection(this.options);
  131. }
  132. // Give it some (more) time to initialize, and assume lack of
  133. // extension if it hasn't.
  134. if (firefoxExtInstalled === null) {
  135. window.setTimeout(
  136. function() {
  137. if (firefoxExtInstalled === null)
  138. firefoxExtInstalled = false;
  139. self.obtainScreenOnFirefox(callback, errorCallback);
  140. },
  141. 300
  142. );
  143. logger.log("Waiting for detection of jidesha on firefox to " +
  144. "finish.");
  145. return;
  146. }
  147. // We need an extension and it isn't installed.
  148. // Make sure we check for the extension when the user clicks again.
  149. firefoxExtInstalled = null;
  150. reDetectFirefoxExtension = true;
  151. // Make sure desktopsharing knows that we failed, so that it doesn't get
  152. // stuck in 'switching' mode.
  153. errorCallback({
  154. type: "jitsiError",
  155. errorObject: JitsiTrackErrors.FIREFOX_EXTENSION_NEEDED
  156. });
  157. },
  158. /**
  159. * Asks Chrome extension to call chooseDesktopMedia and gets chrome
  160. * 'desktop' stream for returned stream token.
  161. */
  162. obtainScreenFromExtension: function (streamCallback, failCallback) {
  163. var self = this;
  164. if (chromeExtInstalled) {
  165. doGetStreamFromExtension(this.options, streamCallback,
  166. failCallback);
  167. } else {
  168. if (chromeExtUpdateRequired) {
  169. alert(
  170. 'Jitsi Desktop Streamer requires update. ' +
  171. 'Changes will take effect after next Chrome restart.');
  172. }
  173. try {
  174. chrome.webstore.install(
  175. getWebStoreInstallUrl(this.options),
  176. function (arg) {
  177. logger.log("Extension installed successfully", arg);
  178. chromeExtInstalled = true;
  179. // We need to give a moment for the endpoint to become
  180. // available
  181. window.setTimeout(function () {
  182. doGetStreamFromExtension(self.options,
  183. streamCallback, failCallback);
  184. }, 500);
  185. },
  186. function (arg) {
  187. logger.log("Failed to install the extension from:"
  188. + getWebStoreInstallUrl(self.options), arg);
  189. failCallback({
  190. type: "jitsiError",
  191. errorObject: JitsiTrackErrors
  192. .CHROME_EXTENSION_INSTALLATION_ERROR
  193. });
  194. }
  195. );
  196. } catch(e) {
  197. logger.log("Failed to install the extension from:"
  198. + self.getWebStoreInstallUrl(this.options), arg);
  199. failCallback({
  200. type: "jitsiError",
  201. errorObject:
  202. JitsiTrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
  203. });
  204. }
  205. }
  206. }
  207. };
  208. /**
  209. * Obtains a desktop stream using getUserMedia.
  210. * For this to work on Chrome, the
  211. * 'chrome://flags/#enable-usermedia-screen-capture' flag must be enabled.
  212. *
  213. * On firefox, the document's domain must be white-listed in the
  214. * 'media.getusermedia.screensharing.allowed_domains' preference in
  215. * 'about:config'.
  216. */
  217. function obtainWebRTCScreen(streamCallback, failCallback) {
  218. GUM(
  219. ['screen'],
  220. streamCallback,
  221. failCallback
  222. );
  223. }
  224. /**
  225. * Constructs inline install URL for Chrome desktop streaming extension.
  226. * The 'chromeExtensionId' must be defined in options parameter.
  227. * @param options supports "desktopSharingChromeExtId" and "chromeExtensionId"
  228. * @returns {string}
  229. */
  230. function getWebStoreInstallUrl(options)
  231. {
  232. //TODO remove chromeExtensionId (deprecated)
  233. return "https://chrome.google.com/webstore/detail/" +
  234. (options.desktopSharingChromeExtId || options.chromeExtensionId);
  235. }
  236. /**
  237. * Checks whether an update of the Chrome extension is required.
  238. * @param minVersion minimal required version
  239. * @param extVersion current extension version
  240. * @returns {boolean}
  241. */
  242. function isUpdateRequired(minVersion, extVersion) {
  243. try {
  244. var s1 = minVersion.split('.');
  245. var s2 = extVersion.split('.');
  246. var len = Math.max(s1.length, s2.length);
  247. for (var i = 0; i < len; i++) {
  248. var n1 = 0,
  249. n2 = 0;
  250. if (i < s1.length)
  251. n1 = parseInt(s1[i]);
  252. if (i < s2.length)
  253. n2 = parseInt(s2[i]);
  254. if (isNaN(n1) || isNaN(n2)) {
  255. return true;
  256. } else if (n1 !== n2) {
  257. return n1 > n2;
  258. }
  259. }
  260. // will happen if both versions have identical numbers in
  261. // their components (even if one of them is longer, has more components)
  262. return false;
  263. }
  264. catch (e) {
  265. logger.error("Failed to parse extension version", e);
  266. return true;
  267. }
  268. }
  269. function checkChromeExtInstalled(callback, options) {
  270. if (!chrome || !chrome.runtime) {
  271. // No API, so no extension for sure
  272. callback(false, false);
  273. return;
  274. }
  275. chrome.runtime.sendMessage(
  276. //TODO: remove chromeExtensionId (deprecated)
  277. (options.desktopSharingChromeExtId || options.chromeExtensionId),
  278. { getVersion: true },
  279. function (response) {
  280. if (!response || !response.version) {
  281. // Communication failure - assume that no endpoint exists
  282. logger.warn(
  283. "Extension not installed?: ", chrome.runtime.lastError);
  284. callback(false, false);
  285. return;
  286. }
  287. // Check installed extension version
  288. var extVersion = response.version;
  289. logger.log('Extension version is: ' + extVersion);
  290. //TODO: remove minChromeExtVersion (deprecated)
  291. var updateRequired
  292. = isUpdateRequired(
  293. (options.desktopSharingChromeMinExtVersion ||
  294. options.minChromeExtVersion),
  295. extVersion);
  296. callback(!updateRequired, updateRequired);
  297. }
  298. );
  299. }
  300. function doGetStreamFromExtension(options, streamCallback, failCallback) {
  301. // Sends 'getStream' msg to the extension.
  302. // Extension id must be defined in the config.
  303. chrome.runtime.sendMessage(
  304. //TODO: remove chromeExtensionId (deprecated)
  305. (options.desktopSharingChromeExtId || options.chromeExtensionId),
  306. {
  307. getStream: true,
  308. //TODO: remove desktopSharingSources (deprecated).
  309. sources: (options.desktopSharingChromeSources ||
  310. options.desktopSharingSources)
  311. },
  312. function (response) {
  313. if (!response) {
  314. failCallback(chrome.runtime.lastError);
  315. return;
  316. }
  317. logger.log("Response from extension: ", response);
  318. if (response.streamId) {
  319. GUM(
  320. ['desktop'],
  321. function (stream) {
  322. streamCallback(stream);
  323. },
  324. failCallback,
  325. {desktopStream: response.streamId});
  326. } else {
  327. // As noted in Chrome Desktop Capture API:
  328. // If user didn't select any source (i.e. canceled the prompt)
  329. // then the callback is called with an empty streamId.
  330. if(response.streamId === "")
  331. {
  332. failCallback({
  333. type: "jitsiError",
  334. errorObject:
  335. JitsiTrackErrors.CHROME_EXTENSION_USER_CANCELED
  336. });
  337. return;
  338. }
  339. failCallback("Extension failed to get the stream");
  340. }
  341. }
  342. );
  343. }
  344. /**
  345. * Initializes <link rel=chrome-webstore-item /> with extension id set in
  346. * config.js to support inline installs. Host site must be selected as main
  347. * website of published extension.
  348. * @param options supports "desktopSharingChromeExtId" and "chromeExtensionId"
  349. */
  350. function initInlineInstalls(options)
  351. {
  352. if($("link[rel=chrome-webstore-item]").length === 0) {
  353. $("head").append("<link rel=\"chrome-webstore-item\">");
  354. }
  355. $("link[rel=chrome-webstore-item]").attr("href",
  356. getWebStoreInstallUrl(options));
  357. }
  358. function initChromeExtension(options) {
  359. // Initialize Chrome extension inline installs
  360. initInlineInstalls(options);
  361. // Check if extension is installed
  362. checkChromeExtInstalled(function (installed, updateRequired) {
  363. chromeExtInstalled = installed;
  364. chromeExtUpdateRequired = updateRequired;
  365. logger.info(
  366. "Chrome extension installed: " + chromeExtInstalled +
  367. " updateRequired: " + chromeExtUpdateRequired);
  368. }, options);
  369. }
  370. /**
  371. * Starts the detection of an installed jidesha extension for firefox.
  372. * @param options supports "desktopSharingFirefoxDisabled",
  373. * "desktopSharingFirefoxExtId" and "chromeExtensionId"
  374. */
  375. function initFirefoxExtensionDetection(options) {
  376. if (options.desktopSharingFirefoxDisabled) {
  377. return;
  378. }
  379. if (firefoxExtInstalled === false || firefoxExtInstalled === true)
  380. return;
  381. if (!options.desktopSharingFirefoxExtId) {
  382. firefoxExtInstalled = false;
  383. return;
  384. }
  385. var img = document.createElement('img');
  386. img.onload = function(){
  387. logger.log("Detected firefox screen sharing extension.");
  388. firefoxExtInstalled = true;
  389. };
  390. img.onerror = function(){
  391. logger.log("Detected lack of firefox screen sharing extension.");
  392. firefoxExtInstalled = false;
  393. };
  394. // The jidesha extension exposes an empty image file under the url:
  395. // "chrome://EXT_ID/content/DOMAIN.png"
  396. // Where EXT_ID is the ID of the extension with "@" replaced by ".", and
  397. // DOMAIN is a domain whitelisted by the extension.
  398. var src = "chrome://" +
  399. (options.desktopSharingFirefoxExtId.replace('@', '.')) +
  400. "/content/" + document.location.hostname + ".png";
  401. img.setAttribute('src', src);
  402. }
  403. module.exports = ScreenObtainer;