@100mslive/roomkit-react 0.2.8-alpha.1 → 0.2.8-alpha.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. package/dist/HLSView-EDROW5VZ.js +1411 -0
  2. package/dist/HLSView-EDROW5VZ.js.map +7 -0
  3. package/dist/Prebuilt/common/hooks.d.ts +3 -0
  4. package/dist/Prebuilt/components/Chat/MwebChatOption.d.ts +1 -1
  5. package/dist/Prebuilt/components/HMSVideo/FullscreenButton.d.ts +5 -0
  6. package/dist/Prebuilt/components/HMSVideo/HLSAutoplayBlockedPrompt.d.ts +5 -0
  7. package/dist/Prebuilt/components/HMSVideo/HLSCaptionSelector.d.ts +1 -2
  8. package/dist/Prebuilt/components/HMSVideo/HLSQualitySelector.d.ts +13 -0
  9. package/dist/Prebuilt/components/HMSVideo/MwebHLSViewTitle.d.ts +2 -0
  10. package/dist/Prebuilt/components/HMSVideo/PlayButton.d.ts +6 -0
  11. package/dist/Prebuilt/components/HMSVideo/PlayPauseButton.d.ts +6 -0
  12. package/dist/Prebuilt/components/HMSVideo/PlayerContext.d.ts +8 -0
  13. package/dist/Prebuilt/components/HMSVideo/SeekControls.d.ts +7 -0
  14. package/dist/Prebuilt/components/HMSVideo/VideoProgress.d.ts +5 -0
  15. package/dist/Prebuilt/components/HMSVideo/VideoTime.d.ts +2 -0
  16. package/dist/Prebuilt/components/HMSVideo/VolumeControl.d.ts +2 -0
  17. package/dist/Prebuilt/components/HMSVideo/index.d.ts +26 -0
  18. package/dist/Prebuilt/components/HMSVideo/utils.d.ts +8 -0
  19. package/dist/Prebuilt/components/Leave/DesktopLeaveRoom.d.ts +2 -1
  20. package/dist/Prebuilt/components/Leave/LeaveRoom.d.ts +2 -1
  21. package/dist/Prebuilt/components/Leave/MwebLeaveRoom.d.ts +2 -3
  22. package/dist/Prebuilt/components/MwebLandscapePrompt.d.ts +1 -1
  23. package/dist/Prebuilt/components/RaiseHand.d.ts +5 -0
  24. package/dist/Prebuilt/components/SidePaneTabs.d.ts +1 -1
  25. package/dist/Sheet/Sheet.d.ts +1 -0
  26. package/dist/{chunk-ERIM35YN.js → chunk-YFJQ4B6X.js} +1544 -1174
  27. package/dist/chunk-YFJQ4B6X.js.map +7 -0
  28. package/dist/index.cjs.js +2727 -1899
  29. package/dist/index.cjs.js.map +4 -4
  30. package/dist/index.js +1 -1
  31. package/dist/meta.cjs.json +777 -290
  32. package/dist/meta.esbuild.json +798 -299
  33. package/package.json +7 -6
  34. package/src/Button/Button.tsx +4 -4
  35. package/src/Fieldset/Fieldset.tsx +1 -1
  36. package/src/Input/PasswordInput.stories.tsx +1 -1
  37. package/src/Pagination/StyledPagination.stories.tsx +2 -2
  38. package/src/Prebuilt/IconButton.tsx +1 -1
  39. package/src/Prebuilt/common/hooks.ts +21 -0
  40. package/src/Prebuilt/components/AppData/useSidepane.js +34 -7
  41. package/src/Prebuilt/components/AuthToken.jsx +1 -1
  42. package/src/Prebuilt/components/Chat/Chat.tsx +41 -1
  43. package/src/Prebuilt/components/Chat/ChatFooter.tsx +33 -13
  44. package/src/Prebuilt/components/Chat/MwebChatOption.tsx +1 -1
  45. package/src/Prebuilt/components/ConferenceScreen.tsx +48 -7
  46. package/src/Prebuilt/components/EmojiReaction.jsx +33 -23
  47. package/src/Prebuilt/components/Footer/Footer.tsx +0 -1
  48. package/src/Prebuilt/components/Footer/RoleOptions.tsx +141 -125
  49. package/src/Prebuilt/components/HMSVideo/Controls.jsx +1 -1
  50. package/src/Prebuilt/components/HMSVideo/FullscreenButton.tsx +13 -0
  51. package/src/Prebuilt/components/HMSVideo/HLSAutoplayBlockedPrompt.tsx +72 -0
  52. package/src/Prebuilt/components/HMSVideo/HLSCaptionSelector.tsx +4 -2
  53. package/src/Prebuilt/components/HMSVideo/HLSQualitySelector.tsx +248 -0
  54. package/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +18 -7
  55. package/src/Prebuilt/components/HMSVideo/MwebHLSViewTitle.tsx +84 -0
  56. package/src/Prebuilt/components/HMSVideo/PlayButton.tsx +27 -0
  57. package/src/Prebuilt/components/HMSVideo/PlayPauseButton.tsx +27 -0
  58. package/src/Prebuilt/components/HMSVideo/PlayerContext.tsx +15 -0
  59. package/src/Prebuilt/components/HMSVideo/SeekControls.tsx +22 -0
  60. package/src/Prebuilt/components/HMSVideo/VideoProgress.tsx +95 -0
  61. package/src/Prebuilt/components/HMSVideo/VideoTime.tsx +43 -0
  62. package/src/Prebuilt/components/HMSVideo/{VolumeControl.jsx → VolumeControl.tsx} +6 -4
  63. package/src/Prebuilt/components/HMSVideo/{index.js → index.ts} +6 -2
  64. package/src/Prebuilt/components/HMSVideo/{HMSVIdeoUtils.js → utils.ts} +5 -5
  65. package/src/Prebuilt/components/Header/StreamActions.tsx +1 -1
  66. package/src/Prebuilt/components/IconButtonWithOptions/IconButtonWithOptions.tsx +1 -1
  67. package/src/Prebuilt/components/Leave/DesktopLeaveRoom.tsx +50 -46
  68. package/src/Prebuilt/components/Leave/LeaveRoom.tsx +15 -4
  69. package/src/Prebuilt/components/Leave/MwebLeaveRoom.tsx +46 -27
  70. package/src/Prebuilt/components/MoreSettings/MoreSettings.tsx +3 -1
  71. package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +37 -31
  72. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +12 -8
  73. package/src/Prebuilt/components/MwebLandscapePrompt.tsx +14 -3
  74. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +5 -2
  75. package/src/Prebuilt/components/Notifications/PeerNotifications.tsx +1 -1
  76. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +19 -8
  77. package/src/Prebuilt/components/Polls/Voting/Voting.tsx +3 -2
  78. package/src/Prebuilt/components/Polls/common/OptionInputWithDelete.tsx +1 -1
  79. package/src/Prebuilt/components/Polls/common/utils.ts +2 -2
  80. package/src/Prebuilt/components/RaiseHand.tsx +24 -0
  81. package/src/Prebuilt/components/RoomDetails/RoomDetailsPane.tsx +41 -14
  82. package/src/Prebuilt/components/SidePaneTabs.tsx +56 -48
  83. package/src/Prebuilt/components/StatsForNerds.jsx +14 -6
  84. package/src/Prebuilt/components/Streaming/Common.jsx +1 -1
  85. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +2 -2
  86. package/src/Prebuilt/components/Toast/ToastBatcher.js +8 -1
  87. package/src/Prebuilt/components/Toast/ToastConfig.jsx +17 -0
  88. package/src/Prebuilt/components/pdfAnnotator/shareScreenOptions.jsx +2 -2
  89. package/src/Prebuilt/components/pdfAnnotator/uploadedFile.jsx +1 -1
  90. package/src/Prebuilt/layouts/HLSView.jsx +379 -179
  91. package/src/Prebuilt/layouts/SidePane.tsx +145 -59
  92. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +22 -2
  93. package/src/Prebuilt/primitives/DialogContent.jsx +1 -1
  94. package/src/Prebuilt/provider/roomLayoutProvider/index.tsx +1 -1
  95. package/src/Sheet/Sheet.tsx +7 -3
  96. package/dist/HLSView-SJCF34GE.js +0 -987
  97. package/dist/HLSView-SJCF34GE.js.map +0 -7
  98. package/dist/chunk-ERIM35YN.js.map +0 -7
  99. package/src/Prebuilt/components/HMSVideo/FullscreenButton.jsx +0 -18
  100. package/src/Prebuilt/components/HMSVideo/HLSAutoplayBlockedPrompt.jsx +0 -35
  101. package/src/Prebuilt/components/HMSVideo/HLSQualitySelector.jsx +0 -127
  102. package/src/Prebuilt/components/HMSVideo/PlayButton.jsx +0 -13
  103. package/src/Prebuilt/components/HMSVideo/VideoProgress.jsx +0 -76
  104. package/src/Prebuilt/components/HMSVideo/VideoTime.jsx +0 -33
  105. package/src/Prebuilt/components/RaiseHand.jsx +0 -17
