From 5b0b82b18b78646a3070976cc7073f19b696a5db Mon Sep 17 00:00:00 2001 From: Daniel Strolle <dstrolle@microsoft.com> Date: Wed, 4 Dec 2024 13:08:09 -0600 Subject: [PATCH] update to support transfer --- .../AudioEffects/AudioEffectsContainer.js | 3 +- Project/src/MakeCall/CallCard.js | 327 +++++++++++------- Project/src/MakeCall/CallSurvey.js | 3 +- Project/src/MakeCall/IncomingCallCard.js | 20 +- Project/src/MakeCall/Lobby.js | 3 +- Project/src/MakeCall/MakeCall.js | 101 +++--- .../RawVideoAccess/CustomVideoEffects.js | 1 - Project/src/MakeCall/RemoteParticipantCard.js | 12 +- Project/src/MakeCall/StreamRenderer.js | 3 +- .../VideoEffects/VideoEffectsContainer.js | 3 +- 10 files changed, 288 insertions(+), 188 deletions(-) diff --git a/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js b/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js index e2cb87e3..e5b066cf 100644 --- a/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js +++ b/Project/src/MakeCall/AudioEffects/AudioEffectsContainer.js @@ -15,7 +15,6 @@ export const LoadingSpinner = () => { export default class AudioEffectsContainer extends React.Component { constructor(props) { super(props); - this.call = props.call; this.deviceManager = props.deviceManager; this.localAudioStreamFeatureApi = null; this.localAudioStream = null; @@ -68,7 +67,7 @@ export default class AudioEffectsContainer extends React.Component { } initLocalAudioStreamFeatureApi() { - const localAudioStream = this.call.localAudioStreams.find(a => { + const localAudioStream = this.props.call.localAudioStreams.find(a => { return a.mediaStreamType === 'Audio'; }); diff --git a/Project/src/MakeCall/CallCard.js b/Project/src/MakeCall/CallCard.js index a773c359..42e47362 100644 --- a/Project/src/MakeCall/CallCard.js +++ b/Project/src/MakeCall/CallCard.js @@ -1,5 +1,5 @@ import React from "react"; -import { MessageBar, MessageBarType } from 'office-ui-fabric-react' +import { MessageBar, MessageBarType, TextField } from 'office-ui-fabric-react' import { FunctionalStreamRenderer as StreamRenderer } from "./FunctionalStreamRenderer"; import AddParticipantPopover from "./AddParticipantPopover"; import RemoteParticipantCard from "./RemoteParticipantCard"; @@ -25,38 +25,39 @@ export default class CallCard extends React.Component { constructor(props) { super(props); this.callFinishConnectingResolve = undefined; - this.call = props.call; - this.localVideoStream = this.call.localVideoStreams.find(lvs => { + this.localVideoStream = this.props.call.localVideoStreams.find(lvs => { return lvs.mediaStreamType === 'Video' || lvs.mediaStreamType === 'RawMedia' }); this.localScreenSharingStream = undefined; this.deviceManager = props.deviceManager; + this.destinationUserId = null; this.remoteVolumeLevelSubscription = undefined; this.handleRemoteVolumeSubscription = undefined; this.streamIsAvailableListeners = new Map(); this.videoStreamsUpdatedListeners = new Map(); this.identifier = props.identityMri; - this.spotlightFeature = this.call.feature(Features.Spotlight); - this.raiseHandFeature = this.call.feature(Features.RaiseHand); - this.capabilitiesFeature = this.call.feature(Features.Capabilities); + this.spotlightFeature = this.props.call.feature(Features.Spotlight); + this.raiseHandFeature = this.props.call.feature(Features.RaiseHand); + this.capabilitiesFeature = this.props.call.feature(Features.Capabilities); this.capabilities = this.capabilitiesFeature.capabilities; - this.dominantSpeakersFeature = this.call.feature(Features.DominantSpeakers); - this.recordingFeature = this.call.feature(Features.Recording); - this.transcriptionFeature = this.call.feature(Features.Transcription); - this.lobby = this.call.lobby; + this.dominantSpeakersFeature = this.props.call.feature(Features.DominantSpeakers); + this.recordingFeature = this.props.call.feature(Features.Recording); + this.transcriptionFeature = this.props.call.feature(Features.Transcription); + this.lobby = this.props.call.lobby; if (Features.Reaction) { - this.meetingReaction = this.call.feature(Features.Reaction); + this.meetingReaction = this.props.call.feature(Features.Reaction); } if (Features.PPTLive) { - this.pptLiveFeature = this.call.feature(Features.PPTLive); + this.pptLiveFeature = this.props.call.feature(Features.PPTLive); this.pptLiveHtml = React.createRef(); } + if (Features.Transfer) { + this.transferFeature = this.props.call.feature(Features.Transfer); + } this.isTeamsUser = props.isTeamsUser; this.dummyStreamTimeout = undefined; this.state = { ovc: 4, - callState: this.call.state, - callId: this.call.id, remoteParticipants: [], allRemoteParticipantStreams: [], remoteScreenShareStream: undefined, @@ -67,11 +68,11 @@ export default class CallCard extends React.Component { canSpotlight: this.capabilities.spotlightParticipant?.isPresent || this.capabilities.spotlightParticipant?.reason === 'FeatureNotSupported', canMuteOthers: this.capabilities.muteOthers?.isPresent || this.capabilities.muteOthers?.reason === 'FeatureNotSupported', canReact: this.capabilities.useReactions?.isPresent || this.capabilities.useReactions?.reason === 'FeatureNotSupported', - videoOn: this.call.isLocalVideoStarted, - screenSharingOn: this.call.isScreenSharingOn, - micMuted: this.call.isMuted, + videoOn: !!props.call.localVideoStreams[0], + screenSharingOn: props.callIsScreenShareOn, + micMuted: this.props.call.isMuted, incomingAudioMuted: false, - onHold: this.call.state === 'LocalHold' || this.call.state === 'RemoteHold', + onHold: this.props.call.state === 'LocalHold' || this.props.call.state === 'RemoteHold', outgoingAudioMediaAccessActive: false, cameraDeviceOptions: props.cameraDeviceOptions ? props.cameraDeviceOptions : [], speakerDeviceOptions: props.speakerDeviceOptions ? props.speakerDeviceOptions : [], @@ -102,47 +103,48 @@ export default class CallCard extends React.Component { pptLiveActive: false, isRecordingActive: false, isTranscriptionActive: false, - lobbyParticipantsCount: this.lobby?.participants.length + lobbyParticipantsCount: this.lobby?.participants.length, + transferArgs: undefined }; this.selectedRemoteParticipants = new Set(); this.dataChannelRef = React.createRef(); this.localVideoPreviewRef = React.createRef(); this.localScreenSharingPreviewRef = React.createRef(); - this.isSetCallConstraints = this.call.setConstraints !== undefined; + this.isSetCallConstraints = this.props.call.setConstraints !== undefined; } componentWillUnmount() { - this.call.off('stateChanged', () => { }); + this.props.call.off('stateChanged', () => { }); this.deviceManager.off('videoDevicesUpdated', () => { }); this.deviceManager.off('audioDevicesUpdated', () => { }); this.deviceManager.off('selectedSpeakerChanged', () => { }); this.deviceManager.off('selectedMicrophoneChanged', () => { }); - this.call.off('localVideoStreamsUpdated', () => { }); - this.call.off('idChanged', () => { }); - this.call.off('isMutedChanged', () => { }); - this.call.off('isIncomingAudioMutedChanged', () => { }); - this.call.off('isScreenSharingOnChanged', () => { }); - this.call.off('remoteParticipantsUpdated', () => { }); + this.props.call.off('localVideoStreamsUpdated', () => { }); + this.props.call.off('idChanged', () => { }); + this.props.call.off('isMutedChanged', () => { }); + this.props.call.off('isIncomingAudioMutedChanged', () => { }); + this.props.call.off('isScreenSharingOnChanged', () => { }); + this.props.call.off('remoteParticipantsUpdated', () => { }); this.state.mediaCollector?.off('sampleReported', () => { }); this.state.mediaCollector?.off('summaryReported', () => { }); - this.call.feature(Features.DominantSpeakers).off('dominantSpeakersChanged', () => { }); - this.call.feature(Features.Spotlight).off('spotlightChanged', this.spotlightStateChangedHandler); - this.call.feature(Features.RaiseHand).off('raisedHandEvent', this.raiseHandChangedHandler); - this.call.feature(Features.RaiseHand).off('loweredHandEvent', this.raiseHandChangedHandler); + this.props.call.feature(Features.DominantSpeakers).off('dominantSpeakersChanged', () => { }); + this.props.call.feature(Features.Spotlight).off('spotlightChanged', this.spotlightStateChangedHandler); + this.props.call.feature(Features.RaiseHand).off('raisedHandEvent', this.raiseHandChangedHandler); + this.props.call.feature(Features.RaiseHand).off('loweredHandEvent', this.raiseHandChangedHandler); this.recordingFeature.off('isRecordingActiveChanged', this.isRecordingActiveChangedHandler); this.transcriptionFeature.off('isTranscriptionActiveChanged', this.isTranscriptionActiveChangedHandler); this.lobby?.off('lobbyParticipantsUpdated', () => { }); if (Features.Reaction) { - this.call.feature(Features.Reaction).off('reaction', this.reactionChangeHandler); + this.props.call.feature(Features.Reaction).off('reaction', this.reactionChangeHandler); } if (Features.PPTLive) { - this.call.feature(Features.PPTLive).off('isActiveChanged', this.pptLiveChangedHandler); + this.props.call.feature(Features.PPTLive).off('isActiveChanged', this.pptLiveChangedHandler); } this.dominantSpeakersFeature.off('dominantSpeakersChanged', this.dominantSpeakersChanged); } componentDidMount() { - if (this.call) { + if (this.props.call) { this.deviceManager.on('videoDevicesUpdated', async e => { e.added.forEach(addedCameraDevice => { const addedCameraDeviceOption = { key: addedCameraDevice.id, text: addedCameraDevice.name }; @@ -212,53 +214,53 @@ export default class CallCard extends React.Component { }); const callStateChanged = () => { - console.log('Call state changed ', this.call.state); - if (this.call.state !== 'None' && - this.call.state !== 'Connecting' && - this.call.state !== 'Incoming') { + console.log('Call state changed ', this.props.call.state); + if (this.props.call.state !== 'None' && + this.props.call.state !== 'Connecting' && + this.props.call.state !== 'Incoming') { if (this.callFinishConnectingResolve) { this.callFinishConnectingResolve(); } } - if (this.call.state !== 'Disconnected') { - this.setState({ callState: this.call.state }); + if (this.props.call.state !== 'Disconnected') { + this.setState({ callState: this.props.call.state }); } - if (this.call.state === 'LocalHold' || this.call.state === 'RemoteHold') { + if (this.props.call.state === 'LocalHold' || this.props.call.state === 'RemoteHold') { this.setState({ canRaiseHands: false }); this.setState({ canSpotlight: false }); } - if (this.call.state === 'Connected') { + if (this.props.call.state === 'Connected') { this.setState({ canRaiseHands: this.capabilities.raiseHand?.isPresent || this.capabilities.raiseHand?.reason === 'FeatureNotSupported' }); this.setState({ canSpotlight: this.capabilities.spotlightParticipant?.isPresent || this.capabilities.spotlightParticipant?.reason === 'FeatureNotSupported' }); } } callStateChanged(); - this.call.on('stateChanged', callStateChanged); + this.props.call.on('stateChanged', callStateChanged); - this.call.on('idChanged', () => { - console.log('Call id Changed ', this.call.id); - this.setState({ callId: this.call.id }); + this.props.call.on('idChanged', () => { + console.log('Call id Changed ', this.props.call.id); + this.setState({ callId: this.props.call.id }); }); - this.call.on('isMutedChanged', () => { - console.log('Local microphone muted changed ', this.call.isMuted); - this.setState({ micMuted: this.call.isMuted }); + this.props.call.on('isMutedChanged', () => { + console.log('Local microphone muted changed ', this.props.call.isMuted); + this.setState({ micMuted: this.props.call.isMuted }); }); - this.call.on('isIncomingAudioMutedChanged', () => { - console.log('Incoming audio muted changed ', this.call.isIncomingAudioMuted); - this.setState({ incomingAudioMuted: this.call.isIncomingAudioMuted }); + this.props.call.on('isIncomingAudioMutedChanged', () => { + console.log('Incoming audio muted changed ', this.props.call.isIncomingAudioMuted); + this.setState({ incomingAudioMuted: this.props.call.isIncomingAudioMuted }); }); - this.call.on('isLocalVideoStartedChanged', () => { - this.setState({ videoOn: this.call.isLocalVideoStarted }); + this.props.call.on('isLocalVideoStartedChanged', () => { + this.setState({ videoOn: this.props.call.isLocalVideoStarted }); }); - this.call.on('isScreenSharingOnChanged', () => { - this.setState({ screenSharingOn: this.call.isScreenSharingOn }); - if (!this.call.isScreenSharing) { + this.props.call.on('isScreenSharingOnChanged', () => { + this.setState({ screenSharingOn: this.props.call.isScreenSharingOn }); + if (!this.props.call.isScreenSharing) { if (this.state.localScreenSharingMode == 'StartWithDummy') { clearTimeout(this.dummyStreamTimeout); this.dummyStreamTimeout = undefined; @@ -267,7 +269,7 @@ export default class CallCard extends React.Component { } }); - this.call.on('mutedByOthers', () => { + this.props.call.on('mutedByOthers', () => { const messageBarText = 'You have been muted by someone else'; this.setState(prevState => ({ ...prevState, @@ -309,10 +311,10 @@ export default class CallCard extends React.Component { } } - this.call.remoteParticipants.forEach(rp => handleParticipant(rp)); + this.props.call.remoteParticipants.forEach(rp => handleParticipant(rp)); - this.call.on('remoteParticipantsUpdated', e => { - console.log(`Call=${this.call.callId}, remoteParticipantsUpdated, added=${e.added}, removed=${e.removed}`); + this.props.call.on('remoteParticipantsUpdated', e => { + console.log(`Call=${this.props.call.callId}, remoteParticipantsUpdated, added=${e.added}, removed=${e.removed}`); e.added.forEach(participant => { console.log('participantAdded', participant); handleParticipant(participant) @@ -342,7 +344,7 @@ export default class CallCard extends React.Component { }); }); }); - const mediaCollector = this.call.feature(Features.MediaStats).createCollector(); + const mediaCollector = this.props.call.feature(Features.MediaStats).createCollector(); this.setState({ mediaCollector }); mediaCollector.on('sampleReported', (data) => { if (this.state.logMediaStats) { @@ -399,12 +401,12 @@ export default class CallCard extends React.Component { this.dominantSpeakersChanged(); if (this.state.dominantSpeakerMode) { - const newDominantSpeakerIdentifier = this.call.feature(Features.DominantSpeakers).dominantSpeakers.speakersList[0]; + const newDominantSpeakerIdentifier = this.props.call.feature(Features.DominantSpeakers).dominantSpeakers.speakersList[0]; if (newDominantSpeakerIdentifier) { console.log(`DominantSpeaker changed, new dominant speaker: ${newDominantSpeakerIdentifier ? utils.getIdentifierText(newDominantSpeakerIdentifier) : `None`}`); // Set the new dominant remote participant - const newDominantRemoteParticipant = utils.getRemoteParticipantObjFromIdentifier(this.call, newDominantSpeakerIdentifier); + const newDominantRemoteParticipant = utils.getRemoteParticipantObjFromIdentifier(this.props.call, newDominantSpeakerIdentifier); // Get the new dominant remote participant's stream tuples const streamsToRender = []; @@ -444,13 +446,19 @@ export default class CallCard extends React.Component { } }; - const dominantSpeakerIdentifier = this.call.feature(Features.DominantSpeakers).dominantSpeakers.speakersList[0]; + const dominantSpeakerIdentifier = this.props.call.feature(Features.DominantSpeakers).dominantSpeakers.speakersList[0]; if (dominantSpeakerIdentifier) { this.setState({ dominantRemoteParticipant: utils.getRemoteParticipantObjFromIdentifier(dominantSpeakerIdentifier) }) } - this.call.feature(Features.DominantSpeakers).on('dominantSpeakersChanged', dominantSpeakersChangedHandler); + this.props.call.feature(Features.DominantSpeakers).on('dominantSpeakersChanged', dominantSpeakersChangedHandler); + + const transferCallback = args => { + this.setState({ transferArgs: args }); + }; - const ovcFeature = this.call.feature(Features.OptimalVideoCount); + this.props.call.feature(Features.Transfer).off('transferRequested', transferCallback); + + const ovcFeature = this.props.call.feature(Features.OptimalVideoCount); const ovcChangedHandler = () => { if (this.state.ovc !== ovcFeature.optimalVideoCount) { this.setState({ ovc: ovcFeature.optimalVideoCount }); @@ -468,12 +476,13 @@ export default class CallCard extends React.Component { this.recordingFeature.on('isRecordingActiveChanged', this.isRecordingActiveChangedHandler); this.transcriptionFeature.on('isTranscriptionActiveChanged', this.isTranscriptionActiveChangedHandler); this.lobby?.on('lobbyParticipantsUpdated', this.lobbyParticipantsUpdatedHandler); + this.transferFeature.on("transferRequested", transferCallback); } } updateListOfParticipantsToRender(reason) { - const ovcFeature = this.call.feature(Features.OptimalVideoCount); + const ovcFeature = this.props.call.feature(Features.OptimalVideoCount); const optimalVideoCount = ovcFeature.optimalVideoCount; console.log(`updateListOfParticipantsToRender because ${reason}, ovc is ${optimalVideoCount}`); console.log(`updateListOfParticipantsToRender currently rendering ${this.state.allRemoteParticipantStreams.length} streams`); @@ -595,7 +604,7 @@ export default class CallCard extends React.Component { if (this.pptLiveHtml) { if (pptLiveActive) { this.pptLiveHtml.current.appendChild(this.pptLiveFeature.target); - if (this.call.isScreenSharingOn) { + if (this.props.call.isScreenSharingOn) { try { await this.handleScreenSharingOnOff(); } catch { @@ -604,7 +613,7 @@ export default class CallCard extends React.Component { } } else { this.pptLiveHtml.current.removeChild(this.pptLiveHtml.current.lastElementChild); - if (!this.call.isScreenSharingOn && this.state.canShareScreen) { + if (!this.props.call.isScreenSharingOn && this.state.canShareScreen) { try { await this.handleScreenSharingOnOff(); } catch { @@ -652,7 +661,7 @@ export default class CallCard extends React.Component { dominantSpeakersChanged = () => { const dominantSpeakersMris = this.dominantSpeakersFeature.dominantSpeakers.speakersList; const remoteParticipants = dominantSpeakersMris.map(dominantSpeakerMri => { - const remoteParticipant = utils.getRemoteParticipantObjFromIdentifier(this.call, dominantSpeakerMri); + const remoteParticipant = utils.getRemoteParticipantObjFromIdentifier(this.props.call, dominantSpeakerMri); return remoteParticipant; }); @@ -669,9 +678,9 @@ export default class CallCard extends React.Component { this.localVideoStream = new LocalVideoStream(cameraDeviceInfo); } - if (this.call.state === 'None' || - this.call.state === 'Connecting' || - this.call.state === 'Incoming') { + if (this.props.call.state === 'None' || + this.props.call.state === 'Connecting' || + this.props.call.state === 'Incoming') { if (this.state.videoOn) { this.setState({ videoOn: false }); } else { @@ -679,15 +688,15 @@ export default class CallCard extends React.Component { } await this.watchForCallFinishConnecting(); if (this.state.videoOn && this.state.canOnVideo) { - await this.call.startVideo(this.localVideoStream); + await this.props.call.startVideo(this.localVideoStream); } else { - await this.call.stopVideo(this.localVideoStream); + await this.props.call.stopVideo(this.localVideoStream); } } else { if (!this.state.videoOn && this.state.canOnVideo) { - await this.call.startVideo(this.localVideoStream); + await this.props.call.startVideo(this.localVideoStream); } else { - await this.call.stopVideo(this.localVideoStream); + await this.props.call.stopVideo(this.localVideoStream); } } } catch (e) { @@ -709,12 +718,12 @@ export default class CallCard extends React.Component { async handleMicOnOff() { try { - if (!this.call.isMuted) { - await this.call.mute(); + if (!this.props.call.isMuted) { + await this.props.call.mute(); } else { - await this.call.unmute(); + await this.props.call.unmute(); } - this.setState({ micMuted: this.call.isMuted }); + this.setState({ micMuted: this.props.call.isMuted }); } catch (e) { console.error(e); } @@ -722,7 +731,7 @@ export default class CallCard extends React.Component { async handleMuteAllRemoteParticipants() { try { - await this.call.muteAllRemoteParticipants?.(); + await this.props.call.muteAllRemoteParticipants?.(); } catch (e) { console.error('Failed to mute all other participants.', e); } @@ -783,12 +792,12 @@ export default class CallCard extends React.Component { async handleIncomingAudioOnOff() { try { - if (!this.call.isIncomingAudioMuted) { - await this.call.muteIncomingAudio(); + if (!this.props.call.isIncomingAudioMuted) { + await this.props.call.muteIncomingAudio(); } else { - await this.call.unmuteIncomingAudio(); + await this.props.call.unmuteIncomingAudio(); } - this.setState({ incomingAudioMuted: this.call.isIncomingAudioMuted }); + this.setState({ incomingAudioMuted: this.props.call.isIncomingAudioMuted }); } catch (e) { console.error(e); } @@ -796,19 +805,41 @@ export default class CallCard extends React.Component { async handleHoldUnhold() { try { - if (this.call.state === 'LocalHold') { - this.call.resume(); + if (this.props.call.state === 'LocalHold') { + this.props.call.resume(); } else { - this.call.hold(); + this.props.call.hold(); } } catch (e) { console.error(e); } } + async handleTransferToParticipant() { + if (!this.destinationUserId.value) { + return; + } + try { + var id = {communicationUserId: this.destinationUserId.value} + + await this.props.call.hold(); + + const transfer = this.transferFeature.transfer({ targetParticipant: id }); + transfer.on('stateChanged', () => { + console.log(`EVENT, transferStateChanged=${transfer.state}`) + + if (transfer.state === 'Transferred') { + this.props.call.hangUp(); + } + }); + } catch(e) { + console.error(e); + } + } + async handleOutgoingAudioEffect() { if (this.state.outgoingAudioMediaAccessActive) { - this.call.stopAudio(); + this.props.call.stopAudio(); } else { this.startOutgoingAudioEffect(); } @@ -853,13 +884,13 @@ export default class CallCard extends React.Component { async startOutgoingAudioEffect() { const stream = this.getDummyAudioStream(); const customLocalAudioStream = new LocalAudioStream(stream); - this.call.startAudio(customLocalAudioStream); + this.props.call.startAudio(customLocalAudioStream); } async handleScreenSharingOnOff() { try { - if (this.call.isScreenSharingOn) { - await this.call.stopScreenSharing(); + if (this.props.call.isScreenSharingOn) { + await this.props.call.stopScreenSharing(); this.setState({ localScreenSharingMode: undefined }); } else if (this.state.canShareScreen) { await this.startScreenSharing(); @@ -870,15 +901,15 @@ export default class CallCard extends React.Component { } async startScreenSharing() { - await this.call.startScreenSharing(); - this.localScreenSharingStream = this.call.localVideoStreams.find(ss => ss.mediaStreamType === 'ScreenSharing'); + await this.props.call.startScreenSharing(); + this.localScreenSharingStream = this.props.call.localVideoStreams.find(ss => ss.mediaStreamType === 'ScreenSharing'); this.setState({ localScreenSharingMode: 'StartWithNormal', pptLiveActive: false }); } async handleRawScreenSharingOnOff() { try { - if (this.call.isScreenSharingOn) { - await this.call.stopScreenSharing(); + if (this.props.call.isScreenSharingOn) { + await this.props.call.stopScreenSharing(); clearImmediate(this.dummyStreamTimeout); this.dummyStreamTimeout = undefined; this.setState({ localScreenSharingMode: undefined }); @@ -916,7 +947,7 @@ export default class CallCard extends React.Component { this.dummyStreamTimeout = setTimeout(createShapes, 0); const dummyStream = canvas.captureStream(FPS); this.localScreenSharingStream = new LocalVideoStream(dummyStream); - await this.call.startScreenSharing(this.localScreenSharingStream); + await this.props.call.startScreenSharing(this.localScreenSharingStream); this.setState({ localScreenSharingMode: 'StartWithDummy'}); } } @@ -941,7 +972,7 @@ export default class CallCard extends React.Component { // Turn on dominant speaker mode this.setState({ dominantSpeakerMode: true }); // Dispose of all remote participants's stream renderers - const dominantSpeakerIdentifier = this.call.feature(Features.DominantSpeakers).dominantSpeakers.speakersList[0]; + const dominantSpeakerIdentifier = this.props.call.feature(Features.DominantSpeakers).dominantSpeakers.speakersList[0]; if (!dominantSpeakerIdentifier) { this.state.allRemoteParticipantStreams.forEach(v => { v.streamRendererComponentRef.current.disposeRenderer(); @@ -952,7 +983,7 @@ export default class CallCard extends React.Component { } // Set the dominant remote participant obj - const dominantRemoteParticipant = utils.getRemoteParticipantObjFromIdentifier(this.call, dominantSpeakerIdentifier); + const dominantRemoteParticipant = utils.getRemoteParticipantObjFromIdentifier(this.props.call, dominantSpeakerIdentifier); this.setState({ dominantRemoteParticipant: dominantRemoteParticipant }); // Dispose of all the remote participants's stream renderers except for the dominant speaker this.state.allRemoteParticipantStreams.forEach(v => { @@ -972,8 +1003,8 @@ export default class CallCard extends React.Component { this.setState({ selectedCameraDeviceId: cameraDeviceInfo.id }); if (this.localVideoStream.mediaStreamType === 'RawMedia' && this.state.videoOn) { this.localVideoStream?.switchSource(cameraDeviceInfo); - await this.call.stopVideo(this.localVideoStream); - await this.call.startVideo(this.localVideoStream); + await this.props.call.stopVideo(this.localVideoStream); + await this.props.call.startVideo(this.localVideoStream); } else { this.localVideoStream?.switchSource(cameraDeviceInfo); } @@ -1026,7 +1057,7 @@ export default class CallCard extends React.Component { meetingAudioConferenceDetails: async() => { let messageBarText = "call in (audio only) details: \n"; try { - const audioConferencingfeature = this.call.feature(Features.TeamsMeetingAudioConferencing); + const audioConferencingfeature = this.props.call.feature(Features.TeamsMeetingAudioConferencing); const audioConferenceDetails = await audioConferencingfeature.getTeamsMeetingAudioConferencingDetails(); console.log(`meetingAudioConferenceDetails: ${JSON.stringify(audioConferenceDetails)}`) messageBarText += `Conference Id: ${audioConferenceDetails.phoneConferenceId}\n`; @@ -1088,7 +1119,7 @@ export default class CallCard extends React.Component { } // Include the stop all spotlight option only if the local participant has the capability // and the current spotlighted participant count is greater than 0 - if ((this.call.role == 'Presenter' || this.call.role == 'Organizer' || this.call.role == 'Co-organizer') + if ((this.props.call.role == 'Presenter' || this.props.call.role == 'Organizer' || this.props.call.role == 'Co-organizer') && this.spotlightFeature.getSpotlightedParticipants().length) { menuItems.push({ key: 'Stop All Spotlight', @@ -1117,7 +1148,7 @@ export default class CallCard extends React.Component { this.selectedRemoteParticipants.delete(identifier); } const selectedParticipants = []; - const allParticipants = new Set(this.call.remoteParticipants.map(rp => rp.identifier)); + const allParticipants = new Set(this.props.call.remoteParticipants.map(rp => rp.identifier)); this.selectedRemoteParticipants.forEach(identifier => { if (allParticipants.has(identifier)) { selectedParticipants.push(identifier); @@ -1127,7 +1158,7 @@ export default class CallCard extends React.Component { } handleMediaConstraint = (constraints) => { - this.call.setConstraints(constraints); + this.props.call.setConstraints(constraints); } render() { @@ -1163,8 +1194,8 @@ export default class CallCard extends React.Component { </div> </div> { - this.call && - <CurrentCallInformation sentResolution={this.state.sentResolution} call={this.call} /> + this.props.call && + <CurrentCallInformation sentResolution={this.state.sentResolution} call={this.props.call} /> } </div> <div className="ms-Grid-row"> @@ -1178,13 +1209,13 @@ export default class CallCard extends React.Component { <div className="ms-Grid-col ms-lg4"> <div> { this.state.showAddParticipantPanel && - <AddParticipantPopover call={this.call} /> + <AddParticipantPopover call={this.props.call} /> } </div> <div> { (this.state.lobbyParticipantsCount > 0) && - <Lobby call={this.call} capabilitiesFeature={this.capabilitiesFeature} lobbyParticipantsCount={this.state.lobbyParticipantsCount} /> + <Lobby call={this.props.call} capabilitiesFeature={this.capabilitiesFeature} lobbyParticipantsCount={this.state.lobbyParticipantsCount} /> } </div> { @@ -1203,7 +1234,7 @@ export default class CallCard extends React.Component { <RemoteParticipantCard key={`${utils.getIdentifierText(remoteParticipant.identifier)}`} remoteParticipant={remoteParticipant} - call={this.call} + call={this.props.call} menuOptionsHandler={this.getParticipantMenuCallBacks()} onSelectionChanged={(identifier, isChecked) => this.remoteParticipantSelectionChanged(identifier, isChecked)} capabilitiesFeature={this.capabilitiesFeature} @@ -1228,7 +1259,7 @@ export default class CallCard extends React.Component { remoteParticipant={v.participant} dominantSpeakerMode={this.state.dominantSpeakerMode} dominantRemoteParticipant={this.state.dominantRemoteParticipant} - call={this.call} + call={this.props.call} showMediaStats={this.state.logMediaStats} /> ) @@ -1243,7 +1274,7 @@ export default class CallCard extends React.Component { remoteParticipant={this.state.remoteScreenShareStream.participant} dominantSpeakerMode={this.state.dominantSpeakerMode} dominantRemoteParticipant={this.state.dominantRemoteParticipant} - call={this.call} + call={this.props.call} showMediaStats={this.state.logMediaStats} /> ) @@ -1280,7 +1311,7 @@ export default class CallCard extends React.Component { } </span> <span className="in-call-button" - onClick={() => this.call.hangUp()}> + onClick={() => this.props.call.hangUp()}> <Icon iconName="DeclineCall" /> </span> <span className="in-call-button" @@ -1496,6 +1527,62 @@ export default class CallCard extends React.Component { style={{ cursor: 'pointer' }}> {emojis[4]} </span> + <span className="in-call-button" + title={`${this.state.transferOn ? 'Hide' : 'Show'} transfer`} + variant="secondary" + onClick={() => this.setState(prevState =>{ + return{ + ...prevState, + transferOn : !prevState.transferOn + } + })}> + { + !this.state.transferOn && + <Icon iconName="TransferCall" /> + } + { + this.state.transferOn && + <Icon iconName="Hide2" /> + } + </span> + {this.state.transferArgs && + <> + <span className="in-call-button" + title="Settings" + variant="secondary" + onClick={() => { + this.state.transferArgs.accept(); + + }}> + <Icon iconName="Accept" /> + </span> + <span className="in-call-button" + onClick={() => { + this.state.transferArgs.reject(); + }}> + <Icon iconName="DeclineCall" /> + </span> + </>} + {this.state.transferOn && + <div className="ms-Grid-row separator-top"> + <div className="ms-Grid-col ms-lg6 call-input-panel text-left"> + <TextField + label="Transfer Identity" + componentRef={(val) => this.destinationUserId = val} + /> + </div> + <div className="ms-Grid-col ms-sm6 text-left pl-2 mt-3"> + <span className="in-call-button" + title={`transfer to identity`} + variant="secondary" + onClick={() => this.handleTransferToParticipant()}> + { + <Icon iconName="TransferCall" /> + } + </span> + </div> + </div> + } <ParticipantMenuOptions id={this.identifier} appendMenuitems={this.getMenuItems()} @@ -1557,7 +1644,7 @@ export default class CallCard extends React.Component { } { (this.state.callState === 'Connected') && !this.state.micMuted && !this.state.incomingAudioMuted && - <VolumeVisualizer call={this.call} deviceManager={this.deviceManager} remoteVolumeLevel={this.state.remoteVolumeLevel} /> + <VolumeVisualizer call={this.props.call} deviceManager={this.deviceManager} remoteVolumeLevel={this.state.remoteVolumeLevel} /> } </div> </div> @@ -1609,7 +1696,7 @@ export default class CallCard extends React.Component { </div> <div className='ms-Grid-col ms-sm12 ms-md5 md-lg6'> - <VideoEffectsContainer call={this.call} /> + <VideoEffectsContainer call={this.props.call} /> </div> </div> </div> @@ -1695,7 +1782,7 @@ export default class CallCard extends React.Component { <div className="md-grid-row"> { this.state.captionOn && - <CallCaption call={this.call} isTeamsUser={this.isTeamsUser}/> + <CallCaption call={this.props.call} isTeamsUser={this.isTeamsUser}/> } </div> </div> @@ -1709,7 +1796,7 @@ export default class CallCard extends React.Component { <div className="md-grid-row"> { this.state.callState === 'Connected' && - <DataChannelCard call={this.call} ref={this.dataChannelRef} remoteParticipants={this.state.remoteParticipants} /> + <DataChannelCard call={this.props.call} ref={this.dataChannelRef} remoteParticipants={this.state.remoteParticipants} /> } </div> </div> @@ -1721,7 +1808,7 @@ export default class CallCard extends React.Component { <h3>Audio effects and enhancements</h3> </div> <div className='ms-Grid-row'> - <AudioEffectsContainer call={this.call} deviceManager={this.deviceManager} /> + <AudioEffectsContainer call={this.props.call} deviceManager={this.deviceManager} /> </div> </div> } diff --git a/Project/src/MakeCall/CallSurvey.js b/Project/src/MakeCall/CallSurvey.js index 927b0d2c..d369f87f 100644 --- a/Project/src/MakeCall/CallSurvey.js +++ b/Project/src/MakeCall/CallSurvey.js @@ -11,7 +11,6 @@ import { TextField } from 'office-ui-fabric-react'; export default class CallSurvey extends React.Component { constructor(props) { super(props); - this.call = props.call; this.state = { overallIssue: '', overallRating: 0, @@ -85,7 +84,7 @@ export default class CallSurvey extends React.Component { if (this.state.screenShareRating !== 0) rating.screenshareRating = { score: this.state.screenShareRating }; if (this.state.screenShareIssue) rating.screenshareRating.issues = [this.state.screenShareIssue]; - this.call.feature(Features.CallSurvey).submitSurvey(rating).then((res) => { + this.props.call.feature(Features.CallSurvey).submitSurvey(rating).then((res) => { if (this.appInsights && this.state.improvementSuggestion !== '') { this.appInsights.trackEvent({ name: "CallSurvey", properties: { diff --git a/Project/src/MakeCall/IncomingCallCard.js b/Project/src/MakeCall/IncomingCallCard.js index 89bd7429..24cb253b 100644 --- a/Project/src/MakeCall/IncomingCallCard.js +++ b/Project/src/MakeCall/IncomingCallCard.js @@ -55,25 +55,37 @@ export default class IncomingCallCard extends React.Component { <div className="ms-Grid-row text-center"> <span className="incoming-call-button" title={'Answer call with microphone unmuted and video off'} - onClick={async () => this.incomingCall.accept(await this.acceptCallMicrophoneUnmutedVideoOff())}> + onClick={async () => { + this.incomingCall.accept(await this.acceptCallMicrophoneUnmutedVideoOff()); + this.props.onReject(); + }}> <Icon iconName="Microphone"/> <Icon iconName="VideoOff"/> </span> <span className="incoming-call-button" title={'Answer call with microphone unmuted and video on'} - onClick={async () => this.incomingCall.accept(await this.acceptCallMicrophoneUnmutedVideoOn())}> + onClick={async () => { + this.incomingCall.accept(await this.acceptCallMicrophoneUnmutedVideoOn()); + this.props.onReject(); + }}> <Icon iconName="Microphone"/> <Icon iconName="Video"/> </span> <span className="incoming-call-button" title={'Answer call with microphone muted and video on'} - onClick={async () => this.incomingCall.accept(await this.acceptCallMicrophoneMutedVideoOn())}> + onClick={async () => { + this.incomingCall.accept(await this.acceptCallMicrophoneMutedVideoOn()); + this.props.onReject(); + }}> <Icon iconName="MicOff"/> <Icon iconName="Video"/> </span> <span className="incoming-call-button" title={'Answer call with microphone muted and video off'} - onClick={async () => this.incomingCall.accept(await this.acceptCallMicrophoneMutedVideoOff())}> + onClick={async () => { + this.incomingCall.accept(await this.acceptCallMicrophoneMutedVideoOff()); + this.props.onReject(); + }}> <Icon iconName="MicOff"/> <Icon iconName="VideoOff"/> </span> diff --git a/Project/src/MakeCall/Lobby.js b/Project/src/MakeCall/Lobby.js index e5a747d6..58fd3e77 100644 --- a/Project/src/MakeCall/Lobby.js +++ b/Project/src/MakeCall/Lobby.js @@ -6,8 +6,7 @@ import { Features } from '@azure/communication-calling'; export default class Lobby extends React.Component { constructor(props) { super(props); - this.call = props.call; - this.lobby = this.call.lobby; + this.lobby = this.props.call.lobby; this.capabilitiesFeature = props.capabilitiesFeature; this.capabilities = this.capabilitiesFeature.capabilities; this.state = { diff --git a/Project/src/MakeCall/MakeCall.js b/Project/src/MakeCall/MakeCall.js index 2555ef5d..e609915e 100644 --- a/Project/src/MakeCall/MakeCall.js +++ b/Project/src/MakeCall/MakeCall.js @@ -51,7 +51,7 @@ export default class MakeCall extends React.Component { id: undefined, loggedIn: false, isCallClientActiveInAnotherTab: false, - call: undefined, + call: [], callSurvey: undefined, incomingCall: undefined, showCallSampleCode: false, @@ -159,7 +159,9 @@ export default class MakeCall extends React.Component { console.log(`callsUpdated, added=${e.added}, removed=${e.removed}`); e.added.forEach(call => { - this.setState({ call: call }); + this.setState(prevState => ({ + call: [...prevState.call, call] + })); const diagnosticChangedListener = (diagnosticInfo) => { const rmsg = `UFD Diagnostic changed: @@ -201,22 +203,24 @@ export default class MakeCall extends React.Component { }); e.removed.forEach(call => { - if (this.state.call && this.state.call === call) { - this.displayCallEndReason(this.state.call.callEndReason); + const newCallsArray = this.state.call.filter(c => c.id != call.id) + + if (newCallsArray.length < this.state.call.length) { + this.setState({call: newCallsArray}); + + if (newCallsArray.length === 0) { + this.displayCallEndReason(call.callEndReason, false); + } } }); }); this.callAgent.on('incomingCall', args => { const incomingCall = args.incomingCall; - if (this.state.call) { - incomingCall.reject(); - return; - } - + this.setState({ incomingCall: incomingCall }); incomingCall.on('callEnded', args => { - this.displayCallEndReason(args.callEndReason); + this.displayCallEndReason(args.callEndReason, true); }); }); @@ -230,12 +234,15 @@ export default class MakeCall extends React.Component { } } - displayCallEndReason = (callEndReason) => { + displayCallEndReason = (callEndReason, incoming) => { if (callEndReason.code !== 0 || callEndReason.subCode !== 0) { this.setState({ callSurvey: this.state.call, callError: `Call end reason: code: ${callEndReason.code}, subcode: ${callEndReason.subCode}` }); } - this.setState({ call: null, callSurvey: this.state.call, incomingCall: null }); + if (incoming) { + this.setState({incomingCall: null}); + return; + } } placeCall = async (withVideo) => { @@ -968,7 +975,7 @@ this.callAgent.on('incomingCall', async (args) => { </MessageBar> } { - !this.state.incomingCall && !this.state.call && !this.state.callSurvey && + !this.state.incomingCall && !this.state.call[0] && !this.state.callSurvey && <div> <div className="ms-Grid-row mt-3"> <div className="call-input-panel mb-5 ms-Grid-col ms-sm12 ms-md12 ms-lg12 ms-xl6 ms-xxl3"> @@ -981,17 +988,17 @@ this.callAgent.on('incomingCall', async (args) => { <div className="md-Grid-col ml-2 mt-0 ms-sm11 ms-md11 ms-lg9 ms-xl9 ms-xxl11"> <TextField className="mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label={`Enter an Identity to make a call to. You can specify multiple Identities to call by using \",\" separated values.`} placeholder="8:acs:<ACA resource ID>_<GUID>" componentRef={(val) => this.destinationUserIds = val} /> <TextField - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Destination Phone Identity or Phone Identities" placeholder="4:+18881231234" componentRef={(val) => this.destinationPhoneIds = val} /> <TextField - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="If calling a Phone Identity, your Alternate Caller Id must be specified." placeholder="4:+18881231234" componentRef={(val) => this.alternateCallerId = val} /> @@ -1001,21 +1008,21 @@ this.callAgent.on('incomingCall', async (args) => { className="primary-button" iconProps={{ iconName: 'Phone', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Place call" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.placeCall(false)}> </PrimaryButton> <PrimaryButton className="primary-button" iconProps={{ iconName: 'Video', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Place call with video" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.placeCall(true)}> </PrimaryButton> <PrimaryButton className="primary-button" iconProps={{iconName: 'Settings', style: {verticalAlign: 'middle', fontSize: 'large'}}} text={this.state.showCustomContext ? 'Remove context' : 'Custom context'} - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.setState({showCustomContext: !this.state.showCustomContext})}> </PrimaryButton> <div className="ms-Grid-row" @@ -1023,7 +1030,7 @@ this.callAgent.on('incomingCall', async (args) => { <div className="md-Grid-col ml-2 mt-0 ms-sm11 ms-md11 ms-lg9 ms-xl9 ms-xxl11"> <TextField className="mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Add user to user value" placeholder="" componentRef={(val) => this.userToUser = val} /> @@ -1035,14 +1042,14 @@ this.callAgent.on('incomingCall', async (args) => { <div className="md-Grid-col inline-flex ml-2 mt-0 ms-sm11 ms-md11 ms-lg9 ms-xl9 ms-xxl11"> <TextField className="mt-0 ms-sm6 ms-md6 ms-lg6 ms-xl6 ms-xxl6" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Custom header key" placeholder="" onChange={() => this.xHeadersChanged()} componentRef={(val) => this.xHeaders[i].key = val} /> <TextField className="mt-0 ml-2 ms-sm6 ms-md6 ms-lg6 ms-xl6 ms-xxl6" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Custom header value" placeholder="" onChange={() => this.xHeadersChanged()} @@ -1061,52 +1068,52 @@ this.callAgent.on('incomingCall', async (args) => { </div> <div className="ms-Grid-row"> <div className="md-Grid-col ml-2 ms-sm11 ms-md11 ms-lg9 ms-xl9 ms-xxl11"> - <div className={this.state.call || !this.state.loggedIn ? "call-input-panel-input-label-disabled" : ""}> + <div className={this.state.call[0] || !this.state.loggedIn ? "call-input-panel-input-label-disabled" : ""}> Enter meeting link </div> <div className="ml-3"> <TextField className="mb-3 mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Meeting link" defaultValue={new URLSearchParams(window.location.search).get(URL_PARAM.MEETING_LINK) ?? ''} componentRef={(val) => this.meetingLink = val} /> </div> - <div className={this.state.call || !this.state.loggedIn ? "call-input-panel-input-label-disabled" : ""}> + <div className={this.state.call[0] || !this.state.loggedIn ? "call-input-panel-input-label-disabled" : ""}> Or enter meeting id (and) passcode </div> <div className="ml-3"> <TextField className="mb-3 mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Meeting id" componentRef={(val) => this.meetingId = val} /> <TextField className="mb-3" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Meeting passcode (optional)" componentRef={(val) => this.passcode = val} /> </div> - <div className={this.state.call || !this.state.loggedIn ? "call-input-panel-input-label-disabled" : ""}> + <div className={this.state.call[0] || !this.state.loggedIn ? "call-input-panel-input-label-disabled" : ""}> Or enter meeting coordinates (Thread Id, Message Id, Organizer Id, and Tenant Id) </div> <div className="ml-3"> <TextField className="mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Thread Id" componentRef={(val) => this.threadId = val} /> <TextField - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Message Id" componentRef={(val) => this.messageId = val} /> <TextField - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Organizer Id" componentRef={(val) => this.organizerId = val} /> <TextField className="mb-3" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Tenant Id" componentRef={(val) => this.tenantId = val} /> </div> @@ -1115,13 +1122,13 @@ this.callAgent.on('incomingCall', async (args) => { <PrimaryButton className="primary-button" iconProps={{ iconName: 'Group', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Join Teams meeting" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.joinTeamsMeeting(false)}> </PrimaryButton> <PrimaryButton className="primary-button" iconProps={{ iconName: 'Video', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Join Teams meeting with video" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.joinTeamsMeeting(true)}> </PrimaryButton> </div> @@ -1134,7 +1141,7 @@ this.callAgent.on('incomingCall', async (args) => { <div className="ms-Grid-col ms-sm11 ms-md11 ms-lg9 ms-xl9 ms-xxl11"> <TextField className="mb-3 mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Group Id" defaultValue="29228d3e-040e-4656-a70e-890ab4e173e5" componentRef={(val) => this.destinationGroup = val} /> @@ -1144,14 +1151,14 @@ this.callAgent.on('incomingCall', async (args) => { className="primary-button" iconProps={{ iconName: 'Group', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Join group call" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.joinGroup(false)}> </PrimaryButton> <PrimaryButton className="primary-button" iconProps={{ iconName: 'Video', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Join group call with video" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.joinGroup(true)}> </PrimaryButton> </div> @@ -1160,7 +1167,7 @@ this.callAgent.on('incomingCall', async (args) => { <div className="ms-Grid-row"> <div className="md-Grid-col ml-2 ms-sm11 ms-md11 ms-lg9 ms-xl9 ms-xxl11"> <TextField className="mb-3 mt-0" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} label="Rooms id" placeholder="<GUID>" componentRef={(val) => this.roomsId = val} /> @@ -1169,13 +1176,13 @@ this.callAgent.on('incomingCall', async (args) => { <PrimaryButton className="primary-button" iconProps={{ iconName: 'Group', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Join Rooms call" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.joinRooms(false)}> </PrimaryButton> <PrimaryButton className="primary-button" iconProps={{ iconName: 'Video', style: { verticalAlign: 'middle', fontSize: 'large' } }} text="Join Rooms call with video" - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.joinRooms(true)}> </PrimaryButton> </div> @@ -1186,7 +1193,7 @@ this.callAgent.on('incomingCall', async (args) => { <h3 className="mb-1">Video Send Constraints</h3> <MediaConstraint onChange={this.handleMediaConstraint} - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} /> </div> </div> @@ -1194,15 +1201,15 @@ this.callAgent.on('incomingCall', async (args) => { } { - this.state.call && this.state.isPreCallDiagnosticsCallInProgress && + this.state.call.length && this.state.isPreCallDiagnosticsCallInProgress && <div> Pre Call Diagnostics call in progress... </div> } { - this.state.call && !this.state.callSurvey && !this.state.isPreCallDiagnosticsCallInProgress && + this.state.call.length === 1 && !this.state.callSurvey && !this.state.isPreCallDiagnosticsCallInProgress && <CallCard - call={this.state.call} + call={this.state.call[0]} deviceManager={this.deviceManager} selectedCameraDeviceId={this.state.selectedCameraDeviceId} cameraDeviceOptions={this.state.cameraDeviceOptions} @@ -1215,7 +1222,7 @@ this.callAgent.on('incomingCall', async (args) => { onShowMicrophoneNotFoundWarning={(show) => { this.setState({ showMicrophoneNotFoundWarning: show }) }} /> } { - this.state.incomingCall && !this.state.call && + this.state.incomingCall && <IncomingCallCard incomingCall={this.state.incomingCall} acceptCallMicrophoneUnmutedVideoOff={async () => await this.getCallOptions({ video: false, micMuted: false })} @@ -1235,7 +1242,7 @@ this.callAgent.on('incomingCall', async (args) => { className="secondary-button" iconProps={{ iconName: 'TestPlan', style: { verticalAlign: 'middle', fontSize: 'large' } }} text={`Run Pre Call Diagnostics`} - disabled={this.state.call || !this.state.loggedIn} + disabled={this.state.call[0] || !this.state.loggedIn} onClick={() => this.runPreCallDiagnostics()}> </PrimaryButton> <PrimaryButton @@ -1247,7 +1254,7 @@ this.callAgent.on('incomingCall', async (args) => { </div> </div> { - this.state.call && this.state.isPreCallDiagnosticsCallInProgress && + this.state.call.length && this.state.isPreCallDiagnosticsCallInProgress && <div> Pre Call Diagnostics call in progress... <div className="custom-row"> diff --git a/Project/src/MakeCall/RawVideoAccess/CustomVideoEffects.js b/Project/src/MakeCall/RawVideoAccess/CustomVideoEffects.js index 8a4a34bd..fcfc5c42 100644 --- a/Project/src/MakeCall/RawVideoAccess/CustomVideoEffects.js +++ b/Project/src/MakeCall/RawVideoAccess/CustomVideoEffects.js @@ -5,7 +5,6 @@ export default class CustomVideoEffects extends React.Component { constructor(props) { super(props); - this.call = props.call; this.stream = props.stream; this.isLocal = props.isLocal; this.bwStream = undefined; diff --git a/Project/src/MakeCall/RemoteParticipantCard.js b/Project/src/MakeCall/RemoteParticipantCard.js index 3c848674..27d63525 100644 --- a/Project/src/MakeCall/RemoteParticipantCard.js +++ b/Project/src/MakeCall/RemoteParticipantCard.js @@ -14,15 +14,14 @@ import { ParticipantMenuOptions } from './ParticipantMenuOptions'; export default class RemoteParticipantCard extends React.Component { constructor(props) { super(props); - this.call = props.call; this.remoteParticipant = props.remoteParticipant; this.identifier = this.remoteParticipant.identifier; this.id = utils.getIdentifierText(this.remoteParticipant.identifier); this.isCheckable = isCommunicationUserIdentifier(this.remoteParticipant.identifier) || isMicrosoftTeamsUserIdentifier(this.remoteParticipant.identifier); - this.spotlightFeature = this.call.feature(Features.Spotlight); - this.raiseHandFeature = this.call.feature(Features.RaiseHand); + this.spotlightFeature = this.props.call.feature(Features.Spotlight); + this.raiseHandFeature = this.props.call.feature(Features.RaiseHand); this.capabilitiesFeature = props.capabilitiesFeature; this.capabilities = this.capabilitiesFeature.capabilities; this.menuOptionsHandler= props.menuOptionsHandler; @@ -61,6 +60,7 @@ export default class RemoteParticipantCard extends React.Component { }); this.remoteParticipant.on('stateChanged', () => { + console.log('EVENT, remote participant state: ' + this.remoteParticipant.state) this.setState({ state: this.remoteParticipant.state }); }); @@ -104,7 +104,7 @@ export default class RemoteParticipantCard extends React.Component { handleRemoveParticipant(e, identifier) { e.preventDefault(); - this.call.removeParticipant(identifier).catch((e) => console.error(e)) + this.props.call.removeParticipant(identifier).catch((e) => console.error(e)) } handleMuteParticipant(e, remoteParticipant) { @@ -151,7 +151,7 @@ export default class RemoteParticipantCard extends React.Component { async admitParticipant() { console.log('admit'); try { - await this.call.lobby.admit(this.identifier); + await this.props.call.lobby.admit(this.identifier); } catch (e) { console.error(e); } @@ -160,7 +160,7 @@ export default class RemoteParticipantCard extends React.Component { async rejectParticipant() { console.log('reject'); try { - await this.call.lobby.reject(this.identifier); + await this.props.call.lobby.reject(this.identifier); } catch (e) { console.error(e); } diff --git a/Project/src/MakeCall/StreamRenderer.js b/Project/src/MakeCall/StreamRenderer.js index 06b3a7c0..58409662 100644 --- a/Project/src/MakeCall/StreamRenderer.js +++ b/Project/src/MakeCall/StreamRenderer.js @@ -24,7 +24,6 @@ export default class StreamRenderer extends React.Component { videoStats: undefined, transportStats: undefined }; - this.call = props.call; } componentDidUpdate(prevProps, prevState, snapshot) { @@ -177,7 +176,7 @@ export default class StreamRenderer extends React.Component { </h4> } </div> - <CustomVideoEffects call={this.call} videoContainerId={this.videoContainerId} remoteParticipantId={this.remoteParticipant.identifier}/> + <CustomVideoEffects call={this.props.call} videoContainerId={this.videoContainerId} remoteParticipantId={this.remoteParticipant.identifier}/> </div> ); } diff --git a/Project/src/MakeCall/VideoEffects/VideoEffectsContainer.js b/Project/src/MakeCall/VideoEffects/VideoEffectsContainer.js index ae66a76c..2ee5d6cf 100644 --- a/Project/src/MakeCall/VideoEffects/VideoEffectsContainer.js +++ b/Project/src/MakeCall/VideoEffects/VideoEffectsContainer.js @@ -13,7 +13,6 @@ export const LoadingSpinner = () => { export default class VideoEffectsContainer extends React.Component { constructor(props) { super(props); - this.call = props.call; this.localVideoStreamFeatureApi = null; this.state = { @@ -45,7 +44,7 @@ export default class VideoEffectsContainer extends React.Component { } initLocalVideoStreamFeatureApi() { - const localVideoStream = this.call.localVideoStreams.find(v => { return v.mediaStreamType === 'Video'}); + const localVideoStream = this.props.call.localVideoStreams.find(v => { return v.mediaStreamType === 'Video'}); if (!localVideoStream) { this.logError('No local video streams found.');