@stream-io/video-react-sdk 0.0.1-alpha.9 → 0.0.1-alpha.91

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 (249) hide show
  1. package/CHANGELOG.md +22 -150
  2. package/README.md +1 -1
  3. package/dist/css/styles.css +273 -407
  4. package/dist/css/styles.css.map +1 -1
  5. package/dist/src/components/Button/CompositeButton.js +2 -4
  6. package/dist/src/components/Button/CompositeButton.js.map +1 -1
  7. package/dist/src/components/CallControls/AcceptCallButton.d.ts +7 -0
  8. package/dist/src/components/CallControls/AcceptCallButton.js +27 -0
  9. package/dist/src/components/CallControls/AcceptCallButton.js.map +1 -0
  10. package/dist/src/components/CallControls/CallControls.d.ts +1 -3
  11. package/dist/src/components/CallControls/CallControls.js +2 -5
  12. package/dist/src/components/CallControls/CallControls.js.map +1 -1
  13. package/dist/src/components/CallControls/CallStatsButton.d.ts +1 -5
  14. package/dist/src/components/CallControls/CallStatsButton.js +2 -4
  15. package/dist/src/components/CallControls/CallStatsButton.js.map +1 -1
  16. package/dist/src/components/CallControls/CancelCallButton.d.ts +2 -3
  17. package/dist/src/components/CallControls/CancelCallButton.js +4 -2
  18. package/dist/src/components/CallControls/CancelCallButton.js.map +1 -1
  19. package/dist/src/components/CallControls/ReactionsButton.js +1 -2
  20. package/dist/src/components/CallControls/ReactionsButton.js.map +1 -1
  21. package/dist/src/components/CallControls/RecordCallButton.d.ts +1 -3
  22. package/dist/src/components/CallControls/RecordCallButton.js +10 -6
  23. package/dist/src/components/CallControls/RecordCallButton.js.map +1 -1
  24. package/dist/src/components/CallControls/ScreenShareButton.d.ts +1 -3
  25. package/dist/src/components/CallControls/ScreenShareButton.js +7 -7
  26. package/dist/src/components/CallControls/ScreenShareButton.js.map +1 -1
  27. package/dist/src/components/CallControls/ToggleAudioButton.d.ts +1 -1
  28. package/dist/src/components/CallControls/ToggleAudioButton.js +17 -10
  29. package/dist/src/components/CallControls/ToggleAudioButton.js.map +1 -1
  30. package/dist/src/components/CallControls/ToggleVideoButton.d.ts +10 -0
  31. package/dist/src/components/CallControls/{ToggleCameraButton.js → ToggleVideoButton.js} +17 -11
  32. package/dist/src/components/CallControls/ToggleVideoButton.js.map +1 -0
  33. package/dist/src/components/CallControls/index.d.ts +2 -2
  34. package/dist/src/components/CallControls/index.js +2 -2
  35. package/dist/src/components/CallControls/index.js.map +1 -1
  36. package/dist/src/components/CallParticipantsList/BlockedUserListing.js +1 -2
  37. package/dist/src/components/CallParticipantsList/BlockedUserListing.js.map +1 -1
  38. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.d.ts +2 -1
  39. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js +13 -5
  40. package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js.map +1 -1
  41. package/dist/src/components/CallParticipantsList/CallParticipantsList.js +1 -2
  42. package/dist/src/components/CallParticipantsList/CallParticipantsList.js.map +1 -1
  43. package/dist/src/components/CallStats/CallStats.d.ts +6 -0
  44. package/dist/src/components/{StreamCall → CallStats}/CallStats.js +3 -3
  45. package/dist/src/components/CallStats/CallStats.js.map +1 -0
  46. package/dist/src/components/CallStats/CallStatsLatencyChart.js.map +1 -0
  47. package/dist/src/components/CallStats/index.d.ts +2 -0
  48. package/dist/src/components/CallStats/index.js +3 -0
  49. package/dist/src/components/CallStats/index.js.map +1 -0
  50. package/dist/src/components/Debug/DebugStatsView.d.ts +1 -1
  51. package/dist/src/components/Debug/DebugStatsView.js +32 -7
  52. package/dist/src/components/Debug/DebugStatsView.js.map +1 -1
  53. package/dist/src/components/DeviceSettings/DeviceSelectorAudio.js +5 -3
  54. package/dist/src/components/DeviceSettings/DeviceSelectorAudio.js.map +1 -1
  55. package/dist/src/components/DeviceSettings/DeviceSelectorVideo.js +3 -2
  56. package/dist/src/components/DeviceSettings/DeviceSelectorVideo.js.map +1 -1
  57. package/dist/src/components/Notification/SpeakingWhileMutedNotification.js +4 -2
  58. package/dist/src/components/Notification/SpeakingWhileMutedNotification.js.map +1 -1
  59. package/dist/src/components/PendingCallPanel/PendingCallControls.d.ts +2 -0
  60. package/dist/src/components/PendingCallPanel/PendingCallControls.js +13 -0
  61. package/dist/src/components/PendingCallPanel/PendingCallControls.js.map +1 -0
  62. package/dist/src/components/PendingCallPanel/PendingCallPanel.d.ts +2 -0
  63. package/dist/src/components/PendingCallPanel/PendingCallPanel.js +34 -0
  64. package/dist/src/components/PendingCallPanel/PendingCallPanel.js.map +1 -0
  65. package/dist/src/components/PendingCallPanel/index.d.ts +2 -0
  66. package/dist/src/components/PendingCallPanel/index.js +3 -0
  67. package/dist/src/components/PendingCallPanel/index.js.map +1 -0
  68. package/dist/src/components/Permissions/PermissionRequests.js +2 -8
  69. package/dist/src/components/Permissions/PermissionRequests.js.map +1 -1
  70. package/dist/src/components/StreamCall/CallParticipantsScreenView.js +3 -3
  71. package/dist/src/components/StreamCall/CallParticipantsScreenView.js.map +1 -1
  72. package/dist/src/components/StreamCall/CallParticipantsView.js +2 -3
  73. package/dist/src/components/StreamCall/CallParticipantsView.js.map +1 -1
  74. package/dist/src/components/StreamTheme/StreamTheme.d.ts +5 -0
  75. package/dist/src/components/StreamTheme/StreamTheme.js +18 -0
  76. package/dist/src/components/StreamTheme/StreamTheme.js.map +1 -0
  77. package/dist/src/components/StreamTheme/index.d.ts +1 -0
  78. package/dist/src/components/StreamTheme/index.js +2 -0
  79. package/dist/src/components/StreamTheme/index.js.map +1 -0
  80. package/dist/src/components/Video/VideoPreview.js +10 -5
  81. package/dist/src/components/Video/VideoPreview.js.map +1 -1
  82. package/dist/src/components/Video/index.d.ts +1 -1
  83. package/dist/src/components/Video/index.js +1 -1
  84. package/dist/src/components/Video/index.js.map +1 -1
  85. package/dist/src/components/index.d.ts +2 -2
  86. package/dist/src/components/index.js +2 -2
  87. package/dist/src/components/index.js.map +1 -1
  88. package/dist/src/core/components/CallLayout/PaginatedGridLayout.d.ts +3 -7
  89. package/dist/src/core/components/CallLayout/PaginatedGridLayout.js +13 -14
  90. package/dist/src/core/components/CallLayout/PaginatedGridLayout.js.map +1 -1
  91. package/dist/src/core/components/CallLayout/SpeakerLayout.d.ts +6 -1
  92. package/dist/src/core/components/CallLayout/SpeakerLayout.js +13 -7
  93. package/dist/src/core/components/CallLayout/SpeakerLayout.js.map +1 -1
  94. package/dist/src/core/components/ParticipantView/DefaultParticipantViewUI.d.ts +18 -0
  95. package/dist/src/core/components/ParticipantView/DefaultParticipantViewUI.js +36 -0
  96. package/dist/src/core/components/ParticipantView/DefaultParticipantViewUI.js.map +1 -0
  97. package/dist/src/core/components/ParticipantView/ParticipantView.d.ts +79 -0
  98. package/dist/src/core/components/ParticipantView/ParticipantView.js +33 -0
  99. package/dist/src/core/components/ParticipantView/ParticipantView.js.map +1 -0
  100. package/dist/src/core/components/ParticipantView/index.d.ts +2 -0
  101. package/dist/src/core/components/ParticipantView/index.js +3 -0
  102. package/dist/src/core/components/ParticipantView/index.js.map +1 -0
  103. package/dist/src/core/components/StreamCall/StreamCall.d.ts +73 -0
  104. package/dist/src/core/components/StreamCall/StreamCall.js +60 -0
  105. package/dist/src/core/components/StreamCall/StreamCall.js.map +1 -0
  106. package/dist/src/core/components/StreamCall/index.d.ts +1 -0
  107. package/dist/src/core/components/StreamCall/index.js +2 -0
  108. package/dist/src/core/components/StreamCall/index.js.map +1 -0
  109. package/dist/src/core/components/Video/BaseVideo.d.ts +3 -3
  110. package/dist/src/core/components/Video/BaseVideo.js +6 -12
  111. package/dist/src/core/components/Video/BaseVideo.js.map +1 -1
  112. package/dist/src/core/components/Video/DefaultVideoPlaceholder.d.ts +6 -0
  113. package/dist/src/core/components/Video/DefaultVideoPlaceholder.js +9 -0
  114. package/dist/src/core/components/Video/DefaultVideoPlaceholder.js.map +1 -0
  115. package/dist/src/core/components/Video/Video.d.ts +11 -6
  116. package/dist/src/core/components/Video/Video.js +31 -28
  117. package/dist/src/core/components/Video/Video.js.map +1 -1
  118. package/dist/src/core/components/index.d.ts +3 -2
  119. package/dist/src/core/components/index.js +2 -1
  120. package/dist/src/core/components/index.js.map +1 -1
  121. package/dist/src/core/contexts/MediaDevicesContext.d.ts +117 -19
  122. package/dist/src/core/contexts/MediaDevicesContext.js +52 -90
  123. package/dist/src/core/contexts/MediaDevicesContext.js.map +1 -1
  124. package/dist/src/core/hooks/index.d.ts +2 -0
  125. package/dist/src/core/hooks/index.js +2 -0
  126. package/dist/src/core/hooks/index.js.map +1 -1
  127. package/dist/src/core/hooks/useAudioPublisher.js +9 -3
  128. package/dist/src/core/hooks/useAudioPublisher.js.map +1 -1
  129. package/dist/src/core/hooks/useDevices.d.ts +80 -0
  130. package/dist/src/core/hooks/useDevices.js +113 -0
  131. package/dist/src/core/hooks/useDevices.js.map +1 -0
  132. package/dist/src/core/hooks/useTrackElementVisibility.d.ts +6 -0
  133. package/dist/src/core/hooks/useTrackElementVisibility.js +27 -0
  134. package/dist/src/core/hooks/useTrackElementVisibility.js.map +1 -0
  135. package/dist/src/core/hooks/useVideoPublisher.js +35 -6
  136. package/dist/src/core/hooks/useVideoPublisher.js.map +1 -1
  137. package/dist/src/hooks/index.d.ts +0 -1
  138. package/dist/src/hooks/index.js +0 -1
  139. package/dist/src/hooks/index.js.map +1 -1
  140. package/dist/src/utilities/applyElementToRef.d.ts +2 -0
  141. package/dist/src/utilities/applyElementToRef.js +8 -0
  142. package/dist/src/utilities/applyElementToRef.js.map +1 -0
  143. package/dist/src/utilities/chunk.d.ts +1 -0
  144. package/dist/src/utilities/chunk.js +5 -0
  145. package/dist/src/utilities/chunk.js.map +1 -0
  146. package/dist/src/utilities/index.d.ts +3 -0
  147. package/dist/src/utilities/index.js +4 -0
  148. package/dist/src/utilities/index.js.map +1 -0
  149. package/dist/src/utilities/isComponentType.d.ts +2 -0
  150. package/dist/src/utilities/isComponentType.js +7 -0
  151. package/dist/src/utilities/isComponentType.js.map +1 -0
  152. package/package.json +12 -10
  153. package/src/components/Button/CompositeButton.tsx +4 -13
  154. package/src/components/CallControls/AcceptCallButton.tsx +36 -0
  155. package/src/components/CallControls/CallControls.tsx +13 -19
  156. package/src/components/CallControls/CallStatsButton.tsx +6 -14
  157. package/src/components/CallControls/CancelCallButton.tsx +12 -4
  158. package/src/components/CallControls/ReactionsButton.tsx +1 -2
  159. package/src/components/CallControls/RecordCallButton.tsx +12 -7
  160. package/src/components/CallControls/ScreenShareButton.tsx +7 -8
  161. package/src/components/CallControls/ToggleAudioButton.tsx +23 -12
  162. package/src/components/CallControls/{ToggleCameraButton.tsx → ToggleVideoButton.tsx} +20 -13
  163. package/src/components/CallControls/index.ts +2 -2
  164. package/src/components/CallParticipantsList/BlockedUserListing.tsx +1 -2
  165. package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +27 -3
  166. package/src/components/CallParticipantsList/CallParticipantsList.tsx +1 -1
  167. package/src/components/{StreamCall → CallStats}/CallStats.tsx +3 -3
  168. package/src/components/CallStats/index.ts +2 -0
  169. package/src/components/Debug/DebugStatsView.tsx +60 -7
  170. package/src/components/DeviceSettings/DeviceSelectorAudio.tsx +9 -4
  171. package/src/components/DeviceSettings/DeviceSelectorVideo.tsx +3 -3
  172. package/src/components/Notification/SpeakingWhileMutedNotification.tsx +9 -8
  173. package/src/components/PendingCallPanel/PendingCallControls.tsx +27 -0
  174. package/src/components/PendingCallPanel/PendingCallPanel.tsx +71 -0
  175. package/src/components/PendingCallPanel/index.ts +2 -0
  176. package/src/components/Permissions/PermissionRequests.tsx +2 -8
  177. package/src/components/StreamCall/CallParticipantsScreenView.tsx +3 -4
  178. package/src/components/StreamCall/CallParticipantsView.tsx +3 -4
  179. package/src/components/StreamTheme/StreamTheme.tsx +19 -0
  180. package/src/components/StreamTheme/index.ts +1 -0
  181. package/src/components/Video/VideoPreview.tsx +16 -6
  182. package/src/components/Video/index.ts +1 -1
  183. package/src/components/index.ts +2 -2
  184. package/src/core/components/CallLayout/PaginatedGridLayout.tsx +32 -36
  185. package/src/core/components/CallLayout/SpeakerLayout.tsx +48 -25
  186. package/src/core/components/ParticipantView/DefaultParticipantViewUI.tsx +160 -0
  187. package/src/core/components/ParticipantView/ParticipantView.tsx +156 -0
  188. package/src/core/components/ParticipantView/index.ts +2 -0
  189. package/src/core/components/StreamCall/StreamCall.tsx +157 -0
  190. package/src/core/components/StreamCall/index.ts +1 -0
  191. package/src/core/components/Video/BaseVideo.tsx +9 -24
  192. package/src/core/components/Video/DefaultVideoPlaceholder.tsx +36 -0
  193. package/src/core/components/Video/Video.tsx +62 -48
  194. package/src/core/components/index.ts +3 -2
  195. package/src/core/contexts/MediaDevicesContext.tsx +179 -136
  196. package/src/core/hooks/index.ts +2 -0
  197. package/src/core/hooks/useAudioPublisher.ts +9 -3
  198. package/src/core/hooks/useDevices.ts +161 -0
  199. package/src/core/hooks/useTrackElementVisibility.ts +44 -0
  200. package/src/core/hooks/useVideoPublisher.ts +36 -4
  201. package/src/hooks/index.ts +0 -1
  202. package/src/utilities/applyElementToRef.ts +12 -0
  203. package/src/utilities/chunk.ts +8 -0
  204. package/src/utilities/index.ts +3 -0
  205. package/src/utilities/isComponentType.ts +9 -0
  206. package/dist/src/components/CallControls/ToggleCameraButton.d.ts +0 -10
  207. package/dist/src/components/CallControls/ToggleCameraButton.js.map +0 -1
  208. package/dist/src/components/CallControls/ToggleParticipantListButton.d.ts +0 -6
  209. package/dist/src/components/CallControls/ToggleParticipantListButton.js +0 -7
  210. package/dist/src/components/CallControls/ToggleParticipantListButton.js.map +0 -1
  211. package/dist/src/components/Moderation/Restricted.d.ts +0 -19
  212. package/dist/src/components/Moderation/Restricted.js +0 -13
  213. package/dist/src/components/Moderation/Restricted.js.map +0 -1
  214. package/dist/src/components/Moderation/index.d.ts +0 -1
  215. package/dist/src/components/Moderation/index.js +0 -2
  216. package/dist/src/components/Moderation/index.js.map +0 -1
  217. package/dist/src/components/StreamCall/CallStats.d.ts +0 -2
  218. package/dist/src/components/StreamCall/CallStats.js.map +0 -1
  219. package/dist/src/components/StreamCall/CallStatsLatencyChart.js.map +0 -1
  220. package/dist/src/components/StreamMeeting/StreamMeeting.d.ts +0 -34
  221. package/dist/src/components/StreamMeeting/StreamMeeting.js +0 -26
  222. package/dist/src/components/StreamMeeting/StreamMeeting.js.map +0 -1
  223. package/dist/src/components/StreamMeeting/index.d.ts +0 -1
  224. package/dist/src/components/StreamMeeting/index.js +0 -2
  225. package/dist/src/components/StreamMeeting/index.js.map +0 -1
  226. package/dist/src/core/components/ParticipantBox/ParticipantBox.d.ts +0 -48
  227. package/dist/src/core/components/ParticipantBox/ParticipantBox.js +0 -58
  228. package/dist/src/core/components/ParticipantBox/ParticipantBox.js.map +0 -1
  229. package/dist/src/core/components/ParticipantBox/index.d.ts +0 -1
  230. package/dist/src/core/components/ParticipantBox/index.js +0 -2
  231. package/dist/src/core/components/ParticipantBox/index.js.map +0 -1
  232. package/dist/src/core/components/Video/VideoPlaceholder.d.ts +0 -6
  233. package/dist/src/core/components/Video/VideoPlaceholder.js +0 -12
  234. package/dist/src/core/components/Video/VideoPlaceholder.js.map +0 -1
  235. package/dist/src/hooks/useRtcStats.d.ts +0 -11
  236. package/dist/src/hooks/useRtcStats.js +0 -39
  237. package/dist/src/hooks/useRtcStats.js.map +0 -1
  238. package/src/components/CallControls/ToggleParticipantListButton.tsx +0 -17
  239. package/src/components/Moderation/Restricted.tsx +0 -38
  240. package/src/components/Moderation/index.ts +0 -1
  241. package/src/components/StreamMeeting/StreamMeeting.tsx +0 -80
  242. package/src/components/StreamMeeting/index.ts +0 -1
  243. package/src/core/components/ParticipantBox/ParticipantBox.tsx +0 -248
  244. package/src/core/components/ParticipantBox/index.ts +0 -1
  245. package/src/core/components/Video/VideoPlaceholder.tsx +0 -40
  246. package/src/hooks/useRtcStats.ts +0 -36
  247. /package/dist/src/components/{StreamCall → CallStats}/CallStatsLatencyChart.d.ts +0 -0
  248. /package/dist/src/components/{StreamCall → CallStats}/CallStatsLatencyChart.js +0 -0
  249. /package/src/components/{StreamCall → CallStats}/CallStatsLatencyChart.tsx +0 -0
