UNPKG

@azure/communication-react

Version:

React library for building modern communication user experiences utilizing Azure Communication Services

247 lines • 16.2 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. import { Features } from '@azure/communication-calling'; import { toFlatCommunicationIdentifier } from "../../acs-ui-common/src"; import { CaptionsFeatureSubscriber } from './CaptionsSubscriber'; import { convertSdkLocalStreamToDeclarativeLocalStream, convertSdkParticipantToDeclarativeParticipant } from './Converter'; import { LocalVideoStreamVideoEffectsSubscriber } from './LocalVideoStreamVideoEffectsSubscriber'; import { ParticipantSubscriber } from './ParticipantSubscriber'; import { RecordingSubscriber } from './RecordingSubscriber'; import { PPTLiveSubscriber } from './PPTLiveSubscriber'; import { disposeView } from './StreamUtils'; import { TranscriptionSubscriber } from './TranscriptionSubscriber'; import { UserFacingDiagnosticsSubscriber } from './UserFacingDiagnosticsSubscriber'; import { RaiseHandSubscriber } from './RaiseHandSubscriber'; import { OptimalVideoCountSubscriber } from './OptimalVideoCountSubscriber'; import { CapabilitiesSubscriber } from './CapabilitiesSubscriber'; import { ReactionSubscriber } from './ReactionSubscriber'; import { SpotlightSubscriber } from './SpotlightSubscriber'; import { BreakoutRoomsSubscriber } from './BreakoutRoomsSubscriber'; import { TogetherModeSubscriber } from './TogetherModeSubscriber'; import { MediaAccessSubscriber } from './MediaAccessSubscriber'; import { _isTeamsMeeting } from './TypeGuards'; import { RealTimeTextSubscriber } from './RealTimeTextSubscriber'; /** * Keeps track of the listeners assigned to a particular call because when we get an event from SDK, it doesn't tell us * which call it is for. If we keep track of this then we know which call in the state that needs an update and also * which property of that call. Also we can use this when unregistering to a call. */ export class CallSubscriber { constructor(call, context, internalContext) { this._safeSubscribeInitCaptionSubscriber = () => { this._safeSubscribe(this.initCaptionSubscriber); }; this._safeSubscribeInitTeamsMeetingConference = () => { this._safeSubscribe(this.initTeamsMeetingConference); }; this._safeSubscribeInitRealTimeText = () => { this._safeSubscribe(this.initRealTimeText); }; this.subscribe = () => { this._call.on('stateChanged', this.stateChanged); this._call.on('stateChanged', this._safeSubscribeInitCaptionSubscriber); this._call.on('stateChanged', this._safeSubscribeInitTeamsMeetingConference); this._call.on('idChanged', this.idChanged); this._call.on('isScreenSharingOnChanged', this.isScreenSharingOnChanged); this._call.on('remoteParticipantsUpdated', this.remoteParticipantsUpdated); this._call.on('localVideoStreamsUpdated', this.localVideoStreamsUpdated); this._call.on('isMutedChanged', this.isMuteChanged); this._call.on('roleChanged', this.callRoleChangedHandler); this._call.feature(Features.DominantSpeakers).on('dominantSpeakersChanged', this.dominantSpeakersChanged); this._call.on('mutedByOthers', this.mutedByOthersHandler); this._safeSubscribeInitRealTimeText(); for (const localVideoStream of this._call.localVideoStreams) { this._internalContext.setLocalRenderInfo(this._callIdRef.callId, localVideoStream.mediaStreamType, localVideoStream, 'NotRendered', undefined); } if (this._call.remoteParticipants.length > 0) { this._call.remoteParticipants.forEach((participant) => { this.addParticipantListener(participant); }); this._context.setCallRemoteParticipants(this._callIdRef.callId, this._call.remoteParticipants.map(convertSdkParticipantToDeclarativeParticipant), []); } }; this.unsubscribe = () => { var _a, _b, _c; this._call.off('stateChanged', this.stateChanged); this._call.off('stateChanged', this._safeSubscribeInitCaptionSubscriber); this._call.off('stateChanged', this._safeSubscribeInitTeamsMeetingConference); this._call.off('idChanged', this.idChanged); this._call.off('isScreenSharingOnChanged', this.isScreenSharingOnChanged); this._call.off('remoteParticipantsUpdated', this.remoteParticipantsUpdated); this._call.off('localVideoStreamsUpdated', this.localVideoStreamsUpdated); this._call.off('isMutedChanged', this.isMuteChanged); this._call.off('roleChanged', this.callRoleChangedHandler); this._call.off('mutedByOthers', this.mutedByOthersHandler); this._participantSubscribers.forEach((participantSubscriber) => { participantSubscriber.unsubscribe(); }); this._participantSubscribers.clear(); // If we are unsubscribing that means we no longer want to display any video for this call (callEnded or callAgent // disposed) and we should not be updating it any more. So if video is rendering we stop rendering. for (const localVideoStream of this._call.localVideoStreams) { const mediaStreamType = localVideoStream.mediaStreamType; disposeView(this._context, this._internalContext, this._callIdRef.callId, undefined, convertSdkLocalStreamToDeclarativeLocalStream(localVideoStream)); this._internalContext.deleteLocalRenderInfo(this._callIdRef.callId, mediaStreamType); } this._diagnosticsSubscriber.unsubscribe(); this._recordingSubscriber.unsubscribe(); this._transcriptionSubscriber.unsubscribe(); this._optimalVideoCountSubscriber.unsubscribe(); this._pptLiveSubscriber.unsubscribe(); (_a = this._CaptionsFeatureSubscriber) === null || _a === void 0 ? void 0 : _a.unsubscribe(); if (this._realTimeTextSubscriber) { this._realTimeTextSubscriber.unsubscribe(); } (_b = this._raiseHandSubscriber) === null || _b === void 0 ? void 0 : _b.unsubscribe(); this._capabilitiesSubscriber.unsubscribe(); (_c = this._reactionSubscriber) === null || _c === void 0 ? void 0 : _c.unsubscribe(); this._spotlightSubscriber.unsubscribe(); this._breakoutRoomsSubscriber.unsubscribe(); this._togetherModeSubscriber.unsubscribe(); this._mediaAccessSubscriber.unsubscribe(); }; this.stateChanged = () => { this._context.setCallState(this._callIdRef.callId, this._call.state); }; this.initCaptionSubscriber = () => { // subscribe to captions here so that we don't call captions when call is not initialized if (this._call.state === 'Connected' && !this._CaptionsFeatureSubscriber) { this._CaptionsFeatureSubscriber = new CaptionsFeatureSubscriber(this._callIdRef, this._context, this._call.feature(Features.Captions)); this._call.off('stateChanged', this._safeSubscribeInitCaptionSubscriber); } }; this.initTeamsMeetingConference = () => { if (this._call.state === 'Connected' && _isTeamsMeeting(this._call)) { this._call.feature(Features.TeamsMeetingAudioConferencing).getTeamsMeetingAudioConferencingDetails().then(teamsMeetingConferenceDetails => { this._context.setTeamsMeetingConference(this._callIdRef.callId, teamsMeetingConferenceDetails); }); this._call.off('stateChanged', this._safeSubscribeInitTeamsMeetingConference); } }; this.initRealTimeText = () => { if (this._context.getState().userId.kind !== 'microsoftTeamsUser') { this._realTimeTextSubscriber = new RealTimeTextSubscriber(this._callIdRef, this._context, this._call.feature(Features.RealTimeText)); } }; this.idChanged = () => { this._internalContext.setCallId(this._call.id, this._callIdRef.callId); this._context.setCallId(this._call.id, this._callIdRef.callId); this._callIdRef.callId = this._call.id; }; this.isScreenSharingOnChanged = () => { this._context.setCallIsScreenSharingOn(this._callIdRef.callId, this._call.isScreenSharingOn); }; this.isMuteChanged = () => { this._context.setCallIsMicrophoneMuted(this._callIdRef.callId, this._call.isMuted); }; this.callRoleChangedHandler = () => { this._context.setRole(this._callIdRef.callId, this._call.role); }; // TODO: Tee to notification state once available this.mutedByOthersHandler = () => { this._context.teeErrorToState({ name: 'mutedByOthers', message: 'Muted by another participant' }, 'Call.mutedByOthers'); }; this.remoteParticipantsUpdated = (event) => { event.added.forEach((participant) => { this.addParticipantListener(participant); }); event.removed.forEach((participant) => { this.removeParticipantListener(participant); }); // Remove any added participants from remoteParticipantsEnded if they are there and add any removed participants to // remoteParticipantsEnded. this._context.setCallRemoteParticipantsEnded(this._callIdRef.callId, event.removed.map(convertSdkParticipantToDeclarativeParticipant), event.added.map((participant) => { return toFlatCommunicationIdentifier(participant.identifier); })); // Add added participants to remoteParticipants and remove removed participants from remoteParticipants. this._context.setCallRemoteParticipants(this._callIdRef.callId, event.added.map(convertSdkParticipantToDeclarativeParticipant), event.removed.map((participant) => { return toFlatCommunicationIdentifier(participant.identifier); })); }; this.localVideoStreamsUpdated = (event) => { var _a, _b; for (const localVideoStream of event.added) { const mediaStreamType = localVideoStream.mediaStreamType; // IMPORTANT: The internalContext should be set before context. This is done to ensure that the internal context // has the required data when component re-renders due to external state changes. this._internalContext.setLocalRenderInfo(this._callIdRef.callId, mediaStreamType, localVideoStream, 'NotRendered', undefined); // Subscribe to video effect changes (_a = this._localVideoStreamVideoEffectsSubscribers.get(mediaStreamType)) === null || _a === void 0 ? void 0 : _a.unsubscribe(); this._localVideoStreamVideoEffectsSubscribers.set(mediaStreamType, new LocalVideoStreamVideoEffectsSubscriber({ parent: this._callIdRef, context: this._context, localVideoStream: localVideoStream, localVideoStreamEffectsAPI: localVideoStream.feature(Features.VideoEffects) })); } for (const localVideoStream of event.removed) { const mediaStreamType = localVideoStream.mediaStreamType; (_b = this._localVideoStreamVideoEffectsSubscribers.get(mediaStreamType)) === null || _b === void 0 ? void 0 : _b.unsubscribe(); disposeView(this._context, this._internalContext, this._callIdRef.callId, undefined, convertSdkLocalStreamToDeclarativeLocalStream(localVideoStream)); this._internalContext.deleteLocalRenderInfo(this._callIdRef.callId, mediaStreamType); } this._context.setCallLocalVideoStream(this._callIdRef.callId, event.added.map(convertSdkLocalStreamToDeclarativeLocalStream), event.removed.map(convertSdkLocalStreamToDeclarativeLocalStream)); }; this.dominantSpeakersChanged = () => { const dominantSpeakers = this._call.feature(Features.DominantSpeakers).dominantSpeakers; this._context.setCallDominantSpeakers(this._callIdRef.callId, dominantSpeakers); }; this._call = call; this._callIdRef = { callId: call.id }; this._context = context; this._internalContext = internalContext; this._diagnosticsSubscriber = new UserFacingDiagnosticsSubscriber(this._callIdRef, this._context, this._call.feature(Features.UserFacingDiagnostics)); this._participantSubscribers = new Map(); this._recordingSubscriber = new RecordingSubscriber(this._callIdRef, this._context, this._call.feature(Features.Recording)); this._pptLiveSubscriber = new PPTLiveSubscriber(this._callIdRef, this._context, this._call); this._transcriptionSubscriber = new TranscriptionSubscriber(this._callIdRef, this._context, this._call.feature(Features.Transcription)); this._raiseHandSubscriber = new RaiseHandSubscriber(this._callIdRef, this._context, this._call.feature(Features.RaiseHand)); this._reactionSubscriber = new ReactionSubscriber(this._callIdRef, this._context, this._call.feature(Features.Reaction)); this._optimalVideoCountSubscriber = new OptimalVideoCountSubscriber({ callIdRef: this._callIdRef, context: this._context, localOptimalVideoCountFeature: this._call.feature(Features.OptimalVideoCount) }); this._localVideoStreamVideoEffectsSubscribers = new Map(); this._capabilitiesSubscriber = new CapabilitiesSubscriber(this._callIdRef, this._context, this._call.feature(Features.Capabilities)); this._spotlightSubscriber = new SpotlightSubscriber(this._callIdRef, this._context, this._call.feature(Features.Spotlight)); // Clear assigned breakout room closed notification for this call. this._context.deleteLatestNotification('assignedBreakoutRoomClosed'); this._breakoutRoomsSubscriber = new BreakoutRoomsSubscriber(this._callIdRef, this._context, this._call.feature(Features.BreakoutRooms)); this._togetherModeSubscriber = new TogetherModeSubscriber(this._callIdRef, this._context, this._internalContext, this._call.feature(Features.TogetherMode)); this._mediaAccessSubscriber = new MediaAccessSubscriber(this._callIdRef, this._context, this._call.feature(Features.MediaAccess)); this.subscribe(); } // This is a helper function to safely call subscriber functions. This is needed in order to prevent events // with the same event type from failing to fire due to a subscriber throwing an error upon subscription. // Wrap your listeners with this helper function if your listener can fail due to initialization or fail // during a function call. This will prevent other events using the same event type from failing to fire. _safeSubscribe(subscriber) { try { subscriber(); } catch (e) { this._context.teeErrorToState(e, 'Call.on'); } } addParticipantListener(participant) { var _a; const participantKey = toFlatCommunicationIdentifier(participant.identifier); (_a = this._participantSubscribers.get(participantKey)) === null || _a === void 0 ? void 0 : _a.unsubscribe(); this._participantSubscribers.set(participantKey, new ParticipantSubscriber(this._callIdRef, participant, this._context, this._internalContext)); } removeParticipantListener(participant) { const participantKey = toFlatCommunicationIdentifier(participant.identifier); const participantSubscriber = this._participantSubscribers.get(participantKey); if (participantSubscriber) { participantSubscriber.unsubscribe(); this._participantSubscribers.delete(participantKey); } } } //# sourceMappingURL=CallSubscriber.js.map