Browse Source

feat(audio-screen-share): improved ux flow (#9411)

* add button state / helper dialog

* new audio screen share flow

* change error message

* address a couple of merge conflicts

* fix lint

* address code review

* address code review

* restrict audio screen share on mobile browsers
master
Andrei Gavrilescu 3 years ago
parent
commit
da7358d564
No account linked to committer's email address

+ 2
- 3
conference.js View File

1867
                     await this.useVideoStream(desktopVideoStream);
1867
                     await this.useVideoStream(desktopVideoStream);
1868
                 }
1868
                 }
1869
 
1869
 
1870
-
1871
                 if (this._desktopAudioStream) {
1870
                 if (this._desktopAudioStream) {
1872
                     // If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing
1871
                     // If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing
1873
                     // api.
1872
                     // api.
2463
         });
2462
         });
2464
 
2463
 
2465
         APP.UI.addListener(
2464
         APP.UI.addListener(
2466
-            UIEvents.TOGGLE_SCREENSHARING, audioOnly => {
2467
-                this.toggleScreenSharing(undefined, { audioOnly });
2465
+            UIEvents.TOGGLE_SCREENSHARING, ({ enabled, audioOnly }) => {
2466
+                this.toggleScreenSharing(enabled, { audioOnly });
2468
             }
2467
             }
2469
         );
2468
         );
2470
     },
2469
     },

+ 2
- 0
css/main.scss View File

41
 @import 'modals/feedback/feedback';
41
 @import 'modals/feedback/feedback';
42
 @import 'modals/invite/info';
42
 @import 'modals/invite/info';
43
 @import 'modals/settings/settings';
43
 @import 'modals/settings/settings';
44
+@import 'modals/screen-share/share-audio';
45
+@import 'modals/screen-share/share-screen-warning';
44
 @import 'modals/speaker_stats/speaker_stats';
46
 @import 'modals/speaker_stats/speaker_stats';
45
 @import 'modals/video-quality/video-quality';
47
 @import 'modals/video-quality/video-quality';
46
 @import 'modals/virtual-background/virtual-background';
48
 @import 'modals/virtual-background/virtual-background';

+ 22
- 0
css/modals/screen-share/_share-audio.scss View File

1
+.share-audio-dialog {
2
+    .share-audio-animation {
3
+        width: 100%;
4
+        height: 90%;
5
+        object-fit: contain;
6
+    }
7
+    input[type="checkbox"] + svg + span {
8
+        color: #9FB0CC;
9
+    }
10
+
11
+    .separator-line {
12
+        margin: 24px 0 24px -20px;
13
+        padding: 0 20px;
14
+        width: 100%;
15
+        height: 1px;
16
+        background: #5E6D7A;
17
+
18
+        &:last-child {
19
+            display: none;
20
+        }
21
+    }
22
+}

+ 23
- 0
css/modals/screen-share/_share-screen-warning.scss View File

1
+.share-screen-warn-dialog {
2
+    font-size: 14px;
3
+
4
+    .separator-line {
5
+        margin: 24px 0 24px -20px;
6
+        padding: 0 20px;
7
+        width: 100%;
8
+        height: 1px;
9
+        background: #5E6D7A;
10
+
11
+        &:last-child {
12
+            display: none;
13
+        }
14
+    }
15
+
16
+    .header {
17
+        font-weight: 600;
18
+    }
19
+
20
+    .description {
21
+        margin-top: 16px;
22
+    }
23
+}

BIN
images/share-audio.gif View File


+ 14
- 1
lang/main.json View File

215
         "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
215
         "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
216
         "grantModeratorDialog": "Are you sure you want to make this participant a moderator?",
216
         "grantModeratorDialog": "Are you sure you want to make this participant a moderator?",
217
         "grantModeratorTitle": "Grant moderator",
217
         "grantModeratorTitle": "Grant moderator",
218
+        "hideShareAudioHelper": "Don't show this dialog again",
218
         "IamHost": "I am the host",
219
         "IamHost": "I am the host",
219
         "incorrectRoomLockPassword": "Incorrect password",
220
         "incorrectRoomLockPassword": "Incorrect password",
220
         "incorrectPassword": "Incorrect username or password",
221
         "incorrectPassword": "Incorrect username or password",
303
         "sessTerminated": "Call terminated",
304
         "sessTerminated": "Call terminated",
304
         "sessionRestarted": "Call restarted by the bridge",
305
         "sessionRestarted": "Call restarted by the bridge",
305
         "Share": "Share",
306
         "Share": "Share",
307
+        "shareAudio": "Continue",
308
+        "shareAudioTitle" : "How to share audio",
309
+        "shareAudioWarningTitle": "You need to stop screen sharing before sharing audio",
310
+        "shareAudioWarningH1": "If you want to share just audio:",
311
+        "shareAudioWarningD1": "you need to stop screen sharing before sharing your audio.",
312
+        "shareAudioWarningD2": "you need to restart your screen sharing and check the \"share audio\" option.",
313
+        "shareMediaWarningGenericH2": "If you want to share your screen and audio",
306
         "shareVideoLinkError": "Please provide a correct youtube link.",
314
         "shareVideoLinkError": "Please provide a correct youtube link.",