@@ -1,12 +1,13 @@
1
1
  import React, { useState } from 'react';
2
2
  import { DefaultConferencingScreen_Elements } from '@100mslive/types-prebuilt';
3
+ import { match } from 'ts-pattern';
3
4
  import {
4
5
  HMSPeer,
5
6
  selectPermissions,
6
7
  selectRoleByRoleName,
8
+ selectTracksMap,
7
9
  useHMSActions,
8
10
  useHMSStore,
9
- useHMSVanillaStore,
10
11
  } from '@100mslive/react-sdk';
11
12
  import {
12
13
  MicOffIcon,
@@ -25,26 +26,78 @@ import { useRoomLayoutConferencingScreen } from '../../provider/roomLayoutProvid
25
26
  import { getMetadata } from '../../common/utils';
26
27
 
27
28
  const dropdownItemCSS = { backgroundColor: '$surface_default', gap: '$4', p: '$8' };
28
- const optionTextCSS = { fontWeight: '$semiBold', color: '$on_surface_high', textTransform: 'none' };
29
+ const optionTextCSS = {
30
+ fontWeight: '$semiBold',
31
+ color: '$on_surface_high',
32
+ textTransform: 'none',
33
+ whiteSpace: 'nowrap',
34
+ };
29
35
 
30
- const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleName: string }) => {
31
- const vanillaStore = useHMSVanillaStore();
32
- const store = vanillaStore.getState();
33
- const hmsActions = useHMSActions();
36
+ const DropdownWrapper = ({ children }: { children: React.ReactNode }) => {
37
+ const [openOptions, setOpenOptions] = useState(false);
38
+ if (React.Children.toArray(children).length === 0) {
39
+ return null;
40
+ }
41
+ return (
42
+ <Dropdown.Root open={openOptions} onOpenChange={setOpenOptions}>
43
+ <Dropdown.Trigger
44
+ data-testid="role_group_options"
45
+ onClick={e => e.stopPropagation()}
46
+ className="role_actions"
47
+ asChild
48
+ css={{
49
+ p: '$1',
50
+ r: '$0',
51
+ c: '$on_surface_high',
52
+ visibility: openOptions ? 'visible' : 'hidden',
53
+ '&:hover': {
54
+ c: '$on_surface_medium',
55
+ },
56
+ '@md': {
57
+ visibility: 'visible',
58
+ },
59
+ }}
60
+ >
61
+ <Flex>
62
+ <VerticalMenuIcon />
63
+ </Flex>
64
+ </Dropdown.Trigger>
65
+ <Dropdown.Content
66
+ onClick={e => e.stopPropagation()}
67
+ css={{ w: 'max-content', bg: '$surface_default', py: 0 }}
68
+ align="end"
69
+ >
70
+ {children}
71
+ </Dropdown.Content>
72
+ </Dropdown.Root>
73
+ );
74
+ };
75
+
76
+ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList: HMSPeer[] }) => {
34
77
  const permissions = useHMSStore(selectPermissions);
78
+ const hmsActions = useHMSActions();
79
+ const { elements } = useRoomLayoutConferencingScreen();
80
+ const { on_stage_role, off_stage_roles = [] } = (elements as DefaultConferencingScreen_Elements)?.on_stage_exp || {};
81
+ const canRemoveRoleFromStage = permissions?.changeRole && roleName === on_stage_role;
35
82
  const role = useHMSStore(selectRoleByRoleName(roleName));
83
+ const tracks = useHMSStore(selectTracksMap);
84
+ if (!role) {
85
+ return null;
86
+ }
87
+ const canPublishAudio = role.publishParams.allowed.includes('audio');
88
+ const canPublishVideo = role.publishParams.allowed.includes('video');
36
89
 
37
- let allPeersHaveVideoOn = true;
38
- let allPeersHaveAudioOn = true;
90
+ let isVideoOnForSomePeers = false;
91
+ let isAudioOnForSomePeers = false;
39
92
 
40
93
  peerList.forEach(peer => {
41
94
  if (peer.isLocal) {
42
95
  return;
43
96
  }
44
- const isAudioOn = !!peer.audioTrack && store.tracks[peer.audioTrack]?.enabled;
45
- const isVideoOn = !!peer.videoTrack && store.tracks[peer.videoTrack]?.enabled;
46
- allPeersHaveAudioOn = allPeersHaveAudioOn && isAudioOn;
47
- allPeersHaveVideoOn = allPeersHaveVideoOn && isVideoOn;
97
+ const isAudioOn = !!peer.audioTrack && tracks[peer.audioTrack]?.enabled;
98
+ const isVideoOn = !!peer.videoTrack && tracks[peer.videoTrack]?.enabled;
99
+ isAudioOnForSomePeers = isAudioOnForSomePeers || isAudioOn;
100
+ isVideoOnForSomePeers = isVideoOnForSomePeers || isVideoOn;
48
101
  });
49
102
 
50
103
  const setTrackEnabled = async (type: 'audio' | 'video', enabled = false) => {
@@ -55,68 +108,16 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
55
108
  }
56
109
  };
