@100mslive/roomkit-react 0.2.8-alpha.2 → 0.2.8-alpha.4

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 (62) hide show
  1. package/dist/{HLSView-CEPQ23TO.js → HLSView-UIPDGADR.js} +509 -234
  2. package/dist/HLSView-UIPDGADR.js.map +7 -0
  3. package/dist/Prebuilt/common/hooks.d.ts +3 -0
  4. package/dist/Prebuilt/components/HMSVideo/FullscreenButton.d.ts +5 -0
  5. package/dist/Prebuilt/components/HMSVideo/HLSAutoplayBlockedPrompt.d.ts +5 -0
  6. package/dist/Prebuilt/components/HMSVideo/HLSCaptionSelector.d.ts +1 -2
  7. package/dist/Prebuilt/components/HMSVideo/HLSQualitySelector.d.ts +12 -0
  8. package/dist/Prebuilt/components/HMSVideo/MwebHLSViewTitle.d.ts +2 -0
  9. package/dist/Prebuilt/components/HMSVideo/PlayButton.d.ts +6 -0
  10. package/dist/Prebuilt/components/HMSVideo/PlayerContext.d.ts +8 -0
  11. package/dist/Prebuilt/components/HMSVideo/VideoProgress.d.ts +2 -0
  12. package/dist/Prebuilt/components/HMSVideo/VideoTime.d.ts +2 -0
  13. package/dist/Prebuilt/components/HMSVideo/VolumeControl.d.ts +2 -0
  14. package/dist/Prebuilt/components/HMSVideo/index.d.ts +17 -0
  15. package/dist/Prebuilt/components/HMSVideo/utils.d.ts +9 -0
  16. package/dist/Prebuilt/components/Leave/MwebLeaveRoom.d.ts +1 -3
  17. package/dist/Prebuilt/components/MwebLandscapePrompt.d.ts +1 -1
  18. package/dist/Prebuilt/components/RaiseHand.d.ts +2 -0
  19. package/dist/Prebuilt/components/SidePaneTabs.d.ts +1 -0
  20. package/dist/{chunk-2Y4FJB25.js → chunk-J4NOQ2YL.js} +562 -450
  21. package/dist/chunk-J4NOQ2YL.js.map +7 -0
  22. package/dist/index.cjs.js +1556 -1145
  23. package/dist/index.cjs.js.map +4 -4
  24. package/dist/index.js +1 -1
  25. package/dist/meta.cjs.json +429 -156
  26. package/dist/meta.esbuild.json +445 -164
  27. package/package.json +6 -6
  28. package/src/Prebuilt/common/hooks.ts +21 -0
  29. package/src/Prebuilt/components/Chat/ChatFooter.tsx +26 -10
  30. package/src/Prebuilt/components/ConferenceScreen.tsx +34 -2
  31. package/src/Prebuilt/components/Footer/Footer.tsx +0 -1
  32. package/src/Prebuilt/components/HMSVideo/Controls.jsx +1 -1
  33. package/src/Prebuilt/components/HMSVideo/FullscreenButton.tsx +13 -0
  34. package/src/Prebuilt/components/HMSVideo/{HLSAutoplayBlockedPrompt.jsx → HLSAutoplayBlockedPrompt.tsx} +13 -6
  35. package/src/Prebuilt/components/HMSVideo/HLSCaptionSelector.tsx +4 -2
  36. package/src/Prebuilt/components/HMSVideo/HLSQualitySelector.tsx +241 -0
  37. package/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +3 -0
  38. package/src/Prebuilt/components/HMSVideo/MwebHLSViewTitle.tsx +91 -0
  39. package/src/Prebuilt/components/HMSVideo/PlayButton.tsx +27 -0
  40. package/src/Prebuilt/components/HMSVideo/PlayerContext.tsx +15 -0
  41. package/src/Prebuilt/components/HMSVideo/VideoProgress.tsx +81 -0
  42. package/src/Prebuilt/components/HMSVideo/VideoTime.tsx +42 -0
  43. package/src/Prebuilt/components/HMSVideo/{VolumeControl.jsx → VolumeControl.tsx} +7 -5
  44. package/src/Prebuilt/components/HMSVideo/{index.js → index.ts} +2 -0
  45. package/src/Prebuilt/components/HMSVideo/utils.ts +35 -0
  46. package/src/Prebuilt/components/Leave/LeaveRoom.tsx +7 -1
  47. package/src/Prebuilt/components/Leave/MwebLeaveRoom.tsx +38 -25
  48. package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +3 -1
  49. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +1 -1
  50. package/src/Prebuilt/components/MwebLandscapePrompt.tsx +5 -0
  51. package/src/Prebuilt/components/{RaiseHand.jsx → RaiseHand.tsx} +3 -2
  52. package/src/Prebuilt/components/SidePaneTabs.tsx +29 -10
  53. package/src/Prebuilt/layouts/HLSView.jsx +272 -156
  54. package/src/Prebuilt/layouts/SidePane.tsx +21 -10
  55. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +11 -1
  56. package/dist/HLSView-CEPQ23TO.js.map +0 -7
  57. package/dist/chunk-2Y4FJB25.js.map +0 -7
  58. package/src/Prebuilt/components/HMSVideo/FullscreenButton.jsx +0 -18
  59. package/src/Prebuilt/components/HMSVideo/HLSQualitySelector.jsx +0 -127
  60. package/src/Prebuilt/components/HMSVideo/HMSVIdeoUtils.js +0 -27
  61. package/src/Prebuilt/components/HMSVideo/VideoProgress.jsx +0 -76
  62. package/src/Prebuilt/components/HMSVideo/VideoTime.jsx +0 -33
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "prebuilt",
11
11
  "roomkit"