307
         "shareVideoTitle": "Share video",
315
         "shareVideoTitle": "Share video",
308
         "shareYourScreen": "Share your screen",
316
         "shareYourScreen": "Share your screen",
310
         "startLiveStreaming": "Start live stream",
318
         "startLiveStreaming": "Start live stream",
311
         "startRecording": "Start recording",
319
         "startRecording": "Start recording",
312
         "startRemoteControlErrorMessage": "An error occurred while trying to start the remote control session!",
320
         "startRemoteControlErrorMessage": "An error occurred while trying to start the remote control session!",
321
+        "shareScreenWarningTitle": "You need to stop audio sharing before sharing your screen",
322
+        "shareScreenWarningH1": "If you want to share just your screen:",
323
+        "shareScreenWarningD1": "you need to stop audio sharing before sharing your screen.",
324
+        "shareScreenWarningD2": "you need to stop audio sharing, start screen sharing and check the \"share audio\" option.",
313
         "stopLiveStreaming": "Stop live stream",
325
         "stopLiveStreaming": "Stop live stream",
314
         "stopRecording": "Stop recording",
326
         "stopRecording": "Stop recording",
315
         "stopRecordingWarning": "Are you sure you would like to stop the recording?",
327
         "stopRecordingWarning": "Are you sure you would like to stop the recording?",
544
         "passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
556
         "passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
545
         "raisedHand": "{{name}} would like to speak.",
557
         "raisedHand": "{{name}} would like to speak.",
546
         "screenShareNoAudio": " Share audio box was not checked in the window selection screen.",
558
         "screenShareNoAudio": " Share audio box was not checked in the window selection screen.",
547
-        "screenShareNoAudioTitle": "Share audio was not checked",
559
+        "screenShareNoAudioTitle": "Couldn't share system audio!",
548
         "somebody": "Somebody",
560
         "somebody": "Somebody",
549
         "startSilentTitle": "You joined with no audio output!",
561
         "startSilentTitle": "You joined with no audio output!",
550
         "startSilentDescription": "Rejoin the meeting to enable audio",
562
         "startSilentDescription": "Rejoin the meeting to enable audio",
899
         "speakerStats": "Speaker stats",
911
         "speakerStats": "Speaker stats",
900
         "startScreenSharing": "Start screen sharing",
912
         "startScreenSharing": "Start screen sharing",
901
         "startSubtitles": "Start subtitles",
913
         "startSubtitles": "Start subtitles",
914
+        "stopAudioSharing": "Stop audio sharing",
902
         "stopScreenSharing": "Stop screen sharing",
915
         "stopScreenSharing": "Stop screen sharing",
903
         "stopSubtitles": "Stop subtitles",
916
         "stopSubtitles": "Stop subtitles",
904
         "stopSharedVideo": "Stop video",
917
         "stopSharedVideo": "Stop video",

+ 13
- 3
modules/API/API.js View File

49
 import { toggleLobbyMode } from '../../react/features/lobby/actions';
49
 import { toggleLobbyMode } from '../../react/features/lobby/actions';
50
 import { RECORDING_TYPES } from '../../react/features/recording/constants';
50
 import { RECORDING_TYPES } from '../../react/features/recording/constants';
51
 import { getActiveSession } from '../../react/features/recording/functions';
51
 import { getActiveSession } from '../../react/features/recording/functions';
52
+import { isScreenAudioSupported } from '../../react/features/screen-share';
53
+import { startScreenShareFlow, startAudioScreenShareFlow } from '../../react/features/screen-share/actions';
52
 import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
54
 import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
53
 import { toggleTileView, setTileView } from '../../react/features/video-layout';
55
 import { toggleTileView, setTileView } from '../../react/features/video-layout';
54
 import { muteAllParticipants } from '../../react/features/video-menu/actions';
56
 import { muteAllParticipants } from '../../react/features/video-menu/actions';
220
             sendAnalytics(createApiEvent('raise-hand.toggled'));
222
             sendAnalytics(createApiEvent('raise-hand.toggled'));
221
             APP.store.dispatch(raiseHand(!raisedHand));
223
             APP.store.dispatch(raiseHand(!raisedHand));
222
         },
224
         },
225
+        'toggle-share-audio': () => {
226
+            sendAnalytics(createApiEvent('audio.screen.sharing.toggled'));
227
+            if (isScreenAudioSupported()) {
228
+                APP.store.dispatch(startAudioScreenShareFlow());
229
+
230
+                return;
231
+            }
232
+
233
+            logger.error('Audio screen sharing is not supported by the current platform!');
234
+        },
223
 
235
 
224
         /**
236
         /**
225
          * Callback to invoke when the "toggle-share-screen" command is received.
237
          * Callback to invoke when the "toggle-share-screen" command is received.
596
  */
608
  */
597
 function toggleScreenSharing(enable) {
609
 function toggleScreenSharing(enable) {
598
     if (JitsiMeetJS.isDesktopSharingEnabled()) {
610
     if (JitsiMeetJS.isDesktopSharingEnabled()) {
599
-        APP.conference.toggleScreenSharing(enable).catch(() => {
600
-            logger.warn('Failed to toggle screen-sharing');
601
-        });
611
+        APP.store.dispatch(startScreenShareFlow(enable));
602
     }
612
     }
603
 }
613
 }
