Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Captions] captions adhoc #4579

Merged
merged 17 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "minor",
"area": "fix",
"workstream": "Captions",
"comment": "Allow captions to be used when in a adhoc teams call",
"packageName": "@azure/communication-react",
"email": "dmceachern@microsoft.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "minor",
"area": "fix",
"workstream": "Captions",
"comment": "Allow captions to be used when in a adhoc teams call",
"packageName": "@azure/communication-react",
"email": "dmceachern@microsoft.com",
"dependentChangeType": "patch"
}
30 changes: 26 additions & 4 deletions packages/calling-stateful-client/src/CallDeclarativeCommon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { CallContext } from './CallContext';
import { CallCommon } from './BetaToStableTypes';
import { Features } from '@azure/communication-calling';
/* @conditional-compile-remove(acs-close-captions) */
import { PropertyChangedEvent, CaptionsCallFeature } from '@azure/communication-calling';
/* @conditional-compile-remove(acs-close-captions) */
import { Captions } from '@azure/communication-calling';
import { TeamsCaptions } from '@azure/communication-calling';
import { TransferCallFeature, TransferAcceptedEvent, TransferEventArgs } from '@azure/communication-calling';
Expand Down Expand Up @@ -80,15 +82,35 @@ export abstract class ProxyCallCommon implements ProxyHandler<CallCommon> {
// these are mini version of Proxy object - if it grows too big, a real Proxy object should be used.
return this._context.withErrorTeedToState((...args: Parameters<CallCommon['feature']>) => {
if (args[0] === Features.Captions) {
const captionsFeature = target.feature(Features.Captions).captions;
const captionsFeature = target.feature(Features.Captions);
let proxyFeature;
/* @conditional-compile-remove(acs-close-captions) */
if (captionsFeature.kind === 'Captions') {
if (captionsFeature.captions.kind === 'Captions') {
proxyFeature = new ProxyCaptions(this._context, target);
return { captions: new Proxy(captionsFeature, proxyFeature) };
return {
captions: new Proxy(captionsFeature.captions, proxyFeature),
on: (...args: Parameters<CaptionsCallFeature['on']>): void => {
const isCaptionsKindChanged = args[0] === 'CaptionsKindChanged';
if (isCaptionsKindChanged) {
const listener = args[1] as PropertyChangedEvent;
const newListener = (): void => {
listener();
};
return captionsFeature.on('CaptionsKindChanged', newListener);
}
},
off: (...args: Parameters<CaptionsCallFeature['off']>): void => {
const isCaptionsKindChanged = args[0] === 'CaptionsKindChanged';
if (isCaptionsKindChanged) {
return captionsFeature.off('CaptionsKindChanged', args[1]);
}
}
};
}
proxyFeature = new ProxyTeamsCaptions(this._context, target);
return { captions: new Proxy(captionsFeature, proxyFeature) };
return {
captions: new Proxy(captionsFeature.captions, proxyFeature)
};
}
if (args[0] === Features.Transfer) {
const transferFeature = target.feature(Features.Transfer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,7 @@ export type CallAdapterClientState = {
devices: DeviceManagerState;
endedCall?: CallState;
isTeamsCall: boolean;
isTeamsMeeting: boolean;
isRoomsCall: boolean;
latestErrors: AdapterErrors;
alternateCallerId?: string;
Expand Down Expand Up @@ -1295,6 +1296,7 @@ export interface CallWithChatClientState {
environmentInfo?: EnvironmentInfo;
hideAttendeeNames?: boolean;
isTeamsCall: boolean;
isTeamsMeeting: boolean;
latestCallErrors: AdapterErrors;
latestChatErrors: AdapterErrors;
onResolveVideoEffectDependency?: () => Promise<VideoBackgroundEffectsDependency>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ export type CallAdapterClientState = {
devices: DeviceManagerState;
endedCall?: CallState;
isTeamsCall: boolean;
isTeamsMeeting: boolean;
isRoomsCall: boolean;
latestErrors: AdapterErrors;
cameraStatus?: 'On' | 'Off';
Expand Down Expand Up @@ -1028,6 +1029,7 @@ export interface CallWithChatClientState {
displayName: string | undefined;
hideAttendeeNames?: boolean;
isTeamsCall: boolean;
isTeamsMeeting: boolean;
latestCallErrors: AdapterErrors;
latestChatErrors: AdapterErrors;
onResolveVideoEffectDependency?: () => Promise<VideoBackgroundEffectsDependency>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ const createDefaultCallAdapterState = (role?: ParticipantRole): CallAdapterState
deviceAccess: { video: true, audio: true }
},
isTeamsCall: false,
isTeamsMeeting: false,
isRoomsCall: false,
latestErrors: {}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ import {
MicrosoftTeamsUserIdentifier,
isMicrosoftTeamsUserIdentifier,
MicrosoftTeamsAppIdentifier,
UnknownIdentifier
UnknownIdentifier,
isMicrosoftTeamsAppIdentifier
} from '@azure/communication-common';
/* @conditional-compile-remove(teams-identity-support) */ /* @conditional-compile-remove(PSTN-calls) */
import { isCommunicationUserIdentifier } from '@azure/communication-common';
Expand Down Expand Up @@ -140,6 +141,7 @@ class CallContext {
constructor(
clientState: CallClientState,
isTeamsCall: boolean,
isTeamsMeeting: boolean,
isRoomsCall: boolean,
options?: {
maxListeners?: number;
Expand All @@ -164,6 +166,7 @@ class CallContext {
page: 'configuration',
latestErrors: clientState.latestErrors,
isTeamsCall,
isTeamsMeeting,
isRoomsCall,
/* @conditional-compile-remove(PSTN-calls) */ alternateCallerId: clientState.alternateCallerId,
/* @conditional-compile-remove(unsupported-browser) */ environmentInfo: clientState.environmentInfo,
Expand Down Expand Up @@ -398,12 +401,25 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
const isTeamsMeeting = this.locator
? 'meetingLink' in this.locator || /* @conditional-compile-remove(meeting-id) */ 'meetingId' in this.locator
: false;
let isTeamsCall: boolean | undefined;
this.targetCallees?.forEach((callee) => {
if (isMicrosoftTeamsUserIdentifier(callee) || isMicrosoftTeamsAppIdentifier(callee)) {
isTeamsCall = true;
}
});

const isRoomsCall = this.locator ? 'roomId' in this.locator : false;

this.onResolveVideoBackgroundEffectsDependency = options?.videoBackgroundOptions?.onResolveDependency;

this.context = new CallContext(callClient.getState(), isTeamsMeeting, isRoomsCall, options, this.targetCallees);
this.context = new CallContext(
callClient.getState(),
!!isTeamsCall,
isTeamsMeeting,
isRoomsCall,
options,
this.targetCallees
);

this.context.onCallEnded((endCallData) => this.emitter.emit('callEnded', endCallData));

Expand Down Expand Up @@ -1078,42 +1094,50 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea

private subscribeToCaptionEvents(): void {
if (this.call && this.call.state === 'Connected') {
if (this.context.getState().isTeamsCall) {
const captionsFeature = this.call?.feature(Features.Captions).captions as TeamsCaptions;
captionsFeature.on('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
captionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
captionsFeature.on('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
captionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
const captionsFeature = this.call?.feature(Features.Captions);
if (
captionsFeature.captions.kind === 'TeamsCaptions' &&
(this.context.getState().isTeamsCall || this.context.getState().isTeamsMeeting)
) {
const teamsCaptionsFeature = captionsFeature.captions as TeamsCaptions;
teamsCaptionsFeature.on('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
teamsCaptionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
teamsCaptionsFeature.on('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
teamsCaptionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
} else {
/* @conditional-compile-remove(acs-close-captions) */
const captionsFeature = this.call?.feature(Features.Captions).captions as Captions;
const acsCaptionsFeature = captionsFeature.captions as Captions;
/* @conditional-compile-remove(acs-close-captions) */
captionsFeature.on('CaptionsReceived', this.captionsReceived.bind(this));
acsCaptionsFeature.on('CaptionsReceived', this.captionsReceived.bind(this));
/* @conditional-compile-remove(acs-close-captions) */
captionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
acsCaptionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
/* @conditional-compile-remove(acs-close-captions) */
captionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
acsCaptionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
}
}
}

private unsubscribeFromCaptionEvents(): void {
if (this.call && this.call.state === 'Connected') {
if (this.context.getState().isTeamsCall) {
const captionsFeature = this.call?.feature(Features.Captions).captions as TeamsCaptions;
captionsFeature.off('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
captionsFeature.off('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
captionsFeature.off('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
captionsFeature.off('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
const captionsFeature = this.call?.feature(Features.Captions);
if (
captionsFeature.captions.kind === 'TeamsCaptions' &&
(this.context.getState().isTeamsCall || this.context.getState().isTeamsMeeting)
) {
const teamsCaptionsFeature = captionsFeature.captions as TeamsCaptions;
teamsCaptionsFeature.off('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
teamsCaptionsFeature.off('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
teamsCaptionsFeature.off('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
teamsCaptionsFeature.off('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
} else {
/* @conditional-compile-remove(acs-close-captions) */
const captionsFeature = this.call?.feature(Features.Captions).captions as Captions;
const acsCaptionsFeature = captionsFeature.captions as Captions;
/* @conditional-compile-remove(acs-close-captions) */
captionsFeature.off('CaptionsReceived', this.captionsReceived.bind(this));
acsCaptionsFeature.off('CaptionsReceived', this.captionsReceived.bind(this));
/* @conditional-compile-remove(acs-close-captions) */
captionsFeature.off('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
acsCaptionsFeature.off('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
/* @conditional-compile-remove(acs-close-captions) */
captionsFeature.off('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
acsCaptionsFeature.off('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
}
this.call?.off('stateChanged', this.subscribeToCaptionEvents.bind(this));
}
Expand All @@ -1138,6 +1162,8 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
this.call?.feature(Features.Capabilities).on('capabilitiesChanged', this.capabilitiesChanged.bind(this));
/* @conditional-compile-remove(spotlight) */
this.call?.feature(Features.Spotlight).on('spotlightChanged', this.spotlightChanged.bind(this));
/* @conditional-compile-remove(acs-close-captions) */
this.call?.feature(Features.Captions).on('CaptionsKindChanged', this.captionsKindChanged.bind(this));
}

private unsubscribeCallEvents(): void {
Expand All @@ -1150,6 +1176,10 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
this.call?.off('isScreenSharingOnChanged', this.isScreenSharingOnChanged.bind(this));
this.call?.off('idChanged', this.callIdChanged.bind(this));
this.call?.off('roleChanged', this.roleChanged.bind(this));
/* @conditional-compile-remove(acs-close-captions) */
if (this.call?.feature(Features.Captions).captions.kind === 'Captions') {
this.call?.feature(Features.Captions).off('CaptionsKindChanged', this.unsubscribeFromCaptionEvents.bind(this));
}

this.unsubscribeFromCaptionEvents();
if (this.callingSoundSubscriber) {
Expand All @@ -1164,6 +1194,16 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
});
};

/* @conditional-compile-remove(acs-close-captions) */
private captionsKindChanged(): void {
const captionsFeature = this.call?.feature(Features.Captions);
const teamsCaptionsFeature = captionsFeature?.captions as TeamsCaptions;
teamsCaptionsFeature.on('CaptionsReceived', this.teamsCaptionsReceived.bind(this));
teamsCaptionsFeature.on('CaptionsActiveChanged', this.isCaptionsActiveChanged.bind(this));
teamsCaptionsFeature.on('CaptionLanguageChanged', this.isCaptionLanguageChanged.bind(this));
teamsCaptionsFeature.on('SpokenLanguageChanged', this.isSpokenLanguageChanged.bind(this));
}

private onRemoteParticipantsUpdated({
added,
removed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,14 @@ export type CallAdapterClientState = {
targetCallees?: CommunicationIdentifier[];
devices: DeviceManagerState;
endedCall?: CallState;
/**
* State to track whether the call is a teams call.
*/
isTeamsCall: boolean;
/**
* State to track whether the call is a teams meeting.
*/
isTeamsMeeting: boolean;
/**
* State to track whether the call is a rooms call.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@ import { useSelector } from '../hooks/useSelector';
import { callStatusSelector } from '../selectors/callStatusSelector';
import { CallControlOptions } from '../types/CallControlOptions';
import { PreparedMoreDrawer } from '../../common/Drawer/PreparedMoreDrawer';
import { getRemoteParticipants } from '../selectors/baseSelectors';
import { getIsTeamsMeeting, getRemoteParticipants } from '../selectors/baseSelectors';
/* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */
import { getPage } from '../selectors/baseSelectors';
import { getCallStatus, getIsTeamsCall, getCaptionsStatus } from '../selectors/baseSelectors';
import { getCallStatus, getCaptionsStatus } from '../selectors/baseSelectors';
import { drawerContainerStyles } from '../styles/CallComposite.styles';
import { SidePane } from './SidePane/SidePane';
import { usePeoplePane } from './SidePane/usePeoplePane';
Expand Down Expand Up @@ -86,7 +86,7 @@ import {
useStopAllSpotlightCallbackWithPrompt
} from '../utils/spotlightUtils';
/* @conditional-compile-remove(acs-close-captions) */
import { getCaptionsKind } from '../selectors/baseSelectors';
import { getCaptionsKind, getIsTeamsCall } from '../selectors/baseSelectors';

/**
* @private
Expand Down Expand Up @@ -349,8 +349,13 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {

/* @conditional-compile-remove(acs-close-captions) */
const isTeamsCaptions = useSelector(getCaptionsKind) === 'TeamsCaptions';
const isTeamsMeeting = useSelector(getIsTeamsMeeting);
/* @conditional-compile-remove(acs-close-captions) */
const isTeamsCall = useSelector(getIsTeamsCall);
const useTeamsCaptions =
useSelector(getIsTeamsCall) || /* @conditional-compile-remove(acs-close-captions) */ isTeamsCaptions;
isTeamsMeeting ||
/* @conditional-compile-remove(acs-close-captions) */ isTeamsCall ||
/* @conditional-compile-remove(acs-close-captions) */ isTeamsCaptions;
const hasJoinedCall = useSelector(getCallStatus) === 'Connected';
const isCaptionsOn = useSelector(getCaptionsStatus);
const minMaxDragPosition = useMinMaxDragPosition(props.modalLayerHostId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,11 @@ export const getSupportedSpokenLanguages = (state: CallAdapterState): string[] |
*/
export const getIsTeamsCall = (state: CallAdapterState): boolean => state.isTeamsCall;

/**
* @private
*/
export const getIsTeamsMeeting = (state: CallAdapterState): boolean => state.isTeamsMeeting;

/**
* @private
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ function createMockCallWithChatAdapter(): CallWithChatAdapter {
speakers: [],
unparentedViews: []
},
isTeamsCall: true,
isTeamsCall: false,
isTeamsMeeting: true,
call: undefined,
chat: undefined,
latestCallErrors: { test: new Error() as AdapterError },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ function callAdapterStateFromCallWithChatAdapterState(
call: callWithChatAdapterState.call,
devices: callWithChatAdapterState.devices,
isTeamsCall: callWithChatAdapterState.isTeamsCall,
isTeamsMeeting: callWithChatAdapterState.isTeamsMeeting,
isRoomsCall: false,
latestErrors: callWithChatAdapterState.latestCallErrors,
/* @conditional-compile-remove(PSTN-calls) */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface CallWithChatClientState {
devices: DeviceManagerState;
/** State of whether the active call is a Teams interop call */
isTeamsCall: boolean;
/** State of whether the active call is a Teams interop meeting */
isTeamsMeeting: boolean;
/* @conditional-compile-remove(PSTN-calls) */
/** alternateCallerId for PSTN call */
alternateCallerId?: string | undefined;
Expand Down Expand Up @@ -124,6 +126,7 @@ export function callWithChatAdapterStateFromBackingStates(callAdapter: CallAdapt
devices: callAdapterState.devices,
isLocalPreviewMicrophoneEnabled: callAdapterState.isLocalPreviewMicrophoneEnabled,
isTeamsCall: callAdapterState.isTeamsCall,
isTeamsMeeting: callAdapterState.isTeamsMeeting,
latestCallErrors: callAdapterState.latestErrors,
latestChatErrors: {},
/* @conditional-compile-remove(attachment-upload) */
Expand Down Expand Up @@ -174,6 +177,7 @@ export function mergeCallAdapterStateIntoCallWithChatAdapterState(
call: callAdapterState.call,
isLocalPreviewMicrophoneEnabled: callAdapterState.isLocalPreviewMicrophoneEnabled,
isTeamsCall: callAdapterState.isTeamsCall,
isTeamsMeeting: callAdapterState.isTeamsMeeting,
latestCallErrors: callAdapterState.latestErrors,

videoBackgroundImages: callAdapterState.videoBackgroundImages,
Expand Down