agora-appbuilder-core 4.1.10-beta.1 → 4.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/template/agora-rn-uikit/src/Utils/isBotUser.ts +1 -1
- package/template/android/app/build.gradle +0 -7
- package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
- package/template/bridge/rtm/web/Types.ts +0 -183
- package/template/bridge/rtm/web/index.ts +488 -450
- package/template/customization-api/typeDefinition.ts +0 -1
- package/template/defaultConfig.js +3 -4
- package/template/global.d.ts +0 -1
- package/template/ios/Podfile +0 -41
- package/template/package.json +5 -5
- package/template/src/AppRoutes.tsx +3 -3
- package/template/src/ai-agent/components/ControlButtons.tsx +1 -1
- package/template/src/assets/font-styles.css +1 -33
- package/template/src/assets/fonts/icomoon.ttf +0 -0
- package/template/src/assets/selection.json +1 -1
- package/template/src/atoms/ActionMenu.tsx +93 -13
- package/template/src/atoms/CustomIcon.tsx +1 -8
- package/template/src/atoms/DropDownMulti.tsx +80 -29
- package/template/src/atoms/Dropdown.tsx +0 -5
- package/template/src/atoms/Input.tsx +2 -1
- package/template/src/atoms/TertiaryButton.tsx +1 -1
- package/template/src/atoms/UserAvatar.tsx +1 -1
- package/template/src/components/ChatContext.ts +3 -5
- package/template/src/components/Controls.tsx +167 -208
- package/template/src/components/DeviceConfigure.tsx +1 -1
- package/template/src/components/EventsConfigure.tsx +168 -118
- package/template/src/components/Navbar.tsx +11 -14
- package/template/src/components/RTMConfigure.tsx +819 -32
- package/template/src/components/beauty-effect/useBeautyEffects.tsx +13 -50
- package/template/src/components/chat/chatConfigure.tsx +1 -7
- package/template/src/components/chat-messages/useChatMessages.tsx +11 -43
- package/template/src/components/controls/useControlPermissionMatrix.tsx +4 -32
- package/template/src/components/participants/AllHostParticipants.tsx +2 -10
- package/template/src/components/participants/Participant.tsx +1 -7
- package/template/src/components/participants/UserActionMenuOptions.tsx +2 -12
- package/template/src/components/precall/joinCallBtn.native.tsx +7 -2
- package/template/src/components/precall/joinCallBtn.tsx +7 -2
- package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +16 -15
- package/template/src/components/precall/joinWaitingRoomBtn.tsx +31 -17
- package/template/src/components/precall/textInput.tsx +45 -22
- package/template/src/components/precall/usePreCall.tsx +7 -0
- package/template/src/components/recordings/RecordingsDateTable.tsx +2 -3
- package/template/src/components/room-info/useRoomInfo.tsx +5 -0
- package/template/src/components/useUserPreference.tsx +12 -39
- package/template/src/components/virtual-background/useVB.tsx +0 -18
- package/template/src/components/whiteboard/WhiteboardConfigure.tsx +0 -27
- package/template/src/language/default-labels/videoCallScreenLabels.ts +27 -11
- package/template/src/logger/AppBuilderLogger.tsx +3 -11
- package/template/src/pages/VideoCall.tsx +518 -171
- package/template/src/pages/video-call/ActionSheetContent.tsx +77 -77
- package/template/src/pages/video-call/SidePanelHeader.tsx +81 -53
- package/template/src/pages/video-call/VideoCallScreen.tsx +0 -18
- package/template/src/pages/video-call/VideoCallScreenWrapper.tsx +1 -0
- package/template/src/rtm/RTMEngine.ts +37 -262
- package/template/src/rtm/utils.ts +1 -68
- package/template/src/rtm-events/constants.ts +7 -40
- package/template/src/rtm-events-api/Events.ts +39 -158
- package/template/src/subComponents/ChatBubble.tsx +3 -3
- package/template/src/subComponents/ChatContainer.tsx +9 -19
- package/template/src/subComponents/LocalAudioMute.tsx +2 -2
- package/template/src/subComponents/LocalVideoMute.tsx +2 -2
- package/template/src/subComponents/SidePanelEnum.tsx +0 -1
- package/template/src/subComponents/caption/Caption.tsx +48 -7
- package/template/src/subComponents/caption/CaptionContainer.tsx +324 -51
- package/template/src/subComponents/caption/CaptionIcon.tsx +35 -34
- package/template/src/subComponents/caption/CaptionText.tsx +103 -2
- package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +179 -69
- package/template/src/subComponents/caption/Transcript.tsx +46 -11
- package/template/src/subComponents/caption/TranscriptIcon.tsx +27 -35
- package/template/src/subComponents/caption/TranscriptText.tsx +78 -3
- package/template/src/subComponents/caption/proto/ptoto.js +38 -4
- package/template/src/subComponents/caption/proto/test.proto +34 -19
- package/template/src/subComponents/caption/useCaption.tsx +754 -11
- package/template/src/subComponents/caption/useSTTAPI.tsx +118 -205
- package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +152 -33
- package/template/src/subComponents/caption/useStreamMessageUtils.ts +165 -34
- package/template/src/subComponents/caption/utils.ts +171 -3
- package/template/src/subComponents/chat/ChatSendButton.tsx +0 -1
- package/template/src/subComponents/screenshare/ScreenshareButton.tsx +0 -16
- package/template/src/subComponents/screenshare/ScreenshareConfigure.native.tsx +1 -1
- package/template/src/subComponents/waiting-rooms/WaitingRoomControls.tsx +4 -7
- package/template/src/utils/SdkEvents.ts +3 -0
- package/template/src/utils/useEndCall.ts +4 -4
- package/template/src/utils/useMuteToggleLocal.ts +10 -14
- package/template/src/utils/useSpeechToText.ts +31 -20
- package/template/bridge/rtm/web/index-legacy.ts +0 -540
- package/template/src/components/RTMConfigure-legacy.tsx +0 -848
- package/template/src/components/UserGlobalPreferenceProvider.tsx +0 -227
- package/template/src/components/breakout-room/BreakoutRoomPanel.tsx +0 -58
- package/template/src/components/breakout-room/context/BreakoutRoomContext.tsx +0 -2508
- package/template/src/components/breakout-room/events/BreakoutRoomEventsConfigure.tsx +0 -272
- package/template/src/components/breakout-room/events/constants.ts +0 -17
- package/template/src/components/breakout-room/hoc/BreakoutRoomNameRenderer.tsx +0 -68
- package/template/src/components/breakout-room/hooks/useBreakoutRoomExit.ts +0 -49
- package/template/src/components/breakout-room/state/reducer.ts +0 -522
- package/template/src/components/breakout-room/state/types.ts +0 -54
- package/template/src/components/breakout-room/ui/BreakoutMeetingTitle.tsx +0 -60
- package/template/src/components/breakout-room/ui/BreakoutRoomActionMenu.tsx +0 -136
- package/template/src/components/breakout-room/ui/BreakoutRoomAnnouncementModal.tsx +0 -135
- package/template/src/components/breakout-room/ui/BreakoutRoomGroupSettings.tsx +0 -588
- package/template/src/components/breakout-room/ui/BreakoutRoomMainRoomUsers.tsx +0 -142
- package/template/src/components/breakout-room/ui/BreakoutRoomMemberActionMenu.tsx +0 -122
- package/template/src/components/breakout-room/ui/BreakoutRoomParticipants.tsx +0 -124
- package/template/src/components/breakout-room/ui/BreakoutRoomRaiseHand.tsx +0 -65
- package/template/src/components/breakout-room/ui/BreakoutRoomRenameModal.tsx +0 -227
- package/template/src/components/breakout-room/ui/BreakoutRoomSettings.tsx +0 -140
- package/template/src/components/breakout-room/ui/BreakoutRoomTransition.tsx +0 -52
- package/template/src/components/breakout-room/ui/BreakoutRoomView.tsx +0 -193
- package/template/src/components/breakout-room/ui/ExitBreakoutRoomIconButton.tsx +0 -79
- package/template/src/components/breakout-room/ui/ParticipantManualAssignmentModal.tsx +0 -638
- package/template/src/components/breakout-room/ui/SelectParticipantAssignmentStrategy.tsx +0 -57
- package/template/src/components/common/Dividers.tsx +0 -53
- package/template/src/components/controls/toolbar-items/ExitBreakoutRoomToolbarItem.tsx +0 -13
- package/template/src/components/raise-hand/RaiseHandButton.tsx +0 -50
- package/template/src/components/raise-hand/RaiseHandProvider.tsx +0 -308
- package/template/src/components/raise-hand/index.ts +0 -14
- package/template/src/components/room-info/useCurrentRoomInfo.tsx +0 -42
- package/template/src/components/room-info/useSetBreakoutRoomInfo.tsx +0 -64
- package/template/src/pages/video-call/BreakoutVideoCall.tsx +0 -213
- package/template/src/pages/video-call/VideoCallContent.tsx +0 -211
- package/template/src/pages/video-call/VideoCallStateWrapper.tsx +0 -495
- package/template/src/rtm/RTMConfigureBreakoutRoomProvider.tsx +0 -882
- package/template/src/rtm/RTMConfigureMainRoomProvider.tsx +0 -757
- package/template/src/rtm/RTMCoreProvider.tsx +0 -419
- package/template/src/rtm/RTMGlobalStateProvider.tsx +0 -706
- package/template/src/rtm/RTMStatusBanner.tsx +0 -99
- package/template/src/rtm/constants.ts +0 -12
- package/template/src/rtm/hooks/useMainRoomUserDisplayName.ts +0 -45
- package/template/src/rtm/rtm-presence-utils.ts +0 -344
- package/template/src/subComponents/chat/ChatAnnouncementView.tsx +0 -65
- package/template/src/utils/useDebouncedCallback.tsx +0 -20
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import {useCaption} from './useCaption';
|
|
3
2
|
import protoRoot from './proto/ptoto';
|
|
4
3
|
import PQueue from 'p-queue';
|
|
@@ -7,6 +6,16 @@ type StreamMessageCallback = (args: [number, Uint8Array]) => void;
|
|
|
7
6
|
type FinalListType = {
|
|
8
7
|
[key: string]: string[];
|
|
9
8
|
};
|
|
9
|
+
type TranslationData = {
|
|
10
|
+
lang: string;
|
|
11
|
+
text: string;
|
|
12
|
+
isFinal: boolean;
|
|
13
|
+
};
|
|
14
|
+
type FinalTranslationListType = {
|
|
15
|
+
[key: string]: {
|
|
16
|
+
[lang: string]: string[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
10
19
|
|
|
11
20
|
const useStreamMessageUtils = (): {
|
|
12
21
|
streamMessageCallback: StreamMessageCallback;
|
|
@@ -16,30 +25,34 @@ const useStreamMessageUtils = (): {
|
|
|
16
25
|
setMeetingTranscript,
|
|
17
26
|
activeSpeakerRef,
|
|
18
27
|
prevSpeakerRef,
|
|
28
|
+
// Use ref instead of state to avoid stale closure issues
|
|
29
|
+
// The ref always has the current value, even in callbacks created at mount time
|
|
30
|
+
selectedTranslationLanguageRef,
|
|
19
31
|
} = useCaption();
|
|
20
32
|
|
|
21
33
|
let captionStartTime: number = 0;
|
|
22
34
|
const finalList: FinalListType = {};
|
|
23
35
|
const finalTranscriptList: FinalListType = {};
|
|
36
|
+
const finalTranslationList: FinalTranslationListType = {};
|
|
24
37
|
const queue = new PQueue({concurrency: 1});
|
|
25
|
-
let counter = 0;
|
|
26
|
-
let lastOfftime = 0;
|
|
27
38
|
|
|
28
39
|
const streamMessageCallback: StreamMessageCallback = args => {
|
|
29
40
|
const queueCallback = (args1: [number, Uint8Array]) => {
|
|
30
41
|
/* uid - bot which sends stream message in channel
|
|
31
42
|
payload - stream message in Uint8Array format
|
|
32
43
|
*/
|
|
33
|
-
const [, payload] = args1;
|
|
44
|
+
const [botUid, payload] = args1;
|
|
34
45
|
let nonFinalText = ''; // holds intermediate results
|
|
35
46
|
let finalText = ''; // holds final strings
|
|
36
47
|
let currentFinalText = ''; // holds current caption
|
|
37
48
|
let isInterjecting = false;
|
|
49
|
+
let translations: TranslationData[] = []; // holds translation data
|
|
38
50
|
|
|
39
51
|
const textstream = protoRoot
|
|
40
|
-
.lookupType('Text')
|
|
52
|
+
.lookupType('agora.audio2text.Text')
|
|
41
53
|
.decode(payload as Uint8Array) as any;
|
|
42
54
|
|
|
55
|
+
console.log('[STT_PER_USER_BOT] stt v7 textstream', botUid, textstream);
|
|
43
56
|
//console.log('STT - Parsed Textstream : ', textstream);
|
|
44
57
|
// console.log(
|
|
45
58
|
// `STT-callback(${++counter}): %c${textstream.uid} %c${textstream.words
|
|
@@ -84,7 +97,9 @@ const useStreamMessageUtils = (): {
|
|
|
84
97
|
|
|
85
98
|
*/
|
|
86
99
|
|
|
87
|
-
const finalWord = textstream.words.filter(
|
|
100
|
+
const finalWord = textstream.words.filter(
|
|
101
|
+
(word: any) => word.isFinal === true,
|
|
102
|
+
);
|
|
88
103
|
// when we only get final word for the previous speaker then don't flip previous speaker as active but update in place.
|
|
89
104
|
|
|
90
105
|
if (
|
|
@@ -94,6 +109,14 @@ const useStreamMessageUtils = (): {
|
|
|
94
109
|
// we have a speaker change so clear the context for prev speaker
|
|
95
110
|
if (prevSpeakerRef.current !== '') {
|
|
96
111
|
finalList[prevSpeakerRef.current] = [];
|
|
112
|
+
// Clear translations for previous speaker
|
|
113
|
+
if (finalTranslationList[prevSpeakerRef.current]) {
|
|
114
|
+
Object.keys(finalTranslationList[prevSpeakerRef.current]).forEach(
|
|
115
|
+
lang => {
|
|
116
|
+
finalTranslationList[prevSpeakerRef.current][lang] = [];
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
}
|
|
97
120
|
isInterjecting = true;
|
|
98
121
|
// console.log(
|
|
99
122
|
// '%cSTT-callback%c Interjection! ',
|
|
@@ -114,16 +137,63 @@ const useStreamMessageUtils = (): {
|
|
|
114
137
|
finalTranscriptList[textstream.uid] = [];
|
|
115
138
|
}
|
|
116
139
|
|
|
140
|
+
// Process translations if available
|
|
141
|
+
if (textstream.trans && textstream.trans.length > 0) {
|
|
142
|
+
for (const trans of textstream.trans) {
|
|
143
|
+
const lang = trans.lang;
|
|
144
|
+
const texts = trans.texts || [];
|
|
145
|
+
const isFinal = trans.isFinal || false;
|
|
146
|
+
|
|
147
|
+
if (!finalTranslationList[textstream.uid]) {
|
|
148
|
+
finalTranslationList[textstream.uid] = {};
|
|
149
|
+
}
|
|
150
|
+
if (!finalTranslationList[textstream.uid][lang]) {
|
|
151
|
+
finalTranslationList[textstream.uid][lang] = [];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const currentTranslationText = texts.join(' ');
|
|
155
|
+
if (currentTranslationText) {
|
|
156
|
+
if (isFinal) {
|
|
157
|
+
finalTranslationList[textstream.uid][lang].push(
|
|
158
|
+
currentTranslationText,
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Build complete translation text (final + current non-final)
|
|
163
|
+
const existingTranslationBuffer = isInterjecting
|
|
164
|
+
? ''
|
|
165
|
+
: finalTranslationList[textstream.uid][lang]?.join(' ');
|
|
166
|
+
const latestTranslationString = isFinal
|
|
167
|
+
? ''
|
|
168
|
+
: currentTranslationText;
|
|
169
|
+
const completeTranslationText =
|
|
170
|
+
existingTranslationBuffer.length > 0
|
|
171
|
+
? latestTranslationString
|
|
172
|
+
? existingTranslationBuffer + ' ' + latestTranslationString
|
|
173
|
+
: existingTranslationBuffer
|
|
174
|
+
: latestTranslationString;
|
|
175
|
+
|
|
176
|
+
if (completeTranslationText || isFinal) {
|
|
177
|
+
translations.push({
|
|
178
|
+
lang,
|
|
179
|
+
text: completeTranslationText,
|
|
180
|
+
isFinal,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
117
187
|
const words = textstream.words; //[Word,Word]
|
|
118
188
|
|
|
119
189
|
/* categorize words into final & nonFinal objects per uid
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
190
|
+
Final Word Ex : {
|
|
191
|
+
"text": "Hello, are you doing?",
|
|
192
|
+
"durationMs": 960,
|
|
193
|
+
"isFinal": true,
|
|
194
|
+
"confidence": 0.8549408316612244
|
|
195
|
+
}
|
|
196
|
+
*/
|
|
127
197
|
|
|
128
198
|
for (const word of words) {
|
|
129
199
|
if (word.isFinal) {
|
|
@@ -150,7 +220,29 @@ const useStreamMessageUtils = (): {
|
|
|
150
220
|
}
|
|
151
221
|
|
|
152
222
|
/* Updating Meeting Transcript */
|
|
153
|
-
|
|
223
|
+
// Update transcript when: (1) new text finalized OR (2) final translations arrived
|
|
224
|
+
const hasFinalTranslations = textstream.trans?.some(
|
|
225
|
+
(t: any) => t.isFinal === true,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
if (currentFinalText.length || hasFinalTranslations) {
|
|
229
|
+
// final translations for transcript - include ALL available final translations for this user
|
|
230
|
+
const finalTranslationsForTranscript: TranslationData[] = [];
|
|
231
|
+
if (finalTranslationList[textstream.uid]) {
|
|
232
|
+
Object.keys(finalTranslationList[textstream.uid]).forEach(lang => {
|
|
233
|
+
const translationText =
|
|
234
|
+
finalTranslationList[textstream.uid][lang]?.join(' ') || '';
|
|
235
|
+
|
|
236
|
+
if (translationText) {
|
|
237
|
+
finalTranslationsForTranscript.push({
|
|
238
|
+
lang: lang,
|
|
239
|
+
text: translationText,
|
|
240
|
+
isFinal: true,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
154
246
|
setMeetingTranscript(prevTranscript => {
|
|
155
247
|
const lastTranscriptIndex = prevTranscript.length - 1;
|
|
156
248
|
const lastTranscript =
|
|
@@ -160,21 +252,30 @@ const useStreamMessageUtils = (): {
|
|
|
160
252
|
|
|
161
253
|
/*
|
|
162
254
|
checking if the last item transcript matches with current uid
|
|
163
|
-
If yes then updating the last transcript msg with current text
|
|
164
|
-
If no then adding a new entry in the transcript
|
|
255
|
+
If yes then updating the last transcript msg with current text and translations
|
|
256
|
+
If no and we have new text, then adding a new entry in the transcript
|
|
257
|
+
If no new text and no matching uid, don't add empty item
|
|
165
258
|
*/
|
|
166
259
|
if (lastTranscript && lastTranscript.uid === textstream.uid) {
|
|
260
|
+
// Update existing transcript item (text + translations or just translations)
|
|
167
261
|
const updatedTranscript = {
|
|
168
262
|
...lastTranscript,
|
|
169
263
|
//text: lastTranscript.text + ' ' + currentFinalText, // missing few updates with reading prev values
|
|
170
|
-
text:
|
|
264
|
+
text: currentFinalText.length
|
|
265
|
+
? finalTranscriptList[textstream.uid].join(' ')
|
|
266
|
+
: lastTranscript.text, // Keep existing text if no new text
|
|
267
|
+
translations: finalTranslationsForTranscript,
|
|
268
|
+
// preserve the original translation language from when this transcript was created
|
|
269
|
+
selectedTranslationLanguage:
|
|
270
|
+
lastTranscript.selectedTranslationLanguage,
|
|
171
271
|
};
|
|
172
272
|
|
|
173
273
|
return [
|
|
174
274
|
...prevTranscript.slice(0, lastTranscriptIndex),
|
|
175
275
|
updatedTranscript,
|
|
176
276
|
];
|
|
177
|
-
} else {
|
|
277
|
+
} else if (currentFinalText.length) {
|
|
278
|
+
// Only create new item if we have new text
|
|
178
279
|
finalTranscriptList[textstream.uid] = [currentFinalText];
|
|
179
280
|
|
|
180
281
|
return [
|
|
@@ -183,8 +284,19 @@ const useStreamMessageUtils = (): {
|
|
|
183
284
|
uid: textstream.uid,
|
|
184
285
|
time: new Date().getTime(),
|
|
185
286
|
text: currentFinalText,
|
|
287
|
+
translations: finalTranslationsForTranscript,
|
|
288
|
+
// Store the current translation language with this transcript item
|
|
289
|
+
// This preserves which translation was active when this text was spoken
|
|
290
|
+
selectedTranslationLanguage:
|
|
291
|
+
selectedTranslationLanguageRef.current,
|
|
186
292
|
},
|
|
187
293
|
];
|
|
294
|
+
} else {
|
|
295
|
+
// No new text and uid doesn't match - don't modify transcript
|
|
296
|
+
// console.log(
|
|
297
|
+
// '[TRANSCRIPT_DEBUG] Skipping transcript update - no new text and uid mismatch',
|
|
298
|
+
// );
|
|
299
|
+
return prevTranscript;
|
|
188
300
|
}
|
|
189
301
|
});
|
|
190
302
|
}
|
|
@@ -197,22 +309,41 @@ const useStreamMessageUtils = (): {
|
|
|
197
309
|
? ''
|
|
198
310
|
: finalList[textstream.uid]?.join(' ');
|
|
199
311
|
const latestString = nonFinalText;
|
|
200
|
-
const captionText =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
312
|
+
const captionText = isInterjecting
|
|
313
|
+
? latestString
|
|
314
|
+
: existingStringBuffer.length > 0
|
|
315
|
+
? existingStringBuffer + ' ' + latestString
|
|
316
|
+
: latestString;
|
|
317
|
+
|
|
318
|
+
// updating the captions with both transcription and translations
|
|
319
|
+
setCaptionObj(prevState => {
|
|
320
|
+
const existingTranslations =
|
|
321
|
+
prevState[textstream.uid]?.translations || [];
|
|
322
|
+
|
|
323
|
+
// Update existing translations or add new ones
|
|
324
|
+
const updatedTranslations = [...existingTranslations];
|
|
325
|
+
|
|
326
|
+
for (const newTrans of translations) {
|
|
327
|
+
const existingIndex = updatedTranslations.findIndex(
|
|
328
|
+
t => t.lang === newTrans.lang,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
if (existingIndex >= 0) {
|
|
332
|
+
updatedTranslations[existingIndex] = newTrans;
|
|
333
|
+
} else {
|
|
334
|
+
updatedTranslations.push(newTrans);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
...prevState,
|
|
340
|
+
[textstream.uid]: {
|
|
341
|
+
text: captionText || prevState[textstream.uid]?.text || '',
|
|
342
|
+
translations: updatedTranslations,
|
|
343
|
+
lastUpdated: new Date().getTime(),
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
});
|
|
216
347
|
|
|
217
348
|
// console.group('STT-logs');
|
|
218
349
|
// console.log('Recived uid =>', textstream.uid);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {ContentObjects} from '../../../agora-rn-uikit/src/Contexts/RtcContext';
|
|
2
|
-
import {TranscriptItem} from './useCaption';
|
|
2
|
+
import {TranscriptItem, LanguageTranslationConfig} from './useCaption';
|
|
3
3
|
|
|
4
4
|
export function formatTime(timestamp: number): string {
|
|
5
5
|
const d = new Date(timestamp);
|
|
@@ -11,6 +11,26 @@ export function formatTime(timestamp: number): string {
|
|
|
11
11
|
return `${H}:${m} ${suffix}`;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Check if translation configuration has changed
|
|
16
|
+
* Compares source language and target languages between two configs
|
|
17
|
+
* @param prev - Previous translation configuration
|
|
18
|
+
* @param next - New translation configuration
|
|
19
|
+
* @returns true if config has changed, false otherwise
|
|
20
|
+
*/
|
|
21
|
+
export const hasConfigChanged = (
|
|
22
|
+
prev: LanguageTranslationConfig,
|
|
23
|
+
next: LanguageTranslationConfig,
|
|
24
|
+
): boolean => {
|
|
25
|
+
const sourceChanged =
|
|
26
|
+
(prev.source || []).sort().join(',') !==
|
|
27
|
+
(next.source || []).sort().join(',');
|
|
28
|
+
const targetsChanged =
|
|
29
|
+
(prev.targets || []).sort().join(',') !==
|
|
30
|
+
(next.targets || []).sort().join(',');
|
|
31
|
+
return sourceChanged || targetsChanged;
|
|
32
|
+
};
|
|
33
|
+
|
|
14
34
|
export type LanguageType =
|
|
15
35
|
| 'ar-EG'
|
|
16
36
|
| 'ar-JO'
|
|
@@ -118,14 +138,56 @@ export const formatTranscriptContent = (
|
|
|
118
138
|
meetingTitle: string,
|
|
119
139
|
defaultContent: ContentObjects,
|
|
120
140
|
) => {
|
|
141
|
+
// Helper function to get display text based on stored translation language
|
|
142
|
+
// Uses the translation language that was active when this transcript item was created
|
|
143
|
+
const getDisplayText = (item: TranscriptItem): string => {
|
|
144
|
+
// Use the stored translation language from when this item was created
|
|
145
|
+
const storedTranslationLanguage = item.selectedTranslationLanguage;
|
|
146
|
+
|
|
147
|
+
if (!storedTranslationLanguage || !item.translations) {
|
|
148
|
+
return item.text; // no translation selected or no translations available, show original
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// find translation for the stored language
|
|
152
|
+
const currentTranslation = item.translations.find(
|
|
153
|
+
t => t.lang === storedTranslationLanguage,
|
|
154
|
+
);
|
|
155
|
+
if (currentTranslation?.text) {
|
|
156
|
+
return currentTranslation.text;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// if stored language not available, show original
|
|
160
|
+
return item.text;
|
|
161
|
+
};
|
|
162
|
+
|
|
121
163
|
const formattedContent = meetingTranscript
|
|
122
164
|
.map(item => {
|
|
123
|
-
if (
|
|
165
|
+
if (
|
|
166
|
+
item.uid.toString().indexOf('langUpdate') !== -1 ||
|
|
167
|
+
item.uid.toString().indexOf('translationUpdate') !== -1
|
|
168
|
+
) {
|
|
124
169
|
return `${defaultContent[item?.uid?.split('-')[1]]?.name} ${item.text}`;
|
|
125
170
|
}
|
|
126
|
-
|
|
171
|
+
|
|
172
|
+
// Build transcript entry with original text and all translations
|
|
173
|
+
let transcriptEntry = `${defaultContent[item.uid]?.name} ${formatTime(
|
|
127
174
|
Number(item?.time),
|
|
128
175
|
)}:\n${item.text}`;
|
|
176
|
+
|
|
177
|
+
// Add all translations with language labels
|
|
178
|
+
if (item.translations && item.translations.length > 0) {
|
|
179
|
+
const translationLines = item.translations
|
|
180
|
+
.map(trans => {
|
|
181
|
+
const langLabel =
|
|
182
|
+
langData.find(l => l.value === trans.lang)?.label || trans.lang;
|
|
183
|
+
return `${langLabel}: ${trans.text}`;
|
|
184
|
+
})
|
|
185
|
+
.join('\n');
|
|
186
|
+
|
|
187
|
+
transcriptEntry += `\n${translationLines}`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return transcriptEntry;
|
|
129
191
|
})
|
|
130
192
|
.join('\n\n');
|
|
131
193
|
|
|
@@ -160,3 +222,109 @@ export const formatTranscriptContent = (
|
|
|
160
222
|
|
|
161
223
|
return [finalContent, fileName];
|
|
162
224
|
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Get the appropriate caption text to display based on the user's source/spoken language
|
|
228
|
+
* For other users' captions: shows translation matching user's source/spoken language
|
|
229
|
+
* For current user's captions: shows original text
|
|
230
|
+
* Falls back to original text if translation is not available
|
|
231
|
+
*
|
|
232
|
+
* @param captionText - The original caption text
|
|
233
|
+
* @param translations - Array of available translations
|
|
234
|
+
* @param viewerSourceLanguage - The user's source (spoken) language
|
|
235
|
+
* @param speakerUid - The UID of the person speaking
|
|
236
|
+
* @param currentUserUid - The UID of the current user
|
|
237
|
+
* @returns The appropriate caption text to display
|
|
238
|
+
*/
|
|
239
|
+
export const getUserTranslatedText = (
|
|
240
|
+
captionText: string,
|
|
241
|
+
translations: Array<{lang: string; text: string; isFinal: boolean}> = [],
|
|
242
|
+
viewerSourceLanguage: LanguageType,
|
|
243
|
+
speakerUid: string | number,
|
|
244
|
+
currentUserUid: string | number,
|
|
245
|
+
): {
|
|
246
|
+
value: string;
|
|
247
|
+
langCode: string;
|
|
248
|
+
} => {
|
|
249
|
+
// console.log(
|
|
250
|
+
// 'getUserTranslatedText input params',
|
|
251
|
+
// captionText,
|
|
252
|
+
// translations,
|
|
253
|
+
// viewerSourceLanguage,
|
|
254
|
+
// speakerUid,
|
|
255
|
+
// currentUserUid,
|
|
256
|
+
// );
|
|
257
|
+
|
|
258
|
+
// 1. If the speaker is the local user, always show their own source text
|
|
259
|
+
if (speakerUid === currentUserUid) {
|
|
260
|
+
return {
|
|
261
|
+
value: captionText,
|
|
262
|
+
langCode: getLanguageLabel([viewerSourceLanguage]) || '',
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
// For other users' captions, try to find translation matching viewer's source language
|
|
266
|
+
if (viewerSourceLanguage && translations && translations.length > 0) {
|
|
267
|
+
const matchingTranslation = translations.find(
|
|
268
|
+
t => t.lang === viewerSourceLanguage,
|
|
269
|
+
);
|
|
270
|
+
if (matchingTranslation) {
|
|
271
|
+
// Translation exists (even if empty)
|
|
272
|
+
// - If text is empty: show nothing (don’t fallback)
|
|
273
|
+
// - If text exists: show it
|
|
274
|
+
const translatedText = matchingTranslation.text?.trim() || '';
|
|
275
|
+
return {
|
|
276
|
+
value: translatedText,
|
|
277
|
+
langCode: getLanguageLabel([matchingTranslation.lang]) || '',
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Fallback to original text if no translation found
|
|
283
|
+
return {
|
|
284
|
+
value: captionText,
|
|
285
|
+
langCode: 'Original',
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
export interface TranslateConfig {
|
|
290
|
+
source_lang: string;
|
|
291
|
+
target_lang: string[];
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export const mergeTranslationConfigs = (
|
|
295
|
+
existingTranslateConfig: TranslateConfig[],
|
|
296
|
+
userOwnLanguages: LanguageType[],
|
|
297
|
+
selectedTranslationLanguage: string,
|
|
298
|
+
): TranslateConfig[] => {
|
|
299
|
+
// Create new translate_config for user's own languages
|
|
300
|
+
const newTranslateConfigs = userOwnLanguages.map(spokenLang => ({
|
|
301
|
+
source_lang: spokenLang,
|
|
302
|
+
target_lang: [selectedTranslationLanguage],
|
|
303
|
+
}));
|
|
304
|
+
|
|
305
|
+
// Merge with existing configuration
|
|
306
|
+
const mergedTranslateConfig = [...existingTranslateConfig];
|
|
307
|
+
|
|
308
|
+
newTranslateConfigs.forEach(newConfig => {
|
|
309
|
+
const existingIndex = mergedTranslateConfig.findIndex(
|
|
310
|
+
existing => existing.source_lang === newConfig.source_lang,
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
if (existingIndex !== -1) {
|
|
314
|
+
// Same source language - merge target languages
|
|
315
|
+
const existingTargets = mergedTranslateConfig[existingIndex].target_lang;
|
|
316
|
+
const mergedTargets = [
|
|
317
|
+
...new Set([...existingTargets, ...newConfig.target_lang]),
|
|
318
|
+
];
|
|
319
|
+
mergedTranslateConfig[existingIndex] = {
|
|
320
|
+
...mergedTranslateConfig[existingIndex],
|
|
321
|
+
target_lang: mergedTargets,
|
|
322
|
+
};
|
|
323
|
+
} else {
|
|
324
|
+
// Different source language - add new config
|
|
325
|
+
mergedTranslateConfig.push(newConfig);
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return mergedTranslateConfig;
|
|
330
|
+
};
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
toolbarItemShareText,
|
|
26
26
|
} from '../../language/default-labels/videoCallScreenLabels';
|
|
27
27
|
import {useToolbarProps} from '../../atoms/ToolbarItem';
|
|
28
|
-
import {useBreakoutRoom} from '../../components/breakout-room/context/BreakoutRoomContext';
|
|
29
28
|
/**
|
|
30
29
|
* A component to start and stop screen sharing on web clients.
|
|
31
30
|
* Screen sharing is not yet implemented on mobile platforms.
|
|
@@ -54,11 +53,6 @@ const ScreenshareButton = (props: ScreenshareButtonProps) => {
|
|
|
54
53
|
const {setShowStartScreenSharePopup} = useVideoCall();
|
|
55
54
|
const screenShareButtonLabel = useString<boolean>(toolbarItemShareText);
|
|
56
55
|
const lstooltip = useString<boolean>(livestreamingShareTooltipText);
|
|
57
|
-
const {permissions} = useBreakoutRoom();
|
|
58
|
-
// In the main room (default case), permissions come from the main room state.
|
|
59
|
-
// If the user is in a breakout room, retrieve permissions from the breakout room instead.
|
|
60
|
-
const canScreenshareInBreakoutRoom = permissions.canScreenshare;
|
|
61
|
-
|
|
62
56
|
const onPress = () => {
|
|
63
57
|
if (isScreenshareActive) {
|
|
64
58
|
stopScreenshare();
|
|
@@ -111,16 +105,6 @@ const ScreenshareButton = (props: ScreenshareButtonProps) => {
|
|
|
111
105
|
iconButtonProps.disabled = true;
|
|
112
106
|
}
|
|
113
107
|
|
|
114
|
-
if (!canScreenshareInBreakoutRoom) {
|
|
115
|
-
iconButtonProps.iconProps = {
|
|
116
|
-
...iconButtonProps.iconProps,
|
|
117
|
-
tintColor: $config.SEMANTIC_NEUTRAL,
|
|
118
|
-
showWarningIcon: true,
|
|
119
|
-
};
|
|
120
|
-
iconButtonProps.toolTipMessage = 'cannot screenshare';
|
|
121
|
-
iconButtonProps.disabled = true;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
108
|
return props?.render ? (
|
|
125
109
|
props.render(onPress, isScreenshareActive)
|
|
126
110
|
) : isToolbarMenuItem ? (
|
|
@@ -319,7 +319,7 @@ export const ScreenshareConfigure = (props: {children: React.ReactNode}) => {
|
|
|
319
319
|
'Trying to start native screenshare',
|
|
320
320
|
);
|
|
321
321
|
if (video) {
|
|
322
|
-
|
|
322
|
+
localMute(MUTE_LOCAL_TYPE.video);
|
|
323
323
|
}
|
|
324
324
|
try {
|
|
325
325
|
await engine.current.startScreenCapture({
|
|
@@ -14,14 +14,12 @@ import {
|
|
|
14
14
|
peoplePanelWaitingRoomRequestApprovalBtnTxt,
|
|
15
15
|
peoplePanelWaitingRoomRequestDenyBtnTxt,
|
|
16
16
|
} from '../../../src/language/default-labels/videoCallScreenLabels';
|
|
17
|
-
import ChatContext from '../../components/ChatContext';
|
|
18
17
|
|
|
19
18
|
const WaitingRoomButton = props => {
|
|
20
19
|
const {uid, screenUid, isAccept} = props;
|
|
21
20
|
const {approval} = useWaitingRoomAPI();
|
|
22
21
|
const localUid = useLocalUid();
|
|
23
22
|
const {dispatch} = useContext(DispatchContext);
|
|
24
|
-
const {syncUserState} = useContext(ChatContext);
|
|
25
23
|
const {waitingRoomRef} = useWaitingRoomContext();
|
|
26
24
|
const admintext = useString(peoplePanelWaitingRoomRequestApprovalBtnTxt)();
|
|
27
25
|
const denytext = useString(peoplePanelWaitingRoomRequestDenyBtnTxt)();
|
|
@@ -42,11 +40,10 @@ const WaitingRoomButton = props => {
|
|
|
42
40
|
Toast.hide();
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
syncUserState(uid, {isInWaitingRoom: false});
|
|
43
|
+
dispatch({
|
|
44
|
+
type: 'UpdateRenderList',
|
|
45
|
+
value: [uid, {isInWaitingRoom: false}],
|
|
46
|
+
});
|
|
50
47
|
|
|
51
48
|
if (waitingRoomRef.current) {
|
|
52
49
|
waitingRoomRef.current[uid] = approved ? 'APPROVED' : 'REJECTED';
|
|
@@ -51,6 +51,9 @@ export interface userEventsMapInterface {
|
|
|
51
51
|
'token-refreshed': () => void;
|
|
52
52
|
'rtc-user-removed': (uid: UidType, channel: string) => void;
|
|
53
53
|
unauthorized: (errorMessage) => void;
|
|
54
|
+
'waiting-room-approval-requested': () => void;
|
|
55
|
+
'waiting-room-approval-granted': () => void;
|
|
56
|
+
'waiting-room-approval-rejected': () => void;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
const SDKEvents = createNanoEvents<userEventsMapInterface>();
|
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
useCaption,
|
|
5
5
|
useContent,
|
|
6
6
|
useRoomInfo,
|
|
7
|
-
useSTTAPI,
|
|
8
7
|
} from 'customization-api';
|
|
9
8
|
import {PropsContext, DispatchContext} from '../../agora-rn-uikit';
|
|
10
9
|
import {useHistory} from '../components/Router';
|
|
11
10
|
import {stopForegroundService} from '../subComponents/LocalEndCall';
|
|
11
|
+
import RTMEngine from '../rtm/RTMEngine';
|
|
12
12
|
import {ENABLE_AUTH} from '../auth/config';
|
|
13
13
|
import {useAuth} from '../auth/AuthProvider';
|
|
14
14
|
import {useChatConfigure} from '../components/chat/chatConfigure';
|
|
@@ -16,13 +16,12 @@ import {useChatConfigure} from '../components/chat/chatConfigure';
|
|
|
16
16
|
const useEndCall = () => {
|
|
17
17
|
const history = useHistory();
|
|
18
18
|
const {defaultContent} = useContent();
|
|
19
|
-
const {isSTTActive} = useCaption();
|
|
19
|
+
const {isSTTActive, stopSTTBotSession} = useCaption();
|
|
20
20
|
const {
|
|
21
21
|
data: {isHost},
|
|
22
22
|
} = useRoomInfo();
|
|
23
23
|
const {authLogin} = useAuth();
|
|
24
24
|
const {deleteChatUser} = useChatConfigure();
|
|
25
|
-
const {stop: stopSTTAPI} = useSTTAPI();
|
|
26
25
|
|
|
27
26
|
const {rtcProps} = useContext(PropsContext);
|
|
28
27
|
const {dispatch} = useContext(DispatchContext);
|
|
@@ -59,7 +58,7 @@ const useEndCall = () => {
|
|
|
59
58
|
);
|
|
60
59
|
if (usersInCall.length === 1 && isSTTActive) {
|
|
61
60
|
console.log('Stopping stt api as only one host is in the call');
|
|
62
|
-
|
|
61
|
+
stopSTTBotSession().catch(error => {
|
|
63
62
|
console.log('Error stopping stt', error);
|
|
64
63
|
});
|
|
65
64
|
}
|
|
@@ -68,6 +67,7 @@ const useEndCall = () => {
|
|
|
68
67
|
if ($config.CHAT) {
|
|
69
68
|
deleteChatUser();
|
|
70
69
|
}
|
|
70
|
+
RTMEngine.getInstance().engine.leaveChannel(rtcProps.channel);
|
|
71
71
|
if (!ENABLE_AUTH) {
|
|
72
72
|
// await authLogout();
|
|
73
73
|
await authLogin();
|
|
@@ -86,16 +86,14 @@ function useMuteToggleLocal() {
|
|
|
86
86
|
);
|
|
87
87
|
|
|
88
88
|
// Enable UI
|
|
89
|
-
const newAudioState =
|
|
90
|
-
localAudioState === ToggleState.enabled
|
|
91
|
-
? ToggleState.disabled
|
|
92
|
-
: ToggleState.enabled;
|
|
93
|
-
|
|
94
89
|
dispatch({
|
|
95
90
|
type: 'LocalMuteAudio',
|
|
96
|
-
value: [
|
|
91
|
+
value: [
|
|
92
|
+
localAudioState === ToggleState.enabled
|
|
93
|
+
? ToggleState.disabled
|
|
94
|
+
: ToggleState.enabled,
|
|
95
|
+
],
|
|
97
96
|
});
|
|
98
|
-
|
|
99
97
|
handleQueue();
|
|
100
98
|
} catch (e) {
|
|
101
99
|
dispatch({
|
|
@@ -154,16 +152,14 @@ function useMuteToggleLocal() {
|
|
|
154
152
|
);
|
|
155
153
|
}
|
|
156
154
|
// Enable UI
|
|
157
|
-
const newVideoState =
|
|
158
|
-
localVideoState === ToggleState.enabled
|
|
159
|
-
? ToggleState.disabled
|
|
160
|
-
: ToggleState.enabled;
|
|
161
|
-
|
|
162
155
|
dispatch({
|
|
163
156
|
type: 'LocalMuteVideo',
|
|
164
|
-
value: [
|
|
157
|
+
value: [
|
|
158
|
+
localVideoState === ToggleState.enabled
|
|
159
|
+
? ToggleState.disabled
|
|
160
|
+
: ToggleState.enabled,
|
|
161
|
+
],
|
|
165
162
|
});
|
|
166
|
-
|
|
167
163
|
handleQueue();
|
|
168
164
|
} catch (e) {
|
|
169
165
|
dispatch({
|