agora-appbuilder-core 3.0.9 → 3.0.10

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.
Files changed (248) hide show
  1. package/Readme.md +6 -0
  2. package/package.json +2 -2
  3. package/template/_package-lock.json +5871 -4728
  4. package/template/agora-rn-uikit/src/Contexts/LocalUserContext.tsx +4 -0
  5. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +18 -0
  6. package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +2 -0
  7. package/template/agora-rn-uikit/src/Controls/BtnTemplate.tsx +30 -26
  8. package/template/agora-rn-uikit/src/Controls/Icons.ts +30 -83
  9. package/template/agora-rn-uikit/src/Controls/ImageIcon.tsx +6 -6
  10. package/template/agora-rn-uikit/src/Reducer/ActiveSpeakerDetected.ts +11 -0
  11. package/template/agora-rn-uikit/src/Reducer/LocalMuteAudio.ts +1 -0
  12. package/template/agora-rn-uikit/src/Reducer/LocalMuteVideo.ts +1 -0
  13. package/template/agora-rn-uikit/src/Reducer/LocalPermissionState.ts +24 -0
  14. package/template/agora-rn-uikit/src/Reducer/RemoteAudioStateChanged.ts +1 -0
  15. package/template/agora-rn-uikit/src/Reducer/RemoteVideoStateChanged.ts +1 -0
  16. package/template/agora-rn-uikit/src/Reducer/UpdateDualStreamMode.ts +1 -0
  17. package/template/agora-rn-uikit/src/Reducer/UserJoined.ts +2 -0
  18. package/template/agora-rn-uikit/src/Reducer/UserMuteRemoteAudio.ts +1 -0
  19. package/template/agora-rn-uikit/src/Reducer/UserMuteRemoteVideo.ts +1 -0
  20. package/template/agora-rn-uikit/src/Reducer/UserOffline.ts +1 -0
  21. package/template/agora-rn-uikit/src/Reducer/UserPin.ts +11 -0
  22. package/template/agora-rn-uikit/src/Reducer/index.ts +3 -0
  23. package/template/agora-rn-uikit/src/Rtc/Create.tsx +89 -1
  24. package/template/agora-rn-uikit/src/RtcConfigure.tsx +39 -2
  25. package/template/agora-rn-uikit/src/Views/MaxVideoView.native.tsx +15 -5
  26. package/template/agora-rn-uikit/src/Views/MaxVideoView.tsx +15 -9
  27. package/template/agora-rn-uikit/src/index.ts +3 -1
  28. package/template/android/app/build.gradle +1 -0
  29. package/template/android/app/src/main/AndroidManifest.xml +22 -15
  30. package/template/android/app/src/main/assets/fonts/SourceSansPro-Regular.ttf +0 -0
  31. package/template/android/app/src/main/assets/fonts/icomoon.ttf +0 -0
  32. package/template/android/app/src/main/java/com/helloworld/MainActivity.java +50 -0
  33. package/template/android/app/src/main/res/values/colors.xml +7 -0
  34. package/template/android/build.gradle +3 -3
  35. package/template/babel.config.js +1 -0
  36. package/template/bridge/rtc/webNg/RtcEngine.ts +110 -17
  37. package/template/customization-api/sub-components.ts +1 -1
  38. package/template/customization-api/typeDefinition.ts +2 -1
  39. package/template/electron/index.html +27 -27
  40. package/template/electron/renderer/index.js +1 -0
  41. package/template/global.d.ts +25 -4
  42. package/template/index.rsdk.tsx +1 -0
  43. package/template/index.web.js +2 -1
  44. package/template/index.wsdk.tsx +1 -1
  45. package/template/ios/HelloWorld/Info.plist +14 -1
  46. package/template/ios/HelloWorld.xcodeproj/project.pbxproj +17 -0
  47. package/template/metro.config.js +1 -1
  48. package/template/package.json +18 -7
  49. package/template/react-native-toast-message/index.d.ts +43 -43
  50. package/template/react-native-toast-message/src/colors/index.js +3 -2
  51. package/template/react-native-toast-message/src/components/base/index.js +46 -59
  52. package/template/react-native-toast-message/src/components/base/styles.js +16 -32
  53. package/template/react-native-toast-message/src/components/checkbox.js +178 -0
  54. package/template/react-native-toast-message/src/components/error.js +3 -2
  55. package/template/react-native-toast-message/src/components/info.js +3 -2
  56. package/template/react-native-toast-message/src/components/success.js +3 -2
  57. package/template/react-native-toast-message/src/index.js +122 -31
  58. package/template/react-native-toast-message/src/index.sdk.tsx +125 -35
  59. package/template/react-native-toast-message/src/styles.js +3 -4
  60. package/template/react-native-toast-message/src/styles.sdk.ts +3 -4
  61. package/template/react-native.config.js +7 -0
  62. package/template/src/App.tsx +6 -0
  63. package/template/src/AppWrapper.tsx +63 -28
  64. package/template/src/assets/font-styles.css +329 -0
  65. package/template/src/assets/fonts/SourceSansPro-Regular.ttf +0 -0
  66. package/template/src/assets/fonts/icomoon.ttf +0 -0
  67. package/template/src/assets/permission.png +0 -0
  68. package/template/src/assets/selection.json +1 -0
  69. package/template/src/atoms/ActionMenu.tsx +236 -0
  70. package/template/src/atoms/AnimatedActiveSpeaker.native.tsx +71 -0
  71. package/template/src/atoms/AnimatedActiveSpeaker.tsx +84 -0
  72. package/template/src/atoms/AnimatedRings.native.tsx +68 -0
  73. package/template/src/atoms/AnimatedRings.tsx +70 -0
  74. package/template/src/atoms/Card.tsx +61 -0
  75. package/template/src/atoms/CircularProgress.native.tsx +121 -0
  76. package/template/src/atoms/CircularProgress.tsx +102 -0
  77. package/template/src/atoms/CustomIcon.tsx +88 -0
  78. package/template/src/atoms/CustomSwitch.tsx +287 -0
  79. package/template/src/atoms/Dropdown.tsx +306 -0
  80. package/template/src/atoms/HorizontalRule.tsx +3 -1
  81. package/template/src/atoms/IconButton.tsx +162 -0
  82. package/template/src/atoms/ImageIcon.tsx +98 -0
  83. package/template/src/atoms/InfoBubble.tsx +291 -0
  84. package/template/src/atoms/Input.tsx +87 -0
  85. package/template/src/atoms/InviteInfo.tsx +166 -0
  86. package/template/src/atoms/LinkButton.tsx +28 -0
  87. package/template/src/atoms/OutlineButton.tsx +61 -0
  88. package/template/src/atoms/ParticipantsCount.tsx +73 -0
  89. package/template/src/atoms/Popup.tsx +147 -0
  90. package/template/src/atoms/PrimaryButton.tsx +51 -26
  91. package/template/src/atoms/RecordingInfo.tsx +49 -0
  92. package/template/src/atoms/SecondaryButton.tsx +8 -5
  93. package/template/src/atoms/Spacer.tsx +22 -0
  94. package/template/src/atoms/TertiaryButton.tsx +78 -0
  95. package/template/src/atoms/TextInput.tsx +12 -14
  96. package/template/src/atoms/Toggle.tsx +47 -0
  97. package/template/src/atoms/Tooltip.native.tsx +65 -0
  98. package/template/src/atoms/Tooltip.tsx +94 -0
  99. package/template/src/atoms/UserAvatar.tsx +60 -0
  100. package/template/src/components/Chat.tsx +86 -214
  101. package/template/src/components/ChatContext.ts +8 -1
  102. package/template/src/components/ColorConfigure.tsx +1 -1
  103. package/template/src/components/ColorContext.ts +1 -1
  104. package/template/src/components/CommonStyles.ts +44 -0
  105. package/template/src/components/Controls.tsx +342 -42
  106. package/template/src/components/{Controls.native.tsx → Controls1.native.tsx} +6 -4
  107. package/template/src/components/DeviceConfigure.tsx +461 -101
  108. package/template/src/components/DeviceContext.tsx +8 -4
  109. package/template/src/components/EventsConfigure.tsx +144 -7
  110. package/template/src/components/GraphQLProvider.tsx +1 -1
  111. package/template/src/components/GridVideo.tsx +59 -44
  112. package/template/src/components/HostControlView.tsx +114 -35
  113. package/template/src/components/Navbar.tsx +216 -398
  114. package/template/src/components/NetworkQualityContext.tsx +20 -20
  115. package/template/src/components/ParticipantsView.tsx +177 -154
  116. package/template/src/components/PinnedVideo.tsx +207 -120
  117. package/template/src/components/Precall.native.tsx +358 -119
  118. package/template/src/components/Precall.tsx +269 -135
  119. package/template/src/components/RTMConfigure.tsx +27 -4
  120. package/template/src/components/Router.electron.ts +1 -0
  121. package/template/src/components/Router.native.ts +1 -0
  122. package/template/src/components/Router.sdk.ts +1 -0
  123. package/template/src/components/Router.ts +1 -0
  124. package/template/src/components/Settings.tsx +26 -95
  125. package/template/src/components/SettingsView.tsx +251 -56
  126. package/template/src/components/Share.tsx +302 -273
  127. package/template/src/components/StorageContext.tsx +30 -3
  128. package/template/src/components/ToastComponent.tsx +8 -0
  129. package/template/src/components/chat-messages/useChatMessages.tsx +69 -23
  130. package/template/src/components/chat-ui/useChatUIControl.tsx +7 -0
  131. package/template/src/components/common/Error.tsx +20 -6
  132. package/template/src/components/common/Logo.tsx +16 -15
  133. package/template/src/components/contexts/LiveStreamDataContext.tsx +10 -5
  134. package/template/src/components/contexts/VideoMeetingDataContext.tsx +37 -7
  135. package/template/src/components/livestream/LiveStreamContext.tsx +270 -36
  136. package/template/src/components/livestream/Types.ts +39 -14
  137. package/template/src/components/livestream/index.ts +1 -0
  138. package/template/src/components/livestream/views/LiveStreamControls.tsx +12 -4
  139. package/template/src/components/participants/AllAudienceParticipants.tsx +101 -30
  140. package/template/src/components/participants/AllHostParticipants.tsx +103 -34
  141. package/template/src/components/participants/Participant.tsx +302 -0
  142. package/template/src/components/participants/ParticipantName.tsx +13 -7
  143. package/template/src/components/participants/ParticipantSectionTitle.tsx +35 -10
  144. package/template/src/components/participants/ScreenshareParticipants.tsx +144 -12
  145. package/template/src/components/participants/UserActionMenuOptions.tsx +398 -0
  146. package/template/src/components/popups/InvitePopup.tsx +115 -0
  147. package/template/src/components/popups/StopRecordingPopup.tsx +114 -0
  148. package/template/src/components/precall/LocalMute.tsx +84 -14
  149. package/template/src/components/precall/{LocalMute.native.tsx → LocalMute1.native.tsx} +21 -5
  150. package/template/src/components/precall/PermissionHelper.native.tsx +5 -0
  151. package/template/src/components/precall/PermissionHelper.tsx +126 -0
  152. package/template/src/components/precall/PreCallSettings.tsx +52 -0
  153. package/template/src/components/precall/VideoPreview.native.tsx +48 -3
  154. package/template/src/components/precall/VideoPreview.tsx +163 -7
  155. package/template/src/components/precall/joinCallBtn.tsx +15 -2
  156. package/template/src/components/precall/meetingTitle.tsx +15 -12
  157. package/template/src/components/precall/selectDevice.tsx +1 -21
  158. package/template/src/components/precall/textInput.tsx +32 -4
  159. package/template/src/components/precall/usePreCall.tsx +16 -0
  160. package/template/src/components/styles.ts +42 -21
  161. package/template/src/components/useShareLink.tsx +12 -14
  162. package/template/src/components/useToast.tsx +41 -0
  163. package/template/src/components/useVideoCall.tsx +65 -0
  164. package/template/src/language/default-labels/precallScreenLabels.ts +3 -3
  165. package/template/src/pages/Authenticate.tsx +5 -15
  166. package/template/src/pages/Create.tsx +293 -165
  167. package/template/src/pages/Endcall.tsx +148 -0
  168. package/template/src/pages/Join.tsx +93 -67
  169. package/template/src/pages/VideoCall.tsx +89 -64
  170. package/template/src/pages/video-call/ActionSheet.native.tsx +215 -0
  171. package/template/src/pages/video-call/ActionSheet.tsx +226 -0
  172. package/template/src/pages/video-call/ActionSheetContent.tsx +479 -0
  173. package/template/src/pages/video-call/ActionSheetHandle.tsx +38 -0
  174. package/template/src/pages/video-call/ActionSheetStyles.css +138 -0
  175. package/template/src/pages/video-call/DefaultLayouts.ts +4 -4
  176. package/template/src/pages/video-call/NameWithMicIcon.tsx +120 -44
  177. package/template/src/pages/video-call/RenderComponent.tsx +3 -2
  178. package/template/src/pages/video-call/SidePanelHeader.tsx +190 -0
  179. package/template/src/pages/video-call/VideoCallMobileView.tsx +139 -0
  180. package/template/src/pages/video-call/VideoCallScreen.native.tsx +37 -0
  181. package/template/src/pages/video-call/VideoCallScreen.tsx +45 -9
  182. package/template/src/pages/video-call/VideoComponent.tsx +18 -3
  183. package/template/src/pages/video-call/VideoRenderer.tsx +218 -60
  184. package/template/src/rtm-events/constants.ts +2 -0
  185. package/template/src/subComponents/ChatBubble.tsx +123 -83
  186. package/template/src/subComponents/ChatContainer.tsx +257 -84
  187. package/template/src/subComponents/ChatInput.ios.tsx +237 -0
  188. package/template/src/subComponents/ChatInput.tsx +61 -46
  189. package/template/src/subComponents/Checkbox.native.tsx +16 -5
  190. package/template/src/subComponents/Checkbox.tsx +2 -2
  191. package/template/src/subComponents/CopyJoinInfo.tsx +36 -58
  192. package/template/src/subComponents/EndcallPopup.tsx +107 -0
  193. package/template/src/subComponents/FallbackLogo.tsx +122 -40
  194. package/template/src/subComponents/LanguageSelector.tsx +1 -1
  195. package/template/src/subComponents/LayoutIconButton.tsx +201 -0
  196. package/template/src/subComponents/LayoutIconDropdown.tsx +131 -134
  197. package/template/src/subComponents/{LayoutIconDropdown.native.tsx → LayoutIconDropdown1.native.tsx} +4 -18
  198. package/template/src/subComponents/LocalAudioMute.tsx +119 -27
  199. package/template/src/subComponents/LocalEndCall.tsx +71 -33
  200. package/template/src/subComponents/LocalSwitchCamera.tsx +17 -30
  201. package/template/src/subComponents/LocalVideoMute.tsx +117 -27
  202. package/template/src/subComponents/Logo.tsx +3 -4
  203. package/template/src/subComponents/LogoutButton.tsx +1 -1
  204. package/template/src/subComponents/NetworkQualityPill.tsx +60 -63
  205. package/template/src/subComponents/OpenInNativeButton.tsx +3 -3
  206. package/template/src/subComponents/Recording.tsx +28 -29
  207. package/template/src/subComponents/RemoteAudioMute.tsx +83 -29
  208. package/template/src/subComponents/RemoteEndCall.tsx +8 -5
  209. package/template/src/subComponents/RemoteMutePopup.tsx +193 -0
  210. package/template/src/subComponents/RemoteVideoMute.tsx +74 -21
  211. package/template/src/subComponents/RemoveMeetingPopup.tsx +109 -0
  212. package/template/src/subComponents/RemoveScreensharePopup.tsx +109 -0
  213. package/template/src/subComponents/ScreenShareNotice.tsx +83 -8
  214. package/template/src/subComponents/SelectDevice.tsx +404 -61
  215. package/template/src/subComponents/SelectDeviceSettings.backup.tsx +207 -0
  216. package/template/src/subComponents/SelectOAuth.tsx +9 -8
  217. package/template/src/subComponents/SidePanelHeader.tsx +112 -0
  218. package/template/src/subComponents/ToastConfig.tsx +150 -10
  219. package/template/src/subComponents/chat/ChatParticipants.tsx +187 -78
  220. package/template/src/subComponents/livestream/CurrentLiveStreamRequestsView.tsx +95 -32
  221. package/template/src/subComponents/livestream/controls/LocalRaiseHand.tsx +29 -33
  222. package/template/src/subComponents/livestream/controls/RemoteLiveStreamApprovedRequestRecall.tsx +6 -6
  223. package/template/src/subComponents/livestream/controls/RemoteLiveStreamRequestApprove.tsx +24 -11
  224. package/template/src/subComponents/livestream/controls/RemoteLiveStreamRequestReject.tsx +17 -10
  225. package/template/src/subComponents/recording/useRecording.tsx +79 -27
  226. package/template/src/subComponents/screenshare/ScreenshareButton.tsx +52 -70
  227. package/template/src/subComponents/screenshare/ScreenshareConfigure.native.tsx +11 -2
  228. package/template/src/subComponents/screenshare/ScreenshareConfigure.tsx +26 -4
  229. package/template/src/theme/index.ts +46 -0
  230. package/template/src/utils/PlatformWrapper.tsx +21 -0
  231. package/template/src/utils/common.tsx +155 -1
  232. package/template/src/utils/hexadecimalTransparency.ts +108 -0
  233. package/template/src/utils/index.tsx +19 -0
  234. package/template/src/utils/isMobileOrTablet.ts +7 -2
  235. package/template/src/utils/pendingStateUpdateHelper.ts +19 -0
  236. package/template/src/utils/useButtonTemplate.tsx +1 -0
  237. package/template/src/utils/useFocus.tsx +46 -0
  238. package/template/src/utils/useIsActiveSpeaker.ts +27 -0
  239. package/template/src/utils/useIsHandRaised.ts +13 -0
  240. package/template/src/utils/useMuteToggleLocal.ts +54 -3
  241. package/template/src/utils/useRemoteEndScreenshare.ts +26 -0
  242. package/template/src/utils/useRemoteRequest.ts +84 -0
  243. package/template/web/index.html +5 -0
  244. package/template/webpack.commons.js +13 -8
  245. package/template/webpack.web.config.js +1 -0
  246. package/template/src/assets/icons.ts +0 -102
  247. package/template/src/components/participants/MeParticipant.tsx +0 -38
  248. package/template/src/components/participants/RemoteParticipants.tsx +0 -71
