|
@@ -72,11 +72,6 @@ let connection;
|
72
|
72
|
let localAudio, localVideo;
|
73
|
73
|
let initialAudioMutedState = false, initialVideoMutedState = false;
|
74
|
74
|
|
75
|
|
-/**
|
76
|
|
- * Indicates whether extension external installation is in progress or not.
|
77
|
|
- */
|
78
|
|
-let DSExternalInstallationInProgress = false;
|
79
|
|
-
|
80
|
75
|
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
|
81
|
76
|
|
82
|
77
|
/*
|
|
@@ -118,68 +113,6 @@ function connect(roomName) {
|
118
|
113
|
});
|
119
|
114
|
}
|
120
|
115
|
|
121
|
|
-/**
|
122
|
|
- * Creates local media tracks and connects to room. Will show error
|
123
|
|
- * dialogs in case if accessing local microphone and/or camera failed. Will
|
124
|
|
- * show guidance overlay for users on how to give access to camera and/or
|
125
|
|
- * microphone,
|
126
|
|
- * @param {string} roomName
|
127
|
|
- * @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
|
128
|
|
- */
|
129
|
|
-function createInitialLocalTracksAndConnect(roomName) {
|
130
|
|
- let audioAndVideoError,
|
131
|
|
- audioOnlyError,
|
132
|
|
- videoOnlyError;
|
133
|
|
-
|
134
|
|
- JitsiMeetJS.mediaDevices.addEventListener(
|
135
|
|
- JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
|
136
|
|
- browser =>
|
137
|
|
- APP.store.dispatch(
|
138
|
|
- mediaPermissionPromptVisibilityChanged(true, browser))
|
139
|
|
- );
|
140
|
|
-
|
141
|
|
- // First try to retrieve both audio and video.
|
142
|
|
- let tryCreateLocalTracks = createLocalTracks(
|
143
|
|
- { devices: ['audio', 'video'] }, true)
|
144
|
|
- .catch(err => {
|
145
|
|
- // If failed then try to retrieve only audio.
|
146
|
|
- audioAndVideoError = err;
|
147
|
|
- return createLocalTracks({ devices: ['audio'] }, true);
|
148
|
|
- })
|
149
|
|
- .catch(err => {
|
150
|
|
- audioOnlyError = err;
|
151
|
|
-
|
152
|
|
- // Try video only...
|
153
|
|
- return createLocalTracks({ devices: ['video'] }, true);
|
154
|
|
- })
|
155
|
|
- .catch(err => {
|
156
|
|
- videoOnlyError = err;
|
157
|
|
-
|
158
|
|
- return [];
|
159
|
|
- });
|
160
|
|
-
|
161
|
|
- return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
162
|
|
- .then(([tracks, con]) => {
|
163
|
|
- APP.store.dispatch(mediaPermissionPromptVisibilityChanged(false));
|
164
|
|
- if (audioAndVideoError) {
|
165
|
|
- if (audioOnlyError) {
|
166
|
|
- // If both requests for 'audio' + 'video' and 'audio' only
|
167
|
|
- // failed, we assume that there is some problems with user's
|
168
|
|
- // microphone and show corresponding dialog.
|
169
|
|
- APP.UI.showDeviceErrorDialog(
|
170
|
|
- audioOnlyError, videoOnlyError);
|
171
|
|
- } else {
|
172
|
|
- // If request for 'audio' + 'video' failed, but request for
|
173
|
|
- // 'audio' only was OK, we assume that we had problems with
|
174
|
|
- // camera and show corresponding dialog.
|
175
|
|
- APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
|
176
|
|
- }
|
177
|
|
- }
|
178
|
|
-
|
179
|
|
- return [tracks, con];
|
180
|
|
- });
|
181
|
|
-}
|
182
|
|
-
|
183
|
116
|
/**
|
184
|
117
|
* Share data to other users.
|
185
|
118
|
* @param command the command
|
|
@@ -567,6 +500,106 @@ export default {
|
567
|
500
|
* Whether the local participant is the dominant speaker in the conference.
|
568
|
501
|
*/
|
569
|
502
|
isDominantSpeaker: false,
|
|
503
|
+
|
|
504
|
+ /**
|
|
505
|
+ * Creates local media tracks and connects to a room. Will show error
|
|
506
|
+ * dialogs in case accessing the local microphone and/or camera failed. Will
|
|
507
|
+ * show guidance overlay for users on how to give access to camera and/or
|
|
508
|
+ * microphone,
|
|
509
|
+ * @param {string} roomName
|
|
510
|
+ * @param {object} options
|
|
511
|
+ * @param {boolean} options.startScreenSharing - if <tt>true</tt> should
|
|
512
|
+ * start with screensharing instead of camera video.
|
|
513
|
+ * @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
|
|
514
|
+ */
|
|
515
|
+ createInitialLocalTracksAndConnect(roomName, options = {}) {
|
|
516
|
+ let audioAndVideoError,
|
|
517
|
+ audioOnlyError,
|
|
518
|
+ screenSharingError,
|
|
519
|
+ videoOnlyError;
|
|
520
|
+
|
|
521
|
+ JitsiMeetJS.mediaDevices.addEventListener(
|
|
522
|
+ JitsiMeetJS.events.mediaDevices.PERMISSION_PROMPT_IS_SHOWN,
|
|
523
|
+ browser =>
|
|
524
|
+ APP.store.dispatch(
|
|
525
|
+ mediaPermissionPromptVisibilityChanged(true, browser))
|
|
526
|
+ );
|
|
527
|
+
|
|
528
|
+ // First try to retrieve both audio and video.
|
|
529
|
+ let tryCreateLocalTracks;
|
|
530
|
+
|
|
531
|
+ // FIXME the logic about trying to go audio only on error is duplicated
|
|
532
|
+ if (options.startScreenSharing) {
|
|
533
|
+ tryCreateLocalTracks = this._createDesktopTrack()
|
|
534
|
+ .then(desktopStream => {
|
|
535
|
+ return createLocalTracks({ devices: ['audio'] }, true)
|
|
536
|
+ .then(([audioStream]) => {
|
|
537
|
+ return [desktopStream, audioStream];
|
|
538
|
+ })
|
|
539
|
+ .catch(error => {
|
|
540
|
+ audioOnlyError = error;
|
|
541
|
+ return [desktopStream];
|
|
542
|
+ });
|
|
543
|
+ }).catch(error => {
|
|
544
|
+ logger.error('Failed to obtain desktop stream', error);
|
|
545
|
+ screenSharingError = error;
|
|
546
|
+ return createLocalTracks({ devices: ['audio'] }, true);
|
|
547
|
+ }).catch(error => {
|
|
548
|
+ audioOnlyError = error;
|
|
549
|
+ return [];
|
|
550
|
+ });
|
|
551
|
+ } else {
|
|
552
|
+ tryCreateLocalTracks = createLocalTracks(
|
|
553
|
+ {devices: ['audio', 'video']}, true)
|
|
554
|
+ .catch(err => {
|
|
555
|
+ // If failed then try to retrieve only audio.
|
|
556
|
+ audioAndVideoError = err;
|
|
557
|
+ return createLocalTracks({devices: ['audio']}, true);
|
|
558
|
+ })
|
|
559
|
+ .catch(err => {
|
|
560
|
+ audioOnlyError = err;
|
|
561
|
+
|
|
562
|
+ // Try video only...
|
|
563
|
+ return createLocalTracks({devices: ['video']}, true);
|
|
564
|
+ })
|
|
565
|
+ .catch(err => {
|
|
566
|
+ videoOnlyError = err;
|
|
567
|
+
|
|
568
|
+ return [];
|
|
569
|
+ });
|
|
570
|
+ }
|
|
571
|
+
|
|
572
|
+ return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
|
573
|
+ .then(([tracks, con]) => {
|
|
574
|
+ APP.store.dispatch(
|
|
575
|
+ mediaPermissionPromptVisibilityChanged(false));
|
|
576
|
+ // FIXME If there will be microphone error it will cover any
|
|
577
|
+ // screensharing dialog, but it's still better than in
|
|
578
|
+ // the reverse order where the screensharing dialog will
|
|
579
|
+ // sometimes be closing the microphone alert ($.prompt.close();
|
|
580
|
+ // is called). Need to figure out dialogs chaining to fix that.
|
|
581
|
+ if (screenSharingError) {
|
|
582
|
+ this._handleScreenSharingError(screenSharingError);
|
|
583
|
+ }
|
|
584
|
+ if (audioAndVideoError || audioOnlyError) {
|
|
585
|
+ if (audioOnlyError || videoOnlyError) {
|
|
586
|
+ // If both requests for 'audio' + 'video' and 'audio'
|
|
587
|
+ // only failed, we assume that there are some problems
|
|
588
|
+ // with user's microphone and show corresponding dialog.
|
|
589
|
+ APP.UI.showDeviceErrorDialog(
|
|
590
|
+ audioOnlyError, videoOnlyError);
|
|
591
|
+ } else {
|
|
592
|
+ // If request for 'audio' + 'video' failed, but request
|
|
593
|
+ // for 'audio' only was OK, we assume that we had
|
|
594
|
+ // problems with camera and show corresponding dialog.
|
|
595
|
+ APP.UI.showDeviceErrorDialog(null, audioAndVideoError);
|
|
596
|
+ }
|
|
597
|
+ }
|
|
598
|
+
|
|
599
|
+ return [tracks, con];
|
|
600
|
+ });
|
|
601
|
+ },
|
|
602
|
+
|
570
|
603
|
/**
|
571
|
604
|
* Open new connection and join to the conference.
|
572
|
605
|
* @param {object} options
|
|
@@ -602,7 +635,10 @@ export default {
|
602
|
635
|
{enableAnalyticsLogging: analytics.isEnabled()}, config)
|
603
|
636
|
).then(() => {
|
604
|
637
|
analytics.init();
|
605
|
|
- return createInitialLocalTracksAndConnect(options.roomName);
|
|
638
|
+ return this.createInitialLocalTracksAndConnect(
|
|
639
|
+ options.roomName, {
|
|
640
|
+ startScreenSharing: config.startScreenSharing
|
|
641
|
+ });
|
606
|
642
|
}).then(([tracks, con]) => {
|
607
|
643
|
tracks.forEach(track => {
|
608
|
644
|
if((track.isAudioTrack() && initialAudioMutedState)
|
|
@@ -1215,7 +1251,6 @@ export default {
|
1215
|
1251
|
|
1216
|
1252
|
/**
|
1217
|
1253
|
* Toggles between screensharing and camera video.
|
1218
|
|
- * @param {boolean} [shareScreen]
|
1219
|
1254
|
* @param {Object} [options] - Screen sharing options that will be passed to
|
1220
|
1255
|
* createLocalTracks.
|
1221
|
1256
|
* @param {Array<string>} [options.desktopSharingSources] - Array with the
|
|
@@ -1239,118 +1274,206 @@ export default {
|
1239
|
1274
|
}
|
1240
|
1275
|
|
1241
|
1276
|
if (!this._untoggleScreenSharing) {
|
1242
|
|
- this.videoSwitchInProgress = true;
|
1243
|
|
- let externalInstallation = false;
|
1244
|
|
- const didHaveVideo = Boolean(localVideo);
|
1245
|
|
- const wasVideoMuted = this.videoMuted;
|
1246
|
|
-
|
1247
|
|
- return createLocalTracks({
|
1248
|
|
- desktopSharingSources: options.desktopSharingSources,
|
1249
|
|
- devices: ['desktop'],
|
1250
|
|
- desktopSharingExtensionExternalInstallation: {
|
1251
|
|
- interval: 500,
|
1252
|
|
- checkAgain: () => {
|
1253
|
|
- return DSExternalInstallationInProgress;
|
1254
|
|
- },
|
1255
|
|
- listener: (status, url) => {
|
1256
|
|
- switch(status) {
|
1257
|
|
- case "waitingForExtension":
|
1258
|
|
- DSExternalInstallationInProgress = true;
|
1259
|
|
- externalInstallation = true;
|
1260
|
|
- APP.UI.showExtensionExternalInstallationDialog(
|
1261
|
|
- url);
|
1262
|
|
- break;
|
1263
|
|
- case "extensionFound":
|
1264
|
|
- if(externalInstallation) //close the dialog
|
1265
|
|
- $.prompt.close();
|
1266
|
|
- break;
|
1267
|
|
- default:
|
1268
|
|
- //Unknown status
|
|
1277
|
+ return this._switchToScreenSharing(options);
|
|
1278
|
+ } else {
|
|
1279
|
+ return this._untoggleScreenSharing();
|
|
1280
|
+ }
|
|
1281
|
+ },
|
|
1282
|
+
|
|
1283
|
+ /**
|
|
1284
|
+ * Creates desktop (screensharing) {@link JitsiLocalTrack}
|
|
1285
|
+ * @param {Object} [options] - Screen sharing options that will be passed to
|
|
1286
|
+ * createLocalTracks.
|
|
1287
|
+ *
|
|
1288
|
+ * @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
|
|
1289
|
+ * {@link JitsiLocalTrack} for the screensharing or rejected with
|
|
1290
|
+ * {@link JitsiTrackError}.
|
|
1291
|
+ *
|
|
1292
|
+ * @private
|
|
1293
|
+ */
|
|
1294
|
+ _createDesktopTrack(options = {}) {
|
|
1295
|
+ let externalInstallation = false;
|
|
1296
|
+ let DSExternalInstallationInProgress = false;
|
|
1297
|
+ const didHaveVideo = Boolean(localVideo);
|
|
1298
|
+ const wasVideoMuted = this.videoMuted;
|
|
1299
|
+
|
|
1300
|
+ return createLocalTracks({
|
|
1301
|
+ desktopSharingSources: options.desktopSharingSources,
|
|
1302
|
+ devices: ['desktop'],
|
|
1303
|
+ desktopSharingExtensionExternalInstallation: {
|
|
1304
|
+ interval: 500,
|
|
1305
|
+ checkAgain: () => {
|
|
1306
|
+ return DSExternalInstallationInProgress;
|
|
1307
|
+ },
|
|
1308
|
+ listener: (status, url) => {
|
|
1309
|
+ switch(status) {
|
|
1310
|
+ case "waitingForExtension": {
|
|
1311
|
+ DSExternalInstallationInProgress = true;
|
|
1312
|
+ externalInstallation = true;
|
|
1313
|
+ const listener = () => {
|
|
1314
|
+ // Wait a little bit more just to be sure that
|
|
1315
|
+ // we won't miss the extension installation
|
|
1316
|
+ setTimeout(
|
|
1317
|
+ () => {
|
|
1318
|
+ DSExternalInstallationInProgress = false;
|
|
1319
|
+ }, 500);
|
|
1320
|
+ APP.UI.removeListener(
|
|
1321
|
+ UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
|
1322
|
+ listener);
|
|
1323
|
+ };
|
|
1324
|
+ APP.UI.addListener(
|
|
1325
|
+ UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
|
1326
|
+ listener);
|
|
1327
|
+ APP.UI.showExtensionExternalInstallationDialog(url);
|
|
1328
|
+ break;
|
|
1329
|
+ }
|
|
1330
|
+ case "extensionFound": {
|
|
1331
|
+ if (externalInstallation) { //close the dialog
|
|
1332
|
+ $.prompt.close();
|
|
1333
|
+ }
|
|
1334
|
+ break;
|
|
1335
|
+ }
|
|
1336
|
+ default: {
|
|
1337
|
+ //Unknown status
|
1269
|
1338
|
}
|
1270
|
1339
|
}
|
1271
|
1340
|
}
|
1272
|
|
- }).then(([stream]) => {
|
1273
|
|
- // Stores the "untoggle" handler which remembers whether was
|
1274
|
|
- // there any video before and whether was it muted.
|
1275
|
|
- this._untoggleScreenSharing
|
1276
|
|
- = this._turnScreenSharingOff
|
1277
|
|
- .bind(this, didHaveVideo, wasVideoMuted);
|
1278
|
|
- DSExternalInstallationInProgress = false;
|
1279
|
|
- // close external installation dialog on success.
|
1280
|
|
- if(externalInstallation)
|
1281
|
|
- $.prompt.close();
|
1282
|
|
- stream.on(
|
1283
|
|
- TrackEvents.LOCAL_TRACK_STOPPED,
|
1284
|
|
- () => {
|
1285
|
|
- // If the stream was stopped during screen sharing
|
1286
|
|
- // session then we should switch back to video.
|
1287
|
|
- if (this.isSharingScreen){
|
1288
|
|
- this._untoggleScreenSharing
|
1289
|
|
- && this._untoggleScreenSharing();
|
1290
|
|
- }
|
|
1341
|
+ }
|
|
1342
|
+ }).then(([desktopStream]) => {
|
|
1343
|
+ // Stores the "untoggle" handler which remembers whether was
|
|
1344
|
+ // there any video before and whether was it muted.
|
|
1345
|
+ this._untoggleScreenSharing
|
|
1346
|
+ = this._turnScreenSharingOff
|
|
1347
|
+ .bind(this, didHaveVideo, wasVideoMuted);
|
|
1348
|
+ desktopStream.on(
|
|
1349
|
+ TrackEvents.LOCAL_TRACK_STOPPED,
|
|
1350
|
+ () => {
|
|
1351
|
+ // If the stream was stopped during screen sharing
|
|
1352
|
+ // session then we should switch back to video.
|
|
1353
|
+ if (this.isSharingScreen) {
|
|
1354
|
+ this._untoggleScreenSharing
|
|
1355
|
+ && this._untoggleScreenSharing();
|
1291
|
1356
|
}
|
1292
|
|
- );
|
1293
|
|
- return this.useVideoStream(stream);
|
1294
|
|
- }).then(() => {
|
1295
|
|
- this.videoSwitchInProgress = false;
|
1296
|
|
- JitsiMeetJS.analytics.sendEvent(
|
1297
|
|
- 'conference.sharingDesktop.start');
|
1298
|
|
- logger.log('sharing local desktop');
|
1299
|
|
- }).catch(err => {
|
1300
|
|
- // close external installation dialog to show the error.
|
1301
|
|
- if(externalInstallation)
|
1302
|
|
- $.prompt.close();
|
1303
|
|
- this.videoSwitchInProgress = false;
|
1304
|
|
-
|
1305
|
|
- if (err.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
|
1306
|
|
- return Promise.reject(err);
|
1307
|
1357
|
}
|
|
1358
|
+ );
|
|
1359
|
+ // close external installation dialog on success.
|
|
1360
|
+ if (externalInstallation) {
|
|
1361
|
+ $.prompt.close();
|
|
1362
|
+ }
|
|
1363
|
+ return desktopStream;
|
|
1364
|
+ }, error => {
|
|
1365
|
+ DSExternalInstallationInProgress = false;
|
|
1366
|
+ // close external installation dialog on success.
|
|
1367
|
+ if (externalInstallation) {
|
|
1368
|
+ $.prompt.close();
|
|
1369
|
+ }
|
|
1370
|
+ throw error;
|
|
1371
|
+ });
|
|
1372
|
+ },
|
1308
|
1373
|
|
1309
|
|
- // Pawel: With this call I'm trying to preserve the original
|
1310
|
|
- // behaviour although it is not clear why would we "untoggle"
|
1311
|
|
- // on failure. I suppose it was to restore video in case there
|
1312
|
|
- // was some problem during "this.useVideoStream(desktopStream)".
|
1313
|
|
- // It's important to note that the handler will not be available
|
1314
|
|
- // if we fail early on trying to get desktop media (which makes
|
1315
|
|
- // sense, because the camera video is still being used, so
|
1316
|
|
- // nothing to "untoggle").
|
1317
|
|
- if (this._untoggleScreenSharing) {
|
1318
|
|
- this._untoggleScreenSharing();
|
1319
|
|
- }
|
|
1374
|
+ /**
|
|
1375
|
+ * Tries to switch to the screenshairng mode by disposing camera stream and
|
|
1376
|
+ * replacing it with a desktop one.
|
|
1377
|
+ *
|
|
1378
|
+ * @param {Object} [options] - Screen sharing options that will be passed to
|
|
1379
|
+ * createLocalTracks.
|
|
1380
|
+ *
|
|
1381
|
+ * @return {Promise} - A Promise resolved if the operation succeeds or
|
|
1382
|
+ * rejected with some unknown type of error in case it fails. Promise will
|
|
1383
|
+ * be rejected immediately if {@link videoSwitchInProgress} is true.
|
|
1384
|
+ *
|
|
1385
|
+ * @private
|
|
1386
|
+ */
|
|
1387
|
+ _switchToScreenSharing(options = {}) {
|
|
1388
|
+ if (this.videoSwitchInProgress) {
|
|
1389
|
+ return Promise.reject('Switch in progress.');
|
|
1390
|
+ }
|
1320
|
1391
|
|
1321
|
|
- logger.error('failed to share local desktop', err);
|
|
1392
|
+ this.videoSwitchInProgress = true;
|
|
1393
|
+ return this._createDesktopTrack(options).then(stream => {
|
|
1394
|
+ return this.useVideoStream(stream);
|
|
1395
|
+ }).then(() => {
|
|
1396
|
+ this.videoSwitchInProgress = false;
|
|
1397
|
+ JitsiMeetJS.analytics.sendEvent('conference.sharingDesktop.start');
|
|
1398
|
+ logger.log('sharing local desktop');
|
|
1399
|
+ }).catch(error => {
|
|
1400
|
+ this.videoSwitchInProgress = false;
|
|
1401
|
+ // Pawel: With this call I'm trying to preserve the original
|
|
1402
|
+ // behaviour although it is not clear why would we "untoggle"
|
|
1403
|
+ // on failure. I suppose it was to restore video in case there
|
|
1404
|
+ // was some problem during "this.useVideoStream(desktopStream)".
|
|
1405
|
+ // It's important to note that the handler will not be available
|
|
1406
|
+ // if we fail early on trying to get desktop media (which makes
|
|
1407
|
+ // sense, because the camera video is still being used, so
|
|
1408
|
+ // nothing to "untoggle").
|
|
1409
|
+ if (this._untoggleScreenSharing) {
|
|
1410
|
+ this._untoggleScreenSharing();
|
|
1411
|
+ }
|
1322
|
1412
|
|
1323
|
|
- if (err.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
|
1324
|
|
- APP.UI.showExtensionRequiredDialog(
|
1325
|
|
- config.desktopSharingFirefoxExtensionURL
|
1326
|
|
- );
|
1327
|
|
- return Promise.reject(err);
|
1328
|
|
- }
|
|
1413
|
+ // FIXME the code inside of _handleScreenSharingError is
|
|
1414
|
+ // asynchronous, but does not return a Promise and is not part of
|
|
1415
|
+ // the current Promise chain.
|
|
1416
|
+ this._handleScreenSharingError(error);
|
|
1417
|
+ });
|
|
1418
|
+ },
|
1329
|
1419
|
|
1330
|
|
- // Handling:
|
1331
|
|
- // TrackErrors.PERMISSION_DENIED
|
1332
|
|
- // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
|
1333
|
|
- // TrackErrors.GENERAL
|
1334
|
|
- // and any other
|
1335
|
|
- let dialogTxt;
|
1336
|
|
- let dialogTitleKey;
|
1337
|
|
-
|
1338
|
|
- if (err.name === TrackErrors.PERMISSION_DENIED) {
|
1339
|
|
- dialogTxt = APP.translation.generateTranslationHTML(
|
1340
|
|
- "dialog.screenSharingPermissionDeniedError");
|
1341
|
|
- dialogTitleKey = "dialog.error";
|
1342
|
|
- } else {
|
1343
|
|
- dialogTxt = APP.translation.generateTranslationHTML(
|
1344
|
|
- "dialog.failtoinstall");
|
1345
|
|
- dialogTitleKey = "dialog.permissionDenied";
|
|
1420
|
+ /**
|
|
1421
|
+ * Handles {@link JitsiTrackError} returned by the lib-jitsi-meet when
|
|
1422
|
+ * trying to create screensharing track. It will either do nothing if
|
|
1423
|
+ * the dialog was canceled on user's request or display inline installation
|
|
1424
|
+ * dialog and ask the user to install the extension, once the extension is
|
|
1425
|
+ * installed it will switch the conference to screensharing. The last option
|
|
1426
|
+ * is that an unrecoverable error dialog will be displayed.
|
|
1427
|
+ * @param {JitsiTrackError} error - The error returned by
|
|
1428
|
+ * {@link _createDesktopTrack} Promise.
|
|
1429
|
+ * @private
|
|
1430
|
+ */
|
|
1431
|
+ _handleScreenSharingError(error) {
|
|
1432
|
+ if (error.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
|
|
1433
|
+ return;
|
|
1434
|
+ }
|
|
1435
|
+
|
|
1436
|
+ logger.error('failed to share local desktop', error);
|
|
1437
|
+
|
|
1438
|
+ if (error.name === TrackErrors.CHROME_EXTENSION_USER_GESTURE_REQUIRED) {
|
|
1439
|
+ // If start with screen sharing the extension will fail to install
|
|
1440
|
+ // (if not found), because the request has been triggered by the
|
|
1441
|
+ // script. Show a dialog which asks user to click "install" and try
|
|
1442
|
+ // again switching to the screen sharing.
|
|
1443
|
+ APP.UI.showExtensionInlineInstallationDialog(
|
|
1444
|
+ () => {
|
|
1445
|
+ this.toggleScreenSharing();
|
1346
|
1446
|
}
|
|
1447
|
+ );
|
1347
|
1448
|
|
1348
|
|
- APP.UI.messageHandler.openDialog(
|
1349
|
|
- dialogTitleKey, dialogTxt, false);
|
1350
|
|
- });
|
|
1449
|
+ return;
|
|
1450
|
+ } else if (error.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
|
|
1451
|
+ APP.UI.showExtensionRequiredDialog(
|
|
1452
|
+ config.desktopSharingFirefoxExtensionURL
|
|
1453
|
+ );
|
|
1454
|
+
|
|
1455
|
+ return;
|
|
1456
|
+ }
|
|
1457
|
+
|
|
1458
|
+ // Handling:
|
|
1459
|
+ // TrackErrors.PERMISSION_DENIED
|
|
1460
|
+ // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
|
|
1461
|
+ // TrackErrors.GENERAL
|
|
1462
|
+ // and any other
|
|
1463
|
+ let dialogTxt;
|
|
1464
|
+ let dialogTitleKey;
|
|
1465
|
+
|
|
1466
|
+ if (error.name === TrackErrors.PERMISSION_DENIED) {
|
|
1467
|
+ dialogTxt = APP.translation.generateTranslationHTML(
|
|
1468
|
+ "dialog.screenSharingPermissionDeniedError");
|
|
1469
|
+ dialogTitleKey = "dialog.error";
|
1351
|
1470
|
} else {
|
1352
|
|
- return this._untoggleScreenSharing();
|
|
1471
|
+ dialogTxt = APP.translation.generateTranslationHTML(
|
|
1472
|
+ "dialog.failtoinstall");
|
|
1473
|
+ dialogTitleKey = "dialog.permissionDenied";
|
1353
|
1474
|
}
|
|
1475
|
+
|
|
1476
|
+ APP.UI.messageHandler.openDialog(dialogTitleKey, dialogTxt, false);
|
1354
|
1477
|
},
|
1355
|
1478
|
/**
|
1356
|
1479
|
* Setup interaction between conference and UI.
|
|
@@ -1637,17 +1760,6 @@ export default {
|
1637
|
1760
|
APP.UI.updateDTMFSupport(isDTMFSupported);
|
1638
|
1761
|
});
|
1639
|
1762
|
|
1640
|
|
- APP.UI.addListener(UIEvents.EXTERNAL_INSTALLATION_CANCELED, () => {
|
1641
|
|
- // Wait a little bit more just to be sure that we won't miss the
|
1642
|
|
- // extension installation
|
1643
|
|
- setTimeout(() => DSExternalInstallationInProgress = false, 500);
|
1644
|
|
- });
|
1645
|
|
- APP.UI.addListener(UIEvents.OPEN_EXTENSION_STORE, (url) => {
|
1646
|
|
- window.open(
|
1647
|
|
- url, "extension_store_window",
|
1648
|
|
- "resizable,scrollbars=yes,status=1");
|
1649
|
|
- });
|
1650
|
|
-
|
1651
|
1763
|
APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
|
1652
|
1764
|
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
|
1653
|
1765
|
if (this.isAudioOnly() && !muted) {
|