@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
168 lines (158 loc) • 5.9 kB
text/typescript
import { HMSPeerStats, HMSTrackStats } from '../../interfaces';
import { HMSLocalTrack as SDKHMSLocalTrack, HMSPoll } from '../../internal';
import { HMSPeer, HMSPeerID, HMSScreenVideoTrack, HMSTrack, HMSTrackID, HMSVideoTrack } from '../../schema';
/**
* updates draftPeers with newPeers ensuring minimal reference changes
* @remarks
* This is mutable and impure function, it modifies the passed in data to ensure
* minimal reference changes
* @param draftPeers the current peers object in store, an immer draft object
* @param newPeers the latest update which needs to be stored
* @param newHmsTracks this will be update if required
* @param newHmsSDkTracks this is future value of local hms tacks map
*/
// eslint-disable-next-line complexity
export const mergeNewPeersInDraft = (
draftPeers: Record<HMSPeerID, HMSPeer>,
newPeers: Record<HMSPeerID, Partial<HMSPeer>>,
) => {
const peerIDs = union(Object.keys(draftPeers), Object.keys(newPeers));
for (const peerID of peerIDs) {
const oldPeer = draftPeers[peerID];
const newPeer = newPeers[peerID];
if (isEntityUpdated(oldPeer, newPeer)) {
if (areArraysEqual(oldPeer.auxiliaryTracks, newPeer.auxiliaryTracks)) {
newPeer.auxiliaryTracks = oldPeer.auxiliaryTracks;
}
if (oldPeer.groups && areArraysEqual(oldPeer.groups, newPeer.groups)) {
newPeer.groups = oldPeer.groups;
}
Object.assign(oldPeer, newPeer);
} else if (isEntityRemoved(oldPeer, newPeer)) {
delete draftPeers[peerID];
} else if (isEntityAdded(oldPeer, newPeer)) {
draftPeers[peerID] = newPeer as HMSPeer;
}
}
};
export const mergeNewTracksInDraft = (
draftTracks: Record<HMSTrackID, HMSTrack>,
newTracks: Record<HMSTrackID, Partial<HMSTrack>>,
) => {
const trackIDs = union(Object.keys(draftTracks), Object.keys(newTracks));
for (const trackID of trackIDs) {
const oldTrack = draftTracks[trackID];
const newTrack = newTracks[trackID];
if (isEntityUpdated(oldTrack, newTrack)) {
mergeTrackArrayFields(oldTrack, newTrack);
Object.assign(oldTrack, newTrack);
} else if (isEntityRemoved(oldTrack, newTrack)) {
delete draftTracks[trackID];
} else if (isEntityAdded(oldTrack, newTrack)) {
draftTracks[trackID] = newTrack as HMSTrack;
}
}
};
export const mergeNewPollsInDraft = (
draftPolls: Record<string, HMSPoll>,
newPolls: Record<string, Partial<HMSPoll>>,
) => {
const pollIDs = union(Object.keys(draftPolls), Object.keys(newPolls));
for (const pollID of pollIDs) {
const oldPoll = draftPolls[pollID];
const newPoll = newPolls[pollID];
if (isEntityUpdated(oldPoll, newPoll)) {
if (oldPoll.questions && areArraysEqual(oldPoll.questions, newPoll.questions)) {
newPoll.questions = oldPoll.questions;
}
Object.assign(oldPoll, newPoll);
} else if (isEntityAdded(oldPoll, newPoll)) {
draftPolls[pollID] = newPoll as HMSPoll;
}
}
};
export const mergeNewIndividualStatsInDraft = <TID extends string, T extends HMSPeerStats | HMSTrackStats>(
draftStats: Record<TID, T | undefined>,
newStats: Record<TID, Partial<T | undefined>>,
) => {
const IDs = union(Object.keys(draftStats), Object.keys(newStats)) as TID[];
for (const trackID of IDs) {
const oldStat = draftStats[trackID];
const newStat = newStats[trackID];
if (isEntityUpdated(oldStat, newStat)) {
Object.assign(oldStat!, newStat);
} else if (isEntityRemoved(oldStat, newStat)) {
delete draftStats[trackID];
} else if (isEntityAdded(oldStat, newStat)) {
draftStats[trackID] = newStat as T;
}
}
};
export const mergeLocalTrackStats = (
draftStats: Record<HMSTrackID, HMSTrackStats[] | undefined>,
newStats: Record<HMSTrackID, Record<string, HMSTrackStats>>,
tracks: SDKHMSLocalTrack[],
) => {
const trackMap: Record<string, HMSTrackStats[]> = tracks.reduce((acc, track) => {
// @ts-ignore
acc[track.firstTrackId] = Object.values(newStats[track.getTrackIDBeingSent()] || {}).sort((a, b) => {
if (!a.rid || !b.rid) {
return 0;
}
return a.rid < b.rid ? -1 : 1;
});
return acc;
}, {});
const IDs = union(Object.keys(draftStats), Object.keys(trackMap));
for (const trackID of IDs) {
if (!trackMap[trackID]) {
delete draftStats[trackID];
continue;
}
draftStats[trackID] = trackMap[trackID];
}
};
/**
* array's are usually created with new reference, avoid that update if both arrays are same
*/
export const mergeTrackArrayFields = (oldTrack: HMSTrack, newTrack: Partial<HMSTrack>) => {
if (oldTrack.plugins && areArraysEqual(oldTrack.plugins, newTrack.plugins)) {
newTrack.plugins = oldTrack.plugins;
}
if (
oldTrack.type === 'video' &&
oldTrack.layerDefinitions &&
areArraysEqual(oldTrack.layerDefinitions, (newTrack as HMSVideoTrack | HMSScreenVideoTrack).layerDefinitions)
) {
(newTrack as HMSVideoTrack | HMSScreenVideoTrack).layerDefinitions = oldTrack.layerDefinitions;
}
};
export const isEntityUpdated = <T>(oldItem: T, newItem: T) => oldItem && newItem;
const isEntityRemoved = <T>(oldItem: T, newItem: T) => oldItem && !newItem;
const isEntityAdded = <T>(oldItem: T, newItem: T) => !oldItem && newItem;
// eslint-disable-next-line complexity
export const areArraysEqual = <T>(arr1: T[], arr2?: T[]): boolean => {
if (arr1 === arr2 || (arr1.length === 0 && arr2?.length === 0)) {
// reference check
return true;
}
if (!arr1 || !arr2 || !(arr1.length === arr2.length)) {
return false;
}
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
};
const union = <T>(arr1: T[], arr2: T[]): T[] => {
const set: Set<T> = new Set();
for (const elem of arr1) {
set.add(elem);
}
for (const elem of arr2) {
set.add(elem);
}
return Array.from(set);
};