@@ -9,36 +9,111 @@
9
9
  information visit https://appbuilder.agora.io.
10
10
  *********************************************
11
11
  */
12
- import React, {useState, useEffect, useCallback} from 'react';
12
+ import React, {
13
+ useState,
14
+ useEffect,
15
+ useCallback,
16
+ useMemo,
17
+ useRef,
18
+ useContext,
19
+ } from 'react';
13
20
  import {ClientRole} from '../../agora-rn-uikit';
14
21
  import DeviceContext from './DeviceContext';
15
- import AgoraRTC from 'agora-rtc-sdk-ng';
16
- import {useRtc} from 'customization-api';
22
+ import AgoraRTC, {DeviceInfo} from 'agora-rtc-sdk-ng';
23
+ import {useRtc, PrimaryButton} from 'customization-api';
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';
28
+ import StorageContext from './StorageContext';
29
+
30
+ import type RtcEngine from '../../bridge/rtc/webNg/';
31
+ import ColorContext from './ColorContext';
32
+
33
+ const log = (...args) => {
34
+ console.log('[DeviceConfigure] ', ...args);
35
+ };
36
+
37
+ type WebRtcEngineInstance = InstanceType<typeof RtcEngine>;
17
38
 
18
39
  interface Props {
19
40
  userRole: ClientRole;
20
41
  }
