瀏覽代碼

feat(raise-hand) add ability for the moderator to lower hands

factor2
Mengyuan Liu 1 年之前
父節點
當前提交
1376f5909c
沒有連結到貢獻者的電子郵件帳戶。

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

839
             "breakoutRooms": "Breakout rooms",
839
             "breakoutRooms": "Breakout rooms",
840
             "goLive": "Go live",
840
             "goLive": "Go live",
841
             "invite": "Invite Someone",
841
             "invite": "Invite Someone",
842
+            "lowerAllHands": "Lower all hands",
843
+            "lowerHand": "Lower the hand",
842
             "moreModerationActions": "More moderation options",
844
             "moreModerationActions": "More moderation options",
843
             "moreModerationControls": "More moderation controls",
845
             "moreModerationControls": "More moderation controls",
844
             "moreParticipantOptions": "More participant options",
846
             "moreParticipantOptions": "More participant options",

+ 1
- 0
react/features/base/tracks/constants.ts 查看文件

2
  * The payload name for remotely setting the camera facing mode message.
2
  * The payload name for remotely setting the camera facing mode message.
3
  */
3
  */
4
 export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message';
4
 export const CAMERA_FACING_MODE_MESSAGE = 'camera-facing-mode-message';
5
+export const LOWER_HAND_MESSAGE = 'lower-hand-message';

+ 13
- 2
react/features/conference/middleware.any.ts 查看文件

9
 import {
9
 import {
10
     CONFERENCE_FAILED,
10
     CONFERENCE_FAILED,
11
     CONFERENCE_JOINED,
11
     CONFERENCE_JOINED,
12
-    CONFERENCE_LEFT
12
+    CONFERENCE_LEFT,
13
+    ENDPOINT_MESSAGE_RECEIVED
13
 } from '../base/conference/actionTypes';
14
 } from '../base/conference/actionTypes';
14
 import { getCurrentConference } from '../base/conference/functions';
15
 import { getCurrentConference } from '../base/conference/functions';
15
 import { getURLWithoutParamsNormalized } from '../base/connection/utils';
16
 import { getURLWithoutParamsNormalized } from '../base/connection/utils';
19
 import { translateToHTML } from '../base/i18n/functions';
20
 import { translateToHTML } from '../base/i18n/functions';
20
 import i18next from '../base/i18n/i18next';
21
 import i18next from '../base/i18n/i18next';
21
 import { browser } from '../base/lib-jitsi-meet';
22
 import { browser } from '../base/lib-jitsi-meet';
22
-import { pinParticipant, raiseHandClear } from '../base/participants/actions';
23
+import { pinParticipant, raiseHand, raiseHandClear } from '../base/participants/actions';
23
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
24
 import MiddlewareRegistry from '../base/redux/MiddlewareRegistry';
24
 import StateListenerRegistry from '../base/redux/StateListenerRegistry';
25
 import StateListenerRegistry from '../base/redux/StateListenerRegistry';
25
 import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
26
 import { SET_REDUCED_UI } from '../base/responsive-ui/actionTypes';
27
+import { LOWER_HAND_MESSAGE } from '../base/tracks/constants';
26
 import { BUTTON_TYPES } from '../base/ui/constants.any';
28
 import { BUTTON_TYPES } from '../base/ui/constants.any';
27
 import { inIframe } from '../base/util/iframeUtils';
29
 import { inIframe } from '../base/util/iframeUtils';
28
 import { isCalendarEnabled } from '../calendar-sync/functions';
30
 import { isCalendarEnabled } from '../calendar-sync/functions';
71
 
73
 
72
         break;
74
         break;
73
     }
75
     }
76
+    case ENDPOINT_MESSAGE_RECEIVED: {
77
+        const { participant, data } = action;
78
+        const { dispatch } = store;
79
+
80
+        if (data.name === LOWER_HAND_MESSAGE && participant.isModerator()) {
81
+            dispatch(raiseHand(false));
82
+        }
83
+        break;
84
+    }
74
     }
85
     }
75
 
86
 
76
     return result;
87
     return result;

+ 22
- 2
react/features/participants-pane/components/native/ContextMenuMore.tsx 查看文件

