浏览代码

feat(screenshare): Audio only screenshare (#8922)

* audio only screen share implementation

* clean up

* handle stop screen share from chrome window

* update icon
j8
Andrei Gavrilescu 4 年前
父节点
当前提交
6d3d65da03
没有帐户链接到提交者的电子邮件

+ 45
- 3
conference.js 查看文件

@@ -6,6 +6,7 @@ import Logger from 'jitsi-meet-logger';
6 6
 
7 7
 import { openConnection } from './connection';
8 8
 import { ENDPOINT_TEXT_MESSAGE_NAME } from './modules/API/constants';
9
+import { AUDIO_ONLY_SCREEN_SHARE_NO_TRACK } from './modules/UI/UIErrors';
9 10
 import AuthHandler from './modules/UI/authentication/AuthHandler';
10 11
 import UIUtil from './modules/UI/util/UIUtil';
11 12
 import mediaDeviceHelper from './modules/devices/mediaDeviceHelper';
@@ -126,6 +127,7 @@ import {
126 127
     makePrecallTest
127 128
 } from './react/features/prejoin';
128 129
 import { disableReceiver, stopReceiver } from './react/features/remote-control';
130
+import { setScreenAudioShareState, isScreenAudioShared } from './react/features/screen-share/';
129 131
 import { toggleScreenshotCaptureEffect } from './react/features/screenshot-capture';
130 132
 import { setSharedVideoStatus } from './react/features/shared-video/actions';
131 133
 import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/AudioMixerEffect';
@@ -1546,6 +1548,8 @@ export default {
1546 1548
             this._desktopAudioStream = undefined;
1547 1549
         }
1548 1550
 
1551
+        APP.store.dispatch(setScreenAudioShareState(false));
1552
+
1549 1553
         if (didHaveVideo) {
1550 1554
             promise = promise.then(() => createLocalTracksF({ devices: [ 'video' ] }))
1551 1555
                 .then(([ stream ]) => {
@@ -1662,6 +1666,23 @@ export default {
1662 1666
                 = this._turnScreenSharingOff.bind(this, didHaveVideo);
1663 1667
 
1664 1668
             const desktopVideoStream = desktopStreams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
1669
+            const dekstopAudioStream = desktopStreams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
1670
+
1671
+            if (dekstopAudioStream) {
1672
+                dekstopAudioStream.on(
1673
+                    JitsiTrackEvents.LOCAL_TRACK_STOPPED,
1674
+                    () => {
1675
+                        logger.debug(`Local screensharing audio track stopped. ${this.isSharingScreen}`);
1676
+
1677
+                        // Handle case where screen share was stopped from  the browsers 'screen share in progress'
1678
+                        // window. If audio screen sharing is stopped via the normal UX flow this point shouldn't
1679
+                        // be reached.
1680
+                        isScreenAudioShared(APP.store.getState())
1681
+                            && this._untoggleScreenSharing
1682
+                            && this._untoggleScreenSharing();
1683
+                    }
1684
+                );
1685
+            }
1665 1686
 
1666 1687
             if (desktopVideoStream) {
1667 1688
                 desktopVideoStream.on(
@@ -1830,14 +1851,28 @@ export default {
1830 1851
 
1831 1852
         return this._createDesktopTrack(options)
1832 1853
             .then(async streams => {
1833
-                const desktopVideoStream = streams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
1854
+                let desktopVideoStream = streams.find(stream => stream.getType() === MEDIA_TYPE.VIDEO);
1855
+
1856
+                this._desktopAudioStream = streams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
1857
+
1858
+                const { audioOnly = false } = options;
1859
+
1860
+                // If we're in audio only mode dispose of the video track otherwise the screensharing state will be
1861
+                // inconsistent.
1862
+                if (audioOnly) {
1863
+                    desktopVideoStream.dispose();
1864
+                    desktopVideoStream = undefined;
1865
+
1866
+                    if (!this._desktopAudioStream) {
1867
+                        return Promise.reject(AUDIO_ONLY_SCREEN_SHARE_NO_TRACK);
1868
+                    }
1869
+                }
1834 1870
 
1835 1871
                 if (desktopVideoStream) {
1836 1872
                     logger.debug(`_switchToScreenSharing is using ${desktopVideoStream} for useVideoStream`);
1837 1873
                     await this.useVideoStream(desktopVideoStream);
1838 1874
                 }
1839 1875
 
1840
-                this._desktopAudioStream = streams.find(stream => stream.getType() === MEDIA_TYPE.AUDIO);
1841 1876
 
1842 1877
                 if (this._desktopAudioStream) {
1843 1878
                     // If there is a localAudio stream, mix in the desktop audio stream captured by the screen sharing
@@ -1850,7 +1885,9 @@ export default {
1850 1885
                         // If no local stream is present ( i.e. no input audio devices) we use the screen share audio
1851 1886
                         // stream as we would use a regular stream.
1852 1887
                         await this.useAudioStream(this._desktopAudioStream);
1888
+
1853 1889
                     }
1890
+                    APP.store.dispatch(setScreenAudioShareState(true));
1854 1891
                 }
1855 1892
             })
1856 1893
             .then(() => {
@@ -1918,6 +1955,9 @@ export default {
1918 1955
         } else if (error.name === JitsiTrackErrors.SCREENSHARING_GENERIC_ERROR) {
1919 1956
             descriptionKey = 'dialog.screenSharingFailed';
1920 1957
             titleKey = 'dialog.screenSharingFailedTitle';
1958
+        } else if (error === AUDIO_ONLY_SCREEN_SHARE_NO_TRACK) {
1959
+            descriptionKey = 'notify.screenShareNoAudio';
1960
+            titleKey = 'notify.screenShareNoAudioTitle';
1921 1961
         }
1922 1962
 
1923 1963
         APP.UI.messageHandler.showError({
@@ -2409,7 +2449,9 @@ export default {
2409 2449
         });
2410 2450
 
2411 2451
         APP.UI.addListener(
2412
-            UIEvents.TOGGLE_SCREENSHARING, this.toggleScreenSharing.bind(this)
2452
+            UIEvents.TOGGLE_SCREENSHARING, audioOnly => {
2453
+                this.toggleScreenSharing(undefined, { audioOnly });
2454
+            }
2413 2455
         );
2414 2456
 
2415 2457
         /* eslint-disable max-params */

+ 1
- 1
config.js 查看文件

@@ -428,7 +428,7 @@ var config = {
428 428
     // toolbarButtons: [
429 429
     //    'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
430 430
     //    'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
431
-    //    'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
431
+    //    'livestreaming', 'etherpad', 'sharedvideo', 'shareaudio', 'settings', 'raisehand',
432 432
     //    'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
433 433
     //    'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone', 'security'
434 434
     // ],

+ 4
- 0
lang/main.json 查看文件

@@ -511,6 +511,8 @@
511 511
         "passwordRemovedRemotely": "$t(lockRoomPasswordUppercase) removed by another participant",
512 512
         "passwordSetRemotely": "$t(lockRoomPasswordUppercase) set by another participant",
513 513
         "raisedHand": "{{name}} would like to speak.",
514
+        "screenShareNoAudio": " Share audio box was not checked in the window selection screen.",
515
+        "screenShareNoAudioTitle": "Share audio was not chcked",
514 516
         "somebody": "Somebody",
515 517
         "startSilentTitle": "You joined with no audio output!",
516 518
         "startSilentDescription": "Rejoin the meeting to enable audio",
@@ -752,6 +754,7 @@
752 754
             "remoteVideoMute": "Disable camera of participant",
753 755
             "security": "Security options",
754 756
             "Settings": "Toggle settings",
757
+            "shareaudio": "Share audio",
755 758
             "sharedvideo": "Toggle YouTube video sharing",
756 759
             "shareRoom": "Invite someone",
757 760
             "shareYourScreen": "Toggle screenshare",
@@ -811,6 +814,7 @@
811 814
         "raiseYourHand": "Raise your hand",
812 815
         "security": "Security options",
813 816
         "Settings": "Settings",
817
+        "shareaudio": "Share audio",
814 818
         "sharedvideo": "Share a YouTube video",
815 819
         "shareRoom": "Invite someone",
816 820
         "shortcuts": "View shortcuts",

+ 7
- 0
modules/UI/UIErrors.js 查看文件

@@ -8,3 +8,10 @@
8 8
  * @type {{FEEDBACK_REQUEST_IN_PROGRESS: string}}
9 9
  */
10 10
 export const FEEDBACK_REQUEST_IN_PROGRESS = 'FeedbackRequestInProgress';
11
+
12
+/**
13
+ * Indicated an attempted audio only screen share session with no audio track present
14
+ *
15
+ * @type {{AUDIO_ONLY_SCREEN_SHARE_NO_TRACK: string}}
16
+ */
17
+export const AUDIO_ONLY_SCREEN_SHARE_NO_TRACK = 'AudioOnlyScreenShareNoTrack';

+ 1
- 0
react/features/app/reducers.any.js 查看文件

@@ -44,6 +44,7 @@ import '../recent-list/reducer';
44 44
 import '../recording/reducer';
45 45
 import '../settings/reducer';
46 46
 import '../subtitles/reducer';
47
+import '../screen-share/reducer';
47 48
 import '../toolbox/reducer';
48 49
 import '../transcribing/reducer';
49 50
 import '../video-layout/reducer';

+ 1
- 1
react/features/base/conference/middleware.web.js 查看文件

@@ -32,7 +32,7 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
32 32
     }
33 33
     case TOGGLE_SCREENSHARING: {
34 34
         if (typeof APP === 'object') {
35
-            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
35
+            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING, action.audioOnly);
36 36
         }
37 37
 
38 38
         break;

+ 1
- 1
react/features/base/config/constants.js 查看文件

@@ -16,7 +16,7 @@ export const _CONFIG_STORE_PREFIX = 'config.js';
16 16
 export const TOOLBAR_BUTTONS = [
17 17
     'microphone', 'camera', 'closedcaptions', 'desktop', 'embedmeeting', 'fullscreen',
18 18
     'fodeviceselection', 'hangup', 'profile', 'chat', 'recording',
19
-    'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand',
19
+    'livestreaming', 'etherpad', 'sharedvideo', 'shareaudio', 'settings', 'raisehand',
20 20
     'videoquality', 'filmstrip', 'invite', 'feedback', 'stats', 'shortcuts',
21 21
     'tileview', 'select-background', 'download', 'help', 'mute-everyone', 'mute-video-everyone',
22 22
     'security', 'toggle-camera'

+ 9
- 0
react/features/base/environment/environment.js 查看文件

@@ -41,6 +41,15 @@ export function isBrowsersOptimal(browserName: string) {
41 41
         .includes(browserName);
42 42
 }
43 43
 
44
+/**
45
+ * Returns whether or not the current OS is Mac.
46
+ *
47
+ * @returns {boolean}
48
+ */
49
+export function isMacOS() {
50
+    return Platform.OS === 'macos';
51
+}
52
+
44 53
 /**
45 54
  * Returns whether or not the current browser or the list of passed in browsers
46 55
  * is considered suboptimal. Suboptimal means it is a supported browser but has

+ 1
- 0
react/features/base/icons/svg/index.js 查看文件

@@ -99,6 +99,7 @@ export { default as IconSignalLevel0 } from './signal_cellular_0.svg';
99 99
 export { default as IconSignalLevel1 } from './signal_cellular_1.svg';
100 100
 export { default as IconSignalLevel2 } from './signal_cellular_2.svg';
101 101
 export { default as IconShare } from './share.svg';
102
+export { default as IconShareAudio } from './share-audio.svg';
102 103
 export { default as IconShareDesktop } from './share-desktop.svg';
103 104
 export { default as IconShareDoc } from './share-doc.svg';
104 105
 export { default as IconShareVideo } from './shared-video.svg';

+ 3
- 0
react/features/base/icons/svg/share-audio.svg 查看文件

@@ -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="M9.31658 3.06949L4.99999 6.66664H2.49999C2.03975 6.66664 1.66666 7.03974 1.66666 7.49998V12.5C1.66666 12.9602 2.03975 13.3333 2.49999 13.3333H4.99999L9.31658 16.9305C9.39146 16.9929 9.48585 17.027 9.58332 17.027C9.81344 17.027 9.99999 16.8405 9.99999 16.6104V3.38958C9.99999 3.2921 9.96582 3.19772 9.90341 3.12283C9.7561 2.94605 9.49336 2.92217 9.31658 3.06949ZM3.33332 8.33331H5.60341L8.33332 6.05838V13.9416L5.60341 11.6666H3.33332V8.33331ZM11.6667 6.66664C13.5076 6.66664 15 8.15903 15 9.99998C15 11.8409 13.5076 13.3333 11.6667 13.3333V11.6666C12.5871 11.6666 13.3333 10.9205 13.3333 9.99998C13.3333 9.0795 12.5871 8.33331 11.6667 8.33331V6.66664ZM11.6667 3.33331C15.3486 3.33331 18.3333 6.31808 18.3333 9.99998C18.3333 13.6819 15.3486 16.6666 11.6667 16.6666V15C14.4281 15 16.6667 12.7614 16.6667 9.99998C16.6667 7.23855 14.4281 4.99998 11.6667 4.99998V3.33331Z" fill="white"/>
3
+</svg>

+ 5
- 2
react/features/base/tracks/actions.js 查看文件

@@ -257,13 +257,16 @@ export function showNoDataFromSourceVideoError(jitsiTrack) {
257 257
  * Signals that the local participant is ending screensharing or beginning the
258 258
  * screensharing flow.
259 259
  *
260
+ * @param {boolean} audioOnly - Only share system audio.
260 261
  * @returns {{
261 262
  *     type: TOGGLE_SCREENSHARING,
263
+ *     audioOnly: boolean
262 264
  * }}
263 265
  */
264
-export function toggleScreensharing() {
266
+export function toggleScreensharing(audioOnly = false) {
265 267
     return {
266
-        type: TOGGLE_SCREENSHARING
268
+        type: TOGGLE_SCREENSHARING,
269
+        audioOnly
267 270
     };
268 271
 }
269 272
 

+ 1
- 1
react/features/base/tracks/middleware.js 查看文件

@@ -135,7 +135,7 @@ MiddlewareRegistry.register(store => next => action => {
135 135
 
136 136
     case TOGGLE_SCREENSHARING:
137 137
         if (typeof APP === 'object') {
138
-            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING);
138
+            APP.UI.emitEvent(UIEvents.TOGGLE_SCREENSHARING, action.audioOnly);
139 139
         }
140 140
         break;
141 141
 

+ 12
- 0
react/features/screen-share/actionTypes.js 查看文件

@@ -0,0 +1,12 @@
1
+// @flow
2
+
3
+/**
4
+ * Type of action which sets the current state of screen audio sharing.
5
+ *
6
+ * {
7
+ *     type: SET_SCREEN_AUDIO_SHARE_STATE,
8
+ *     isSharingAudio: boolean
9
+ * }
10
+ */
11
+export const SET_SCREEN_AUDIO_SHARE_STATE = 'SET_SCREEN_AUDIO_SHARE_STATE';
12
+

+ 19
- 0
react/features/screen-share/actions.js 查看文件

@@ -0,0 +1,19 @@
1
+// @flow
2
+
3
+import { SET_SCREEN_AUDIO_SHARE_STATE } from './actionTypes';
4
+
5
+/**
6
+ * Updates the current known status of the shared video.
7
+ *
8
+ * @param {boolean} isSharingAudio - Is audio currently being shared or not.
9
+ * @returns {{
10
+ *     type: SET_SCREEN_AUDIO_SHARE_STATE,
11
+ *     isSharingAudio: boolean
12
+ * }}
13
+ */
14
+export function setScreenAudioShareState(isSharingAudio: boolean) {
15
+    return {
16
+        type: SET_SCREEN_AUDIO_SHARE_STATE,
17
+        isSharingAudio
18
+    };
19
+}

+ 25
- 0
react/features/screen-share/functions.js 查看文件

@@ -0,0 +1,25 @@
1
+// @flow
2
+
3
+import { isMacOS } from '../base/environment';
4
+import { browser } from '../base/lib-jitsi-meet';
5
+
6
+
7
+/**
8
+ * State of audio sharing.
9
+ *
10
+ * @param {Object} state - The state of the application.
11
+ * @returns {boolean}
12
+ */
13
+export function isScreenAudioShared(state: Object) {
14
+    return state['features/screen-share'].isSharingAudio;
15
+}
16
+
17
+/**
18
+ * Returns the visibility of the audio only screen share button. Currently electron on mac os doesn't
19
+ * have support for this functionality.
20
+ *
21
+ * @returns {boolean}
22
+ */
23
+export function isScreenAudioSupported() {
24
+    return !(browser.isElectron() && isMacOS());
25
+}

+ 4
- 0
react/features/screen-share/index.js 查看文件

@@ -0,0 +1,4 @@
1
+export * from './actions';
2
+export * from './actionTypes';
3
+export * from './functions';
4
+

+ 22
- 0
react/features/screen-share/reducer.js 查看文件

@@ -0,0 +1,22 @@
1
+
2
+import { ReducerRegistry } from '../base/redux';
3
+
4
+import { SET_SCREEN_AUDIO_SHARE_STATE } from './actionTypes';
5
+
6
+/**
7
+ * Reduces the Redux actions of the feature features/screen-share.
8
+ */
9
+ReducerRegistry.register('features/screen-share', (state = {}, action) => {
10
+    const { isSharingAudio } = action;
11
+
12
+    switch (action.type) {
13
+    case SET_SCREEN_AUDIO_SHARE_STATE:
14
+        return {
15
+            ...state,
16
+            isSharingAudio
17
+        };
18
+
19
+    default:
20
+        return state;
21
+    }
22
+});

+ 23
- 3
react/features/stream-effects/audio-mixer/AudioMixerEffect.js 查看文件

@@ -14,6 +14,16 @@ export class AudioMixerEffect {
14 14
      */
15 15
     _mixAudio: Object;
16 16
 
17
+    /**
18
+     * MediaStream resulted from mixing.
19
+     */
20
+    _mixedMediaStream: Object;
21
+
22
+    /**
23
+     * MediaStreamTrack obtained from mixed stream.
24
+     */
25
+    _mixedMediaTrack: Object;
26
+
17 27
     /**
18 28
      * Original MediaStream from the JitsiLocalTrack that uses this effect.
19 29
      */
@@ -68,7 +78,14 @@ export class AudioMixerEffect {
68 78
         this._audioMixer.addMediaStream(this._mixAudio.getOriginalStream());
69 79
         this._audioMixer.addMediaStream(this._originalStream);
70 80
 
71
-        return this._audioMixer.start();
81
+        this._mixedMediaStream = this._audioMixer.start();
82
+        this._mixedMediaTrack = this._mixedMediaStream.getTracks()[0];
83
+
84
+        // Sync the resulting mixed track enabled state with that of the track using the effect.
85
+        this.setMuted(!this._originalTrack.enabled);
86
+        this._originalTrack.enabled = true;
87
+
88
+        return this._mixedMediaStream;
72 89
     }
73 90
 
74 91
     /**
@@ -77,6 +94,9 @@ export class AudioMixerEffect {
77 94
      * @returns {void}
78 95
      */
79 96
     stopEffect() {
97
+        // Match state of the original track with that of the mixer track, not doing so can
98
+        // result in an inconsistent state e.g. redux state is muted yet track is enabled.
99
+        this._originalTrack.enabled = this._mixedMediaTrack.enabled;
80 100
         this._audioMixer.reset();
81 101
     }
82 102
 
@@ -87,7 +107,7 @@ export class AudioMixerEffect {
87 107
      * @returns {void}
88 108
      */
89 109
     setMuted(muted: boolean) {
90
-        this._originalTrack.enabled = !muted;
110
+        this._mixedMediaTrack.enabled = !muted;
91 111
     }
92 112
 
93 113
     /**
@@ -96,6 +116,6 @@ export class AudioMixerEffect {
96 116
      * @returns {boolean}
97 117
      */
98 118
     isMuted() {
99
-        return !this._originalTrack.enabled;
119
+        return !this._mixedMediaTrack.enabled;
100 120
     }
101 121
 }

+ 26
- 3
react/features/toolbox/components/web/Toolbox.js 查看文件

@@ -23,6 +23,7 @@ import {
23 23
     IconPresentation,
24 24
     IconRaisedHand,
25 25
     IconRec,
26
+    IconShareAudio,
26 27
     IconShareDesktop
27 28
 } from '../../../base/icons';
28 29
 import JitsiMeetJS from '../../../base/lib-jitsi-meet';
@@ -47,6 +48,7 @@ import {
47 48
     LiveStreamButton,
48 49
     RecordButton
49 50
 } from '../../../recording';
51
+import { isScreenAudioShared, isScreenAudioSupported } from '../../../screen-share/';
50 52
 import SecurityDialogButton from '../../../security/components/security-dialog/SecurityDialogButton';
51 53
 import {
52 54
     SETTINGS_TABS,
@@ -252,6 +254,7 @@ class Toolbox extends Component<Props> {
252 254
         this._onToolbarToggleProfile = this._onToolbarToggleProfile.bind(this);
253 255
         this._onToolbarToggleRaiseHand = this._onToolbarToggleRaiseHand.bind(this);
254 256
         this._onToolbarToggleScreenshare = this._onToolbarToggleScreenshare.bind(this);
257
+        this._onToolbarToggleShareAudio = this._onToolbarToggleShareAudio.bind(this);
255 258
         this._onToolbarOpenLocalRecordingInfoDialog = this._onToolbarOpenLocalRecordingInfoDialog.bind(this);
256 259
         this._onShortcutToggleTileView = this._onShortcutToggleTileView.bind(this);
257 260
     }
@@ -487,11 +490,12 @@ class Toolbox extends Component<Props> {
487 490
      * Dispatches an action to toggle screensharing.
488 491
      *
489 492
      * @private
493
+     * @param {boolean} audioOnly - Only share system audio.
490 494
      * @returns {void}
491 495
      */
492
-    _doToggleScreenshare() {
496
+    _doToggleScreenshare(audioOnly = false) {
493 497
         if (this.props._desktopSharingEnabled) {
494
-            this.props.dispatch(toggleScreensharing());
498
+            this.props.dispatch(toggleScreensharing(audioOnly));
495 499
         }
496 500
     }
497 501
 
@@ -857,6 +861,18 @@ class Toolbox extends Component<Props> {
857 861
         this._doToggleScreenshare();
858 862
     }
859 863
 
864
+    _onToolbarToggleShareAudio: () => void;
865
+
866
+    /**
867
+     * Handles toggle share audio action.
868
+     *
869
+     * @returns {void}
870
+     */
871
+    _onToolbarToggleShareAudio() {
872
+        this._closeOverflowMenuIfOpen();
873
+        this._doToggleScreenshare(true);
874
+    }
875
+
860 876
     _onToolbarOpenLocalRecordingInfoDialog: () => void;
861 877
 
862 878
     /**
@@ -983,6 +999,13 @@ class Toolbox extends Component<Props> {
983 999
                 && <SharedVideoButton
984 1000
                     key = 'sharedvideo'
985 1001
                     showLabel = { true } />,
1002
+            this._shouldShowButton('shareaudio') && isScreenAudioSupported()
1003
+                && <OverflowMenuItem
1004
+                    accessibilityLabel = { t('toolbar.accessibilityLabel.shareaudio') }
1005
+                    icon = { IconShareAudio }
1006
+                    key = 'shareaudio'
1007
+                    onClick = { this._onToolbarToggleShareAudio }
1008
+                    text = { t('toolbar.shareaudio') } />,
986 1009
             this._shouldShowButton('etherpad')
987 1010
                 && <SharedDocumentButton
988 1011
                     key = 'etherpad'
@@ -1322,7 +1345,7 @@ function _mapStateToProps(state) {
1322 1345
         _locked: locked,
1323 1346
         _overflowMenuVisible: overflowMenuVisible,
1324 1347
         _raisedHand: localParticipant.raisedHand,
1325
-        _screensharing: localVideo && localVideo.videoType === 'desktop',
1348
+        _screensharing: (localVideo && localVideo.videoType === 'desktop') || isScreenAudioShared(state),
1326 1349
         _visible: isToolboxVisible(state),
1327 1350
         _visibleButtons: getToolbarButtons(state)
1328 1351
     };

正在加载...
取消
保存