@100mslive/roomkit-react 0.3.10 → 0.3.11-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. package/dist/{HLSView-PF44ZBXN.js → HLSView-HJ44JWJK.js} +18 -3
  2. package/dist/HLSView-HJ44JWJK.js.map +7 -0
  3. package/dist/{HLSView-6J5FD3IV.css → HLSView-IBWU4R7W.css} +3 -3
  4. package/dist/{HLSView-6J5FD3IV.css.map → HLSView-IBWU4R7W.css.map} +1 -1
  5. package/dist/Prebuilt/common/constants.d.ts +0 -2
  6. package/dist/Prebuilt/common/hooks.d.ts +8 -1
  7. package/dist/Prebuilt/components/MoreSettings/CaptionContent.d.ts +5 -0
  8. package/dist/Prebuilt/components/MoreSettings/CaptionModal.d.ts +4 -0
  9. package/dist/Prebuilt/components/Polls/Voting/StandardVoting.d.ts +4 -2
  10. package/dist/Prebuilt/components/Polls/Voting/TimedVoting.d.ts +4 -2
  11. package/dist/Prebuilt/layouts/WaitingView.d.ts +6 -0
  12. package/dist/{chunk-TUSCTU6T.js → chunk-WDZ4KRYM.js} +2095 -1805
  13. package/dist/chunk-WDZ4KRYM.js.map +7 -0
  14. package/dist/index.cjs.css +2 -2
  15. package/dist/index.cjs.css.map +1 -1
  16. package/dist/index.cjs.js +2752 -2446
  17. package/dist/index.cjs.js.map +4 -4
  18. package/dist/index.css +2 -2
  19. package/dist/index.css.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/meta.cjs.json +292 -114
  22. package/dist/meta.esbuild.json +311 -132
  23. package/package.json +7 -7
  24. package/src/Prebuilt/common/constants.ts +0 -2
  25. package/src/Prebuilt/common/hooks.ts +34 -1
  26. package/src/Prebuilt/common/utils.js +11 -11
  27. package/src/Prebuilt/components/AppData/AppData.tsx +2 -4
  28. package/src/Prebuilt/components/AppData/useUISettings.js +0 -3
  29. package/src/Prebuilt/components/Chat/Chat.tsx +26 -6
  30. package/src/Prebuilt/components/Chat/ChatFooter.tsx +18 -2
  31. package/src/Prebuilt/components/Chat/ChatStates.tsx +1 -1
  32. package/src/Prebuilt/components/Footer/ChatToggle.tsx +5 -1
  33. package/src/Prebuilt/components/Footer/ParticipantList.tsx +4 -2
  34. package/src/Prebuilt/components/Footer/PollsToggle.tsx +1 -1
  35. package/src/Prebuilt/components/MoreSettings/CaptionContent.tsx +132 -0
  36. package/src/Prebuilt/components/MoreSettings/CaptionModal.tsx +37 -0
  37. package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +40 -3
  38. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +19 -19
  39. package/src/Prebuilt/components/Polls/CreatePollQuiz/PollsQuizMenu.tsx +2 -15
  40. package/src/Prebuilt/components/Polls/Voting/LeaderboardSummary.tsx +71 -66
  41. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +39 -40
  42. package/src/Prebuilt/components/Polls/Voting/StandardVoting.tsx +12 -6
  43. package/src/Prebuilt/components/Polls/Voting/TimedVoting.tsx +21 -10
  44. package/src/Prebuilt/components/Polls/Voting/Voting.tsx +44 -2
  45. package/src/Prebuilt/components/VideoLayouts/EqualProminence.tsx +13 -17
  46. package/src/Prebuilt/components/VideoLayouts/GridLayout.tsx +17 -0
  47. package/src/Prebuilt/layouts/HLSView.jsx +14 -11
  48. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +43 -9
  49. package/src/Prebuilt/layouts/WaitingView.tsx +52 -0
  50. package/dist/HLSView-PF44ZBXN.js.map +0 -7
  51. package/dist/chunk-TUSCTU6T.js.map +0 -7
  52. package/src/Prebuilt/layouts/NonPublisherView.jsx +0 -51
  53. package/src/Prebuilt/layouts/WaitingView.jsx +0 -51
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.3.10",
13
+ "version": "0.3.11-alpha.0",
14
14
  "author": "100ms",