604
 
614
 

+ 1
- 0
modules/API/external/external_api.js View File

60
     toggleChat: 'toggle-chat',
60
     toggleChat: 'toggle-chat',
61
     toggleFilmStrip: 'toggle-film-strip',
61
     toggleFilmStrip: 'toggle-film-strip',
62
     toggleRaiseHand: 'toggle-raise-hand',
62
     toggleRaiseHand: 'toggle-raise-hand',
63
+    toggleShareAudio: 'toggle-share-audio',
63
     toggleShareScreen: 'toggle-share-screen',
64
     toggleShareScreen: 'toggle-share-screen',
64
     toggleTileView: 'toggle-tile-view',
65
     toggleTileView: 'toggle-tile-view',
65
     toggleVirtualBackgroundDialog: 'toggle-virtual-background',
66
     toggleVirtualBackgroundDialog: 'toggle-virtual-background',

+ 1
- 0
react/features/base/icons/svg/index.js View File

111
 export { default as IconShareDoc } from './share-doc.svg';
111
 export { default as IconShareDoc } from './share-doc.svg';
112
 export { default as IconShareVideo } from './shared-video.svg';
112
 export { default as IconShareVideo } from './shared-video.svg';
113
 export { default as IconSmile } from './smile.svg';
113
 export { default as IconSmile } from './smile.svg';
114
+export { default as IconStopAudioShare } from './stop-audio-share.svg';
114
 export { default as IconSwitchCamera } from './switch-camera.svg';
115
 export { default as IconSwitchCamera } from './switch-camera.svg';
115
 export { default as IconTileView } from './tiles-many.svg';
116
 export { default as IconTileView } from './tiles-many.svg';
116
 export { default as IconToggleRecording } from './camera-take-picture.svg';
117
 export { default as IconToggleRecording } from './camera-take-picture.svg';

+ 3
- 0
react/features/base/icons/svg/stop-audio-share.svg View File

1
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.57392 5.35509L6.56245 5.36465L3.11271 1.9149C2.78183 1.58402 2.24546 1.58394 1.91469 1.91471C1.58392 2.24548 1.58401 2.78185 1.91488 3.11272L5.25574 6.45358L5.00001 6.66669H2.50001C2.03977 6.66669 1.66667 7.03978 1.66667 7.50002V12.5C1.66667 12.9603 2.03977 13.3334 2.50001 13.3334H5.00001L9.3166 16.9305C9.39148 16.9929 9.48586 17.0271 9.58334 17.0271C9.81346 17.0271 10 16.8405 10 16.6104V11.1978L11.6667 12.8645V13.3334C11.8158 13.3334 11.9627 13.3236 12.1067 13.3046L13.4679 14.6657C12.9091 14.8816 12.3017 15 11.6667 15V16.6667C12.7697 16.6667 13.8102 16.3988 14.7267 15.9245L16.8873 18.0851C17.2182 18.416 17.7545 18.4161 18.0853 18.0853C18.4161 17.7546 18.416 17.2182 18.0851 16.8873L16.1405 14.9427C16.1442 14.9394 16.1478 14.936 16.1515 14.9327L14.9712 13.7524C14.9675 13.7557 14.9638 13.759 14.9601 13.7622L13.7777 12.5798C13.7815 12.5767 13.7854 12.5735 13.7892 12.5704L12.6003 11.3814C12.5961 11.3843 12.5919 11.3871 12.5877 11.3899L10 8.8022V8.78118L8.33334 7.11451V7.13553L7.74583 6.54802L7.75729 6.53846L6.57392 5.35509ZM11.9766 8.36212L11.6667 8.0522V6.66669C13.5076 6.66669 15 8.15907 15 10C15 10.4129 14.9249 10.8083 14.7877 11.1732L13.3046 9.6901C13.1782 9.01814 12.6486 8.48848 11.9766 8.36212ZM16.04 12.4255L17.2535 13.639C17.9364 12.5927 18.3333 11.3427 18.3333 10C18.3333 6.31812 15.3486 3.33335 11.6667 3.33335V5.00002C14.4281 5.00002 16.6667 7.2386 16.6667 10C16.6667 10.8802 16.4393 11.7072 16.04 12.4255ZM10 6.38553L7.88064 4.26616L9.3166 3.06953C9.49338 2.92221 9.75611 2.9461 9.90343 3.12288C9.96583 3.19776 10 3.29215 10 3.38962V6.38553ZM5.60342 8.33335L6.43911 7.63695L8.33334 9.53118V13.9416L5.60342 11.6667H3.33334V8.33335H5.60342Z" fill="white"/>
3
+</svg>

+ 10
- 0
react/features/base/settings/functions.any.js View File

248
         ? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
248
         ? foundMatchBasedOnLabel.deviceId : userSelectedDeviceId;
249
 }
249
 }
250
 
250
 
251
+/**
252
+ * Should we hide the helper dialog when a user tries to do audio only screen sharing.
253
+ *
254
+ * @param {Object} state - The state of the application.
255
+ * @returns {boolean}
256
+ */
257
+export function shouldHideShareAudioHelper(state: Object): boolean {
258
+
259
+    return state['features/base/settings'].hideShareAudioHelper;
260
+}

