Skip to content

Commit

Permalink
Merge branch 'main' into vhuseinova/storybook-fixes-jan-27-2025
Browse files Browse the repository at this point in the history
  • Loading branch information
vhuseinova-msft authored Jan 29, 2025
2 parents 7fedc6d + 2bd2199 commit 1f15a0f
Show file tree
Hide file tree
Showing 28 changed files with 5,583 additions and 4,946 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "prerelease",
"area": "feature",
"workstream": "RTT",
"comment": "RTT API changes",
"packageName": "@azure/communication-react",
"email": "[email protected]",
"dependentChangeType": "patch"
}
24 changes: 23 additions & 1 deletion packages/calling-component-bindings/src/baseSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
import { TeamsIncomingCallState } from '@internal/calling-stateful-client';
import { ReactionState } from '@internal/calling-stateful-client';
import { CaptionsInfo } from '@internal/calling-stateful-client';

/* @conditional-compile-remove(rtt) */
import { RealTimeTextInfo } from '@internal/calling-stateful-client';
import { CaptionsKind } from '@azure/communication-calling';
import { RaisedHandState } from '@internal/calling-stateful-client';
import { SupportedCaptionLanguage, SupportedSpokenLanguage } from '@internal/react-components';
Expand Down Expand Up @@ -308,6 +309,27 @@ export const getAssignedBreakoutRoom = (
return state.calls[props.callId]?.breakoutRooms?.assignedBreakoutRoom;
};

/* @conditional-compile-remove(rtt) */
/** @private */
export const getRealTimeTextStatus = (state: CallClientState, props: CallingBaseSelectorProps): boolean | undefined => {
return state.calls[props.callId]?.realTimeTextFeature.isRealTimeTextFeatureActive;
};

/* @conditional-compile-remove(rtt) */
/** @private */
export const getRealTimeText = (
state: CallClientState,
props: CallingBaseSelectorProps
):
| {
completedMessages?: RealTimeTextInfo[];
currentInProgress?: RealTimeTextInfo[];
myInProgress?: RealTimeTextInfo;
}
| undefined => {
return state.calls[props.callId]?.realTimeTextFeature.realTimeTexts;
};

