agora-appbuilder-core 4.0.31 → 4.0.32
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 +1 -1
- package/template/Gulpfile.js +1 -1
- package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
- package/template/defaultConfig.js +2 -2
- package/template/src/components/Controls.tsx +79 -3
- package/template/src/components/EventsConfigure.tsx +6 -1
- package/template/src/components/recordings/RecordingDeletePopup.tsx +115 -0
- package/template/src/components/recordings/RecordingsDateTable.tsx +28 -5
- package/template/src/components/recordings/ViewRecordingsModal.tsx +3 -2
- package/template/src/components/recordings/recording-table.tsx +114 -90
- package/template/src/components/recordings/style.ts +2 -4
- package/template/src/logger/AppBuilderLogger.tsx +2 -1
- package/template/src/pages/video-call/ActionSheetContent.tsx +4 -0
- package/template/src/rtm-events/constants.ts +2 -1
- package/template/src/subComponents/caption/Caption.tsx +10 -3
- package/template/src/subComponents/recording/useRecording.tsx +81 -0
- package/template/src/utils/useSpeechToText.ts +32 -1
package/package.json
CHANGED
package/template/Gulpfile.js
CHANGED
|
@@ -1170,14 +1170,14 @@ export default class RtcEngine {
|
|
|
1170
1170
|
`RTC [createClient] creating user and screen client with profile ${profile}`,
|
|
1171
1171
|
);
|
|
1172
1172
|
this.client = AgoraRTC.createClient({
|
|
1173
|
-
codec: '
|
|
1173
|
+
codec: 'vp8',
|
|
1174
1174
|
mode:
|
|
1175
1175
|
profile === ChannelProfileType.ChannelProfileLiveBroadcasting
|
|
1176
1176
|
? mode.live
|
|
1177
1177
|
: mode.rtc,
|
|
1178
1178
|
});
|
|
1179
1179
|
this.screenClient = AgoraRTC.createClient({
|
|
1180
|
-
codec: '
|
|
1180
|
+
codec: 'vp8',
|
|
1181
1181
|
mode:
|
|
1182
1182
|
profile === ChannelProfileType.ChannelProfileLiveBroadcasting
|
|
1183
1183
|
? mode.live
|
|
@@ -76,8 +76,8 @@ const DefaultConfig = {
|
|
|
76
76
|
CHAT_ORG_NAME: '',
|
|
77
77
|
CHAT_APP_NAME: '',
|
|
78
78
|
CHAT_URL: '',
|
|
79
|
-
CLI_VERSION: '3.0.
|
|
80
|
-
CORE_VERSION: '4.0.
|
|
79
|
+
CLI_VERSION: '3.0.32',
|
|
80
|
+
CORE_VERSION: '4.0.32',
|
|
81
81
|
DISABLE_LANDSCAPE_MODE: false,
|
|
82
82
|
STT_AUTO_START: false,
|
|
83
83
|
CLOUD_RECORDING_AUTO_START: false,
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
useLayout,
|
|
56
56
|
useRecording,
|
|
57
57
|
useSidePanel,
|
|
58
|
+
useSpeechToText,
|
|
58
59
|
} from 'customization-api';
|
|
59
60
|
import {useVideoCall} from './useVideoCall';
|
|
60
61
|
import {useScreenshare} from '../subComponents/screenshare/useScreenshare';
|
|
@@ -110,6 +111,7 @@ import {useModal} from '../utils/useModal';
|
|
|
110
111
|
import ViewRecordingsModal from './recordings/ViewRecordingsModal';
|
|
111
112
|
import {filterObject} from '../utils/index';
|
|
112
113
|
import {useLanguage} from '../language/useLanguage';
|
|
114
|
+
import RecordingDeletePopup from './recordings/RecordingDeletePopup';
|
|
113
115
|
|
|
114
116
|
export const useToggleWhiteboard = () => {
|
|
115
117
|
const {
|
|
@@ -253,6 +255,12 @@ export const WhiteboardListener = () => {
|
|
|
253
255
|
};
|
|
254
256
|
|
|
255
257
|
const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
|
|
258
|
+
//recording delete
|
|
259
|
+
const [isRecordingDeletePopupVisible, setRecordingDeletePopupVisible] =
|
|
260
|
+
React.useState<boolean>(false);
|
|
261
|
+
const [recordingIdToDelete, setRecordingIdToDelete] = useState(0);
|
|
262
|
+
//recording delete
|
|
263
|
+
|
|
256
264
|
const {label} = useToolbarProps();
|
|
257
265
|
const {data} = useRoomInfo();
|
|
258
266
|
const noiseCancellationLabel = useString(toolbarItemNoiseCancellationText)();
|
|
@@ -312,7 +320,8 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
|
|
|
312
320
|
useVideoCall();
|
|
313
321
|
const {isScreenshareActive, startScreenshare, stopScreenshare} =
|
|
314
322
|
useScreenshare();
|
|
315
|
-
const {isRecordingActive, startRecording, inProgress} =
|
|
323
|
+
const {isRecordingActive, startRecording, inProgress, deleteRecording} =
|
|
324
|
+
useRecording();
|
|
316
325
|
const {setChatType} = useChatUIControls();
|
|
317
326
|
const actionMenuitems: ActionMenuItem[] = [];
|
|
318
327
|
|
|
@@ -849,6 +858,51 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
|
|
|
849
858
|
?.filter(i => !isHidden(i))
|
|
850
859
|
?.sort(CustomToolbarSort);
|
|
851
860
|
|
|
861
|
+
const onRecordingDeleteConfirmation = () => {
|
|
862
|
+
setRecordingDeletePopupVisible(false);
|
|
863
|
+
deleteRecording(recordingIdToDelete)
|
|
864
|
+
.then(() => {
|
|
865
|
+
//To inform other user -> refresh the recording list
|
|
866
|
+
//in case recording list opened
|
|
867
|
+
events.send(
|
|
868
|
+
EventNames.RECORDING_DELETED,
|
|
869
|
+
JSON.stringify({recordingId: recordingIdToDelete}),
|
|
870
|
+
PersistanceLevel.None,
|
|
871
|
+
);
|
|
872
|
+
Toast.show({
|
|
873
|
+
leadingIconName: 'alert',
|
|
874
|
+
type: 'success',
|
|
875
|
+
text1: 'Recording has been deleted successfully.',
|
|
876
|
+
visibilityTime: 3000,
|
|
877
|
+
primaryBtn: null,
|
|
878
|
+
secondaryBtn: null,
|
|
879
|
+
leadingIcon: null,
|
|
880
|
+
});
|
|
881
|
+
setRecordingIdToDelete(0);
|
|
882
|
+
//to reopen recording list again
|
|
883
|
+
setTimeout(() => {
|
|
884
|
+
setVRModalOpen(true);
|
|
885
|
+
}, 3000);
|
|
886
|
+
})
|
|
887
|
+
.catch(() => {
|
|
888
|
+
Toast.show({
|
|
889
|
+
leadingIconName: 'alert',
|
|
890
|
+
type: 'error',
|
|
891
|
+
text1: 'Failed to delete the recording. Please try again later',
|
|
892
|
+
visibilityTime: 1000 * 10,
|
|
893
|
+
primaryBtn: null,
|
|
894
|
+
secondaryBtn: null,
|
|
895
|
+
leadingIcon: null,
|
|
896
|
+
});
|
|
897
|
+
setRecordingIdToDelete(0);
|
|
898
|
+
});
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
const onRecordingDeleteCancel = () => {
|
|
902
|
+
setRecordingIdToDelete(0);
|
|
903
|
+
toggleVRModal();
|
|
904
|
+
};
|
|
905
|
+
|
|
852
906
|
return (
|
|
853
907
|
<>
|
|
854
908
|
<LanguageSelectorPopup
|
|
@@ -857,8 +911,27 @@ const MoreButton = (props: {fields: ToolbarMoreButtonDefaultFields}) => {
|
|
|
857
911
|
onConfirm={onConfirm}
|
|
858
912
|
isFirstTimePopupOpen={isFirstTimePopupOpen.current}
|
|
859
913
|
/>
|
|
860
|
-
{$config.CLOUD_RECORDING && isHost && isWeb() &&
|
|
861
|
-
|
|
914
|
+
{$config.CLOUD_RECORDING && isHost && isWeb() && (
|
|
915
|
+
<>
|
|
916
|
+
<RecordingDeletePopup
|
|
917
|
+
modalVisible={isRecordingDeletePopupVisible}
|
|
918
|
+
setModalVisible={setRecordingDeletePopupVisible}
|
|
919
|
+
onConfirm={onRecordingDeleteConfirmation}
|
|
920
|
+
onCancel={onRecordingDeleteCancel}
|
|
921
|
+
/>
|
|
922
|
+
{isVRModalOpen ? (
|
|
923
|
+
<ViewRecordingsModal
|
|
924
|
+
setModalOpen={setVRModalOpen}
|
|
925
|
+
onDeleteAction={id => {
|
|
926
|
+
setRecordingIdToDelete(id);
|
|
927
|
+
toggleVRModal();
|
|
928
|
+
setRecordingDeletePopupVisible(true);
|
|
929
|
+
}}
|
|
930
|
+
/>
|
|
931
|
+
) : (
|
|
932
|
+
<></>
|
|
933
|
+
)}
|
|
934
|
+
</>
|
|
862
935
|
)}
|
|
863
936
|
<ActionMenu
|
|
864
937
|
containerStyle={globalWidth < 720 ? {width: 180} : {width: 260}}
|
|
@@ -1141,6 +1214,7 @@ const Controls = (props: ControlsProps) => {
|
|
|
1141
1214
|
}>(sttSpokenLanguageToastSubHeading);
|
|
1142
1215
|
|
|
1143
1216
|
const {sttLanguage, isSTTActive} = useRoomInfo();
|
|
1217
|
+
const {addStreamMessageListener} = useSpeechToText();
|
|
1144
1218
|
|
|
1145
1219
|
React.useEffect(() => {
|
|
1146
1220
|
defaultContentRef.current = defaultContent;
|
|
@@ -1212,6 +1286,8 @@ const Controls = (props: ControlsProps) => {
|
|
|
1212
1286
|
},
|
|
1213
1287
|
];
|
|
1214
1288
|
});
|
|
1289
|
+
// start listening to stream Message callback
|
|
1290
|
+
addStreamMessageListener();
|
|
1215
1291
|
}, [sttLanguage]);
|
|
1216
1292
|
|
|
1217
1293
|
React.useEffect(() => {
|
|
@@ -268,7 +268,8 @@ const EventsConfigure: React.FC<Props> = ({
|
|
|
268
268
|
}, [permissionStatus]);
|
|
269
269
|
|
|
270
270
|
const {hasUserJoinedRTM, isInitialQueueCompleted} = useContext(ChatContext);
|
|
271
|
-
const {startSpeechToText} = useSpeechToText();
|
|
271
|
+
const {startSpeechToText, addStreamMessageListener} = useSpeechToText();
|
|
272
|
+
|
|
272
273
|
//auto start stt
|
|
273
274
|
useEffect(() => {
|
|
274
275
|
if (
|
|
@@ -285,6 +286,10 @@ const EventsConfigure: React.FC<Props> = ({
|
|
|
285
286
|
logger.log(LogSource.Internals, 'STT', 'STT_AUTO_START triggered', {
|
|
286
287
|
uidWhoTriggered: localUid,
|
|
287
288
|
});
|
|
289
|
+
|
|
290
|
+
// add stream message callback listener
|
|
291
|
+
addStreamMessageListener();
|
|
292
|
+
|
|
288
293
|
//start with default language
|
|
289
294
|
startSpeechToText(['en-US'])
|
|
290
295
|
.then(() => {
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import React, {SetStateAction} from 'react';
|
|
2
|
+
import {StyleSheet, Text, View} from 'react-native';
|
|
3
|
+
import Spacer from '../../atoms/Spacer';
|
|
4
|
+
import Popup from '../../atoms/Popup';
|
|
5
|
+
import TertiaryButton from '../../atoms/TertiaryButton';
|
|
6
|
+
import PrimaryButton from '../../atoms/PrimaryButton';
|
|
7
|
+
import ThemeConfig from '../../theme';
|
|
8
|
+
import {useIsDesktop} from '../../utils/common';
|
|
9
|
+
import {useString} from '../../utils/useString';
|
|
10
|
+
import {cancelText} from '../../language/default-labels/commonLabels';
|
|
11
|
+
|
|
12
|
+
interface RecordingDeletePopupProps {
|
|
13
|
+
modalVisible: boolean;
|
|
14
|
+
setModalVisible: React.Dispatch<SetStateAction<boolean>>;
|
|
15
|
+
onConfirm: () => void;
|
|
16
|
+
onCancel: () => void;
|
|
17
|
+
}
|
|
18
|
+
const RecordingDeletePopup = (props: RecordingDeletePopupProps) => {
|
|
19
|
+
const isDesktop = useIsDesktop()('popup');
|
|
20
|
+
const heading = 'DELETE';
|
|
21
|
+
const subheading =
|
|
22
|
+
"Are you sure want to delete the recording? This action can't be undone.";
|
|
23
|
+
|
|
24
|
+
const cancelBtnLabel = useString(cancelText)();
|
|
25
|
+
const deleteBtnLabel = 'DELETE';
|
|
26
|
+
return (
|
|
27
|
+
<Popup
|
|
28
|
+
modalVisible={props.modalVisible}
|
|
29
|
+
setModalVisible={props.setModalVisible}
|
|
30
|
+
showCloseIcon={false}
|
|
31
|
+
contentContainerStyle={styles.contentContainer}>
|
|
32
|
+
<Text style={styles.heading}>{heading}</Text>
|
|
33
|
+
<Spacer size={8} />
|
|
34
|
+
<Text style={styles.subHeading}>{subheading}</Text>
|
|
35
|
+
<Spacer size={32} />
|
|
36
|
+
<View style={isDesktop ? styles.btnContainer : styles.btnContainerMobile}>
|
|
37
|
+
<View style={isDesktop && {flex: 1}}>
|
|
38
|
+
<TertiaryButton
|
|
39
|
+
containerStyle={{
|
|
40
|
+
width: '100%',
|
|
41
|
+
height: 48,
|
|
42
|
+
paddingVertical: 12,
|
|
43
|
+
paddingHorizontal: 12,
|
|
44
|
+
borderRadius: ThemeConfig.BorderRadius.medium,
|
|
45
|
+
}}
|
|
46
|
+
textStyle={styles.btnText}
|
|
47
|
+
text={cancelBtnLabel}
|
|
48
|
+
onPress={() => {
|
|
49
|
+
props?.setModalVisible(false);
|
|
50
|
+
props?.onCancel();
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
</View>
|
|
54
|
+
<Spacer
|
|
55
|
+
size={isDesktop ? 10 : 20}
|
|
56
|
+
horizontal={isDesktop ? true : false}
|
|
57
|
+
/>
|
|
58
|
+
<View style={isDesktop && {flex: 1}}>
|
|
59
|
+
<PrimaryButton
|
|
60
|
+
containerStyle={{
|
|
61
|
+
minWidth: 'auto',
|
|
62
|
+
width: '100%',
|
|
63
|
+
borderRadius: ThemeConfig.BorderRadius.medium,
|
|
64
|
+
height: 48,
|
|
65
|
+
backgroundColor: $config.SEMANTIC_ERROR,
|
|
66
|
+
paddingVertical: 12,
|
|
67
|
+
paddingHorizontal: 12,
|
|
68
|
+
}}
|
|
69
|
+
textStyle={styles.btnText}
|
|
70
|
+
text={deleteBtnLabel}
|
|
71
|
+
onPress={() => {
|
|
72
|
+
props?.setModalVisible(false);
|
|
73
|
+
props?.onConfirm();
|
|
74
|
+
}}
|
|
75
|
+
/>
|
|
76
|
+
</View>
|
|
77
|
+
</View>
|
|
78
|
+
</Popup>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default RecordingDeletePopup;
|
|
83
|
+
|
|
84
|
+
const styles = StyleSheet.create({
|
|
85
|
+
btnContainer: {
|
|
86
|
+
flex: 1,
|
|
87
|
+
flexDirection: 'row',
|
|
88
|
+
justifyContent: 'center',
|
|
89
|
+
alignItems: 'center',
|
|
90
|
+
},
|
|
91
|
+
contentContainer: {
|
|
92
|
+
padding: 24,
|
|
93
|
+
maxWidth: 342,
|
|
94
|
+
},
|
|
95
|
+
btnText: {
|
|
96
|
+
fontWeight: '600',
|
|
97
|
+
fontSize: 16,
|
|
98
|
+
lineHeight: 24,
|
|
99
|
+
},
|
|
100
|
+
btnContainerMobile: {
|
|
101
|
+
flexDirection: 'column-reverse',
|
|
102
|
+
},
|
|
103
|
+
heading: {
|
|
104
|
+
fontFamily: ThemeConfig.FontFamily.sansPro,
|
|
105
|
+
fontWeight: '600',
|
|
106
|
+
fontSize: 22,
|
|
107
|
+
color: $config.SEMANTIC_ERROR,
|
|
108
|
+
},
|
|
109
|
+
subHeading: {
|
|
110
|
+
fontFamily: ThemeConfig.FontFamily.sansPro,
|
|
111
|
+
fontWeight: '400',
|
|
112
|
+
fontSize: 14,
|
|
113
|
+
color: $config.FONT_COLOR,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
@@ -3,8 +3,10 @@ import {View, Text} from 'react-native';
|
|
|
3
3
|
import {style} from './style';
|
|
4
4
|
import {RTableHeader, RTableBody, RTableFooter} from './recording-table';
|
|
5
5
|
import {useRecording} from '../../subComponents/recording/useRecording';
|
|
6
|
+
import events from '../../rtm-events-api';
|
|
7
|
+
import {EventNames} from '../../rtm-events';
|
|
6
8
|
|
|
7
|
-
function RecordingsDateTable() {
|
|
9
|
+
function RecordingsDateTable(props) {
|
|
8
10
|
const [state, setState] = React.useState({
|
|
9
11
|
status: 'idle',
|
|
10
12
|
data: {
|
|
@@ -21,11 +23,24 @@ function RecordingsDateTable() {
|
|
|
21
23
|
|
|
22
24
|
const {fetchRecordings} = useRecording();
|
|
23
25
|
|
|
24
|
-
const
|
|
26
|
+
const defaultPageNumber = 1;
|
|
27
|
+
const [currentPage, setCurrentPage] = useState(defaultPageNumber);
|
|
28
|
+
|
|
29
|
+
const onRecordingDeleteCallback = () => {
|
|
30
|
+
setCurrentPage(defaultPageNumber);
|
|
31
|
+
getRecordings(defaultPageNumber);
|
|
32
|
+
};
|
|
25
33
|
|
|
26
34
|
useEffect(() => {
|
|
35
|
+
events.on(EventNames.RECORDING_DELETED, onRecordingDeleteCallback);
|
|
36
|
+
return () => {
|
|
37
|
+
events.off(EventNames.RECORDING_DELETED, onRecordingDeleteCallback);
|
|
38
|
+
};
|
|
39
|
+
}, []);
|
|
40
|
+
|
|
41
|
+
const getRecordings = pageNumber => {
|
|
27
42
|
setState(prev => ({...prev, status: 'pending'}));
|
|
28
|
-
fetchRecordings(
|
|
43
|
+
fetchRecordings(pageNumber).then(
|
|
29
44
|
response =>
|
|
30
45
|
setState(prev => ({
|
|
31
46
|
...prev,
|
|
@@ -37,7 +52,11 @@ function RecordingsDateTable() {
|
|
|
37
52
|
})),
|
|
38
53
|
error => setState(prev => ({...prev, status: 'rejected', error})),
|
|
39
54
|
);
|
|
40
|
-
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
getRecordings(currentPage);
|
|
59
|
+
}, [currentPage]);
|
|
41
60
|
|
|
42
61
|
if (status === 'rejected') {
|
|
43
62
|
return (
|
|
@@ -49,7 +68,11 @@ function RecordingsDateTable() {
|
|
|
49
68
|
return (
|
|
50
69
|
<View style={style.ttable}>
|
|
51
70
|
<RTableHeader />
|
|
52
|
-
<RTableBody
|
|
71
|
+
<RTableBody
|
|
72
|
+
status={status}
|
|
73
|
+
recordings={recordings}
|
|
74
|
+
onDeleteAction={props?.onDeleteAction}
|
|
75
|
+
/>
|
|
53
76
|
<RTableFooter
|
|
54
77
|
currentPage={currentPage}
|
|
55
78
|
setCurrentPage={setCurrentPage}
|
|
@@ -8,6 +8,7 @@ import {recordingModalTitleIntn} from '../../language/default-labels/videoCallSc
|
|
|
8
8
|
|
|
9
9
|
interface ViewRecordingsModalProps {
|
|
10
10
|
setModalOpen: Dispatch<SetStateAction<boolean>>;
|
|
11
|
+
onDeleteAction: (recordingId: number) => void;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
// interface FetchRecordingResponse {
|
|
@@ -31,7 +32,7 @@ interface ViewRecordingsModalProps {
|
|
|
31
32
|
// error: null;
|
|
32
33
|
// }
|
|
33
34
|
export default function ViewRecordingsModal(props: ViewRecordingsModalProps) {
|
|
34
|
-
const {setModalOpen} = props;
|
|
35
|
+
const {setModalOpen, onDeleteAction} = props;
|
|
35
36
|
|
|
36
37
|
const recordingModalTitle = useString(recordingModalTitleIntn)();
|
|
37
38
|
|
|
@@ -44,7 +45,7 @@ export default function ViewRecordingsModal(props: ViewRecordingsModalProps) {
|
|
|
44
45
|
cancelable={false}
|
|
45
46
|
contentContainerStyle={style.mContainer}>
|
|
46
47
|
<View style={style.mbody}>
|
|
47
|
-
<RecordingsDateTable />
|
|
48
|
+
<RecordingsDateTable onDeleteAction={onDeleteAction} />
|
|
48
49
|
</View>
|
|
49
50
|
</RecordingsModal>
|
|
50
51
|
);
|
|
@@ -32,7 +32,7 @@ function RTableHeader() {
|
|
|
32
32
|
);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function RTableBody({status, recordings}) {
|
|
35
|
+
function RTableBody({status, recordings, onDeleteAction}) {
|
|
36
36
|
const renderTableBodyContent = () => {
|
|
37
37
|
if (status === 'idle' || status === 'pending') {
|
|
38
38
|
return <Loading background="transparent" text="Fetching recordings.." />;
|
|
@@ -104,97 +104,121 @@ function RTableBody({status, recordings}) {
|
|
|
104
104
|
</Text>
|
|
105
105
|
</View>
|
|
106
106
|
) : item?.download_url?.length > 0 ? (
|
|
107
|
-
|
|
108
|
-
<View
|
|
109
|
-
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
107
|
+
<View style={style.tactions}>
|
|
108
|
+
<View>
|
|
109
|
+
{item?.download_url?.map((link: string, i: number) => (
|
|
110
|
+
<View
|
|
111
|
+
style={[
|
|
112
|
+
style.tactions,
|
|
113
|
+
//if recording contains multiple parts then we need to add some space each row
|
|
114
|
+
i >= 1 ? {marginTop: 8} : {},
|
|
115
|
+
]}>
|
|
116
|
+
<View>
|
|
117
|
+
<IconButtonWithToolTip
|
|
118
|
+
hoverEffect={true}
|
|
119
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
120
|
+
containerStyle={style.iconButton}
|
|
121
|
+
iconProps={{
|
|
122
|
+
name: 'download',
|
|
123
|
+
iconType: 'plain',
|
|
124
|
+
iconSize: 20,
|
|
125
|
+
tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
|
|
126
|
+
}}
|
|
127
|
+
onPress={() => {
|
|
128
|
+
downloadRecording(link);
|
|
129
|
+
}}
|
|
130
|
+
/>
|
|
131
|
+
</View>
|
|
132
|
+
<View style={style.pl10}>
|
|
133
|
+
<IconButtonWithToolTip
|
|
134
|
+
// placement="bottom"
|
|
135
|
+
// toolTipMessage="Share"
|
|
136
|
+
hoverEffect={true}
|
|
137
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
138
|
+
containerStyle={style.iconButton}
|
|
139
|
+
iconProps={{
|
|
140
|
+
name: 'link-share',
|
|
141
|
+
iconType: 'plain',
|
|
142
|
+
iconSize: 20,
|
|
143
|
+
tintColor: `${$config.SECONDARY_ACTION_COLOR}`,
|
|
144
|
+
}}
|
|
145
|
+
onPress={async () => {
|
|
146
|
+
if (await Linking.canOpenURL(link)) {
|
|
147
|
+
await Linking.openURL(link);
|
|
148
|
+
}
|
|
149
|
+
}}
|
|
150
|
+
/>
|
|
151
|
+
</View>
|
|
152
|
+
<View style={[style.pl10]}>
|
|
153
|
+
<Tooltip
|
|
154
|
+
isClickable
|
|
155
|
+
placement="left"
|
|
156
|
+
toolTipMessage="Link Copied"
|
|
157
|
+
onPress={() => {
|
|
158
|
+
Clipboard.setString(link);
|
|
159
|
+
}}
|
|
160
|
+
toolTipIcon={
|
|
161
|
+
<>
|
|
162
|
+
<ImageIcon
|
|
163
|
+
iconType="plain"
|
|
164
|
+
name="tick-fill"
|
|
165
|
+
tintColor={$config.SEMANTIC_SUCCESS}
|
|
166
|
+
iconSize={20}
|
|
167
|
+
/>
|
|
168
|
+
<Spacer size={8} horizontal={true} />
|
|
169
|
+
</>
|
|
170
|
+
}
|
|
171
|
+
fontSize={12}
|
|
172
|
+
renderContent={() => {
|
|
173
|
+
return (
|
|
174
|
+
<PlatformWrapper>
|
|
175
|
+
{(isHovered: boolean) => (
|
|
176
|
+
<TouchableOpacity
|
|
177
|
+
style={[
|
|
178
|
+
isHovered
|
|
179
|
+
? style.iconButtonHoverEffect
|
|
180
|
+
: {},
|
|
181
|
+
style.iconShareLink,
|
|
182
|
+
]}
|
|
183
|
+
onPress={() => {
|
|
184
|
+
Clipboard.setString(link);
|
|
185
|
+
}}>
|
|
186
|
+
<ImageIcon
|
|
187
|
+
iconType="plain"
|
|
188
|
+
name="copy-link"
|
|
189
|
+
iconSize={20}
|
|
190
|
+
tintColor={
|
|
191
|
+
$config.SECONDARY_ACTION_COLOR
|
|
192
|
+
}
|
|
193
|
+
/>
|
|
194
|
+
</TouchableOpacity>
|
|
195
|
+
)}
|
|
196
|
+
</PlatformWrapper>
|
|
197
|
+
);
|
|
198
|
+
}}
|
|
199
|
+
/>
|
|
200
|
+
</View>
|
|
194
201
|
</View>
|
|
195
|
-
|
|
202
|
+
))}
|
|
196
203
|
</View>
|
|
197
|
-
|
|
204
|
+
<View style={[style.pl10]}>
|
|
205
|
+
<IconButtonWithToolTip
|
|
206
|
+
hoverEffect={true}
|
|
207
|
+
hoverEffectStyle={style.iconButtonHoverEffect}
|
|
208
|
+
containerStyle={style.iconButton}
|
|
209
|
+
iconProps={{
|
|
210
|
+
name: 'delete',
|
|
211
|
+
iconType: 'plain',
|
|
212
|
+
iconSize: 20,
|
|
213
|
+
tintColor: `${$config.SEMANTIC_ERROR}`,
|
|
214
|
+
}}
|
|
215
|
+
onPress={() => {
|
|
216
|
+
//show confirmation popup
|
|
217
|
+
onDeleteAction && onDeleteAction(item.id);
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
</View>
|
|
221
|
+
</View>
|
|
198
222
|
) : (
|
|
199
223
|
<View style={(style.tactions, {marginTop: 0})}>
|
|
200
224
|
<Text style={style.placeHolder}>No recordings found</Text>
|
|
@@ -13,7 +13,7 @@ export const style = StyleSheet.create({
|
|
|
13
13
|
flexShrink: 0,
|
|
14
14
|
// width: 620,
|
|
15
15
|
width: '100%',
|
|
16
|
-
maxWidth:
|
|
16
|
+
maxWidth: 680,
|
|
17
17
|
minWidth: 340,
|
|
18
18
|
height: 620,
|
|
19
19
|
maxHeight: 620,
|
|
@@ -98,9 +98,8 @@ export const style = StyleSheet.create({
|
|
|
98
98
|
},
|
|
99
99
|
td: {
|
|
100
100
|
flex: 1,
|
|
101
|
-
alignSelf: '
|
|
101
|
+
alignSelf: 'center',
|
|
102
102
|
justifyContent: 'center',
|
|
103
|
-
paddingHorizontal: 12,
|
|
104
103
|
// height: 100,
|
|
105
104
|
gap: 10,
|
|
106
105
|
},
|
|
@@ -127,7 +126,6 @@ export const style = StyleSheet.create({
|
|
|
127
126
|
tactions: {
|
|
128
127
|
display: 'flex',
|
|
129
128
|
flexDirection: 'row',
|
|
130
|
-
marginTop: -8,
|
|
131
129
|
},
|
|
132
130
|
tlink: {
|
|
133
131
|
color: $config.PRIMARY_ACTION_BRAND_COLOR,
|
|
@@ -102,7 +102,8 @@ type LogType = {
|
|
|
102
102
|
| 'whiteboard_screenshot'
|
|
103
103
|
| 'recording_start'
|
|
104
104
|
| 'recording_stop'
|
|
105
|
-
| 'recordings_get'
|
|
105
|
+
| 'recordings_get'
|
|
106
|
+
| 'recording_delete';
|
|
106
107
|
[LogSource.Events]: 'CUSTOM_EVENTS' | 'RTM_EVENTS';
|
|
107
108
|
[LogSource.CustomizationAPI]: 'Log';
|
|
108
109
|
[LogSource.SDK]: 'Log' | 'Event';
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
ToolbarItem,
|
|
35
35
|
ToolbarItemHide,
|
|
36
36
|
ToolbarItemLabel,
|
|
37
|
+
useSpeechToText,
|
|
37
38
|
} from 'customization-api';
|
|
38
39
|
import LayoutIconButton from '../../subComponents/LayoutIconButton';
|
|
39
40
|
import CaptionIcon from '../../../src/subComponents/caption/CaptionIcon';
|
|
@@ -259,6 +260,7 @@ const ActionSheetContent = props => {
|
|
|
259
260
|
const {defaultContent} = useContent();
|
|
260
261
|
const {waitingRoomUids} = useWaitingRoomContext();
|
|
261
262
|
const defaultContentRef = React.useRef(defaultContent);
|
|
263
|
+
const {addStreamMessageListener} = useSpeechToText();
|
|
262
264
|
|
|
263
265
|
React.useEffect(() => {
|
|
264
266
|
defaultContentRef.current = defaultContent;
|
|
@@ -335,6 +337,8 @@ const ActionSheetContent = props => {
|
|
|
335
337
|
},
|
|
336
338
|
];
|
|
337
339
|
});
|
|
340
|
+
// start listening to stream Message callback
|
|
341
|
+
addStreamMessageListener();
|
|
338
342
|
}, [sttLanguage]);
|
|
339
343
|
|
|
340
344
|
const isLiveStream = $config.EVENT_MODE && !$config.AUDIO_ROOM;
|
|
@@ -38,7 +38,7 @@ const WAITING_ROOM_STATUS_UPDATE = 'WAITING_ROOM_STATUS_UPDATE';
|
|
|
38
38
|
const WHITEBOARD_ACTIVE = 'WHITEBOARD_ACTIVE';
|
|
39
39
|
const BOARD_COLOR_CHANGED = 'BOARD_COLOR_CHANGED';
|
|
40
40
|
const WHITEBOARD_LAST_IMAGE_UPLOAD_POSITION = 'WHITEBOARD_L_I_U_P';
|
|
41
|
-
|
|
41
|
+
const RECORDING_DELETED = 'RECORDING_DELETED';
|
|
42
42
|
const EventNames = {
|
|
43
43
|
RECORDING_STATE_ATTRIBUTE,
|
|
44
44
|
RECORDING_STARTED_BY_ATTRIBUTE,
|
|
@@ -58,6 +58,7 @@ const EventNames = {
|
|
|
58
58
|
WHITEBOARD_ACTIVE,
|
|
59
59
|
BOARD_COLOR_CHANGED,
|
|
60
60
|
WHITEBOARD_LAST_IMAGE_UPLOAD_POSITION,
|
|
61
|
+
RECORDING_DELETED,
|
|
61
62
|
};
|
|
62
63
|
/** ***** EVENT NAMES ENDS ***** */
|
|
63
64
|
|
|
@@ -10,9 +10,16 @@ import hexadecimalTransparency from '../../utils/hexadecimalTransparency';
|
|
|
10
10
|
import {useString} from '../../utils/useString';
|
|
11
11
|
import {sttSettingSpokenLanguageText} from '../../language/default-labels/videoCallScreenLabels';
|
|
12
12
|
|
|
13
|
-
type WebStreamMessageArgs = [number, Uint8Array];
|
|
14
|
-
type NativeStreamMessageArgs = [
|
|
15
|
-
|
|
13
|
+
export type WebStreamMessageArgs = [number, Uint8Array];
|
|
14
|
+
export type NativeStreamMessageArgs = [
|
|
15
|
+
{},
|
|
16
|
+
number,
|
|
17
|
+
number,
|
|
18
|
+
Uint8Array,
|
|
19
|
+
number,
|
|
20
|
+
number,
|
|
21
|
+
];
|
|
22
|
+
export type StreamMessageArgs = WebStreamMessageArgs | NativeStreamMessageArgs;
|
|
16
23
|
|
|
17
24
|
interface CaptionProps {
|
|
18
25
|
captionTextStyle?: TextStyle;
|
|
@@ -76,6 +76,7 @@ export interface RecordingContextInterface {
|
|
|
76
76
|
isRecordingActive: boolean;
|
|
77
77
|
inProgress: boolean;
|
|
78
78
|
fetchRecordings?: (page: number) => Promise<RecordingsData>;
|
|
79
|
+
deleteRecording?: (id: number) => Promise<boolean>;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
const RecordingContext = createContext<RecordingContextInterface>({
|
|
@@ -625,6 +626,85 @@ const RecordingProvider = (props: RecordingProviderProps) => {
|
|
|
625
626
|
[roomId?.host, store.token],
|
|
626
627
|
);
|
|
627
628
|
|
|
629
|
+
const deleteRecording = useCallback(
|
|
630
|
+
(recordingId: number) => {
|
|
631
|
+
const requestId = getUniqueID();
|
|
632
|
+
const startReqTs = Date.now();
|
|
633
|
+
logger.debug(
|
|
634
|
+
LogSource.NetworkRest,
|
|
635
|
+
'recording_delete',
|
|
636
|
+
'Trying to delete recording recording id:' + recordingId,
|
|
637
|
+
{
|
|
638
|
+
recordingId,
|
|
639
|
+
},
|
|
640
|
+
);
|
|
641
|
+
return fetch(
|
|
642
|
+
`${$config.BACKEND_ENDPOINT}/v1/recording/${recordingId}?passphrase=${roomId?.host}`,
|
|
643
|
+
{
|
|
644
|
+
method: 'DELETE',
|
|
645
|
+
headers: {
|
|
646
|
+
'Content-Type': 'application/json',
|
|
647
|
+
authorization: store.token ? `Bearer ${store.token}` : '',
|
|
648
|
+
'X-Request-Id': requestId,
|
|
649
|
+
'X-Session-Id': logger.getSessionId(),
|
|
650
|
+
},
|
|
651
|
+
},
|
|
652
|
+
)
|
|
653
|
+
.then(async response => {
|
|
654
|
+
const endReqTs = Date.now();
|
|
655
|
+
if (response.ok) {
|
|
656
|
+
logger.debug(
|
|
657
|
+
LogSource.NetworkRest,
|
|
658
|
+
'recording_delete',
|
|
659
|
+
'delete recording successfull',
|
|
660
|
+
response,
|
|
661
|
+
{
|
|
662
|
+
recordingId,
|
|
663
|
+
startReqTs,
|
|
664
|
+
endReqTs,
|
|
665
|
+
latency: endReqTs - startReqTs,
|
|
666
|
+
requestId,
|
|
667
|
+
},
|
|
668
|
+
);
|
|
669
|
+
return Promise.resolve(true);
|
|
670
|
+
} else {
|
|
671
|
+
logger.error(
|
|
672
|
+
LogSource.NetworkRest,
|
|
673
|
+
'recording_delete',
|
|
674
|
+
'Error while deleting recording',
|
|
675
|
+
response,
|
|
676
|
+
{
|
|
677
|
+
recordingId,
|
|
678
|
+
startReqTs,
|
|
679
|
+
endReqTs,
|
|
680
|
+
latency: endReqTs - startReqTs,
|
|
681
|
+
requestId,
|
|
682
|
+
},
|
|
683
|
+
);
|
|
684
|
+
return Promise.reject(false);
|
|
685
|
+
}
|
|
686
|
+
})
|
|
687
|
+
.catch(error => {
|
|
688
|
+
const endReqTs = Date.now();
|
|
689
|
+
logger.error(
|
|
690
|
+
LogSource.NetworkRest,
|
|
691
|
+
'recording_delete',
|
|
692
|
+
'Error while deleting recording',
|
|
693
|
+
error,
|
|
694
|
+
{
|
|
695
|
+
recordingId,
|
|
696
|
+
startReqTs,
|
|
697
|
+
endReqTs,
|
|
698
|
+
latency: endReqTs - startReqTs,
|
|
699
|
+
requestId,
|
|
700
|
+
},
|
|
701
|
+
);
|
|
702
|
+
return Promise.reject(false);
|
|
703
|
+
});
|
|
704
|
+
},
|
|
705
|
+
[roomId?.host, store.token],
|
|
706
|
+
);
|
|
707
|
+
|
|
628
708
|
// Events
|
|
629
709
|
useEffect(() => {
|
|
630
710
|
events.on(EventNames.RECORDING_STATE_ATTRIBUTE, data => {
|
|
@@ -924,6 +1004,7 @@ const RecordingProvider = (props: RecordingProviderProps) => {
|
|
|
924
1004
|
stopRecording,
|
|
925
1005
|
isRecordingActive,
|
|
926
1006
|
fetchRecordings,
|
|
1007
|
+
deleteRecording,
|
|
927
1008
|
}}>
|
|
928
1009
|
{props.children}
|
|
929
1010
|
</RecordingContext.Provider>
|
|
@@ -3,12 +3,20 @@ import {
|
|
|
3
3
|
SidePanelType,
|
|
4
4
|
customEvents,
|
|
5
5
|
useContent,
|
|
6
|
+
useRtc,
|
|
6
7
|
useSTTAPI,
|
|
7
8
|
useSidePanel,
|
|
8
9
|
} from 'customization-api';
|
|
9
10
|
import useTranscriptDownload from '../subComponents/caption/useTranscriptDownload';
|
|
10
11
|
import {useCaption} from '../subComponents/caption/useCaption';
|
|
11
12
|
import {LanguageType} from '../subComponents/caption/utils';
|
|
13
|
+
import useStreamMessageUtils from '../subComponents/caption/useStreamMessageUtils';
|
|
14
|
+
import {
|
|
15
|
+
NativeStreamMessageArgs,
|
|
16
|
+
StreamMessageArgs,
|
|
17
|
+
WebStreamMessageArgs,
|
|
18
|
+
} from '../subComponents/caption/Caption';
|
|
19
|
+
import {isWebInternal} from './common';
|
|
12
20
|
|
|
13
21
|
const useSpeechToText = () => {
|
|
14
22
|
const {
|
|
@@ -18,6 +26,8 @@ const useSpeechToText = () => {
|
|
|
18
26
|
captionObj: captionData,
|
|
19
27
|
prevSpeakerRef,
|
|
20
28
|
activeSpeakerRef,
|
|
29
|
+
isSTTListenerAdded,
|
|
30
|
+
setIsSTTListenerAdded,
|
|
21
31
|
} = useCaption();
|
|
22
32
|
const {setSidePanel} = useSidePanel();
|
|
23
33
|
|
|
@@ -26,6 +36,8 @@ const useSpeechToText = () => {
|
|
|
26
36
|
|
|
27
37
|
const isAuthorizedSTTUserRef = useRef(isAuthorizedSTTUser);
|
|
28
38
|
const defaultContentRef = useRef(defaultContent);
|
|
39
|
+
const {RtcEngineUnsafe} = useRtc();
|
|
40
|
+
const {streamMessageCallback} = useStreamMessageUtils();
|
|
29
41
|
|
|
30
42
|
const showTranscriptPanel = (show: boolean) => {
|
|
31
43
|
show
|
|
@@ -40,7 +52,7 @@ const useSpeechToText = () => {
|
|
|
40
52
|
useEffect(() => {
|
|
41
53
|
if (!$config.ENABLE_STT) {
|
|
42
54
|
//throw new Error('Speech To Text is not enabled');
|
|
43
|
-
console.error('Speech To Text is not enabled')
|
|
55
|
+
console.error('Speech To Text is not enabled');
|
|
44
56
|
}
|
|
45
57
|
}, []);
|
|
46
58
|
|
|
@@ -76,6 +88,24 @@ const useSpeechToText = () => {
|
|
|
76
88
|
return await stop();
|
|
77
89
|
};
|
|
78
90
|
|
|
91
|
+
const addStreamMessageListener = () => {
|
|
92
|
+
!isSTTListenerAdded &&
|
|
93
|
+
RtcEngineUnsafe.addListener(
|
|
94
|
+
'onStreamMessage',
|
|
95
|
+
handleStreamMessageCallback,
|
|
96
|
+
);
|
|
97
|
+
};
|
|
98
|
+
const handleStreamMessageCallback = (...args: StreamMessageArgs) => {
|
|
99
|
+
setIsSTTListenerAdded(true);
|
|
100
|
+
if (isWebInternal()) {
|
|
101
|
+
const [uid, data] = args as WebStreamMessageArgs;
|
|
102
|
+
streamMessageCallback([uid, data]);
|
|
103
|
+
} else {
|
|
104
|
+
const [, uid, , data] = args as NativeStreamMessageArgs;
|
|
105
|
+
streamMessageCallback([uid, data]);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
79
109
|
const changeSpeakingLanguage = async (language: LanguageType[]) => {
|
|
80
110
|
if (!isAuthorizedSTTUserRef.current) {
|
|
81
111
|
throw new Error('Invalid user');
|
|
@@ -96,6 +126,7 @@ const useSpeechToText = () => {
|
|
|
96
126
|
showCaptionPanel,
|
|
97
127
|
transcriptData,
|
|
98
128
|
captionData,
|
|
129
|
+
addStreamMessageListener,
|
|
99
130
|
}
|
|
100
131
|
: {};
|
|
101
132
|
};
|