@100mslive/roomkit-react 0.2.8-alpha.3 → 0.2.8-alpha.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. package/dist/{HLSView-PACDZWJN.js → HLSView-CVNJNDUQ.js} +509 -234
  2. package/dist/HLSView-CVNJNDUQ.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-2QHBD2VO.js → chunk-25HZFDG5.js} +562 -450
  21. package/dist/chunk-25HZFDG5.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-PACDZWJN.js.map +0 -7
  57. package/dist/chunk-2QHBD2VO.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.3",
13
+ "version": "0.2.8-alpha.5",
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.3",
85
+ "@100mslive/hls-player": "0.2.8-alpha.5",
86
86
  "@100mslive/hms-noise-cancellation": "0.0.0-alpha.1",
87
- "@100mslive/hms-virtual-background": "1.12.8-alpha.3",
88
- "@100mslive/react-icons": "0.9.8-alpha.3",
89
- "@100mslive/react-sdk": "0.9.8-alpha.3",
87
+ "@100mslive/hms-virtual-background": "1.12.8-alpha.5",
88
+ "@100mslive/react-icons": "0.9.8-alpha.5",
89
+ "@100mslive/react-sdk": "0.9.8-alpha.5",
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": "74f622b36302db0279d3b0eb4c87278ae8fdf9db"
124
+ "gitHead": "6ba28232c0bb10f4ba990777369c5c5b132203b1"
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