@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.
989 lines (949 loc) • 104 kB
JavaScript
import { getSoftInputMode, HMSConfig, HMSHLSPlayerPlaybackState, HMSMessage, HMSMessageRecipient, HMSMessageRecipientType, HMSPeerUpdate, HMSPIPListenerActions, HMSPollUpdateType, HMSRoomUpdate, HMSTrackSource, HMSTrackType, HMSTrackUpdate, HMSUpdateListenerActions, HMSVideoViewMode, setSoftInputMode, SoftInputModes, TranscriptionsMode, TranscriptionState, useHMSHLSPlayerCue, useHMSHLSPlayerPlaybackState, useHMSHLSPlayerResolution, useHmsViewsResolutionsState, WindowController } from '@100mslive/react-native-hms';
import Toast from 'react-native-simple-toast';
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { ChatBroadcastFilter, MaxTilesInOnePage, ModalTypes, OnLeaveReason, PeerListRefreshInterval, PipModes } from './utils/types';
import { createPeerTrackNode, parseMetadata } from './utils/functions';
import { batch, shallowEqual, useDispatch, useSelector, useStore } from 'react-redux';
import { addCuedPollId, addMessage, addNotification, addParticipant, addParticipants, addPinnedMessages, addScreenshareTile, addUpdateParticipant, changeMeetingState, changePipModeStatus, changeStartingHLSStream, clearStore, filterOutMsgsFromBlockedPeers, removeNotification, removeParticipant, removeParticipants, removeScreenshareTile, replaceParticipantsList, saveUserData, setActiveChatBottomSheetTab, setActiveSpeakers, setAndroidHLSStreamPaused, setAutoEnterPipMode, setChatPeerBlacklist, setChatState, setEditUsernameDisabled, setFullScreenPeerTrackNode, setHandleBackButton, setHMSLocalPeerState, setHMSRoleState, setHMSRoomState, setIsLocalAudioMutedState, setIsLocalVideoMutedState, setLayoutConfig, setLocalPeerTrackNode, setMiniViewPeerTrackNode, setModalType, setOnLeaveHandler, setPrebuiltData, setReconnecting, setRoleChangeRequest, setStartingOrStoppingRecording, updateFullScreenPeerTrackNode, updateLocalPeerTrackNode, updateMiniViewPeerTrackNode, updateScreenshareTile } from './redux/actions';
import { createPeerTrackNodeUniqueId, degradeOrRestorePeerTrackNodes, peerTrackNodeExistForPeer, peerTrackNodeExistForPeerAndTrack, removePeerTrackNodes, removePeerTrackNodesWithTrack, replacePeerTrackNodes, replacePeerTrackNodesWithTrack } from './peerTrackNodeUtils';
import { MeetingState, NotificationTypes } from './types';
import { BackHandler, InteractionManager, Keyboard, Platform } from 'react-native';
import { NavigationContext } from '@react-navigation/native';
import { useIsLandscapeOrientation, useIsPortraitOrientation } from './utils/dimension';
import { selectChatLayoutConfig, selectConferencingScreenConfig, selectIsHLSViewer, selectLayoutConfigForRole, selectShouldGoLive, selectVideoTileLayoutConfig } from './hooks-util-selectors';
import { getRoomLayout } from './modules/HMSManager';
import { DEFAULT_THEME, DEFAULT_TYPOGRAPHY } from './utils/theme';
import { KeyboardState, useSharedValue } from 'react-native-reanimated';
import { useCanPublishAudio, useCanPublishScreen, useCanPublishVideo, useHMSActions } from './hooks-sdk';
import { useSafeAreaFrame, useSafeAreaInsets } from 'react-native-safe-area-context';
export const useHMSListeners = setPeerTrackNodes => {
const hmsInstance = useHMSInstance();
const updateLocalPeer = useUpdateHMSLocalPeer(hmsInstance);
useHMSRoomUpdate(hmsInstance);
useHMSPeersUpdate(hmsInstance, updateLocalPeer, setPeerTrackNodes);
useHMSPeerListUpdated(hmsInstance);
useHMSTrackUpdate(hmsInstance, updateLocalPeer, setPeerTrackNodes);
};
const useHMSRoomUpdate = hmsInstance => {
const dispatch = useDispatch();
const reduxStore = useStore();
useEffect(() => {
const roomUpdateHandler = data => {
const {
room,
type
} = data;
dispatch(setHMSRoomState(room));
if (type === HMSRoomUpdate.BROWSER_RECORDING_STATE_UPDATED) {
const startingOrStoppingRecording = reduxStore.getState().app.startingOrStoppingRecording;
if (startingOrStoppingRecording) {
dispatch(setStartingOrStoppingRecording(false));
}
} else if (type === HMSRoomUpdate.HLS_STREAMING_STATE_UPDATED) {
dispatch(changeStartingHLSStream(false));
} else if (type === HMSRoomUpdate.TRANSCRIPTIONS_UPDATED) {
var _room$transcriptions;
const captionTranscription = (_room$transcriptions = room.transcriptions) === null || _room$transcriptions === void 0 ? void 0 : _room$transcriptions.find(transcription => transcription.mode === TranscriptionsMode.CAPTION);
if ((captionTranscription === null || captionTranscription === void 0 ? void 0 : captionTranscription.state) === TranscriptionState.STARTED) {
batch(() => {
dispatch(removeNotification('enable-cc'));
dispatch(removeNotification('TranscriptionState.STARTED'));
dispatch(addNotification({
id: 'TranscriptionState.STARTED',
type: NotificationTypes.INFO,
icon: 'cc',
title: 'Closed Captioning enabled for everyone'
}));
});
} else if ((captionTranscription === null || captionTranscription === void 0 ? void 0 : captionTranscription.state) === TranscriptionState.STOPPED) {
batch(() => {
dispatch(removeNotification('disable-cc'));
dispatch(addNotification({
id: Math.random().toString(16).slice(2),
type: NotificationTypes.INFO,
icon: 'cc',
title: 'Closed Captioning disabled for everyone'
}));
});
} else if ((captionTranscription === null || captionTranscription === void 0 ? void 0 : captionTranscription.state) === TranscriptionState.FAILED) {
const transcriptionError = captionTranscription.error;
batch(() => {
dispatch(removeNotification('enable-cc'));
dispatch(removeNotification('disable-cc'));
if (transcriptionError !== undefined) {
dispatch(addNotification({
id: Math.random().toString(16).slice(2),
title: transcriptionError.message || 'Failed to enable/disable Closed Captions',
type: NotificationTypes.ERROR
}));
}
});
}
}
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_ROOM_UPDATE, roomUpdateHandler);
return () => {
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_ROOM_UPDATE);
};
}, [hmsInstance]);
};
const useHMSPeersUpdate = (hmsInstance, updateLocalPeer, setPeerTrackNodes) => {
const dispatch = useDispatch();
const store = useStore();
// const inMeeting = useSelector(
// (state: RootState) => state.app.meetingState === MeetingState.IN_MEETING
// );
const hmsActions = useHMSActions();
const isFirstRunForRoleChangeModal = useRef(true);
useEffect(() => {
const peerUpdateHandler = ({
peer,
type
}) => {
// Handle State from Preview screen
// TODO: When `inMeeting` becomes true Peer Update is resubscribed, we might lose some events during that time
// if (!inMeeting) {
// if (type === HMSPeerUpdate.PEER_JOINED) {
// dispatch(addToPreviewPeersList(peer));
// } else if (type === HMSPeerUpdate.PEER_LEFT) {
// dispatch(removeFromPreviewPeersList(peer));
// }
// }
// Handle State for Meeting screen
if (type === HMSPeerUpdate.PEER_JOINED) {
dispatch(addParticipant(peer));
return;
}
if (type === HMSPeerUpdate.PEER_LEFT) {
dispatch(removeParticipant(peer));
// Handling regular tiles list
setPeerTrackNodes(prevPeerTrackNodes => removePeerTrackNodes(prevPeerTrackNodes, peer));
const reduxState = store.getState();
// Handling Screenshare tiles list
const screensharePeerTrackNodes = reduxState.app.screensharePeerTrackNodes;
const nodeToRemove = screensharePeerTrackNodes.find(node => node.peer.peerID === peer.peerID);
if (nodeToRemove) {
dispatch(removeScreenshareTile(nodeToRemove.id));
}
// Handling Full screen view
const fullScreenPeerTrackNode = reduxState.app.fullScreenPeerTrackNode;
if (fullScreenPeerTrackNode !== null && fullScreenPeerTrackNode.peer.peerID === peer.peerID) {
dispatch(setFullScreenPeerTrackNode(null));
}
return;
}
if (peer.isLocal) {
var _selectVideoTileLayou;
const reduxState = store.getState();
const fullScreenPeerTrackNode = reduxState.app.fullScreenPeerTrackNode;
const miniviewPeerTrackNode = reduxState.app.miniviewPeerTrackNode;
const localPeerTrackNode = reduxState.app.localPeerTrackNode;
const initialRole = reduxState.app.initialRole;
// Currently Applied Layout config
const currentLayoutConfig = selectLayoutConfigForRole(reduxState.hmsStates.layoutConfig, peer.role || null);
// Local Tile Inset layout is enabled
const enableLocalTileInset = (_selectVideoTileLayou = selectVideoTileLayoutConfig(currentLayoutConfig)) === null || _selectVideoTileLayou === void 0 || (_selectVideoTileLayou = _selectVideoTileLayou.grid) === null || _selectVideoTileLayou === void 0 ? void 0 : _selectVideoTileLayou.enable_local_tile_inset;
// Local Tile Inset layout is disabled
const localTileInsetDisbaled = !enableLocalTileInset;
// Local Tile Inset layout is disabled
// then update local peer track node available in list of peer track nodes
if (localTileInsetDisbaled) {
setPeerTrackNodes(prevPeerTrackNodes => {
if (peerTrackNodeExistForPeer(prevPeerTrackNodes, peer)) {
return replacePeerTrackNodes(prevPeerTrackNodes, peer);
}
return prevPeerTrackNodes;
});
}
batch(() => {
if (localPeerTrackNode) {
dispatch(updateLocalPeerTrackNode({
peer
}));
} else if (isPublishingAllowed(peer)) {
dispatch(setLocalPeerTrackNode(createPeerTrackNode(peer, peer.videoTrack)));
}
if (fullScreenPeerTrackNode && fullScreenPeerTrackNode.peer.peerID === peer.peerID) {
dispatch(updateFullScreenPeerTrackNode({
peer
}));
}
// If Local Tile Inset layout is enabled, then update or set it
if (enableLocalTileInset) {
var _peer$role;
if (miniviewPeerTrackNode !== null && miniviewPeerTrackNode.peer.peerID === peer.peerID) {
dispatch(updateMiniViewPeerTrackNode({
peer
}));
} else if (miniviewPeerTrackNode === null && (_peer$role = peer.role) !== null && _peer$role !== void 0 && (_peer$role = _peer$role.publishSettings) !== null && _peer$role !== void 0 && _peer$role.allowed && peer.role.publishSettings.allowed.length > 0) {
dispatch(setMiniViewPeerTrackNode(createPeerTrackNode(peer, peer.videoTrack)));
}
}
// If Local Tile Inset layout is disabled, then remove it if it exists
else if (miniviewPeerTrackNode) {
dispatch(setMiniViewPeerTrackNode(null));
}
});
// - TODO: update local localPeer state
// - Pass this updated data to Meeting component -> DisplayView component
updateLocalPeer();
if (type === HMSPeerUpdate.ROLE_CHANGED) {
const parsedLocalPeerMetadata = parseMetadata(peer.metadata);
if (parsedLocalPeerMetadata.prevRole !== initialRole) {
const newMetadata = {
...parsedLocalPeerMetadata,
prevRole: initialRole === null || initialRole === void 0 ? void 0 : initialRole.name
};
hmsActions.changeMetadata(newMetadata).then(r => {
console.log('Metadata changed successfully', r);
}).catch(e => {
console.log('Metadata change failed', e);
});
if (isFirstRunForRoleChangeModal.current) {
isFirstRunForRoleChangeModal.current = false;
} else {
var _peer$role2;
dispatch(addNotification({
id: Math.random().toString(16).slice(2),
type: NotificationTypes.INFO,
title: `You are now a ${(_peer$role2 = peer.role) === null || _peer$role2 === void 0 ? void 0 : _peer$role2.name}`
}));
}
}
}
return;
}
if (type === HMSPeerUpdate.ROLE_CHANGED) {
var _peer$role3, _peer$role4, _peer$role5, _peer$role6, _peer$role7, _peer$role8, _peer$role9, _peer$role10, _peer$role11;
dispatch(addUpdateParticipant(peer));
// saving current role in peer metadata,
// so that when peer is removed from stage, we can assign previous role to it.
// if (localPeerRoleName) {
// const newMetadata = {
// ...localPeerMetadata,
// prevRole: localPeerRoleName,
// };
// await hmsActions.changeMetadata(newMetadata);
// }
// Handling regular tiles list
if (((_peer$role3 = peer.role) === null || _peer$role3 === void 0 || (_peer$role3 = _peer$role3.publishSettings) === null || _peer$role3 === void 0 ? void 0 : _peer$role3.allowed) === undefined || (_peer$role4 = peer.role) !== null && _peer$role4 !== void 0 && (_peer$role4 = _peer$role4.publishSettings) !== null && _peer$role4 !== void 0 && _peer$role4.allowed && ((_peer$role5 = peer.role) === null || _peer$role5 === void 0 || (_peer$role5 = _peer$role5.publishSettings) === null || _peer$role5 === void 0 ? void 0 : _peer$role5.allowed.length) < 1) {
setPeerTrackNodes(prevPeerTrackNodes => {
if (peerTrackNodeExistForPeer(prevPeerTrackNodes, peer)) {
return removePeerTrackNodes(prevPeerTrackNodes, peer);
}
return prevPeerTrackNodes;
});
}
const reduxState = store.getState();
// Handling screenshare tiles list
if (((_peer$role6 = peer.role) === null || _peer$role6 === void 0 || (_peer$role6 = _peer$role6.publishSettings) === null || _peer$role6 === void 0 ? void 0 : _peer$role6.allowed) === undefined || (_peer$role7 = peer.role) !== null && _peer$role7 !== void 0 && (_peer$role7 = _peer$role7.publishSettings) !== null && _peer$role7 !== void 0 && _peer$role7.allowed && !((_peer$role8 = peer.role) !== null && _peer$role8 !== void 0 && (_peer$role8 = _peer$role8.publishSettings) !== null && _peer$role8 !== void 0 && _peer$role8.allowed.includes('screen'))) {
const screensharePeerTrackNodes = reduxState.app.screensharePeerTrackNodes;
const nodeToRemove = screensharePeerTrackNodes.find(node => node.peer.peerID === peer.peerID);
if (nodeToRemove) {
dispatch(removeScreenshareTile(nodeToRemove.id));
}
}
// Handling full screen view
if (((_peer$role9 = peer.role) === null || _peer$role9 === void 0 || (_peer$role9 = _peer$role9.publishSettings) === null || _peer$role9 === void 0 ? void 0 : _peer$role9.allowed) === undefined || (_peer$role10 = peer.role) !== null && _peer$role10 !== void 0 && (_peer$role10 = _peer$role10.publishSettings) !== null && _peer$role10 !== void 0 && _peer$role10.allowed && !((_peer$role11 = peer.role) !== null && _peer$role11 !== void 0 && (_peer$role11 = _peer$role11.publishSettings) !== null && _peer$role11 !== void 0 && _peer$role11.allowed.includes('video'))) {
const fullScreenPeerTrackNode = reduxState.app.fullScreenPeerTrackNode;
if (fullScreenPeerTrackNode !== null && fullScreenPeerTrackNode.peer.peerID === peer.peerID) {
dispatch(setFullScreenPeerTrackNode(null));
}
}
return;
}
if (type === HMSPeerUpdate.METADATA_CHANGED || type === HMSPeerUpdate.HAND_RAISED_CHANGED || type === HMSPeerUpdate.NAME_CHANGED || type === HMSPeerUpdate.NETWORK_QUALITY_UPDATED) {
dispatch(addUpdateParticipant(peer));
const reduxState = store.getState();
if (type === HMSPeerUpdate.HAND_RAISED_CHANGED) {
const handRaised = peer.isHandRaised;
if (handRaised) {
var _selectedLayoutConfig, _peer$role12, _reduxState$hmsStates;
const {
layoutConfig,
localPeer
} = reduxState.hmsStates;
const selectedLayoutConfig = selectLayoutConfigForRole(layoutConfig, (localPeer === null || localPeer === void 0 ? void 0 : localPeer.role) || null);
// list of roles which should be brought on stage when they raise hand
const offStageRoles = selectedLayoutConfig === null || selectedLayoutConfig === void 0 || (_selectedLayoutConfig = selectedLayoutConfig.screens) === null || _selectedLayoutConfig === void 0 || (_selectedLayoutConfig = _selectedLayoutConfig.conferencing) === null || _selectedLayoutConfig === void 0 || (_selectedLayoutConfig = _selectedLayoutConfig.default) === null || _selectedLayoutConfig === void 0 || (_selectedLayoutConfig = _selectedLayoutConfig.elements) === null || _selectedLayoutConfig === void 0 || (_selectedLayoutConfig = _selectedLayoutConfig.on_stage_exp) === null || _selectedLayoutConfig === void 0 ? void 0 : _selectedLayoutConfig.off_stage_roles;
// checking if the current peer role is included in the above list
const shouldBringOnStage = offStageRoles && offStageRoles.includes((_peer$role12 = peer.role) === null || _peer$role12 === void 0 ? void 0 : _peer$role12.name);
const canChangeRole = (_reduxState$hmsStates = reduxState.hmsStates.localPeer) === null || _reduxState$hmsStates === void 0 || (_reduxState$hmsStates = _reduxState$hmsStates.role) === null || _reduxState$hmsStates === void 0 || (_reduxState$hmsStates = _reduxState$hmsStates.permissions) === null || _reduxState$hmsStates === void 0 ? void 0 : _reduxState$hmsStates.changeRole;
if (shouldBringOnStage && canChangeRole) {
dispatch(addNotification({
id: `${peer.peerID}-${NotificationTypes.HAND_RAISE}`,
type: NotificationTypes.HAND_RAISE,
peer
}));
}
} else {
const notifications = reduxState.app.notifications;
const notificationToRemove = notifications.find(notification => notification.id === `${peer.peerID}-${NotificationTypes.HAND_RAISE}`);
if (notificationToRemove) {
dispatch(removeNotification(notificationToRemove.id));
}
}
}
setPeerTrackNodes(prevPeerTrackNodes => {
if (peerTrackNodeExistForPeer(prevPeerTrackNodes, peer)) {
return replacePeerTrackNodes(prevPeerTrackNodes, peer);
}
return prevPeerTrackNodes;
});
// Handling screenshare tile views
const screensharePeerTrackNodes = reduxState.app.screensharePeerTrackNodes;
const nodeToUpdate = screensharePeerTrackNodes.find(node => node.peer.peerID === peer.peerID);
if (nodeToUpdate) {
dispatch(updateScreenshareTile({
id: nodeToUpdate.id,
peer
}));
}
// Handling fullscreen view
const fullScreenPeerTrackNode = reduxState.app.fullScreenPeerTrackNode;
if (fullScreenPeerTrackNode !== null && fullScreenPeerTrackNode.peer.peerID === peer.peerID) {
dispatch(updateFullScreenPeerTrackNode({
peer
}));
}
return;
}
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_PEER_UPDATE, peerUpdateHandler);
return () => {
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_PEER_UPDATE);
};
}, [hmsInstance]);
};
const useHMSPeerListUpdated = hmsInstance => {
const dispatch = useDispatch();
useEffect(() => {
const peerListUpdateHandler = ({
addedPeers,
removedPeers
}) => {
batch(() => {
dispatch(addParticipants(addedPeers));
dispatch(removeParticipants(removedPeers));
});
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_PEER_LIST_UPDATED, peerListUpdateHandler);
return () => {
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_PEER_LIST_UPDATED);
};
}, [hmsInstance]);
};
export const isPublishingAllowed = peer => {
var _peer$role13, _peer$role14;
return (((_peer$role13 = peer.role) === null || _peer$role13 === void 0 || (_peer$role13 = _peer$role13.publishSettings) === null || _peer$role13 === void 0 ? void 0 : _peer$role13.allowed) && ((_peer$role14 = peer.role) === null || _peer$role14 === void 0 || (_peer$role14 = _peer$role14.publishSettings) === null || _peer$role14 === void 0 || (_peer$role14 = _peer$role14.allowed) === null || _peer$role14 === void 0 ? void 0 : _peer$role14.length) > 0) ?? false;
};
const useHMSTrackUpdate = (hmsInstance, updateLocalPeer, setPeerTrackNodes) => {
const dispatch = useDispatch();
const store = useStore();
useEffect(() => {
const trackUpdateHandler = ({
peer,
track,
type
}) => {
var _reduxState$hmsStates2, _selectVideoTileLayou2;
const reduxState = store.getState();
const fullScreenPeerTrackNode = reduxState.app.fullScreenPeerTrackNode;
const miniviewPeerTrackNode = reduxState.app.miniviewPeerTrackNode;
const localPeerTrackNode = reduxState.app.localPeerTrackNode;
const localPeerRole = ((_reduxState$hmsStates2 = reduxState.hmsStates.localPeer) === null || _reduxState$hmsStates2 === void 0 ? void 0 : _reduxState$hmsStates2.role) ?? null;
const currentLayoutConfig = selectLayoutConfigForRole(reduxState.hmsStates.layoutConfig, localPeerRole);
const localTileInsetEnabled = (_selectVideoTileLayou2 = selectVideoTileLayoutConfig(currentLayoutConfig)) === null || _selectVideoTileLayou2 === void 0 || (_selectVideoTileLayou2 = _selectVideoTileLayou2.grid) === null || _selectVideoTileLayou2 === void 0 ? void 0 : _selectVideoTileLayou2.enable_local_tile_inset;
if (type === HMSTrackUpdate.TRACK_ADDED) {
const newPeerTrackNode = createPeerTrackNode(peer, track);
if (track.source === HMSTrackSource.SCREEN) {
if (!peer.isLocal && track.type === HMSTrackType.VIDEO) {
dispatch(addScreenshareTile(newPeerTrackNode));
}
if (track.type === HMSTrackType.VIDEO) {
var _localPeerRole$permis;
const whiteboard = reduxState.hmsStates.whiteboard;
// If white board is open and local peer is owner, close whiteboard
if (whiteboard &&
// Is local peer has whiteboard admin permission
!!(localPeerRole !== null && localPeerRole !== void 0 && (_localPeerRole$permis = localPeerRole.permissions) !== null && _localPeerRole$permis !== void 0 && (_localPeerRole$permis = _localPeerRole$permis.whiteboard) !== null && _localPeerRole$permis !== void 0 && _localPeerRole$permis.admin) &&
// Is local peer owner of whiteboard
whiteboard.isOwner) {
hmsInstance.interactivityCenter.stopWhiteboard().then(success => {
console.log('StopWhiteboard on Screenshare: ', success);
}).catch(error => {
console.log('StopWhiteboard error: ', error);
});
}
}
} else {
setPeerTrackNodes(prevPeerTrackNodes => {
if (peerTrackNodeExistForPeerAndTrack(prevPeerTrackNodes, peer, track)) {
if (track.type === HMSTrackType.VIDEO) {
return replacePeerTrackNodesWithTrack(prevPeerTrackNodes, peer, track);
}
return replacePeerTrackNodes(prevPeerTrackNodes, peer);
}
if (peer.isLocal && !localTileInsetEnabled) {
return [newPeerTrackNode, ...prevPeerTrackNodes];
}
if (!peer.isLocal && (miniviewPeerTrackNode ? newPeerTrackNode.id !== miniviewPeerTrackNode.id : true)) {
return [...prevPeerTrackNodes, newPeerTrackNode];
}
return prevPeerTrackNodes;
});
}
// - TODO: update local localPeer state
// - Pass this updated data to Meeting component -> DisplayView component
if (peer.isLocal) {
if (track.source === HMSTrackSource.REGULAR) {
if (!localPeerTrackNode) {
if (isPublishingAllowed(newPeerTrackNode.peer)) {
dispatch(setLocalPeerTrackNode(newPeerTrackNode));
}
} else {
dispatch(updateLocalPeerTrackNode(track.type === HMSTrackType.VIDEO ? {
peer,
track
} : {
peer
}));
}
if (localTileInsetEnabled) {
// only setting `miniviewPeerTrackNode`, when:
// - there is no `miniviewPeerTrackNode`
// - if there is, then it is of regular track
if (!miniviewPeerTrackNode) {
dispatch(setMiniViewPeerTrackNode(newPeerTrackNode));
} else if (miniviewPeerTrackNode.id === newPeerTrackNode.id) {
dispatch(updateMiniViewPeerTrackNode(track.type === HMSTrackType.VIDEO ? {
peer,
track
} : {
peer
}));
}
}
// if (track.type === HMSTrackType.AUDIO) {
// dispatch(setIsLocalAudioMutedState(track.isMute()));
// } else if (track.type === HMSTrackType.VIDEO) {
// dispatch(setIsLocalVideoMutedState(track.isMute()));
// }
}
// else -> {
// should `localPeerTrackNode` be created/updated for non-regular track addition?
// should `miniviewPeerTrackNode` be created/updated for non-regular track addition?
// }
updateLocalPeer();
} else {
// only setting `miniviewPeerTrackNode`, when:
// - there is already `miniviewPeerTrackNode`
// - and it is of same peer's regular track
if (miniviewPeerTrackNode && miniviewPeerTrackNode.id === newPeerTrackNode.id) {
dispatch(updateMiniViewPeerTrackNode(track.type === HMSTrackType.VIDEO ? {
peer,
track
} : {
peer
}));
}
}
return;
}
if (type === HMSTrackUpdate.TRACK_REMOVED) {
var _peer$audioTrack, _peer$videoTrack;
if (track.source === HMSTrackSource.SCREEN) {
if (!peer.isLocal && track.type === HMSTrackType.VIDEO) {
hmsInstance.setActiveSpeakerInIOSPIP(true);
const screensharePeerTrackNodes = reduxState.app.screensharePeerTrackNodes;
const nodeToRemove = screensharePeerTrackNodes.find(node => {
var _node$track;
return ((_node$track = node.track) === null || _node$track === void 0 ? void 0 : _node$track.trackId) === track.trackId;
});
if (nodeToRemove) {
dispatch(removeScreenshareTile(nodeToRemove.id));
}
}
} else if (track.source === HMSTrackSource.PLUGIN || ((_peer$audioTrack = peer.audioTrack) === null || _peer$audioTrack === void 0 ? void 0 : _peer$audioTrack.trackId) === undefined && ((_peer$videoTrack = peer.videoTrack) === null || _peer$videoTrack === void 0 ? void 0 : _peer$videoTrack.trackId) === undefined) {
setPeerTrackNodes(prevPeerTrackNodes => removePeerTrackNodesWithTrack(prevPeerTrackNodes, peer, track));
}
if (fullScreenPeerTrackNode && fullScreenPeerTrackNode.track && fullScreenPeerTrackNode.track.trackId === track.trackId) {
dispatch(setFullScreenPeerTrackNode(null));
}
// - TODO: update local localPeer state
// - Pass this updated data to Meeting component -> DisplayView component
if (peer.isLocal) {
if (track.source === HMSTrackSource.REGULAR) {
var _peer$audioTrack2, _peer$videoTrack2;
if (!((_peer$audioTrack2 = peer.audioTrack) !== null && _peer$audioTrack2 !== void 0 && _peer$audioTrack2.trackId) && !((_peer$videoTrack2 = peer.videoTrack) !== null && _peer$videoTrack2 !== void 0 && _peer$videoTrack2.trackId)) {
dispatch(setLocalPeerTrackNode(null));
// removing `miniviewPeerTrackNode`, when:
// - `localPeerTrack` was used as `miniviewPeerTrackNode`
// - and now local peer doesn't have any tracks
if (miniviewPeerTrackNode && miniviewPeerTrackNode.peer.peerID === peer.peerID) {
dispatch(setMiniViewPeerTrackNode(null));
}
} else {
if (track.type === HMSTrackType.VIDEO) {
dispatch(updateLocalPeerTrackNode({
peer,
track: undefined
}));
} else {
dispatch(updateLocalPeerTrackNode({
peer
}));
}
// updating `miniviewPeerTrackNode`
if (miniviewPeerTrackNode && miniviewPeerTrackNode.peer.peerID === peer.peerID) {
if (track.type === HMSTrackType.VIDEO) {
dispatch(updateMiniViewPeerTrackNode({
peer,
track: undefined
}));
} else {
dispatch(updateMiniViewPeerTrackNode({
peer
}));
}
}
}
}
updateLocalPeer();
} else {
// only removing `miniviewPeerTrackNode`, when:
// - there is already `miniviewPeerTrackNode`
// - and it is of same peer's regular track
const uniqueId = createPeerTrackNodeUniqueId(peer, track);
if (miniviewPeerTrackNode && miniviewPeerTrackNode.id === uniqueId) {
dispatch(setMiniViewPeerTrackNode(null));
}
}
return;
}
if (type === HMSTrackUpdate.TRACK_MUTED || type === HMSTrackUpdate.TRACK_UNMUTED) {
// - TODO: update local mute states
// - Pass this updated data to Meeting component -> DisplayView component
if (peer.isLocal) {
if (track.type === HMSTrackType.AUDIO) {
dispatch(setIsLocalAudioMutedState(track.isMute()));
} else if (track.type === HMSTrackType.VIDEO) {
dispatch(setIsLocalVideoMutedState(track.isMute()));
}
}
setPeerTrackNodes(prevPeerTrackNodes => {
if (peerTrackNodeExistForPeerAndTrack(prevPeerTrackNodes, peer, track)) {
if (track.type === HMSTrackType.VIDEO) {
return replacePeerTrackNodesWithTrack(prevPeerTrackNodes, peer, track);
}
return replacePeerTrackNodes(prevPeerTrackNodes, peer);
}
return prevPeerTrackNodes;
});
const uniqueId = createPeerTrackNodeUniqueId(peer, track);
// - TODO: update local localPeer state
// - Pass this updated data to Meeting component -> DisplayView component
const updatePayload = track.type === HMSTrackType.VIDEO ? {
peer,
track
} : {
peer
};
if (peer.isLocal) {
dispatch(updateLocalPeerTrackNode(updatePayload));
updateLocalPeer();
}
if (miniviewPeerTrackNode && miniviewPeerTrackNode.id === uniqueId) {
dispatch(updateMiniViewPeerTrackNode(updatePayload));
}
if (fullScreenPeerTrackNode && fullScreenPeerTrackNode.id === uniqueId) {
dispatch(updateFullScreenPeerTrackNode(updatePayload));
}
return;
}
if (type === HMSTrackUpdate.TRACK_RESTORED || type === HMSTrackUpdate.TRACK_DEGRADED) {
// Checking if track source is screenshare
if (track.source === HMSTrackSource.SCREEN) {
// Handling screenshare tiles list
const screensharePeerTrackNodes = reduxState.app.screensharePeerTrackNodes;
const nodeToUpdate = screensharePeerTrackNodes.find(node => {
var _node$track2;
return ((_node$track2 = node.track) === null || _node$track2 === void 0 ? void 0 : _node$track2.trackId) === track.trackId;
});
if (nodeToUpdate) {
dispatch(updateScreenshareTile({
id: nodeToUpdate.id,
isDegraded: type === HMSTrackUpdate.TRACK_DEGRADED
}));
}
} else {
// Handling regular tiles list
setPeerTrackNodes(prevPeerTrackNodes => {
if (peerTrackNodeExistForPeerAndTrack(prevPeerTrackNodes, peer, track)) {
return degradeOrRestorePeerTrackNodes(prevPeerTrackNodes, peer, track, type === HMSTrackUpdate.TRACK_DEGRADED);
}
return prevPeerTrackNodes;
});
}
const uniqueId = createPeerTrackNodeUniqueId(peer, track);
// Handling miniview
if (miniviewPeerTrackNode && miniviewPeerTrackNode.id === uniqueId) {
dispatch(updateMiniViewPeerTrackNode({
isDegraded: type === HMSTrackUpdate.TRACK_DEGRADED
}));
}
// Handling full screen view
if (fullScreenPeerTrackNode && fullScreenPeerTrackNode.id === uniqueId) {
dispatch(updateFullScreenPeerTrackNode({
isDegraded: type === HMSTrackUpdate.TRACK_DEGRADED
}));
}
return;
}
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_TRACK_UPDATE, trackUpdateHandler);
return () => {
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_TRACK_UPDATE);
};
}, [hmsInstance]);
};
const useUpdateHMSLocalPeer = hmsInstance => {
const mountRef = useRef(false);
const dispatch = useDispatch();
const updateLocalPeer = useCallback(() => {
hmsInstance.getLocalPeer().then(latestLocalPeer => {
if (mountRef.current) {
dispatch(setHMSLocalPeerState(latestLocalPeer));
}
});
}, [hmsInstance]);
useEffect(() => {
mountRef.current = true;
updateLocalPeer();
return () => {
mountRef.current = false;
};
}, [updateLocalPeer]);
return updateLocalPeer;
};
export const useHMSInstance = () => {
const hmsInstance = useSelector(state => state.user.hmsInstance);
if (!hmsInstance) {
throw new Error('HMS Instance not available');
}
return hmsInstance;
};
export const useIsHLSViewer = () => {
return useSelector(state => {
const {
layoutConfig,
localPeer
} = state.hmsStates;
const selectedLayoutConfig = selectLayoutConfigForRole(layoutConfig, (localPeer === null || localPeer === void 0 ? void 0 : localPeer.role) || null);
return selectIsHLSViewer(localPeer === null || localPeer === void 0 ? void 0 : localPeer.role, selectedLayoutConfig);
});
};
export const useHMSChangeTrackStateRequest = (callback, deps) => {
const hmsInstance = useHMSInstance();
const [trackStateChangeRequest, setTrackStateChangeRequest] = useState(null);
useEffect(() => {
const changeTrackStateRequestHandler = request => {
if (!(request !== null && request !== void 0 && request.mute)) {
var _request$requestedBy;
setTrackStateChangeRequest({
requestedBy: request === null || request === void 0 || (_request$requestedBy = request.requestedBy) === null || _request$requestedBy === void 0 ? void 0 : _request$requestedBy.name,
suggestedRole: request === null || request === void 0 ? void 0 : request.trackType
});
callback === null || callback === void 0 || callback(request);
} else {
var _request$requestedBy2;
Toast.showWithGravity(`Track Muted: ${request === null || request === void 0 || (_request$requestedBy2 = request.requestedBy) === null || _request$requestedBy2 === void 0 ? void 0 : _request$requestedBy2.name} Muted Your ${request === null || request === void 0 ? void 0 : request.trackType}`, Toast.LONG, Toast.TOP);
}
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_CHANGE_TRACK_STATE_REQUEST, changeTrackStateRequestHandler);
return () => {
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_CHANGE_TRACK_STATE_REQUEST);
};
}, [...(deps || []), hmsInstance]);
return trackStateChangeRequest;
};
export const useHMSRoleChangeRequest = (callback, deps) => {
const taskRef = useRef(null);
const dispatch = useDispatch();
const hmsInstance = useHMSInstance();
useEffect(() => {
const changeRoleRequestHandler = async request => {
taskRef.current = InteractionManager.runAfterInteractions(() => {
dispatch(setRoleChangeRequest(request));
callback === null || callback === void 0 || callback(request);
});
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_ROLE_CHANGE_REQUEST, changeRoleRequestHandler);
return () => {
var _taskRef$current;
(_taskRef$current = taskRef.current) === null || _taskRef$current === void 0 || _taskRef$current.cancel();
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_ROLE_CHANGE_REQUEST);
};
}, [...(deps || []), hmsInstance]);
};
export const useHMSSessionStoreListeners = gridViewRef => {
const store = useStore();
const dispatch = useDispatch();
const hmsSessionStore = useSelector(state => state.user.hmsSessionStore);
const sessionStoreListenersRef = useRef([]);
useEffect(() => {
// Check if instance of HMSSessionStore is available
if (hmsSessionStore) {
// let toastTimeoutId: NodeJS.Timeout | null = null;
const addSessionStoreListeners = () => {
// Handle 'spotlight' key values
const handleSpotlightIdChange = id => {
if (id === null || id === undefined || typeof id === 'string') {
var _gridViewRef$current;
// set value to the state to rerender the component to reflect changes
dispatch(saveUserData({
spotlightTrackId: id
}));
// Scroll to start of the list
gridViewRef === null || gridViewRef === void 0 || (_gridViewRef$current = gridViewRef.current) === null || _gridViewRef$current === void 0 || (_gridViewRef$current = _gridViewRef$current.getRegularTilesFlatlistRef().current) === null || _gridViewRef$current === void 0 || _gridViewRef$current.scrollToOffset({
animated: true,
offset: 0
});
}
};
// Handle 'pinnedMessages' key values
const handlePinnedMessagesChange = data => {
if (Array.isArray(data)) {
dispatch(addPinnedMessages(data));
}
};
// Handle 'chatState' key values
const handleChatStateChange = data => {
try {
var _reduxState$hmsStates3;
if (typeof data !== 'object' || Array.isArray(data) || data === null) {
throw new Error('`data` is a falsy value');
}
if (!('enabled' in data)) {
throw new Error("`data` doesn't have `enabled` property");
}
const parsedData = data;
const reduxState = store.getState();
const currentChatState = reduxState.app.chatState;
if (parsedData.enabled === (currentChatState === null || currentChatState === void 0 ? void 0 : currentChatState.enabled)) {
return;
}
const currentLayoutConfig = selectLayoutConfigForRole(reduxState.hmsStates.layoutConfig, ((_reduxState$hmsStates3 = reduxState.hmsStates.localPeer) === null || _reduxState$hmsStates3 === void 0 ? void 0 : _reduxState$hmsStates3.role) ?? null);
const chatLayoutConfig = selectChatLayoutConfig(currentLayoutConfig);
const isAllowedToSendMessage = ((chatLayoutConfig === null || chatLayoutConfig === void 0 ? void 0 : chatLayoutConfig.private_chat_enabled) || (chatLayoutConfig === null || chatLayoutConfig === void 0 ? void 0 : chatLayoutConfig.public_chat_enabled) || (chatLayoutConfig === null || chatLayoutConfig === void 0 ? void 0 : chatLayoutConfig.roles_whitelist) && (chatLayoutConfig === null || chatLayoutConfig === void 0 ? void 0 : chatLayoutConfig.roles_whitelist.length) > 0) ?? false;
batch(() => {
if (isAllowedToSendMessage && (
// Only show notification when allowed to send message, AND
!parsedData.enabled ||
// Chat is Paused, OR
currentChatState && parsedData.enabled !== currentChatState.enabled) // current Chat state is different from previous state
) {
dispatch(addNotification({
id: `chat-state-enabled-${Math.random().toString(16).slice(2)}`,
icon: parsedData.enabled ? 'chat-on' : 'chat-off',
type: NotificationTypes.INFO,
title: `Chat ${parsedData.enabled ? 'Resumed' : 'Paused'}`,
message: `Chat ${parsedData.enabled ? 'resumed' : 'paused'} ${parsedData.updatedBy ? `by ${parsedData.updatedBy.userName}` : ''}`
}));
}
dispatch(setChatState(parsedData));
});
} catch (error) {
dispatch(setChatState(null));
}
};
// Handle 'chatPeerBlacklist' key values
const handleChatPeerBlacklistChange = data => {
// Whenever list changes :
// - check if local peer is blocked or unblocked
// - filter out messages of blocked peers
if (Array.isArray(data)) {
batch(() => {
dispatch(setChatPeerBlacklist(data));
dispatch(filterOutMsgsFromBlockedPeers(data));
});
}
};
// Getting value for 'spotlight' key by using `get` method on HMSSessionStore instance
hmsSessionStore.get('spotlight').then(data => {
console.log('Session Store get `spotlight` key value success: ', data);
handleSpotlightIdChange(data);
}).catch(error => console.log('Session Store get `spotlight` key value error: ', error));
// Getting value for 'pinnedMessages' key by using `get` method on HMSSessionStore instance
hmsSessionStore.get('pinnedMessages').then(data => {
console.log('Session Store get `pinnedMessages` key value success: ', data);
handlePinnedMessagesChange(data);
}).catch(error => console.log('Session Store get `pinnedMessages` key value error: ', error));
// Getting value for 'chatState' key by using `get` method on HMSSessionStore instance
hmsSessionStore.get('chatState').then(data => {
console.log('Session Store get `chatState` key value success: ', data);
handleChatStateChange(data);
}).catch(error => console.log('Session Store get `chatState` key value error: ', error));
// Getting value for 'chatPeerBlacklist' key by using `get` method on HMSSessionStore instance
hmsSessionStore.get('chatPeerBlacklist').then(data => {
console.log('Session Store get `chatPeerBlacklist` key value success: ', data);
handleChatPeerBlacklistChange(data);
}).catch(error => console.log('Session Store get `chatPeerBlacklist` key value error: ', error));
// let lastSpotlightValue: HMSSessionStoreValue = null;
// let lastPinnedMessageValue: HMSSessionStoreValue = null;
// Add subscription for `spotlight`, `pinnedMessages`, `chatState` & `chatPeerBlacklist` keys updates on Session Store
const subscription = hmsSessionStore.addKeyChangeListener(['spotlight', 'pinnedMessages', 'chatState', 'chatPeerBlacklist'], (error, data) => {
// If error occurs, handle error and return early
if (error !== null) {
console.log('`spotlight`, `pinnedMessages`, `chatState` & `chatPeerBlacklist` key listener Error -> ', error);
return;
}
// If no error, handle data
if (data !== null) {
switch (data.key) {
case 'spotlight':
{
handleSpotlightIdChange(data.value);
break;
}
case 'pinnedMessages':
{
handlePinnedMessagesChange(data.value);
break;
}
case 'chatState':
{
handleChatStateChange(data.value);
break;
}
case 'chatPeerBlacklist':
{
handleChatPeerBlacklistChange(data.value);
break;
}
}
}
});
// Save reference of `subscription` in a ref
sessionStoreListenersRef.current.push(subscription);
};
addSessionStoreListeners();
return () => {
// remove Session Store key update listener on cleanup
sessionStoreListenersRef.current.forEach(listener => listener.remove());
// if (toastTimeoutId !== null) clearTimeout(toastTimeoutId);
};
}
}, [store, hmsSessionStore]);
};
export const useHMSSessionStore = () => {
const hmsInstance = useHMSInstance();
const dispatch = useDispatch();
useEffect(() => {
const onSessionStoreAvailableListener = ({
sessionStore
}) => {
// Saving `sessionStore` reference in `redux`
dispatch(saveUserData({
hmsSessionStore: sessionStore
}));
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_SESSION_STORE_AVAILABLE, onSessionStoreAvailableListener);
return () => {
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_SESSION_STORE_AVAILABLE);
};
}, [hmsInstance]);
};
export const useHMSMessages = () => {
const hmsInstance = useHMSInstance();
const dispatch = useDispatch();
const canChangeRole = useSelector(state => {
var _state$hmsStates$loca;
return (_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 || (_state$hmsStates$loca = _state$hmsStates$loca.permissions) === null || _state$hmsStates$loca === void 0 ? void 0 : _state$hmsStates$loca.changeRole;
});
const canShowChat = useHMSConferencingScreenConfig(conferencingScreenConfig => {
var _conferencingScreenCo;
return !!(conferencingScreenConfig !== null && conferencingScreenConfig !== void 0 && (_conferencingScreenCo = conferencingScreenConfig.elements) !== null && _conferencingScreenCo !== void 0 && _conferencingScreenCo.chat);
});
useEffect(() => {
const onMessageListener = message => {
if (message.type === NotificationTypes.ROLE_CHANGE_DECLINED) {
if (canChangeRole) {
var _message$sender;
dispatch(addNotification({
id: `${(_message$sender = message.sender) === null || _message$sender === void 0 ? void 0 : _message$sender.peerID}-${NotificationTypes.ROLE_CHANGE_DECLINED}`,
type: NotificationTypes.ROLE_CHANGE_DECLINED,
peer: message.sender
}));
}
} else if (message.type === 'EMOJI_REACTION') {
console.log('Ignoring Emoji Reaction Message: ', message);
} else if (canShowChat) {
dispatch(addMessage(message));
}
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_MESSAGE, onMessageListener);
return () => {
// TODO: Remove this listener when user leaves, removed or room is ended
hmsInstance.removeEventListener(HMSUpdateListenerActions.ON_MESSAGE);
};
}, [canChangeRole, canShowChat, hmsInstance]);
};
export const useHMSReconnection = () => {
const dispatch = useDispatch();
const hmsInstance = useHMSInstance();
useEffect(() => {
let mounted = true;
hmsInstance.addEventListener(HMSUpdateListenerActions.RECONNECTING, () => {
if (mounted) {
batch(() => {
dispatch(setReconnecting(true));
dispatch(addNotification({
id: NotificationTypes.RECONNECTING,
type: NotificationTypes.RECONNECTING
}));
});
}
});
hmsInstance.addEventListener(HMSUpdateListenerActions.RECONNECTED, () => {
if (mounted) {
batch(() => {
dispatch(setReconnecting(false));
dispatch(removeNotification(NotificationTypes.RECONNECTING));
});
}
});
return () => {
mounted = false;
hmsInstance.removeEventListener(HMSUpdateListenerActions.RECONNECTING);
hmsInstance.removeEventListener(HMSUpdateListenerActions.RECONNECTED);
};
}, [hmsInstance]);
};
export const useHMSPIPRoomLeave = () => {
const hmsInstance = useHMSInstance();
const {
destroy
} = useLeaveMethods();
useEffect(() => {
const pipRoomLeaveHandler = () => {
destroy(OnLeaveReason.PIP);
};
hmsInstance.addEventListener(HMSPIPListenerActions.ON_PIP_ROOM_LEAVE, pipRoomLeaveHandler);
return () => {
hmsInstance.removeEventListener(HMSPIPListenerActions.ON_PIP_ROOM_LEAVE);
};
}, [destroy, hmsInstance]);
};
export const useHMSRemovedFromRoomUpdate = () => {
const hmsInstance = useHMSInstance();
const {
destroy
} = useLeaveMethods();
useEffect(() => {
const removedFromRoomHandler = data => {
destroy(data.roomEnded ? OnLeaveReason.ROOM_END : OnLeaveReason.PEER_KICKED);
};
hmsInstance.addEventListener(HMSUpdateListenerActions.ON_REMOVED_FROM_ROOM, removedFromRoomHandler);
return () => {
hmsInstance.rem