12
12
  ],
13
- "version": "0.2.8-alpha.2",
13
+ "version": "0.2.8-alpha.4",
14
14
  "author": "100ms",
15
15
  "license": "MIT",
16
16
  "repository": {
@@ -82,11 +82,11 @@
82
82
  "react": ">=17.0.2 <19.0.0"
83
83
  },
84
84
  "dependencies": {
85
- "@100mslive/hls-player": "0.2.8-alpha.2",
85
+ "@100mslive/hls-player": "0.2.8-alpha.4",
86
86
  "@100mslive/hms-noise-cancellation": "0.0.0-alpha.1",
87
- "@100mslive/hms-virtual-background": "1.12.8-alpha.2",
88
- "@100mslive/react-icons": "0.9.8-alpha.2",
89
- "@100mslive/react-sdk": "0.9.8-alpha.2",
87
+ "@100mslive/hms-virtual-background": "1.12.8-alpha.4",
88
+ "@100mslive/react-icons": "0.9.8-alpha.4",
89
+ "@100mslive/react-sdk": "0.9.8-alpha.4",
90
90
  "@100mslive/types-prebuilt": "0.12.7",
91
91
  "@emoji-mart/data": "^1.0.6",
92
92
  "@emoji-mart/react": "^1.0.1",
@@ -121,5 +121,5 @@
121
121
  "uuid": "^8.3.2",
122
122
  "worker-timers": "^7.0.40"
123
123
  },
124
- "gitHead": "ae7569036b49c02d22414e5de343119c48b4aa91"
124
+ "gitHead": "ec046697fe2d6a12f4be23b04f217cf38e63af34"
125
125
  }
@@ -1,6 +1,8 @@
1
1
  import { useEffect, useRef, useState } from 'react';
2
+ import { useMedia } from 'react-use';
2
3
  import { JoinForm_JoinBtnType } from '@100mslive/types-prebuilt/elements/join_form';