15
     isEnabled as isAvModerationEnabled,
15
     isEnabled as isAvModerationEnabled,
16
     isSupported as isAvModerationSupported
16
     isSupported as isAvModerationSupported
17
 } from '../../../av-moderation/functions';
17
 } from '../../../av-moderation/functions';
18
+import { getCurrentConference } from '../../../base/conference/functions';
18
 import { hideSheet, openDialog } from '../../../base/dialog/actions';
19
 import { hideSheet, openDialog } from '../../../base/dialog/actions';
19
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
20
 import BottomSheet from '../../../base/dialog/components/native/BottomSheet';
20
 import Icon from '../../../base/icons/components/Icon';
21
 import Icon from '../../../base/icons/components/Icon';
21
-import { IconCheck, IconVideoOff } from '../../../base/icons/svg';
22
+import { IconCheck, IconRaiseHand, IconVideoOff } from '../../../base/icons/svg';
22
 import { MEDIA_TYPE } from '../../../base/media/constants';
23
 import { MEDIA_TYPE } from '../../../base/media/constants';
23
-import { getParticipantCount, isEveryoneModerator } from '../../../base/participants/functions';
24
+import { raiseHand } from '../../../base/participants/actions';
25
+import { getParticipantCount, getRaiseHandsQueue, isEveryoneModerator, isLocalParticipantModerator }
26
+    from '../../../base/participants/functions';
27
+import { LOWER_HAND_MESSAGE } from '../../../base/tracks/constants';
24
 import MuteEveryonesVideoDialog
28
 import MuteEveryonesVideoDialog
25
     from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
29
     from '../../../video-menu/components/native/MuteEveryonesVideoDialog';
26
 
30
 
32
         dispatch(openDialog(MuteEveryonesVideoDialog));
36
         dispatch(openDialog(MuteEveryonesVideoDialog));
33
         dispatch(hideSheet());
37
         dispatch(hideSheet());
34
     }, [ dispatch ]);
38
     }, [ dispatch ]);
39
+    const conference = useSelector(getCurrentConference);
40
+    const raisedHandsQueue = useSelector(getRaiseHandsQueue);
41
+    const moderator = useSelector(isLocalParticipantModerator);
42
+    const lowerAllHands = useCallback(() => {
43
+        dispatch(raiseHand(false));
44
+        conference?.sendEndpointMessage('', { name: LOWER_HAND_MESSAGE });
45
+        dispatch(hideSheet());
46
+    }, [ dispatch ]);
35
     const { t } = useTranslation();
47
     const { t } = useTranslation();
36
 
48
 
37
     const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
49
     const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
59
                     src = { IconVideoOff } />
71
                     src = { IconVideoOff } />
60
                 <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
72
                 <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.stopEveryonesVideo')}</Text>
61
             </TouchableOpacity>
73
             </TouchableOpacity>
