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

RTT #5522

Draft
wants to merge 34 commits into
base: main
Choose a base branch
from
Draft

RTT #5522

Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1044cc4
rtt
carocao-msft Dec 23, 2024
f50f120
rtt
carocao-msft Dec 23, 2024
14d3751
RTt update with captions
carocao-msft Dec 24, 2024
b5e37bb
update
carocao-msft Dec 24, 2024
d8acf9d
test
carocao-msft Dec 25, 2024
a2db45c
update
carocao-msft Dec 25, 2024
bfd9368
add missing cc
carocao-msft Dec 25, 2024
b1c48c9
Merge branch 'main' into carocao/RTTWork
carocao-msft Dec 27, 2024
1196c52
RTT disclosure banner
carocao-msft Dec 30, 2024
a4e8b97
Change files
carocao-msft Dec 30, 2024
db84dd4
rename
carocao-msft Dec 30, 2024
f5bbe8f
Change files
carocao-msft Dec 30, 2024
a84ff25
Merge branch 'carocao/rttbanner' into carocao/RTTWork
carocao-msft Dec 30, 2024
73176d8
Merge branch 'carocao/rename' into carocao/RTTWork
carocao-msft Dec 30, 2024
e4a814b
Update
carocao-msft Dec 30, 2024
9f98ea1
Adapter event
carocao-msft Dec 31, 2024
c60f37d
update
carocao-msft Dec 31, 2024
8286bbb
Revert "update"
carocao-msft Dec 31, 2024
07a95f6
update
carocao-msft Dec 31, 2024
24ede3e
cc
carocao-msft Dec 31, 2024
c34b4a0
Merge branch 'main' into carocao/RTTWork
carocao-msft Dec 31, 2024
3e818ec
update
carocao-msft Jan 6, 2025
467bf1e
fix build
carocao-msft Jan 6, 2025
3242fb7
Update
carocao-msft Jan 7, 2025
ba3587d
update
carocao-msft Jan 8, 2025
399f656
Merge branch 'main' into carocao/RTTWork
carocao-msft Jan 10, 2025
27f3061
update
carocao-msft Jan 11, 2025
6e73f34
cc bug
carocao-msft Jan 11, 2025
17f8989
update
carocao-msft Jan 11, 2025
02434ea
Merge branch 'main' into carocao/RTTWork
carocao-msft Jan 11, 2025
662ba57
Merge branch 'main' into carocao/RTTWork
carocao-msft Jan 13, 2025
22805b1
update
carocao-msft Jan 14, 2025
0f2ab34
Merge branch 'main' into carocao/RTTWork
carocao-msft Jan 15, 2025
afd0c82
udpate api
carocao-msft Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -306,3 +307,24 @@ export const getAssignedBreakoutRoom = (
): BreakoutRoom | undefined => {
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;
};
151 changes: 144 additions & 7 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,10 +129,32 @@ 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);
const userId = getCaptionsSpeakerIdentifier(c as CaptionsInfo);
let finalDisplayName;
if (userId === identifier) {
finalDisplayName = displayName;
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 ?? [],
captions: (captionsInfo as CaptionsInformation[]) ?? [],
/* @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, finalized: 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, finalized: boolean): Promise<void> => {
const realTimeTextFeature = call?.feature(Features.RealTimeText);
await realTimeTextFeature?.sendRealTimeText(text, finalized);
};
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 @@ -947,7 +953,9 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
/* @conditional-compile-remove(media-access) */
onForbidOthersVideo,
/* @conditional-compile-remove(media-access) */
onPermitOthersVideo
onPermitOthersVideo,
/* @conditional-compile-remove(rtt) */
onSendRealTimeText
};
}
);
68 changes: 66 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;
carocao-msft marked this conversation as resolved.
Show resolved Hide resolved
/**
* The language that the captions are presented in. Corresponds to the captionLanguage specified in startCaptions / setCaptionLanguage.
*/
Expand All @@ -92,6 +98,41 @@ 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
*/
isMe: boolean;
}

/**
* @public
*/
Expand Down Expand Up @@ -124,13 +165,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 @@ -641,9 +700,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
Loading