21
- interface deviceInfo {
22
- deviceId: string;
23
- groupId: string;
24
- kind: string;
25
- label: string;
26
- }
27
- interface changedDeviceInfo {
28
- device: deviceInfo;
29
- initAt: number;
30
- state: 'INACTIVE' | 'ACTIVE';
31
- updateAt: number;
32
- }
42
+ type deviceInfo = MediaDeviceInfo;
43
+ type deviceId = deviceInfo['deviceId'];
44
+ type deviceKind = deviceInfo['kind'];
45
+
33
46
  const DeviceConfigure: React.FC<Props> = (props: any) => {
34
- const [selectedCam, setSelectedCam] = useState('');
35
- const [selectedMic, setSelectedMic] = useState('');
36
- const [deviceList, setDeviceList] = useState<any>([]);
37
47
  const rtc = useRtc();
48
+ const [selectedCam, setUiSelectedCam] = useState('');
49
+ const [selectedMic, setUiSelectedMic] = useState('');
50
+ const [selectedSpeaker, setUiSelectedSpeaker] = useState('');
51
+ const [deviceList, setDeviceList] = useState<deviceInfo[]>([]);
52
+
53
+ const {primaryColor} = useContext(ColorContext);
54
+ const {store, setStore} = useContext(StorageContext);
55
+ const {rememberedDevicesList, activeDeviceId} = store;
56
+
57
+ const isChrome = useMemo(() => {
58
+ return (
59
+ deviceList.filter((device) => device.deviceId === 'default').length > 0
60
+ );
61
+ }, [deviceList]);
62
+
63
+ // const rememberedDevicesList = useRef<
64
+ // Record<MediaDeviceInfo['kind'], savedDeviceInfo[]>
65
+ // >({
66
+ // audioinput: [],
67
+ // videoinput: [],
68
+ // audiooutput: [],
69
+ // });
38
70
 
39
- const refreshDevices = useCallback(async () => {
40
- rtc.RtcEngine.getDevices(function (devices: deviceInfo[]) {
41
- console.log('DeviceTesting: fetching all devices: ', devices);
71
+ const updateActiveDeviceId = (
72
+ kind: MediaDeviceInfo['kind'],
73
+ deviceId: string,
74
+ ) => {
75
+ // const {kind, deviceId} = device;
76
+
77
+ setStore((prevState) => ({
78
+ ...prevState,
79
+ activeDeviceId: {
80
+ ...activeDeviceId,
81
+ [kind]: deviceId,
82
+ },
83
+ }));
84
+ };
85
+
86
+ const updateRememberedDeviceList = (
87
+ device: MediaDeviceInfo,
88
+ switchOnConnect: boolean,
89
+ ) => {
90
+ const {kind} = device;
91
+ // rememberedDevicesList.current[kind].push({...device, switchOnConnect});
92
+ // window.localStorage.setItem(
93
+ // 'rememberedDevicesList',
94
+ // JSON.stringify(rememberedDevicesList.current),
95
+ // );
96
+ setStore((prevState) => ({
97
+ ...prevState,
98
+ rememberedDevicesList: {
99
+ ...prevState.rememberedDevicesList,
100
+ [kind]: {
101
+ [device.deviceId]: switchOnConnect
102
+ ? 'switch-on-connect'
103
+ : 'ignore-on-connect',
104
+ ...prevState.rememberedDevicesList[kind],
105
+ },
106
+ },
107
+ }));
108
+ };
109
+
110
+ const {RtcEngine} = rtc as unknown as {RtcEngine: WebRtcEngineInstance};
111
+ const {localStream} = RtcEngine;
112
+
113
+ const refreshDeviceList = useCallback(async () => {
114
+ let updatedDeviceList: MediaDeviceInfo[];
115
+ await RtcEngine.getDevices(function (devices: deviceInfo[]) {
116
+ log('Fetching all devices: ', devices);
42
117
  /**
43
118
  * Some browsers list the same microphone twice with different Id's,
44
119
  * their group Id's match as they are the same physical device.
@@ -46,115 +121,398 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
46
121
  * preference
47
122
  */
48
123
  /**
49
- * 1. Fetch devices and filter so the deviceId with default or empty
50
- * values are exluded for both audio and video devices. Also exclude
51
- * output devices. ex: Mac speakers are of type audiooutput(device.kind)
124
+ * 1. Fetch devices and filter so the deviceId with empty
125
+ * values are exluded
52
126
  * 2. Store only unique devices with unique groupIds
53
127
  */
54
128
 
55
- const uniqueDevices = devices.filter(
129
+ updatedDeviceList = devices.filter(
56
130
  (device: deviceInfo) =>
57
- device?.deviceId !== 'default' &&
131
+ // device?.deviceId !== 'default' &&
58
132
  device?.deviceId !== '' &&
59
- (device.kind == 'audioinput' || device.kind == 'videoinput'),
133
+ (device.kind == 'audioinput' ||
134
+ device.kind == 'videoinput' ||
135
+ device.kind == 'audiooutput'),
60
136
  );
61
- console.log('DeviceTesting: set unique devices', uniqueDevices);
62
- setDeviceList(uniqueDevices);
137
+
138
+ log('Setting unique devices', updatedDeviceList);
139
+ setDeviceList(updatedDeviceList);
63
140
  });
141
+
142
+ return updatedDeviceList;
64
143
  }, []);
65
144
 
145
+ const getAgoraTrackDeviceId = (type: 'audio' | 'video') => {
146
+ const mutedState =
147
+ //@ts-ignore
148
+ type === 'audio' ? !RtcEngine.isAudioEnabled : !RtcEngine.isVideoEnabled;
149
+
150
+ let currentDevice: string;
151
+
152
+ if (mutedState) {
153
+ currentDevice =
154
+ //@ts-ignore
155
+ type === 'audio' ? RtcEngine.audioDeviceId : RtcEngine.videoDeviceId;
156
+ log(`Agora ${type} Engine is using`, currentDevice);
157
+ } else {
158
+ currentDevice = localStream[type]
159
+ ?.getMediaStreamTrack()
160
+ .getSettings().deviceId;
161
+ log(`Agora ${type} Track is using`, currentDevice);
162
+ }
163
+ return currentDevice ?? '';
164
+ };
165
+
166
+ /**
167
+ * Retrieves the devices being used by agora tracks and
168
+ * updates the selected Ui states with them.
169
+ * Ignores for audioOutput since state acts as ground
170
+ * truth.
171
+ */
172
+ const syncSelectedDeviceUi = (kind?: deviceKind) => {
173
+ log('Refreshing', kind ?? 'all');
174
+ switch (kind) {
175
+ case 'audioinput':
176
+ setUiSelectedMic(getAgoraTrackDeviceId('audio'));
177
+ break;
178
+ case 'videoinput':
179
+ setUiSelectedCam(getAgoraTrackDeviceId('video'));
180
+ break;
181
+ case 'audiooutput':
182
+ break;
183
+ default:
184
+ setUiSelectedMic(getAgoraTrackDeviceId('audio'));
185
+ setUiSelectedCam(getAgoraTrackDeviceId('video'));
186
+ }
187
+ };
188
+
189
+ /**
190
+ * Sets the devices to the default device on chrome or
191
+ * the first item on the devices list on other browsers
192
+ * optionally takes device list to use that instead
193
+ * of state which might be stale
194
+ */
195
+ const fallbackToDefaultDevice = (
196
+ kind: deviceKind,
197
+ uniqueDevices?: MediaDeviceInfo[],
198
+ ) => {
199
+ const deviceListLocal = uniqueDevices || deviceList;
200
+ switch (kind) {
201
+ case 'audioinput':
202
+ const audioInputFallbackDeviceId = deviceListLocal.find(
203
+ (device) =>
204
+ device.kind === 'audioinput' &&
205
+ (isChrome ? device.deviceId === 'default' : true),
206
+ )?.deviceId;
207
+ setSelectedMic(audioInputFallbackDeviceId);
208
+ break;
209
+ case 'videoinput':
210
+ const videoInputFallbackDeviceId = deviceListLocal.find(
211
+ (device) => device.kind === 'videoinput',
212
+ )?.deviceId;
213
+ setSelectedCam(videoInputFallbackDeviceId);
214
+ break;
215
+ case 'audiooutput':
216
+ const audioOutputFallbackDeviceId = deviceListLocal.find(
217
+ (device) =>
218
+ device.kind === 'audiooutput' &&
219
+ (isChrome ? device.deviceId === 'default' : true),
220
+ )?.deviceId;
221
+
222
+ setSelectedSpeaker(audioOutputFallbackDeviceId);
223
+ break;
224
+ }
225
+ };
226
+
66
227
  useEffect(() => {
67
- AgoraRTC.onMicrophoneChanged = async (changedDevice: changedDeviceInfo) => {
68
- // When new audio device is plugged in ,refresh the devices list.
69
- console.log('DeviceTesting: on-microphone-changed');
70
- if (changedDevice && changedDevice.state === 'ACTIVE') {
71
- if (changedDevice.device?.kind === 'audioinput') {
72
- console.log('DeviceTesting: NEW audio device detected and selected');
73
- setSelectedMic(changedDevice.device?.deviceId);
74
- }
75
- }
76
- if (changedDevice && changedDevice.state === 'INACTIVE') {
77
- if (changedDevice.device?.kind === 'audioinput') {
78
- console.log('DeviceTesting: audio device inactive');
79
- setSelectedMic('');
80
- }
81
- }
228
+ const interval = setInterval(() => {
229
+ navigator.mediaDevices.enumerateDevices();
230
+ }, 2000);
231
+ return () => {
232
+ clearInterval(interval);
82
233
  };
83
- AgoraRTC.onCameraChanged = async (changedDevice: changedDeviceInfo) => {
84
- // When new video device is plugged in ,refresh the devices list.
85
- console.log('DeviceTesting: on-camera-changed');
86
- if (changedDevice && changedDevice.state === 'ACTIVE') {
87
- if (changedDevice.device?.kind === 'videoinput') {
88
- console.log('DeviceTesting: NEW video device detected and selected');
89
- setSelectedCam(changedDevice.device?.deviceId);
234
+ }, []);
235
+
236
+ useEffect(() => {
237
+ // Labels are empty in firefox when permission is granted first time
238
+ // refresh device list if labels are empty
239
+
240
+ const logTag = 'useEffect[rtc,store]';
241
+
242
+ if (activeDeviceId && deviceList.length > 0) {
243
+ // If stream exists and selected devices are empty, check for devices again
244
+ if (!selectedCam || selectedCam.trim().length == 0) {
245
+ log(logTag, 'cam: Device list populated but No selected cam');
246
+ const currentVideoDevice = getAgoraTrackDeviceId('video');
247
+ const {videoinput: storedVideoInput} = activeDeviceId;
248
+
249
+ if (
250
+ storedVideoInput &&
251
+ currentVideoDevice &&
252
+ currentVideoDevice !== storedVideoInput &&
253
+ deviceList.find((device) => device.deviceId === storedVideoInput)
254
+ ) {
255
+ log(logTag, 'cam: Setting cam to active id', storedVideoInput);
256
+ setSelectedCam(storedVideoInput);
257
+ } else {
258
+ setUiSelectedCam(currentVideoDevice);
90
259
  }
91
260
  }
92
- if (changedDevice && changedDevice.state === 'INACTIVE') {
93
- if (changedDevice.device?.kind === 'videoinput') {
94
- console.log('DeviceTesting: video device inactive');
95
- setSelectedCam('');
261
+
262
+ if (!selectedMic || selectedMic.trim().length == 0) {
263
+ log(logTag, 'mic: Device list populated but No selected mic');
264
+ const currentAudioDevice = getAgoraTrackDeviceId('audio');
265
+ const {audioinput: storedAudioInput} = activeDeviceId;
266
+
267
+ if (
268
+ storedAudioInput &&
269
+ currentAudioDevice &&
270
+ currentAudioDevice !== storedAudioInput &&
271
+ deviceList.find((device) => device.deviceId === storedAudioInput)
272
+ ) {
273
+ log(logTag, 'mic: Setting mic to active id', storedAudioInput);
274
+ setSelectedMic(storedAudioInput);
275
+ } else {
276
+ setUiSelectedMic(currentAudioDevice);
96
277
  }
97
278
  }
98
- };
99
- }, []);
100
279
 
101
- useEffect(() => {
102
- refreshDevices();
103
- }, [selectedCam, selectedMic]);
280
+ if (!selectedSpeaker || selectedSpeaker.trim().length == 0) {
281
+ log(logTag, 'speaker: Device list populated but No selected speaker');
282
+ const {audiooutput: storedAudioOutput} = activeDeviceId;
283
+ const defaultSpeaker = deviceList.find(
284
+ (device) =>
285
+ device.deviceId === 'default' &&
286
+ (isChrome ? device.deviceId === 'default' : true),
287
+ )?.deviceId;
104
288
 
105
- useEffect(() => {
106
- if (!selectedMic || selectedMic.trim().length == 0) {
107
- for (const i in deviceList) {
108
- if (deviceList[i].kind === 'audioinput') {
109
- console.log('DeviceTesting: set selected audio');
110
- setSelectedMic(deviceList[i].deviceId);
111
- break;
289
+ if (
290
+ defaultSpeaker &&
291
+ storedAudioOutput &&
292
+ defaultSpeaker !== storedAudioOutput &&
293
+ deviceList.find((device) => device.deviceId === storedAudioOutput)
294
+ ) {
295
+ log(
296
+ logTag,
297
+ 'speaker: Setting speaker to active id',
298
+ storedAudioOutput,
299
+ );
300
+ setSelectedSpeaker(storedAudioOutput);
301
+ } else {
302
+ setUiSelectedSpeaker(defaultSpeaker);
112
303
  }
113
304
  }
114
305
  }
115
- if (!selectedCam || selectedCam.trim().length == 0) {
116
- for (const i in deviceList) {
117
- if (deviceList[i].kind === 'videoinput') {
118
- console.log('DeviceTesting: set selected camera');
119
- setSelectedCam(deviceList[i].deviceId);
120
- break;
306
+
307
+ if (
308
+ deviceList.length === 0 ||
309
+ deviceList.find((device: MediaDeviceInfo) => device.label === '')
310
+ ) {
311
+ log(logTag, 'Empty device list');
312
+ refreshDeviceList();
313
+ }
314
+ }, [rtc, store]);
315
+
316
+ const commonOnChangedEvent = async (changedDeviceData: DeviceInfo) => {
317
+ // Extracted devicelist because we want to perform fallback with
318
+ // the most current version.
319
+ const previousDeviceList = deviceList;
320
+ const updatedDeviceList = await refreshDeviceList();
321
+ const changedDevice = changedDeviceData.device;
322
+
323
+ const {logTag, currentDevice, setCurrentDevice} = {
324
+ audioinput: {
325
+ logTag: 'mic: on-microphone-changed',
326
+ currentDevice: selectedMic,
327
+ setCurrentDevice: setSelectedMic,
328
+ },
329
+ audiooutput: {
330
+ logTag: 'speaker: on-speaker-changed',
331
+ currentDevice: selectedSpeaker,
332
+ setCurrentDevice: setSelectedSpeaker,
333
+ },
334
+ videoinput: {
335
+ logTag: 'cam: on-camera-changed',
336
+ currentDevice: selectedCam,
337
+ setCurrentDevice: setSelectedCam,
338
+ },
339
+ }[changedDevice.kind];
340
+
341
+ log(logTag, changedDeviceData);
342
+
343
+ if (currentDevice === 'default') {
344
+ // const previousDefaultDevice = previousDeviceList.find(
345
+ // (device) => device.deviceId === 'default',
346
+ // );
347
+ // const currentDefaultDevice = updatedDeviceList.find(
348
+ // (device) => device.deviceId === 'default',
349
+ // );
350
+ // log(logTag, 'current Default device', {
351
+ // changedDeviceData,
352
+ // previousDeviceList,
353
+ // updatedDeviceList,
354
+ // previousDefaultDevice,
355
+ // currentDefaultDevice,
356
+ // });
357
+ // if (previousDefaultDevice.groupId !== currentDefaultDevice.groupId) {
358
+ // log(logTag, 'Default device changed', {
359
+ // changedDeviceData,
360
+ // previousDeviceList,
361
+ // updatedDeviceList,
362
+ // previousDefaultDevice,
363
+ // currentDefaultDevice,
364
+ // });
365
+ // setCurrentDevice('default');
366
+ // }
367
+ setCurrentDevice('default');
368
+ }
369
+
370
+ const didChangeDeviceExistBefore = previousDeviceList.find(
371
+ (device) => device.deviceId === changedDevice.deviceId,
372
+ )
373
+ ? true
374
+ : false;
375
+
376
+ if (changedDeviceData.state === 'ACTIVE' && !didChangeDeviceExistBefore) {
377
+ const rememberedDevice =
378
+ rememberedDevicesList[changedDevice.kind][changedDevice.deviceId];
379
+
380
+ if (!rememberedDevice) {
381
+ showNewDeviceDetectedToast(changedDevice);
382
+ } else {
383
+ if (rememberedDevice === 'switch-on-connect') {
384
+ setCurrentDevice(changedDevice.deviceId);
385
+ return;
121
386
  }
122
387
  }
388
+ } else if (changedDeviceData.state === 'INACTIVE') {
389
+ if (changedDevice.deviceId === currentDevice) {
390
+ fallbackToDefaultDevice(changedDevice.kind, updatedDeviceList);
391
+ return;
392
+ }
123
393
  }
124
- }, [deviceList]);
394
+ };
125
395
 
396
+ // Port this to useEffectEvent(https://beta.reactjs.org/reference/react/useEffectEvent) when
397
+ // released
126
398
  useEffect(() => {
127
- if (selectedCam.length !== 0) {
128
- rtc.RtcEngine.changeCamera(
129
- selectedCam,
130
- () => {},
131
- (e: any) => console.log(e),
132
- );
133
- }
134
- // eslint-disable-next-line react-hooks/exhaustive-deps
135
- }, [selectedCam]);
399
+ log('previous devicelist updated', deviceList);
400
+ AgoraRTC.onMicrophoneChanged = commonOnChangedEvent;
401
+ }, [selectedMic, deviceList]);
136
402
 
137
403
  useEffect(() => {
138
- if (selectedMic.length !== 0) {
139
- rtc.RtcEngine.changeMic(
140
- selectedMic,
141
- () => {},
142
- (e: any) => console.log(e),
143
- );
144
- }
145
- // eslint-disable-next-line react-hooks/exhaustive-deps
146
- }, [selectedMic]);
404
+ AgoraRTC.onPlaybackDeviceChanged = commonOnChangedEvent;
405
+ }, [selectedSpeaker, deviceList]);
147
406
 
148
407
  useEffect(() => {
149
- // If stream exists and deviceList are empty, check for devices again
150
- // Labels are empty in firefox when permission is grante first time, refresh device list if labels are empty
151
- if (
152
- deviceList.length === 0 ||
153
- deviceList.find((device: MediaDeviceInfo) => device.label === '')
154
- ) {
155
- refreshDevices();
156
- }
157
- }, [rtc]);
408
+ AgoraRTC.onCameraChanged = commonOnChangedEvent;
409
+ }, [selectedCam, deviceList]);
410
+
411
+ const setSelectedMic = (deviceId: deviceId) => {
412
+ log('mic: setting to', deviceId);
413
+ return new Promise((res, rej) => {
414
+ RtcEngine.changeMic(
415
+ deviceId,
416
+ () => {
417
+ syncSelectedDeviceUi('audioinput');
418
+ updateActiveDeviceId('audioinput', deviceId);
419
+ res(null);
420
+ },
421
+ (e: any) => {
422
+ console.error('DeviceConfigure: Error setting mic', e);
423
+ rej(e);
424
+ },
425
+ );
426
+ });
427
+ };
428
+
429
+ const setSelectedCam = (deviceId: deviceId) => {
430
+ log('cam: setting to', deviceId);
431
+ return new Promise((res, rej) => {
432
+ RtcEngine.changeCamera(
433
+ deviceId,
434
+ () => {
435
+ syncSelectedDeviceUi('videoinput');
436
+ updateActiveDeviceId('videoinput', deviceId);
437
+ res(null);
438
+ },
439
+ (e: any) => {
440
+ console.error('Device Configure: Error setting webcam', e);
441
+ rej(e);
442
+ },
443
+ );
444
+ });
445
+ };
446
+
447
+ const setSelectedSpeaker = (deviceId: deviceId) => {
448
+ log('speaker: setting to', deviceId);
449
+ return new Promise((res, rej) => {
450
+ RtcEngine.changeSpeaker(
451
+ deviceId,
452
+ () => {
453
+ setUiSelectedSpeaker(deviceId);
454
+ updateActiveDeviceId('audiooutput', deviceId);
455
+ res(null);
456
+ },
457
+ (e: any) => {
458
+ console.error('Device Configure: Error setting speaker', e);
459
+ rej(selectedSpeaker);
460
+ },
461
+ );
462
+ });
463
+ };
464
+
465
+ const showNewDeviceDetectedToast = (device: MediaDeviceInfo) => {
466
+ const {name, setAction} = {
467
+ audioinput: {
468
+ name: 'mic',
469
+ setAction: setSelectedMic,
470
+ },
471
+ videoinput: {
472
+ name: 'webcam',
473
+ setAction: setSelectedCam,
474
+ },
475
+ audiooutput: {
476
+ name: 'speaker',
477
+ setAction: setSelectedSpeaker,
478
+ },
479
+ }[device.kind];
480
+
481
+ Toast.show({
482
+ type: 'checked',
483
+ // leadingIcon: <CustomIcon name={'mic-on'} />,
484
+ text1: `New ${name} detected`,
485
+ // @ts-ignore
486
+ text2: (
487
+ <Text>
488
+ <Text>New {name} named </Text>
489
+ <Text style={{fontWeight: 'bold'}}>{device.label}</Text>
490
+ <Text> detected. Do you want to switch?</Text>
491
+ </Text>
492
+ ),
493
+ visibilityTime: 6000,
494
+ checkbox: {
495
+ disabled: false,
496
+ color: primaryColor,
497
+ text: 'Remember my choice',
498
+ },
499
+ primaryBtn: {
500
+ text: 'SWITCH DEVICE',
501
+ onPress: (checked: boolean) => {
502
+ setAction(device.deviceId);
503
+ checked && updateRememberedDeviceList(device, true);
504
+ Toast.hide();
505
+ },
506
+ },
507
+ secondaryBtn: {
508
+ text: 'IGNORE',
509
+ onPress: (checked: boolean) => {
510
+ checked && updateRememberedDeviceList(device, false);
511
+ Toast.hide();
512
+ },
513
+ },
514
+ });
515
+ };
158
516
 
159
517
  return (
160
518
  <DeviceContext.Provider
@@ -163,6 +521,8 @@ const DeviceConfigure: React.FC<Props> = (props: any) => {
163
521
  setSelectedCam,
164
522
  selectedMic,
165
523
  setSelectedMic,
524
+ selectedSpeaker,
525
+ setSelectedSpeaker,
166
526
  deviceList,
167
527
  setDeviceList,
168
528
  }}>
@@ -13,9 +13,11 @@ import {createContext} from 'react';
13
13
 
14
14
  interface DeviceContext {
15
15
  selectedCam: string;
16
- setSelectedCam: (cam: string) => void;
16
+ setSelectedCam: (cam: string) => Promise<any>;
17
17
  selectedMic: string;
18
- setSelectedMic: (mic: string) => void;
18
+ setSelectedMic: (mic: string) => Promise<any>;
19
+ selectedSpeaker: string;
20
+ setSelectedSpeaker: (speaker: string) => Promise<any>;
19
21
  deviceList: MediaDeviceInfo[];
20
22
  setDeviceList: (devices: MediaDeviceInfo[]) => void;
21
23
  }
@@ -23,9 +25,11 @@ interface DeviceContext {
23
25
  const DeviceContext = createContext<DeviceContext>({
24
26
  selectedCam: '',
25
27
  selectedMic: '',
28
+ selectedSpeaker: '',
26
29
  deviceList: [],
27
- setSelectedCam: () => {},
28
- setSelectedMic: () => {},
30
+ setSelectedCam: async () => {},
31
+ setSelectedMic: async () => {},
32
+ setSelectedSpeaker: async () => {},
29
33
  setDeviceList: () => {},
30
34
  });
31
35
  export default DeviceContext;