+ 1
- 0
react/features/base/settings/reducer.js View File

26
     localFlipX: true,
26
     localFlipX: true,
27
     micDeviceId: undefined,
27
     micDeviceId: undefined,
28
     serverURL: undefined,
28
     serverURL: undefined,
29
+    hideShareAudioHelper: false,
29
     startAudioOnly: false,
30
     startAudioOnly: false,
30
     startWithAudioMuted: false,
31
     startWithAudioMuted: false,
31
     startWithVideoMuted: false,
32
     startWithVideoMuted: false,

+ 4
- 1
react/features/base/tracks/middleware.js View File

145
                 return;
145
                 return;
146
             }
146
             }
147
 
147
 
148
-            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING, action.audioOnly);
148
+            const { enabled, audioOnly } = action;
149
+
150
+            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING, { enabled,
151
+                audioOnly });
149
         }
152
         }
150
         break;
153
         break;
151
 
154
 

+ 66
- 0
react/features/screen-share/actions.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
+import { openDialog } from '../base/dialog/actions';
4
+import { browser } from '../base/lib-jitsi-meet';
5
+import { shouldHideShareAudioHelper } from '../base/settings';
6
+import { toggleScreensharing } from '../base/tracks';
7
+
3
 import { SET_SCREEN_AUDIO_SHARE_STATE, SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
8
 import { SET_SCREEN_AUDIO_SHARE_STATE, SET_SCREENSHARE_CAPTURE_FRAME_RATE } from './actionTypes';
9
+import { ShareAudioDialog } from './components';
10
+import ShareMediaWarningDialog from './components/ShareScreenWarningDialog';
11
+import { isAudioOnlySharing, isScreenVideoShared } from './functions';
4
 
12
 
5
 /**
13
 /**
6
  * Updates the current known status of the shared video.
14
  * Updates the current known status of the shared video.
33
         captureFrameRate
41
         captureFrameRate
34
     };
42
     };
35
 }
43
 }
44
+
45
+/**
46
+ * Start the audio only screen sharing flow. Function will switch between off and on states depending on the context.
47
+ *
48
+ * @param {Object} state - The state of the application.
49
+ * @returns {void}
50
+ */
51
+export function startAudioScreenShareFlow() {
52
+    return (dispatch: Object => Object, getState: () => any) => {
53
+        const state = getState();
54
+        const audioOnlySharing = isAudioOnlySharing(state);
55
+
56
+        // If we're already in a normal screen sharing session, warn the user.
57
+        if (isScreenVideoShared(state)) {
58
+            dispatch(openDialog(ShareMediaWarningDialog, { _isAudioScreenShareWarning: true }));
59
+
60
+            return;
61
+        }
62
+
63
+        // If users opted out of the helper dialog toggle directly.
64
+        // If we're in an electron environment the helper dialog is not needed as there's only one option
65
+        // available for audio screen sharing, namely full window audio.
66
+        // If we're already sharing audio, toggle off.
67
+        if (shouldHideShareAudioHelper(state) || browser.isElectron() || audioOnlySharing) {
68
+            // We don't want to explicity set the screens share state, by passing undefined we let the
69
+            // underlying logic decide if it's on or off.
70
+            dispatch(toggleScreensharing(undefined, true));
71
+
72
+            return;
73
+        }
74
+
75
+        dispatch(openDialog(ShareAudioDialog));
76
+    };
77
+}
78
+
79
+/**
80
+ * Start normal screen sharing flow.Function will switch between off and on states depending on the context, and if
81
+ * not explicity told otherwise.
82
+ *
83
+ * @param {boolean} enabled - Explicitly set the screen sharing state. This has been kept for backward compatibility
84
+ * with the external API exposed by the iframe, even though it might not be used.
85
+ * @returns {void}
86
+ */
87
+export function startScreenShareFlow(enabled: boolean) {
88
+    return (dispatch: Object => Object, getState: () => any) => {
89
+        const state = getState();
90
+        const audioOnlySharing = isAudioOnlySharing(state);
91
+
92
+        // If we're in an audio screen sharing session, warn the user.
93
+        if (audioOnlySharing) {
94
+            dispatch(openDialog(ShareMediaWarningDialog, { _isAudioScreenShareWarning: false }));
95
+
96
+            return;
97
+        }
98
+
99
+        dispatch(toggleScreensharing(enabled));
100
+    };
101
+}

+ 80
- 0
react/features/screen-share/components/ShareAudioButton.js View File

1
+// @flow
2
+
3
+import type { Dispatch } from 'redux';
4
+
5
+import { translate } from '../../base/i18n';
6
+import {
7
+    IconShareAudio,
8
+    IconStopAudioShare
9
+} from '../../base/icons';
10
+import { connect } from '../../base/redux';
11
+import {
12
+    AbstractButton,
13
+    type AbstractButtonProps
14
+} from '../../base/toolbox/components';
15
+import { setOverflowMenuVisible } from '../../toolbox/actions';
16
+import { startAudioScreenShareFlow } from '../actions';
17
+import { isAudioOnlySharing } from '../functions';
18
+
19
+type Props = AbstractButtonProps & {
20
+
21
+    /**
22
+     * The redux {@code dispatch} function.
23
+     */
24
+    dispatch: Dispatch<any>,
25
+
26
+    /**
27
+     * Whether or not the local participant is audio only screen sharing.
28
+     */
29
+    _isAudioOnlySharing: boolean
30
+}
31
+
32
+/**
33
+ * Component that renders a toolbar button for toggling audio only screen share.
34
+ */
35
+class ShareAudioButton extends AbstractButton<Props, *> {
36
+    accessibilityLabel = 'toolbar.accessibilityLabel.shareaudio';
37
+    icon = IconShareAudio;
38
+    label = 'toolbar.shareaudio';
39
+    tooltip = 'toolbar.shareaudio';
40
+    toggledIcon = IconStopAudioShare;
41
+    toggledLabel = 'toolbar.stopAudioSharing';
42
+
43
+    /**
44
+     * Handles clicking / pressing the button, and opens a new dialog.
45
+     *
46
+     * @private
47
+     * @returns {void}
48
+     */
49
+    _handleClick() {
50
+        this.props.dispatch(startAudioScreenShareFlow());
51
+        this.props.dispatch(setOverflowMenuVisible(false));
52
+    }
53
+
54
+    /**
55
+     * Indicates whether this button is in toggled state or not.
56
+     *
57
+     * @override
58
+     * @protected
59
+     * @returns {boolean}
60
+     */
61
+    _isToggled() {
62
+        return this.props._isAudioOnlySharing;
63
+    }
64
+}
65
+
66
+/**
67
+ * Maps part of the Redux state to the props of this component.
68
+ *
69
+ * @param {Object} state - The Redux state.
70
+ * @private
71
+ * @returns {Props}
72
+ */
73
+function _mapStateToProps(state: Object): $Shape<Props> {
74
+
75
+    return {
76
+        _isAudioOnlySharing: isAudioOnlySharing(state)
77
+    };
78
+}
79
+
80
+export default translate(connect(_mapStateToProps)(ShareAudioButton));

+ 128
- 0
react/features/screen-share/components/ShareAudioDialog.js View File

1
+// @flow
2
+
3
+import { Checkbox } from '@atlaskit/checkbox';
4
+import React, { Component } from 'react';
5
+import type { Dispatch } from 'redux';
6
+
7
+import { Dialog } from '../../base/dialog';
8
+import { translate } from '../../base/i18n';
9
+import { connect } from '../../base/redux';
10
+import {
11
+    updateSettings,
12
+    shouldHideShareAudioHelper
13
+} from '../../base/settings';
14
+import { toggleScreensharing } from '../../base/tracks';
15
+
16
+/**
17
+ * The type of the React {@code Component} props of {@link ShareAudioDialog}.
18
+ */
19
+export type Props = {
20
+
21
+    /**
22
+     * The redux {@code dispatch} function.
23
+     */
24
+    dispatch: Dispatch<any>,
25
+
26
+    /**
27
+     * Boolean stored in local storage that determines whether or not the dialog will be displayed again.
28
+     */
29
+     _shouldHideShareAudioHelper: boolean,
30
+
31
+    /**
32
+     * Invoked to obtain translated strings.
33
+     */
34
+    t: Function
35
+};
36
+
37
+/**
38
+ * Component that displays the audio screen share helper dialog.
39
+ */
40
+class ShareAudioDialog extends Component<Props> {
41
+
42
+    /**
43
+     * Instantiates a new component.
44
+     *
45
+     * @inheritdoc
46
+     */
47
+    constructor(props) {
48
+        super(props);
49
+
50
+        this._onContinue = this._onContinue.bind(this);
51
+        this._onSelectHideShareAudioHelper = this._onSelectHideShareAudioHelper.bind(this);
52
+    }
53
+
54
+    _onContinue: () => boolean;
55
+
56
+    /**
57
+     * Continue the normal screen sharing flow when the user clicks continue.
58
+     *
59
+     * @returns {boolean}
60
+     */
61
+    _onContinue() {
62
+        // Pass undefined as the first parameter so the underlying logic decides weather or not to stop screen sharing.
63
+        this.props.dispatch(toggleScreensharing(undefined, true));
64
+
65
+        return true;
66
+    }
67
+
68
+    _onSelectHideShareAudioHelper: (Object) => void;
69
+
70
+    /**
71
+     * Callback invoked when the hide audio helper checkbox has been selected. This setting will be persisted in
72
+     * the local storage, thus the dialog won't be displayed again.
73
+     *
74
+     * @param {Object} e - The key event to handle.
75
+     * @returns {void}
76
+     */
77
+    _onSelectHideShareAudioHelper({ target: { checked } }) {
78
+        this.props.dispatch(updateSettings({ hideShareAudioHelper: checked }));
79
+    }
80
+
81
+    /**
82
+     * Implements {@Component#render}.
83
+     *
84
+     * @inheritdoc
85
+     */
86
+    render() {
87
+
88
+        const { t } = this.props;
89
+
90
+        return (
91
+            <Dialog
92
+                hideCancelButton = { false }
93
+                okKey = { t('dialog.shareAudio') }
94
+                onSubmit = { this._onContinue }
95
+                titleKey = { t('dialog.shareAudioTitle') }
96
+                width = { 'medium' } >
97
+                <div className = 'share-audio-dialog'>
98
+                    <img
99
+                        className = 'share-audio-animation'
100
+                        src = 'images/share-audio.gif' />
101
+                    <Checkbox
102
+                        isChecked = { this.props._shouldHideShareAudioHelper }
103
+                        label = { t('dialog.hideShareAudioHelper') }
104
+                        name = 'hide-share-audio-helper'
105
+                        // eslint-disable-next-line react/jsx-no-bind
106
+                        onChange = { this._onSelectHideShareAudioHelper } />
107
+                </div>
108
+            </Dialog>
109
+        );
110
+    }
111
+}
112
+
113
+/**
114
+ * Maps part of the Redux state to the props of this component.
115
+ *
116
+ * @param {Object} state - The Redux state.
117
+ * @private
118
+ * @returns {Props}
119
+ */
120
+function _mapStateToProps(state: Object): $Shape<Props> {
121
+
122
+    return {
123
+        _shouldHideShareAudioHelper: shouldHideShareAudioHelper(state)
124
+    };
125
+}
126
+
127
+
128
+export default translate(connect(_mapStateToProps)(ShareAudioDialog));

+ 105
- 0
react/features/screen-share/components/ShareScreenWarningDialog.js View File

1
+// @flow
2
+
3
+import React, { Component } from 'react';
4
+import type { Dispatch } from 'redux';
5
+
6
+import { Dialog } from '../../base/dialog';
7
+import { translate } from '../../base/i18n';
8
+import { connect } from '../../base/redux';
9
+import { toggleScreensharing } from '../../base/tracks';
10
+
11
+export type Props = {
12
+
13
+    /**
14
+     * The redux {@code dispatch} function.
15
+     */
16
+     dispatch: Dispatch<any>,
17
+
18
+    /**
19
+     * Whether or not the dialog was opened for the audio screen sharing flow or the normal one.
20
+     */
21
+    _isAudioScreenShareWarning: Boolean,
22
+
23
+    /**
24
+     * Invoked to obtain translated strings.
25
+     */
26
+    t: Function
27
+}
28
+
29
+/**
30
+ *  Component that displays the share audio helper dialog.
31
+ */
32
+class ShareScreenWarningDialog extends Component<Props> {
33
+
34
+    /**
35
+     * Instantiates a new component.
36
+     *
37
+     * @inheritdoc
38
+     */
39
+    constructor(props) {
40
+        super(props);
41
+
42
+        this._onStopSharing = this._onStopSharing.bind(this);
43
+    }
44
+
45
+    _onStopSharing: () => boolean;
46
+
47
+    /**
48
+     * Stop current screen sharing session.
49
+     *
50
+     * @returns {boolean}
51
+     */
52
+    _onStopSharing() {
53
+        // Depending on the context from which this dialog is opened we'll either be toggling off an audio only
54
+        // share session or a normal screen sharing one, this is indicated by the _isAudioScreenShareWarning prop.
55
+        this.props.dispatch(toggleScreensharing(undefined,
56
+            !this.props._isAudioScreenShareWarning));
57
+
58
+        return true;
59
+    }
60
+
61
+    /**
62
+     * Implements {@Component#render}.
63
+     *§
64
+     * @inheritdoc
65
+     */
66
+    render() {
67
+        const { t } = this.props;
68
+
69
+        let description1, description2, header1, header2, stopSharing, title;
70
+
71
+        if (this.props._isAudioScreenShareWarning) {
72
+            header1 = 'dialog.shareAudioWarningH1';
73
+            header2 = 'dialog.shareMediaWarningGenericH2';
74
+            description1 = 'dialog.shareAudioWarningD1';
75
+            description2 = 'dialog.shareAudioWarningD2';
76
+            title = 'dialog.shareAudioWarningTitle';
77
+            stopSharing = 'toolbar.stopScreenSharing';
78
+        } else {
79
+            header1 = 'dialog.shareScreenWarningTitle';
80
+            header2 = 'dialog.shareMediaWarningGenericH2';
81
+            description1 = 'dialog.shareScreenWarningD1';
82
+            description2 = 'dialog.shareScreenWarningD2';
83
+            title = 'dialog.shareScreenWarningTitle';
84
+            stopSharing = 'toolbar.stopAudioSharing';
85
+        }
86
+
87
+        return (<Dialog
88
+            hideCancelButton = { false }
89
+            okKey = { t(stopSharing) }
90
+            onSubmit = { this._onStopSharing }
91
+            titleKey = { t(title) }
92
+            width = { 'small' }>
93
+            <div className = 'share-screen-warn-dialog'>
94
+                <p className = 'header'> { t(header1) } </p>
95
+                <p className = 'description' > { t(description1) } </p>
96
+                <div className = 'separator-line' />
97
+                <p className = 'header' > { t(header2) } </p>
98
+                <p className = 'description' > { t(description2) } </p>
99
+            </div>
100
+        </Dialog>);
101
+
102
+    }
103
+}
104
+
105
+export default translate(connect()(ShareScreenWarningDialog));

+ 2
- 0
react/features/screen-share/components/index.js View File

1
+export { default as ShareAudioButton } from './ShareAudioButton';
2
+export { default as ShareAudioDialog } from './ShareAudioDialog';

+ 36
- 1
react/features/screen-share/functions.js View File

1
 // @flow
1
 // @flow
2
 
2
 
3
 import { isWindows } from '../base/environment';
3
 import { isWindows } from '../base/environment';
4
+import { isMobileBrowser } from '../base/environment/utils';
4
 import { browser } from '../base/lib-jitsi-meet';
5
 import { browser } from '../base/lib-jitsi-meet';
6
+import { VIDEO_TYPE } from '../base/media';
7
+import { getLocalVideoTrack } from '../base/tracks';
5
 
8
 
9
+/**
10
+ * Is the current screen sharing session audio only.
11
+ *
12
+ * @param {Object} state - The state of the application.
13
+ * @returns {boolean}
14
+ */
15
+export function isAudioOnlySharing(state: Object) {
16
+    return isScreenAudioShared(state) && !isScreenVideoShared(state);
17
+}
6
 
18
 
7
 /**
19
 /**
8
  * State of audio sharing.
20
  * State of audio sharing.
21
  * @returns {boolean}
33
  * @returns {boolean}
22
  */
34
  */
23
 export function isScreenAudioSupported() {
35
 export function isScreenAudioSupported() {
24
-    return browser.isChrome() || (browser.isElectron() && isWindows());
36
+    return (!isMobileBrowser() && browser.isChrome()) || (browser.isElectron() && isWindows());
37
+}
38
+
39
+/**
40
+ * Is any screen media currently being shared, audio or video.
41
+ *
42
+ * @param {Object} state - The state of the application.
43
+ * @returns {boolean}
44
+ */
45
+export function isScreenMediaShared(state: Object) {
46
+    return isScreenAudioShared(state) || isScreenVideoShared(state);
47
+}
48
+
49
+/**
50
+ * Is screen sharing currently active.
51
+ *
52
+ * @param {Object} state - The state of the application.
53
+ * @returns {boolean}
54
+ */
55
+export function isScreenVideoShared(state: Object) {
56
+    const localVideo = getLocalVideoTrack(state['features/base/tracks']);
57
+
58
+    // $FlowFixMe - No support for optional chain method calls in flow atm.
59
+    return localVideo?.jitsiTrack?.getVideoType() === VIDEO_TYPE.DESKTOP;
25
 }
60
 }

+ 2
- 0
react/features/screen-share/index.js View File

1
+
1
 export * from './actions';
2
 export * from './actions';
2
 export * from './actionTypes';
3
 export * from './actionTypes';
4
+export * from './components';
3
 export * from './functions';
5
 export * from './functions';
4
 
6
 

+ 16
- 34
react/features/toolbox/components/web/Toolbox.js View File

25
     IconPresentation,
25
     IconPresentation,
26
     IconRaisedHand,
26
     IconRaisedHand,
27
     IconRec,
27
     IconRec,
28
-    IconShareAudio,
29
     IconShareDesktop
28
     IconShareDesktop
30
 } from '../../../base/icons';
