Task/2426782 2426783 call control handlers selectors (#203)
* [#2426782] Initial handlers for call controls * [#2426782][#2426783] Handlers and Selectors for Call Controls * [#2426782][#2426783] Memoizing createDefaultHandlersForComponent * [#2426782][#2426783] Renaming videoButtonSelector to cameraButtonSelector * Change files * Update rush changelog * [#2426782][#2426783] Updating API signature * [#2426782][#2426783] Updating handlers * [#2426782][#2426783] Restructuring handlers into one unified memoized function
This commit is contained in:
Родитель
4a99ce8b5a
Коммит
6168a8007a
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "prerelease",
|
||||
"comment": "Add handler and selector for call controls",
|
||||
"packageName": "@azure/acs-calling-declarative",
|
||||
"email": "anjulgarg@live.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "prerelease",
|
||||
"comment": "Add handler and selector for call controls",
|
||||
"packageName": "@azure/acs-calling-selector",
|
||||
"email": "anjulgarg@live.com",
|
||||
"dependentChangeType": "patch"
|
||||
}
|
|
@ -83,7 +83,7 @@ export interface IncomingCall {
|
|||
export interface LocalVideoStream {
|
||||
mediaStreamType: MediaStreamType;
|
||||
source: VideoDeviceInfo;
|
||||
videoStreamRendererView: VideoStreamRendererView | undefined;
|
||||
videoStreamRendererView?: VideoStreamRendererView | undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
|
|
@ -45,7 +45,7 @@ export interface LocalVideoStream {
|
|||
* {@Link VideoStreamRendererView} is added/removed from state by startRenderVideo/stopRenderVideo in
|
||||
* {@Link DeclarativeCallClient} API.
|
||||
*/
|
||||
videoStreamRendererView: VideoStreamRendererView | undefined;
|
||||
videoStreamRendererView?: VideoStreamRendererView | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -55,7 +55,7 @@ module.exports = {
|
|||
{
|
||||
// remove ban on files affected by https://github.com/microsoft/rushstack/pull/1916.
|
||||
// This should be removed once this issue is fixed.
|
||||
files: ['TEMPORARY-REMOVE-THIS'],
|
||||
files: ['callControlSelectors.ts'],
|
||||
rules: {
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off'
|
||||
|
|
|
@ -4,14 +4,18 @@
|
|||
|
||||
```ts
|
||||
|
||||
import { AudioDeviceInfo } from '@azure/communication-calling';
|
||||
import { Call } from '@azure/communication-calling';
|
||||
import { CallAgent } from '@azure/communication-calling';
|
||||
import * as callingDeclarative from '@azure/acs-calling-declarative';
|
||||
import { CommunicationUserIdentifier } from '@azure/communication-common';
|
||||
import { CreateViewOptions } from '@azure/communication-calling';
|
||||
import { DeclarativeCallClient } from '@azure/acs-calling-declarative';
|
||||
import { DeviceManager } from '@azure/communication-calling';
|
||||
import { HangUpOptions } from '@azure/communication-calling';
|
||||
import { PhoneNumberIdentifier } from '@azure/communication-common';
|
||||
import { ReactElement } from 'react';
|
||||
import * as reselect from 'reselect';
|
||||
import { StartCallOptions } from '@azure/communication-calling';
|
||||
import { UnknownIdentifier } from '@azure/communication-common';
|
||||
import { VideoDeviceInfo } from '@azure/communication-calling';
|
||||
|
@ -21,33 +25,81 @@ export type BaseSelectorProps = {
|
|||
callId: string;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CallAgentHandlers = {
|
||||
onStartCall(participants: (CommunicationUserIdentifier | PhoneNumberIdentifier | UnknownIdentifier)[], options?: StartCallOptions): Call;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CallClientHandlers = {
|
||||
getDeviceManager: () => Promise<DeviceManager>;
|
||||
};
|
||||
|
||||
// @public
|
||||
export type CallHandlers = {
|
||||
onHangUp(options?: HangUpOptions): Promise<void>;
|
||||
};
|
||||
// @public (undocumented)
|
||||
export const cameraButtonSelector: reselect.OutputParametricSelector<callingDeclarative.CallClientState, BaseSelectorProps, {
|
||||
checked: boolean;
|
||||
}, (res: callingDeclarative.Call | undefined) => {
|
||||
checked: boolean;
|
||||
}>;
|
||||
|
||||
// @public
|
||||
export type CommonProperties<A, B> = {
|
||||
[P in keyof A & keyof B]: A[P] extends B[P] ? (A[P] extends B[P] ? P : never) : never;
|
||||
[P in keyof A & keyof B]: A[P] extends B[P] ? P : never;
|
||||
}[keyof A & keyof B];
|
||||
|
||||
// @public
|
||||
export const createDefaultHandlersForComponent: <Props>(declarativeCallClient: DeclarativeCallClient, callAgent: CallAgent | undefined, deviceManager: DeviceManager | undefined, call: Call | undefined, _: (props: Props) => ReactElement | null) => Pick<CallClientHandlers & CallAgentHandlers & DeviceManagerHandlers & CallHandlers, CommonProperties<CallClientHandlers & CallAgentHandlers & DeviceManagerHandlers & CallHandlers, Props>> | Pick<CallClientHandlers, CommonProperties<CallClientHandlers, Props>>;
|
||||
export const createDefaultHandlersForComponent: <Props>(declarativeCallClient: DeclarativeCallClient, callAgent: CallAgent | undefined, deviceManager: DeviceManager | undefined, call: Call | undefined, _Component: (props: Props) => ReactElement | null) => Pick<{
|
||||
onHangUp: (options?: HangUpOptions | undefined) => Promise<void> | void;
|
||||
onMute: () => Promise<void> | void;
|
||||
onUnmute: () => Promise<void> | void;
|
||||
onSelectCamera: (deviceId: string) => Promise<void | undefined>;
|
||||
onSelectMicrophone: (deviceId: string) => Promise<void | undefined>;
|
||||
onSelectSpeaker: (deviceId: string) => Promise<void | undefined>;
|
||||
onStartCall: (participants: (CommunicationUserIdentifier | PhoneNumberIdentifier | UnknownIdentifier)[], options?: StartCallOptions | undefined) => Call | undefined;
|
||||
onStartLocalVideo: (callId: string, deviceId: string, options: CreateViewOptions) => Promise<void>;
|
||||
onStopLocalVideo: (callId: string) => Promise<void> | void;
|
||||
onStartScreenShare: () => Promise<void> | void;
|
||||
onStopScreenShare: () => Promise<void> | void;
|
||||
onToggleLocalVideo: (callId: string, videoDeviceInfo: any, options: any) => Promise<void> | void;
|
||||
onToggleMicrophone: () => Promise<void> | void;
|
||||
onToggleScreenShare: () => Promise<void> | void;
|
||||
}, CommonProperties<{
|
||||
onHangUp: (options?: HangUpOptions | undefined) => Promise<void> | void;
|
||||
onMute: () => Promise<void> | void;
|
||||
onUnmute: () => Promise<void> | void;
|
||||
onSelectCamera: (deviceId: string) => Promise<void | undefined>;
|
||||
onSelectMicrophone: (deviceId: string) => Promise<void | undefined>;
|
||||
onSelectSpeaker: (deviceId: string) => Promise<void | undefined>;
|
||||
onStartCall: (participants: (CommunicationUserIdentifier | PhoneNumberIdentifier | UnknownIdentifier)[], options?: StartCallOptions | undefined) => Call | undefined;
|
||||
onStartLocalVideo: (callId: string, deviceId: string, options: CreateViewOptions) => Promise<void>;
|
||||
onStopLocalVideo: (callId: string) => Promise<void> | void;
|
||||
onStartScreenShare: () => Promise<void> | void;
|
||||
onStopScreenShare: () => Promise<void> | void;
|
||||
onToggleLocalVideo: (callId: string, videoDeviceInfo: any, options: any) => Promise<void> | void;
|
||||
onToggleMicrophone: () => Promise<void> | void;
|
||||
onToggleScreenShare: () => Promise<void> | void;
|
||||
}, Props>>;
|
||||
|
||||
// @public
|
||||
export type DeviceManagerHandlers = {
|
||||
getCameras(): Promise<VideoDeviceInfo[]>;
|
||||
};
|
||||
// @public (undocumented)
|
||||
export const microphoneButtonSelector: reselect.OutputParametricSelector<callingDeclarative.CallClientState, BaseSelectorProps, {
|
||||
checked: boolean;
|
||||
}, (res: callingDeclarative.Call | undefined) => {
|
||||
checked: boolean;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const optionsButtonSelector: reselect.OutputParametricSelector<callingDeclarative.CallClientState, BaseSelectorProps, {
|
||||
microphones: AudioDeviceInfo[];
|
||||
speakers: AudioDeviceInfo[];
|
||||
cameras: VideoDeviceInfo[];
|
||||
selectedMicrophone: AudioDeviceInfo | undefined;
|
||||
selectedSpeaker: AudioDeviceInfo | undefined;
|
||||
selectedCamera: VideoDeviceInfo | undefined;
|
||||
}, (res1: callingDeclarative.DeviceManagerState, res2: callingDeclarative.Call | undefined) => {
|
||||
microphones: AudioDeviceInfo[];
|
||||
speakers: AudioDeviceInfo[];
|
||||
cameras: VideoDeviceInfo[];
|
||||
selectedMicrophone: AudioDeviceInfo | undefined;
|
||||
selectedSpeaker: AudioDeviceInfo | undefined;
|
||||
selectedCamera: VideoDeviceInfo | undefined;
|
||||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const screenShareButtonSelector: reselect.OutputParametricSelector<callingDeclarative.CallClientState, BaseSelectorProps, {
|
||||
checked: boolean | undefined;
|
||||
}, (res: callingDeclarative.Call | undefined) => {
|
||||
checked: boolean | undefined;
|
||||
}>;
|
||||
|
||||
|
||||
// (No @packageDocumentation comment for this package)
|
||||
|
|
|
@ -18,3 +18,6 @@ export const getIncomingCalls = (state: CallClientState): Map<string, IncomingCa
|
|||
export const getIncomingCallsEnded = (state: CallClientState): IncomingCall[] => state.incomingCallsEnded;
|
||||
|
||||
export const getDeviceManager = (state: CallClientState): DeviceManagerState => state.deviceManagerState;
|
||||
|
||||
export const getCall = (state: CallClientState, props: BaseSelectorProps): Call | undefined =>
|
||||
state.calls.get(props.callId);
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
// © Microsoft Corporation. All rights reserved.
|
||||
|
||||
// @ts-ignore
|
||||
import * as reselect from 'reselect';
|
||||
// @ts-ignore
|
||||
import * as callingDeclarative from '@azure/acs-calling-declarative';
|
||||
// @ts-ignore
|
||||
import { BaseSelectorProps } from './baseSelectors';
|
||||
import { getCall, getDeviceManager } from './baseSelectors';
|
||||
// @ts-ignore
|
||||
import { AudioDeviceInfo, VideoDeviceInfo } from '@azure/communication-calling';
|
||||
|
||||
export const microphoneButtonSelector = reselect.createSelector([getCall], (call) => {
|
||||
return {
|
||||
checked: !call?.isMuted
|
||||
};
|
||||
});
|
||||
|
||||
export const cameraButtonSelector = reselect.createSelector([getCall], (call) => {
|
||||
return {
|
||||
checked: !!call?.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video')
|
||||
};
|
||||
});
|
||||
|
||||
export const screenShareButtonSelector = reselect.createSelector([getCall], (call) => {
|
||||
return {
|
||||
checked: call?.isScreenSharingOn
|
||||
};
|
||||
});
|
||||
|
||||
export const optionsButtonSelector = reselect.createSelector([getDeviceManager, getCall], (deviceManager, call) => {
|
||||
return {
|
||||
microphones: deviceManager.microphones,
|
||||
speakers: deviceManager.speakers,
|
||||
cameras: deviceManager.cameras,
|
||||
selectedMicrophone: deviceManager.selectedMicrophone,
|
||||
selectedSpeaker: deviceManager.selectedSpeaker,
|
||||
selectedCamera: call?.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video')?.source
|
||||
};
|
||||
});
|
|
@ -3,11 +3,11 @@
|
|||
import { CallAgent, CallAgentOptions, DeviceManager } from '@azure/communication-calling';
|
||||
import { CommunicationTokenCredential } from '@azure/communication-common';
|
||||
import { ReactElement } from 'react';
|
||||
import { CallClientHandlers, createDefaultHandlersForComponent } from './createHandlers';
|
||||
import { DefaultHandlers, createDefaultHandlersForComponent } from './createHandlers';
|
||||
import { DeclarativeCallClient } from '@azure/acs-calling-declarative';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function TestCallClientComponent(props: CallClientHandlers): ReactElement | null {
|
||||
function TestCallClientComponent(props: DefaultHandlers): ReactElement | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -43,7 +43,7 @@ describe('createHandlers', () => {
|
|||
undefined,
|
||||
TestCallClientComponent
|
||||
);
|
||||
|
||||
console.log(handlers);
|
||||
expect(handlers).toBeDefined();
|
||||
expect(Object.keys(handlers).length > 0).toBe(true);
|
||||
});
|
||||
|
|
|
@ -5,75 +5,111 @@ import {
|
|||
Call,
|
||||
StartCallOptions,
|
||||
HangUpOptions,
|
||||
VideoDeviceInfo
|
||||
LocalVideoStream,
|
||||
CreateViewOptions
|
||||
} from '@azure/communication-calling';
|
||||
import { CommunicationUserIdentifier, PhoneNumberIdentifier, UnknownIdentifier } from '@azure/communication-common';
|
||||
import { DeclarativeCallClient } from '@azure/acs-calling-declarative';
|
||||
import { ReactElement } from 'react';
|
||||
import memoizeOne from 'memoize-one';
|
||||
|
||||
/**
|
||||
* Defines all handlers associated with {@Link @azure/communication-calling#CallClient}.
|
||||
*/
|
||||
export type CallClientHandlers = {
|
||||
getDeviceManager: () => Promise<DeviceManager>;
|
||||
};
|
||||
export type DefaultHandlers = ReturnType<typeof createDefaultHandlers>;
|
||||
|
||||
/**
|
||||
* Defines all handlers associated with {@Link @azure/communication-calling#CallAgent}.
|
||||
*/
|
||||
export type CallAgentHandlers = {
|
||||
onStartCall(
|
||||
participants: (CommunicationUserIdentifier | PhoneNumberIdentifier | UnknownIdentifier)[],
|
||||
options?: StartCallOptions
|
||||
): Call;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines all handlers associated with {@Link @azure/communication-calling#DeviceManager}.
|
||||
*/
|
||||
export type DeviceManagerHandlers = {
|
||||
getCameras(): Promise<VideoDeviceInfo[]>;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines all handlers associated with {@Link @azure/communication-calling#Call}.
|
||||
*/
|
||||
export type CallHandlers = {
|
||||
onHangUp(options?: HangUpOptions): Promise<void>;
|
||||
};
|
||||
|
||||
const createCallClientDefaultHandlers = memoizeOne(
|
||||
(declarativeCallClient: DeclarativeCallClient): CallClientHandlers => {
|
||||
return {
|
||||
getDeviceManager: () => declarativeCallClient.getDeviceManager()
|
||||
const createDefaultHandlers = memoizeOne(
|
||||
(
|
||||
callClient: DeclarativeCallClient,
|
||||
callAgent: CallAgent | undefined,
|
||||
deviceManager: DeviceManager | undefined,
|
||||
call: Call | undefined
|
||||
) => {
|
||||
const onStartLocalVideo = async (callId: string, deviceId: string, options: CreateViewOptions): Promise<void> => {
|
||||
if (!deviceManager) return;
|
||||
const devices = await deviceManager.getCameras();
|
||||
const selected = devices.find((device) => device.id === deviceId);
|
||||
if (!selected) return;
|
||||
const stream = new LocalVideoStream(selected);
|
||||
return callClient.startRenderVideo(callId, stream, options);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const createCallAgentDefaultHandlers = memoizeOne(
|
||||
(declarativeCallAgent: CallAgent): CallAgentHandlers => {
|
||||
return {
|
||||
onStartCall: (
|
||||
participants: (CommunicationUserIdentifier | PhoneNumberIdentifier | UnknownIdentifier)[],
|
||||
options?: StartCallOptions
|
||||
): Call => declarativeCallAgent.startCall(participants, options)
|
||||
const onStopLocalVideo = (callId: string): Promise<void> | void => {
|
||||
const call = callClient.state.calls.get(callId);
|
||||
const stream = call?.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video');
|
||||
if (!stream) return;
|
||||
return callClient.stopRenderVideo(callId, stream);
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const createDeviceManagerDefaultHandlers = memoizeOne(
|
||||
(declarativeDeviceManager: DeviceManager): DeviceManagerHandlers => {
|
||||
return {
|
||||
getCameras: (): Promise<VideoDeviceInfo[]> => declarativeDeviceManager.getCameras()
|
||||
const onToggleLocalVideo = (callId: string, videoDeviceInfo, options): Promise<void> | void => {
|
||||
const call = callClient.state.calls.get(callId);
|
||||
const stream = call?.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video');
|
||||
if (stream) {
|
||||
return onStopLocalVideo(callId);
|
||||
} else {
|
||||
return onStartLocalVideo(callId, videoDeviceInfo, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const createCallDefaultHandlers = memoizeOne(
|
||||
(declarativeCall: Call): CallHandlers => {
|
||||
const onStartCall = (
|
||||
participants: (CommunicationUserIdentifier | PhoneNumberIdentifier | UnknownIdentifier)[],
|
||||
options?: StartCallOptions
|
||||
): Call | undefined => {
|
||||
return callAgent ? callAgent.startCall(participants, options) : undefined;
|
||||
};
|
||||
|
||||
const onSelectMicrophone = async (deviceId: string): Promise<void | undefined> => {
|
||||
if (!deviceManager) return;
|
||||
const devices = await deviceManager.getMicrophones();
|
||||
const selected = devices.find((device) => device.id === deviceId);
|
||||
if (!selected) return;
|
||||
return deviceManager.selectMicrophone(selected);
|
||||
};
|
||||
|
||||
const onSelectSpeaker = async (deviceId: string): Promise<void | undefined> => {
|
||||
if (!deviceManager) return;
|
||||
const devices = await deviceManager.getSpeakers();
|
||||
const selected = devices.find((device) => device.id === deviceId);
|
||||
if (!selected) return;
|
||||
return deviceManager.selectMicrophone(selected);
|
||||
};
|
||||
|
||||
const onSelectCamera = async (deviceId: string): Promise<void | undefined> => {
|
||||
if (!call || !deviceManager) return;
|
||||
const devices = await deviceManager.getCameras();
|
||||
const selected = devices.find((device) => device.id === deviceId);
|
||||
const stream = call.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video');
|
||||
if (!selected || !stream) return;
|
||||
return stream.switchSource(selected);
|
||||
};
|
||||
|
||||
const onMute = (): Promise<void> | void => call?.mute();
|
||||
|
||||
const onUnmute = (): Promise<void> | void => call?.unmute();
|
||||
|
||||
const onToggleMicrophone = (): Promise<void> | void => (call?.isMuted ? call?.unmute() : call?.mute());
|
||||
|
||||
const onStartScreenShare = (): Promise<void> | void => call?.startScreenSharing();
|
||||
|
||||
const onStopScreenShare = (): Promise<void> | void => call?.stopScreenSharing();
|
||||
|
||||
const onToggleScreenShare = (): Promise<void> | void =>
|
||||
call?.isScreenSharingOn ? onStopScreenShare() : onStartScreenShare();
|
||||
|
||||
const onHangUp = (options?: HangUpOptions): Promise<void> | void => call?.hangUp(options);
|
||||
|
||||
return {
|
||||
onHangUp: (options?: HangUpOptions): Promise<void> => declarativeCall.hangUp(options)
|
||||
onHangUp,
|
||||
onMute,
|
||||
onUnmute,
|
||||
onSelectCamera,
|
||||
onSelectMicrophone,
|
||||
onSelectSpeaker,
|
||||
onStartCall,
|
||||
onStartLocalVideo,
|
||||
onStopLocalVideo,
|
||||
onStartScreenShare,
|
||||
onStopScreenShare,
|
||||
onToggleLocalVideo,
|
||||
onToggleMicrophone,
|
||||
onToggleScreenShare
|
||||
};
|
||||
}
|
||||
);
|
||||
|
@ -82,7 +118,7 @@ const createCallDefaultHandlers = memoizeOne(
|
|||
* Type guard for common properties between two types.
|
||||
*/
|
||||
export type CommonProperties<A, B> = {
|
||||
[P in keyof A & keyof B]: A[P] extends B[P] ? (A[P] extends B[P] ? P : never) : never;
|
||||
[P in keyof A & keyof B]: A[P] extends B[P] ? P : never;
|
||||
}[keyof A & keyof B];
|
||||
|
||||
type Common<A, B> = Pick<A, CommonProperties<A, B>>;
|
||||
|
@ -105,18 +141,5 @@ export const createDefaultHandlersForComponent = <Props>(
|
|||
callAgent: CallAgent | undefined,
|
||||
deviceManager: DeviceManager | undefined,
|
||||
call: Call | undefined,
|
||||
_: (props: Props) => ReactElement | null
|
||||
):
|
||||
| Common<CallClientHandlers & CallAgentHandlers & DeviceManagerHandlers & CallHandlers, Props>
|
||||
| Common<CallClientHandlers, Props> => {
|
||||
const callClientHandlers = createCallClientDefaultHandlers(declarativeCallClient);
|
||||
const callAgentHandlers = callAgent ? createCallAgentDefaultHandlers(callAgent) : undefined;
|
||||
const deviceManagerHandlers = deviceManager ? createDeviceManagerDefaultHandlers(deviceManager) : undefined;
|
||||
const callHandlers = call ? createCallDefaultHandlers(call) : undefined;
|
||||
return {
|
||||
...callClientHandlers,
|
||||
...callAgentHandlers,
|
||||
...deviceManagerHandlers,
|
||||
...callHandlers
|
||||
};
|
||||
};
|
||||
_Component: (props: Props) => ReactElement | null
|
||||
): Common<DefaultHandlers, Props> => createDefaultHandlers(declarativeCallClient, callAgent, deviceManager, call);
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
// © Microsoft Corporation. All rights reserved.
|
||||
export { createDefaultHandlersForComponent } from './handlers/createHandlers';
|
||||
export type { BaseSelectorProps } from './baseSelectors';
|
||||
export type {
|
||||
CallClientHandlers,
|
||||
CallAgentHandlers,
|
||||
DeviceManagerHandlers,
|
||||
CallHandlers,
|
||||
CommonProperties
|
||||
} from './handlers/createHandlers';
|
||||
export type { CommonProperties } from './handlers/createHandlers';
|
||||
export * from './callControlSelectors';
|
||||
|
|
Загрузка…
Ссылка в новой задаче