/* @conditional-compile-remove(together-mode) */
/**
* @private
Expand Down
147 changes: 142 additions & 5 deletions packages/calling-component-bindings/src/captionsSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// Licensed under the MIT License.

import { CallClientState, CaptionsInfo } from '@internal/calling-stateful-client';
/* @conditional-compile-remove(rtt) */
import { RealTimeTextInfo, RemoteParticipantState } from '@internal/calling-stateful-client';
import {
CallingBaseSelectorProps,
getDisplayName,
Expand All @@ -10,6 +12,8 @@ import {
getStartCaptionsInProgress,
getSupportedCaptionLanguages
} from './baseSelectors';
/* @conditional-compile-remove(rtt) */
import { getRealTimeTextStatus, getRealTimeText } from './baseSelectors';
import {
getCaptions,
getCaptionsStatus,
Expand All @@ -20,6 +24,8 @@ import {
import * as reselect from 'reselect';
import { toFlatCommunicationIdentifier } from '@internal/acs-ui-common';
import { CaptionsInformation, SupportedCaptionLanguage, SupportedSpokenLanguage } from '@internal/react-components';
/* @conditional-compile-remove(rtt) */
import { RealTimeTextInformation } from '@internal/react-components';

/**
* Selector type for the {@link StartCaptionsButton} component.
Expand Down Expand Up @@ -103,7 +109,18 @@ export type CaptionsBannerSelector = (
props: CallingBaseSelectorProps
) => {
captions: CaptionsInformation[];
/* @conditional-compile-remove(rtt) */
realTimeTexts: {
completedMessages?: RealTimeTextInformation[];
currentInProgress?: RealTimeTextInformation[];
myInProgress?: RealTimeTextInformation;
};
isCaptionsOn: boolean;
startCaptionsInProgress: boolean;
/* @conditional-compile-remove(rtt) */
isRealTimeTextOn: boolean;
/* @conditional-compile-remove(rtt) */
latestLocalRealTimeText: RealTimeTextInformation;
};

/**
Expand All @@ -112,8 +129,30 @@ export type CaptionsBannerSelector = (
* @public
*/
export const captionsBannerSelector: CaptionsBannerSelector = reselect.createSelector(
[getCaptions, getCaptionsStatus, getStartCaptionsInProgress, getRemoteParticipants, getDisplayName, getIdentifier],
(captions, isCaptionsFeatureActive, startCaptionsInProgress, remoteParticipants, displayName, identifier) => {
[
getCaptions,
/* @conditional-compile-remove(rtt) */
getRealTimeText,
getCaptionsStatus,
/* @conditional-compile-remove(rtt) */
getRealTimeTextStatus,
getStartCaptionsInProgress,
getRemoteParticipants,
getDisplayName,
getIdentifier
],
(
captions,
/* @conditional-compile-remove(rtt) */
realTimeTexts,
isCaptionsFeatureActive,
/* @conditional-compile-remove(rtt) */
isRealTimeTextActive,
startCaptionsInProgress,
remoteParticipants,
displayName,
identifier
) => {
const captionsInfo = captions?.map((c, index) => {
const userId = getCaptionsSpeakerIdentifier(c);
let finalDisplayName;
Expand All @@ -125,21 +164,119 @@ export const captionsBannerSelector: CaptionsBannerSelector = reselect.createSel
finalDisplayName = participant.displayName;
}
}

return {
id: (finalDisplayName ?? 'Unnamed Participant') + index,
displayName: finalDisplayName ?? 'Unnamed Participant',
captionText: c.captionText ?? '',
userId
captionText: c.captionText,
userId,
createdTimeStamp: c.timestamp
};
});
/* @conditional-compile-remove(rtt) */
const completedRealTimeTexts = realTimeTexts?.completedMessages
?.filter((rtt) => rtt.message !== '')
.map((rtt) => {
const userId = getRealTimeTextSpeakerIdentifier(rtt);
return {
id: rtt.id,
displayName: getRealTimeTextDisplayName(rtt, identifier, remoteParticipants, displayName, userId),
message: rtt.message,
userId,
isTyping: rtt.resultType === 'Partial',
isMe: rtt.isMe,
finalizedTimeStamp: rtt.updatedTimestamp
};
});
/* @conditional-compile-remove(rtt) */
const inProgressRealTimeTexts = realTimeTexts?.currentInProgress
?.filter((rtt) => rtt.message !== '')
.map((rtt) => {
const userId = getRealTimeTextSpeakerIdentifier(rtt);
return {
id: rtt.id,
displayName: getRealTimeTextDisplayName(rtt, identifier, remoteParticipants, displayName, userId),
message: rtt.message,
userId,
isTyping: rtt.resultType === 'Partial',
isMe: rtt.isMe,
finalizedTimeStamp: rtt.updatedTimestamp
};
});
/* @conditional-compile-remove(rtt) */
const myInProgress =
realTimeTexts?.myInProgress && realTimeTexts.myInProgress.message !== ''
? {
id: realTimeTexts.myInProgress.id,
displayName: displayName,
message: realTimeTexts.myInProgress.message,
userId: identifier,
isTyping: realTimeTexts.myInProgress.resultType === 'Partial',
isMe: true,
finalizedTimeStamp: realTimeTexts.myInProgress.updatedTimestamp
}
: undefined;

/* @conditional-compile-remove(rtt) */
// find the last final local real time text caption if myInProgress is not available
let latestLocalRealTimeText;
/* @conditional-compile-remove(rtt) */
if (!myInProgress) {
latestLocalRealTimeText =
realTimeTexts &&
realTimeTexts.completedMessages &&
realTimeTexts.completedMessages
.slice()
.reverse()
.find((rtt) => rtt.isMe);
}

return {
captions: captionsInfo ?? [],
/* @conditional-compile-remove(rtt) */
realTimeTexts: {
completedMessages: completedRealTimeTexts as RealTimeTextInformation[],
currentInProgress: inProgressRealTimeTexts as RealTimeTextInformation[],
myInProgress: myInProgress as RealTimeTextInformation
},
isCaptionsOn: isCaptionsFeatureActive ?? false,
startCaptionsInProgress: startCaptionsInProgress ?? false
startCaptionsInProgress: startCaptionsInProgress ?? false,
/* @conditional-compile-remove(rtt) */
isRealTimeTextOn: isRealTimeTextActive ?? false,
/* @conditional-compile-remove(rtt) */
latestLocalRealTimeText: (myInProgress ?? latestLocalRealTimeText) as RealTimeTextInformation
};
}
);

const getCaptionsSpeakerIdentifier = (captions: CaptionsInfo): string => {
return captions.speaker.identifier ? toFlatCommunicationIdentifier(captions.speaker.identifier) : '';
};
/* @conditional-compile-remove(rtt) */
const getRealTimeTextSpeakerIdentifier = (realTimeText: RealTimeTextInfo): string => {
return realTimeText.sender.identifier ? toFlatCommunicationIdentifier(realTimeText.sender.identifier) : '';
};

/* @conditional-compile-remove(rtt) */
const getRealTimeTextDisplayName = (
realTimeText: RealTimeTextInfo,
identifier: string,
remoteParticipants:
| {
[keys: string]: RemoteParticipantState;
}
| undefined,
displayName: string | undefined,
userId: string
): string => {
let finalDisplayName;
if (userId === identifier) {
finalDisplayName = displayName;
} else if (remoteParticipants) {
const participant = remoteParticipants[userId];
if (participant) {
finalDisplayName = participant.displayName;
}
}
return finalDisplayName ?? 'Unnamed Participant';
};
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ export interface CommonCallingHandlers {
onStopNoiseSuppressionEffect: () => Promise<void>;
onStartCaptions: (options?: CaptionsOptions) => Promise<void>;
onStopCaptions: () => Promise<void>;
/* @conditional-compile-remove(rtt) */
onSendRealTimeText: (text: string, isFinalized: boolean) => Promise<void>;
onSetSpokenLanguage: (language: string) => Promise<void>;
onSetCaptionLanguage: (language: string) => Promise<void>;
onSubmitSurvey(survey: CallSurvey): Promise<CallSurveyResponse | undefined>;
Expand Down Expand Up @@ -735,7 +737,11 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
const captionsFeature = call?.feature(Features.Captions).captions as TeamsCaptions;
await captionsFeature.setCaptionLanguage(language);
};

/* @conditional-compile-remove(rtt) */
const onSendRealTimeText = async (text: string, isFinalized: boolean): Promise<void> => {
const realTimeTextFeature = call?.feature(Features.RealTimeText);
await realTimeTextFeature?.sendRealTimeText(text, isFinalized);
};
const onSubmitSurvey = async (survey: CallSurvey): Promise<CallSurveyResponse | undefined> =>
await call?.feature(Features.CallSurvey).submitSurvey(survey);
const onStartSpotlight = async (userIds?: string[]): Promise<void> => {
Expand Down Expand Up @@ -946,7 +952,9 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
/* @conditional-compile-remove(media-access) */
onForbidOthersVideo,
/* @conditional-compile-remove(media-access) */
onPermitOthersVideo
onPermitOthersVideo,
/* @conditional-compile-remove(rtt) */
onSendRealTimeText
};
}
);
69 changes: 67 additions & 2 deletions packages/calling-stateful-client/src/CallClientState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ import type {
DiagnosticQuality,
DiagnosticFlag
} from '@azure/communication-calling';
/* @conditional-compile-remove(rtt) */
import { ParticipantInfo, RealTimeTextResultType } from '@azure/communication-calling';
import { TeamsCallInfo } from '@azure/communication-calling';
import { CallInfo } from '@azure/communication-calling';
import { CapabilitiesChangeInfo, ParticipantCapabilities } from '@azure/communication-calling';
Expand Down Expand Up @@ -82,6 +84,10 @@ export interface CaptionsInfo {
* Timestamp of when the captioned words were initially spoken.
*/
timestamp: Date;
/**
* Timestamp of when the captions were last updated.
*/
lastUpdatedTimestamp?: Date;
/**
* The language that the captions are presented in. Corresponds to the captionLanguage specified in startCaptions / setCaptionLanguage.
*/
Expand All @@ -92,6 +98,42 @@ export interface CaptionsInfo {
spokenText?: string;
}

/* @conditional-compile-remove(rtt) */
/**
* @beta
*/
export interface RealTimeTextInfo {
/**
* The sequence id of the real time text.
*/
id: number;
/**
* The sender of the real time text.
*/
sender: ParticipantInfo;
/**
* The real time text message.
*/
message: string;
/**
* The result type of the real time text message.
*/
resultType: RealTimeTextResultType;
/**
* The timestamp when the real time text message was created.
*/
receivedTimestamp?: Date;
/**
* The timestamp when the real time text message was last updated.
*/
updatedTimestamp?: Date;
/**
* If message originated from the local participant
* default is false
*/
isMe?: boolean;
}

/**
* @public
*/
Expand Down Expand Up @@ -124,13 +166,31 @@ export interface CaptionsCallFeatureState {
* current caption language
*/
currentCaptionLanguage: string;

/**
* current caption kind: teams or acs captions
*/
captionsKind: CaptionsKind;
}

/* @conditional-compile-remove(rtt) */
/**
* @beta
*/
export interface RealTimeTextCallFeatureState {
/**
* array of received captions
*/
realTimeTexts: {
completedMessages?: RealTimeTextInfo[];
currentInProgress?: RealTimeTextInfo[];
myInProgress?: RealTimeTextInfo;
};
/**
* whether real time text is on/off
*/
isRealTimeTextFeatureActive?: boolean;
}

/**
* State only version of {@link @azure/communication-calling#TranscriptionCallFeature}. {@link StatefulCallClient} will
* automatically listen for transcription state of the call and update the state exposed by {@link StatefulCallClient}
Expand Down Expand Up @@ -639,9 +699,14 @@ export interface CallState {
*/
transcription: TranscriptionCallFeatureState;
/**
* Proxy of {@link @azure/communication-calling#TranscriptionCallFeature}.
* Proxy of {@link @azure/communication-calling#CaptionsCallFeature}.
*/
captionsFeature: CaptionsCallFeatureState;
/* @conditional-compile-remove(rtt) */
/**
* Proxy of {@link @azure/communication-calling#RealTimeTextCallFeature}.
*/
realTimeTextFeature: RealTimeTextCallFeatureState;
/**
* Proxy of {@link @azure/communication-calling#OptimalVideoCountCallFeature}.
*/
Expand Down
Loading

0 comments on commit 1f15a0f

Please sign in to comment.