agora-appbuilder-core 4.0.0-spl.3 → 4.0.0-spl.5

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.0.0-spl.3",
3
+ "version": "4.0.0-spl.5",
4
4
  "description": "React Native template for RTE app builder",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -460,11 +460,11 @@ module.exports.androidWin = series(
460
460
  );
461
461
 
462
462
  module.exports.test = series(
463
- general.generateNpmPackage,
464
- // general.typescript,
465
- // general.typescriptFix,
466
- // reactSdk.typescript,
467
- // reactSdk.typescriptFix,
468
- // webSdk.typescript,
469
- // webSdk.typescriptFix,
463
+ general.createBuildDirectory,
464
+ general.generateApiTypedefs,
465
+ general.bundleApiTypedefs,
466
+ webSdk.generateSdkTypedefs,
467
+ webSdk.bundleSdkTypedefs,
468
+ general.cleanTempFiles,
469
+ general.genTsDefs,
470
470
  );
@@ -101,7 +101,8 @@ export type ComponentsInterface = {
101
101
  */
102
102
  appRoot?: React.ComponentType;
103
103
  // commented for v1 release
104
- //precall?: PreCallInterface | React.ComponentType;
104
+ // precall?: PreCallInterface | React.ComponentType;
105
+ precall?: React.ComponentType;
105
106
  //create?: React.ComponentType;
106
107
  //share?: React.ComponentType;
107
108
  //join?: React.ComponentType;
@@ -3,31 +3,13 @@ import SDKAppWrapper, {
3
3
  AppBuilderSdkApi,
4
4
  AppBuilderSdkApiInterface,
5
5
  } from './src/SDKAppWrapper';
6
- import SDKEvents from './src/utils/SdkEvents';
7
6
  import React from 'react';
8
7
  import * as RN from 'react-native-web';
9
8
  import './src/assets/font-styles.css';
10
9
  export * from 'customization-api';
11
10
  export * from 'customization-implementation';
12
11
 
13
- interface AppBuilderWebSdkInterface extends AppBuilderSdkApiInterface {}
14
-
15
- const clearEvent = {
16
- clear: () => {},
17
- };
18
-
19
- const AppBuilderWebSdkApi: AppBuilderWebSdkInterface = {
20
- ...AppBuilderSdkApi,
21
- // Override customize function for web-sdk
22
- customize: (customization) => {
23
- SDKEvents.emit('addFpe', customization);
24
- clearEvent.clear = SDKEvents.on('addFpeInit', () => {
25
- console.log('addFpeInit called');
26
- SDKEvents.emit('addFpe', customization);
27
- clearEvent.clear();
28
- });
29
- },
30
- };
12
+ const AppBuilderWebSdkApi: AppBuilderSdkApiInterface = AppBuilderSdkApi;
31
13
 
32
14
  // init code
33
15
  class AppBuilder extends HTMLElement {
@@ -11,82 +11,65 @@ import SDKMethodEventsManager from './utils/SdkMethodEvents';
11
11
  import App from './App';
12
12
  import SdkApiContextProvider from './components/SdkApiContext';
13
13
  import {Unsubscribe} from 'nanoevents';
14
+ import {deviceId} from './components/DeviceConfigure';
14
15
 
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>;
16
+ type meetingData = Partial<MeetingInfoContextInterface['data']>;
22
17
 
23
- type deviceId = MediaDeviceInfo['deviceId'];
24
-
25
- export interface SdkMethodEvents {
26
- customize: (customization: CustomizationApiInterface) => void;
27
- join(
28
- roomid: string | Partial<MeetingInfoContextInterface['data']>,
29
- skipPrecall?: boolean,
30
- ): MeetingInfoContextInterface['data'];
31
- // mediaDevice: (deviceId: string, kind: MediaDeviceInfo['kind']) => void;
32
- microphoneDevice: (deviceId: deviceId) => void;
33
- speakerDevice: (deviceId: deviceId) => void;
34
- cameraDevice: (deviceId: deviceId) => void;
18
+ // Hard defined since its an api.
19
+ export interface AppBuilderSdkApiInterface {
20
+ customize: (customization: CustomizationApiInterface) => Promise<void>;
21
+ joinRoom: (roomDetails: string | meetingData) => Promise<meetingData>;
22
+ joinPrecall: (
23
+ roomDetails: string | meetingData,
24
+ ) => Promise<[meetingData, () => void]>;
25
+ setMicrophone: (deviceId: deviceId) => Promise<void>;
26
+ setCamera: (deviceId: deviceId) => Promise<void>;
27
+ setSpeaker: (deviceId: deviceId) => Promise<void>;
28
+ muteAudio: (
29
+ mute: boolean | ((currentMute: boolean) => boolean),
30
+ ) => Promise<void>;
31
+ muteVideo: (
32
+ mute: boolean | ((currentMute: boolean) => boolean),
33
+ ) => Promise<void>;
34
+ createCustomization: (
35
+ customization: CustomizationApiInterface,
36
+ ) => CustomizationApiInterface;
37
+ customEvents: typeof customEvents;
38
+ on: <T extends keyof userEventsMapInterface>(
39
+ userEventName: T,
40
+ cb: userEventsMapInterface[T],
41
+ ) => Unsubscribe;
35
42
  }
36
43
 
37
- // interface AppBuilderSdkApiInterface {
38
- // customize: makeAsync<SdkMethodEvents['customize']>;
39
- // joinRoom: makeAsync<takeOnlyFirstParam<SdkMethodEvents['join']>>;
40
- // joinPrecall: makeAsync<takeOnlyFirstParam<SdkMethodEvents['join']>>;
41
- // createCustomization: (
42
- // customization: CustomizationApiInterface,
43
- // ) => CustomizationApiInterface;
44
- // on: <T extends keyof userEventsMapInterface>(
45
- // userEventName: T,
46
- // callBack: userEventsMapInterface[T],
47
- // ) => Unsubscribe;
48
- // }
49
-
50
- export const AppBuilderSdkApi = {
51
- customize: async (customization: CustomizationApiInterface) => {
44
+ export const AppBuilderSdkApi: AppBuilderSdkApiInterface = {
45
+ customize: async (customization) => {
52
46
  return await SDKMethodEventsManager.emit('customize', customization);
53
47
  },
54
48
  customEvents: customEvents,
55
- join: async (roomDetails: string) => {
56
- await SDKMethodEventsManager.emit('join', roomDetails, false);
57
- },
58
- joinRoom: async (
59
- roomDetails: string | Partial<MeetingInfoContextInterface['data']>,
60
- ) => {
49
+ joinRoom: async (roomDetails) => {
61
50
  return await SDKMethodEventsManager.emit('join', roomDetails, true);
62
51
  },
63
- joinPrecall: async (
64
- roomDetails: string | Partial<MeetingInfoContextInterface['data']>,
65
- ) => {
52
+ joinPrecall: async (roomDetails) => {
66
53
  const t = await SDKMethodEventsManager.emit('join', roomDetails);
67
54
  return t as unknown as [MeetingInfoContextInterface['data'], () => {}];
68
55
  },
69
- // setMediaDevice: async (device: MediaDeviceInfo) => {
70
- // return await SDKMethodEventsManager.emit(
71
- // 'mediaDevice',
72
- // device.deviceId,
73
- // device.kind,
74
- // );
75
- // },
76
- setMicrophone: async (deviceId: MediaDeviceInfo['deviceId']) => {
56
+ setMicrophone: async (deviceId) => {
77
57
  return await SDKMethodEventsManager.emit('microphoneDevice', deviceId);
78
58
  },
79
- setSpeaker: async (deviceId: MediaDeviceInfo['deviceId']) => {
59
+ setSpeaker: async (deviceId) => {
80
60
  return await SDKMethodEventsManager.emit('speakerDevice', deviceId);
81
61
  },
82
- setCamera: async (deviceId: MediaDeviceInfo['deviceId']) => {
62
+ setCamera: async (deviceId) => {
83
63
  return await SDKMethodEventsManager.emit('cameraDevice', deviceId);
84
64
  },
65
+ muteAudio: async (state) => {
66
+ return await SDKMethodEventsManager.emit('muteAudio', state);
67
+ },
68
+ muteVideo: async (state) => {
69
+ return await SDKMethodEventsManager.emit('muteVideo', state);
70
+ },
85
71
  createCustomization: customize,
86
- on: <T extends keyof userEventsMapInterface>(
87
- userEventName: T,
88
- cb: userEventsMapInterface[T],
89
- ): Unsubscribe => {
72
+ on: (userEventName, cb) => {
90
73
  console.log('SDKEvents: Event Registered', userEventName);
91
74
  return SDKEvents.on(userEventName, cb);
92
75
  },
@@ -81,14 +81,15 @@ const Chat = (props?: ChatProps) => {
81
81
 
82
82
  const {primaryColor} = useContext(ColorContext);
83
83
 
84
- React.useEffect(() => {
85
- return () => {
86
- // reset both the active tabs
87
- setGroupActive(false);
88
- setPrivateActive(false);
89
- setSelectedUser(0);
90
- };
91
- }, []);
84
+ //not need since state are controlled by chatUIControl
85
+ // React.useEffect(() => {
86
+ // return () => {
87
+ // // reset both the active tabs
88
+ // setGroupActive(false);
89
+ // setPrivateActive(false);
90
+ // setSelectedUser(0);
91
+ // };
92
+ // }, []);
92
93
 
93
94
  const selectUser = (userUID: UidType) => {
94
95
  setSelectedUser(userUID);
@@ -20,20 +20,17 @@ import React, {
20
20
  import {ClientRole} from '../../agora-rn-uikit';
21
21
  import DeviceContext from './DeviceContext';
22
22
  import AgoraRTC, {DeviceInfo} from 'agora-rtc-sdk-ng';
23
- import {useRtc, PrimaryButton} from 'customization-api';
23
+ import {useRtc} from 'customization-api';
24
24
  import Toast from '../../react-native-toast-message';
25
- import TertiaryButton from '../atoms/TertiaryButton';
26
- import {StyleSheet, Text} from 'react-native';
27
- import CustomIcon from '../atoms/CustomIcon';
25
+ import {Text} from 'react-native';
28
26
  import StorageContext from './StorageContext';
29
27
 
30
28
  import type RtcEngine from '../../bridge/rtc/webNg/';
31
29
  import ColorContext from './ColorContext';
32
30
  import {SdkApiContext} from './SdkApiContext';
33
- import SDKMethodEventsManager from '../utils/SdkMethodEvents';
34
31
  import SDKEvents from '../utils/SdkEvents';
35
32
 
36
- const log = (...args) => {
33
+ const log = (...args: any[]) => {
37
34
  console.log('[DeviceConfigure] ', ...args);
38
35
  };
39
36
 
@@ -42,9 +39,9 @@ type WebRtcEngineInstance = InstanceType<typeof RtcEngine>;
42
39
  interface Props {
43
40
  userRole: ClientRole;
44
41
  }
45
- type deviceInfo = MediaDeviceInfo;
46
- type deviceId = deviceInfo['deviceId'];
47
- type deviceKind = deviceInfo['kind'];
42
+ export type deviceInfo = MediaDeviceInfo;
43
+ export type deviceId = deviceInfo['deviceId'];
44
+ export type deviceKind = deviceInfo['kind'];
48
45
 
49
46
  const DeviceConfigure: React.FC<Props> = (props: any) => {
50
47
  const rtc = useRtc();
@@ -213,7 +210,7 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
213
210
  break;
214
211
  case 'audiooutput':
215
212
  //@ts-ignore
216
- const speakerId = RtcEngine.speakerDeviceId;
213
+ let speakerId = RtcEngine.speakerDeviceId;
217
214
  speakerId &&
218
215
  SDKEvents.emit('devices-selected-speaker-changed', speakerId);
219
216
  setUiSelectedSpeaker(speakerId);
@@ -163,7 +163,7 @@ const ParticipantView = (props) => {
163
163
  onPress={() => setShowHostSection(!showHostSection)}
164
164
  />
165
165
  {showHostSection ? (
166
- <AllAudienceParticipants
166
+ <AllHostParticipants
167
167
  emptyMessage={'No Host has joined yet.'}
168
168
  uids={hostUids}
169
169
  isMobile={isSmall()}
@@ -19,6 +19,7 @@ import {
19
19
  trimText,
20
20
  useIsDesktop,
21
21
  useResponsive,
22
+ isValidReactComponent,
22
23
  } from '../utils/common';
23
24
  import {useMeetingInfo} from './meeting-info/useMeetingInfo';
24
25
  import {useCustomization} from 'customization-implementation';
@@ -276,23 +277,23 @@ const Precall = () => {
276
277
  res(devices);
277
278
  }),
278
279
  ).then((devices: MediaDeviceInfo[]) => {
279
- SDKEvents.emit('preJoin', meetingTitle, devices);
280
+ SDKEvents.emit('ready-to-join', meetingTitle, devices);
280
281
  });
281
282
  }
282
283
  }, [isJoinDataFetched]);
283
284
 
284
285
  const FpePrecallComponent = useCustomization((data) => {
285
286
  // commented for v1 release
286
- // if (
287
- // data?.components?.precall &&
288
- // typeof data?.components?.precall !== 'object'
289
- // ) {
290
- // if (isValidReactComponent(data?.components?.precall)) {
291
- // return data?.components?.precall;
292
- // }
293
- // return undefined;
294
- // }
295
- return undefined;
287
+ if (
288
+ data?.components?.precall &&
289
+ typeof data?.components?.precall !== 'object'
290
+ ) {
291
+ if (isValidReactComponent(data?.components?.precall)) {
292
+ return data?.components?.precall;
293
+ }
294
+ return undefined;
295
+ }
296
+ // return undefined;
296
297
  });
297
298
 
298
299
  const isDesktop = useIsDesktop();
@@ -1,4 +1,4 @@
1
- import React, {createContext, useState, useEffect} from 'react';
1
+ import React, {createContext, useState, useEffect, useRef} from 'react';
2
2
  import SDKMethodEventsManager, {
3
3
  _InternalSDKMethodEventsMap,
4
4
  } from '../utils/SdkMethodEvents';
@@ -31,12 +31,6 @@ type SdkApiContextInterface = {
31
31
  customization?: CustomizationApiInterface;
32
32
  promise?: extractPromises<_InternalSDKMethodEventsMap['customize']>;
33
33
  };
34
- // mediaDevice: {
35
- // [k in MediaDeviceInfo['kind']]?: {
36
- // deviceId: string;
37
- // promise?: extractPromises<_InternalSDKMethodEventsMap['mediaDevice']>;
38
- // };
39
- // };
40
34
  microphoneDevice: {
41
35
  deviceId?: string;
42
36
  promise?: extractPromises<_InternalSDKMethodEventsMap['microphoneDevice']>;
@@ -49,9 +43,19 @@ type SdkApiContextInterface = {
49
43
  deviceId?: string;
50
44
  promise?: extractPromises<_InternalSDKMethodEventsMap['cameraDevice']>;
51
45
  };
46
+ onMuteAudio: (callback: _InternalSDKMethodEventsMap['muteAudio']) => void;
47
+ onMuteVideo: (callback: _InternalSDKMethodEventsMap['muteVideo']) => void;
52
48
  clearState: (key: keyof _InternalSDKMethodEventsMap, param?: any) => void;
53
49
  };
54
50
 
51
+ const defaultMuteListener = ((_, rej) => {
52
+ rej(
53
+ new Error(
54
+ "Video call not initialized, call this method on 'join' or 'ready-to-join' event listener callback",
55
+ ),
56
+ );
57
+ }) as _InternalSDKMethodEventsMap['muteVideo'];
58
+
55
59
  const SdkApiInitState: SdkApiContextInterface = {
56
60
  join: {
57
61
  initialized: false,
@@ -61,17 +65,20 @@ const SdkApiInitState: SdkApiContextInterface = {
61
65
  microphoneDevice: {},
62
66
  speakerDevice: {},
63
67
  cameraDevice: {},
68
+ onMuteVideo: (_) => {},
69
+ onMuteAudio: (_) => {},
64
70
  clearState: () => {},
65
71
  };
66
72
 
67
73
  export const SDK_MEETING_TAG = 'sdk-initiated-meeting';
68
74
 
69
- export const SdkApiContext = createContext(SdkApiInitState);
75
+ export const SdkApiContext =
76
+ createContext<SdkApiContextInterface>(SdkApiInitState);
70
77
 
71
78
  let moduleEventsUnsub: any[] = [];
72
79
 
73
80
  type commonEventHandlers = {
74
- [K in keyof _InternalSDKMethodEventsMap]?: (
81
+ [K in keyof Omit<_InternalSDKMethodEventsMap, 'muteVideo' | 'muteAudio'>]?: (
75
82
  setter: (p: SdkApiContextInterface[K]) => void,
76
83
  ) => Unsubscribe;
77
84
  };
@@ -117,19 +124,6 @@ const commonEventHandlers: commonEventHandlers = {
117
124
  res();
118
125
  });
119
126
  },
120
- // mediaDevice: (setter) => {
121
- // return SDKMethodEventsManager.on(
122
- // 'mediaDevice',
123
- // (res, rej, deviceId, kind) => {
124
- // setter({
125
- // [kind]: {
126
- // deviceId,
127
- // promise: {res, rej},
128
- // },
129
- // });
130
- // },
131
- // );
132
- // },
133
127
  microphoneDevice: (setter) => {
134
128
  return SDKMethodEventsManager.on(
135
129
  'microphoneDevice',
@@ -167,12 +161,6 @@ const registerListener = () => {
167
161
  commonEventHandlers.join((state) => {
168
162
  SdkApiInitState.join = state;
169
163
  }),
170
- // commonEventHandlers.mediaDevice((state) => {
171
- // SdkApiInitState.mediaDevice = {
172
- // ...SdkApiInitState.mediaDevice,
173
- // ...state,
174
- // };
175
- // }),
176
164
  commonEventHandlers.microphoneDevice((state) => {
177
165
  SdkApiInitState.microphoneDevice = state;
178
166
  }),
@@ -182,6 +170,8 @@ const registerListener = () => {
182
170
  commonEventHandlers.cameraDevice((state) => {
183
171
  SdkApiInitState.cameraDevice = state;
184
172
  }),
173
+ SDKMethodEventsManager.on('muteVideo', defaultMuteListener),
174
+ SDKMethodEventsManager.on('muteAudio', defaultMuteListener),
185
175
  ];
186
176
  };
187
177
 
@@ -194,9 +184,6 @@ const SdkApiContextProvider: React.FC = (props) => {
194
184
  const [userCustomization, setUserCustomization] = useState(
195
185
  SdkApiInitState.customize,
196
186
  );
197
- // const [mediaDeviceState, setMediaDeviceState] = useState(
198
- // SdkApiInitState.setCamera,
199
- // );
200
187
  const [microphoneDeviceState, setMicrophoneDeviceState] = useState(
201
188
  SdkApiInitState.microphoneDevice,
202
189
  );
@@ -207,57 +194,50 @@ const SdkApiContextProvider: React.FC = (props) => {
207
194
  SdkApiInitState.cameraDevice,
208
195
  );
209
196
 
210
- const clearState: SdkApiContextInterface['clearState'] = (key, param) => {
197
+ const muteVideoListener = useRef(defaultMuteListener);
198
+ const muteAudioListener = useRef(defaultMuteListener);
199
+
200
+ const setMuteVideoListener = (
201
+ value: _InternalSDKMethodEventsMap['muteVideo'],
202
+ ) => {
203
+ muteVideoListener.current = value;
204
+ };
205
+
206
+ const setMuteAudioListener = (
207
+ value: _InternalSDKMethodEventsMap['muteAudio'],
208
+ ) => {
209
+ muteAudioListener.current = value;
210
+ };
211
+
212
+ const clearState: SdkApiContextInterface['clearState'] = (key) => {
211
213
  switch (key) {
212
214
  case 'join':
213
215
  setJoinState(SdkApiInitState.join);
214
- return;
216
+ break;
215
217
  case 'customize':
216
218
  setUserCustomization(SdkApiInitState.customize);
217
- return;
219
+ break;
218
220
  case 'microphoneDevice':
219
221
  setMicrophoneDeviceState({});
222
+ break;
220
223
  case 'speakerDevice':
221
224
  setSpeakerDeviceState({});
225
+ break;
222
226
  case 'cameraDevice':
223
227
  setCameraDeviceState({});
224
- // case 'mediaDevice':
225
- // setMediaDeviceState((currentState) => {
226
- // currentState[param] = undefined;
227
- // return currentState;
228
- // });
228
+ break;
229
+ case 'muteVideo':
230
+ setMuteVideoListener(defaultMuteListener);
231
+ break;
232
+ case 'muteAudio':
233
+ setMuteVideoListener(defaultMuteListener);
234
+ break;
229
235
  }
230
236
  };
231
237
 
232
238
  useEffect(() => {
233
239
  deRegisterListener();
234
240
 
235
- // const applyPromiseWrapper = (
236
- // state: SdkApiContextInterface['mediaDevice'],
237
- // kind: MediaDeviceInfo['kind'],
238
- // ) => {
239
- // if (state[kind]) {
240
- // state[kind].promise.res = () => {
241
- // const resolve = state[kind].promise.res;
242
- // clearState('mediaDevice', kind);
243
- // resolve();
244
- // };
245
- // state[kind].promise.rej = (reason?: any) => {
246
- // const reject = state[kind].promise.rej;
247
- // clearState('mediaDevice', kind);
248
- // reject(reason);
249
- // };
250
- // }
251
- // };
252
-
253
- // setMediaDeviceState((currentState) => {
254
- // // applyPromiseWrapper(currentState, 'audiooutput');
255
- // // applyPromiseWrapper(currentState, 'audioinput');
256
- // // applyPromiseWrapper(currentState, 'videoinput');
257
- //
258
- // return currentState;
259
- // });
260
-
261
241
  const unsub = [
262
242
  commonEventHandlers.customize((state) => {
263
243
  setUserCustomization(state);
@@ -274,18 +254,12 @@ const SdkApiContextProvider: React.FC = (props) => {
274
254
  commonEventHandlers.cameraDevice((state) => {
275
255
  setCameraDeviceState(state);
276
256
  }),
277
- // commonEventHandlers.mediaDevice((state) => {
278
- // // applyPromiseWrapper(state, 'audioinput');
279
- // // applyPromiseWrapper(state, 'audiooutput');
280
- // // applyPromiseWrapper(state, 'videoinput');
281
- //
282
- // setMediaDeviceState((currentState) => {
283
- // return {
284
- // ...currentState,
285
- // ...state,
286
- // };
287
- // });
288
- // }),
257
+ SDKMethodEventsManager.on('muteVideo', (...args) => {
258
+ muteVideoListener.current(...args);
259
+ }),
260
+ SDKMethodEventsManager.on('muteAudio', (...args) => {
261
+ muteAudioListener.current(...args);
262
+ }),
289
263
  ];
290
264
 
291
265
  return () => {
@@ -302,6 +276,8 @@ const SdkApiContextProvider: React.FC = (props) => {
302
276
  microphoneDevice: microphoneDeviceState,
303
277
  speakerDevice: speakerDeviceState,
304
278
  cameraDevice: cameraDeviceState,
279
+ onMuteAudio: setMuteAudioListener,
280
+ onMuteVideo: setMuteVideoListener,
305
281
  clearState,
306
282
  }}>
307
283
  {props.children}
@@ -0,0 +1,92 @@
1
+ import React, { useContext, useEffect, createContext, useRef } from 'react';
2
+ import { SdkApiContext } from './SdkApiContext';
3
+ import {
4
+ useMuteToggleLocal,
5
+ useLocalUserInfo,
6
+ ToggleState,
7
+ MUTE_LOCAL_TYPE,
8
+ } from 'customization-api';
9
+
10
+ export const SdkMuteQueueContext = createContext({
11
+ videoMuteQueue: { current: [] },
12
+ audioMuteQueue: { current: [] },
13
+ });
14
+
15
+ const SdkMuteToggleListener = (props) => {
16
+ const videoMuteQueue = useRef([]);
17
+ const audioMuteQueue = useRef([]);
18
+
19
+ const {
20
+ onMuteVideo: onSdkMuteVideo,
21
+ onMuteAudio: onSdkMuteAudio,
22
+ clearState,
23
+ } = useContext(SdkApiContext);
24
+ const local = useLocalUserInfo();
25
+ const toggleMute = useMuteToggleLocal();
26
+
27
+ const queuedToggleMute = (type, status) => {
28
+ const localstatus =
29
+ local[type === MUTE_LOCAL_TYPE.video ? 'video' : 'audio'];
30
+
31
+ // if ([ToggleState.enabling, ToggleState.disabling].includes(localstatus)) {
32
+ // if ({[ToggleState.enabling]: true, [ToggleState.disabling]: true}[localstatus]) {
33
+ if (
34
+ ToggleState.enabling === localstatus ||
35
+ ToggleState.disabling === localstatus
36
+ ) {
37
+ return new Promise((res, rej) => {
38
+ if (type === MUTE_LOCAL_TYPE.video) {
39
+ videoMuteQueue.current.push({
40
+ resolveQueued: res,
41
+ rejectQueued: rej,
42
+ action: status ? ToggleState.disabled : ToggleState.enabled,
43
+ });
44
+ } else {
45
+ audioMuteQueue.current.push({
46
+ resolveQueued: res,
47
+ rejectQueued: rej,
48
+ action: status ? ToggleState.disabled : ToggleState.enabled,
49
+ });
50
+ }
51
+ });
52
+ } else
53
+ return toggleMute(
54
+ type,
55
+ status ? ToggleState.disabled : ToggleState.enabled,
56
+ );
57
+ };
58
+
59
+ useEffect(() => {
60
+ onSdkMuteVideo(async (res, rej, mute) => {
61
+ const status = typeof mute === 'function' ? mute(!local.video) : mute;
62
+ try {
63
+ await queuedToggleMute(MUTE_LOCAL_TYPE.video, status);
64
+ res();
65
+ } catch (e) {
66
+ rej(e);
67
+ }
68
+ });
69
+ onSdkMuteAudio(async (res, rej, mute) => {
70
+ const status = typeof mute === 'function' ? mute(!local.audio) : mute;
71
+ try {
72
+ await queuedToggleMute(MUTE_LOCAL_TYPE.audio, status);
73
+ res();
74
+ } catch (e) {
75
+ rej(e);
76
+ }
77
+ });
78
+
79
+ return () => {
80
+ clearState('muteVideo');
81
+ clearState('muteAudio');
82
+ };
83
+ }, [local]);
84
+
85
+ return (
86
+ <SdkMuteQueueContext.Provider value={{ videoMuteQueue, audioMuteQueue }}>
87
+ {props.children}
88
+ </SdkMuteQueueContext.Provider>
89
+ );
90
+ };
91
+
92
+ export default SdkMuteToggleListener;
@@ -67,6 +67,7 @@ import {VideoCallProvider} from '../components/useVideoCall';
67
67
  import {SdkApiContext, SDK_MEETING_TAG} from '../components/SdkApiContext';
68
68
  import isSDK from '../utils/isSDK';
69
69
  import {useSetMeetingInfo} from '../components/meeting-info/useSetMeetingInfo';
70
+ import SdkMuteToggleListener from '../components/SdkMuteToggleListener';
70
71
 
71
72
  enum RnEncryptionEnum {
72
73
  /**
@@ -252,15 +253,15 @@ const VideoCall: React.FC = () => {
252
253
  }, 0);
253
254
  },
254
255
  UserJoined: (uid: UidType) => {
255
- console.log("UIKIT Callback: UserJoined", uid)
256
+ console.log('UIKIT Callback: UserJoined', uid);
256
257
  SDKEvents.emit('rtc-user-joined', uid);
257
258
  },
258
259
  UserOffline: (uid: UidType) => {
259
- console.log("UIKIT Callback: UserOffline", uid)
260
+ console.log('UIKIT Callback: UserOffline', uid);
260
261
  SDKEvents.emit('rtc-user-joined', uid);
261
262
  },
262
263
  RemoteAudioStateChanged: (uid: UidType, status: 0 | 2) => {
263
- console.log("UIKIT Callback: RemoteAudioStateChanged", uid, status)
264
+ console.log('UIKIT Callback: RemoteAudioStateChanged', uid, status);
264
265
  if (status === 0) {
265
266
  SDKEvents.emit('rtc-user-unpublished', uid, 'audio');
266
267
  } else {
@@ -268,7 +269,7 @@ const VideoCall: React.FC = () => {
268
269
  }
269
270
  },
270
271
  RemoteVideoStateChanged: (uid: UidType, status: 0 | 2) => {
271
- console.log("UIKIT Callback: RemoteVideoStateChanged", uid, status)
272
+ console.log('UIKIT Callback: RemoteVideoStateChanged', uid, status);
272
273
  if (status === 0) {
273
274
  SDKEvents.emit('rtc-user-unpublished', uid, 'video');
274
275
  } else {
@@ -342,31 +343,33 @@ const VideoCall: React.FC = () => {
342
343
  {!isMobileUA() && (
343
344
  <PermissionHelper />
344
345
  )}
345
- {callActive ? (
346
- <VideoMeetingDataProvider>
347
- <VideoCallProvider>
348
- <VideoCallScreen />
349
- </VideoCallProvider>
350
- </VideoMeetingDataProvider>
351
- ) : $config.PRECALL ? (
352
- <PreCallProvider
353
- value={{
354
- callActive,
355
- setCallActive,
356
- isCameraAvailable,
357
- isMicAvailable,
358
- setCameraAvailable,
359
- setMicAvailable,
360
- isPermissionRequested,
361
- setIsPermissionRequested,
362
- isSpeakerAvailable,
363
- setSpeakerAvailable,
364
- }}>
365
- <Precall />
366
- </PreCallProvider>
367
- ) : (
368
- <></>
369
- )}
346
+ <SdkMuteToggleListener>
347
+ {callActive ? (
348
+ <VideoMeetingDataProvider>
349
+ <VideoCallProvider>
350
+ <VideoCallScreen />
351
+ </VideoCallProvider>
352
+ </VideoMeetingDataProvider>
353
+ ) : $config.PRECALL ? (
354
+ <PreCallProvider
355
+ value={{
356
+ callActive,
357
+ setCallActive,
358
+ isCameraAvailable,
359
+ isMicAvailable,
360
+ setCameraAvailable,
361
+ setMicAvailable,
362
+ isPermissionRequested,
363
+ setIsPermissionRequested,
364
+ isSpeakerAvailable,
365
+ setSpeakerAvailable,
366
+ }}>
367
+ <Precall />
368
+ </PreCallProvider>
369
+ ) : (
370
+ <></>
371
+ )}
372
+ </SdkMuteToggleListener>
370
373
  </NetworkQualityProvider>
371
374
  </CustomUserContextHolder>
372
375
  </LocalUserContext>
@@ -44,9 +44,6 @@ export interface userEventsMapInterface {
44
44
  'devices-selected-speaker-changed': (
45
45
  deviceId: MediaDeviceInfo['deviceId'],
46
46
  ) => void;
47
- 'devices-device-list-updated': (
48
- deviceId: MediaDeviceInfo['deviceId'],
49
- ) => void;
50
47
  }
51
48
 
52
49
  const SDKEvents = createNanoEvents<userEventsMapInterface>();
@@ -1,5 +1,22 @@
1
- import {SdkMethodEvents} from '../SDKAppWrapper';
2
1
  import {createNanoEvents, Emitter} from 'nanoevents';
2
+ import {
3
+ CustomizationApiInterface,
4
+ MeetingInfoContextInterface,
5
+ } from 'customization-api';
6
+ import {deviceId} from '../components/DeviceConfigure';
7
+
8
+ export interface SdkMethodEvents {
9
+ customize: (customization: CustomizationApiInterface) => void;
10
+ join(
11
+ roomid: string | Partial<MeetingInfoContextInterface['data']>,
12
+ skipPrecall?: boolean,
13
+ ): MeetingInfoContextInterface['data'];
14
+ microphoneDevice: (deviceId: deviceId) => void;
15
+ speakerDevice: (deviceId: deviceId) => void;
16
+ cameraDevice: (deviceId: deviceId) => void;
17
+ muteAudio: (mute: boolean | ((currentMute: boolean) => boolean)) => void;
18
+ muteVideo: (mute: boolean | ((currentMute: boolean) => boolean)) => void;
19
+ }
3
20
 
4
21
  type EventParameterHelper<T extends keyof SdkMethodEvents> = Parameters<
5
22
  SdkMethodEvents[T]
@@ -15,6 +15,7 @@ import {useContext, useEffect, useRef, useState} from 'react';
15
15
  import {ToggleState} from '../../agora-rn-uikit/src/Contexts/PropsContext';
16
16
  import {isMobileUA, isWebInternal} from './common';
17
17
  import {AppState} from 'react-native';
18
+ import {SdkMuteQueueContext} from '../components/SdkMuteToggleListener';
18
19
 
19
20
  export enum MUTE_LOCAL_TYPE {
20
21
  audio,
@@ -27,6 +28,8 @@ function useMuteToggleLocal() {
27
28
  const {RtcEngine, dispatch} = useRtc();
28
29
  const local = useLocalUserInfo();
29
30
 
31
+ const {videoMuteQueue, audioMuteQueue} = useContext(SdkMuteQueueContext);
32
+
30
33
  const appState = useRef(AppState.currentState);
31
34
  const [appStateVisible, setAppStateVisible] = useState(appState.current);
32
35
  const isCamON = useRef(local.video);
@@ -69,7 +72,26 @@ function useMuteToggleLocal() {
69
72
  }
70
73
  }, [appStateVisible]);
71
74
 
72
- return async (type: MUTE_LOCAL_TYPE) => {
75
+ const toggleMute = async (
76
+ type: MUTE_LOCAL_TYPE,
77
+ _action?: ToggleState,
78
+ _fromSdk?: boolean,
79
+ ) => {
80
+ const queueRef =
81
+ type === MUTE_LOCAL_TYPE.video ? videoMuteQueue : audioMuteQueue;
82
+
83
+ const handleQueue = async () => {
84
+ if (queueRef.current.length > 0) {
85
+ const queueItem = queueRef.current.shift();
86
+ try {
87
+ await toggleMute(type, queueItem.action, true);
88
+ queueItem.resolveQueued();
89
+ } catch (e) {
90
+ queueItem.rejectQueued(e);
91
+ }
92
+ }
93
+ };
94
+
73
95
  switch (type) {
74
96
  case MUTE_LOCAL_TYPE.audio:
75
97
  let localAudioState = local.audio;
@@ -78,35 +100,45 @@ function useMuteToggleLocal() {
78
100
  localAudioState === ToggleState.enabled ||
79
101
  localAudioState === ToggleState.disabled
80
102
  ) {
81
- // Disable UI
82
- dispatch({
83
- type: 'LocalMuteAudio',
84
- value: [
85
- localAudioState === ToggleState.enabled
86
- ? ToggleState.disabling
87
- : ToggleState.enabling,
88
- ],
89
- });
90
-
91
- try {
92
- await RtcEngine.muteLocalAudioStream(
93
- localAudioState === ToggleState.enabled,
94
- );
95
- // Enable UI
103
+ if (localAudioState !== _action) {
104
+ // Disable UI
96
105
  dispatch({
97
106
  type: 'LocalMuteAudio',
98
107
  value: [
99
108
  localAudioState === ToggleState.enabled
100
- ? ToggleState.disabled
101
- : ToggleState.enabled,
109
+ ? ToggleState.disabling
110
+ : ToggleState.enabling,
102
111
  ],
103
112
  });
104
- } catch (e) {
105
- console.error(e);
106
- dispatch({
107
- type: 'LocalMuteAudio',
108
- value: [localAudioState],
109
- });
113
+
114
+ try {
115
+ await RtcEngine.muteLocalAudioStream(
116
+ localAudioState === ToggleState.enabled,
117
+ );
118
+ // Enable UI
119
+ dispatch({
120
+ type: 'LocalMuteAudio',
121
+ value: [
122
+ localAudioState === ToggleState.enabled
123
+ ? ToggleState.disabled
124
+ : ToggleState.enabled,
125
+ ],
126
+ });
127
+ handleQueue();
128
+ } catch (e) {
129
+ dispatch({
130
+ type: 'LocalMuteAudio',
131
+ value: [localAudioState],
132
+ });
133
+ handleQueue();
134
+ if (_fromSdk) {
135
+ throw e;
136
+ } else {
137
+ console.error('Error toggling audio', e);
138
+ }
139
+ }
140
+ } else {
141
+ handleQueue();
110
142
  }
111
143
  }
112
144
  break;
@@ -118,45 +150,57 @@ function useMuteToggleLocal() {
118
150
  localVideoState === ToggleState.disabled
119
151
  ) {
120
152
  // Disable UI
121
- dispatch({
122
- type: 'LocalMuteVideo',
123
- value: [
124
- localVideoState === ToggleState.enabled
125
- ? ToggleState.disabling
126
- : ToggleState.enabling,
127
- ],
128
- });
129
-
130
- try {
131
- //enableLocalVideo not available on web
132
- isWebInternal()
133
- ? await RtcEngine.muteLocalVideoStream(
134
- localVideoState === ToggleState.enabled ? true : false,
135
- )
136
- : await RtcEngine.enableLocalVideo(
137
- localVideoState === ToggleState.enabled ? false : true,
138
- );
139
-
140
- // Enable UI
153
+ if (localVideoState !== _action) {
141
154
  dispatch({
142
155
  type: 'LocalMuteVideo',
143
156
  value: [
144
157
  localVideoState === ToggleState.enabled
145
- ? ToggleState.disabled
146
- : ToggleState.enabled,
158
+ ? ToggleState.disabling
159
+ : ToggleState.enabling,
147
160
  ],
148
161
  });
149
- } catch (e) {
150
- console.log('error while dispatching');
151
- dispatch({
152
- type: 'LocalMuteVideo',
153
- value: [localVideoState],
154
- });
162
+
163
+ try {
164
+ //enableLocalVideo not available on web
165
+ isWebInternal()
166
+ ? await RtcEngine.muteLocalVideoStream(
167
+ localVideoState === ToggleState.enabled ? true : false,
168
+ )
169
+ : await RtcEngine.enableLocalVideo(
170
+ localVideoState === ToggleState.enabled ? false : true,
171
+ );
172
+
173
+ // Enable UI
174
+ dispatch({
175
+ type: 'LocalMuteVideo',
176
+ value: [
177
+ localVideoState === ToggleState.enabled
178
+ ? ToggleState.disabled
179
+ : ToggleState.enabled,
180
+ ],
181
+ });
182
+ handleQueue();
183
+ } catch (e) {
184
+ dispatch({
185
+ type: 'LocalMuteVideo',
186
+ value: [localVideoState],
187
+ });
188
+ handleQueue();
189
+ if (_fromSdk) {
190
+ throw e;
191
+ } else {
192
+ console.error('Error toggling video', e);
193
+ }
194
+ }
195
+ } else {
196
+ handleQueue();
155
197
  }
156
198
  }
157
199
  break;
158
200
  }
159
201
  };
202
+
203
+ return toggleMute;
160
204
  }
161
205
 
162
206
  export default useMuteToggleLocal;
@@ -4,7 +4,7 @@
4
4
  // "files":["./customization-api/index.ts","./global.d.ts","./bridge/rtm/web/index.ts","./bridge/rtc/webNg/index.ts"],
5
5
  // --------------------------------------------------------
6
6
  // Use with typescript tsc command
7
- "files": ["./src/SDKAppWrapper.tsx", "./index.rsdk.tsx", "./global.d.ts"],
7
+ "files": ["./index.rsdk.tsx", "./global.d.ts"],
8
8
  // --------------------------------------------------------
9
9
  "compilerOptions": {
10
10
  /* Basic Options */
@@ -47,8 +47,8 @@
47
47
  // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
48
48
 
49
49
  /* Module Resolution Options */
50
- // "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
51
- "noResolve": true /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
50
+ "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
51
+ "noResolve": false /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
52
52
  "baseUrl": "./" /* Base directory to resolve non-absolute module names. */,
53
53
  "resolveJsonModule": true,
54
54
  // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
@@ -4,7 +4,7 @@
4
4
  // "files":["./customization-api/index.ts","./global.d.ts","./bridge/rtm/web/index.ts","./bridge/rtc/webNg/index.ts"],
5
5
  // --------------------------------------------------------
6
6
  // Use with typescript tsc command
7
- "files": ["./src/SDKAppWrapper.tsx", "./index.wsdk.tsx", "./global.d.ts"],
7
+ "files": ["./index.wsdk.tsx", "./global.d.ts"],
8
8
  // --------------------------------------------------------
9
9
  "compilerOptions": {
10
10
  /* Basic Options */