|
@@ -78,11 +78,6 @@ let connection;
|
78
|
78
|
let localAudio, localVideo;
|
79
|
79
|
let initialAudioMutedState = false, initialVideoMutedState = false;
|
80
|
80
|
|
81
|
|
-/**
|
82
|
|
- * Indicates whether extension external installation is in progress or not.
|
83
|
|
- */
|
84
|
|
-let DSExternalInstallationInProgress = false;
|
85
|
|
-
|
86
|
81
|
import {VIDEO_CONTAINER_TYPE} from "./modules/UI/videolayout/VideoContainer";
|
87
|
82
|
|
88
|
83
|
/*
|
|
@@ -498,11 +493,14 @@ export default {
|
498
|
493
|
* show guidance overlay for users on how to give access to camera and/or
|
499
|
494
|
* microphone,
|
500
|
495
|
* @param {string} roomName
|
|
496
|
+ * @param {boolean} startScreenSharing - if <tt>true</tt> should start with
|
|
497
|
+ * screensharing instead of camera video.
|
501
|
498
|
* @returns {Promise.<JitsiLocalTrack[], JitsiConnection>}
|
502
|
499
|
*/
|
503
|
|
- createInitialLocalTracksAndConnect(roomName) {
|
|
500
|
+ createInitialLocalTracksAndConnect(roomName, startScreenSharing) {
|
504
|
501
|
let audioAndVideoError,
|
505
|
502
|
audioOnlyError,
|
|
503
|
+ screenSharingError,
|
506
|
504
|
videoOnlyError;
|
507
|
505
|
|
508
|
506
|
JitsiMeetJS.mediaDevices.addEventListener(
|
|
@@ -513,31 +511,55 @@ export default {
|
513
|
511
|
);
|
514
|
512
|
|
515
|
513
|
// First try to retrieve both audio and video.
|
516
|
|
- let tryCreateLocalTracks = createLocalTracks(
|
517
|
|
- { devices: ['audio', 'video'] }, true)
|
518
|
|
- .catch(err => {
|
519
|
|
- // If failed then try to retrieve only audio.
|
520
|
|
- audioAndVideoError = err;
|
521
|
|
- return createLocalTracks({ devices: ['audio'] }, true);
|
522
|
|
- })
|
523
|
|
- .catch(err => {
|
524
|
|
- audioOnlyError = err;
|
525
|
|
-
|
526
|
|
- // Try video only...
|
527
|
|
- return createLocalTracks({ devices: ['video'] }, true);
|
528
|
|
- })
|
529
|
|
- .catch(err => {
|
530
|
|
- videoOnlyError = err;
|
531
|
|
-
|
532
|
|
- return [];
|
533
|
|
- });
|
|
514
|
+ let tryCreateLocalTracks;
|
|
515
|
+
|
|
516
|
+ // FIXME the logic about trying to go audio only on error is duplicated
|
|
517
|
+ if (startScreenSharing) {
|
|
518
|
+ tryCreateLocalTracks = this._createDesktopTrack()
|
|
519
|
+ .then(desktopStream => {
|
|
520
|
+ return createLocalTracks({ devices: ['audio'] }, true)
|
|
521
|
+ .then(([audioStream]) => {
|
|
522
|
+ return [desktopStream, audioStream];
|
|
523
|
+ })
|
|
524
|
+ .catch(error => {
|
|
525
|
+ audioOnlyError = error;
|
|
526
|
+ return [desktopStream];
|
|
527
|
+ });
|
|
528
|
+ }).catch(error => {
|
|
529
|
+ logger.error('Failed to obtain desktop stream', error);
|
|
530
|
+ screenSharingError = error;
|
|
531
|
+ return createLocalTracks({ devices: ['audio'] }, true);
|
|
532
|
+ }).catch(error => {
|
|
533
|
+ audioOnlyError = error;
|
|
534
|
+ return [];
|
|
535
|
+ });
|
|
536
|
+ } else {
|
|
537
|
+ tryCreateLocalTracks = createLocalTracks(
|
|
538
|
+ {devices: ['audio', 'video']}, true)
|
|
539
|
+ .catch(err => {
|
|
540
|
+ // If failed then try to retrieve only audio.
|
|
541
|
+ audioAndVideoError = err;
|
|
542
|
+ return createLocalTracks({devices: ['audio']}, true);
|
|
543
|
+ })
|
|
544
|
+ .catch(err => {
|
|
545
|
+ audioOnlyError = err;
|
|
546
|
+
|
|
547
|
+ // Try video only...
|
|
548
|
+ return createLocalTracks({devices: ['video']}, true);
|
|
549
|
+ })
|
|
550
|
+ .catch(err => {
|
|
551
|
+ videoOnlyError = err;
|
|
552
|
+
|
|
553
|
+ return [];
|
|
554
|
+ });
|
|
555
|
+ }
|
534
|
556
|
|
535
|
557
|
return Promise.all([ tryCreateLocalTracks, connect(roomName) ])
|
536
|
558
|
.then(([tracks, con]) => {
|
537
|
559
|
APP.store.dispatch(
|
538
|
560
|
mediaPermissionPromptVisibilityChanged(false));
|
539
|
|
- if (audioAndVideoError) {
|
540
|
|
- if (audioOnlyError) {
|
|
561
|
+ if (audioAndVideoError || audioOnlyError) {
|
|
562
|
+ if (audioOnlyError || videoOnlyError) {
|
541
|
563
|
// If both requests for 'audio' + 'video' and 'audio'
|
542
|
564
|
// only failed, we assume that there is some problems
|
543
|
565
|
// with user's microphone and show corresponding dialog.
|
|
@@ -551,6 +573,18 @@ export default {
|
551
|
573
|
}
|
552
|
574
|
}
|
553
|
575
|
|
|
576
|
+ // FIXME If there was a screen sharing error or the extension
|
|
577
|
+ // needs to be installed it will appear on top of eventual
|
|
578
|
+ // "microphone error" dialog. That is not great, but currently
|
|
579
|
+ // it's pretty hard to chain dialogs since they don't return
|
|
580
|
+ // Promises.
|
|
581
|
+ if (screenSharingError) {
|
|
582
|
+ // FIXME if _handleScreenSharingError will be dealing with
|
|
583
|
+ // installing external extension it may close previously
|
|
584
|
+ // opened microphone dialog ($.prompt.close(); is called).
|
|
585
|
+ this._handleScreenSharingError(screenSharingError);
|
|
586
|
+ }
|
|
587
|
+
|
554
|
588
|
return [tracks, con];
|
555
|
589
|
});
|
556
|
590
|
},
|
|
@@ -591,7 +625,8 @@ export default {
|
591
|
625
|
).then(() => {
|
592
|
626
|
analytics.init();
|
593
|
627
|
return this.createInitialLocalTracksAndConnect(
|
594
|
|
- options.roomName);
|
|
628
|
+ options.roomName,
|
|
629
|
+ config.startScreenSharing);
|
595
|
630
|
}).then(([tracks, con]) => {
|
596
|
631
|
tracks.forEach(track => {
|
597
|
632
|
if((track.isAudioTrack() && initialAudioMutedState)
|
|
@@ -1197,7 +1232,6 @@ export default {
|
1197
|
1232
|
|
1198
|
1233
|
/**
|
1199
|
1234
|
* Toggles between screensharing and camera video.
|
1200
|
|
- * @param {boolean} [shareScreen]
|
1201
|
1235
|
* @param {Object} [options] - Screen sharing options that will be passed to
|
1202
|
1236
|
* createLocalTracks.
|
1203
|
1237
|
* @param {Array<string>} [options.desktopSharingSources] - Array with the
|
|
@@ -1221,118 +1255,205 @@ export default {
|
1221
|
1255
|
}
|
1222
|
1256
|
|
1223
|
1257
|
if (!this._untoggleScreenSharing) {
|
1224
|
|
- this.videoSwitchInProgress = true;
|
1225
|
|
- let externalInstallation = false;
|
1226
|
|
- const didHaveVideo = Boolean(localVideo);
|
1227
|
|
- const wasVideoMuted = this.videoMuted;
|
1228
|
|
-
|
1229
|
|
- return createLocalTracks({
|
1230
|
|
- desktopSharingSources: options.desktopSharingSources,
|
1231
|
|
- devices: ['desktop'],
|
1232
|
|
- desktopSharingExtensionExternalInstallation: {
|
1233
|
|
- interval: 500,
|
1234
|
|
- checkAgain: () => {
|
1235
|
|
- return DSExternalInstallationInProgress;
|
1236
|
|
- },
|
1237
|
|
- listener: (status, url) => {
|
1238
|
|
- switch(status) {
|
1239
|
|
- case "waitingForExtension":
|
1240
|
|
- DSExternalInstallationInProgress = true;
|
1241
|
|
- externalInstallation = true;
|
1242
|
|
- APP.UI.showExtensionExternalInstallationDialog(
|
1243
|
|
- url);
|
1244
|
|
- break;
|
1245
|
|
- case "extensionFound":
|
1246
|
|
- if(externalInstallation) //close the dialog
|
1247
|
|
- $.prompt.close();
|
1248
|
|
- break;
|
1249
|
|
- default:
|
1250
|
|
- //Unknown status
|
|
1258
|
+ return this._switchToScreenSharing(options);
|
|
1259
|
+ } else {
|
|
1260
|
+ return this._untoggleScreenSharing();
|
|
1261
|
+ }
|
|
1262
|
+ },
|
|
1263
|
+
|
|
1264
|
+ /**
|
|
1265
|
+ * Creates desktop (screensharing) {@link JitsiLocalTrack}
|
|
1266
|
+ * @param {Object} [options] - Screen sharing options that will be passed to
|
|
1267
|
+ * createLocalTracks.
|
|
1268
|
+ *
|
|
1269
|
+ * @return {Promise.<JitsiLocalTrack>} - A Promise resolved with
|
|
1270
|
+ * {@link JitsiLocalTrack} for the screensharing or rejected with
|
|
1271
|
+ * {@link JitsiTrackError}.
|
|
1272
|
+ *
|
|
1273
|
+ * @private
|
|
1274
|
+ */
|
|
1275
|
+ _createDesktopTrack(options = {}) {
|
|
1276
|
+ let externalInstallation = false;
|
|
1277
|
+ let DSExternalInstallationInProgress = false;
|
|
1278
|
+ const didHaveVideo = Boolean(localVideo);
|
|
1279
|
+ const wasVideoMuted = this.videoMuted;
|
|
1280
|
+
|
|
1281
|
+ return createLocalTracks({
|
|
1282
|
+ desktopSharingSources: options.desktopSharingSources,
|
|
1283
|
+ devices: ['desktop'],
|
|
1284
|
+ desktopSharingExtensionExternalInstallation: {
|
|
1285
|
+ interval: 500,
|
|
1286
|
+ checkAgain: () => {
|
|
1287
|
+ return DSExternalInstallationInProgress;
|
|
1288
|
+ },
|
|
1289
|
+ listener: (status, url) => {
|
|
1290
|
+ switch(status) {
|
|
1291
|
+ case "waitingForExtension": {
|
|
1292
|
+ DSExternalInstallationInProgress = true;
|
|
1293
|
+ externalInstallation = true;
|
|
1294
|
+ const listener = () => {
|
|
1295
|
+ // Wait a little bit more just to be sure that
|
|
1296
|
+ // we won't miss the extension installation
|
|
1297
|
+ setTimeout(
|
|
1298
|
+ () => {
|
|
1299
|
+ DSExternalInstallationInProgress = false;
|
|
1300
|
+ }, 500);
|
|
1301
|
+ APP.UI.removeListener(
|
|
1302
|
+ UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
|
1303
|
+ listener);
|
|
1304
|
+ };
|
|
1305
|
+ APP.UI.addListener(
|
|
1306
|
+ UIEvents.EXTERNAL_INSTALLATION_CANCELED,
|
|
1307
|
+ listener);
|
|
1308
|
+ APP.UI.showExtensionExternalInstallationDialog(url);
|
|
1309
|
+ break;
|
|
1310
|
+ }
|
|
1311
|
+ case "extensionFound": {
|
|
1312
|
+ if (externalInstallation) //close the dialog
|
|
1313
|
+ $.prompt.close();
|
|
1314
|
+ break;
|
|
1315
|
+ }
|
|
1316
|
+ default: {
|
|
1317
|
+ //Unknown status
|
1251
|
1318
|
}
|
1252
|
1319
|
}
|
1253
|
1320
|
}
|
1254
|
|
- }).then(([stream]) => {
|
1255
|
|
- // Stores the "untoggle" handler which remembers whether was
|
1256
|
|
- // there any video before and whether was it muted.
|
1257
|
|
- this._untoggleScreenSharing
|
1258
|
|
- = this._turnScreenSharingOff
|
1259
|
|
- .bind(this, didHaveVideo, wasVideoMuted);
|
1260
|
|
- DSExternalInstallationInProgress = false;
|
1261
|
|
- // close external installation dialog on success.
|
1262
|
|
- if(externalInstallation)
|
1263
|
|
- $.prompt.close();
|
1264
|
|
- stream.on(
|
1265
|
|
- TrackEvents.LOCAL_TRACK_STOPPED,
|
1266
|
|
- () => {
|
1267
|
|
- // If the stream was stopped during screen sharing
|
1268
|
|
- // session then we should switch back to video.
|
1269
|
|
- if (this.isSharingScreen){
|
1270
|
|
- this._untoggleScreenSharing
|
1271
|
|
- && this._untoggleScreenSharing();
|
1272
|
|
- }
|
|
1321
|
+ }
|
|
1322
|
+ }).then(([desktopStream])=> {
|
|
1323
|
+ // Stores the "untoggle" handler which remembers whether was
|
|
1324
|
+ // there any video before and whether was it muted.
|
|
1325
|
+ this._untoggleScreenSharing
|
|
1326
|
+ = this._turnScreenSharingOff
|
|
1327
|
+ .bind(this, didHaveVideo, wasVideoMuted);
|
|
1328
|
+ desktopStream.on(
|
|
1329
|
+ TrackEvents.LOCAL_TRACK_STOPPED,
|
|
1330
|
+ () => {
|
|
1331
|
+ // If the stream was stopped during screen sharing
|
|
1332
|
+ // session then we should switch back to video.
|
|
1333
|
+ if (this.isSharingScreen) {
|
|
1334
|
+ this._untoggleScreenSharing
|
|
1335
|
+ && this._untoggleScreenSharing();
|
1273
|
1336
|
}
|
1274
|
|
- );
|
1275
|
|
- return this.useVideoStream(stream);
|
1276
|
|
- }).then(() => {
|
1277
|
|
- this.videoSwitchInProgress = false;
|
1278
|
|
- JitsiMeetJS.analytics.sendEvent(
|
1279
|
|
- 'conference.sharingDesktop.start');
|
1280
|
|
- logger.log('sharing local desktop');
|
1281
|
|
- }).catch(err => {
|
1282
|
|
- // close external installation dialog to show the error.
|
1283
|
|
- if(externalInstallation)
|
1284
|
|
- $.prompt.close();
|
1285
|
|
- this.videoSwitchInProgress = false;
|
1286
|
|
-
|
1287
|
|
- if (err.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
|
1288
|
|
- return Promise.reject(err);
|
1289
|
1337
|
}
|
|
1338
|
+ );
|
|
1339
|
+ // close external installation dialog on success.
|
|
1340
|
+ if (externalInstallation) {
|
|
1341
|
+ $.prompt.close();
|
|
1342
|
+ }
|
|
1343
|
+ return desktopStream;
|
|
1344
|
+ }, error => {
|
|
1345
|
+ DSExternalInstallationInProgress = false;
|
|
1346
|
+ // close external installation dialog on success.
|
|
1347
|
+ if (externalInstallation) {
|
|
1348
|
+ $.prompt.close();
|
|
1349
|
+ }
|
|
1350
|
+ throw error;
|
|
1351
|
+ });
|
|
1352
|
+ },
|
1290
|
1353
|
|
1291
|
|
- // Pawel: With this call I'm trying to preserve the original
|
1292
|
|
- // behaviour although it is not clear why would we "untoggle"
|
1293
|
|
- // on failure. I suppose it was to restore video in case there
|
1294
|
|
- // was some problem during "this.useVideoStream(desktopStream)".
|
1295
|
|
- // It's important to note that the handler will not be available
|
1296
|
|
- // if we fail early on trying to get desktop media (which makes
|
1297
|
|
- // sense, because the camera video is still being used, so
|
1298
|
|
- // nothing to "untoggle").
|
1299
|
|
- if (this._untoggleScreenSharing) {
|
1300
|
|
- this._untoggleScreenSharing();
|
1301
|
|
- }
|
|
1354
|
+ /**
|
|
1355
|
+ * Tries to switch to the screenshairng mode by disposing camera stream and
|
|
1356
|
+ * replacing it with a desktop one.
|
|
1357
|
+ *
|
|
1358
|
+ * @param {Object} [options] - Screen sharing options that will be passed to
|
|
1359
|
+ * createLocalTracks.
|
|
1360
|
+ *
|
|
1361
|
+ * @return {Promise} - A Promise resolved if the operation succeeds or
|
|
1362
|
+ * rejected with some unknown type of error in case it fails. Promise will
|
|
1363
|
+ * be rejected immediately if {@link videoSwitchInProgress} is true.
|
|
1364
|
+ *
|
|
1365
|
+ * @private
|
|
1366
|
+ */
|
|
1367
|
+ _switchToScreenSharing(options = {}) {
|
|
1368
|
+ if (this.videoSwitchInProgress) {
|
|
1369
|
+ return Promise.reject('Switch in progress.');
|
|
1370
|
+ }
|
1302
|
1371
|
|
1303
|
|
- logger.error('failed to share local desktop', err);
|
|
1372
|
+ this.videoSwitchInProgress = true;
|
|
1373
|
+ return this._createDesktopTrack(options).then(stream => {
|
|
1374
|
+ return this.useVideoStream(stream);
|
|
1375
|
+ }).then(() => {
|
|
1376
|
+ this.videoSwitchInProgress = false;
|
|
1377
|
+ JitsiMeetJS.analytics.sendEvent('conference.sharingDesktop.start');
|
|
1378
|
+ logger.log('sharing local desktop');
|
|
1379
|
+ }).catch(error => {
|
|
1380
|
+ this.videoSwitchInProgress = false;
|
|
1381
|
+ // Pawel: With this call I'm trying to preserve the original
|
|
1382
|
+ // behaviour although it is not clear why would we "untoggle"
|
|
1383
|
+ // on failure. I suppose it was to restore video in case there
|
|
1384
|
+ // was some problem during "this.useVideoStream(desktopStream)".
|
|
1385
|
+ // It's important to note that the handler will not be available
|
|
1386
|
+ // if we fail early on trying to get desktop media (which makes
|
|
1387
|
+ // sense, because the camera video is still being used, so
|
|
1388
|
+ // nothing to "untoggle").
|
|
1389
|
+ if (this._untoggleScreenSharing) {
|
|
1390
|
+ this._untoggleScreenSharing();
|
|
1391
|
+ }
|
1304
|
1392
|
|
1305
|
|
- if (err.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
|
1306
|
|
- APP.UI.showExtensionRequiredDialog(
|
1307
|
|
- config.desktopSharingFirefoxExtensionURL
|
1308
|
|
- );
|
1309
|
|
- return Promise.reject(err);
|
1310
|
|
- }
|
|
1393
|
+ // FIXME the code inside of _handleScreenSharingError is
|
|
1394
|
+ // asynchronous, but does not return a Promise and is not part of
|
|
1395
|
+ // the current Promise chain.
|
|
1396
|
+ this._handleScreenSharingError(error);
|
|
1397
|
+ });
|
|
1398
|
+ },
|
1311
|
1399
|
|
1312
|
|
- // Handling:
|
1313
|
|
- // TrackErrors.PERMISSION_DENIED
|
1314
|
|
- // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
|
1315
|
|
- // TrackErrors.GENERAL
|
1316
|
|
- // and any other
|
1317
|
|
- let dialogTxt;
|
1318
|
|
- let dialogTitleKey;
|
1319
|
|
-
|
1320
|
|
- if (err.name === TrackErrors.PERMISSION_DENIED) {
|
1321
|
|
- dialogTxt = APP.translation.generateTranslationHTML(
|
1322
|
|
- "dialog.screenSharingPermissionDeniedError");
|
1323
|
|
- dialogTitleKey = "dialog.error";
|
1324
|
|
- } else {
|
1325
|
|
- dialogTxt = APP.translation.generateTranslationHTML(
|
1326
|
|
- "dialog.failtoinstall");
|
1327
|
|
- dialogTitleKey = "dialog.permissionDenied";
|
|
1400
|
+ /**
|
|
1401
|
+ * Handles {@link JitsiTrackError} returned by the lib-jitsi-meet when
|
|
1402
|
+ * trying to create screensharing track. It will either do nothing if
|
|
1403
|
+ * the dialog was canceled on user's request or display inline installation
|
|
1404
|
+ * dialog and ask the user to install the extension, once the extension is
|
|
1405
|
+ * installed it will switch the conference to screensharing. The last option
|
|
1406
|
+ * is that an unrecoverable error dialog will be displayed.
|
|
1407
|
+ * @param {JitsiTrackError} error - The error returned by
|
|
1408
|
+ * {@link _createDesktopTrack} Promise.
|
|
1409
|
+ * @private
|
|
1410
|
+ */
|
|
1411
|
+ _handleScreenSharingError(error) {
|
|
1412
|
+ if (error.name === TrackErrors.CHROME_EXTENSION_USER_CANCELED) {
|
|
1413
|
+ return;
|
|
1414
|
+ }
|
|
1415
|
+
|
|
1416
|
+ logger.error('failed to share local desktop', error);
|
|
1417
|
+
|
|
1418
|
+ if (error.name === TrackErrors.CHROME_EXTENSION_USER_GESTURE_REQUIRED) {
|
|
1419
|
+ // If start with screen sharing the extension will fail to install
|
|
1420
|
+ // (if not found), because the request has been triggered by the
|
|
1421
|
+ // script. Show a dialog which asks user to click "install" and try
|
|
1422
|
+ // again switching to the screen sharing.
|
|
1423
|
+ APP.UI.showExtensionInlineInstallationDialog(
|
|
1424
|
+ () => {
|
|
1425
|
+ this.toggleScreenSharing();
|
1328
|
1426
|
}
|
|
1427
|
+ );
|
1329
|
1428
|
|
1330
|
|
- APP.UI.messageHandler.openDialog(
|
1331
|
|
- dialogTitleKey, dialogTxt, false);
|
1332
|
|
- });
|
|
1429
|
+ return;
|
|
1430
|
+ } else if (error.name === TrackErrors.FIREFOX_EXTENSION_NEEDED) {
|
|
1431
|
+ APP.UI.showExtensionRequiredDialog(
|
|
1432
|
+ config.desktopSharingFirefoxExtensionURL
|
|
1433
|
+ );
|
|
1434
|
+
|
|
1435
|
+ return;
|
|
1436
|
+ }
|
|
1437
|
+
|
|
1438
|
+ // Handling:
|
|
1439
|
+ // TrackErrors.PERMISSION_DENIED
|
|
1440
|
+ // TrackErrors.CHROME_EXTENSION_INSTALLATION_ERROR
|
|
1441
|
+ // TrackErrors.GENERAL
|
|
1442
|
+ // and any other
|
|
1443
|
+ let dialogTxt;
|
|
1444
|
+ let dialogTitleKey;
|
|
1445
|
+
|
|
1446
|
+ if (error.name === TrackErrors.PERMISSION_DENIED) {
|
|
1447
|
+ dialogTxt = APP.translation.generateTranslationHTML(
|
|
1448
|
+ "dialog.screenSharingPermissionDeniedError");
|
|
1449
|
+ dialogTitleKey = "dialog.error";
|
1333
|
1450
|
} else {
|
1334
|
|
- return this._untoggleScreenSharing();
|
|
1451
|
+ dialogTxt = APP.translation.generateTranslationHTML(
|
|
1452
|
+ "dialog.failtoinstall");
|
|
1453
|
+ dialogTitleKey = "dialog.permissionDenied";
|
1335
|
1454
|
}
|
|
1455
|
+
|
|
1456
|
+ APP.UI.messageHandler.openDialog(dialogTitleKey, dialogTxt, false);
|
1336
|
1457
|
},
|
1337
|
1458
|
/**
|
1338
|
1459
|
* Setup interaction between conference and UI.
|
|
@@ -1619,17 +1740,6 @@ export default {
|
1619
|
1740
|
APP.UI.updateDTMFSupport(isDTMFSupported);
|
1620
|
1741
|
});
|
1621
|
1742
|
|
1622
|
|
- APP.UI.addListener(UIEvents.EXTERNAL_INSTALLATION_CANCELED, () => {
|
1623
|
|
- // Wait a little bit more just to be sure that we won't miss the
|
1624
|
|
- // extension installation
|
1625
|
|
- setTimeout(() => DSExternalInstallationInProgress = false, 500);
|
1626
|
|
- });
|
1627
|
|
- APP.UI.addListener(UIEvents.OPEN_EXTENSION_STORE, (url) => {
|
1628
|
|
- window.open(
|
1629
|
|
- url, "extension_store_window",
|
1630
|
|
- "resizable,scrollbars=yes,status=1");
|
1631
|
|
- });
|
1632
|
|
-
|
1633
|
1743
|
APP.UI.addListener(UIEvents.AUDIO_MUTED, muteLocalAudio);
|
1634
|
1744
|
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
|
1635
|
1745
|
if (this.isAudioOnly() && !muted) {
|