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

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 (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)`;