3
4
  import {
5
+ parsedUserAgent,
4
6
  selectAvailableRoleNames,
5
7
  selectIsConnectedToRoom,
6
8
  selectPeerCount,
@@ -10,6 +12,7 @@ import {
10
12
  useHMSStore,
11
13
  useHMSVanillaStore,
12
14
  } from '@100mslive/react-sdk';
15
+ import { config } from '../../Theme';
13
16
  import { useRoomLayout } from '../provider/roomLayoutProvider';
14
17
  import { useRoomLayoutConferencingScreen } from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
15
18
  import { CHAT_SELECTOR } from './constants';
@@ -100,3 +103,21 @@ export const useParticipants = (params?: { metadata?: { isHandRaised?: boolean }
100
103
  }
101
104
  return { participants: participantList, isConnected, peerCount, rolesWithParticipants };
102
105
  };
106
+
107
+ export const useIsLandscape = () => {
108
+ const isMobile = parsedUserAgent.getDevice().type === 'mobile';
109
+ const isLandscape = useMedia(config.media.ls);
110
+ return isMobile && isLandscape;
111
+ };
112
+
113
+ export const useLandscapeHLSStream = () => {
114
+ const isLandscape = useIsLandscape();
115
+ const { screenType } = useRoomLayoutConferencingScreen();
116
+ return isLandscape && screenType === 'hls_live_streaming';
117
+ };
118
+
119
+ export const useMobileHLSStream = () => {
120
+ const isMobile = useMedia(config.media.md);
121
+ const { screenType } = useRoomLayoutConferencingScreen();
122
+ return isMobile && screenType === 'hls_live_streaming';
123
+ };
@@ -2,10 +2,12 @@ import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'reac
2
2
  import { useMedia } from 'react-use';
3
3
  import data from '@emoji-mart/data';
4
4
  import Picker from '@emoji-mart/react';
5
- import { HMSException, selectLocalPeer, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
5
+ import { HMSException, selectLocalPeer, useAVToggle, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
6
6
  import { EmojiIcon, PauseCircleIcon, SendIcon, VerticalMenuIcon } from '@100mslive/react-icons';
7
7
  import { Box, config as cssConfig, Flex, IconButton as BaseIconButton, Popover, styled, Text } from '../../..';
8
8
  import { IconButton } from '../../../IconButton';
9
+ import { MoreSettings } from '../MoreSettings/MoreSettings';
10
+ import { RaiseHand } from '../RaiseHand';
9
11
  // @ts-ignore
10
12
  import { ToastManager } from '../Toast/ToastManager';
11
13
  import { ChatSelectorContainer } from './ChatSelectorContainer';
@@ -17,7 +19,7 @@ import { useSetSubscribedChatSelector, useSubscribeChatSelector } from '../AppDa
17
19
  import { useIsPeerBlacklisted } from '../hooks/useChatBlacklist';
18
20
  // @ts-ignore
19
21
  import { useEmojiPickerStyles } from './useEmojiPickerStyles';
20
- import { useDefaultChatSelection } from '../../common/hooks';
22
+ import { useDefaultChatSelection, useLandscapeHLSStream, useMobileHLSStream } from '../../common/hooks';
21
23
  import { CHAT_SELECTOR, SESSION_STORE_KEY } from '../../common/constants';
22
24
 
23
25
  const TextArea = styled('textarea', {
@@ -77,7 +79,7 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
77
79
  const inputRef = useRef<HTMLTextAreaElement>(null);
78
80
  const [draftMessage, setDraftMessage] = useChatDraftMessage();
79
81
  const isMobile = useMedia(cssConfig.media.md);
80
- const { elements } = useRoomLayoutConferencingScreen();
82
+ const { elements, screenType } = useRoomLayoutConferencingScreen();
81
83
  const message_placeholder = elements?.chat?.message_placeholder || 'Send a message';
82
84
  const localPeer = useHMSStore(selectLocalPeer);
83
85
  const isOverlayChat = elements?.chat?.is_overlay;
@@ -87,18 +89,21 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
87
89
  const defaultSelection = useDefaultChatSelection();
88
90
  const selection = selectedPeer.name || selectedRole || defaultSelection;
89
91
  const isLocalPeerBlacklisted = useIsPeerBlacklisted({ local: true });
92
+ const { toggleAudio, toggleVideo } = useAVToggle();
93
+ const noAVPermissions = !(toggleAudio || toggleVideo);
94
+ const isMwebHLSStream = useMobileHLSStream();
95
+ const isLandscapeHLSStream = useLandscapeHLSStream();
90
96
 
91
97
  useEffect(() => {
92
98
  if (!selectedPeer.id && !selectedRole && !['Everyone', ''].includes(defaultSelection)) {
93
99
  setRoleSelector(defaultSelection);
94
100
  } else {
95
101
  // @ts-ignore
96
- if (!elements?.chat?.disable_autofocus) {
102
+ if (!(isMobile || isLandscapeHLSStream) || !elements?.chat?.disable_autofocus) {
97
103
  inputRef.current?.focus();
98
104
  }
99
105
  }
100
- // @ts-ignore
101
- }, [defaultSelection, elements?.chat?.disable_autofocus, selectedPeer, selectedRole, setRoleSelector]);
106
+ }, [defaultSelection, selectedPeer, selectedRole, setRoleSelector, isMobile, isLandscapeHLSStream, elements?.chat]);
102
107
  const sendMessage = useCallback(async () => {
103
108
  const message = inputRef?.current?.value;
104
109
  if (!message || !message.trim().length) {
@@ -197,18 +202,17 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
197
202
  align="center"
198
203
  css={{
199
204
  bg: isOverlayChat && isMobile ? '$surface_dim' : '$surface_default',
200
- minHeight: '$16',
201
205
  maxHeight: '$24',
202
206
  position: 'relative',
203
- py: '$6',
204
207
  pl: '$8',
205
- flexGrow: 1,
208
+ flexGrow: '1',
206
209
  r: '$1',
207
210
  '@md': {
208
211
  minHeight: 'unset',
209
212
  h: '$14',
210
213
  boxSizing: 'border-box',
211
214
  },
215
+ ...(isLandscapeHLSStream ? { minHeight: '$14', py: 0 } : {}),
212
216
  }}
213
217
  >
214
218
  {children}
@@ -223,6 +227,7 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
223
227
  placeholder={message_placeholder}
224
228
  ref={inputRef}
225
229
  required
230
+ autoFocus={!(isMobile || isLandscapeHLSStream)}
226
231
  onKeyPress={async event => {
227
232
  if (event.key === 'Enter') {
228
233
  if (!event.shiftKey) {
@@ -237,7 +242,7 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
237
242
  onCut={e => e.stopPropagation()}
238
243
  onCopy={e => e.stopPropagation()}
239
244
  />
240
- {!isMobile ? (
245
+ {!isMobile && !isLandscapeHLSStream ? (
241
246
  <EmojiPicker
242
247
  onSelect={emoji => {
243
248
  if (inputRef.current) {
@@ -260,6 +265,17 @@ export const ChatFooter = ({ onSend, children }: { onSend: (count: number) => vo
260
265
  <SendIcon />
261
266
  </BaseIconButton>
262
267
  </Flex>
268
+ {(isMwebHLSStream || isLandscapeHLSStream) && (
269
+ <Flex
270
+ css={{
271
+ alignItems: 'center',
272
+ }}
273
+ gap="1"
274
+ >
275
+ {noAVPermissions ? <RaiseHand /> : null}
276
+ <MoreSettings elements={elements} screenType={screenType} />
277
+ </Flex>
278
+ )}
263
279
  </Flex>
264
280
  )}
265
281
  </Box>
@@ -6,10 +6,13 @@ import {
6
6
  selectAppData,
7
7
  selectIsConnectedToRoom,
8
8
  selectRoomState,
9
+ useAVToggle,
9
10
  useHMSActions,
10
11
  useHMSStore,
11
12
  } from '@100mslive/react-sdk';
12
13
  import { Footer } from './Footer/Footer';
14
+ import { LeaveRoom } from './Leave/LeaveRoom';
15
+ import { MoreSettings } from './MoreSettings/MoreSettings';
13
16
  import { HLSFailureModal } from './Notifications/HLSFailureModal';
14
17
  // @ts-ignore: No implicit Any
15
18
  import { ActivatedPIP } from './PIP/PIPComponent';
@@ -23,12 +26,14 @@ import { VideoStreamingSection } from '../layouts/VideoStreamingSection';
23
26
  import FullPageProgress from './FullPageProgress';
24
27
  import { Header } from './Header';
25
28
  import { PreviousRoleInMetadata } from './PreviousRoleInMetadata';
29
+ import { RaiseHand } from './RaiseHand';
26
30
  import {
27
31
  useRoomLayoutConferencingScreen,
28
32
  useRoomLayoutPreviewScreen,
29
33
  } from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
30
34
  // @ts-ignore: No implicit Any
31
35
  import { useAuthToken, useSetAppDataByKey } from './AppData/useUISettings';
36
+ import { useLandscapeHLSStream, useMobileHLSStream } from '../common/hooks';
32
37
  // @ts-ignore: No implicit Any
33
38
  import { APP_DATA, isAndroid, isIOS, isIPadOS } from '../common/constants';
34
39
 
@@ -47,12 +52,20 @@ export const ConferenceScreen = () => {
47
52
  const isMobileDevice = isAndroid || isIOS || isIPadOS;
48
53
  const dropdownListRef = useRef<string[]>();
49
54
  const [isHLSStarted] = useSetAppDataByKey(APP_DATA.hlsStarted);
55
+
56
+ const { toggleAudio, toggleVideo } = useAVToggle();
57
+ const noAVPermissions = !(toggleAudio || toggleVideo);
58
+ // using it in hls stream to show action button when chat is disabled
59
+ const showChat = !!screenProps.elements?.chat;
50
60
  const toggleControls = () => {
51
61
  if (dropdownListRef.current?.length === 0 && isMobileDevice) {
52
62
  setHideControls(value => !value);
53
63
  }
54
64
  };
55
65
  const autoRoomJoined = useRef(isPreviewScreenEnabled);
66
+ const isMobileHLSStream = useMobileHLSStream();
67
+ const isLandscapeHLSStream = useLandscapeHLSStream();
68
+ const isMwebHLSStream = isMobileHLSStream || isLandscapeHLSStream;
56
69
 
57
70
  useEffect(() => {
58
71
  let timeout: undefined | ReturnType<typeof setTimeout>;
@@ -113,7 +126,7 @@ export const ConferenceScreen = () => {
113
126
  </Box>
114
127
  ) : null}
115
128
  <Flex css={{ size: '100%', overflow: 'hidden' }} direction="column">
116
- {!screenProps.hideSections.includes('header') && (
129
+ {!(screenProps.hideSections.includes('header') || isMwebHLSStream) && (
117
130
  <Box
118
131
  ref={headerRef}
119
132
  css={{
@@ -129,6 +142,11 @@ export const ConferenceScreen = () => {
129
142
  <Header />
130
143
  </Box>
131
144
  )}
145
+ {isMwebHLSStream && (
146
+ <Flex align="center" gap="2" css={{ position: 'absolute', left: '$4', top: '$4', zIndex: 1 }}>
147
+ <LeaveRoom screenType={screenProps.screenType} />
148
+ </Flex>
149
+ )}
132
150
  <Box
133
151
  css={{
134
152
  w: '100%',
@@ -155,7 +173,7 @@ export const ConferenceScreen = () => {
155
173
  />
156
174
  ) : null}
157
175
  </Box>
158
- {!screenProps.hideSections.includes('footer') && screenProps.elements && (
176
+ {!screenProps.hideSections.includes('footer') && screenProps.elements && !isMwebHLSStream && (
159
177
  <Box
160
178
  ref={footerRef}
161
179
  css={{
@@ -174,6 +192,20 @@ export const ConferenceScreen = () => {
174
192
  <Footer elements={screenProps.elements} screenType={screenProps.screenType} />
175
193
  </Box>
176
194
  )}
195
+ {isMwebHLSStream && !showChat && (
196
+ <Flex
197
+ css={{
198
+ alignItems: 'center',
199
+ pr: '$4',
200
+ pb: '$4',
201
+ }}
202
+ justify="end"
203
+ gap="1"
204
+ >
205
+ {noAVPermissions ? <RaiseHand /> : null}
206
+ <MoreSettings elements={screenProps.elements} screenType={screenProps.screenType} />
207
+ </Flex>
208
+ )}
177
209
  <RoleChangeRequestModal />
178
210
  <HLSFailureModal />
179
211
  <ActivatedPIP />
@@ -12,7 +12,6 @@ import { EmojiReaction } from '../EmojiReaction';
12
12
  import { LeaveRoom } from '../Leave/LeaveRoom';
13
13
  // @ts-ignore: No implicit Any
14
14
  import { MoreSettings } from '../MoreSettings/MoreSettings';
15
- // @ts-ignore: No implicit Any
16
15
  import { RaiseHand } from '../RaiseHand';
17
16
  // @ts-ignore: No implicit Any
18
17
  import { ScreenshareToggle } from '../ScreenShareToggle';
@@ -1,4 +1,4 @@
1
- import { Flex, styled } from '../../../';
1
+ import { Flex, styled } from '../../..';
2
2
 
3
3
  export const VideoControls = styled(Flex, {
4
4
  justifyContent: 'center',
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { ExpandIcon, ShrinkIcon } from '@100mslive/react-icons';
3
+ import { Flex, IconButton, Tooltip } from '../../..';
4
+
5
+ export const FullScreenButton = ({ isFullScreen, onToggle }: { isFullScreen: boolean; onToggle: () => void }) => {
6
+ return (
7
+ <Tooltip title={`${isFullScreen ? 'Exit' : 'Go'} fullscreen`} side="top">
8
+ <IconButton css={{ margin: '0px' }} onClick={onToggle} key="fullscreen_btn" data-testid="fullscreen_btn">
9
+ <Flex>{isFullScreen ? <ShrinkIcon /> : <ExpandIcon />}</Flex>
10
+ </IconButton>
11
+ </Tooltip>
12
+ );
13
+ };
@@ -1,14 +1,21 @@
1
1
  import React from 'react';
2
- import { Button, Dialog, Text } from '../../../';
2
+ import { Button, Dialog, Text } from '../../..';
3
+ // @ts-ignore
3
4
  import { DialogContent, DialogRow } from '../../primitives/DialogContent';
4
5
 
5
- export function HLSAutoplayBlockedPrompt({ open, unblockAutoPlay }) {
6
+ export function HLSAutoplayBlockedPrompt({
7
+ open,
8
+ unblockAutoPlay,
9
+ }: {
10
+ open: boolean;
11
+ unblockAutoPlay: () => Promise<void>;
12
+ }) {
6
13
  return (
7
14
  <Dialog.Root
8
15
  open={open}
9
- onOpenChange={value => {
16
+ onOpenChange={async value => {
10
17
  if (!value) {
11
- unblockAutoPlay();
18
+ await unblockAutoPlay();
12
19
  }
13
20
  }}
14
21
  >
@@ -22,8 +29,8 @@ export function HLSAutoplayBlockedPrompt({ open, unblockAutoPlay }) {
22
29
  <DialogRow justify="end">
23
30
  <Button
24
31
  variant="primary"
25
- onClick={() => {
26
- unblockAutoPlay();
32
+ onClick={async () => {
33
+ await unblockAutoPlay();
27
34
  }}
28
35
  >
29
36
  Play stream
@@ -1,11 +1,13 @@
1
1
  import React from 'react';
2
2
  import { ClosedCaptionIcon, OpenCaptionIcon } from '@100mslive/react-icons';
3
3
  import { IconButton, Tooltip } from '../../../';
4
+ import { useHMSPlayerContext } from './PlayerContext';
4
5
 
5
- export function HLSCaptionSelector({ isEnabled, onClick }: { isEnabled: boolean; onClick: () => void }) {
6
+ export function HLSCaptionSelector({ isEnabled }: { isEnabled: boolean }) {
7
+ const { hlsPlayer } = useHMSPlayerContext();
6
8
  return (
7
9
  <Tooltip title="Subtitles/closed captions" side="top">
8
- <IconButton css={{ p: '$2' }} onClick={() => onClick()}>
10
+ <IconButton css={{ p: '$2' }} onClick={() => hlsPlayer?.toggleCaption()}>
9
11
  {isEnabled ? <ClosedCaptionIcon width="20" height="20px" /> : <OpenCaptionIcon width="20" height="20px" />}
10
12
  </IconButton>
11
13
  </Tooltip>
@@ -0,0 +1,241 @@
1
+ import React from 'react';
2
+ import { useMedia } from 'react-use';
3
+ import { HMSHLSLayer } from '@100mslive/hls-player';
4
+ import { CheckIcon, CrossIcon, SettingsIcon } from '@100mslive/react-icons';
5
+ import { Box, Dropdown, Flex, Text, Tooltip } from '../../..';
6
+ import { Sheet } from '../../../Sheet';
7
+ import { config } from '../../../Theme';
8
+ import { useIsLandscape } from '../../common/hooks';
9
+
10
+ export function HLSQualitySelector({
11
+ open,
12
+ onOpenChange,
13
+ layers,
14
+ onQualityChange,
15
+ selection,
16
+ isAuto,
17
+ }: {
18
+ open: boolean;
19
+ onOpenChange: (value: boolean) => void;
20
+ layers: HMSHLSLayer[];
21
+ onQualityChange: (quality: { [key: string]: string | number } | HMSHLSLayer) => void;
22
+ selection: HMSHLSLayer;
23
+ isAuto: boolean;
24
+ }) {
25
+ const isMobile = useMedia(config.media.md);
26
+ const isLandscape = useIsLandscape();
27
+ if (isMobile || isLandscape) {
28
+ return (
29
+ <Sheet.Root open={open} onOpenChange={onOpenChange}>
30
+ <Sheet.Trigger asChild data-testid="quality_selector">
31
+ <Flex
32
+ css={{
33
+ color: '$on_primary_high',
34
+ r: '$1',
35
+ cursor: 'pointer',
36
+ p: '$2',
37
+ }}
38
+ >
39
+ <SettingsIcon />
40
+ </Flex>
41
+ </Sheet.Trigger>
42
+
43
+ {layers.length > 0 && (
44
+ <Sheet.Content css={{ bg: '$surface_default', pb: '$1' }} onClick={() => onOpenChange(false)}>
45
+ <Sheet.Title
46
+ css={{
47
+ display: 'flex',
48
+ color: '$on_surface_high',
49
+ w: '100%',
50
+ justifyContent: 'space-between',
51
+ mt: '$8',
52
+ fontSize: '$md',
53
+ px: '$10',
54
+ pb: '$8',
55
+ borderBottom: '1px solid $border_bright',
56
+ alignItems: 'center',
57
+ }}
58
+ >
59
+ Quality
60
+ <Sheet.Close css={{ color: '$on_surface_high' }} onClick={() => onOpenChange(false)}>
61
+ <CrossIcon />
62
+ </Sheet.Close>
63
+ </Sheet.Title>
64
+ {layers.map(layer => {
65
+ return (
66
+ <Flex
67
+ align="center"
68
+ css={{
69
+ w: '100%',
70
+ bg: '$surface_default',
71
+ '&:hover': {
72
+ bg: '$surface_brighter',
73
+ },
74
+ cursor: 'pointer',
75
+ gap: '$4',
76
+ py: '$8',
77
+ px: '$10',
78
+ }}
79
+ key={layer.width}
80
+ onClick={() => onQualityChange(layer)}
81
+ >
82
+ <Text variant="caption" css={{ fontWeight: '$semiBold' }}>
83
+ {getQualityText(layer)}
84
+ </Text>
85
+ <Text variant="caption" css={{ flex: '1 1 0', c: '$on_surface_low', pl: '$2' }}>
86
+ {getBitrateText(layer)}
87
+ </Text>
88
+ {!isAuto && layer.width === selection?.width && layer.height === selection?.height && (
89
+ <CheckIcon width="16px" height="16px" />
90
+ )}
91
+ </Flex>
92
+ );
93
+ })}
94
+ <Flex
95
+ align="center"
96
+ css={{
97
+ w: '100%',
98
+ bg: '$surface_default',
99
+ '&:hover': {
100
+ bg: '$surface_brighter',
101
+ },
102
+ cursor: 'pointer',
103
+ gap: '$4',
104
+ py: '$8',
105
+ px: '$10',
106
+ }}
107
+ key="auto"
108
+ onClick={() => onQualityChange({ height: 'auto' })}
109
+ >
110
+ <Text variant="caption" css={{ fontWeight: '$semiBold', flex: '1 1 0' }}>
111
+ Auto
112
+ </Text>
113
+ {isAuto && <CheckIcon width="16px" height="16px" />}
114
+ </Flex>
115
+ </Sheet.Content>
116
+ )}
117
+ </Sheet.Root>
118
+ );
119
+ }
120
+ return (
121
+ <Dropdown.Root open={open} onOpenChange={value => onOpenChange(value)}>
122
+ <Dropdown.Trigger asChild data-testid="quality_selector">
123
+ <Flex
124
+ css={{
125
+ color: '$on_primary_high',
126
+ r: '$1',
127
+ cursor: 'pointer',
128
+ p: '$2',
129
+ }}
130
+ >
131
+ <Tooltip title="Select Quality" side="top">
132
+ <Flex align="center">
133
+ <Box
134
+ css={{
135
+ w: '$9',
136
+ h: '$9',
137
+ display: 'inline-flex',
138
+ alignItems: 'center',
139
+ c: '$on_surface_high',
140
+ }}
141
+ >
142
+ <SettingsIcon />
143
+ </Box>
144
+ <Text
145
+ variant={{
146
+ '@md': 'sm',
147
+ '@sm': 'xs',
148
+ '@xs': 'tiny',
149
+ }}
150
+ css={{ display: 'flex', alignItems: 'center', ml: '$2', c: '$on_surface_medium' }}
151
+ >
152
+ {isAuto && (
153
+ <>
154
+ Auto
155
+ <Box
156
+ css={{
157
+ mx: '$2',
158
+ w: '$2',
159
+ h: '$2',
160
+ background: '$on_surface_medium',
161
+ r: '$1',
162
+ }}
163
+ />
164
+ </>
165
+ )}
166
+ {selection && Math.min(selection.width || 0, selection.height || 0)}p
167
+ </Text>
168
+ </Flex>
169
+ </Tooltip>
170
+ </Flex>
171
+ </Dropdown.Trigger>
172
+ {layers.length > 0 && (
173
+ <Dropdown.Content
174
+ sideOffset={5}
175
+ align="end"
176
+ css={{
177
+ height: 'auto',
178
+ maxHeight: '$52',
179
+ w: '$40',
180
+ bg: '$surface_bright',
181
+ py: '$4',
182
+ gap: '$4',
183
+ display: 'grid',
184
+ }}
185
+ >
186
+ {layers.map(layer => {
187
+ return (
188
+ <Dropdown.Item
189
+ onClick={() => onQualityChange(layer)}
190
+ key={layer.width}
191
+ css={{
192
+ bg:
193
+ !isAuto && layer.width === selection?.width && layer.height === selection?.height
194
+ ? '$surface_default'
195
+ : '$surface_bright',
196
+ '&:hover': {
197
+ bg: '$surface_brighter',
198
+ },
199
+ p: '$2 $4 $2 $8',
200
+ h: '$12',
201
+ gap: '$2',
202
+ }}
203
+ >
204
+ <Text variant="caption" css={{ fontWeight: '$semiBold' }}>
205
+ {getQualityText(layer)}
206
+ </Text>
207
+ <Text variant="caption" css={{ flex: '1 1 0', c: '$on_surface_low', pl: '$2' }}>
208
+ {getBitrateText(layer)}
209
+ </Text>
210
+ {!isAuto && layer.width === selection?.width && layer.height === selection?.height && (
211
+ <CheckIcon width="16px" height="16px" />
212
+ )}
213
+ </Dropdown.Item>
214
+ );
215
+ })}
216
+ <Dropdown.Item
217
+ onClick={() => onQualityChange({ height: 'auto' })}
218
+ key="auto"
219
+ css={{
220
+ bg: !isAuto ? '$surface_bright' : '$surface_default',
221
+ '&:hover': {
222
+ bg: '$surface_brighter',
223
+ },
224
+ p: '$2 $4 $2 $8',
225
+ h: '$12',
226
+ gap: '$2',
227
+ }}
228
+ >
229
+ <Text variant="caption" css={{ fontWeight: '$semiBold', flex: '1 1 0' }}>
230
+ Auto
231
+ </Text>
232
+ {isAuto && <CheckIcon width="16px" height="16px" />}
233
+ </Dropdown.Item>
234
+ </Dropdown.Content>
235
+ )}
236
+ </Dropdown.Root>
237
+ );
238
+ }
239
+
240
+ const getQualityText = (layer: HMSHLSLayer) => `${Math.min(layer.height || 0, layer.width || 0)}p `;
241
+ const getBitrateText = (layer: HMSHLSLayer) => `(${(Number(layer.bitrate / 1000) / 1000).toFixed(2)} Mbps)`;
@@ -20,6 +20,7 @@ export const HMSVideo = forwardRef(({ children, ...props }, videoRef) => {
20
20
  },
21
21
  '& video::-webkit-media-text-track-display': {
22
22
  padding: '0 $4',
23
+ boxShadow: '0px 1px 3px 0px #000000A3',
23
24
  },
24
25
  '& video::-webkit-media-text-track-container': {
25
26
  fontSize: '$space$10 !important',
@@ -33,6 +34,8 @@ export const HMSVideo = forwardRef(({ children, ...props }, videoRef) => {
33
34
  flex: '1 1 0',
34
35
  margin: '0 auto',
35
36
  minHeight: '0',
37
+ objectFit: 'contain',
38
+ width: 'inherit',
36
39
  }}
37
40
  ref={videoRef}
38
41
  playsInline