57
110
 
58
- return (
59
- <>
60
- {role.publishParams.allowed?.includes('audio') && (
61
- <>
62
- {allPeersHaveAudioOn && permissions?.mute ? (
63
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', false)}>
64
- <MicOffIcon />
65
- <Text variant="sm" css={optionTextCSS}>
66
- Mute Audio for All
67
- </Text>
68
- </Dropdown.Item>
69
- ) : null}
70
-
71
- {!allPeersHaveAudioOn && permissions?.unmute ? (
72
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', true)}>
73
- <MicOnIcon />
74
- <Text variant="sm" css={optionTextCSS}>
75
- Unmute Audio for All
76
- </Text>
77
- </Dropdown.Item>
78
- ) : null}
79
- </>
80
- )}
81
-
82
- {role.publishParams.allowed?.includes('audio') && (
83
- <>
84
- {allPeersHaveVideoOn && permissions?.mute ? (
85
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', false)}>
86
- <VideoOffIcon />
87
- <Text variant="sm" css={optionTextCSS}>
88
- Mute Video for All
89
- </Text>
90
- </Dropdown.Item>
91
- ) : null}
92
-
93
- {!allPeersHaveVideoOn && permissions?.unmute ? (
94
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', true)}>
95
- <VideoOnIcon />
96
- <Text variant="sm" css={optionTextCSS}>
97
- Unmute Video for All
98
- </Text>
99
- </Dropdown.Item>
100
- ) : null}
101
- </>
102
- )}
103
- </>
104
- );
105
- };
106
-
107
- export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList: HMSPeer[] }) => {
108
- const [openOptions, setOpenOptions] = useState(false);
109
- const permissions = useHMSStore(selectPermissions);
110
- const hmsActions = useHMSActions();
111
- const { elements } = useRoomLayoutConferencingScreen();
112
- const { on_stage_role, off_stage_roles = [] } = (elements as DefaultConferencingScreen_Elements)?.on_stage_exp || {};
113
- const canMuteOrUnmute = permissions?.mute || permissions?.unmute;
114
- const canRemoveRoleFromStage = permissions?.changeRole && roleName === on_stage_role;
115
111
  // on stage and off stage roles
116
112
  const canRemoveRoleFromRoom =
117
113
  permissions?.removeOthers && (on_stage_role === roleName || off_stage_roles?.includes(roleName));
118
114
 
119
- if (!(canMuteOrUnmute || canRemoveRoleFromStage || canRemoveRoleFromRoom) || peerList.length === 0) {
115
+ if (
116
+ peerList.length === 0 ||
117
+ // if only local peer is present no need to show any options
118
+ (peerList.length === 1 && peerList[0].isLocal) ||
119
+ !role
120
+ ) {
120
121
  return null;
121
122
  }
122
123
 
@@ -140,60 +141,75 @@ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList
140
141
  };
141
142
 
142
143
  return (
143
- <Dropdown.Root open={openOptions} onOpenChange={setOpenOptions}>
144
- <Dropdown.Trigger
145
- data-testid="role_group_options"
146
- onClick={e => e.stopPropagation()}
147
- className="role_actions"
148
- asChild
149
- css={{
150
- p: '$1',
151
- r: '$0',
152
- c: '$on_surface_high',
153
- visibility: openOptions ? 'visible' : 'hidden',
154
- '&:hover': {
155
- c: '$on_surface_medium',
156
- },
157
- '@md': {
158
- visibility: 'visible',
159
- },
160
- }}
161
- >
162
- <Flex>
163
- <VerticalMenuIcon />
164
- </Flex>
165
- </Dropdown.Trigger>
166
- <Dropdown.Content
167
- onClick={e => e.stopPropagation()}
168
- css={{ w: 'max-content', maxWidth: '$64', bg: '$surface_default', py: 0 }}
169
- align="end"
170
- >
171
- {canRemoveRoleFromStage && (
172
- <Dropdown.Item
173
- css={{ ...dropdownItemCSS, borderBottom: '1px solid $border_bright' }}
174
- onClick={removeAllFromStage}
175
- >
176
- <PersonRectangleIcon />
177
- <Text variant="sm" css={optionTextCSS}>
178
- Remove all from Stage
179
- </Text>
180
- </Dropdown.Item>
181
- )}
182
-
183
- {canMuteOrUnmute && <MuteUnmuteOption peerList={peerList} roleName={roleName} />}
184
-
185
- {canRemoveRoleFromRoom && (
186
- <Dropdown.Item
187
- css={{ ...dropdownItemCSS, borderTop: '1px solid $border_bright', color: '$alert_error_default' }}
188
- onClick={removePeersFromRoom}
189
- >
190
- <RemoveUserIcon />
191
- <Text variant="sm" css={{ ...optionTextCSS, color: 'inherit' }}>
192
- Remove all from Room
193
- </Text>
194
- </Dropdown.Item>
195
- )}
196
- </Dropdown.Content>
197
- </Dropdown.Root>
144
+ <DropdownWrapper>
145
+ {canRemoveRoleFromStage ? (
146
+ <Dropdown.Item
147
+ css={{ ...dropdownItemCSS, borderBottom: '1px solid $border_bright' }}
148
+ onClick={removeAllFromStage}
149
+ >
150
+ <PersonRectangleIcon />
151
+ <Text variant="sm" css={optionTextCSS}>
152
+ Remove all from Stage
153
+ </Text>
154
+ </Dropdown.Item>
155
+ ) : null}
156
+
157
+ {match({ canPublishAudio, isAudioOnForSomePeers, canMute: permissions?.mute, canUnmute: permissions?.unmute })
158
+ .with({ canPublishAudio: true, isAudioOnForSomePeers: true, canMute: true }, () => {
159
+ return (
160
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', false)}>
161
+ <MicOffIcon />
162
+ <Text variant="sm" css={optionTextCSS}>
163
+ Mute Audio for All
164
+ </Text>
165
+ </Dropdown.Item>
166
+ );
167
+ })
168
+ .with({ canPublishAudio: true, isAudioOnForSomePeers: false, canUnmute: true }, () => {
169
+ return (
170
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', true)}>
171
+ <MicOnIcon />
172
+ <Text variant="sm" css={optionTextCSS}>
173
+ Request to Unmute Audio for All
174
+ </Text>
175
+ </Dropdown.Item>
176
+ );
177
+ })
178
+ .otherwise(() => null)}
179
+ {match({ canPublishVideo, isVideoOnForSomePeers, canMute: permissions?.mute, canUnmute: permissions?.unmute })
180
+ .with({ canPublishVideo: true, isVideoOnForSomePeers: true, canMute: true }, () => {
181
+ return (
182
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', false)}>
183
+ <VideoOffIcon />
184
+ <Text variant="sm" css={optionTextCSS}>
185
+ Mute Video for All
186
+ </Text>
187
+ </Dropdown.Item>
188
+ );
189
+ })
190
+ .with({ canPublishVideo: true, isVideoOnForSomePeers: false, canUnmute: true }, () => {
191
+ return (
192
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', true)}>
193
+ <VideoOnIcon />
194
+ <Text variant="sm" css={optionTextCSS}>
195
+ Request to Unmute Video for All
196
+ </Text>
197
+ </Dropdown.Item>
198
+ );
199
+ })
200
+ .otherwise(() => null)}
201
+
202
+ {canRemoveRoleFromRoom ? (
203
+ <Dropdown.Item
204
+ css={{ ...dropdownItemCSS, borderTop: '1px solid $border_bright', color: '$alert_error_default' }}
205
+ onClick={removePeersFromRoom}
206
+ >
207
+ <RemoveUserIcon />
208
+ <Text variant="sm" css={{ ...optionTextCSS, color: 'inherit' }}>
209
+ Remove all from Room
210
+ </Text>
211
+ </Dropdown.Item>
212
+ ) : null}
213
+ </DropdownWrapper>
198
214
  );
199
215
  };