74
+            { moderator && raisedHandsQueue.length !== 0 && <TouchableOpacity
75
+                onPress = { lowerAllHands }
76
+                style = { styles.contextMenuItem as ViewStyle }>
77
+                <Icon
78
+                    size = { 24 }
79
+                    src = { IconRaiseHand } />
80
+                <Text style = { styles.contextMenuItemText }>{t('participantsPane.actions.lowerAllHands')}</Text>
81
+            </TouchableOpacity> }
62
             {isModerationSupported && ((participantCount === 1 || !allModerators)) && <>
82
             {isModerationSupported && ((participantCount === 1 || !allModerators)) && <>
63
                 {/* @ts-ignore */}
83
                 {/* @ts-ignore */}
64
                 <Divider style = { styles.divider } />
84
                 <Divider style = { styles.divider } />

+ 4
- 0
react/features/participants-pane/components/web/FooterContextMenu.tsx 查看文件

23
 import { MEDIA_TYPE } from '../../../base/media/constants';
23
 import { MEDIA_TYPE } from '../../../base/media/constants';
24
 import {
24
 import {
25
     getParticipantCount,
25
     getParticipantCount,
26
+    getRaiseHandsQueue,
26
     isEveryoneModerator
27
     isEveryoneModerator
27
 } from '../../../base/participants/functions';
28
 } from '../../../base/participants/functions';
28
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
29
 import { withPixelLineHeight } from '../../../base/styles/functions.web';
32
 import { openSettingsDialog } from '../../../settings/actions.web';
33
 import { openSettingsDialog } from '../../../settings/actions.web';
33
 import { SETTINGS_TABS } from '../../../settings/constants';
34
 import { SETTINGS_TABS } from '../../../settings/constants';
34
 import { shouldShowModeratorSettings } from '../../../settings/functions.web';
35
 import { shouldShowModeratorSettings } from '../../../settings/functions.web';
36
+import LowerHandButton from '../../../video-menu/components/web/LowerHandButton';
35
 import MuteEveryonesVideoDialog from '../../../video-menu/components/web/MuteEveryonesVideoDialog';
37
 import MuteEveryonesVideoDialog from '../../../video-menu/components/web/MuteEveryonesVideoDialog';
36
 
38
 
37
 const useStyles = makeStyles()(theme => {
39
 const useStyles = makeStyles()(theme => {
85
 export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProps) => {
87
 export const FooterContextMenu = ({ isOpen, onDrawerClose, onMouseLeave }: IProps) => {
86
     const dispatch = useDispatch();
88
     const dispatch = useDispatch();
87
     const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
89
     const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
90
+    const raisedHandsQueue = useSelector(getRaiseHandsQueue);
88
     const allModerators = useSelector(isEveryoneModerator);
91
     const allModerators = useSelector(isEveryoneModerator);
89
     const isModeratorSettingsTabEnabled = useSelector(shouldShowModeratorSettings);
92
     const isModeratorSettingsTabEnabled = useSelector(shouldShowModeratorSettings);
90
     const participantCount = useSelector(getParticipantCount);
93
     const participantCount = useSelector(getParticipantCount);
147
                     onClick: muteAllVideo,
150
                     onClick: muteAllVideo,
148
                     text: t('participantsPane.actions.stopEveryonesVideo')
151
                     text: t('participantsPane.actions.stopEveryonesVideo')
149
                 } ] } />
152
                 } ] } />
153
+            {raisedHandsQueue.length !== 0 && <LowerHandButton />}
150
             {!isBreakoutRoom && isModerationSupported && (participantCount === 1 || !allModerators) && (
154
             {!isBreakoutRoom && isModerationSupported && (participantCount === 1 || !allModerators) && (
151
                 <ContextMenuItemGroup actions = { actions }>
155
                 <ContextMenuItemGroup actions = { actions }>
152
                     <div className = { classes.text }>
156
                     <div className = { classes.text }>

+ 71
- 0
react/features/video-menu/components/native/LowerHandButton.tsx 查看文件

1
+import { connect } from 'react-redux';
2
+
3
+import { IReduxState } from '../../../app/types';
4
+import { getCurrentConference } from '../../../base/conference/functions';
5
+import { IJitsiConference } from '../../../base/conference/reducer';
6
+import { translate } from '../../../base/i18n/functions';
7
+import { IconRaiseHand } from '../../../base/icons/svg';
8
+import AbstractButton, { IProps as AbstractButtonProps } from '../../../base/toolbox/components/AbstractButton';
9
+import { LOWER_HAND_MESSAGE } from '../../../base/tracks/constants';
10
+
11
+interface IProps extends AbstractButtonProps {
12
+
13
+    /**
14
+     * The current conference.
15
+     */
16
+    _conference: IJitsiConference | undefined;
17
+
18
+    /**
19
+     * The ID of the participant object that this button is supposed to
20
+     * ask to lower the hand.
21
+     */
22
+    participantId: String | undefined;
23
+}
24
+
25
+/**
26
+ * Implements a React {@link Component} which displays a button for lowering certain
27
+ * participant raised hands.
28
+ *
29
+ * @returns {JSX.Element}
30
+ */
31
+class LowerHandButton extends AbstractButton<IProps> {
32
+    icon = IconRaiseHand;
33
+    accessibilityLabel = 'participantsPane.actions.lowerHand';
34
+    label = 'participantsPane.actions.lowerHand';
35
+
36
+    /**
37
+     * Handles clicking / pressing the button, and asks the participant to lower hand.
38
+     *
39
+     * @private
40
+     * @returns {void}
41
+     */
42
+    _handleClick() {
43
+        const { participantId, _conference } = this.props;
44
+
45
+        _conference?.sendEndpointMessage(
46
+            participantId,
47
+            {
48
+                name: LOWER_HAND_MESSAGE
49
+            }
50
+        );
51
+    }
52
+}
53
+
54
+/**
55
+ * Maps part of the Redux state to the props of this component.
56
+ *
57
+ * @param {Object} state - The Redux state.
58
+ * @param {Object} ownProps - Properties of component.
59
+ * @returns {IProps}
60
+ */
61
+function mapStateToProps(state: IReduxState, ownProps: any) {
62
+    const { participantID } = ownProps;
63
+    const currentConference = getCurrentConference(state);
64
+
65
+    return {
66
+        _conference: currentConference,
67
+        participantId: participantID
68
+    };
69
+}
70
+
71
+export default translate(connect(mapStateToProps)(LowerHandButton));

+ 11
- 0
react/features/video-menu/components/native/RemoteVideoMenu.tsx 查看文件

16
 import {
16
 import {
17
     getParticipantById,
17
     getParticipantById,
18
     getParticipantDisplayName,
18
     getParticipantDisplayName,
19
+    hasRaisedHand,
19
     isLocalParticipantModerator
20
     isLocalParticipantModerator
20
 } from '../../../base/participants/functions';
21
 } from '../../../base/participants/functions';
21
 import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
22
 import { getBreakoutRooms, getCurrentRoomId, isInBreakoutRoom } from '../../../breakout-rooms/functions';
27
 import DemoteToVisitorButton from './DemoteToVisitorButton';
28
 import DemoteToVisitorButton from './DemoteToVisitorButton';
28
 import GrantModeratorButton from './GrantModeratorButton';
29
 import GrantModeratorButton from './GrantModeratorButton';
29
 import KickButton from './KickButton';
30
 import KickButton from './KickButton';
31
+import LowerHandButton from './LowerHandButton';
30
 import MuteButton from './MuteButton';
32
 import MuteButton from './MuteButton';
31
 import MuteEveryoneElseButton from './MuteEveryoneElseButton';
33
 import MuteEveryoneElseButton from './MuteEveryoneElseButton';
32
 import MuteVideoButton from './MuteVideoButton';
34
 import MuteVideoButton from './MuteVideoButton';
93
      */
95
      */
94
     _participantDisplayName: string;
96
     _participantDisplayName: string;
95
 
97
 
98
+    /**
99
+     * Whether the targeted participant raised hand or not.
100
+     */
101
+    _raisedHand: boolean;
102
+
96
     /**
103
     /**
97
      * Array containing the breakout rooms.
104
      * Array containing the breakout rooms.
98
      */
105
      */
150
             _isParticipantAvailable,
157
             _isParticipantAvailable,
151
             _isParticipantSilent,
158
             _isParticipantSilent,
152
             _moderator,
159
             _moderator,
160
+            _raisedHand,
153
             _rooms,
161
             _rooms,
154
             _showDemote,
162
             _showDemote,
155
             _currentRoomId,
163
             _currentRoomId,
175
                 {!_isParticipantSilent && <AskUnmuteButton { ...buttonProps } />}
183
                 {!_isParticipantSilent && <AskUnmuteButton { ...buttonProps } />}
176
                 { !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
184
                 { !_disableRemoteMute && <MuteButton { ...buttonProps } /> }
177
                 <MuteEveryoneElseButton { ...buttonProps } />
185
                 <MuteEveryoneElseButton { ...buttonProps } />
186
+                { _moderator && _raisedHand && <LowerHandButton { ...buttonProps } /> }
178
                 { !_disableRemoteMute && !_isParticipantSilent && <MuteVideoButton { ...buttonProps } /> }
187
                 { !_disableRemoteMute && !_isParticipantSilent && <MuteVideoButton { ...buttonProps } /> }
179
                 {/* @ts-ignore */}
188
                 {/* @ts-ignore */}
180
                 <Divider style = { styles.divider as ViewStyle } />
189
                 <Divider style = { styles.divider as ViewStyle } />
256
     const moderator = isLocalParticipantModerator(state);
265
     const moderator = isLocalParticipantModerator(state);
257
     const _iAmVisitor = state['features/visitors'].iAmVisitor;
266
     const _iAmVisitor = state['features/visitors'].iAmVisitor;
258
     const _isBreakoutRoom = isInBreakoutRoom(state);
267
     const _isBreakoutRoom = isInBreakoutRoom(state);
268
+    const raisedHand = hasRaisedHand(participant);
259
 
269
 
260
     return {
270
     return {
261
         _currentRoomId,
271
         _currentRoomId,
267
         _isParticipantSilent: Boolean(participant?.isSilent),
277
         _isParticipantSilent: Boolean(participant?.isSilent),
268
         _moderator: moderator,
278
         _moderator: moderator,
269
         _participantDisplayName: getParticipantDisplayName(state, participantId),
279
         _participantDisplayName: getParticipantDisplayName(state, participantId),
280
+        _raisedHand: raisedHand,
270
         _rooms,
281
         _rooms,
271
         _showDemote: moderator
282
         _showDemote: moderator
272
     };
283
     };

+ 56
- 0
react/features/video-menu/components/web/LowerHandButton.tsx 查看文件

1
+import React, { useCallback } from 'react';
2
+import { useTranslation } from 'react-i18next';
3
+import { useDispatch, useSelector } from 'react-redux';
4
+
5
+import { getCurrentConference } from '../../../base/conference/functions';
6
+import { IconRaiseHand } from '../../../base/icons/svg';
7
+import { raiseHand } from '../../../base/participants/actions';
8
+import { LOWER_HAND_MESSAGE } from '../../../base/tracks/constants';
9
+import ContextMenuItem from '../../../base/ui/components/web/ContextMenuItem';
10
+
11
+interface IProps {
12
+
13
+    /**
14
+     * The ID of the participant that's linked to the button.
15
+     */
16
+    participantID?: String;
17
+}
18
+
19
+/**
20
+ * Implements a React {@link Component} which displays a button for notifying certain
21
+ * participant who raised hand to lower hand.
22
+ *
23
+ * @returns {JSX.Element}
24
+ */
25
+const LowerHandButton = ({
26
+    participantID = ''
27
+}: IProps): JSX.Element => {
28
+    const { t } = useTranslation();
29
+    const dispatch = useDispatch();
30
+    const currentConference = useSelector(getCurrentConference);
31
+    const accessibilityText = participantID
32
+        ? t('participantsPane.actions.lowerHand')
33
+        : t('participantsPane.actions.lowerAllHands');
34
+
35
+    const handleClick = useCallback(() => {
36
+        if (!participantID) {
37
+            dispatch(raiseHand(false));
38
+        }
39
+        currentConference?.sendEndpointMessage(
40
+            participantID,
41
+            {
42
+                name: LOWER_HAND_MESSAGE
43
+            }
44
+        );
45
+    }, [ participantID ]);
46
+
47
+    return (
48
+        <ContextMenuItem
49
+            accessibilityLabel = { accessibilityText }
50
+            icon = { IconRaiseHand }
51
+            onClick = { handleClick }
52
+            text = { accessibilityText } />
53
+    );
54
+};
55
+
56
+export default LowerHandButton;

+ 7
- 1
react/features/video-menu/components/web/ParticipantContextMenu.tsx 查看文件

9
 import { isIosMobileBrowser, isMobileBrowser } from '../../../base/environment/utils';
9
 import { isIosMobileBrowser, isMobileBrowser } from '../../../base/environment/utils';
10
 import { MEDIA_TYPE } from '../../../base/media/constants';
10
 import { MEDIA_TYPE } from '../../../base/media/constants';
11
 import { PARTICIPANT_ROLE } from '../../../base/participants/constants';
11
 import { PARTICIPANT_ROLE } from '../../../base/participants/constants';
12
-import { getLocalParticipant } from '../../../base/participants/functions';
12
+import { getLocalParticipant, hasRaisedHand } from '../../../base/participants/functions';
13
 import { IParticipant } from '../../../base/participants/types';
13
 import { IParticipant } from '../../../base/participants/types';
14
 import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks/functions.any';
14
 import { isParticipantAudioMuted, isParticipantVideoMuted } from '../../../base/tracks/functions.any';
15
 import ContextMenu from '../../../base/ui/components/web/ContextMenu';
15
 import ContextMenu from '../../../base/ui/components/web/ContextMenu';
33
 import DemoteToVisitorButton from './DemoteToVisitorButton';
33
 import DemoteToVisitorButton from './DemoteToVisitorButton';
34
 import GrantModeratorButton from './GrantModeratorButton';
34
 import GrantModeratorButton from './GrantModeratorButton';
35
 import KickButton from './KickButton';
35
 import KickButton from './KickButton';
36
+import LowerHandButton from './LowerHandButton';
36
 import MuteButton from './MuteButton';
37
 import MuteButton from './MuteButton';
37
 import MuteEveryoneElseButton from './MuteEveryoneElseButton';
38
 import MuteEveryoneElseButton from './MuteEveryoneElseButton';
38
 import MuteEveryoneElsesVideoButton from './MuteEveryoneElsesVideoButton';
39
 import MuteEveryoneElsesVideoButton from './MuteEveryoneElsesVideoButton';
148
         : participant?.id ? participantsVolume[participant?.id] : undefined) ?? 1;
149
         : participant?.id ? participantsVolume[participant?.id] : undefined) ?? 1;
149
     const isBreakoutRoom = useSelector(isInBreakoutRoom);
150
     const isBreakoutRoom = useSelector(isInBreakoutRoom);
150
     const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
151
     const isModerationSupported = useSelector((state: IReduxState) => isAvModerationSupported()(state));
152
+    const raisedHands = hasRaisedHand(participant);
151
     const stageFilmstrip = useSelector(isStageFilmstripAvailable);
153
     const stageFilmstrip = useSelector(isStageFilmstripAvailable);
152
     const shouldDisplayVerification = useSelector((state: IReduxState) => displayVerification(state, participant?.id));
154
     const shouldDisplayVerification = useSelector((state: IReduxState) => displayVerification(state, participant?.id));
153
     const buttonsWithNotifyClick = useSelector(getParticipantMenuButtonsWithNotifyClick);
155
     const buttonsWithNotifyClick = useSelector(getParticipantMenuButtonsWithNotifyClick);
241
             buttons.push(<MuteEveryoneElsesVideoButton { ...getButtonProps(BUTTONS.MUTE_OTHERS_VIDEO) } />);
243
             buttons.push(<MuteEveryoneElsesVideoButton { ...getButtonProps(BUTTONS.MUTE_OTHERS_VIDEO) } />);
242
         }
244
         }
243
 
245
 
246
+        if (raisedHands) {
247
+            buttons2.push(<LowerHandButton { ...getButtonProps(BUTTONS.LOWER_PARTICIPANT_HAND) } />);
248
+        }
249
+
244
         if (!disableGrantModerator && !isBreakoutRoom) {
250
         if (!disableGrantModerator && !isBreakoutRoom) {
245
             buttons2.push(<GrantModeratorButton { ...getButtonProps(BUTTONS.GRANT_MODERATOR) } />);
251
             buttons2.push(<GrantModeratorButton { ...getButtonProps(BUTTONS.GRANT_MODERATOR) } />);
246
         }
252
         }

+ 1
- 0
react/features/video-menu/constants.ts 查看文件

25
     GRANT_MODERATOR: 'grant-moderator',
25
     GRANT_MODERATOR: 'grant-moderator',
26
     HIDE_SELF_VIEW: 'hide-self-view',
26
     HIDE_SELF_VIEW: 'hide-self-view',
27
     KICK: 'kick',
27
     KICK: 'kick',
28
+    LOWER_PARTICIPANT_HAND: 'lower-participant-hand',
28
     MUTE: 'mute',
29
     MUTE: 'mute',
29
     MUTE_OTHERS: 'mute-others',
30
     MUTE_OTHERS: 'mute-others',
30
     MUTE_OTHERS_VIDEO: 'mute-others-video',
31
     MUTE_OTHERS_VIDEO: 'mute-others-video',

Loading…
取消
儲存