* [#2367027] Fixing Screen Share not working in 1-1 Calls. And Callers Display Name not showing in IncomingCallAlert * [#2367027] Fixing Typo * [#2367027] Adding a fix for race condition while screen sharing. * [#2367027] Exporting IncomingCallAlert Types.
This commit is contained in:
Родитель
c40ba8e3de
Коммит
3f83ce230f
|
@ -39,3 +39,4 @@ export { MediaGalleryTileComponent } from './MediaGalleryTile';
|
|||
export { StreamMediaComponent } from './StreamMedia';
|
||||
export { ParticipantStackItemComponent } from './ParticipantStackItem';
|
||||
export { IncomingCallModal, IncomingCallToast } from './IncomingCallAlerts';
|
||||
export type { IncomingCallModalProps, IncomingCallToastProps } from './IncomingCallAlerts';
|
||||
|
|
|
@ -1,31 +1,42 @@
|
|||
// © Microsoft Corporation. All rights reserved.
|
||||
import { Call } from '@azure/communication-calling';
|
||||
import { Stack } from '@fluentui/react';
|
||||
import { useCallingContext } from '../../index';
|
||||
import { IncomingCallsProvider, useIncomingCallsContext } from '../../providers';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { IncomingCallToast } from '../../components';
|
||||
import { useMicrophone, useIncomingCall } from '../../hooks';
|
||||
import { IncomingCallToastProps } from '../../components';
|
||||
import { useIncomingCall, useMicrophone } from '../../hooks';
|
||||
import { useCallingContext } from '../../index';
|
||||
import { IncomingCallsProvider } from '../../providers';
|
||||
|
||||
export type IncomingCallProps = {
|
||||
onIncomingCallAccepted?: () => void;
|
||||
onIncomingCallRejected?: () => void;
|
||||
};
|
||||
|
||||
const IncomingCallAlertACSWrapper = (props: IncomingCallToastProps & { call: Call }): JSX.Element => {
|
||||
const { call } = props;
|
||||
const [callerName, setCallerName] = useState<string | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
setCallerName(call.remoteParticipants[0]?.displayName);
|
||||
}, [call]);
|
||||
|
||||
return <IncomingCallToast {...props} callerName={callerName} />;
|
||||
};
|
||||
|
||||
const IncomingCallContainer = (props: IncomingCallProps): JSX.Element => {
|
||||
// todo: move to mapper:
|
||||
const { incomingCalls } = useIncomingCallsContext();
|
||||
const { accept, reject } = useIncomingCall();
|
||||
const { accept, reject, incomingCalls } = useIncomingCall();
|
||||
const { unmute } = useMicrophone();
|
||||
|
||||
if (!(incomingCalls && incomingCalls.length > 0)) {
|
||||
return <></>;
|
||||
} else {
|
||||
if (!(incomingCalls && incomingCalls.length > 0)) return <></>;
|
||||
else {
|
||||
return (
|
||||
<Stack>
|
||||
{incomingCalls.map((call) => (
|
||||
<div style={{ marginTop: '8px', marginBottom: '8px' }} key={call.id}>
|
||||
<IncomingCallToast
|
||||
callerName={call.remoteParticipants[0]?.displayName}
|
||||
<IncomingCallAlertACSWrapper
|
||||
call={call}
|
||||
onClickReject={async () => {
|
||||
await reject(call);
|
||||
props.onIncomingCallRejected && props.onIncomingCallRejected();
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// © Microsoft Corporation. All rights reserved.
|
||||
|
||||
import { CallState, HangupCallOptions } from '@azure/communication-calling';
|
||||
import { useCallAgent } from '../hooks';
|
||||
import { useCallContext, useCallingContext } from '../providers';
|
||||
import { ParticipantStream } from '../types/ParticipantStream';
|
||||
import { useOutgoingCall } from '../hooks';
|
||||
|
@ -19,9 +18,6 @@ export const MapToOneToOneCallProps = (): CallContainerProps => {
|
|||
const { callState, screenShareStream, localScreenShareActive } = useCallContext();
|
||||
const { endCall } = useOutgoingCall();
|
||||
|
||||
// Call useCallAgent to subscribe to events.
|
||||
useCallAgent();
|
||||
|
||||
return {
|
||||
isCallInitialized: !!(callAgent && deviceManager),
|
||||
callState: callState,
|
||||
|
|
|
@ -262,7 +262,7 @@ describe('useCallAgent tests', () => {
|
|||
expect(setScreenShareStreamCallback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('if availabilityChanged of addedRemoteVideoStream is called and addedRemoteVideoStream is not available, setScreenShareStream should have been called with undefined', async () => {
|
||||
test('if availabilityChanged of addedRemoteVideoStream and addedRemoteVideoStream is not available, setScreenShareStream should have been called with undefined', async () => {
|
||||
addedRemoteVideoStream.type = 'ScreenSharing';
|
||||
addedRemoteVideoStream.isAvailable = false;
|
||||
renderHook(() => useCallAgent());
|
||||
|
|
|
@ -18,7 +18,15 @@ export type UseCallAgentType = {
|
|||
|
||||
export default (): void => {
|
||||
const { callAgent } = useCallingContext();
|
||||
const { call, setCall, setScreenShareStream, setCallState, setParticipants, setLocalScreenShare } = useCallContext();
|
||||
const {
|
||||
call,
|
||||
setCall,
|
||||
screenShareStream,
|
||||
setScreenShareStream,
|
||||
setCallState,
|
||||
setParticipants,
|
||||
setLocalScreenShare
|
||||
} = useCallContext();
|
||||
|
||||
useEffect(() => {
|
||||
const subscribeToParticipant = (participant: RemoteParticipant, call: Call): void => {
|
||||
|
@ -53,7 +61,13 @@ export default (): void => {
|
|||
if (addedStream.isAvailable) {
|
||||
setScreenShareStream({ stream: addedStream, user: participant });
|
||||
} else {
|
||||
setScreenShareStream(undefined);
|
||||
// Prevents race condition when participant A turns on screen sharing
|
||||
// and participant B turns off screen sharing at the same time.
|
||||
// Ensures that the screen sharing stream is turned off only when
|
||||
// the current screen sharing participant turns it off.
|
||||
if (!screenShareStream || screenShareStream?.stream?.id === addedStream.id) {
|
||||
setScreenShareStream(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -109,5 +123,14 @@ export default (): void => {
|
|||
return () => {
|
||||
callAgent?.off('callsUpdated', onCallsUpdated);
|
||||
};
|
||||
}, [call, callAgent, setCallState, setParticipants, setScreenShareStream, setCall, setLocalScreenShare]);
|
||||
}, [
|
||||
call,
|
||||
callAgent,
|
||||
setCallState,
|
||||
setParticipants,
|
||||
setScreenShareStream,
|
||||
setCall,
|
||||
setLocalScreenShare,
|
||||
screenShareStream
|
||||
]);
|
||||
};
|
||||
|
|
|
@ -1,41 +1,65 @@
|
|||
// © Microsoft Corporation. All rights reserved.
|
||||
|
||||
import { AcceptCallOptions, Call } from '@azure/communication-calling';
|
||||
import { useCallback } from 'react';
|
||||
import { useCallContext } from '../providers';
|
||||
import { useCallContext, useIncomingCallsContext } from '../providers';
|
||||
|
||||
export type UseIncomingCallType = {
|
||||
accept: (incomingCall: Call, acceptCallOptions?: AcceptCallOptions) => Promise<void>;
|
||||
reject: (incomingCall: Call) => Promise<void>;
|
||||
incomingCalls: Call[];
|
||||
};
|
||||
|
||||
export const useIncomingCall = (): UseIncomingCallType => {
|
||||
const { setCall, localVideoStream } = useCallContext();
|
||||
const { incomingCalls } = useIncomingCallsContext();
|
||||
const { setCall, localVideoStream, setScreenShareStream, screenShareStream } = useCallContext();
|
||||
|
||||
/** Accept an incoming calls and set it as the active call. */
|
||||
const accept = useCallback(
|
||||
async (incomingCall: Call, acceptCallOptions?: AcceptCallOptions): Promise<void> => {
|
||||
if (!incomingCall) {
|
||||
throw new Error('incomingCall is null or undefined');
|
||||
}
|
||||
const accept = async (incomingCall: Call, acceptCallOptions?: AcceptCallOptions): Promise<void> => {
|
||||
if (!incomingCall) {
|
||||
throw new Error('incomingCall is null or undefined');
|
||||
}
|
||||
|
||||
const videoOptions = acceptCallOptions?.videoOptions || {
|
||||
localVideoStreams: localVideoStream ? [localVideoStream] : undefined
|
||||
};
|
||||
const videoOptions = acceptCallOptions?.videoOptions || {
|
||||
localVideoStreams: localVideoStream ? [localVideoStream] : undefined
|
||||
};
|
||||
|
||||
await incomingCall.accept({ videoOptions });
|
||||
setCall(incomingCall);
|
||||
},
|
||||
[localVideoStream, setCall]
|
||||
);
|
||||
await incomingCall.accept({ videoOptions });
|
||||
setCall(incomingCall);
|
||||
|
||||
// Listen to Remote Participant screen share stream
|
||||
// Should we move this logic to CallProvider ?
|
||||
incomingCall.remoteParticipants.forEach((participant) => {
|
||||
participant.on('videoStreamsUpdated', (e) => {
|
||||
e.added.forEach((addedStream) => {
|
||||
if (addedStream.type === 'Video') return;
|
||||
addedStream.on('availabilityChanged', () => {
|
||||
if (addedStream.isAvailable) {
|
||||
setScreenShareStream({ stream: addedStream, user: participant });
|
||||
} else {
|
||||
// Prevents race condition when participant A turns on screen sharing
|
||||
// and participant B turns off screen sharing at the same time.
|
||||
// Ensures that the screen sharing stream is turned off only when
|
||||
// the current screen sharing participant turns it off.
|
||||
if (!screenShareStream || screenShareStream?.stream?.id === addedStream.id) {
|
||||
setScreenShareStream(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (addedStream.isAvailable) {
|
||||
setScreenShareStream({ stream: addedStream, user: participant });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/** Reject an incoming call and remove it from `incomingCalls`. */
|
||||
const reject = useCallback(async (incomingCall: Call): Promise<void> => {
|
||||
const reject = async (incomingCall: Call): Promise<void> => {
|
||||
if (!incomingCall) {
|
||||
throw new Error('incomingCall is null or undefined');
|
||||
}
|
||||
await incomingCall.reject();
|
||||
}, []);
|
||||
};
|
||||
|
||||
return { accept, reject };
|
||||
return { accept, reject, incomingCalls };
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { AudioOptions, JoinCallOptions, LocalVideoStream } from '@azure/communication-calling';
|
||||
import { CommunicationUser } from '@azure/communication-signaling';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useCallingContext, useCallContext } from '../providers';
|
||||
|
||||
export type UseOutgoingCallType = {
|
||||
|
@ -12,7 +12,7 @@ export type UseOutgoingCallType = {
|
|||
|
||||
export const useOutgoingCall = (): UseOutgoingCallType => {
|
||||
const { callAgent } = useCallingContext();
|
||||
const { call, setCall, setCallState, localVideoStream } = useCallContext();
|
||||
const { call, setCall, setCallState, localVideoStream, setScreenShareStream, screenShareStream } = useCallContext();
|
||||
|
||||
useEffect(() => {
|
||||
const updateCallState = (): void => {
|
||||
|
@ -27,35 +27,58 @@ export const useOutgoingCall = (): UseOutgoingCallType => {
|
|||
};
|
||||
}, [call, setCallState]);
|
||||
|
||||
const makeCall = useCallback(
|
||||
(receiver: CommunicationUser, joinCallOptions?: JoinCallOptions): void => {
|
||||
if (!callAgent) {
|
||||
throw new Error('CallAgent is invalid');
|
||||
}
|
||||
const makeCall = (receiver: CommunicationUser, joinCallOptions?: JoinCallOptions): void => {
|
||||
if (!callAgent) {
|
||||
throw new Error('CallAgent is invalid');
|
||||
}
|
||||
|
||||
// Use provided audio options or default to unmuted for a direct call being made
|
||||
const audioOptions: AudioOptions = joinCallOptions?.audioOptions || { muted: false };
|
||||
// Use provided audio options or default to unmuted for a direct call being made
|
||||
const audioOptions: AudioOptions = joinCallOptions?.audioOptions || { muted: false };
|
||||
|
||||
// Use provided video options or grab options set in the react context
|
||||
let videoOptions = joinCallOptions?.videoOptions;
|
||||
if (!videoOptions && localVideoStream) {
|
||||
videoOptions = {
|
||||
localVideoStreams: [localVideoStream as LocalVideoStream]
|
||||
};
|
||||
}
|
||||
// Use provided video options or grab options set in the react context
|
||||
let videoOptions = joinCallOptions?.videoOptions;
|
||||
if (!videoOptions && localVideoStream) {
|
||||
videoOptions = {
|
||||
localVideoStreams: [localVideoStream as LocalVideoStream]
|
||||
};
|
||||
}
|
||||
|
||||
const newCall = callAgent.call([receiver], { videoOptions, audioOptions });
|
||||
setCall(newCall);
|
||||
},
|
||||
[callAgent, localVideoStream, setCall]
|
||||
);
|
||||
const newCall = callAgent.call([receiver], { videoOptions, audioOptions });
|
||||
setCall(newCall);
|
||||
|
||||
const endCall = useCallback(async (): Promise<void> => {
|
||||
// Listen to Remote Participant screen share stream
|
||||
// Should we move this logic to CallProvider ?
|
||||
newCall.remoteParticipants.forEach((participant) => {
|
||||
participant.on('videoStreamsUpdated', (e) => {
|
||||
e.added.forEach((addedStream) => {
|
||||
if (addedStream.type === 'Video') return;
|
||||
addedStream.on('availabilityChanged', () => {
|
||||
if (addedStream.isAvailable) {
|
||||
setScreenShareStream({ stream: addedStream, user: participant });
|
||||
} else {
|
||||
// Prevents race condition when participant A turns on screen sharing
|
||||
// and participant B turns off screen sharing at the same time.
|
||||
// Ensures that the screen sharing stream is turned off only when
|
||||
// the current screen sharing participant turns it off.
|
||||
if (!screenShareStream || screenShareStream?.stream?.id === addedStream.id) {
|
||||
setScreenShareStream(undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (addedStream.isAvailable) {
|
||||
setScreenShareStream({ stream: addedStream, user: participant });
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const endCall = async (): Promise<void> => {
|
||||
if (!call) {
|
||||
throw new Error('Call is invalid');
|
||||
}
|
||||
await call.hangUp({ forEveryone: true });
|
||||
}, [call]);
|
||||
};
|
||||
|
||||
return { makeCall, endCall };
|
||||
};
|
||||
|
|
|
@ -4,7 +4,7 @@ import { Call, CollectionUpdatedEvent } from '@azure/communication-calling';
|
|||
import React, { createContext, useState } from 'react';
|
||||
import { useValidContext } from '../utils';
|
||||
import { useEffect } from 'react';
|
||||
import { useCallContext, useCallingContext } from '../providers';
|
||||
import { useCallingContext } from '../providers';
|
||||
|
||||
export type IncomingCallsContextType = {
|
||||
incomingCalls: Call[];
|
||||
|
@ -15,25 +15,28 @@ export const IncomingCallsContext = createContext<IncomingCallsContextType | und
|
|||
export const IncomingCallsProvider = (props: { children: React.ReactNode }): JSX.Element => {
|
||||
const [incomingCalls, setIncomingCalls] = useState<Call[]>([]);
|
||||
const { callAgent } = useCallingContext();
|
||||
const { call } = useCallContext();
|
||||
|
||||
// Update incomingCalls whenever a new call is added or removed.
|
||||
// Update `incomingCalls` whenever a new call is added or removed.
|
||||
// Also configures an event on each call so that it removes itself from
|
||||
// active calls.
|
||||
useEffect(() => {
|
||||
const onCallsUpdate: CollectionUpdatedEvent<Call> = () => {
|
||||
setIncomingCalls(callAgent?.calls.filter((c: Call) => c.isIncoming) ?? []);
|
||||
const validCalls = callAgent?.calls.filter((c: Call) => c.isIncoming) ?? [];
|
||||
setIncomingCalls(validCalls);
|
||||
validCalls.forEach((c) => {
|
||||
c.on('callStateChanged', () => {
|
||||
if (c.state !== 'Incoming') {
|
||||
validCalls.splice(validCalls.indexOf(c), 1);
|
||||
setIncomingCalls(validCalls);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
callAgent?.on('callsUpdated', onCallsUpdate);
|
||||
return () => callAgent?.off('callsUpdated', onCallsUpdate);
|
||||
}, [callAgent, setIncomingCalls]);
|
||||
|
||||
// Remove the active call from incomingCalls.
|
||||
useEffect(() => {
|
||||
if (call) {
|
||||
setIncomingCalls(callAgent?.calls.filter((c: Call) => c.isIncoming && c !== call) ?? []);
|
||||
}
|
||||
}, [call, callAgent, setIncomingCalls]);
|
||||
|
||||
return <IncomingCallsContext.Provider value={{ incomingCalls }}>{props.children}</IncomingCallsContext.Provider>;
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче