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 13 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 @@ -511,6 +512,7 @@ export interface CallAdapterSubscribers {
off(event: 'capabilitiesChanged', listener: CapabilitiesChangedListener): void;
off(event: 'roleChanged', listener: PropertyChangedEvent): void;
off(event: 'spotlightChanged', listener: SpotlightChangedListener): void;
off(event: 'CaptionsKindChanged', listener: PropertyChangedEvent): void;
on(event: 'participantsJoined', listener: ParticipantsJoinedListener): void;
on(event: 'participantsLeft', listener: ParticipantsLeftListener): void;
on(event: 'isMutedChanged', listener: IsMutedChangedListener): void;
Expand All @@ -531,6 +533,7 @@ export interface CallAdapterSubscribers {
on(event: 'capabilitiesChanged', listener: CapabilitiesChangedListener): void;
on(event: 'roleChanged', listener: PropertyChangedEvent): void;
on(event: 'spotlightChanged', listener: SpotlightChangedListener): void;
on(event: 'CaptionsKindChanged', listener: PropertyChangedEvent): void;
}

// @public
Expand Down Expand Up @@ -1295,6 +1298,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 @@ -1070,6 +1086,8 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
on(event: 'roleChanged', listener: PropertyChangedEvent): void;
/* @conditional-compile-remove(spotlight) */
on(event: 'spotlightChanged', listener: SpotlightChangedListener): void;
/* @conditional-compile-remove(acs-close-captions) */
on(event: 'CaptionsKindChanged', listener: PropertyChangedEvent): void;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public on(event: string, listener: (e: any) => void): void {
Expand All @@ -1078,42 +1096,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 +1164,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 +1178,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 +1196,17 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
});
};

/* @conditional-compile-remove(acs-close-captions) */
private captionsKindChanged(): void {
this.emitter.emit('CaptionsKindChanged', {});
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 Expand Up @@ -1298,6 +1341,8 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
off(event: 'roleChanged', listener: PropertyChangedEvent): void;
/* @conditional-compile-remove(spotlight) */
off(event: 'spotlightChanged', listener: SpotlightChangedListener): void;
/* @conditional-compile-remove(acs-close-captions) */
off(event: 'CaptionsKindChanged', listener: PropertyChangedEvent): void;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
public off(event: string, listener: (e: any) => void): void {
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 Expand Up @@ -887,6 +894,11 @@ export interface CallAdapterSubscribers {
* Subscribe function for 'spotlightChanged' event.
*/
on(event: 'spotlightChanged', listener: SpotlightChangedListener): void;
/* @conditional-compile-remove(acs-close-captions) */
/**
* Subscribe function for 'CaptionsKindChanged' event.
*/
on(event: 'CaptionsKindChanged', listener: PropertyChangedEvent): void;

/**
* Unsubscribe function for 'participantsJoined' event.
Expand Down Expand Up @@ -970,6 +982,11 @@ export interface CallAdapterSubscribers {
* Subscribe function for 'spotlightChanged' event.
*/
off(event: 'spotlightChanged', listener: SpotlightChangedListener): void;
/* @conditional-compile-remove(acs-close-captions) */
/**
* Unsubscribe function for 'CaptionsKindChanged' event.
*/
off(event: 'CaptionsKindChanged', listener: PropertyChangedEvent): void;
}

// This type remains for non-breaking change reason
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