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,7 +1867,6 @@ export default {
1867 1867
                     await this.useVideoStream(desktopVideoStream);
1868 1868
                 }
1869 1869
 
1870
-
1871 1870
                 if (this._desktopAudioStream) {
1872 1871
                     // If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing
1873 1872
                     // api.
@@ -2463,8 +2462,8 @@ export default {
2463 2462
         });
2464 2463
 
2465 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,6 +41,8 @@ $flagsImagePath: "../images/";
41 41
 @import 'modals/feedback/feedback';
42 42
 @import 'modals/invite/info';
43 43
 @import 'modals/settings/settings';
44
+@import 'modals/screen-share/share-audio';
45
+@import 'modals/screen-share/share-screen-warning';
44 46
 @import 'modals/speaker_stats/speaker_stats';
45 47
 @import 'modals/video-quality/video-quality';
46 48
 @import 'modals/virtual-background/virtual-background';

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

@@ -0,0 +1,22 @@
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

@@ -0,0 +1,23 @@
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,6 +215,7 @@
215 215
         "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.",
216 216
         "grantModeratorDialog": "Are you sure you want to make this participant a moderator?",
217 217
         "grantModeratorTitle": "Grant moderator",
218
+        "hideShareAudioHelper": "Don't show this dialog again",
218 219
         "IamHost": "I am the host",
219 220
         "incorrectRoomLockPassword": "Incorrect password",
220 221
         "incorrectPassword": "Incorrect username or password",
@@ -303,6 +304,13 @@
303 304
         "sessTerminated": "Call terminated",
304 305
         "sessionRestarted": "Call restarted by the bridge",
305 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 314
         "shareVideoLinkError": "Please provide a correct youtube link.",
307 315
         "shareVideoTitle": "Share video",
308 316
         "shareYourScreen": "Share your screen",
@@ -310,6 +318,10 @@
310 318
         "startLiveStreaming": "Start live stream",
311 319
         "startRecording": "Start recording",
312 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 325
         "stopLiveStreaming": "Stop live stream",
314 326
         "stopRecording": "Stop recording",
315 327
         "stopRecordingWarning": "Are you sure you would like to stop the recording?",
@@ -544,7 +556,7 @@
544 556
         "passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
545 557
         "raisedHand": "{{name}} would like to speak.",
546 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 560
         "somebody": "Somebody",
549 561
         "startSilentTitle": "You joined with no audio output!",
550 562
         "startSilentDescription": "Rejoin the meeting to enable audio",
@@ -899,6 +911,7 @@
899 911
         "speakerStats": "Speaker stats",
900 912
         "startScreenSharing": "Start screen sharing",
901 913
         "startSubtitles": "Start subtitles",
914
+        "stopAudioSharing": "Stop audio sharing",
902 915
         "stopScreenSharing": "Stop screen sharing",
903 916
         "stopSubtitles": "Stop subtitles",
904 917
         "stopSharedVideo": "Stop video",

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

@@ -49,6 +49,8 @@ import {
49 49
 import { toggleLobbyMode } from '../../react/features/lobby/actions';
50 50
 import { RECORDING_TYPES } from '../../react/features/recording/constants';
51 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 54
 import { playSharedVideo, stopSharedVideo } from '../../react/features/shared-video/actions.any';
53 55
 import { toggleTileView, setTileView } from '../../react/features/video-layout';
54 56
 import { muteAllParticipants } from '../../react/features/video-menu/actions';
@@ -220,6 +222,16 @@ function initCommands() {
220 222
             sendAnalytics(createApiEvent('raise-hand.toggled'));
221 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 237
          * Callback to invoke when the "toggle-share-screen" command is received.
@@ -596,9 +608,7 @@ function shouldBeEnabled() {
596 608
  */
597 609
 function toggleScreenSharing(enable) {
598 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,6 +60,7 @@ const commands = {
60 60
     toggleChat: 'toggle-chat',
61 61
     toggleFilmStrip: 'toggle-film-strip',
62 62
     toggleRaiseHand: 'toggle-raise-hand',
63
+    toggleShareAudio: 'toggle-share-audio',
63 64
     toggleShareScreen: 'toggle-share-screen',
64 65
     toggleTileView: 'toggle-tile-view',
65 66
     toggleVirtualBackgroundDialog: 'toggle-virtual-background',

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

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

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

@@ -0,0 +1,3 @@
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,3 +248,13 @@ function _getUserSelectedDeviceId(options) {
248 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,6 +26,7 @@ const DEFAULT_STATE = {
26 26
     localFlipX: true,
27 27
     micDeviceId: undefined,
28 28
     serverURL: undefined,
29
+    hideShareAudioHelper: false,
29 30
     startAudioOnly: false,
30 31
     startWithAudioMuted: false,
31 32
     startWithVideoMuted: false,

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

@@ -145,7 +145,10 @@ MiddlewareRegistry.register(store => next => action => {
145 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 153
         break;
151 154
 

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

@@ -1,6 +1,14 @@
1 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 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 14
  * Updates the current known status of the shared video.
@@ -33,3 +41,61 @@ export function setScreenshareFramerate(captureFrameRate: number) {
33 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

@@ -0,0 +1,80 @@
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

@@ -0,0 +1,128 @@
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

@@ -0,0 +1,105 @@
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

@@ -0,0 +1,2 @@
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,8 +1,20 @@
1 1
 // @flow
2 2
 
3 3
 import { isWindows } from '../base/environment';
4
+import { isMobileBrowser } from '../base/environment/utils';
4 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 20
  * State of audio sharing.
@@ -21,5 +33,28 @@ export function isScreenAudioShared(state: Object) {
21 33
  * @returns {boolean}
22 34
  */
23 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,4 +1,6 @@
1
+
1 2
 export * from './actions';
2 3
 export * from './actionTypes';
4
+export * from './components';
3 5
 export * from './functions';
4 6
 

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

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

Loading…
Cancel
Save