@100mslive/roomkit-react 0.3.10 → 0.3.11-alpha.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 (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
  )} */}