agora-appbuilder-core 4.0.0-beta.11 → 4.0.0-beta.13
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/customization-implementation/createHook.ts +24 -6
- package/template/customization-implementation/index.ts +1 -1
- package/template/customization-implementation/useCustomization.tsx +5 -7
- package/template/index.wsdk.tsx +8 -1
- package/template/src/App.tsx +1 -1
- package/template/src/AppWrapper.tsx +12 -2
- package/template/src/SDKAppWrapper.tsx +60 -64
- package/template/src/components/DeviceConfigure.tsx +56 -16
- package/template/src/components/Navigation.tsx +15 -1
- package/template/src/components/RTMConfigure.tsx +19 -2
- package/template/src/components/SdkApiContext.tsx +161 -0
- package/template/src/components/meeting-info/useMeetingInfo.tsx +24 -0
- package/template/src/components/precall/usePreCall.tsx +25 -1
- package/template/src/components/useVideoCall.tsx +20 -1
- package/template/src/pages/Create.tsx +2 -19
- package/template/src/pages/VideoCall.tsx +101 -15
- package/template/src/pages/video-call/VideoCallScreen.tsx +0 -27
- package/template/src/rtm/RTMEngine.ts +8 -0
- package/template/src/rtm-events-api/Events.ts +1 -0
- package/template/src/utils/SdkEvents.ts +23 -14
- package/template/src/utils/SdkMethodEvents.ts +81 -0
- package/template/src/utils/useJoinMeeting.ts +10 -4
- package/template/webpack.rsdk.config.js +1 -2
package/package.json
CHANGED
|
@@ -9,25 +9,43 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
import React, {
|
|
12
|
+
import React, {useContext} from 'react';
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
*
|
|
15
15
|
* @param context - any context data which we want to extract the data.
|
|
16
16
|
* @returns useContextWithSelector in which we can pass selector function to extract data from the context that we passed.
|
|
17
17
|
*/
|
|
18
18
|
function createHook<T>(context: React.Context<T>) {
|
|
19
|
-
|
|
20
19
|
function useContextWithSelector<U>(contextSelector: (data: T) => U): U;
|
|
21
20
|
function useContextWithSelector(): T;
|
|
22
21
|
/**
|
|
23
|
-
*
|
|
22
|
+
*
|
|
24
23
|
* @param contextSelector is used to pass callback function used to select data from the context data
|
|
25
24
|
* @returns the data selected from the context
|
|
26
25
|
*/
|
|
27
26
|
function useContextWithSelector<U>(contextSelector?: (data: T) => U): U | T {
|
|
28
27
|
const data = useContext(context);
|
|
29
|
-
return contextSelector ? contextSelector(data) : data
|
|
28
|
+
return contextSelector ? contextSelector(data) : data;
|
|
30
29
|
}
|
|
31
30
|
return useContextWithSelector;
|
|
32
31
|
}
|
|
33
|
-
|
|
32
|
+
|
|
33
|
+
export function createConcealedHook<T, V>(
|
|
34
|
+
context: React.Context<T>,
|
|
35
|
+
preselect: (data: T) => V,
|
|
36
|
+
) {
|
|
37
|
+
function useContextWithSelector<U>(contextSelector: (data: V) => U): U;
|
|
38
|
+
function useContextWithSelector(): V;
|
|
39
|
+
/**
|
|
40
|
+
*
|
|
41
|
+
* @param contextSelector is used to pass callback function used to select data from the context data
|
|
42
|
+
* @returns the data selected from the context
|
|
43
|
+
*/
|
|
44
|
+
function useContextWithSelector<U>(contextSelector?: (data: V) => U): U | V {
|
|
45
|
+
const data = useContext(context);
|
|
46
|
+
return contextSelector ? contextSelector(preselect(data)) : preselect(data);
|
|
47
|
+
}
|
|
48
|
+
return useContextWithSelector;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default createHook;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {default as createHook} from './createHook';
|
|
1
|
+
export {default as createHook, createConcealedHook} from './createHook';
|
|
2
2
|
export {CustomizationProvider, useCustomization} from './useCustomization';
|
|
3
3
|
export type {CustomizationProviderProps} from './useCustomization';
|
|
4
4
|
export {default as customizationConfig} from 'customization';
|
|
@@ -9,22 +9,20 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
import React from 'react';
|
|
12
|
+
import React, {useContext} from 'react';
|
|
13
13
|
import {CustomizationApiInterface} from 'customization-api';
|
|
14
14
|
import customizationConfig from 'customization';
|
|
15
15
|
import createHook from './createHook';
|
|
16
|
+
import {SdkApiContext} from '../src/components/SdkApiContext';
|
|
16
17
|
|
|
17
18
|
const CustomizationContext: React.Context<CustomizationApiInterface> =
|
|
18
19
|
React.createContext(customizationConfig);
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
value: CustomizationApiInterface;
|
|
23
|
-
}
|
|
21
|
+
const CustomizationProvider: React.FC = (props) => {
|
|
22
|
+
const {customize: userCustomization} = useContext(SdkApiContext);
|
|
24
23
|
|
|
25
|
-
const CustomizationProvider = (props: CustomizationProviderProps) => {
|
|
26
24
|
return (
|
|
27
|
-
<CustomizationContext.Provider value={
|
|
25
|
+
<CustomizationContext.Provider value={userCustomization.customization}>
|
|
28
26
|
{props.children}
|
|
29
27
|
</CustomizationContext.Provider>
|
|
30
28
|
);
|
package/template/index.wsdk.tsx
CHANGED
|
@@ -12,12 +12,19 @@ export * from 'customization-implementation';
|
|
|
12
12
|
|
|
13
13
|
interface AppBuilderWebSdkInterface extends AppBuilderSdkApiInterface {}
|
|
14
14
|
|
|
15
|
+
const clearEvent = {
|
|
16
|
+
clear: () => {},
|
|
17
|
+
};
|
|
18
|
+
|
|
15
19
|
const AppBuilderWebSdkApi: AppBuilderWebSdkInterface = {
|
|
16
20
|
...AppBuilderSdkApi,
|
|
17
21
|
// Override customize function for web-sdk
|
|
18
22
|
customize: (customization) => {
|
|
19
|
-
SDKEvents.
|
|
23
|
+
SDKEvents.emit('addFpe', customization);
|
|
24
|
+
clearEvent.clear = SDKEvents.on('addFpeInit', () => {
|
|
25
|
+
console.log('addFpeInit called');
|
|
20
26
|
SDKEvents.emit('addFpe', customization);
|
|
27
|
+
clearEvent.clear();
|
|
21
28
|
});
|
|
22
29
|
},
|
|
23
30
|
};
|
package/template/src/App.tsx
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
import React, {useState} from 'react';
|
|
12
|
+
import React, {useState, useContext} from 'react';
|
|
13
13
|
import {Platform} from 'react-native';
|
|
14
14
|
import Join from './pages/Join';
|
|
15
15
|
import VideoCall from './pages/VideoCall';
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
import React from 'react';
|
|
12
|
+
import React, {useContext} from 'react';
|
|
13
13
|
import {Router} from './components/Router';
|
|
14
14
|
import Navigation from './components/Navigation';
|
|
15
15
|
import {StorageProvider} from './components/StorageContext';
|
|
@@ -32,6 +32,8 @@ import {LanguageProvider} from './language/useLanguage';
|
|
|
32
32
|
import {PropsConsumer} from 'agora-rn-uikit';
|
|
33
33
|
import ToastComponent from './components/ToastComponent';
|
|
34
34
|
import {ToastContext, ToastProvider} from './components/useToast';
|
|
35
|
+
import {SdkApiContext} from './components/SdkApiContext';
|
|
36
|
+
import isSDK from './utils/isSDK';
|
|
35
37
|
|
|
36
38
|
interface AppWrapperProps {
|
|
37
39
|
children: React.ReactNode;
|
|
@@ -73,6 +75,8 @@ const AppWrapper = (props: AppWrapperProps) => {
|
|
|
73
75
|
return React.Fragment;
|
|
74
76
|
});
|
|
75
77
|
|
|
78
|
+
const {join: SdkJoinState} = useContext(SdkApiContext);
|
|
79
|
+
|
|
76
80
|
return (
|
|
77
81
|
<AppRoot>
|
|
78
82
|
<ImageBackgroundComp bg={$config.BG} color={$config.BACKGROUND_COLOR}>
|
|
@@ -88,7 +92,13 @@ const AppWrapper = (props: AppWrapperProps) => {
|
|
|
88
92
|
</ToastContext.Consumer>
|
|
89
93
|
<StorageProvider>
|
|
90
94
|
<GraphQLProvider>
|
|
91
|
-
<Router
|
|
95
|
+
<Router
|
|
96
|
+
/*@ts-ignore Router will be memory Router in sdk*/
|
|
97
|
+
initialEntries={[
|
|
98
|
+
isSDK && SdkJoinState.phrase
|
|
99
|
+
? `/${SdkJoinState.phrase}`
|
|
100
|
+
: '',
|
|
101
|
+
]}>
|
|
92
102
|
<SessionProvider>
|
|
93
103
|
<ColorConfigure>
|
|
94
104
|
<DimensionProvider>
|
|
@@ -1,86 +1,82 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import {CustomizationApiInterface, customize} from 'customization-api';
|
|
1
|
+
import React from 'react';
|
|
3
2
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
CustomizationApiInterface,
|
|
4
|
+
customize,
|
|
5
|
+
MeetingInfoContextInterface,
|
|
6
|
+
customEvents,
|
|
7
|
+
} from 'customization-api';
|
|
8
|
+
import {CustomizationProvider} from 'customization-implementation';
|
|
9
|
+
import SDKEvents, {userEventsMapInterface} from './utils/SdkEvents';
|
|
10
|
+
import SDKMethodEventsManager from './utils/SdkMethodEvents';
|
|
9
11
|
import App from './App';
|
|
12
|
+
import SdkApiContextProvider from './components/SdkApiContext';
|
|
13
|
+
import {Unsubscribe} from 'nanoevents';
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
pin: string;
|
|
19
|
-
},
|
|
20
|
-
) => void;
|
|
21
|
-
'ready-to-join': (meetingTitle: string, devices: MediaDeviceInfo[]) => void;
|
|
22
|
-
join: (
|
|
23
|
-
meetingTitle: string,
|
|
24
|
-
devices: MediaDeviceInfo[],
|
|
25
|
-
isHost: boolean,
|
|
26
|
-
) => void;
|
|
27
|
-
}
|
|
15
|
+
// type makeAsync<T extends (...p: any) => void> = (
|
|
16
|
+
// ...p: Parameters<T>
|
|
17
|
+
// ) => PromiseLike<ReturnType<T>>;
|
|
18
|
+
//
|
|
19
|
+
// type takeOnlyFirstParam<T extends (...p: any) => void> = (
|
|
20
|
+
// p: Parameters<T>[0],
|
|
21
|
+
// ) => ReturnType<T>;
|
|
28
22
|
|
|
29
|
-
export interface
|
|
23
|
+
export interface SdkMethodEvents {
|
|
30
24
|
customize: (customization: CustomizationApiInterface) => void;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
on: <T extends keyof userEventsMapInterface>(
|
|
36
|
-
userEventName: T,
|
|
37
|
-
callBack: userEventsMapInterface[T],
|
|
38
|
-
) => Unsubscribe;
|
|
25
|
+
join(
|
|
26
|
+
roomid: string | Partial<MeetingInfoContextInterface['data']>,
|
|
27
|
+
skipPrecall?: boolean,
|
|
28
|
+
): MeetingInfoContextInterface['data'];
|
|
39
29
|
}
|
|
40
30
|
|
|
41
|
-
|
|
31
|
+
// interface AppBuilderSdkApiInterface {
|
|
32
|
+
// customize: makeAsync<SdkMethodEvents['customize']>;
|
|
33
|
+
// joinRoom: makeAsync<takeOnlyFirstParam<SdkMethodEvents['join']>>;
|
|
34
|
+
// joinPrecall: makeAsync<takeOnlyFirstParam<SdkMethodEvents['join']>>;
|
|
35
|
+
// createCustomization: (
|
|
36
|
+
// customization: CustomizationApiInterface,
|
|
37
|
+
// ) => CustomizationApiInterface;
|
|
38
|
+
// on: <T extends keyof userEventsMapInterface>(
|
|
39
|
+
// userEventName: T,
|
|
40
|
+
// callBack: userEventsMapInterface[T],
|
|
41
|
+
// ) => Unsubscribe;
|
|
42
|
+
// }
|
|
42
43
|
|
|
43
|
-
export const AppBuilderSdkApi
|
|
44
|
-
customize: (customization: CustomizationApiInterface) => {
|
|
45
|
-
|
|
44
|
+
export const AppBuilderSdkApi = {
|
|
45
|
+
customize: async (customization: CustomizationApiInterface) => {
|
|
46
|
+
return await SDKMethodEventsManager.emit('customize', customization);
|
|
47
|
+
},
|
|
48
|
+
customEvents: customEvents,
|
|
49
|
+
join: async (roomDetails: string) => {
|
|
50
|
+
await SDKMethodEventsManager.emit('join', roomDetails, false);
|
|
51
|
+
},
|
|
52
|
+
joinRoom: async (
|
|
53
|
+
roomDetails: string | Partial<MeetingInfoContextInterface['data']>,
|
|
54
|
+
) => {
|
|
55
|
+
return await SDKMethodEventsManager.emit('join', roomDetails, true);
|
|
56
|
+
},
|
|
57
|
+
joinPrecall: async (
|
|
58
|
+
roomDetails: string | Partial<MeetingInfoContextInterface['data']>,
|
|
59
|
+
) => {
|
|
60
|
+
const t = await SDKMethodEventsManager.emit('join', roomDetails);
|
|
61
|
+
return t as unknown as [MeetingInfoContextInterface['data'], () => {}];
|
|
46
62
|
},
|
|
47
|
-
join: (roomid: string) =>
|
|
48
|
-
new Promise((resolve, reject) => {
|
|
49
|
-
if (joinInit) {
|
|
50
|
-
console.log('[SDKEvents] Join listener emitted preemptive');
|
|
51
|
-
SDKEvents.emit('joinMeetingWithPhrase', roomid, resolve, reject);
|
|
52
|
-
}
|
|
53
|
-
SDKEvents.on('joinInit', () => {
|
|
54
|
-
if (!joinInit) {
|
|
55
|
-
console.log('[SDKEvents] Join listener emitted');
|
|
56
|
-
SDKEvents.emit('joinMeetingWithPhrase', roomid, resolve, reject);
|
|
57
|
-
joinInit = true;
|
|
58
|
-
}
|
|
59
|
-
});
|
|
60
|
-
}),
|
|
61
63
|
createCustomization: customize,
|
|
62
|
-
on:
|
|
64
|
+
on: <T extends keyof userEventsMapInterface>(
|
|
65
|
+
userEventName: T,
|
|
66
|
+
cb: userEventsMapInterface[T],
|
|
67
|
+
): Unsubscribe => {
|
|
63
68
|
console.log('SDKEvents: Event Registered', userEventName);
|
|
64
69
|
return SDKEvents.on(userEventName, cb);
|
|
65
70
|
},
|
|
66
71
|
};
|
|
67
72
|
|
|
68
73
|
const SDKAppWrapper = () => {
|
|
69
|
-
const [fpe, setFpe] = useState(customizationConfig);
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
SDKEvents.on('addFpe', (sdkFpeConfig) => {
|
|
72
|
-
console.log('SDKEvents: addFpe event called');
|
|
73
|
-
setFpe(sdkFpeConfig);
|
|
74
|
-
});
|
|
75
|
-
SDKEvents.emit('addFpeInit');
|
|
76
|
-
// Join event consumed in Create.tsx
|
|
77
|
-
}, []);
|
|
78
74
|
return (
|
|
79
|
-
|
|
80
|
-
<CustomizationProvider
|
|
75
|
+
<SdkApiContextProvider>
|
|
76
|
+
<CustomizationProvider>
|
|
81
77
|
<App />
|
|
82
78
|
</CustomizationProvider>
|
|
83
|
-
|
|
79
|
+
</SdkApiContextProvider>
|
|
84
80
|
);
|
|
85
81
|
};
|
|
86
82
|
|
|
@@ -45,11 +45,15 @@ type deviceKind = deviceInfo['kind'];
|
|
|
45
45
|
|
|
46
46
|
const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
47
47
|
const rtc = useRtc();
|
|
48
|
-
const [
|
|
49
|
-
const [
|
|
50
|
-
const [
|
|
48
|
+
const [uiSelectedCam, setUiSelectedCam] = useState('');
|
|
49
|
+
const [uiSelectedMic, setUiSelectedMic] = useState('');
|
|
50
|
+
const [uiSelectedSpeaker, setUiSelectedSpeaker] = useState('');
|
|
51
51
|
const [deviceList, setDeviceList] = useState<deviceInfo[]>([]);
|
|
52
52
|
|
|
53
|
+
const micSelectInProgress = useRef(false);
|
|
54
|
+
const camSelectInProgress = useRef(false);
|
|
55
|
+
const speakerSelectInProgress = useRef(false);
|
|
56
|
+
|
|
53
57
|
const {primaryColor} = useContext(ColorContext);
|
|
54
58
|
const {store, setStore} = useContext(StorageContext);
|
|
55
59
|
const {rememberedDevicesList, activeDeviceId} = store;
|
|
@@ -241,7 +245,7 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
241
245
|
|
|
242
246
|
if (activeDeviceId && deviceList.length > 0) {
|
|
243
247
|
// If stream exists and selected devices are empty, check for devices again
|
|
244
|
-
if (!
|
|
248
|
+
if (!uiSelectedCam || uiSelectedCam.trim().length == 0) {
|
|
245
249
|
log(logTag, 'cam: Device list populated but No selected cam');
|
|
246
250
|
const currentVideoDevice = getAgoraTrackDeviceId('video');
|
|
247
251
|
const {videoinput: storedVideoInput} = activeDeviceId;
|
|
@@ -259,7 +263,7 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
259
263
|
}
|
|
260
264
|
}
|
|
261
265
|
|
|
262
|
-
if (!
|
|
266
|
+
if (!uiSelectedMic || uiSelectedMic.trim().length == 0) {
|
|
263
267
|
log(logTag, 'mic: Device list populated but No selected mic');
|
|
264
268
|
const currentAudioDevice = getAgoraTrackDeviceId('audio');
|
|
265
269
|
const {audioinput: storedAudioInput} = activeDeviceId;
|
|
@@ -277,7 +281,7 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
277
281
|
}
|
|
278
282
|
}
|
|
279
283
|
|
|
280
|
-
if (!
|
|
284
|
+
if (!uiSelectedSpeaker || uiSelectedSpeaker.trim().length == 0) {
|
|
281
285
|
log(logTag, 'speaker: Device list populated but No selected speaker');
|
|
282
286
|
const {audiooutput: storedAudioOutput} = activeDeviceId;
|
|
283
287
|
const defaultSpeaker = deviceList.find(
|
|
@@ -323,17 +327,17 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
323
327
|
const {logTag, currentDevice, setCurrentDevice} = {
|
|
324
328
|
audioinput: {
|
|
325
329
|
logTag: 'mic: on-microphone-changed',
|
|
326
|
-
currentDevice:
|
|
330
|
+
currentDevice: uiSelectedMic,
|
|
327
331
|
setCurrentDevice: setSelectedMic,
|
|
328
332
|
},
|
|
329
333
|
audiooutput: {
|
|
330
334
|
logTag: 'speaker: on-speaker-changed',
|
|
331
|
-
currentDevice:
|
|
335
|
+
currentDevice: uiSelectedSpeaker,
|
|
332
336
|
setCurrentDevice: setSelectedSpeaker,
|
|
333
337
|
},
|
|
334
338
|
videoinput: {
|
|
335
339
|
logTag: 'cam: on-camera-changed',
|
|
336
|
-
currentDevice:
|
|
340
|
+
currentDevice: uiSelectedCam,
|
|
337
341
|
setCurrentDevice: setSelectedCam,
|
|
338
342
|
},
|
|
339
343
|
}[changedDevice.kind];
|
|
@@ -398,27 +402,45 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
398
402
|
useEffect(() => {
|
|
399
403
|
log('previous devicelist updated', deviceList);
|
|
400
404
|
AgoraRTC.onMicrophoneChanged = commonOnChangedEvent;
|
|
401
|
-
|
|
405
|
+
return () => {
|
|
406
|
+
AgoraRTC.onMicrophoneChanged = null;
|
|
407
|
+
};
|
|
408
|
+
}, [uiSelectedMic, deviceList]);
|
|
402
409
|
|
|
403
410
|
useEffect(() => {
|
|
404
411
|
AgoraRTC.onPlaybackDeviceChanged = commonOnChangedEvent;
|
|
405
|
-
|
|
412
|
+
return () => {
|
|
413
|
+
AgoraRTC.onPlaybackDeviceChanged = null;
|
|
414
|
+
};
|
|
415
|
+
}, [uiSelectedSpeaker, deviceList]);
|
|
406
416
|
|
|
407
417
|
useEffect(() => {
|
|
408
418
|
AgoraRTC.onCameraChanged = commonOnChangedEvent;
|
|
409
|
-
|
|
419
|
+
return () => {
|
|
420
|
+
AgoraRTC.onCameraChanged = null;
|
|
421
|
+
};
|
|
422
|
+
}, [uiSelectedCam, deviceList]);
|
|
410
423
|
|
|
411
424
|
const setSelectedMic = (deviceId: deviceId) => {
|
|
412
425
|
log('mic: setting to', deviceId);
|
|
413
426
|
return new Promise((res, rej) => {
|
|
427
|
+
if (micSelectInProgress.current) {
|
|
428
|
+
const e = new Error('Change already in progress');
|
|
429
|
+
console.error('DeviceConfigure: Error setting mic', e);
|
|
430
|
+
rej(e);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
micSelectInProgress.current = true;
|
|
414
434
|
RtcEngine.changeMic(
|
|
415
435
|
deviceId,
|
|
416
436
|
() => {
|
|
417
437
|
syncSelectedDeviceUi('audioinput');
|
|
418
438
|
updateActiveDeviceId('audioinput', deviceId);
|
|
439
|
+
micSelectInProgress.current = false;
|
|
419
440
|
res(null);
|
|
420
441
|
},
|
|
421
442
|
(e: any) => {
|
|
443
|
+
micSelectInProgress.current = false;
|
|
422
444
|
console.error('DeviceConfigure: Error setting mic', e);
|
|
423
445
|
rej(e);
|
|
424
446
|
},
|
|
@@ -429,14 +451,23 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
429
451
|
const setSelectedCam = (deviceId: deviceId) => {
|
|
430
452
|
log('cam: setting to', deviceId);
|
|
431
453
|
return new Promise((res, rej) => {
|
|
454
|
+
if (camSelectInProgress.current) {
|
|
455
|
+
const e = new Error('Change already in progress');
|
|
456
|
+
console.error('DeviceConfigure: Error setting webcam', e);
|
|
457
|
+
rej(e);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
camSelectInProgress.current = true;
|
|
432
461
|
RtcEngine.changeCamera(
|
|
433
462
|
deviceId,
|
|
434
463
|
() => {
|
|
435
464
|
syncSelectedDeviceUi('videoinput');
|
|
436
465
|
updateActiveDeviceId('videoinput', deviceId);
|
|
466
|
+
camSelectInProgress.current = false;
|
|
437
467
|
res(null);
|
|
438
468
|
},
|
|
439
469
|
(e: any) => {
|
|
470
|
+
camSelectInProgress.current = false;
|
|
440
471
|
console.error('Device Configure: Error setting webcam', e);
|
|
441
472
|
rej(e);
|
|
442
473
|
},
|
|
@@ -447,16 +478,25 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
447
478
|
const setSelectedSpeaker = (deviceId: deviceId) => {
|
|
448
479
|
log('speaker: setting to', deviceId);
|
|
449
480
|
return new Promise((res, rej) => {
|
|
481
|
+
if (speakerSelectInProgress.current) {
|
|
482
|
+
const e = new Error('Change already in progress');
|
|
483
|
+
console.error('DeviceConfigure: Error setting speaker', e);
|
|
484
|
+
rej(e);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
speakerSelectInProgress.current = true;
|
|
450
488
|
RtcEngine.changeSpeaker(
|
|
451
489
|
deviceId,
|
|
452
490
|
() => {
|
|
453
491
|
setUiSelectedSpeaker(deviceId);
|
|
454
492
|
updateActiveDeviceId('audiooutput', deviceId);
|
|
493
|
+
speakerSelectInProgress.current = false;
|
|
455
494
|
res(null);
|
|
456
495
|
},
|
|
457
496
|
(e: any) => {
|
|
497
|
+
speakerSelectInProgress.current = false;
|
|
458
498
|
console.error('Device Configure: Error setting speaker', e);
|
|
459
|
-
rej(
|
|
499
|
+
rej(uiSelectedSpeaker);
|
|
460
500
|
},
|
|
461
501
|
);
|
|
462
502
|
});
|
|
@@ -517,11 +557,11 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
|
|
|
517
557
|
return (
|
|
518
558
|
<DeviceContext.Provider
|
|
519
559
|
value={{
|
|
520
|
-
selectedCam,
|
|
560
|
+
selectedCam: uiSelectedCam,
|
|
521
561
|
setSelectedCam,
|
|
522
|
-
selectedMic,
|
|
562
|
+
selectedMic: uiSelectedMic,
|
|
523
563
|
setSelectedMic,
|
|
524
|
-
selectedSpeaker,
|
|
564
|
+
selectedSpeaker: uiSelectedSpeaker,
|
|
525
565
|
setSelectedSpeaker,
|
|
526
566
|
deviceList,
|
|
527
567
|
setDeviceList,
|
|
@@ -9,9 +9,23 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
import React from 'react';
|
|
12
|
+
import React, {useEffect, useContext} from 'react';
|
|
13
|
+
import {SdkApiContext} from './SdkApiContext';
|
|
14
|
+
import {useHistory} from './Router';
|
|
15
|
+
import isSDK from '../utils/isSDK';
|
|
13
16
|
|
|
14
17
|
const Navigation = () => {
|
|
18
|
+
const {join: SdkJoinState} = useContext(SdkApiContext);
|
|
19
|
+
|
|
20
|
+
const history = useHistory();
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (isSDK() && SdkJoinState.initialized) {
|
|
24
|
+
if (SdkJoinState.phrase) {
|
|
25
|
+
history.push(`/${SdkJoinState.phrase}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, [SdkJoinState]);
|
|
15
29
|
return <></>;
|
|
16
30
|
};
|
|
17
31
|
|
|
@@ -114,6 +114,7 @@ const RtmConfigure = (props: any) => {
|
|
|
114
114
|
uid: localUid.toString(),
|
|
115
115
|
token: rtcProps.rtm,
|
|
116
116
|
});
|
|
117
|
+
RTMEngine.getInstance().setLocalUID(localUid.toString());
|
|
117
118
|
timerValueRef.current = 5;
|
|
118
119
|
setAttribute();
|
|
119
120
|
} catch (error) {
|
|
@@ -144,7 +145,12 @@ const RtmConfigure = (props: any) => {
|
|
|
144
145
|
|
|
145
146
|
const joinChannel = async () => {
|
|
146
147
|
try {
|
|
147
|
-
|
|
148
|
+
if (RTMEngine.getInstance().channelUid !== rtcProps.channel) {
|
|
149
|
+
await engine.current.joinChannel(rtcProps.channel);
|
|
150
|
+
RTMEngine.getInstance().setChannelId(rtcProps.channel);
|
|
151
|
+
} else {
|
|
152
|
+
console.log('RTM already joined channel skipping');
|
|
153
|
+
}
|
|
148
154
|
timerValueRef.current = 5;
|
|
149
155
|
await getMembers();
|
|
150
156
|
} catch (error) {
|
|
@@ -260,7 +266,7 @@ const RtmConfigure = (props: any) => {
|
|
|
260
266
|
|
|
261
267
|
const init = async () => {
|
|
262
268
|
engine.current = RTMEngine.getInstance().engine;
|
|
263
|
-
RTMEngine.getInstance()
|
|
269
|
+
RTMEngine.getInstance();
|
|
264
270
|
|
|
265
271
|
engine.current.on('connectionStateChanged', (evt: any) => {
|
|
266
272
|
//console.log(evt);
|
|
@@ -414,6 +420,17 @@ const RtmConfigure = (props: any) => {
|
|
|
414
420
|
const {payload, persistLevel, source} = JSON.parse(value);
|
|
415
421
|
console.log('CUSTOM_EVENT_API: emiting event..: ');
|
|
416
422
|
EventUtils.emitEvent(evt, source, {payload, persistLevel, sender, ts});
|
|
423
|
+
// Because async gets evaluated in a different order when in an sdk
|
|
424
|
+
if (evt === 'name') {
|
|
425
|
+
setTimeout(() => {
|
|
426
|
+
EventUtils.emitEvent(evt, source, {
|
|
427
|
+
payload,
|
|
428
|
+
persistLevel,
|
|
429
|
+
sender,
|
|
430
|
+
ts,
|
|
431
|
+
});
|
|
432
|
+
}, 200);
|
|
433
|
+
}
|
|
417
434
|
} catch (error) {
|
|
418
435
|
console.log('CUSTOM_EVENT_API: error while emiting event: ', error);
|
|
419
436
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import React, {createContext, useState, useEffect} from 'react';
|
|
2
|
+
import SDKMethodEventsManager, {
|
|
3
|
+
_InternalSDKMethodEventsMap,
|
|
4
|
+
} from '../utils/SdkMethodEvents';
|
|
5
|
+
import {
|
|
6
|
+
validateMeetingInfoData,
|
|
7
|
+
MeetingInfoContextInterface,
|
|
8
|
+
} from './meeting-info/useMeetingInfo';
|
|
9
|
+
import {CustomizationApiInterface} from 'customization-api';
|
|
10
|
+
import {Unsubscribe} from 'nanoevents';
|
|
11
|
+
|
|
12
|
+
type SdkApiContextInterface = {
|
|
13
|
+
join:
|
|
14
|
+
| {
|
|
15
|
+
initialized: true;
|
|
16
|
+
phrase: string;
|
|
17
|
+
meetingDetails?: Partial<MeetingInfoContextInterface['data']>;
|
|
18
|
+
skipPrecall: boolean;
|
|
19
|
+
promise: {
|
|
20
|
+
res: Parameters<_InternalSDKMethodEventsMap['join']>[0];
|
|
21
|
+
rej: Parameters<_InternalSDKMethodEventsMap['join']>[1];
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
| {
|
|
25
|
+
initialized: false;
|
|
26
|
+
};
|
|
27
|
+
customize: {
|
|
28
|
+
customization?: CustomizationApiInterface;
|
|
29
|
+
promise?: {
|
|
30
|
+
res: Parameters<_InternalSDKMethodEventsMap['customize']>[0];
|
|
31
|
+
rej: Parameters<_InternalSDKMethodEventsMap['customize']>[1];
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
clearState: (key: keyof _InternalSDKMethodEventsMap) => void;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const SdkApiInitState: SdkApiContextInterface = {
|
|
38
|
+
join: {
|
|
39
|
+
initialized: false,
|
|
40
|
+
},
|
|
41
|
+
customize: {},
|
|
42
|
+
clearState: () => {},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const SDK_MEETING_TAG = 'sdk-initiated-meeting';
|
|
46
|
+
|
|
47
|
+
export const SdkApiContext = createContext(SdkApiInitState);
|
|
48
|
+
|
|
49
|
+
let moduleEventsUnsub: any[] = [];
|
|
50
|
+
|
|
51
|
+
type commonEventHandlers = {
|
|
52
|
+
[K in keyof _InternalSDKMethodEventsMap]?: (
|
|
53
|
+
setter: (p: SdkApiContextInterface[K]) => void,
|
|
54
|
+
) => Unsubscribe;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const commonEventHandlers: commonEventHandlers = {
|
|
58
|
+
join: (setter) => {
|
|
59
|
+
return SDKMethodEventsManager.on(
|
|
60
|
+
'join',
|
|
61
|
+
(res, rej, roomDetail, skipPrecall) => {
|
|
62
|
+
if (typeof roomDetail === 'object') {
|
|
63
|
+
if (!validateMeetingInfoData(roomDetail)) {
|
|
64
|
+
rej(new Error('Invalid meeting details'));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
setter({
|
|
68
|
+
initialized: true,
|
|
69
|
+
phrase: SDK_MEETING_TAG,
|
|
70
|
+
meetingDetails: roomDetail,
|
|
71
|
+
skipPrecall,
|
|
72
|
+
promise: {res, rej},
|
|
73
|
+
});
|
|
74
|
+
} else if (
|
|
75
|
+
typeof roomDetail === 'string' &&
|
|
76
|
+
roomDetail.trim().length > 0
|
|
77
|
+
) {
|
|
78
|
+
setter({
|
|
79
|
+
initialized: true,
|
|
80
|
+
phrase: roomDetail,
|
|
81
|
+
skipPrecall,
|
|
82
|
+
promise: {res, rej},
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
rej(new Error('Invalid room detail'));
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
},
|
|
90
|
+
customize: (setter) => {
|
|
91
|
+
return SDKMethodEventsManager.on('customize', (res, rej, customization) => {
|
|
92
|
+
setter({
|
|
93
|
+
customization: customization,
|
|
94
|
+
});
|
|
95
|
+
res();
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const registerListener = () => {
|
|
101
|
+
moduleEventsUnsub = [
|
|
102
|
+
commonEventHandlers.customize((state) => {
|
|
103
|
+
SdkApiInitState.customize = state;
|
|
104
|
+
}),
|
|
105
|
+
commonEventHandlers.join((state) => {
|
|
106
|
+
SdkApiInitState.join = state;
|
|
107
|
+
}),
|
|
108
|
+
];
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const deRegisterListener = () => {
|
|
112
|
+
moduleEventsUnsub.forEach((v) => v());
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const SdkApiContextProvider: React.FC = (props) => {
|
|
116
|
+
const [joinState, setJoinState] = useState(SdkApiInitState.join);
|
|
117
|
+
const [userCustomization, setUserCustomization] = useState(
|
|
118
|
+
SdkApiInitState.customize,
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
const clearState: SdkApiContextInterface['clearState'] = (key) => {
|
|
122
|
+
switch (key) {
|
|
123
|
+
case 'join':
|
|
124
|
+
setJoinState(SdkApiInitState.join);
|
|
125
|
+
return;
|
|
126
|
+
case 'customize':
|
|
127
|
+
setUserCustomization(SdkApiInitState.customize);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
useEffect(() => {
|
|
132
|
+
deRegisterListener();
|
|
133
|
+
console.log('[SDKContext] join state is ', joinState);
|
|
134
|
+
const unsub = [
|
|
135
|
+
commonEventHandlers.customize((state) => {
|
|
136
|
+
setUserCustomization(state);
|
|
137
|
+
}),
|
|
138
|
+
commonEventHandlers.join((state) => {
|
|
139
|
+
setJoinState(state);
|
|
140
|
+
}),
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
return () => {
|
|
144
|
+
unsub.forEach((v) => v());
|
|
145
|
+
registerListener();
|
|
146
|
+
};
|
|
147
|
+
}, []);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<SdkApiContext.Provider
|
|
151
|
+
value={{
|
|
152
|
+
join: joinState,
|
|
153
|
+
customize: userCustomization,
|
|
154
|
+
clearState,
|
|
155
|
+
}}>
|
|
156
|
+
{props.children}
|
|
157
|
+
</SdkApiContext.Provider>
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export default SdkApiContextProvider;
|
|
@@ -36,6 +36,30 @@ export interface MeetingInfoContextInterface {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export const validateMeetingInfoData = (
|
|
40
|
+
meetingInfo: Partial<MeetingInfoContextInterface['data']>,
|
|
41
|
+
) => {
|
|
42
|
+
const {
|
|
43
|
+
channel,
|
|
44
|
+
encryptionSecret,
|
|
45
|
+
rtmToken,
|
|
46
|
+
screenShareToken,
|
|
47
|
+
screenShareUid,
|
|
48
|
+
token,
|
|
49
|
+
uid,
|
|
50
|
+
} = meetingInfo;
|
|
51
|
+
if ($config.ENCRYPTION_ENABLED && !encryptionSecret) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
if ($config.SCREEN_SHARING && (!screenShareToken || !screenShareUid)) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
if (!channel || !rtmToken || !token || !uid) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
|
|
39
63
|
export const MeetingInfoDefaultValue: MeetingInfoContextInterface = {
|
|
40
64
|
isJoinDataFetched: false,
|
|
41
65
|
data: {
|
|
@@ -9,9 +9,15 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
import React, {createContext} from 'react';
|
|
12
|
+
import React, {createContext, useContext, useEffect} from 'react';
|
|
13
13
|
import {createHook} from 'customization-implementation';
|
|
14
14
|
import {ApolloError} from '@apollo/client';
|
|
15
|
+
import {SdkApiContext} from '../SdkApiContext';
|
|
16
|
+
import {
|
|
17
|
+
useMeetingInfo,
|
|
18
|
+
} from '../meeting-info/useMeetingInfo';
|
|
19
|
+
import SDKEvents from '../../utils/SdkEvents';
|
|
20
|
+
import DeviceContext from '../DeviceContext';
|
|
15
21
|
|
|
16
22
|
export interface PreCallContextInterface {
|
|
17
23
|
callActive: boolean;
|
|
@@ -46,6 +52,24 @@ interface PreCallProviderProps {
|
|
|
46
52
|
}
|
|
47
53
|
|
|
48
54
|
const PreCallProvider = (props: PreCallProviderProps) => {
|
|
55
|
+
const {join} = useContext(SdkApiContext);
|
|
56
|
+
const meetingInfo = useMeetingInfo();
|
|
57
|
+
const {deviceList} = useContext(DeviceContext);
|
|
58
|
+
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (join.phrase) {
|
|
61
|
+
//@ts-ignore
|
|
62
|
+
join?.promise?.res([
|
|
63
|
+
meetingInfo.data,
|
|
64
|
+
() => {
|
|
65
|
+
props.value.setCallActive(true);
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
SDKEvents.emit('ready-to-join', meetingInfo.data.meetingTitle, deviceList);
|
|
71
|
+
}, []);
|
|
72
|
+
|
|
49
73
|
return (
|
|
50
74
|
<PreCallContext.Provider value={{...props.value}}>
|
|
51
75
|
{props.children}
|
|
@@ -10,10 +10,14 @@
|
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import React, {SetStateAction, useState} from 'react';
|
|
13
|
+
import React, {SetStateAction, useState, useContext, useEffect} from 'react';
|
|
14
14
|
import {createHook} from 'customization-implementation';
|
|
15
15
|
import InvitePopup from './popups/InvitePopup';
|
|
16
16
|
import StopRecordingPopup from './popups/StopRecordingPopup';
|
|
17
|
+
import {SdkApiContext} from './SdkApiContext';
|
|
18
|
+
import {useRtc, useMeetingInfo} from 'customization-api';
|
|
19
|
+
import SDKEvents from '../utils/SdkEvents';
|
|
20
|
+
import DeviceContext from './DeviceContext';
|
|
17
21
|
|
|
18
22
|
export interface VideoCallContextInterface {
|
|
19
23
|
showInvitePopup: boolean;
|
|
@@ -40,6 +44,21 @@ const VideoCallProvider = (props: VideoCallProviderProps) => {
|
|
|
40
44
|
const [showLayoutOption, setShowLayoutOption] = useState(false);
|
|
41
45
|
const [showInvitePopup, setShowInvitePopup] = useState(false);
|
|
42
46
|
const [showStopRecordingPopup, setShowStopRecordingPopup] = useState(false);
|
|
47
|
+
const {join} = useContext(SdkApiContext);
|
|
48
|
+
const meetingInfo = useMeetingInfo();
|
|
49
|
+
const {deviceList} = useContext(DeviceContext);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (join.initialized && join.phrase) {
|
|
53
|
+
join.promise.res(meetingInfo.data);
|
|
54
|
+
}
|
|
55
|
+
SDKEvents.emit(
|
|
56
|
+
'join',
|
|
57
|
+
meetingInfo.data.meetingTitle,
|
|
58
|
+
deviceList,
|
|
59
|
+
meetingInfo.data.isHost,
|
|
60
|
+
);
|
|
61
|
+
}, []);
|
|
43
62
|
return (
|
|
44
63
|
<VideoCallContext.Provider
|
|
45
64
|
value={{
|
|
@@ -28,9 +28,7 @@ import {useString} from '../utils/useString';
|
|
|
28
28
|
import useCreateMeeting from '../utils/useCreateMeeting';
|
|
29
29
|
import {CreateProvider} from './create/useCreate';
|
|
30
30
|
import useJoinMeeting from '../utils/useJoinMeeting';
|
|
31
|
-
import SDKEvents from '../utils/SdkEvents';
|
|
32
31
|
import {MeetingInfoDefaultValue} from '../components/meeting-info/useMeetingInfo';
|
|
33
|
-
import {useSetMeetingInfo} from '../components/meeting-info/useSetMeetingInfo';
|
|
34
32
|
import Input from '../atoms/Input';
|
|
35
33
|
import Toggle from '../atoms/Toggle';
|
|
36
34
|
import Card from '../atoms/Card';
|
|
@@ -42,6 +40,7 @@ import Tooltip from '../atoms/Tooltip';
|
|
|
42
40
|
import ImageIcon from '../atoms/ImageIcon';
|
|
43
41
|
import hexadecimalTransparency from '../utils/hexadecimalTransparency';
|
|
44
42
|
import {randomNameGenerator} from '../utils';
|
|
43
|
+
import {useSetMeetingInfo} from '../components/meeting-info/useSetMeetingInfo';
|
|
45
44
|
|
|
46
45
|
const Create = () => {
|
|
47
46
|
const {CreateComponent} = useCustomization((data) => {
|
|
@@ -121,23 +120,7 @@ const Create = () => {
|
|
|
121
120
|
document.title = $config.APP_NAME;
|
|
122
121
|
}
|
|
123
122
|
console.log('[SDKEvents] Join listener registered');
|
|
124
|
-
|
|
125
|
-
'joinMeetingWithPhrase',
|
|
126
|
-
(phrase, resolve, reject) => {
|
|
127
|
-
console.log('SDKEvents: joinMeetingWithPhrase event called', phrase);
|
|
128
|
-
try {
|
|
129
|
-
setMeetingInfo(MeetingInfoDefaultValue);
|
|
130
|
-
history.push(phrase);
|
|
131
|
-
resolve();
|
|
132
|
-
} catch (error) {
|
|
133
|
-
reject(error);
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
|
-
);
|
|
137
|
-
SDKEvents.emit('joinInit');
|
|
138
|
-
return () => {
|
|
139
|
-
unbind();
|
|
140
|
-
};
|
|
123
|
+
return () => {};
|
|
141
124
|
}, []);
|
|
142
125
|
|
|
143
126
|
const showShareScreen = () => {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
12
|
// @ts-nocheck
|
|
13
|
-
import React, {useState, useContext, useEffect} from 'react';
|
|
13
|
+
import React, {useState, useContext, useEffect, useRef} from 'react';
|
|
14
14
|
import {View, StyleSheet, Text, useWindowDimensions} from 'react-native';
|
|
15
15
|
import {
|
|
16
16
|
RtcConfigure,
|
|
@@ -18,6 +18,9 @@ import {
|
|
|
18
18
|
ClientRole,
|
|
19
19
|
ChannelProfile,
|
|
20
20
|
LocalUserContext,
|
|
21
|
+
UidType,
|
|
22
|
+
CallbacksInterface,
|
|
23
|
+
ToggleState,
|
|
21
24
|
} from '../../agora-rn-uikit';
|
|
22
25
|
import styles from '../components/styles';
|
|
23
26
|
import {useParams, useHistory} from '../components/Router';
|
|
@@ -38,7 +41,11 @@ import {useString} from '../utils/useString';
|
|
|
38
41
|
import useLayoutsData from './video-call/useLayoutsData';
|
|
39
42
|
import {RecordingProvider} from '../subComponents/recording/useRecording';
|
|
40
43
|
import useJoinMeeting from '../utils/useJoinMeeting';
|
|
41
|
-
import {
|
|
44
|
+
import {
|
|
45
|
+
useMeetingInfo,
|
|
46
|
+
MeetingInfoDefaultValue,
|
|
47
|
+
validateMeetingInfoData,
|
|
48
|
+
} from '../components/meeting-info/useMeetingInfo';
|
|
42
49
|
import {SidePanelProvider} from '../utils/useSidePanel';
|
|
43
50
|
import VideoCallScreen from './video-call/VideoCallScreen';
|
|
44
51
|
import {NetworkQualityProvider} from '../components/NetworkQualityContext';
|
|
@@ -57,6 +64,9 @@ import EventsConfigure from '../components/EventsConfigure';
|
|
|
57
64
|
import PermissionHelper from '../components/precall/PermissionHelper';
|
|
58
65
|
import {currentFocus, FocusProvider} from '../utils/useFocus';
|
|
59
66
|
import {VideoCallProvider} from '../components/useVideoCall';
|
|
67
|
+
import {SdkApiContext, SDK_MEETING_TAG} from '../components/SdkApiContext';
|
|
68
|
+
import isSDK from '../utils/isSDK';
|
|
69
|
+
import {useSetMeetingInfo} from '../components/meeting-info/useSetMeetingInfo';
|
|
60
70
|
|
|
61
71
|
enum RnEncryptionEnum {
|
|
62
72
|
/**
|
|
@@ -127,11 +137,18 @@ const VideoCall: React.FC = () => {
|
|
|
127
137
|
activeSpeaker: $config.ACTIVE_SPEAKER,
|
|
128
138
|
});
|
|
129
139
|
|
|
140
|
+
const {join: SdkJoinState, clearState} = useContext(SdkApiContext);
|
|
141
|
+
const history = useHistory();
|
|
142
|
+
const currentMeetingPhrase = useRef(history.location.pathname);
|
|
143
|
+
|
|
130
144
|
const useJoin = useJoinMeeting();
|
|
145
|
+
const {setMeetingInfo} = useSetMeetingInfo();
|
|
146
|
+
const {isJoinDataFetched, data} = useMeetingInfo();
|
|
131
147
|
|
|
132
148
|
React.useEffect(() => {
|
|
133
149
|
return () => {
|
|
134
150
|
console.log('Videocall unmounted');
|
|
151
|
+
setMeetingInfo(MeetingInfoDefaultValue);
|
|
135
152
|
if (awake) {
|
|
136
153
|
release();
|
|
137
154
|
}
|
|
@@ -139,18 +156,56 @@ const VideoCall: React.FC = () => {
|
|
|
139
156
|
}, []);
|
|
140
157
|
|
|
141
158
|
useEffect(() => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
159
|
+
if (!SdkJoinState.phrase) {
|
|
160
|
+
useJoin(phrase)
|
|
161
|
+
.then(() => {})
|
|
162
|
+
.catch((error) => {
|
|
163
|
+
setGlobalErrorMessage(error);
|
|
164
|
+
history.push('/');
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (!isSDK() || !SdkJoinState.initialized) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const {
|
|
174
|
+
phrase: sdkMeetingPhrase,
|
|
175
|
+
meetingDetails: sdkMeetingDetails,
|
|
176
|
+
skipPrecall,
|
|
177
|
+
promise,
|
|
178
|
+
} = SdkJoinState;
|
|
179
|
+
|
|
180
|
+
const sdkMeetingPath = `/${sdkMeetingPhrase}`;
|
|
181
|
+
|
|
182
|
+
setCallActive(skipPrecall);
|
|
183
|
+
|
|
184
|
+
if (sdkMeetingDetails) {
|
|
185
|
+
setQueryComplete(false);
|
|
186
|
+
setMeetingInfo((meetingInfo) => {
|
|
187
|
+
return {
|
|
188
|
+
isJoinDataFetched: true,
|
|
189
|
+
data: {
|
|
190
|
+
...meetingInfo.data,
|
|
191
|
+
...sdkMeetingDetails,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
} else if (sdkMeetingPhrase) {
|
|
196
|
+
setQueryComplete(false);
|
|
197
|
+
currentMeetingPhrase.current = sdkMeetingPath;
|
|
198
|
+
useJoin(sdkMeetingPhrase).catch((error) => {
|
|
145
199
|
setGlobalErrorMessage(error);
|
|
146
200
|
history.push('/');
|
|
201
|
+
currentMeetingPhrase.current = '';
|
|
202
|
+
promise.rej(error);
|
|
147
203
|
});
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const {isJoinDataFetched, data} = useMeetingInfo();
|
|
204
|
+
}
|
|
205
|
+
}, [SdkJoinState]);
|
|
151
206
|
|
|
152
207
|
React.useEffect(() => {
|
|
153
|
-
if (isJoinDataFetched === true) {
|
|
208
|
+
if (isJoinDataFetched === true && !queryComplete) {
|
|
154
209
|
setRtcProps({
|
|
155
210
|
appId: $config.APP_ID,
|
|
156
211
|
channel: data.channel,
|
|
@@ -178,17 +233,48 @@ const VideoCall: React.FC = () => {
|
|
|
178
233
|
// if (data.username) {
|
|
179
234
|
// setUsername(data.username);
|
|
180
235
|
// }
|
|
181
|
-
|
|
236
|
+
setQueryComplete(true);
|
|
182
237
|
}
|
|
183
|
-
}, [isJoinDataFetched]);
|
|
238
|
+
}, [isJoinDataFetched, data, queryComplete]);
|
|
184
239
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
240
|
+
const callbacks: CallbacksInterface = {
|
|
241
|
+
// RtcLeft: () => {},
|
|
242
|
+
// RtcJoined: () => {
|
|
243
|
+
// if (SdkJoinState.phrase && SdkJoinState.skipPrecall) {
|
|
244
|
+
// SdkJoinState.promise?.res();
|
|
245
|
+
// }
|
|
246
|
+
// },
|
|
247
|
+
EndCall: () => {
|
|
248
|
+
clearState('join');
|
|
188
249
|
setTimeout(() => {
|
|
189
250
|
SDKEvents.emit('leave');
|
|
190
251
|
history.push('/');
|
|
191
|
-
}, 0)
|
|
252
|
+
}, 0);
|
|
253
|
+
},
|
|
254
|
+
UserJoined: (uid: UidType) => {
|
|
255
|
+
console.log("UIKIT Callback: UserJoined", uid)
|
|
256
|
+
SDKEvents.emit('rtc-user-joined', uid);
|
|
257
|
+
},
|
|
258
|
+
UserOffline: (uid: UidType) => {
|
|
259
|
+
console.log("UIKIT Callback: UserOffline", uid)
|
|
260
|
+
SDKEvents.emit('rtc-user-joined', uid);
|
|
261
|
+
},
|
|
262
|
+
RemoteAudioStateChanged: (uid: UidType, status: 0 | 2) => {
|
|
263
|
+
console.log("UIKIT Callback: RemoteAudioStateChanged", uid, status)
|
|
264
|
+
if (status === 0) {
|
|
265
|
+
SDKEvents.emit('rtc-user-unpublished', uid, 'audio');
|
|
266
|
+
} else {
|
|
267
|
+
SDKEvents.emit('rtc-user-published', uid, 'audio');
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
RemoteVideoStateChanged: (uid: UidType, status: 0 | 2) => {
|
|
271
|
+
console.log("UIKIT Callback: RemoteVideoStateChanged", uid, status)
|
|
272
|
+
if (status === 0) {
|
|
273
|
+
SDKEvents.emit('rtc-user-unpublished', uid, 'video');
|
|
274
|
+
} else {
|
|
275
|
+
SDKEvents.emit('rtc-user-published', uid, 'video');
|
|
276
|
+
}
|
|
277
|
+
},
|
|
192
278
|
};
|
|
193
279
|
const [isCameraAvailable, setCameraAvailable] = useState(false);
|
|
194
280
|
const [isMicAvailable, setMicAvailable] = useState(false);
|
|
@@ -142,33 +142,6 @@ const VideoCallScreen = () => {
|
|
|
142
142
|
return components;
|
|
143
143
|
});
|
|
144
144
|
|
|
145
|
-
useEffect(() => {
|
|
146
|
-
// setTimeout(() => {
|
|
147
|
-
// events.send(
|
|
148
|
-
// controlMessageEnum.newUserJoined,
|
|
149
|
-
// JSON.stringify({name}),
|
|
150
|
-
// EventPersistLevel.LEVEL1,
|
|
151
|
-
// );
|
|
152
|
-
// }, 1000);
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* OLD: Commenting this code as getDevices API is web only
|
|
156
|
-
* The below code fails on native app
|
|
157
|
-
* RESPONSE: Added isWebInternal check to restrict execution only on web.
|
|
158
|
-
*/
|
|
159
|
-
if (isWebInternal()) {
|
|
160
|
-
new Promise((res) =>
|
|
161
|
-
//@ts-ignore
|
|
162
|
-
rtc.RtcEngine.getDevices(function (devices: MediaDeviceInfo[]) {
|
|
163
|
-
res(devices);
|
|
164
|
-
}),
|
|
165
|
-
).then((devices: MediaDeviceInfo[]) => {
|
|
166
|
-
SDKEvents.emit('join', meetingTitle, devices, isHost);
|
|
167
|
-
console.log('SDKEvents: Event Called join');
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}, []);
|
|
171
|
-
|
|
172
145
|
const isDesktop = useIsDesktop();
|
|
173
146
|
|
|
174
147
|
return VideocallComponent ? (
|
|
@@ -47,6 +47,12 @@ class RTMEngine {
|
|
|
47
47
|
|
|
48
48
|
return RTMEngine._instance;
|
|
49
49
|
}
|
|
50
|
+
setLocalUID(localUID: string) {
|
|
51
|
+
this.localUID = localUID;
|
|
52
|
+
}
|
|
53
|
+
setChannelId(channelID: string) {
|
|
54
|
+
this.channelId = channelID;
|
|
55
|
+
}
|
|
50
56
|
|
|
51
57
|
setLoginInfo(localUID: string, channelID: string) {
|
|
52
58
|
this.localUID = localUID;
|
|
@@ -62,6 +68,8 @@ class RTMEngine {
|
|
|
62
68
|
try {
|
|
63
69
|
this.destroyClientInstance();
|
|
64
70
|
RTMEngine._instance = null;
|
|
71
|
+
this.localUID = '';
|
|
72
|
+
this.channelId = '';
|
|
65
73
|
} catch (error) {
|
|
66
74
|
console.log('Error destroying instance error: ', error);
|
|
67
75
|
}
|
|
@@ -165,6 +165,7 @@ class Events {
|
|
|
165
165
|
if (!this._validateEvt(eventName) || !this._validateListener(listener))
|
|
166
166
|
return;
|
|
167
167
|
EventUtils.addListener(eventName, listener, this.source);
|
|
168
|
+
console.log('CUSTOM_EVENT_API event listener registered', eventName);
|
|
168
169
|
return () => {
|
|
169
170
|
EventUtils.removeListener(eventName, listener, this.source);
|
|
170
171
|
};
|
|
@@ -9,24 +9,33 @@
|
|
|
9
9
|
information visit https://appbuilder.agora.io.
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
|
-
|
|
13
|
-
* @format
|
|
14
|
-
*/
|
|
15
|
-
type callBackType = (...args: any[]) => void;
|
|
16
|
-
import {userEventsMapInterface} from '../SDKAppWrapper';
|
|
12
|
+
|
|
17
13
|
import {createNanoEvents} from 'nanoevents';
|
|
14
|
+
import {UidType} from 'agora-rn-uikit';
|
|
15
|
+
import {IRemoteTrack} from 'agora-rtc-sdk-ng';
|
|
18
16
|
|
|
19
|
-
interface
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
export interface userEventsMapInterface {
|
|
18
|
+
leave: () => void;
|
|
19
|
+
create: (
|
|
20
|
+
hostPhrase: string,
|
|
21
|
+
attendeePhrase?: string,
|
|
22
|
+
pstnNumer?: {
|
|
23
|
+
number: string;
|
|
24
|
+
pin: string;
|
|
25
|
+
},
|
|
26
|
+
) => void;
|
|
27
|
+
'ready-to-join': (meetingTitle: string, devices: MediaDeviceInfo[]) => void;
|
|
28
|
+
join: (
|
|
29
|
+
meetingTitle: string,
|
|
30
|
+
devices: MediaDeviceInfo[],
|
|
31
|
+
isHost: boolean,
|
|
27
32
|
) => void;
|
|
33
|
+
'rtc-user-published': (uid: UidType, trackType: 'audio' | 'video') => void;
|
|
34
|
+
'rtc-user-unpublished': (uid: UidType, trackType: 'audio' | 'video') => void;
|
|
35
|
+
'rtc-user-joined': (uid: UidType) => void;
|
|
36
|
+
'rtc-user-left': (uid: UidType) => void;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
|
-
const SDKEvents = createNanoEvents<
|
|
39
|
+
const SDKEvents = createNanoEvents<userEventsMapInterface>();
|
|
31
40
|
|
|
32
41
|
export default SDKEvents;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {SdkMethodEvents} from '../SDKAppWrapper';
|
|
2
|
+
import {createNanoEvents, Emitter} from 'nanoevents';
|
|
3
|
+
|
|
4
|
+
type EventParameterHelper<T extends keyof SdkMethodEvents> = Parameters<
|
|
5
|
+
SdkMethodEvents[T]
|
|
6
|
+
>;
|
|
7
|
+
|
|
8
|
+
type EventReturnTypeHelper<T extends keyof SdkMethodEvents> = ReturnType<
|
|
9
|
+
SdkMethodEvents[T]
|
|
10
|
+
>;
|
|
11
|
+
|
|
12
|
+
type EventKeyNameHelper = keyof SdkMethodEvents;
|
|
13
|
+
|
|
14
|
+
type injectAsync<T extends (...p: any) => any> = (
|
|
15
|
+
res: (result?: ReturnType<T> | PromiseLike<ReturnType<T>>) => void,
|
|
16
|
+
rej: (reason?: any) => void,
|
|
17
|
+
...params: Parameters<T>
|
|
18
|
+
) => void;
|
|
19
|
+
|
|
20
|
+
export type _InternalSDKMethodEventsMap = {
|
|
21
|
+
[K in EventKeyNameHelper]: injectAsync<SdkMethodEvents[K]>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
type emitCacheType = {
|
|
25
|
+
[K in EventKeyNameHelper]?: Parameters<_InternalSDKMethodEventsMap[K]>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type emitCacheEnabledType = {
|
|
29
|
+
[K in EventKeyNameHelper]?: boolean;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class SDKMethodEvents {
|
|
33
|
+
constructor() {
|
|
34
|
+
this.emitter = createNanoEvents();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
emitter: Emitter;
|
|
38
|
+
|
|
39
|
+
emitCache: emitCacheType = {};
|
|
40
|
+
emitCacheDisabled: emitCacheEnabledType = {};
|
|
41
|
+
|
|
42
|
+
async emit<T extends EventKeyNameHelper>(
|
|
43
|
+
event: T,
|
|
44
|
+
...params: EventParameterHelper<T>
|
|
45
|
+
) {
|
|
46
|
+
if (this.emitCache[event]) {
|
|
47
|
+
throw new Error(`Event: ${event} already in callstack`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await new Promise<EventReturnTypeHelper<T>>((res, rej) => {
|
|
51
|
+
this.emitCache[event] = [res, rej, ...params] as any;
|
|
52
|
+
this.emitter.emit(event, res, rej, ...params);
|
|
53
|
+
})
|
|
54
|
+
.then((res) => {
|
|
55
|
+
delete this.emitCache[event];
|
|
56
|
+
return res;
|
|
57
|
+
})
|
|
58
|
+
.catch((e) => {
|
|
59
|
+
delete this.emitCache[event];
|
|
60
|
+
throw e;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
on<T extends EventKeyNameHelper>(
|
|
67
|
+
event: T,
|
|
68
|
+
callback: _InternalSDKMethodEventsMap[T],
|
|
69
|
+
) {
|
|
70
|
+
const unsub = this.emitter.on(event, callback);
|
|
71
|
+
if (this.emitCache[event] && !this.emitCacheDisabled[event]) {
|
|
72
|
+
this.emitter.emit(event, ...this.emitCache[event]);
|
|
73
|
+
}
|
|
74
|
+
this.emitCacheDisabled[event] = true;
|
|
75
|
+
return unsub;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const SDKMethodEventsManager = new SDKMethodEvents();
|
|
80
|
+
|
|
81
|
+
export default SDKMethodEventsManager;
|
|
@@ -111,16 +111,22 @@ export default function useJoinMeeting() {
|
|
|
111
111
|
// if (data?.getUser?.name) {
|
|
112
112
|
// meetingInfo.username = data.getUser.name;
|
|
113
113
|
// }
|
|
114
|
+
console.log('!!!!!Meetinginfo', {
|
|
115
|
+
meetingInfo,
|
|
116
|
+
response: response.data,
|
|
117
|
+
});
|
|
114
118
|
setMeetingInfo((prevState) => {
|
|
119
|
+
let compiledMeetingInfo = {
|
|
120
|
+
...prevState.data,
|
|
121
|
+
...meetingInfo,
|
|
122
|
+
};
|
|
115
123
|
return {
|
|
116
124
|
...prevState,
|
|
117
125
|
isJoinDataFetched: true,
|
|
118
|
-
data:
|
|
119
|
-
...prevState.data,
|
|
120
|
-
...meetingInfo,
|
|
121
|
-
},
|
|
126
|
+
data: compiledMeetingInfo,
|
|
122
127
|
};
|
|
123
128
|
});
|
|
129
|
+
return meetingInfo;
|
|
124
130
|
} else {
|
|
125
131
|
throw new Error('An error occurred in parsing the channel data.');
|
|
126
132
|
}
|
|
@@ -17,13 +17,12 @@ module.exports = merge(commons, {
|
|
|
17
17
|
'react-router': 'react-router',
|
|
18
18
|
'react-router-dom': 'react-router-dom',
|
|
19
19
|
'@apollo/client': '@apollo/client',
|
|
20
|
-
nanoid: 'nanoid',
|
|
21
20
|
},
|
|
22
21
|
// Main entry point for the web application
|
|
23
22
|
entry: {
|
|
24
23
|
main: './index.rsdk.tsx',
|
|
25
24
|
},
|
|
26
|
-
target: '
|
|
25
|
+
target: 'web',
|
|
27
26
|
output: {
|
|
28
27
|
path: path.resolve(__dirname, `../Builds/react-sdk`),
|
|
29
28
|
filename: 'index.js',
|