15
15
  "license": "MIT",
16
16
  "repository": {
@@ -74,12 +74,12 @@
74
74
  "react": ">=17.0.2 <19.0.0"
75
75
  },
76
76
  "dependencies": {
77
- "@100mslive/hls-player": "0.3.10",
77
+ "@100mslive/hls-player": "0.3.11-alpha.0",
78
78
  "@100mslive/hms-noise-cancellation": "0.0.1",
79
- "@100mslive/hms-virtual-background": "1.13.10",
80
- "@100mslive/hms-whiteboard": "0.0.0",
81
- "@100mslive/react-icons": "0.10.10",
82
- "@100mslive/react-sdk": "0.10.10",
79
+ "@100mslive/hms-virtual-background": "1.13.11-alpha.0",
80
+ "@100mslive/hms-whiteboard": "0.0.1-alpha.0",
81
+ "@100mslive/react-icons": "0.10.11-alpha.0",
82
+ "@100mslive/react-sdk": "0.10.11-alpha.0",
83
83
  "@100mslive/types-prebuilt": "0.12.8",
84
84
  "@emoji-mart/data": "^1.0.6",
85
85
  "@emoji-mart/react": "^1.0.1",
@@ -115,5 +115,5 @@
115
115
  "uuid": "^8.3.2",
116
116
  "worker-timers": "^7.0.40"
117
117
  },
118
- "gitHead": "391cefe10a5dd5c200c59c577eb46741b5476d8f"
118
+ "gitHead": "a4ed4a99addf5a2e5b923743c65bd20bd41d4175"
119
119
  }
@@ -1,6 +1,5 @@
1
1
  import { parsedUserAgent } from '@100mslive/react-sdk';
2
2
 
3
- export const DEFAULT_WAITING_VIEWER_ROLE = 'waiting-room';
4
3
  export const QUERY_PARAM_SKIP_PREVIEW = 'skip_preview';
5
4
  export const QUERY_PARAM_SKIP_PREVIEW_HEADFUL = 'skip_preview_headful';
6
5
  export const QUERY_PARAM_NAME = 'name';