@@ -1,16 +1,16 @@
1
1
  export * from './Avatar';
2
2
  export * from './Button';
3
3
  export * from './CallControls';
4
- export * from '../core/components/CallLayout';
5
4
  export * from './CallParticipantsList';
6
5
  export * from './CallRecordingList';
7
6
  export * from './DeviceSettings';
8
7
  export * from './LoadingIndicator';
9
8
  export * from './Menu';
10
9
  export * from './Notification';
10
+ export * from './PendingCallPanel';
11
11
  export * from './Permissions';
12
12
  export * from './StreamCall';
13
- export * from './StreamMeeting';
13
+ export * from './StreamTheme';
14
14
  export * from './Search';
15
15
  export * from './Tooltip';
16
16
  export * from './Video';
@@ -11,9 +11,14 @@ import {
11
11
  } from '@stream-io/video-client';
12
12
  import clsx from 'clsx';
13
13
 
14
- import { ParticipantBox } from '../ParticipantBox';
14
+ import {
15
+ ParticipantView,
16
+ DefaultParticipantViewUI,
17
+ ParticipantViewProps,
18
+ } from '../ParticipantView';
15
19
  import { Audio } from '../Audio';
16
20
  import { IconButton } from '../../../components';
21
+ import { chunk } from '../../../utilities';
17
22
 