29
 } from '../../../base/icons';
31
 import JitsiMeetJS from '../../../base/lib-jitsi-meet';
30
 import JitsiMeetJS from '../../../base/lib-jitsi-meet';
36
 } from '../../../base/participants';
35
 } from '../../../base/participants';
37
 import { connect } from '../../../base/redux';
36
 import { connect } from '../../../base/redux';
38
 import { OverflowMenuItem } from '../../../base/toolbox/components';
37
 import { OverflowMenuItem } from '../../../base/toolbox/components';
39
-import { getLocalVideoTrack, toggleScreensharing } from '../../../base/tracks';
38
+import { getLocalVideoTrack } from '../../../base/tracks';
40
 import { isVpaasMeeting } from '../../../billing-counter/functions';
39
 import { isVpaasMeeting } from '../../../billing-counter/functions';
41
 import { ChatCounter, toggleChat } from '../../../chat';
40
 import { ChatCounter, toggleChat } from '../../../chat';
42
 import { EmbedMeetingDialog } from '../../../embed-meeting';
41
 import { EmbedMeetingDialog } from '../../../embed-meeting';
53
     LiveStreamButton,
52
     LiveStreamButton,
54
     RecordButton
53
     RecordButton
55
 } from '../../../recording';
54
 } from '../../../recording';
