UNPKG

@100mslive/react-native-room-kit

Version:

100ms Room Kit provides simple & easy to use UI components to build Live Streaming & Video Conferencing experiences in your apps.

556 lines (541 loc) 23.4 kB
import * as React from 'react'; import { View, StyleSheet, Text, TouchableOpacity } from 'react-native'; import { HMSRecordingState, TranscriptionState, TranscriptionsMode } from '@100mslive/react-native-hms'; import { useDispatch, useSelector } from 'react-redux'; import { ModalTypes } from '../utils/types'; import { groupIntoTriplets, parseMetadata } from '../utils/functions'; import { BRBIcon, CCIcon, HandIcon, ParticipantsIcon, PencilIcon, PollVoteIcon, RecordingIcon, ScreenShareIcon, VirtualBackgroundIcon, WaveIcon } from '../Icons'; import { BottomSheet, useBottomSheetActions } from './BottomSheet'; import { isPublishingAllowed, useHMSConferencingScreenConfig, useHMSInstance, useHMSLayoutConfig, useHMSRoomColorPalette, useHMSRoomStyleSheet, useShowChatAndParticipants, useStartRecording } from '../hooks-util'; import { useCanPublishAudio, useCanPublishScreen, useCanPublishVideo, useHMSActions, useIsAnyStreamingOn } from '../hooks-sdk'; import { RoomSettingsModalDebugModeContent } from './RoomSettingsModalDebugModeContent'; import { ParticipantsCount } from './ParticipantsCount'; import { selectAllowedTracksToPublish } from '../hooks-sdk-selectors'; import { TestIds } from '../utils/constants'; import { addNotification, setShowClosedCaptions } from '../redux/actions'; import { NotificationTypes } from '../types'; export const RoomSettingsModalContent = props => { const { closeRoomSettingsModal, setModalVisible } = props; const hmsInstance = useHMSInstance(); const dispatch = useDispatch(); const debugMode = useSelector(state => state.user.debugMode); const hmsActions = useHMSActions(); const { alert_error_default: alertErrorDefaultColor } = useHMSRoomColorPalette(); const isPublisher = useSelector(state => { const localPeer = state.hmsStates.localPeer; return localPeer ? isPublishingAllowed(localPeer) : false; }); const editUsernameDisabled = useSelector(state => state.app.editUsernameDisabled); const canReadOrWritePoll = useSelector(state => { var _state$hmsStates$loca; const permissions = (_state$hmsStates$loca = state.hmsStates.localPeer) === null || _state$hmsStates$loca === void 0 || (_state$hmsStates$loca = _state$hmsStates$loca.role) === null || _state$hmsStates$loca === void 0 ? void 0 : _state$hmsStates$loca.permissions; return (permissions === null || permissions === void 0 ? void 0 : permissions.pollRead) || (permissions === null || permissions === void 0 ? void 0 : permissions.pollWrite); }); const whiteboardAdminPermission = useSelector(state => { var _state$hmsStates$loca2; return !!((_state$hmsStates$loca2 = state.hmsStates.localPeer) !== null && _state$hmsStates$loca2 !== void 0 && (_state$hmsStates$loca2 = _state$hmsStates$loca2.role) !== null && _state$hmsStates$loca2 !== void 0 && (_state$hmsStates$loca2 = _state$hmsStates$loca2.permissions) !== null && _state$hmsStates$loca2 !== void 0 && (_state$hmsStates$loca2 = _state$hmsStates$loca2.whiteboard) !== null && _state$hmsStates$loca2 !== void 0 && _state$hmsStates$loca2.admin); }); const { registerOnModalHideAction } = useBottomSheetActions(); const { canShowParticipants, show } = useShowChatAndParticipants(); // #region Participants related states and functions const onParticipantsPress = () => { // Register callback to be called when bottom sheet is hidden registerOnModalHideAction(() => show('participants')); // Close the current bottom sheet closeRoomSettingsModal(); }; // #endregion // #region Screen Share related states and functions const canPublishScreen = useCanPublishScreen(); const isLocalScreenShared = useSelector(state => state.hmsStates.isLocalScreenShared); const handleScreenShareTogglePress = async () => { closeRoomSettingsModal(); await hmsActions.setScreenShareEnabled(!isLocalScreenShared); }; // #endregion // #region "BRB" and "Hand Raise" related states and functions const localPeerMetadata = useSelector(state => { var _state$hmsStates$loca3; return (_state$hmsStates$loca3 = state.hmsStates.localPeer) === null || _state$hmsStates$loca3 === void 0 ? void 0 : _state$hmsStates$loca3.metadata; }); const parsedMetadata = parseMetadata(localPeerMetadata); const isBRBOn = !!parsedMetadata.isBRBOn; const isHandRaised = useSelector(state => { var _state$hmsStates$loca4; return !!((_state$hmsStates$loca4 = state.hmsStates.localPeer) !== null && _state$hmsStates$loca4 !== void 0 && _state$hmsStates$loca4.isHandRaised); }); const toggleRaiseHand = async () => { closeRoomSettingsModal(); if (isBRBOn) { const newMetadata = { ...parsedMetadata, isBRBOn: false }; await hmsActions.changeMetadata(newMetadata); } if (isHandRaised) { await hmsActions.lowerLocalPeerHand(); } else { await hmsActions.raiseLocalPeerHand(); } }; const toggleBRB = async () => { const newMetadata = { ...parsedMetadata, isBRBOn: !isBRBOn }; closeRoomSettingsModal(); await hmsActions.changeMetadata(newMetadata); if (isHandRaised) { await hmsActions.lowerLocalPeerHand(); } }; // #endregion // #region Recording related states and functions const canStartRecording = useSelector(state => { var _state$hmsStates$loca5; return !!((_state$hmsStates$loca5 = state.hmsStates.localPeer) !== null && _state$hmsStates$loca5 !== void 0 && (_state$hmsStates$loca5 = _state$hmsStates$loca5.role) !== null && _state$hmsStates$loca5 !== void 0 && (_state$hmsStates$loca5 = _state$hmsStates$loca5.permissions) !== null && _state$hmsStates$loca5 !== void 0 && _state$hmsStates$loca5.browserRecording); }); const isAnyStreamingOn = useIsAnyStreamingOn(); const isRecordingOn = useSelector(state => { var _room$browserRecordin, _room$browserRecordin2; const room = state.hmsStates.room; return (room === null || room === void 0 || (_room$browserRecordin = room.browserRecordingState) === null || _room$browserRecordin === void 0 ? void 0 : _room$browserRecordin.state) === HMSRecordingState.RESUMED || (room === null || room === void 0 || (_room$browserRecordin2 = room.browserRecordingState) === null || _room$browserRecordin2 === void 0 ? void 0 : _room$browserRecordin2.state) === HMSRecordingState.STARTED; }); const isRecordingPaused = useSelector(state => { var _state$hmsStates$room; return ((_state$hmsStates$room = state.hmsStates.room) === null || _state$hmsStates$room === void 0 || (_state$hmsStates$room = _state$hmsStates$room.browserRecordingState) === null || _state$hmsStates$room === void 0 ? void 0 : _state$hmsStates$room.state) === HMSRecordingState.PAUSED; }); const isRecordingDisabled = useSelector(state => { var _room$browserRecordin3; const room = state.hmsStates.room; return isAnyStreamingOn || (room === null || room === void 0 || (_room$browserRecordin3 = room.browserRecordingState) === null || _room$browserRecordin3 === void 0 ? void 0 : _room$browserRecordin3.state) === HMSRecordingState.STARTING; }); const { startRecording } = useStartRecording(); const handleRecordingTogglePress = () => { if (isRecordingOn) { registerOnModalHideAction(() => { setModalVisible(ModalTypes.STOP_RECORDING); }); closeRoomSettingsModal(); } else { startRecording(); closeRoomSettingsModal(); } }; // #endregion // #region Noise Cancellation Plugin const noiseCancellationPlugin = useSelector(state => state.hmsStates.noiseCancellationPlugin); const [isNoiseCancellationEnabled, setIsNoiseCancellationEnabled] = React.useState(false); const [isNoiseCancellationAvailable, setIsNoiseCancellationAvailable] = React.useState(false); const canPublishAudio = useCanPublishAudio(); const isLocalAudioMuted = useSelector(state => state.hmsStates.isLocalAudioMuted); const showNoiseCancellationButton = canPublishAudio && isNoiseCancellationAvailable; React.useEffect(() => { if (noiseCancellationPlugin) { let mounted = true; Promise.all([noiseCancellationPlugin.isEnabled(), noiseCancellationPlugin.isNoiseCancellationAvailable()].map(promise => promise.then(value => ({ status: 'fulfilled', value })).catch(reason => ({ status: 'rejected', reason })))).then(results => { const [isEnabledResult, isAvailableResult] = results; if (mounted) { if (isEnabledResult && isEnabledResult.status === 'fulfilled') { setIsNoiseCancellationEnabled(isEnabledResult.value); } if (isAvailableResult && isAvailableResult.status === 'fulfilled') { setIsNoiseCancellationAvailable(isAvailableResult.value); } } }); return () => { mounted = false; }; } }, [noiseCancellationPlugin]); const handleNoiseCancellation = () => { // Register callback to be called when bottom sheet is hidden registerOnModalHideAction(() => { if (!noiseCancellationPlugin || !isNoiseCancellationAvailable) return; if (isNoiseCancellationEnabled) { noiseCancellationPlugin.disable(); } else { noiseCancellationPlugin.enable(); } }); // Close the current bottom sheet closeRoomSettingsModal(); }; // #endregion // #region Virtual Background const canPublishVideo = useCanPublishVideo(); const videoPlugin = useSelector(state => state.hmsStates.videoPlugin); const isLocalVideoMuted = useSelector(state => state.hmsStates.isLocalVideoMuted); const virtualBackgroundApplied = useSelector(state => state.app.selectedVirtualBackground !== null); const handleVirtualBackground = () => { // Register callback to be called when bottom sheet is hiddden registerOnModalHideAction(() => { if (!videoPlugin || isLocalVideoMuted) return; setModalVisible(ModalTypes.VIRTUAL_BACKGROUND); }); // Close the current bottom sheet closeRoomSettingsModal(); }; // #endregion // #region Closed Captions const isCCAdmin = useSelector(state => { var _state$hmsStates$loca6; const captionPermission = (_state$hmsStates$loca6 = state.hmsStates.localPeer) === null || _state$hmsStates$loca6 === void 0 || (_state$hmsStates$loca6 = _state$hmsStates$loca6.role) === null || _state$hmsStates$loca6 === void 0 || (_state$hmsStates$loca6 = _state$hmsStates$loca6.permissions) === null || _state$hmsStates$loca6 === void 0 || (_state$hmsStates$loca6 = _state$hmsStates$loca6.transcriptions) === null || _state$hmsStates$loca6 === void 0 ? void 0 : _state$hmsStates$loca6.find(element => element.mode === TranscriptionsMode.CAPTION); return (captionPermission === null || captionPermission === void 0 ? void 0 : captionPermission.admin) || false; }); const ccEnabledForEveryone = useSelector(state => { var _state$hmsStates$room2; const captionTranscription = (_state$hmsStates$room2 = state.hmsStates.room) === null || _state$hmsStates$room2 === void 0 || (_state$hmsStates$room2 = _state$hmsStates$room2.transcriptions) === null || _state$hmsStates$room2 === void 0 ? void 0 : _state$hmsStates$room2.find(transcription => transcription.mode === TranscriptionsMode.CAPTION); return captionTranscription ? captionTranscription.state === TranscriptionState.STARTED : false; }); const ccEnabledForSelf = useSelector(state => state.app.showClosedCaptions); const handleCCForSelf = () => { dispatch(setShowClosedCaptions(!ccEnabledForSelf)); // Close the current bottom sheet closeRoomSettingsModal(); }; const handleCCForEveryone = () => { // Register callback to be called when bottom sheet is hiddden registerOnModalHideAction(() => { setModalVisible(ModalTypes.CLOSED_CAPTIONS_CONTROL); }); // Close the current bottom sheet closeRoomSettingsModal(); }; // #endregion const changeName = () => { // Register callback to be called when bottom sheet is hiddden registerOnModalHideAction(() => { setModalVisible(ModalTypes.CHANGE_NAME); }); // Close the current bottom sheet closeRoomSettingsModal(); }; const openPollsQuizzesModal = () => { registerOnModalHideAction(() => { setModalVisible(ModalTypes.POLLS_AND_QUIZZES); }); // Close the current bottom sheet closeRoomSettingsModal(); }; const whiteboard = useSelector(state => state.hmsStates.whiteboard); const screenShareNodesAvailable = useSelector(state => state.app.screensharePeerTrackNodes.length > 0); const toggleWhiteboard = async () => { if (!whiteboardAdminPermission) return; if (whiteboard && whiteboard.isOwner) { hmsInstance.interactivityCenter.stopWhiteboard().then(success => { console.log('#stopWhiteboard stopped whiteboard ', success); }).catch(error => { console.log('#stopWhiteboard error ', error); }); } else if (whiteboard && !whiteboard.isOwner) { const uid = Math.random().toString(16).slice(2); dispatch(addNotification({ id: uid, type: NotificationTypes.ERROR, title: 'Only the peer who started the whiteboard has the ability to close it!' })); } else if (isLocalScreenShared || screenShareNodesAvailable) { const uid = Math.random().toString(16).slice(2); dispatch(addNotification({ id: uid, type: NotificationTypes.ERROR, title: isLocalScreenShared ? 'Discontinue screenshare to open the whiteboard!' : "Can't open whiteboard while screenshare is happening!" })); } else { hmsInstance.interactivityCenter.startWhiteboard('Interactive Session').then(success => { console.log('#startWhiteboard started whiteboard ', success); }).catch(error => { console.log('#startWhiteboard error ', error); }); } // Close the current bottom sheet closeRoomSettingsModal(); }; const canShowBRB = useHMSLayoutConfig(layoutConfig => { var _layoutConfig$screens; return !!(layoutConfig !== null && layoutConfig !== void 0 && (_layoutConfig$screens = layoutConfig.screens) !== null && _layoutConfig$screens !== void 0 && (_layoutConfig$screens = _layoutConfig$screens.conferencing) !== null && _layoutConfig$screens !== void 0 && (_layoutConfig$screens = _layoutConfig$screens.default) !== null && _layoutConfig$screens !== void 0 && (_layoutConfig$screens = _layoutConfig$screens.elements) !== null && _layoutConfig$screens !== void 0 && _layoutConfig$screens.brb); }); const canRaiseHand = useHMSConferencingScreenConfig(confScreenConfig => { var _confScreenConfig$ele; return !!(confScreenConfig !== null && confScreenConfig !== void 0 && (_confScreenConfig$ele = confScreenConfig.elements) !== null && _confScreenConfig$ele !== void 0 && _confScreenConfig$ele.hand_raise); }); const isOnStage = useHMSLayoutConfig(layoutConfig => { var _layoutConfig$screens2; return !!(layoutConfig !== null && layoutConfig !== void 0 && (_layoutConfig$screens2 = layoutConfig.screens) !== null && _layoutConfig$screens2 !== void 0 && (_layoutConfig$screens2 = _layoutConfig$screens2.conferencing) !== null && _layoutConfig$screens2 !== void 0 && (_layoutConfig$screens2 = _layoutConfig$screens2.default) !== null && _layoutConfig$screens2 !== void 0 && (_layoutConfig$screens2 = _layoutConfig$screens2.elements) !== null && _layoutConfig$screens2 !== void 0 && _layoutConfig$screens2.on_stage_exp); }); const allowedToPublish = useSelector(state => { const allowed = selectAllowedTracksToPublish(state); return (allowed && allowed.length > 0) ?? false; }); const showHandRaiseIcon = canRaiseHand && !isOnStage && allowedToPublish; return /*#__PURE__*/React.createElement(View, null, /*#__PURE__*/React.createElement(BottomSheet.Header, { dismissModal: closeRoomSettingsModal, heading: "Options", headingTestID: TestIds.room_modal_heading, closeIconTestID: TestIds.room_modal_close_btn }), /*#__PURE__*/React.createElement(BottomSheet.Divider, null), /*#__PURE__*/React.createElement(View, { style: styles.contentContainer }, groupIntoTriplets([{ id: 'participants', testID: TestIds.room_modal_participants_btn, icon: /*#__PURE__*/React.createElement(ParticipantsIcon, { style: { width: 20, height: 20 } }), label: 'Participants', pressHandler: onParticipantsPress, isActive: false, hide: !canShowParticipants, sibling: /*#__PURE__*/React.createElement(ParticipantsCount, null) // parent // children }, { id: 'share-screen', testID: !!isLocalScreenShared ? TestIds.room_modal_stop_screen_share_btn : TestIds.room_modal_share_screen_btn, icon: /*#__PURE__*/React.createElement(ScreenShareIcon, { style: { width: 20, height: 20 } }), label: isLocalScreenShared ? 'Sharing Screen' : 'Share Screen', pressHandler: handleScreenShareTogglePress, isActive: !!isLocalScreenShared, // Show active if screen is shared hide: !canPublishScreen // Hide if can't publish screen }, { id: 'brb', testID: isBRBOn ? TestIds.room_modal_stop_brb_btn : TestIds.room_modal_brb_btn, icon: /*#__PURE__*/React.createElement(BRBIcon, { style: { width: 20, height: 20 } }), label: isBRBOn ? "I'm Back" : 'Be Right Back', pressHandler: toggleBRB, isActive: isBRBOn, // Show active if `isBRBOn` is set on metadata hide: !canShowBRB // Hide if can't publish screen }, { id: 'raise-hand', testID: isHandRaised ? TestIds.room_modal_hand_raised_btn : TestIds.room_modal_hand_raise_btn, icon: /*#__PURE__*/React.createElement(HandIcon, { style: { width: 20, height: 20 } }), label: isHandRaised ? 'Hand Raised' : 'Raise Hand', pressHandler: toggleRaiseHand, isActive: isHandRaised, // Show active if `isHandRaised` is set on metadata hide: !showHandRaiseIcon // Hide if can't publish screen }, { id: 'recording', testID: isRecordingOn ? TestIds.room_modal_stop_recording_btn : TestIds.room_modal_start_recording_btn, icon: /*#__PURE__*/React.createElement(RecordingIcon, { type: isRecordingPaused ? 'pause' : 'on', style: [{ width: 20, height: 20 }, isRecordingOn ? { tintColor: alertErrorDefaultColor } : null] }), label: isRecordingOn ? 'Recording' : isRecordingPaused ? 'Recording Paused' : 'Record', pressHandler: handleRecordingTogglePress, isActive: false, disabled: isRecordingDisabled, hide: !canStartRecording // Hide if can't publish screen }, { id: 'change-name', testID: TestIds.room_modal_change_name_btn, icon: /*#__PURE__*/React.createElement(PencilIcon, { style: { width: 20, height: 20 } }), label: 'Change Name', pressHandler: changeName, isActive: false, hide: isPublisher || editUsernameDisabled }, { id: 'polls-and-quizes', icon: /*#__PURE__*/React.createElement(PollVoteIcon, { style: { width: 20, height: 20 } }), label: 'Polls and Quizzes', pressHandler: openPollsQuizzesModal, isActive: false, hide: !canReadOrWritePoll }, { id: 'whiteboard', icon: /*#__PURE__*/React.createElement(PencilIcon, { type: "board", style: { width: 20, height: 20 } }), label: whiteboard ? 'Close Whiteboard' : 'Open Whiteboard', pressHandler: toggleWhiteboard, isActive: !!whiteboard && whiteboard.isOwner, disabled: !!whiteboard && !whiteboard.isOwner, hide: !whiteboardAdminPermission }, { id: 'noise-cancellation', icon: /*#__PURE__*/React.createElement(WaveIcon, { style: { width: 20, height: 20 } }), label: isNoiseCancellationEnabled ? 'Noise Reduced' : 'Reduce Noise', pressHandler: handleNoiseCancellation, isActive: isNoiseCancellationEnabled, hide: !showNoiseCancellationButton, disabled: isLocalAudioMuted }, { id: 'virtual-background', icon: /*#__PURE__*/React.createElement(VirtualBackgroundIcon, { style: { width: 20, height: 20 } }), label: 'Virtual Background', pressHandler: handleVirtualBackground, isActive: virtualBackgroundApplied, hide: !videoPlugin || !canPublishVideo, disabled: isLocalVideoMuted }, { id: 'closed-captions', icon: /*#__PURE__*/React.createElement(CCIcon, { style: { width: 20, height: 20 } }), label: isCCAdmin ? 'Closed Captions' : ccEnabledForSelf ? 'Hide Captions' : 'Show Captions', pressHandler: isCCAdmin ? handleCCForEveryone : handleCCForSelf, isActive: !isCCAdmin && ccEnabledForSelf, hide: !isCCAdmin && !ccEnabledForEveryone, disabled: false }].filter(itm => !itm.hide), true).map((itm, idx) => { const isFirst = idx === 0; return /*#__PURE__*/React.createElement(React.Fragment, { key: idx.toString() }, isFirst ? null : /*#__PURE__*/React.createElement(View, { style: styles.rowSpacer }), /*#__PURE__*/React.createElement(View, { style: styles.row }, itm.map((item, index) => { const isFirst = index === 0; return /*#__PURE__*/React.createElement(React.Fragment, { key: item ? item.id : index.toString() }, isFirst ? null : /*#__PURE__*/React.createElement(View, { style: styles.colSpacer }), /*#__PURE__*/React.createElement(View, { style: styles.col }, item ? /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(SettingItem, { testID: item.testID, icon: item.icon, onPress: item.pressHandler, text: item.label, isActive: item.isActive, disabled: item.disabled }), item.sibling) : null)); }))); })), debugMode ? /*#__PURE__*/React.createElement(RoomSettingsModalDebugModeContent, props) : null); }; const SettingItem = ({ testID, text, icon, onPress, disabled, isActive = false }) => { const hmsRoomStyles = useHMSRoomStyleSheet((theme, typography) => ({ button: { backgroundColor: theme.palette.surface_bright }, text: { color: theme.palette.on_surface_high, fontFamily: `${typography.font_family}-SemiBold` } })); return /*#__PURE__*/React.createElement(TouchableOpacity, { testID: testID, disabled: disabled, style: [styles.button, isActive ? hmsRoomStyles.button : null, disabled ? { opacity: 0.6 } : null], onPress: onPress }, icon, /*#__PURE__*/React.createElement(Text, { style: [styles.text, hmsRoomStyles.text], numberOfLines: 2 }, text)); }; const styles = StyleSheet.create({ contentContainer: { marginHorizontal: 20 }, row: { flexDirection: 'row' }, rowSpacer: { height: 16, width: '100%' }, col: { flex: 1, position: 'relative' }, colSpacer: { width: 12, height: '100%' }, button: { alignItems: 'center', borderRadius: 4, paddingVertical: 8, paddingHorizontal: 4 }, text: { textAlign: 'center', fontSize: 12, lineHeight: 16, letterSpacing: 0.4, marginTop: 8 } }); //# sourceMappingURL=RoomSettingsModalContent.js.map