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

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-FBEGJ3L7.js +1396 -0
  2. package/dist/HLSView-FBEGJ3L7.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-R2JJJQR3.js} +1539 -1173
  27. package/dist/chunk-R2JJJQR3.js.map +7 -0
  28. package/dist/index.cjs.js +2709 -1898
  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 +796 -298
  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 +138 -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 +17 -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 +359 -178
  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,75 @@ 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 canPublishAudio = role.publishParams.allowed.includes('audio');
84
+ const canPublishVideo = role.publishParams.allowed.includes('video');
85
+ const tracks = useHMSStore(selectTracksMap);
36
86
 
37
- let allPeersHaveVideoOn = true;
38
- let allPeersHaveAudioOn = true;
87
+ let isVideoOnForSomePeers = false;
88
+ let isAudioOnForSomePeers = false;
39
89
 
40
90
  peerList.forEach(peer => {
41
91
  if (peer.isLocal) {
42
92
  return;
43
93
  }
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;
94
+ const isAudioOn = !!peer.audioTrack && tracks[peer.audioTrack]?.enabled;
95
+ const isVideoOn = !!peer.videoTrack && tracks[peer.videoTrack]?.enabled;
96
+ isAudioOnForSomePeers = isAudioOnForSomePeers || isAudioOn;
97
+ isVideoOnForSomePeers = isVideoOnForSomePeers || isVideoOn;
48
98
  });
49
99
 
50
100
  const setTrackEnabled = async (type: 'audio' | 'video', enabled = false) => {
@@ -55,68 +105,16 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
55
105
  }
56
106
  };
57
107
 
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
108
  // on stage and off stage roles
116
109
  const canRemoveRoleFromRoom =
117
110
  permissions?.removeOthers && (on_stage_role === roleName || off_stage_roles?.includes(roleName));
118
111
 
119
- if (!(canMuteOrUnmute || canRemoveRoleFromStage || canRemoveRoleFromRoom) || peerList.length === 0) {
112
+ if (
113
+ peerList.length === 0 ||
114
+ // if only local peer is present no need to show any options
115
+ (peerList.length === 1 && peerList[0].isLocal) ||
116
+ !role
117
+ ) {
120
118
  return null;
121
119
  }
122
120
 
@@ -140,60 +138,75 @@ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList
140
138
  };
141
139
 
142
140
  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>
141
+ <DropdownWrapper>
142
+ {canRemoveRoleFromStage ? (
143
+ <Dropdown.Item
144
+ css={{ ...dropdownItemCSS, borderBottom: '1px solid $border_bright' }}
145
+ onClick={removeAllFromStage}
146
+ >
147
+ <PersonRectangleIcon />
148
+ <Text variant="sm" css={optionTextCSS}>
149
+ Remove all from Stage
150
+ </Text>
151
+ </Dropdown.Item>
152
+ ) : null}
153
+
154
+ {match({ canPublishAudio, isAudioOnForSomePeers, canMute: permissions?.mute, canUnmute: permissions?.unmute })
155
+ .with({ canPublishAudio: true, isAudioOnForSomePeers: true, canMute: true }, () => {
156
+ return (
157
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', false)}>
158
+ <MicOffIcon />
159
+ <Text variant="sm" css={optionTextCSS}>
160
+ Mute Audio for All
161
+ </Text>
162
+ </Dropdown.Item>
163
+ );
164
+ })
165
+ .with({ canPublishAudio: true, isAudioOnForSomePeers: false, canUnmute: true }, () => {
166
+ return (
167
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', true)}>
168
+ <MicOnIcon />
169
+ <Text variant="sm" css={optionTextCSS}>
170
+ Request to Unmute Audio for All
171
+ </Text>
172
+ </Dropdown.Item>
173
+ );
174
+ })
175
+ .otherwise(() => null)}
176
+ {match({ canPublishVideo, isVideoOnForSomePeers, canMute: permissions?.mute, canUnmute: permissions?.unmute })
177
+ .with({ canPublishVideo: true, isVideoOnForSomePeers: true, canMute: true }, () => {
178
+ return (
179
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', false)}>
180
+ <VideoOffIcon />
181
+ <Text variant="sm" css={optionTextCSS}>
182
+ Mute Video for All
183
+ </Text>
184
+ </Dropdown.Item>
185
+ );
186
+ })
187
+ .with({ canPublishVideo: true, isVideoOnForSomePeers: false, canUnmute: true }, () => {
188
+ return (
189
+ <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', true)}>
190
+ <VideoOnIcon />
191
+ <Text variant="sm" css={optionTextCSS}>
192
+ Request to Unmute Video for All
193
+ </Text>
194
+ </Dropdown.Item>
195
+ );
196
+ })
197
+ .otherwise(() => null)}
198
+
199
+ {canRemoveRoleFromRoom ? (
200
+ <Dropdown.Item
201
+ css={{ ...dropdownItemCSS, borderTop: '1px solid $border_bright', color: '$alert_error_default' }}
202
+ onClick={removePeersFromRoom}
203
+ >
204
+ <RemoveUserIcon />
205
+ <Text variant="sm" css={{ ...optionTextCSS, color: 'inherit' }}>
206
+ Remove all from Room
207
+ </Text>
208
+ </Dropdown.Item>
209
+ ) : null}
210
+ </DropdownWrapper>
198
211
  );
199
212
  };
@@ -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)`;