56
-import { isScreenAudioShared, isScreenAudioSupported } from '../../../screen-share/';
55
+import {
56
+    isScreenAudioSupported,
57
+    isScreenVideoShared,
58
+    ShareAudioButton,
59
+    startScreenShareFlow
60
+} from '../../../screen-share/';
57
 import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton';
61
 import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton';
58
 import {
62
 import {
59
     SETTINGS_TABS,
63
     SETTINGS_TABS,
287
         this._onToolbarToggleProfile = this._onToolbarToggleProfile.bind(this);
291
         this._onToolbarToggleProfile = this._onToolbarToggleProfile.bind(this);
288
         this._onToolbarToggleRaiseHand = this._onToolbarToggleRaiseHand.bind(this);
292
         this._onToolbarToggleRaiseHand = this._onToolbarToggleRaiseHand.bind(this);
289
         this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
293
         this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
290
-        this._onToolbarToggleShareAudio = this._onToolbarToggleShareAudio.bind(this);
291
         this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
294
         this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
292
         this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this);
295
         this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this);
293
         this._onEscKey = this._onEscKey.bind(this);
296
         this._onEscKey = this._onEscKey.bind(this);
535
      * @param {boolean} audioOnly - Only share system audio.
538
      * @param {boolean} audioOnly - Only share system audio.
