agora-appbuilder-core 3.0.9 → 4.0.0-api.0

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