@100mslive/hms-video-store
Version:
@100mslive Core SDK which abstracts the complexities of webRTC while providing a reactive store for data management with a unidirectional data flow
482 lines (427 loc) • 16.7 kB
text/typescript
import { createSelector } from 'reselect';
import {
getScreenSharesByPeer,
isAudioPlaylist,
isDegraded,
isTrackDisplayEnabled,
isTrackEnabled,
isVideo,
isVideoPlaylist,
} from './selectorUtils';
// noinspection ES6PreferShortImport
import { HMSRole, HMSTranscriptionMode, HMSTranscriptionState, HMSWhiteboard } from '../internal';
import {
HMSException,
HMSMessage,
HMSPeer,
HMSPeerID,
HMSRoom,
HMSRoomState,
HMSStore,
HMSVideoTrack,
} from '../schema';
/**
* Select the current {@link HMSRoom} object to which you are connected.
* @param store
*/
export const selectRoom = (store: HMSStore): HMSRoom => store.room;
/**
* Select the current {@link HMSException[]} object to monitor the error logs
* @param store
*/
export const selectErrors = (store: HMSStore): HMSException[] => store.errors;
/**
* It will help to get the all the error
*/
export const selectRecentError = createSelector(selectErrors, errors => (errors.length === 0 ? null : errors.at(-1)));
/**
* Select the ID of the current room to which you are connected.
*/
export const selectRoomID = createSelector(selectRoom, room => room.id);
/**
* @internal
*/
export const selectPeersMap = (store: HMSStore): Record<HMSPeerID, HMSPeer> => store.peers;
/**
* @internal
*/
export const selectMessagesMap = (store: HMSStore) => store.messages.byID;
/**
* Select IDs of messages you've sent or received sorted chronologically.
*/
export const selectMessageIDsInOrder = (store: HMSStore) => store.messages.allIDs;
/**
* @internal
*/
export const selectTracksMap = (store: HMSStore) => store.tracks;
/**
* Select your media settings
* i.e., choosen audio input device, audio output device and video input device, audio mode
* @param store
*/
export const selectLocalMediaSettings = (store: HMSStore) => store.settings;
/**
* select appData.
* @internal
*/
export const selectFullAppData = (store: HMSStore) => store.appData;
/**
* Select the available audio input, audio output and video input devices on your machine.
* @param store
* @returns An object of array of available audio input, audio output and video input devices.
* ```
* type DeviceMap = {
* audioInput: InputDeviceInfo[];
* audioOutput: MediaDeviceInfo[];
* videoInput: InputDeviceInfo[];
* }
* ```
*/
export const selectDevices = (store: HMSStore) => {
return store.devices;
};
export const selectSpeakers = (store: HMSStore) => {
return store.speakers;
};
export const selectConnectionQualities = (store: HMSStore) => {
return store.connectionQualities;
};
/**
* Select a boolean flag denoting whether you've joined a room.
* NOTE: Returns true only after join, returns false during preview.
*/
export const selectIsConnectedToRoom = createSelector([selectRoom], room => room && room.isConnected);
/**
* selectPeerCount gives the number of peers Inside the room. This doesn't count the local peer if
* they're still in preview and haven't yet joined the room. Note that this will not necessarily equal the
* number of peers received through selectPeers, it's possible to know total number of people in the room
* without having details of everyone depending on dashboard settings.
*/
export const selectPeerCount = createSelector([selectIsConnectedToRoom, selectRoom], (isConnected, room) => {
if (isConnected) {
// if we have peer count from server return that else return number of peers in the store.
// In case the strongly consistent peer list is disabled and only eventual consistent count and peer
// details is sent, room.peerCount may be 0 for a few second even though local peer is connected, send 1 in that case.
// TODO: Fix this at populating room.peerCount level than in selector.
return room.peerCount !== undefined ? room.peerCount || 1 : room.peers.length;
} else {
// if we have peer count from server return that, else return number of peers except the local one because local is
// not joined yet.
// Math.max to ensure we're not returning -1, if the selector is called before local peer is put in the store
return Math.max(room.peerCount !== undefined ? room.peerCount : room.peers.length - 1, 0);
}
});
/**
* @internal
* Select a boolean flag denoting whether to hide local peer.
* When this is true, `selectPeers` skips local peer.
*/
const selectHideLocalPeer = (store: HMSStore): boolean => store.hideLocalPeer;
/**
* Select an array of peers(remote peers and your local peer) present in the room.
*/
export const selectPeers = createSelector(
[selectRoom, selectPeersMap, selectHideLocalPeer],
(room, storePeers, hideLocalPeer) => {
if (hideLocalPeer) {
return room.peers.filter(peerID => room.localPeer !== peerID).map(peerID => storePeers[peerID]);
}
return room.peers.map(peerID => storePeers[peerID]);
},
);
/**
* Select an array of tracks(remote peer tracks and your local tracks) present in the room.
*/
const selectTracks = createSelector(selectTracksMap, storeTracks => {
return Object.values(storeTracks);
});
/**
* Select the local peer object object assigned to you.
*/
export const selectLocalPeer = createSelector(selectRoom, selectPeersMap, (room, peers): HMSPeer | undefined => {
return peers[room.localPeer];
});
/**
* Select the peer ID of your local peer.
*/
export const selectLocalPeerID = createSelector(selectRoom, room => {
return room.localPeer;
});
/**
* Select the peer name of your local peer.
*/
export const selectLocalPeerName = createSelector(selectLocalPeer, peer => peer?.name);
/**
* Select the role name of your local peer.
*/
export const selectLocalPeerRoleName = createSelector(selectLocalPeer, peer => peer?.roleName);
/**
* Select the track ID of your local peer's primary audio track
*/
export const selectLocalAudioTrackID = createSelector(selectLocalPeer, peer => peer?.audioTrack);
/**
* Select the track ID of your local peer's primary video track
*/
export const selectLocalVideoTrackID = createSelector(selectLocalPeer, peer => peer?.videoTrack);
/**
* Select an array of track IDs of your local peer's auxiliary tracks
*/
const selectLocalAuxiliaryTrackIDs = createSelector(selectLocalPeer, peer => peer?.auxiliaryTracks);
/**
* Select an array of track IDs of all your local peer's tracks
*/
export const selectLocalTrackIDs = createSelector(
[selectLocalAudioTrackID, selectLocalVideoTrackID, selectLocalAuxiliaryTrackIDs],
(audioTrackID, videoTrackID, auxiliaryTrackIDs) => {
const trackIDs: string[] = auxiliaryTrackIDs ? [...auxiliaryTrackIDs] : [];
audioTrackID && trackIDs.unshift(audioTrackID);
videoTrackID && trackIDs.unshift(videoTrackID);
return trackIDs;
},
);
/**
* Select remote peers(other users you're connected with via the internet) present in the room.
*/
export const selectRemotePeers = createSelector(selectPeers, peers => {
return peers.filter(p => !p.isLocal);
});
/**
* Select the peer who's speaking the loudest at the moment
*/
export const selectDominantSpeaker = createSelector(selectPeersMap, selectSpeakers, (peersMap, speakers) => {
// sort in descending order by audio level
const speakersInOrder = Object.entries(speakers).sort((s1, s2) => {
const s1Level = s1[1]?.audioLevel || 0;
const s2Level = s2[1]?.audioLevel || 0;
return s2Level > s1Level ? 1 : -1;
});
if (speakersInOrder.length > 0 && speakersInOrder[0][1].audioLevel && speakersInOrder[0][1].audioLevel > 0) {
const peerID = speakersInOrder[0][1].peerID;
if (peerID in peersMap) {
return peersMap[peerID];
}
}
return null;
});
/**
* Select a boolean denoting whether your local audio is unmuted
* and the audio from your microphone is shared to remote peers
*/
export const selectIsLocalAudioEnabled = (store: HMSStore) => {
const localPeer = selectLocalPeer(store);
return isTrackEnabled(store, localPeer?.audioTrack);
};
/**
* Select a boolean denoting whether your local video is unmuted
* and the video from your camera is shared to remote peers
*/
export const selectIsLocalVideoEnabled = (store: HMSStore) => {
const localPeer = selectLocalPeer(store);
return isTrackEnabled(store, localPeer?.videoTrack);
};
/**
* Select a boolean denoting whether you've chosen to unmute and share your local video.
*
* NOTE: Once you call `hmsActions.setLocalVideoEnabled(true)`to unmute your local video,
* it takes some time to fetch your video from your video source.
* This displayEnabled property gives immediate feedback for a more interactive UI,
* without waiting for the video source
*/
export const selectIsLocalVideoDisplayEnabled = (store: HMSStore) => {
const localPeer = selectLocalPeer(store);
return isTrackDisplayEnabled(store, localPeer?.videoTrack);
};
/**
* Select a boolean denoting whether your screen is shared to remote peers in the room.
*/
export const selectIsLocalScreenShared = createSelector(selectLocalPeer, selectTracksMap, (localPeer, tracksMap) => {
const { video, audio } = getScreenSharesByPeer(tracksMap, localPeer);
return !!(video || audio);
});
/**
* Select the first peer who is currently sharing their screen.
*/
export const selectPeerScreenSharing = createSelector(selectPeersMap, selectTracksMap, (peersMap, tracksMap) => {
let screensharePeer = undefined;
for (const peerID in peersMap) {
const peer = peersMap[peerID];
const { video, audio } = getScreenSharesByPeer(tracksMap, peer);
if (video) {
return peer;
} else if (audio && !screensharePeer) {
screensharePeer = peer;
}
}
return screensharePeer;
});
/**
* Select a boolean denoting whether someone is sharing screen in the room.
*/
export const selectIsSomeoneScreenSharing = createSelector(selectPeerScreenSharing, peer => {
return !!peer;
});
/**
* Select the first peer who is currently sharing their audio only screen
*/
export const selectPeerSharingAudio = createSelector(selectPeersMap, selectTracksMap, (peersMap, tracksMap) => {
for (const peerID in peersMap) {
const peer = peersMap[peerID];
const { audio, video } = getScreenSharesByPeer(tracksMap, peer);
if (!video && !!audio) {
return peer;
}
}
return undefined;
});
/**
* Select an array of peers who are currently sharing their screen.
*/
export const selectPeersScreenSharing = createSelector(selectPeersMap, selectTracksMap, (peersMap, tracksMap) => {
const videoPeers = [];
const audioPeers = [];
for (const peerID in peersMap) {
const peer = peersMap[peerID];
const { video, audio } = getScreenSharesByPeer(tracksMap, peer);
if (video) {
videoPeers.push(peer);
} else if (audio) {
audioPeers.push(peer);
}
}
return videoPeers.concat(audioPeers);
});
export const selectPeerSharingVideoPlaylist = createSelector(selectPeersMap, selectTracksMap, (peersMap, tracksMap) => {
for (const trackId in tracksMap) {
const track = tracksMap[trackId];
if (isVideoPlaylist(track) && isVideo(track) && track.peerId) {
return peersMap[track.peerId];
}
}
return undefined;
});
export const selectPeerSharingAudioPlaylist = createSelector(selectPeersMap, selectTracksMap, (peersMap, tracksMap) => {
for (const trackId in tracksMap) {
const track = tracksMap[trackId];
if (isAudioPlaylist(track) && track.peerId) {
return peersMap[track.peerId];
}
}
return undefined;
});
/**
* Select an array of tracks that have been degraded(receiving lower video quality/no video) due to bad network locally.
*/
export const selectDegradedTracks = createSelector(selectTracks, tracks =>
(tracks as HMSVideoTrack[]).filter(isDegraded),
);
/**
* Select the number of messages(sent and received).
*/
export const selectHMSMessagesCount = createSelector(selectMessageIDsInOrder, messageIDs => messageIDs.length);
/**
* Select the number of unread messages.
*/
export const selectUnreadHMSMessagesCount = createSelector(selectMessagesMap, messages => {
return Object.values(messages).filter(m => !m.read).length;
});
/**
* Select an array of messages in the room(sent and received).
*/
export const selectHMSMessages = createSelector(selectMessageIDsInOrder, selectMessagesMap, (msgIDs, msgMap) => {
const messages: HMSMessage[] = [];
msgIDs.forEach(msgId => {
messages.push(msgMap[msgId]);
});
return messages;
});
export const selectHMSBroadcastMessages = createSelector(selectHMSMessages, messages => {
return messages.filter(m => !m.recipientPeer && !(m.recipientRoles && m.recipientRoles?.length > 0));
});
/**
* Select the number of unread broadcast messages
*/
export const selectUnreadHMSBroadcastMessagesCount = createSelector(selectHMSBroadcastMessages, messages => {
return messages.filter(m => !m.read).length;
});
/**
* Select the current state of the room.
*/
export const selectRoomState = createSelector([selectRoom], room => room && room.roomState);
/**
* Select a boolean denoting whether the room is in Preview state.
*/
export const selectIsInPreview = createSelector(selectRoomState, roomState => roomState === HMSRoomState.Preview);
export const selectRoomStarted = createSelector(selectRoom, room => room.roomState !== HMSRoomState.Disconnected);
export const selectIsTranscriptionEnabled = createSelector(selectRoom, room => {
if (!room.transcriptions || room.transcriptions.length <= 0) {
return false;
}
return room.transcriptions.some(
transcription =>
transcription.mode === HMSTranscriptionMode.CAPTION && transcription.state === HMSTranscriptionState.STARTED,
);
});
/**
* Select available roles in the room as a map between the role name and {@link HMSRole} object.
*/
export const selectRolesMap = (store: HMSStore): Record<string, HMSRole> => {
return store.roles;
};
/**
* Select an array of names of available roles in the room.
*/
export const selectAvailableRoleNames = createSelector([selectRolesMap], rolesMap => Object.keys(rolesMap));
/**
* Select the {@link HMSRole} object of your local peer.
*/
export const selectLocalPeerRole = createSelector([selectLocalPeer, selectRolesMap], (localPeer, rolesMap) =>
localPeer?.roleName ? rolesMap[localPeer.roleName] : null,
);
export const selectPreviewRoleName = (store: HMSStore) => store.preview?.asRole;
/**
* Select the {@link HMSRole} used for preview.
*
*/
export const selectPreviewRole = createSelector([selectPreviewRoleName, selectRolesMap], (roleName, rolesMap) =>
roleName ? rolesMap[roleName] : null,
);
/**
* Select a boolean denoting whether if your local peer is allowed to subscribe to any other role.
*/
export const selectIsAllowedToSubscribe = createSelector([selectLocalPeerRole], (role): boolean => {
if (!role?.subscribeParams?.subscribeToRoles) {
return false;
}
return role.subscribeParams.subscribeToRoles.length > 0;
});
/**
* Select the permissions which determine what actions the local peer can do.
*/
export const selectPermissions = createSelector(selectLocalPeerRole, role => role?.permissions);
export const selectRecordingState = createSelector(selectRoom, room => room.recording);
export const selectRTMPState = createSelector(selectRoom, room => room.rtmp);
export const selectHLSState = createSelector(selectRoom, room => room.hls);
export const selectTranscriptionsState = createSelector(selectRoom, room => room.transcriptions);
export const selectSessionId = createSelector(selectRoom, room => room.sessionId);
export const selectRoomStartTime = createSelector(selectRoom, room => room.startedAt);
export const selectIsLargeRoom = createSelector(selectRoom, room => !!room.isLargeRoom);
export const selectIsEffectsEnabled = createSelector(selectRoom, room => !!room.isEffectsEnabled);
export const selectIsVBEnabled = createSelector(selectRoom, room => !!room.isVBEnabled);
export const selectEffectsKey = createSelector(selectRoom, room => room.effectsKey);
export const selectTemplateAppData = (store: HMSStore) => store.templateAppData;
/** @deprecated - use `selectSessionStore` instead */
export const selectSessionMetadata = (store: HMSStore) => store.sessionMetadata;
export const selectPollsMap = (store: HMSStore) => store.polls;
export const selectPolls = (store: HMSStore) => {
return Object.values(store.polls);
};
export const selectHandRaisedPeers = createSelector(selectPeers, peers => {
return peers.filter(peer => peer.isHandRaised);
});
/** select a map of all the whiteboards in the session */
export const selectWhiteboards = (store: HMSStore) => store.whiteboards;
/** select the primary/first whiteboard of a session */
export const selectWhiteboard = createSelector(
selectWhiteboards,
whiteboards => Object.values(whiteboards)[0] as HMSWhiteboard | undefined,
);