@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
144 lines (129 loc) • 5.35 kB
text/typescript
import { IHMSStatsStore, IHMSStore } from '../IHMSStore';
import { HMSPeerStats, HMSTrackStats } from '../interfaces';
import { mergeLocalTrackStats, mergeNewIndividualStatsInDraft } from '../reactive-store/sdkUtils/storeMergeUtils';
import { HMSWebrtcStats } from '../rtc-stats';
import { createDefaultStatsStore, HMSPeerID, HMSRoomState, HMSTrack, HMSTrackID } from '../schema';
import { HMSSdk } from '../sdk';
import {
selectLocalAudioTrackID,
selectLocalPeerID,
selectLocalVideoTrackID,
selectRoomState,
selectTracksMap,
} from '../selectors';
type Unsubscribe = (() => void) | undefined;
export const subscribeToSdkWebrtcStats = (sdk: HMSSdk, webrtcStore: IHMSStatsStore, store: IHMSStore) => {
// also used as flag to check if webrtc internals has been initialised
let unsubscribe: Unsubscribe;
/**
* Connected to room, webrtc internals can be initialized
*/
if (store.getState(selectRoomState) === HMSRoomState.Connected) {
unsubscribe = initAndSubscribeWebrtcStore(sdk, webrtcStore, store);
}
/**
* Subscribe to room state for 2 purposes:
* - unsubscribe on leave
* - if internals is called before join is completed, init internals when roomState changes to connected
*/
store.subscribe(roomState => {
if ([HMSRoomState.Connected, HMSRoomState.Reconnecting].includes(roomState)) {
if (!unsubscribe) {
unsubscribe = initAndSubscribeWebrtcStore(sdk, webrtcStore, store);
}
// room state can go to disconnecting and back to connected if leave fails, we don't want to resubscribe in that case
} else if ([HMSRoomState.Disconnected, HMSRoomState.Failed].includes(roomState)) {
if (unsubscribe) {
resetHMSStatsStore(webrtcStore, roomState);
unsubscribe();
// set flag to defined after unsubscribing to enable subscribing again
unsubscribe = undefined;
}
}
}, selectRoomState);
};
const initAndSubscribeWebrtcStore = (sdk: HMSSdk, webrtcStore: IHMSStatsStore, store: IHMSStore) => {
const unsubLocalPeer = updateLocalPeerInWebrtcStore(store, webrtcStore);
sdk.getWebrtcInternals()?.start();
const unsubSdkStats = sdk
.getWebrtcInternals()
?.onStatsChange(stats => updateWebrtcStoreStats(webrtcStore, stats, store, sdk));
return () => {
unsubLocalPeer();
unsubSdkStats && unsubSdkStats();
};
};
const updateLocalPeerInWebrtcStore = (store: IHMSStore, webrtcStore: IHMSStatsStore) => {
let unsubID: Unsubscribe, unsubVideoTrackID: Unsubscribe, unsubAudioTrackID: Unsubscribe;
if (store.getState(selectLocalPeerID)) {
webrtcStore.namedSetState(draft => {
draft.localPeer.id = store.getState(selectLocalPeerID);
}, 'localpeer-id');
} else {
unsubID = store.subscribe(localPeerID => {
localPeerID &&
webrtcStore.namedSetState(draft => {
draft.localPeer.id = localPeerID;
}, 'localpeer-id');
}, selectLocalPeerID);
}
if (store.getState(selectLocalVideoTrackID)) {
webrtcStore.namedSetState(draft => {
draft.localPeer.videoTrack = store.getState(selectLocalVideoTrackID);
}, 'localpeer-videotrack-id');
} else {
unsubVideoTrackID = store.subscribe(videoTrackID => {
videoTrackID &&
webrtcStore.namedSetState(draft => {
draft.localPeer.videoTrack = videoTrackID;
}, 'localpeer-videotrack-id');
}, selectLocalVideoTrackID);
}
if (store.getState(selectLocalAudioTrackID)) {
webrtcStore.namedSetState(draft => {
draft.localPeer.audioTrack = store.getState(selectLocalAudioTrackID);
}, 'localpeer-audiotrack-id');
} else {
unsubAudioTrackID = store.subscribe(audioTrackID => {
audioTrackID &&
webrtcStore.namedSetState(draft => {
draft.localPeer.audioTrack = audioTrackID;
}, 'localpeer-audiotrack-id');
}, selectLocalAudioTrackID);
}
return () => {
unsubID?.();
unsubVideoTrackID?.();
unsubAudioTrackID?.();
};
};
const updateWebrtcStoreStats = (
webrtcStore: IHMSStatsStore,
stats: HMSWebrtcStats,
hmsStore: IHMSStore,
sdk: HMSSdk,
) => {
const tracks: Record<HMSTrackID, HMSTrack> = hmsStore.getState(selectTracksMap);
webrtcStore.namedSetState(store => {
const localPeerID = hmsStore.getState(selectLocalPeerID);
const newTrackStats: Record<HMSTrackID, HMSTrackStats> = {};
const trackIDs = Object.keys(tracks).filter(trackID => tracks[trackID].peerId !== localPeerID);
for (const trackID of trackIDs) {
const sdkTrackStats = stats.getRemoteTrackStats(trackID);
if (sdkTrackStats) {
newTrackStats[trackID] = sdkTrackStats;
}
}
mergeNewIndividualStatsInDraft<HMSTrackID, HMSTrackStats>(store.remoteTrackStats, newTrackStats);
// @TODO: Include all peer stats, own ticket, transmit local peer stats to other peer's using biz
const newPeerStats = { [localPeerID]: stats.getLocalPeerStats() };
mergeNewIndividualStatsInDraft<HMSPeerID, HMSPeerStats>(store.peerStats, newPeerStats);
// @ts-ignore
mergeLocalTrackStats(store.localTrackStats, stats.getLocalTrackStats(), sdk.store.getLocalPeerTracks());
}, 'webrtc-stats');
};
const resetHMSStatsStore = (store: IHMSStatsStore, reason = 'resetState') => {
store.namedSetState(draft => {
Object.assign(draft, createDefaultStatsStore());
}, reason);
};