UNPKG

@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

190 lines (169 loc) 6.6 kB
import { TrackManager } from './TrackManager'; import { createRemotePeer } from './utils'; import { HMSPeerUpdate, HMSTrackUpdate, HMSUpdateListener } from '../../interfaces'; import { HMSRemoteVideoTrack } from '../../media/tracks'; import { HMSPeer, HMSRemotePeer } from '../../sdk/models/peer'; import { Store } from '../../sdk/store'; import { HAND_RAISE_GROUP_NAME } from '../../utils/constants'; import { convertDateNumToDate } from '../../utils/date'; import HMSLogger from '../../utils/logger'; import { HMSNotificationMethod } from '../HMSNotificationMethod'; import { PeerNotification } from '../HMSNotifications'; /** * Handles: * - New peer join * - Peer Leave * - Role update for peer * * Notes: * - Peer join comes with track meta-data, * we add it to the store and call TrackManager to process it when RTC Track comes in. */ export class PeerManager { private readonly TAG = '[PeerManager]'; constructor(private store: Store, private trackManager: TrackManager, public listener?: HMSUpdateListener) {} handleNotification(method: string, notification: any) { switch (method) { case HMSNotificationMethod.PEER_JOIN: { const peer = notification as PeerNotification; this.handlePeerJoin(peer); break; } case HMSNotificationMethod.PEER_LEAVE: { const peer = notification as PeerNotification; this.handlePeerLeave(peer); break; } case HMSNotificationMethod.PEER_UPDATE: this.handlePeerUpdate(notification as PeerNotification); break; default: break; } } handlePeerList = (peers: PeerNotification[]) => { if (peers.length === 0) { this.listener?.onPeerUpdate(HMSPeerUpdate.PEER_LIST, []); return; } const hmsPeers: HMSRemotePeer[] = []; const newPeers = new Set(peers.map(peer => peer.peer_id)); this.store.getRemotePeers().forEach(({ peerId, fromRoomState }) => { /** * Remove only if the peer join happened from preview roomstate update. This will prevent the peer joined * from peer-join event post join from being removed from the store. */ if (!newPeers.has(peerId) && fromRoomState) { this.store.removePeer(peerId); } }); for (const peer of peers) { hmsPeers.push(this.makePeer(peer)); } this.listener?.onPeerUpdate(HMSPeerUpdate.PEER_LIST, hmsPeers); this.trackManager.processPendingTracks(); }; handlePeerJoin = (peer: PeerNotification) => { const hmsPeer = this.makePeer(peer); this.listener?.onPeerUpdate(HMSPeerUpdate.PEER_JOINED, hmsPeer); this.trackManager.processPendingTracks(); }; handlePeerLeave = (peer: PeerNotification) => { const hmsPeer = this.store.getPeerById(peer.peer_id); this.store.removePeer(peer.peer_id); HMSLogger.d(this.TAG, `PEER_LEAVE`, peer.peer_id, `remainingPeers=${this.store.getPeers().length}`); if (!hmsPeer) { return; } if (hmsPeer.audioTrack) { this.listener?.onTrackUpdate(HMSTrackUpdate.TRACK_REMOVED, hmsPeer.audioTrack, hmsPeer); } if (hmsPeer.videoTrack) { this.listener?.onTrackUpdate(HMSTrackUpdate.TRACK_REMOVED, hmsPeer.videoTrack, hmsPeer); } hmsPeer.auxiliaryTracks?.forEach(track => { this.listener?.onTrackUpdate(HMSTrackUpdate.TRACK_REMOVED, track, hmsPeer); }); this.listener?.onPeerUpdate(HMSPeerUpdate.PEER_LEFT, hmsPeer); }; // eslint-disable-next-line complexity handlePeerUpdate(notification: PeerNotification) { let peer = this.store.getPeerById(notification.peer_id); if (!peer && notification.realtime) { // create peer if not already created in store peer = this.makePeer(notification); this.listener?.onPeerUpdate( peer.isHandRaised ? HMSPeerUpdate.HAND_RAISE_CHANGED : HMSPeerUpdate.PEER_ADDED, peer, ); return; } // if peer is present but not realtime now, remove it from store if (peer && !peer.isLocal && !notification.realtime) { this.store.removePeer(peer.peerId); this.listener?.onPeerUpdate(HMSPeerUpdate.PEER_REMOVED, peer); return; } if (!peer) { HMSLogger.d(this.TAG, `peer ${notification.peer_id} not found`); return; } if (peer.role && peer.role.name !== notification.role) { const newRole = this.store.getPolicyForRole(notification.role); peer.updateRole(newRole); this.updateSimulcastLayersForPeer(peer); this.listener?.onPeerUpdate(HMSPeerUpdate.ROLE_UPDATED, peer); } const wasHandRaised = peer.isHandRaised; peer.updateGroups(notification.groups); const isHandRaised = notification.groups?.includes(HAND_RAISE_GROUP_NAME); if (wasHandRaised !== isHandRaised) { this.listener?.onPeerUpdate(HMSPeerUpdate.HAND_RAISE_CHANGED, peer); } this.handlePeerInfoUpdate({ peer, ...notification.info }); } handlePeerInfoUpdate({ peer, name, data }: { peer?: HMSPeer; name?: string; data?: string }) { if (!peer) { return; } if (name && peer.name !== name) { peer.updateName(name); this.listener?.onPeerUpdate(HMSPeerUpdate.NAME_UPDATED, peer); } if (data && peer.metadata !== data) { peer.updateMetadata(data); this.listener?.onPeerUpdate(HMSPeerUpdate.METADATA_UPDATED, peer); } } private makePeer(peer: PeerNotification) { let hmsPeer = this.store.getPeerById(peer.peer_id) as HMSRemotePeer; if (!hmsPeer) { hmsPeer = createRemotePeer(peer, this.store); hmsPeer.realtime = peer.realtime; hmsPeer.joinedAt = convertDateNumToDate(peer.joined_at); hmsPeer.fromRoomState = !!peer.is_from_room_state; this.store.addPeer(hmsPeer); HMSLogger.d(this.TAG, `adding to the peerList`, `${hmsPeer}`); } for (const trackId in peer.tracks) { const trackInfo = peer.tracks[trackId]; this.store.setTrackState({ peerId: peer.peer_id, trackInfo, }); if (trackInfo.type === 'video') { this.trackManager.processTrackInfo(trackInfo, peer.peer_id, false); } } return hmsPeer; } private updateSimulcastLayersForPeer(peer: HMSPeer) { this.store.getPeerTracks(peer.peerId).forEach(track => { if (track.type === 'video' && ['regular', 'screen'].includes(track.source!)) { const remoteTrack = track as HMSRemoteVideoTrack; const simulcastDefinitions = this.store.getSimulcastDefinitionsForPeer(peer, remoteTrack.source!); remoteTrack.setSimulcastDefinitons(simulcastDefinitions); } }); } }