536
      * @returns {void}
539
      * @returns {void}
537
      */
540
      */
538
-    _doToggleScreenshare(enabled, audioOnly = false) {
541
+    _doToggleScreenshare() {
539
         if (this.props._desktopSharingEnabled) {
542
         if (this.props._desktopSharingEnabled) {
540
-            this.props.dispatch(toggleScreensharing(enabled, audioOnly));
543
+            this.props.dispatch(startScreenShareFlow());
541
         }
544
         }
542
     }
545
     }
543
 
546
 
725
      * @returns {void}
728
      * @returns {void}
726
      */
729
      */
727
     _onShortcutToggleScreenshare() {
730
     _onShortcutToggleScreenshare() {
728
-        const enable = !this.props._screensharing;
729
-
730
         sendAnalytics(createToolbarEvent(
731
         sendAnalytics(createToolbarEvent(
731
             'screen.sharing',
732
             'screen.sharing',
732
             {
733
             {
733
-                enable
734
+                enable: !this.props._screensharing
734
             }));
735
             }));
735
 
736
 
736
-        this._doToggleScreenshare(enable);
737
+        this._doToggleScreenshare();
737
     }
738
     }
738
 
739
 
739
     _onTabIn: () => void;
740
     _onTabIn: () => void;
940
             return;