@@ -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
+ };
@@ -0,0 +1,72 @@
1
+ import React from 'react';
2
+ import { useMedia } from 'react-use';
3
+ import { VolumeTwoIcon } from '@100mslive/react-icons';
4
+ import { Button, config, Dialog, IconButton, Text } from '../../..';
5
+ // @ts-ignore
6
+ import { DialogContent, DialogRow } from '../../primitives/DialogContent';
7
+ import { useIsLandscape } from '../../common/hooks';
8
+
9
+ export function HLSAutoplayBlockedPrompt({
10
+ open,
11
+ unblockAutoPlay,
12
+ }: {
13
+ open: boolean;
14
+ unblockAutoPlay: () => Promise<void>;
15
+ }) {
16
+ const isLandscape = useIsLandscape();
17
+ const isMobile = useMedia(config.media.md);
18
+ if ((isMobile || isLandscape) && open) {
19
+ return (
20
+ <IconButton
21
+ css={{
22
+ border: '1px solid white',
23
+ bg: 'white',
24
+ color: '#000',
25
+ r: '$2',
26
+ }}
27
+ onClick={async () => await unblockAutoPlay()}
28
+ >
29
+ <VolumeTwoIcon width="32" height="32" />
30
+ <Text
31
+ variant="body1"
32
+ css={{
33
+ fontWeight: '$semiBold',
34
+ px: '$2',
35
+ color: '#000',
36
+ }}
37
+ >
38
+ Tap To Unmute
39
+ </Text>
40
+ </IconButton>
41
+ );
42
+ }
43
+ return (
44
+ <Dialog.Root
45
+ open={open}
46
+ onOpenChange={async value => {
47
+ if (!value) {
48
+ await unblockAutoPlay();
49
+ }
50
+ }}
51
+ >
52
+ <DialogContent title="Attention" closeable={false}>
53
+ <DialogRow>
54
+ <Text variant="md">
55
+ The browser wants us to get a confirmation for playing the HLS Stream. Please click "play stream" to
56
+ proceed.
57
+ </Text>
58
+ </DialogRow>
59
+ <DialogRow justify="end">
60
+ <Button
61
+ variant="primary"
62
+ onClick={async () => {
63
+ await unblockAutoPlay();
64
+ }}
65
+ >
66
+ Play stream
67
+ </Button>
68
+ </DialogRow>
69
+ </DialogContent>
70
+ </Dialog.Root>
71
+ );
72
+ }
@@ -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,248 @@
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
+ containerRef,
18
+ }: {
19
+ open: boolean;
20
+ onOpenChange: (value: boolean) => void;
21
+ layers: HMSHLSLayer[];
22
+ onQualityChange: (quality: { [key: string]: string | number } | HMSHLSLayer) => void;
23
+ selection: HMSHLSLayer;
24
+ isAuto: boolean;
25
+ containerRef?: HTMLDivElement;
26
+ }) {
27
+ const isMobile = useMedia(config.media.md);
28
+ const isLandscape = useIsLandscape();
29
+
30
+ if (layers.length === 0) {
31
+ return null;
32
+ }
33
+ if (isMobile || isLandscape) {
34
+ return (
35
+ <Sheet.Root open={open} onOpenChange={onOpenChange}>
36
+ <Sheet.Trigger asChild data-testid="quality_selector">
37
+ <Flex
38
+ css={{
39
+ color: '$on_primary_high',
40
+ r: '$1',
41
+ cursor: 'pointer',
42
+ p: '$2',
43
+ }}
44
+ >
45
+ <SettingsIcon />
46
+ </Flex>
47
+ </Sheet.Trigger>
48
+ <Sheet.Content
49
+ container={containerRef}
50
+ css={{ bg: '$surface_default', pb: '$1' }}
51
+ onClick={() => onOpenChange(false)}
52
+ >
53
+ <Sheet.Title
54
+ css={{
55
+ display: 'flex',
56
+ color: '$on_surface_high',
57
+ w: '100%',
58
+ justifyContent: 'space-between',
59
+ mt: '$8',
60
+ fontSize: '$md',
61
+ px: '$10',
62
+ pb: '$8',
63
+ borderBottom: '1px solid $border_bright',
64
+ alignItems: 'center',
65
+ }}
66
+ >
67
+ Quality
68
+ <Sheet.Close css={{ color: '$on_surface_high' }} onClick={() => onOpenChange(false)}>
69
+ <CrossIcon />
70
+ </Sheet.Close>
71
+ </Sheet.Title>
72
+ {layers.map(layer => {
73
+ return (
74
+ <Flex
75
+ align="center"
76
+ css={{
77
+ w: '100%',
78
+ bg: '$surface_default',
79
+ '&:hover': {
80
+ bg: '$surface_brighter',
81
+ },
82
+ cursor: 'pointer',
83
+ gap: '$4',
84
+ py: '$8',
85
+ px: '$10',
86
+ }}
87
+ key={layer.width}
88
+ onClick={() => onQualityChange(layer)}
89
+ >
90
+ <Text variant="caption" css={{ fontWeight: '$semiBold' }}>
91
+ {getQualityText(layer)}
92
+ </Text>
93
+ <Text variant="caption" css={{ flex: '1 1 0', c: '$on_surface_low', pl: '$2' }}>
94
+ {getBitrateText(layer)}
95
+ </Text>
96
+ {!isAuto && layer.width === selection?.width && layer.height === selection?.height && (
97
+ <CheckIcon width="16px" height="16px" />
98
+ )}
99
+ </Flex>
100
+ );
101
+ })}
102
+ <Flex
103
+ align="center"
104
+ css={{
105
+ w: '100%',
106
+ bg: '$surface_default',
107
+ '&:hover': {
108
+ bg: '$surface_brighter',
109
+ },
110
+ cursor: 'pointer',
111
+ gap: '$4',
112
+ py: '$8',
113
+ px: '$10',
114
+ }}
115
+ key="auto"
116
+ onClick={() => onQualityChange({ height: 'auto' })}
117
+ >
118
+ <Text variant="caption" css={{ fontWeight: '$semiBold', flex: '1 1 0' }}>
119
+ Auto
120
+ </Text>
121
+ {isAuto && <CheckIcon width="16px" height="16px" />}
122
+ </Flex>
123
+ </Sheet.Content>
124
+ </Sheet.Root>
125
+ );
126
+ }
127
+ return (
128
+ <Dropdown.Root open={open} onOpenChange={value => onOpenChange(value)} modal={false}>
129
+ <Dropdown.Trigger asChild data-testid="quality_selector">
130
+ <Flex
131
+ css={{
132
+ color: '$on_primary_high',
133
+ r: '$1',
134
+ cursor: 'pointer',
135
+ p: '$2',
136
+ }}
137
+ >
138
+ <Tooltip title="Select Quality" side="top">
139
+ <Flex align="center">
140
+ <Box
141
+ css={{
142
+ w: '$9',
143
+ h: '$9',
144
+ display: 'inline-flex',
145
+ alignItems: 'center',
146
+ c: '$on_surface_high',
147
+ }}
148
+ >
149
+ <SettingsIcon />
150
+ </Box>
151
+ <Text
152
+ variant={{
153
+ '@md': 'sm',
154
+ '@sm': 'xs',
155
+ '@xs': 'tiny',
156
+ }}
157
+ css={{ display: 'flex', alignItems: 'center', ml: '$2', c: '$on_surface_medium' }}
158
+ >
159
+ {isAuto && (
160
+ <>
161
+ Auto
162
+ <Box
163
+ css={{
164
+ mx: '$2',
165
+ w: '$2',
166
+ h: '$2',
167
+ background: '$on_surface_medium',
168
+ r: '$1',
169
+ }}
170
+ />
171
+ </>
172
+ )}
173
+ {selection && Math.min(selection.width || 0, selection.height || 0)}p
174
+ </Text>
175
+ </Flex>
176
+ </Tooltip>
177
+ </Flex>
178
+ </Dropdown.Trigger>
179
+ <Dropdown.Portal container={containerRef}>
180
+ <Dropdown.Content
181
+ sideOffset={5}
182
+ align="end"
183
+ css={{
184
+ height: 'auto',
185
+ maxHeight: '$52',
186
+ w: '$40',
187
+ bg: '$surface_bright',
188
+ py: '$4',
189
+ gap: '$4',
190
+ display: 'grid',
191
+ }}
192
+ >
193
+ {layers.map(layer => {
194
+ return (
195
+ <Dropdown.Item
196
+ onClick={() => onQualityChange(layer)}
197
+ key={layer.width}
198
+ css={{
199
+ bg:
200
+ !isAuto && layer.width === selection?.width && layer.height === selection?.height
201
+ ? '$surface_default'
202
+ : '$surface_bright',
203
+ '&:hover': {
204
+ bg: '$surface_brighter',
205
+ },
206
+ p: '$2 $4 $2 $8',
207
+ h: '$12',
208
+ gap: '$2',
209
+ }}
210
+ >
211
+ <Text variant="caption" css={{ fontWeight: '$semiBold' }}>
212
+ {getQualityText(layer)}
213
+ </Text>
214
+ <Text variant="caption" css={{ flex: '1 1 0', c: '$on_surface_low', pl: '$2' }}>
215
+ {getBitrateText(layer)}
216
+ </Text>
217
+ {!isAuto && layer.width === selection?.width && layer.height === selection?.height && (
218
+ <CheckIcon width="16px" height="16px" />
219
+ )}
220
+ </Dropdown.Item>
221
+ );
222
+ })}
223
+ <Dropdown.Item
224
+ onClick={() => onQualityChange({ height: 'auto' })}
225
+ key="auto"
226
+ css={{
227
+ bg: !isAuto ? '$surface_bright' : '$surface_default',
228
+ '&:hover': {
229
+ bg: '$surface_brighter',
230
+ },
231
+ p: '$2 $4 $2 $8',
232
+ h: '$12',
233
+ gap: '$2',
234
+ }}
235
+ >
236
+ <Text variant="caption" css={{ fontWeight: '$semiBold', flex: '1 1 0' }}>
237
+ Auto
238
+ </Text>
239
+ {isAuto && <CheckIcon width="16px" height="16px" />}
240
+ </Dropdown.Item>
241
+ </Dropdown.Content>
242
+ </Dropdown.Portal>
243
+ </Dropdown.Root>
244
+ );
245
+ }
246
+
247
+ const getQualityText = (layer: HMSHLSLayer) => `${Math.min(layer.height || 0, layer.width || 0)}p `;
248
+ const getBitrateText = (layer: HMSHLSLayer) => `(${(Number(layer.bitrate / 1000) / 1000).toFixed(2)} Mbps)`;