зеркало из
1
0
Форкнуть 0

[#2367027] Fixing Screen Share and Caller Name in 1-1 Calls. (#27)

* [#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:
Anjul Garg 2021-02-25 17:40:13 -08:00 коммит произвёл GitHub
Родитель c40ba8e3de
Коммит 3f83ce230f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
8 изменённых файлов: 153 добавлений и 72 удалений

Просмотреть файл

@ -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>;
};