18
23
  const GROUP_SIZE = 16;
19
24
 
@@ -22,21 +27,17 @@ type PaginatedGridLayoutGroupProps = {
22
27
  * The group of participants to render.
23
28
  */
24
29
  group: Array<StreamVideoParticipant | StreamVideoLocalParticipant>;
30
+ } & Pick<ParticipantViewProps, 'VideoPlaceholder'> &
31
+ Required<Pick<ParticipantViewProps, 'ParticipantViewUI'>>;
25
32
 
26
- /**
27
- * Turns on/off the status indicator icons (mute, connection quality, etc...)
28
- * on the participant boxes.
29
- */
30
- indicatorsVisible?: boolean;
31
- };
32
33
  const PaginatedGridLayoutGroup = ({
33
34
  group,
34
- indicatorsVisible = true,
35
+ VideoPlaceholder,
36
+ ParticipantViewUI,
35
37
  }: PaginatedGridLayoutGroupProps) => {
36
- const call = useCall();
37
38
  return (
38
39
  <div
39
- className={clsx('str-video__paginated-grid-layout--group', {
40
+ className={clsx('str-video__paginated-grid-layout__group', {
40
41
  'str-video__paginated-grid-layout--one': group.length === 1,
41
42
  'str-video__paginated-grid-layout--two-four':
42
43
  group.length >= 2 && group.length <= 4,
@@ -45,12 +46,12 @@ const PaginatedGridLayoutGroup = ({
45
46
  })}
46
47
  >
47
48
  {group.map((participant) => (
48
- <ParticipantBox
49
+ <ParticipantView
49
50
  key={participant.sessionId}
50
51
  participant={participant}
51
- call={call!}
52
- indicatorsVisible={indicatorsVisible}
53
52
  muteAudio
53
+ VideoPlaceholder={VideoPlaceholder}
54
+ ParticipantViewUI={ParticipantViewUI}
54
55
  />
55
56
  ))}
56
57
  </div>
@@ -68,26 +69,22 @@ export type PaginatedGridLayoutProps = {
68
69
  */
69
70
  excludeLocalParticipant?: boolean;
70
71
 
71
- /**
72
- * Turns on/off the status indicator icons (mute, connection quality, etc...)
73
- * on the participant boxes.
74
- */
75
- indicatorsVisible?: boolean;
76
-
77
72
  /**
78
73
  * Turns on/off the pagination arrows.
79
74
  */
80
75
  pageArrowsVisible?: boolean;
81
- };
76
+ } & Pick<ParticipantViewProps, 'ParticipantViewUI' | 'VideoPlaceholder'>;
82
77
 
83
78
  export const PaginatedGridLayout = ({
84
79
  groupSize = GROUP_SIZE,
85
80
  excludeLocalParticipant = false,
86
- indicatorsVisible = true,
87
81
  pageArrowsVisible = true,
82
+ VideoPlaceholder,
83
+ ParticipantViewUI = DefaultParticipantViewUI,
88
84
  }: PaginatedGridLayoutProps) => {
89
85
  const [page, setPage] = useState(0);
90
86
 
87
+ const call = useCall();
91
88
  const localParticipant = useLocalParticipant();
92
89
  const participants = useParticipants();
93
90
  // used to render audio elements
@@ -107,11 +104,15 @@ export const PaginatedGridLayout = ({
107
104
 
108
105
  // update page when page count is reduced and selected page no longer exists
109
106
  useEffect(() => {
110
- if (page > pageCount - 1) setPage(pageCount - 1);
107
+ if (page > pageCount - 1) {
108
+ setPage(Math.max(0, pageCount - 1));
109
+ }
111
110
  }, [page, pageCount]);
112
111
 
113
112
  const selectedGroup = participantGroups[page];
114
113
 
114
+ if (!call) return null;
115
+
115
116
  return (
116
117
  <>
117
118
  {remoteParticipants.map((participant) => (
@@ -122,19 +123,22 @@ export const PaginatedGridLayout = ({
122
123
  sinkId={localParticipant?.audioOutputDeviceId}
123
124
  />
124
125
  ))}
125
- <div className="str-video__paginated-grid-layout--wrapper">
126
+ <div className="str-video__paginated-grid-layout__wrapper">
126
127
  <div className="str-video__paginated-grid-layout">
127
128
  {pageArrowsVisible && pageCount > 1 && (
128
129
  <IconButton
129
130
  icon="caret-left"
130
131
  disabled={page === 0}
131
- onClick={() => setPage((pv) => (pv === 0 ? pv : pv - 1))}
132
+ onClick={() =>
133
+ setPage((currentPage) => Math.max(0, currentPage - 1))
134
+ }
132
135
  />
133
136
  )}
134
137
  {selectedGroup && (
135
138
  <PaginatedGridLayoutGroup
136
139
  group={participantGroups[page]}
137
- indicatorsVisible={indicatorsVisible}
140
+ VideoPlaceholder={VideoPlaceholder}
141
+ ParticipantViewUI={ParticipantViewUI}
138
142
  />
139
143
  )}
140
144
  {pageArrowsVisible && pageCount > 1 && (
@@ -142,7 +146,9 @@ export const PaginatedGridLayout = ({
142
146
  disabled={page === pageCount - 1}
143
147
  icon="caret-right"
144
148
  onClick={() =>
145
- setPage((pv) => (pv === pageCount - 1 ? pv : pv + 1))
149
+ setPage((currentPage) =>
150
+ Math.min(pageCount - 1, currentPage + 1),
151
+ )
146
152
  }
147
153
  />
148
154
  )}
@@ -151,13 +157,3 @@ export const PaginatedGridLayout = ({
151
157
  </>
152
158
  );
153
159
  };
154
-
155
- // TODO: move to utilities
156
- const chunk = <T extends unknown[]>(array: T, size = GROUP_SIZE) => {
157
- const chunkCount = Math.ceil(array.length / size);
158
-
159
- return Array.from(
160
- { length: chunkCount },
161
- (_, index) => array.slice(size * index, size * index + size) as T,
162
- );
163
- };
@@ -16,12 +16,31 @@ import {
16
16
  useParticipants,
17
17
  } from '@stream-io/video-react-bindings';
18
18
 
19
- import { ParticipantBox } from '../ParticipantBox';
20
- import { IconButton } from '../../../components/Button';
19
+ import {
20
+ DefaultParticipantViewUI,
21
+ ParticipantView,
22
+ ParticipantViewProps,
23
+ } from '../ParticipantView';
24
+ import { IconButton } from '../../../components';
21
25
  import { useHorizontalScrollPosition } from '../../../components/StreamCall/hooks';
22
26
 
23
- export const SpeakerLayout = () => {
24
- const call = useCall()!;
27
+ export type SpeakerLayoutProps = {
28
+ ParticipantViewUISpotlight?: ParticipantViewProps['ParticipantViewUI'];
29
+ ParticipantViewUIBar?: ParticipantViewProps['ParticipantViewUI'];
30
+ } & Pick<ParticipantViewProps, 'VideoPlaceholder'>;
31
+
32
+ const DefaultParticipantViewUIBar = () => (
33
+ <DefaultParticipantViewUI menuPlacement="top-end" />
34
+ );
35
+
36
+ const DefaultParticipantViewUISpotlight = () => <DefaultParticipantViewUI />;
37
+
38
+ export const SpeakerLayout = ({
39
+ ParticipantViewUIBar = DefaultParticipantViewUIBar,
40
+ ParticipantViewUISpotlight = DefaultParticipantViewUISpotlight,
41
+ VideoPlaceholder,
42
+ }: SpeakerLayoutProps) => {
43
+ const call = useCall();
25
44
  const [participantInSpotlight, ...otherParticipants] = useParticipants();
26
45
  const [scrollWrapper, setScrollWrapper] = useState<HTMLDivElement | null>(
27
46
  null,
@@ -40,14 +59,15 @@ export const SpeakerLayout = () => {
40
59
  };
41
60
 
42
61
  useEffect(() => {
43
- if (!scrollWrapper) return;
62
+ if (!scrollWrapper || !call) return;
44
63
 
45
64
  const cleanup = call.viewportTracker.setViewport(scrollWrapper);
46
65
 
47
66
  return () => cleanup();
48
- }, [scrollWrapper, call.viewportTracker]);
67
+ }, [scrollWrapper, call]);
49
68
 
50
69
  useEffect(() => {
70
+ if (!call) return;
51
71
  // always show the remote participant in the spotlight
52
72
  if (isOneOnOneCall) {
53
73
  call.setSortParticipantsBy(combineComparators(screenSharing, loggedIn));
@@ -64,58 +84,61 @@ export const SpeakerLayout = () => {
64
84
  };
65
85
  }, [call, isOneOnOneCall]);
66
86
 
87
+ if (!call) return null;
88
+
67
89
  const isSpeakerScreenSharing = hasScreenShare(participantInSpotlight);
68
90
  return (
69
- <div className="str-video__speaker-layout--wrapper">
91
+ <div className="str-video__speaker-layout__wrapper">
70
92
  <div className="str-video__speaker-layout">
71
- <div className="str-video__speaker-layout--spotlight">
93
+ <div className="str-video__speaker-layout__spotlight">
72
94
  {participantInSpotlight && (
73
- <ParticipantBox
95
+ <ParticipantView
74
96
  participant={participantInSpotlight}
75
- call={call}
76
97
  muteAudio={isSpeakerScreenSharing}
77
98
  videoKind={isSpeakerScreenSharing ? 'screen' : 'video'}
78
99
  sinkId={localParticipant?.audioOutputDeviceId}
100
+ ParticipantViewUI={ParticipantViewUISpotlight}
101
+ VideoPlaceholder={VideoPlaceholder}
79
102
  />
80
103
  )}
81
104
  </div>
82
105
  {otherParticipants.length > 0 && (
83
- <div className="str-video__speaker-layout--participants-bar-buttons-wrapper">
106
+ <div className="str-video__speaker-layout__participants-bar-buttons-wrapper">
84
107
  {scrollPosition && scrollPosition !== 'start' && (
85
108
  <IconButton
86
109
  onClick={scrollStartClickHandler}
87
110
  icon="caret-left"
88
- className="str-video__speaker-layout--participants-bar-button-left"
111
+ className="str-video__speaker-layout__participants-bar--button-left"
89
112
  />
90
113
  )}
91
114
  <div
92
- className="str-video__speaker-layout--participants-bar-wrapper"
115
+ className="str-video__speaker-layout__participants-bar-wrapper"
93
116
  ref={setScrollWrapper}
94
117
  >
95
- <div className="str-video__speaker-layout--participants-bar">
118
+ <div className="str-video__speaker-layout__participants-bar">
96
119
  {isSpeakerScreenSharing && (
97
120
  <div
98
- className="str-video__speaker-layout--participant-tile"
121
+ className="str-video__speaker-layout__participant-tile"
99
122
  key={participantInSpotlight.sessionId}
100
123
  >
101
- <ParticipantBox
124
+ <ParticipantView
102
125
  participant={participantInSpotlight}
103
- call={call}
104
126
  sinkId={localParticipant?.audioOutputDeviceId}
105
- toggleMenuPosition="top"
127
+ ParticipantViewUI={ParticipantViewUIBar}
128
+ VideoPlaceholder={VideoPlaceholder}
106
129
  />
107
130
  </div>
108
131
  )}
109
132
  {otherParticipants.map((participant) => (
110
133
  <div
111
- className="str-video__speaker-layout--participant-tile"
134
+ className="str-video__speaker-layout__participant-tile"
112
135
  key={participant.sessionId}
113
136
  >
114
- <ParticipantBox
137
+ <ParticipantView
115
138
  participant={participant}
116
- call={call}
117
139
  sinkId={localParticipant?.audioOutputDeviceId}
118
- toggleMenuPosition="top"
140
+ ParticipantViewUI={ParticipantViewUIBar}
141
+ VideoPlaceholder={VideoPlaceholder}
119
142
  />
120
143
  </div>
121
144
  ))}
@@ -125,7 +148,7 @@ export const SpeakerLayout = () => {
125
148
  <IconButton
126
149
  onClick={scrollEndClickHandler}
127
150
  icon="caret-right"
128
- className="str-video__speaker-layout--participants-bar-button-right"
151
+ className="str-video__speaker-layout__participants-bar--button-right"
129
152
  />
130
153
  )}
131
154
  </div>
@@ -135,8 +158,8 @@ export const SpeakerLayout = () => {
135
158
  );
136
159
  };
137
160
 
138
- const hasScreenShare = (p: StreamVideoParticipant) =>
139
- p.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE);
161
+ const hasScreenShare = (p?: StreamVideoParticipant) =>
162
+ !!p?.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE);
140
163
 
141
164
  const loggedIn: Comparator<StreamVideoParticipant> = (a, b) => {
142
165
  if (a.isLoggedInUser) return 1;
@@ -0,0 +1,160 @@
1
+ import { forwardRef } from 'react';
2
+ import { Placement } from '@floating-ui/react';
3
+ import { SfuModels } from '@stream-io/video-client';
4
+ import { useCall } from '@stream-io/video-react-bindings';
5
+ import { clsx } from 'clsx';
6
+
7
+ import {
8
+ IconButton,
9
+ MenuToggle,
10
+ Notification,
11
+ ParticipantActionsContextMenu,
12
+ ToggleMenuButtonProps,
13
+ } from '../../../components';
14
+ import { Reaction } from '../../../components/Reaction';
15
+
16
+ import { DebugParticipantPublishQuality } from '../../../components/Debug/DebugParticipantPublishQuality';
17
+ import { DebugStatsView } from '../../../components/Debug/DebugStatsView';
18
+ import { useIsDebugMode } from '../../../components/Debug/useIsDebugMode';
19
+ import { useParticipantViewContext } from './ParticipantView';
20
+
21
+ export type DefaultParticipantViewUIProps = {
22
+ /**
23
+ * Turns on/off the status indicator icons (mute, connection quality, etc...).
24
+ */
25
+ indicatorsVisible?: boolean;
26
+ /**
27
+ * Placement of the context menu component when opened
28
+ */
29
+ menuPlacement?: Placement;
30
+ /**
31
+ * Option to show/hide menu button component
32
+ */
33
+ showMenuButton?: boolean;
34
+ };
35
+
36
+ const ToggleButton = forwardRef<HTMLButtonElement, ToggleMenuButtonProps>(
37
+ (props, ref) => {
38
+ return <IconButton enabled={props.menuShown} icon="ellipsis" ref={ref} />;
39
+ },
40
+ );
41
+
42
+ export const DefaultParticipantViewUI = ({
43
+ indicatorsVisible = true,
44
+ menuPlacement = 'bottom-end',
45
+ showMenuButton = true,
46
+ }: DefaultParticipantViewUIProps) => {
47
+ const call = useCall()!;
48
+ const { participant, participantViewElement } = useParticipantViewContext();
49
+ const { reaction, sessionId } = participant;
50
+
51
+ return (
52
+ <>
53
+ {showMenuButton && (
54
+ <MenuToggle
55
+ strategy="fixed"
56
+ placement={menuPlacement}
57
+ ToggleButton={ToggleButton}
58
+ >
59
+ <ParticipantActionsContextMenu
60
+ participantViewElement={participantViewElement}
61
+ participant={participant}
62
+ />
63
+ </MenuToggle>
64
+ )}
65
+ {reaction && (
66
+ <Reaction reaction={reaction} sessionId={sessionId} call={call} />
67
+ )}
68
+ <ParticipantDetails indicatorsVisible={indicatorsVisible} />
69
+ </>
70
+ );
71
+ };
72
+
73
+ export const ParticipantDetails = ({
74
+ indicatorsVisible = true,
75
+ }: Pick<DefaultParticipantViewUIProps, 'indicatorsVisible'>) => {
76
+ const { participant } = useParticipantViewContext();
77
+ const {
78
+ isDominantSpeaker,
79
+ isLoggedInUser,
80
+ connectionQuality,
81
+ publishedTracks,
82
+ pinnedAt,
83
+ sessionId,
84
+ name,
85
+ userId,
86
+ videoStream,
87
+ } = participant;
88
+ const call = useCall()!;
89
+
90
+ const connectionQualityAsString =
91
+ !!connectionQuality &&
92
+ SfuModels.ConnectionQuality[connectionQuality].toLowerCase();
93
+
94
+ const hasAudio = publishedTracks.includes(SfuModels.TrackType.AUDIO);
95
+ const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO);
96
+ const isPinned = !!pinnedAt;
97
+
98
+ const isDebugMode = useIsDebugMode();
99
+
100
+ return (
101
+ <div className="str-video__participant-details">
102
+ <span className="str-video__participant-details__name">
103
+ {name || userId}
104
+ {indicatorsVisible && isDominantSpeaker && (
105
+ <span
106
+ className="str-video__participant-details__name--dominant_speaker"
107
+ title="Dominant speaker"
108
+ />
109
+ )}
110
+ {indicatorsVisible && (
111
+ <Notification
112
+ isVisible={
113
+ isLoggedInUser &&
114
+ connectionQuality === SfuModels.ConnectionQuality.POOR
115
+ }
116
+ message="Poor connection quality. Please check your internet connection."
117
+ >
118
+ {connectionQualityAsString && (
119
+ <span
120
+ className={clsx(
121
+ 'str-video__participant-details__connection-quality',
122
+ `str-video__participant-details__connection-quality--${connectionQualityAsString}`,
123
+ )}
124
+ title={connectionQualityAsString}
125
+ />
126
+ )}
127
+ </Notification>
128
+ )}
129
+ {indicatorsVisible && !hasAudio && (
130
+ <span className="str-video__participant-details__name--audio-muted" />
131
+ )}
132
+ {indicatorsVisible && !hasVideo && (
133
+ <span className="str-video__participant-details__name--video-muted" />
134
+ )}
135
+ {indicatorsVisible && isPinned && (
136
+ // TODO: remove this monstrosity once we have a proper design
137
+ <span
138
+ title="Unpin"
139
+ onClick={() => call?.setParticipantPinnedAt(sessionId)}
140
+ style={{ cursor: 'pointer' }}
141
+ className="str-video__participant-details__name--pinned"
142
+ />
143
+ )}
144
+ </span>
145
+ {isDebugMode && (
146
+ <>
147
+ <DebugParticipantPublishQuality
148
+ participant={participant}
149
+ call={call}
150
+ />
151
+ <DebugStatsView
152
+ call={call}
153
+ sessionId={sessionId}
154
+ mediaStream={videoStream}
155
+ />
156
+ </>
157
+ )}
158
+ </div>
159
+ );
160
+ };
@@ -0,0 +1,156 @@
1
+ import { useContext, useMemo } from 'react';
2
+ import {
3
+ forwardRef,
4
+ ComponentType,
5
+ useState,
6
+ ReactElement,
7
+ createContext,
8
+ } from 'react';
9
+ import clsx from 'clsx';
10
+ import {
11
+ SfuModels,
12
+ StreamVideoLocalParticipant,
13
+ StreamVideoParticipant,
14
+ } from '@stream-io/video-client';
15
+
16
+ import { Audio } from '../Audio';
17
+ import { Video, VideoProps } from '../Video';
18
+ import { useTrackElementVisibility } from '../../hooks';
19
+ import { DefaultParticipantViewUI } from './DefaultParticipantViewUI';
20
+ import { isComponentType, applyElementToRef } from '../../../utilities';
21
+
22
+ export type ParticipantViewContextValue = Pick<
23
+ ParticipantViewProps,
24
+ 'participant'
25
+ > & {
26
+ participantViewElement: HTMLDivElement | null;
27
+ };
28
+
29
+ const ParticipantViewContext = createContext<
30
+ ParticipantViewContextValue | undefined
31
+ >(undefined);
32
+
33
+ export const useParticipantViewContext = () =>
34
+ useContext(ParticipantViewContext) as ParticipantViewContextValue;
35
+
36
+ export type ParticipantViewProps = {
37
+ /**
38
+ * The participant bound to this component.
39
+ */
40
+ participant: StreamVideoParticipant | StreamVideoLocalParticipant;
41
+
42
+ /**
43
+ * Component used to render user interface elements (details, network status...),
44
+ * pass `null` if you wish to not render anything
45
+ * @default DefaultParticipantViewUI
46
+ */
47
+ ParticipantViewUI?: ComponentType | ReactElement | null;
48
+
49
+ /**
50
+ * In supported browsers, this sets the default audio output.
51
+ * The value of this prop should be a valid Audio Output `deviceId`.
52
+ */
53
+ sinkId?: string;
54
+
55
+ /**
56
+ * The kind of video stream to play for the given participant.
57
+ */
58
+ videoKind?: 'video' | 'screen';
59
+
60
+ /**
61
+ * Turns on/off the audio for the participant.
62
+ */
63
+ muteAudio?: boolean;
64
+
65
+ /**
66
+ * An object with set functions meant for exposing the "native" video
67
+ * and video placeholder elements to the integrators.
68
+ * - `refs.setVideoElement`
69
+ * - `refs.setVideoPlaceholderElement`
70
+ */
71
+ refs?: VideoProps['refs'];
72
+
73
+ /**
74
+ * Custom class applied to the root DOM element.
75
+ */
76
+ className?: string;
77
+ } & Pick<VideoProps, 'VideoPlaceholder'>;
78
+
79
+ export const ParticipantView = forwardRef<HTMLDivElement, ParticipantViewProps>(
80
+ (
81
+ {
82
+ participant,
83
+ sinkId,
84
+ videoKind = 'video',
85
+ muteAudio,
86
+ refs,
87
+ className,
88
+ VideoPlaceholder,
89
+ ParticipantViewUI = DefaultParticipantViewUI as ComponentType,
90
+ },
91
+ ref,
92
+ ) => {
93
+ const {
94
+ audioStream,
95
+ isLoggedInUser,
96
+ isSpeaking,
97
+ publishedTracks,
98
+ sessionId,
99
+ } = participant;
100
+
101
+ const hasAudio = publishedTracks.includes(SfuModels.TrackType.AUDIO);
102
+ const hasVideo = publishedTracks.includes(SfuModels.TrackType.VIDEO);
103
+
104
+ const [trackedElement, setTrackedElement] = useState<HTMLDivElement | null>(
105
+ null,
106
+ );
107
+
108
+ // TODO: allow to pass custom ViewportTracker instance from props
109
+ useTrackElementVisibility({
110
+ sessionId,
111
+ trackedElement,
112
+ });
113
+
114
+ const participantViewContextValue = useMemo(
115
+ () => ({ participant, participantViewElement: trackedElement }),
116
+ [participant, trackedElement],
117
+ );
118
+
119
+ return (
120
+ <div
121
+ ref={(element) => {
122
+ applyElementToRef(ref, element);
123
+ setTrackedElement(element);
124
+ }}
125
+ className={clsx(
126
+ 'str-video__participant-view',
127
+ isSpeaking && 'str-video__participant-view--speaking',
128
+ !hasVideo && 'str-video__participant-view--no-video',
129
+ !hasAudio && 'str-video__participant-view--no-audio',
130
+ className,
131
+ )}
132
+ >
133
+ <ParticipantViewContext.Provider value={participantViewContextValue}>
134
+ <Audio
135
+ // mute the local participant, as we don't want to hear ourselves
136
+ muted={isLoggedInUser || muteAudio}
137
+ sinkId={sinkId}
138
+ audioStream={audioStream}
139
+ />
140
+ <Video
141
+ VideoPlaceholder={VideoPlaceholder}
142
+ participant={participant}
143
+ kind={videoKind}
144
+ refs={refs}
145
+ autoPlay
146
+ />
147
+ {isComponentType(ParticipantViewUI) ? (
148
+ <ParticipantViewUI />
149
+ ) : (
150
+ ParticipantViewUI
151
+ )}
152
+ </ParticipantViewContext.Provider>
153
+ </div>
154
+ );
155
+ },
156
+ );
@@ -0,0 +1,2 @@
1
+ export * from './ParticipantView';
2
+ export * from './DefaultParticipantViewUI';