941
             return;
941
         }
942
         }
942
 
943
 
943
-        const enable = !this.props._screensharing;
944
-
945
         sendAnalytics(createShortcutEvent(
944
         sendAnalytics(createShortcutEvent(
946
             'toggle.screen.sharing',
945
             'toggle.screen.sharing',
947
             ACTION_SHORTCUT_TRIGGERED,
946
             ACTION_SHORTCUT_TRIGGERED,
948
-            { enable }));
949
-
950
-        this._closeOverflowMenuIfOpen();
951
-        this._doToggleScreenshare(enable);
952
-    }
953
-
954
-    _onToolbarToggleShareAudio: () => void;
955
-
956
-    /**
957
-     * Handles toggle share audio action.
958
-     *
959
-     * @returns {void}
960
-     */
961
-    _onToolbarToggleShareAudio() {
962
-        const enable = !this.props._screensharing;
947
+            { enable: !this.props._screensharing }));
963
 
948
 
964
         this._closeOverflowMenuIfOpen();
949
         this._closeOverflowMenuIfOpen();
965
-        this._doToggleScreenshare(enable, true);
950
+        this._doToggleScreenshare();
966
     }
951
     }
967
 
952
 
968
     _onToolbarOpenLocalRecordingInfoDialog: () => void;
953
     _onToolbarOpenLocalRecordingInfoDialog: () => void;
1095
             this.props._shouldShowButton('shareaudio')
1080
             this.props._shouldShowButton('shareaudio')
1096
                 && _desktopSharingEnabled
1081
                 && _desktopSharingEnabled
1097
                 && isScreenAudioSupported()
1082
                 && isScreenAudioSupported()
1098
-                && <OverflowMenuItem
1099
-                    accessibilityLabel = { t('toolbar.accessibilityLabel.shareaudio') }
1100
-                    icon = { IconShareAudio }
1083
+                && <ShareAudioButton
1101
                     key = 'shareaudio'
1084
                     key = 'shareaudio'
1102
-                    onClick = { this._onToolbarToggleShareAudio }
1103
-                    text = { t('toolbar.shareaudio') } />,
1085
+                    showLabel = { true } />,
1104
             this.props._shouldShowButton('etherpad')
1086
             this.props._shouldShowButton('etherpad')
1105
                 && <SharedDocumentButton
1087
                 && <SharedDocumentButton
1106
                     key = 'etherpad'
1088
                     key = 'etherpad'
1442
         _overflowMenuVisible: overflowMenuVisible,
1424
         _overflowMenuVisible: overflowMenuVisible,
1443
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1425
         _participantsPaneOpen: getParticipantsPaneOpen(state),
1444
         _raisedHand: localParticipant.raisedHand,
1426
         _raisedHand: localParticipant.raisedHand,
1445
-        _screensharing: (localVideo && localVideo.videoType === 'desktop') || isScreenAudioShared(state),
1427
+        _screensharing: isScreenVideoShared(state),
1446
         _shouldShowButton: buttonName => isToolbarButtonEnabled(buttonName)(state),
1428
         _shouldShowButton: buttonName => isToolbarButtonEnabled(buttonName)(state),
1447
         _visible: isToolboxVisible(state),
1429
         _visible: isToolboxVisible(state),
1448
         _visibleButtons: getToolbarButtons(state)
1430
         _visibleButtons: getToolbarButtons(state)

Loading…
Cancel
Save