@@ -30,7 +29,6 @@ export const APP_DATA = {
30
29
  appConfig: 'appConfig',
31
30
  sidePane: 'sidePane',
32
31
  hlsStats: 'hlsStats',
33
- waitingViewerRole: 'waitingViewerRole',
34
32
  subscribedNotifications: 'subscribedNotifications',
35
33
  logo: 'logo',
36
34
  hlsStarted: 'hlsStarted',
@@ -1,17 +1,22 @@
1
- import { useCallback, useEffect, useRef, useState } from 'react';
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
3
  import { HMSHLSPlayer } from '@100mslive/hls-player';
4
4
  import { JoinForm_JoinBtnType } from '@100mslive/types-prebuilt/elements/join_form';
5
5
  import {
6
+ HMSPeer,
6
7
  HMSRecording,
7
8
  parsedUserAgent,
8
9
  selectAvailableRoleNames,
10
+ selectIsAllowedToPublish,
9
11
  selectIsConnectedToRoom,
12
+ selectLocalPeerRole,
10
13
  selectPeerCount,
11
14
  selectPeerMetadata,
12
15
  selectPeers,
16
+ selectPeersByRoles,
13
17
  selectRecordingState,
14
18
  selectRemotePeers,
19
+ selectRolesMap,
15
20
  useHMSActions,
16
21
  useHMSStore,
17
22
  useHMSVanillaStore,
@@ -218,3 +223,31 @@ export function getResolution(
218
223
  }
219
224
  return resolution;
220
225
  }
226
+
227
+ export interface WaitingRoomInfo {
228
+ isNotAllowedToPublish: boolean;
229
+ isScreenOnlyPublishParams: boolean;
230
+ hasSubscribedRolePublishing: boolean;
231
+ }
232
+ export function useWaitingRoomInfo(): WaitingRoomInfo {
233
+ const localPeerRole = useHMSStore(selectLocalPeerRole);
234
+ const { video, audio, screen } = useHMSStore(selectIsAllowedToPublish);
235
+ const roles = useHMSStore(selectRolesMap);
236
+ const peersByRoles = useHMSStore(selectPeersByRoles(localPeerRole?.subscribeParams.subscribeToRoles || []));
237
+ const isNotAllowedToPublish = !(video || audio || screen);
238
+ const isScreenOnlyPublishParams: boolean = screen && !(video || audio);
239
+ const hasSubscribedRolePublishing: boolean = useMemo(() => {
240
+ return peersByRoles.some((peer: HMSPeer) => {
241
+ if (peer.roleName && roles[peer.roleName] && !peer.isLocal) {
242
+ return !!roles[peer.roleName].publishParams?.allowed.length;
243
+ }
244
+ return false;
245
+ });
246
+ }, [peersByRoles, roles]);
247
+
248
+ return {
249
+ isNotAllowedToPublish,
250
+ isScreenOnlyPublishParams,
251
+ hasSubscribedRolePublishing,
252
+ };
253
+ }
@@ -142,22 +142,22 @@ export const getPeerResponses = (questions, peerid, userid) => {
142
142
  return questions.map(question =>
143
143
  question.responses?.filter(
144
144
  response =>
145
- ((response && response.peer?.peerid === peerid) || response.peer?.userid === userid) && !response.skipped,
145
+ response && (response.peer?.peerid === peerid || response.peer?.userid === userid) && !response.skipped,
146
146
  ),
147
147
  );
148
148
  };
149
149
 
150
- export const getLastAttemptedIndex = (questions, peerid, userid = '') => {
151
- const peerResponses = getPeerResponses(questions, peerid, userid) || [];
152
- for (let i = 0; i < peerResponses.length; i++) {
153
- // If another peer has attempted, undefined changes to an empty array
154
- if (peerResponses[i] === undefined || peerResponses[i].length === 0) {
155
- // Backend question index starts at 1
156
- return i + 1;
150
+ export const getIndexToShow = responses => {
151
+ let lastAttemptedIndex = 0;
152
+
153
+ Object.keys(responses).forEach(key => {
154
+ const keyNum = parseInt(key);
155
+ if (keyNum > lastAttemptedIndex && responses[key]) {
156
+ lastAttemptedIndex = keyNum;
157
157
  }
158
- }
159
- // To indicate all have been attempted
160
- return questions.length + 1;
158
+ });
159
+
160
+ return lastAttemptedIndex + 1;
161
161
  };
162
162
 
163
163
  export const getPeerParticipationSummary = (poll, localPeerID, localCustomerUserID) => {
@@ -21,7 +21,6 @@ import { useSetAppDataByKey } from './useUISettings';
21
21
  import {
22
22
  APP_DATA,
23
23
  CHAT_SELECTOR,
24
- DEFAULT_WAITING_VIEWER_ROLE,
25
24
  POLL_STATE,
26
25
  SIDE_PANE_OPTIONS,
27
26
  UI_MODE_GRID,
@@ -56,7 +55,6 @@ const initialAppData = {
56
55
  [APP_DATA.hlsStarted]: false,
57
56
  [APP_DATA.rtmpStarted]: false,
58
57
  [APP_DATA.recordingStarted]: false,
59
- [APP_DATA.waitingViewerRole]: DEFAULT_WAITING_VIEWER_ROLE,
60
58
  [APP_DATA.dropdownList]: [],
61
59
  [APP_DATA.authToken]: '',
62
60
  [APP_DATA.minimiseInset]: false,
@@ -68,8 +66,8 @@ const initialAppData = {
68
66
  [POLL_STATE.pollInView]: '',
69
67
  [POLL_STATE.view]: '',
70
68
  },
71
- // by default off, so it will not appear in beam bots
72
- [APP_DATA.caption]: false,
69
+ // by default on because of on demand now
70
+ [APP_DATA.caption]: true,
73
71
  };
74
72
 
75
73
  export const AppData = React.memo(() => {
@@ -48,9 +48,6 @@ export const useSetUiSettings = uiSettingKey => {
48
48
  return [value, setValue];
49
49
  };
50
50
 
51
- export const useWaitingViewerRole = () => {
52
- return useHMSStore(selectAppData(APP_DATA.waitingViewerRole));
53
- };
54
51
  export const useIsHLSStartedFromUI = () => {
55
52
  return useHMSStore(selectAppData(APP_DATA.hlsStarted));
56
53
  };
@@ -10,17 +10,20 @@ import { Box, Flex } from '../../../Layout';
10
10
  import { config as cssConfig } from '../../../Theme';
11
11
  // @ts-ignore: No implicit any
12
12
  import { EmojiReaction } from '../EmojiReaction';
13
+ import { MoreSettings } from '../MoreSettings/MoreSettings';
14
+ import { RaiseHand } from '../RaiseHand';
13
15
  import { ChatBody } from './ChatBody';
14
16
  import { ChatFooter } from './ChatFooter';
15
17
  import { ChatBlocked, ChatPaused } from './ChatStates';
16
18
  import { PinnedMessage } from './PinnedMessage';
17
19
  import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
18
20
  import { useSidepaneResetOnLayoutUpdate } from '../AppData/useSidepaneResetOnLayoutUpdate';
21
+ import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
19
22
  import { useLandscapeHLSStream, useMobileHLSStream } from '../../common/hooks';
20
23
  import { SESSION_STORE_KEY, SIDE_PANE_OPTIONS } from '../../common/constants';
21
24
 
22
25
  export const Chat = () => {
23
- const { elements } = useRoomLayoutConferencingScreen();
26
+ const { elements, screenType } = useRoomLayoutConferencingScreen();
24
27
  const listRef = useRef<VariableSizeList | null>(null);
25
28
  const hmsActions = useHMSActions();
26
29
  const vanillaStore = useHMSVanillaStore();
@@ -29,6 +32,7 @@ export const Chat = () => {
29
32
  const isMobileHLSStream = useMobileHLSStream();
30
33
  const isLandscapeStream = useLandscapeHLSStream();
31
34
  useSidepaneResetOnLayoutUpdate('chat', SIDE_PANE_OPTIONS.CHAT);
35
+ const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
32
36
 
33
37
  const scrollToBottom = useCallback(
34
38
  (unreadCount = 0) => {
@@ -57,20 +61,27 @@ export const Chat = () => {
57
61
  >
58
62
  {isMobile && elements?.chat?.is_overlay && !streaming ? null : <PinnedMessage />}
59
63
  <ChatBody ref={listRef} scrollToBottom={scrollToBottom} />
60
-
61
- <ChatPaused />
62
- <ChatBlocked />
64
+ <Flex align="center" css={{ w: '100%', gap: '$2' }}>
65
+ <ChatPaused />
66
+ <ChatBlocked />
67
+ {streaming && (!isChatEnabled || isLocalPeerBlacklisted) && (
68
+ <>
69
+ <RaiseHand css={{ bg: '$surface_default' }} />
70
+ <MoreSettings elements={elements} screenType={screenType} />
71
+ </>
72
+ )}
73
+ </Flex>
63
74
  {isMobile && elements?.chat?.is_overlay && !streaming ? <PinnedMessage /> : null}
64
75
  {isChatEnabled ? (
65
76
  <ChatFooter onSend={scrollToBottom}>
66
77
  <NewMessageIndicator scrollToBottom={scrollToBottom} listRef={listRef} />
67
78
  </ChatFooter>
68
79
  ) : null}
69
- {(isMobileHLSStream || isLandscapeStream) && (
80
+ {streaming && (
70
81
  <Box
71
82
  css={{
72
83
  position: 'absolute',
73
- ...match({ isLandscapeStream, isMobileHLSStream, isChatEnabled })
84
+ ...match({ isLandscapeStream, isMobileHLSStream, isChatEnabled, isLocalPeerBlacklisted })
74
85
  .with(
75
86
  {
76
87
  isLandscapeStream: true,
@@ -96,6 +107,7 @@ export const Chat = () => {
96
107
  {
97
108
  isMobileHLSStream: true,
98
109
  isChatEnabled: true,
110
+ isLocalPeerBlacklisted: false,
99
111
  },
100
112
  () => ({ bottom: '$17', right: '$8' }),
101
113
  )
@@ -103,6 +115,14 @@ export const Chat = () => {
103
115
  {
104
116
  isLandscapeStream: false,
105
117
  isChatEnabled: true,
118
+ isLocalPeerBlacklisted: true,
119
+ },
120
+ () => ({ bottom: '$18', right: '$8' }),
121
+ )
122
+ .with(
123
+ {
124
+ isMobileHLSStream: true,
125
+ isLocalPeerBlacklisted: true,
106
126
  },
107
127
  () => ({ bottom: '$20', right: '$8' }),
108
128
  )
@@ -102,6 +102,19 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
102
102
  }
103
103
  }
104
104
  }, [defaultSelection, selectedPeer, selectedRole, setRoleSelector, isMobile, isLandscapeHLSStream, elements?.chat]);
105
+
106
+ const resetInputHeight = useCallback(() => {
107
+ if (inputRef.current) {
108
+ inputRef.current.style.height = `${Math.max(32, inputRef.current.value ? inputRef.current.scrollHeight : 0)}px`;
109
+ }
110
+ }, []);
111
+
112
+ const updateInputHeight = useCallback(() => {
113
+ if (inputRef.current) {
114
+ inputRef.current.style.height = `${Math.max(32, Math.min(inputRef.current.scrollHeight, 24 * 4))}px`;
115
+ }
116
+ }, []);
117
+
105
118
  const sendMessage = useCallback(async () => {
106
119
  const message = inputRef?.current?.value;
107
120
  if (!message || !message.trim().length) {
@@ -116,6 +129,7 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
116
129
  await hmsActions.sendBroadcastMessage(message);
117
130
  }
118
131
  inputRef.current.value = '';
132
+ resetInputHeight();
119
133
  setTimeout(() => {
120
134
  onSend(1);
121
135
  }, 0);
@@ -131,6 +145,7 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
131
145
  const messageElement = inputRef.current;
132
146
  if (messageElement) {
133
147
  messageElement.value = draftMessage;
148
+ updateInputHeight();
134
149
  }
135
150
  }, [draftMessage]);
136
151
 
@@ -197,11 +212,10 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
197
212
  {selection && (
198
213
  <Flex align="center" css={{ gap: '$4', w: '100%' }}>
199
214
  <Flex
200
- align="center"
215
+ align="end"
201
216
  css={{
202
217
  bg: isOverlayChat && isMobile ? '$surface_dim' : '$surface_default',
203
218
  minHeight: '$16',
204
- maxHeight: '$24',
205
219
  position: 'relative',
206
220
  py: '$6',
207
221
  pl: '$8',
@@ -238,6 +252,8 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
238
252
  }}
239
253
  autoComplete="off"
240
254
  aria-autocomplete="none"
255
+ onChange={updateInputHeight}
256
+ onBlur={resetInputHeight}
241
257
  onPaste={e => e.stopPropagation()}
242
258
  onCut={e => e.stopPropagation()}
243
259
  onCopy={e => e.stopPropagation()}
@@ -30,7 +30,7 @@ export const ChatPaused = () => {
30
30
  <Flex
31
31
  align="center"
32
32
  justify="between"
33
- css={{ borderRadius: '$1', bg: '$surface_default', p: '$4 $4 $4 $8', w: '100%' }}
33
+ css={{ borderRadius: '$1', bg: '$surface_default', p: '$2 $4 $2 $8', w: '100%' }}
34
34
  >
35
35
  <Box>
36
36
  <Text variant="sm" css={{ fontWeight: '$semiBold', color: '$on_surface_high' }}>
@@ -21,7 +21,11 @@ export const ChatToggle = ({ onClick }: { onClick?: () => void }) => {
21
21
  }}
22
22
  >
23
23
  <Tooltip key="chat" title={`${isChatOpen ? 'Close' : 'Open'} chat`}>
24
- <IconButton onClick={() => (onClick ? onClick() : toggleChat())} active={!isChatOpen} data-testid="chat_btn">
24
+ <IconButton
25
+ onClick={() => (onClick ? onClick() : toggleChat())}
26
+ css={{ bg: isChatOpen ? '$surface_brighter' : '' }}
27
+ data-testid="chat_btn"
28
+ >
25
29
  <ChatIcon />
26
30
  </IconButton>
27
31
  </Tooltip>
@@ -86,6 +86,7 @@ export const ParticipantList = ({
86
86
  return { ...filterValue };
87
87
  });
88
88
  }, []);
89
+
89
90
  if (peerCount === 0) {
90
91
  return null;
91
92
  }
@@ -128,7 +129,7 @@ export const ParticipantList = ({
128
129
  export const ParticipantCount = () => {
129
130
  const peerCount = useHMSStore(selectPeerCount);
130
131
  const toggleSidepane = useSidepaneToggle(SIDE_PANE_OPTIONS.PARTICIPANTS);
131
- const isParticipantsOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.PARTICIPANTS);
132
+ const isPeerListOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.PARTICIPANTS);
132
133
 
133
134
  if (peerCount === 0) {
134
135
  return null;
@@ -139,13 +140,13 @@ export const ParticipantCount = () => {
139
140
  w: 'auto',
140
141
  p: '$4',
141
142
  h: 'auto',
143
+ bg: isPeerListOpen ? '$surface_brighter' : '',
142
144
  }}
143
145
  onClick={() => {
144
146
  if (peerCount > 0) {
145
147
  toggleSidepane();
146
148
  }
147
149
  }}
148
- active={!isParticipantsOpen}
149
150
  data-testid="participant_list"
150
151
  >
151
152
  <PeopleIcon />
@@ -447,6 +448,7 @@ export const ParticipantSearch = ({
447
448
  300,
448
449
  [value, onSearch],
449
450
  );
451
+
450
452
  return (
451
453
  <Flex
452
454
  align="center"
@@ -27,7 +27,7 @@ export const PollsToggle = () => {
27
27
  togglePollView();
28
28
  setUnreadPollQuiz(false);
29
29
  }}
30
- active={!isPollsOpen}
30
+ css={{ bg: isPollsOpen ? '$surface_brighter' : '' }}
31
31
  data-testid="polls_btn"
32
32
  >
33
33
  {unreadPollQuiz ? <QuizActiveIcon /> : <QuizIcon />}
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import { HMSTranscriptionMode, selectIsTranscriptionEnabled, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
3
+ import { AlertTriangleIcon, CrossIcon } from '@100mslive/react-icons';
4
+ import { Button } from '../../../Button';
5
+ import { Box, Flex } from '../../../Layout';
6
+ import { Loading } from '../../../Loading';
7
+ import { Text } from '../../../Text';
8
+ // @ts-ignore: No implicit Any
9
+ import { ToastManager } from '../Toast/ToastManager';
10
+ // @ts-ignore: No implicit Any
11
+ import { useSetIsCaptionEnabled } from '../AppData/useUISettings';
12
+
13
+ export const CaptionContent = ({ isMobile, onExit }: { isMobile: boolean; onExit: () => void }) => {
14
+ const DURATION = 2000;
15
+ const actions = useHMSActions();
16
+ const isTranscriptionEnabled = useHMSStore(selectIsTranscriptionEnabled);
17
+
18
+ const [isCaptionEnabled, setIsCaptionEnabled] = useSetIsCaptionEnabled();
19
+ return (
20
+ <>
21
+ <Text
22
+ variant={isMobile ? 'md' : 'lg'}
23
+ css={{
24
+ color: '$on_surface_high',
25
+ fontWeight: '$semiBold',
26
+ display: 'flex',
27
+ pb: '$4',
28
+ '@md': { px: '$8', borderBottom: '1px solid $border_default' },
29
+ }}
30
+ >
31
+ {isTranscriptionEnabled ? 'Disable' : 'Enable'} Closed Caption (CC) for this session?
32
+ <Box
33
+ css={{ color: 'inherit', ml: 'auto', '&:hover': { color: '$on_surface_medium', cursor: 'pointer' } }}
34
+ onClick={onExit}
35
+ >
36
+ <CrossIcon />
37
+ </Box>
38
+ </Text>
39
+ {!isMobile ? (
40
+ <Text variant="sm" css={{ color: '$on_surface_medium', pb: '$6', mb: '$8', '@md': { px: '$8', mt: '$4' } }}>
41
+ This will {isTranscriptionEnabled ? 'disable' : 'enable'} Closed Captions for everyone in this room. You can
42
+ {isTranscriptionEnabled ? 'enable' : 'disable'} it later.
43
+ </Text>
44
+ ) : null}
45
+
46
+ <Flex
47
+ justify="between"
48
+ align="center"
49
+ css={{
50
+ width: '100%',
51
+ gap: '$md',
52
+ mt: '$10',
53
+ '@md': { px: '$4' },
54
+ }}
55
+ >
56
+ {isMobile ? null : (
57
+ <Button variant="standard" css={{ w: '100%' }} outlined onClick={onExit}>
58
+ Cancel
59
+ </Button>
60
+ )}
61
+ <Flex
62
+ direction="column"
63
+ justify="between"
64
+ align="center"
65
+ css={{
66
+ width: '100%',
67
+ }}
68
+ >
69
+ {isMobile && isTranscriptionEnabled ? (
70
+ <Button
71
+ variant="standard"
72
+ css={{ w: '100%', mb: '$8' }}
73
+ outlined
74
+ onClick={() => {
75
+ setIsCaptionEnabled(!isCaptionEnabled);
76
+ onExit();
77
+ }}
78
+ >
79
+ {isCaptionEnabled ? 'Hide For Me' : 'Show For Me'}
80
+ </Button>
81
+ ) : null}
82
+ <Button
83
+ variant={isTranscriptionEnabled ? 'danger' : 'primary'}
84
+ css={{ width: '100%' }}
85
+ data-testid="popup_change_btn"
86
+ onClick={async () => {
87
+ try {
88
+ if (isTranscriptionEnabled) {
89
+ await actions.stopTranscription({
90
+ mode: HMSTranscriptionMode.CAPTION,
91
+ });
92
+ ToastManager.addToast({
93
+ title: `Disabling Closed Caption for everyone.`,
94
+ variant: 'standard',
95
+ duration: DURATION,
96
+ icon: <Loading color="currentColor" />,
97
+ });
98
+ onExit();
99
+ return;
100
+ }
101
+ await actions.startTranscription({
102
+ mode: HMSTranscriptionMode.CAPTION,
103
+ });
104
+ ToastManager.addToast({
105
+ title: `Enabling Closed Caption for everyone.`,
106
+ variant: 'standard',
107
+ duration: DURATION,
108
+ icon: <Loading color="currentColor" />,
109
+ });
110
+ } catch (err) {
111
+ ToastManager.addToast({
112
+ title: `Failed to ${isTranscriptionEnabled ? 'disabled' : 'enabled'} closed caption`,
113
+ variant: 'error',
114
+ icon: <AlertTriangleIcon style={{ marginRight: '0.5rem' }} />,
115
+ });
116
+ }
117
+ onExit();
118
+ }}
119
+ >
120
+ {isTranscriptionEnabled ? 'Disable' : 'Enable'} for Everyone
121
+ </Button>
122
+ </Flex>
123
+ </Flex>
124
+ {isMobile && (
125
+ <Text variant="sm" css={{ color: '$on_surface_medium', pb: '$6', mb: '$8', '@md': { px: '$8', mt: '$4' } }}>
126
+ This will {isTranscriptionEnabled ? 'disable' : 'enable'} Closed Captions for everyone in this room. You can
127
+ {isTranscriptionEnabled ? 'enable' : 'disable'} it later.
128
+ </Text>
129
+ )}
130
+ </>
131
+ );
132
+ };
@@ -0,0 +1,37 @@
1
+ import React from 'react';
2
+ import { useMedia } from 'react-use';
3
+ import { config as cssConfig, Dialog } from '../../..';
4
+ import { Sheet } from '../../../Sheet';
5
+ import { CaptionContent } from './CaptionContent';
6
+
7
+ export const CaptionModal = ({ onOpenChange }: { onOpenChange: (value: boolean) => void }) => {
8
+ const isMobile = useMedia(cssConfig.media.md);
9
+
10
+ const props = {
11
+ isMobile,
12
+ onExit: () => {
13
+ onOpenChange(false);
14
+ },
15
+ };
16
+
17
+ if (isMobile) {
18
+ return (
19
+ <Sheet.Root defaultOpen onOpenChange={onOpenChange}>
20
+ <Sheet.Content css={{ bg: '$surface_dim', p: '$8 0' }}>
21
+ <CaptionContent {...props} />
22
+ </Sheet.Content>
23
+ </Sheet.Root>
24
+ );
25
+ }
26
+
27
+ return (
28
+ <Dialog.Root defaultOpen onOpenChange={onOpenChange}>
29
+ <Dialog.Portal>
30
+ <Dialog.Overlay />
31
+ <Dialog.Content css={{ bg: '$surface_dim', width: 'min(400px,80%)', p: '$10' }}>
32
+ <CaptionContent {...props} />
33
+ </Dialog.Content>
34
+ </Dialog.Portal>
35
+ </Dialog.Root>
36
+ );
37
+ };
@@ -6,9 +6,23 @@ import {
6
6
  HLSLiveStreamingScreen_Elements,
7
7
  } from '@100mslive/types-prebuilt';
8
8
  import { match } from 'ts-pattern';
9
- import { selectAppData, selectLocalPeerID, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
10
- import { BrbIcon, CheckIcon, HamburgerMenuIcon, InfoIcon, PipIcon, SettingsIcon } from '@100mslive/react-icons';
11
- import { Checkbox, Dropdown, Flex, Text, Tooltip } from '../../../..';
9
+ import {
10
+ selectAppData,
11
+ selectIsTranscriptionEnabled,
12
+ selectLocalPeerID,
13
+ useHMSActions,
14
+ useHMSStore,
15
+ } from '@100mslive/react-sdk';
16
+ import {
17
+ BrbIcon,
18
+ CheckIcon,
19
+ HamburgerMenuIcon,
20
+ InfoIcon,
21
+ OpenCaptionIcon,
22
+ PipIcon,
23
+ SettingsIcon,
24
+ } from '@100mslive/react-icons';
25
+ import { Checkbox, Dropdown, Flex, Switch, Text, Tooltip } from '../../../..';
12
26
  import IconButton from '../../../IconButton';
13
27
  // @ts-ignore: No implicit any
14
28
  import { PIP } from '../../PIP';
@@ -24,6 +38,7 @@ import StartRecording from '../../Settings/StartRecording';
24
38
  import { StatsForNerds } from '../../StatsForNerds';
25
39
  // @ts-ignore: No implicit any
26
40
  import { BulkRoleChangeModal } from '../BulkRoleChangeModal';
41
+ import { CaptionModal } from '../CaptionModal';
27
42
  // @ts-ignore: No implicit any
28
43
  import { FullScreenItem } from '../FullScreenItem';
29
44
  import { MuteAllModal } from '../MuteAllModal';
@@ -43,6 +58,7 @@ const MODALS = {
43
58
  BULK_ROLE_CHANGE: 'bulkRoleChange',
44
59
  MUTE_ALL: 'muteAll',
45
60
  EMBED_URL: 'embedUrl',
61
+ CAPTION: 'caption',
46
62
  };
47
63
 
48
64
  export const DesktopOptions = ({
@@ -59,6 +75,7 @@ export const DesktopOptions = ({
59
75
  const { isBRBOn, toggleBRB } = useMyMetadata();
60
76
  const isPipOn = PictureInPicture.isOn();
61
77
  const isBRBEnabled = !!elements?.brb;
78
+ const isTranscriptionEnabled = useHMSStore(selectIsTranscriptionEnabled);
62
79
 
63
80
  useDropdownList({ open: openModals.size > 0, name: 'MoreSettings' });
64
81
 
@@ -115,6 +132,23 @@ export const DesktopOptions = ({
115
132
  </Dropdown.Item>
116
133
  ) : null}
117
134
 
135
+ <Dropdown.Item
136
+ data-testid="closed_caption_admin"
137
+ onClick={() => {
138
+ updateState(MODALS.CAPTION, true);
139
+ }}
140
+ >
141
+ <OpenCaptionIcon />
142
+ <Flex direction="column" css={{ flexGrow: '1' }}>
143
+ <Text variant="sm" css={{ ml: '$4', color: '$on_surface_high' }}>
144
+ Closed Captions
145
+ </Text>
146
+ <Text variant="caption" css={{ ml: '$4', color: '$on_surface_medium' }}>
147
+ {isTranscriptionEnabled ? 'Enabled' : 'Disabled'}
148
+ </Text>
149
+ </Flex>
150
+ <Switch id="closed_caption_start_stop" checked={isTranscriptionEnabled} disabled={false} />
151
+ </Dropdown.Item>
118
152
  {screenType !== 'hls_live_streaming' ? (
119
153
  <Dropdown.Item css={{ p: 0, '&:empty': { display: 'none' } }}>
120
154
  <PIP
@@ -211,6 +245,9 @@ export const DesktopOptions = ({
211
245
  onOpenChange={(value: boolean) => updateState(MODALS.SELF_ROLE_CHANGE, value)}
212
246
  />
213
247
  )}
248
+ {openModals.has(MODALS.CAPTION) && (
249
+ <CaptionModal onOpenChange={(value: boolean) => updateState(MODALS.CAPTION, value)} />
250
+ )}
214
251
  {/* {openModals.has(MODALS.EMBED_URL) && (
215
252
  <EmbedUrlModal onOpenChange={value => updateState(MODALS.EMBED_URL, value)} />
216
253
  )} */}