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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. package/dist/{HLSView-CVNJNDUQ.js → HLSView-MYKM5AXS.js} +239 -144
  2. package/dist/HLSView-MYKM5AXS.js.map +7 -0
  3. package/dist/Prebuilt/components/Chat/MwebChatOption.d.ts +1 -1
  4. package/dist/Prebuilt/components/HMSVideo/PlayPauseButton.d.ts +6 -0
  5. package/dist/Prebuilt/components/HMSVideo/SeekControls.d.ts +7 -0
  6. package/dist/Prebuilt/components/HMSVideo/VideoProgress.d.ts +3 -1
  7. package/dist/Prebuilt/components/HMSVideo/index.d.ts +10 -2
  8. package/dist/Prebuilt/layouts/SidePane.d.ts +1 -1
  9. package/dist/{chunk-25HZFDG5.js → chunk-DRBTAFKN.js} +356 -222
  10. package/dist/chunk-DRBTAFKN.js.map +7 -0
  11. package/dist/index.cjs.js +949 -712
  12. package/dist/index.cjs.js.map +4 -4
  13. package/dist/index.js +1 -1
  14. package/dist/meta.cjs.json +217 -78
  15. package/dist/meta.esbuild.json +227 -87
  16. package/package.json +7 -6
  17. package/src/Prebuilt/components/AppData/useSidepane.js +34 -7
  18. package/src/Prebuilt/components/AuthToken.jsx +1 -1
  19. package/src/Prebuilt/components/Chat/ChatFooter.tsx +1 -1
  20. package/src/Prebuilt/components/Chat/MwebChatOption.tsx +1 -1
  21. package/src/Prebuilt/components/ConferenceScreen.tsx +11 -14
  22. package/src/Prebuilt/components/Footer/RoleOptions.tsx +32 -15
  23. package/src/Prebuilt/components/HMSVideo/HLSAutoplayBlockedPrompt.tsx +31 -1
  24. package/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +7 -1
  25. package/src/Prebuilt/components/HMSVideo/PlayPauseButton.tsx +27 -0
  26. package/src/Prebuilt/components/HMSVideo/SeekControls.tsx +22 -0
  27. package/src/Prebuilt/components/HMSVideo/VideoProgress.tsx +4 -3
  28. package/src/Prebuilt/components/HMSVideo/VolumeControl.tsx +1 -1
  29. package/src/Prebuilt/components/HMSVideo/index.ts +4 -2
  30. package/src/Prebuilt/components/Header/StreamActions.tsx +1 -1
  31. package/src/Prebuilt/components/MoreSettings/SplitComponents/DesktopOptions.tsx +37 -31
  32. package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +6 -5
  33. package/src/Prebuilt/components/Notifications/HandRaisedNotifications.tsx +2 -2
  34. package/src/Prebuilt/components/Notifications/PeerNotifications.tsx +1 -1
  35. package/src/Prebuilt/components/Polls/Voting/QuestionCard.jsx +19 -8
  36. package/src/Prebuilt/components/SidePaneTabs.tsx +27 -35
  37. package/src/Prebuilt/components/StatsForNerds.jsx +14 -6
  38. package/src/Prebuilt/components/Streaming/Common.jsx +1 -1
  39. package/src/Prebuilt/components/TileMenu/TileMenuContent.tsx +2 -2
  40. package/src/Prebuilt/components/Toast/ToastBatcher.js +8 -1
  41. package/src/Prebuilt/components/Toast/ToastConfig.jsx +17 -0
  42. package/src/Prebuilt/layouts/HLSView.jsx +109 -69
  43. package/src/Prebuilt/layouts/SidePane.tsx +125 -67
  44. package/src/Prebuilt/layouts/VideoStreamingSection.tsx +2 -1
  45. package/src/Prebuilt/provider/roomLayoutProvider/index.tsx +1 -1
  46. package/src/Sheet/Sheet.tsx +4 -0
  47. package/dist/HLSView-CVNJNDUQ.js.map +0 -7
  48. package/dist/chunk-25HZFDG5.js.map +0 -7
  49. package/src/Prebuilt/components/HMSVideo/PlayButton.jsx +0 -13
@@ -1,5 +1,6 @@
1
1
  // @ts-check
2
2
  import React, { useCallback, useMemo, useRef, useState } from 'react';
3
+ import { match } from 'ts-pattern';
3
4
  import { selectLocalPeer, selectLocalPeerRoleName, useHMSActions, useHMSStore } from '@100mslive/react-sdk';
4
5
  import { CheckCircleIcon, ChevronDownIcon, CrossCircleIcon } from '@100mslive/react-icons';
5
6
  import { Box, Button, Flex, Text } from '../../../../';
@@ -92,20 +93,30 @@ export const QuestionCard = ({
92
93
  <Text
93
94
  variant="caption"
94
95
  css={{
95
- color:
96
- respondedToQuiz && !isLive
97
- ? isCorrectAnswer
98
- ? '$alert_success'
99
- : '$alert_error_default'
100
- : '$on_surface_low',
96
+ color: match({ respondedToQuiz, isLive, isCorrectAnswer })
97
+ .when(
98
+ ({ respondedToQuiz, isLive }) => respondedToQuiz && !isLive,
99
+ ({ isCorrectAnswer }) => (isCorrectAnswer ? '$alert_success' : '$alert_error_default'),
100
+ )
101
+ .otherwise(() => '$on_surface_low'),
101
102
  fontWeight: '$semiBold',
102
103
  display: 'flex',
103
104
  alignItems: 'center',
104
105
  gap: '$4',
105
106
  }}
106
107
  >
107
- {respondedToQuiz && isCorrectAnswer && pollEnded ? <CheckCircleIcon height={16} width={16} /> : null}
108
- {respondedToQuiz && !isCorrectAnswer && pollEnded ? <CrossCircleIcon height={16} width={16} /> : null}
108
+ {match({ respondedToQuiz, pollEnded, isCorrectAnswer })
109
+ .when(
110
+ ({ respondedToQuiz, pollEnded }) => respondedToQuiz && pollEnded,
111
+ ({ isCorrectAnswer }) => {
112
+ return isCorrectAnswer ? (
113
+ <CheckCircleIcon height={16} width={16} />
114
+ ) : (
115
+ <CrossCircleIcon height={16} width={16} />
116
+ );
117
+ },
118
+ )
119
+ .otherwise(() => null)}
109
120
  QUESTION {index} OF {totalQuestions}: {type.toUpperCase()}
110
121
  </Text>
111
122
  </Flex>
@@ -1,16 +1,15 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import { useMedia } from 'react-use';
3
3
  import { DefaultConferencingScreen_Elements } from '@100mslive/types-prebuilt';
4
+ import { match } from 'ts-pattern';
4
5
  import { selectPeerCount, useHMSStore } from '@100mslive/react-sdk';
5
6
  import { CrossIcon } from '@100mslive/react-icons';
6
- // @ts-ignore: No implicit Any
7
7
  import { Chat } from './Chat/Chat';
8
8
  import { PaginatedParticipants } from './Footer/PaginatedParticipants';
9
9
  import { ParticipantList } from './Footer/ParticipantList';
10
10
  import { Box, config as cssConfig, Flex, IconButton, Tabs, Text } from '../..';
11
11
  import { Tooltip } from '../../Tooltip';
12
12
  import { ChatSettings } from './ChatSettings';
13
- // @ts-ignore: No implicit Any
14
13
  import { useRoomLayoutConferencingScreen } from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
15
14
  // @ts-ignore: No implicit Any
16
15
  import { useIsSidepaneTypeOpen, useSidepaneReset, useSidepaneToggle } from './AppData/useSidepane';
@@ -19,12 +18,15 @@ import { getFormattedCount } from '../common/utils';
19
18
  import { SIDE_PANE_OPTIONS } from '../common/constants';
20
19
 
21
20
  const tabTriggerCSS = {
22
- color: '$on_surface_high',
21
+ color: '$on_surface_low',
23
22
  p: '$4',
24
23
  fontWeight: '$semiBold',
25
24
  fontSize: '$sm',
26
25
  w: '100%',
27
26
  justifyContent: 'center',
27
+ '&[data-state="active"]': {
28
+ color: '$on_surface_high',
29
+ },
28
30
  };
29
31
 
30
32
  const ParticipantCount = ({ count }: { count: number }) => {
@@ -60,13 +62,16 @@ export const SidePaneTabs = React.memo<{
60
62
  const showChatSettings = showChat && isChatOpen && (!isMobile || !isOverlayChat);
61
63
 
62
64
  useEffect(() => {
63
- if (activeTab === SIDE_PANE_OPTIONS.CHAT && !showChat && showParticipants) {
64
- setActiveTab(SIDE_PANE_OPTIONS.PARTICIPANTS);
65
- } else if (activeTab === SIDE_PANE_OPTIONS.PARTICIPANTS && showChat && !showParticipants) {
66
- setActiveTab(SIDE_PANE_OPTIONS.CHAT);
67
- } else if (!showChat && !showParticipants) {
68
- resetSidePane();
69
- }
65
+ match({ activeTab, showChat, showParticipants })
66
+ .with({ activeTab: SIDE_PANE_OPTIONS.CHAT, showChat: false, showParticipants: true }, () => {
67
+ setActiveTab(SIDE_PANE_OPTIONS.PARTICIPANTS);
68
+ })
69
+ .with({ activeTab: SIDE_PANE_OPTIONS.PARTICIPANTS, showChat: true, showParticipants: false }, () => {
70
+ setActiveTab(SIDE_PANE_OPTIONS.CHAT);
71
+ })
72
+ .with({ showChat: false, showParticipants: false }, () => {
73
+ resetSidePane();
74
+ });
70
75
  }, [showChat, activeTab, showParticipants, resetSidePane]);
71
76
 
72
77
  useEffect(() => {
@@ -102,11 +107,10 @@ export const SidePaneTabs = React.memo<{
102
107
  transition: 'margin 0.3s ease-in-out',
103
108
  }}
104
109
  >
105
- {isOverlayChat && isChatOpen && showChat ? (
106
- <Chat />
107
- ) : (
108
- <>
109
- {hideTabs ? (
110
+ {match({ isOverlayChat, isChatOpen, showChat, hideTabs })
111
+ .with({ isOverlayChat: true, isChatOpen: true, showChat: true }, () => <Chat />)
112
+ .with({ hideTabs: true }, () => {
113
+ return (
110
114
  <>
111
115
  <Flex justify="between" css={{ w: '100%', '&:empty': { display: 'none' } }}>
112
116
  <Text
@@ -159,7 +163,10 @@ export const SidePaneTabs = React.memo<{
159
163
  <ParticipantList offStageRoles={off_stage_roles} onActive={setActiveRole} />
160
164
  )}
161
165
  </>
162
- ) : (
166
+ );
167
+ })
168
+ .otherwise(() => {
169
+ return (
163
170
  <Tabs.Root
164
171
  value={activeTab}
165
172
  onValueChange={setActiveTab}
@@ -170,24 +177,10 @@ export const SidePaneTabs = React.memo<{
170
177
  >
171
178
  <Flex css={{ w: '100%' }}>
172
179
  <Tabs.List css={{ flexGrow: 1, borderRadius: '$2', bg: '$surface_default' }}>
173
- <Tabs.Trigger
174
- value={SIDE_PANE_OPTIONS.CHAT}
175
- onClick={toggleChat}
176
- css={{
177
- ...tabTriggerCSS,
178
- color: activeTab !== SIDE_PANE_OPTIONS.CHAT ? '$on_surface_low' : '$on_surface_high',
179
- }}
180
- >
180
+ <Tabs.Trigger value={SIDE_PANE_OPTIONS.CHAT} onClick={toggleChat} css={tabTriggerCSS}>
181
181
  {chat_title}
182
182
  </Tabs.Trigger>
183
- <Tabs.Trigger
184
- value={SIDE_PANE_OPTIONS.PARTICIPANTS}
185
- onClick={toggleParticipants}
186
- css={{
187
- ...tabTriggerCSS,
188
- color: activeTab !== SIDE_PANE_OPTIONS.PARTICIPANTS ? '$on_surface_low' : '$on_surface_high',
189
- }}
190
- >
183
+ <Tabs.Trigger value={SIDE_PANE_OPTIONS.PARTICIPANTS} onClick={toggleParticipants} css={tabTriggerCSS}>
191
184
  Participants&nbsp;
192
185
  <ParticipantCount count={peerCount} />
193
186
  </Tabs.Trigger>
@@ -217,9 +210,8 @@ export const SidePaneTabs = React.memo<{
217
210
  <Chat />
218
211
  </Tabs.Content>
219
212
  </Tabs.Root>
220
- )}
221
- </>
222
- )}
213
+ );
214
+ })}
223
215
  </Flex>
224
216
  );
225
217
  });
@@ -1,4 +1,5 @@
1
1
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { match, P } from 'ts-pattern';
2
3
  import {
3
4
  selectHMSStats,
4
5
  selectLocalPeerID,
@@ -188,11 +189,15 @@ const LocalPeerStats = () => {
188
189
  };
189
190
 
190
191
  const TrackStats = ({ trackID, layer, local }) => {
191
- const selector = layer
192
- ? selectHMSStats.localVideoTrackStatsByLayer(layer)(trackID)
193
- : local
194
- ? selectHMSStats.localAudioTrackStatsByID(trackID)
195
- : selectHMSStats.trackStatsByID(trackID);
192
+ const selector = match({ trackID, layer, local })
193
+ .with(
194
+ {
195
+ layer: P.when(layer => !!layer),
196
+ },
197
+ () => selectHMSStats.localVideoTrackStatsByLayer(layer)(trackID),
198
+ )
199
+ .with({ local: P.when(local => !!local) }, () => selectHMSStats.localAudioTrackStatsByID(trackID))
200
+ .otherwise(() => selectHMSStats.trackStatsByID(trackID));
196
201
  const stats = useHMSStatsStore(selector);
197
202
  if (!stats) {
198
203
  return null;
@@ -215,7 +220,10 @@ const TrackStats = ({ trackID, layer, local }) => {
215
220
  {!inbound && <StatsRow label="Quality Limitation Reason" value={stats.qualityLimitationReason} />}
216
221
  </>
217
222
  )}
218
- <StatsRow label="Round Trip Time" value={stats.roundTripTime ? `${stats.roundTripTime * 1000} ms` : '-'} />
223
+ <StatsRow
224
+ label="Round Trip Time"
225
+ value={stats.roundTripTime ? `${(stats.roundTripTime * 1000).toFixed(3)} ms` : '-'}
226
+ />
219
227
  </Flex>
220
228
  );
221
229
  };
@@ -20,7 +20,7 @@ export const StreamCard = ({ title, subtitle, Icon, imgSrc = '', css = {}, onCli
20
20
  onClick={onClick}
21
21
  >
22
22
  <Text css={{ alignSelf: 'center', p: '$4' }}>
23
- {imgSrc ? <img src={imgSrc} height={40} width={40} /> : <Icon width={40} height={40} />}
23
+ {imgSrc ? <img src={imgSrc} height={40} width={40} alt="Streaming" /> : <Icon width={40} height={40} />}
24
24
  </Text>
25
25
  <Box css={{ flex: '1 1 0', mx: '$8' }}>
26
26
  <Text variant="h6" css={{ mb: '$4' }}>
@@ -289,7 +289,7 @@ export const TileMenuContent = ({
289
289
  data-testid={isVideoEnabled ? 'mute_video_participant_btn' : 'unmute_video_participant_btn'}
290
290
  >
291
291
  {isVideoEnabled ? <VideoOnIcon height={20} width={20} /> : <VideoOffIcon height={20} width={20} />}
292
- <span>{isVideoEnabled ? 'Mute' : 'Request Unmute'}</span>
292
+ <span>{isVideoEnabled ? 'Mute Video' : 'Request to Unmute Video'}</span>
293
293
  </StyledMenuTile.ItemButton>
294
294
  ) : null}
295
295
 
@@ -304,7 +304,7 @@ export const TileMenuContent = ({
304
304
  data-testid={isAudioEnabled ? 'mute_audio_participant_btn' : 'unmute_audio_participant_btn'}
305
305
  >
306
306
  {isAudioEnabled ? <MicOnIcon height={20} width={20} /> : <MicOffIcon height={20} width={20} />}
307
- <span>{isAudioEnabled ? 'Mute' : 'Request Unmute'}</span>
307
+ <span>{isAudioEnabled ? 'Mute Audio' : 'Request to Unmute Audio'}</span>
308
308
  </StyledMenuTile.ItemButton>
309
309
  ) : null}
310
310
 
@@ -3,6 +3,7 @@ import { ToastManager } from './ToastManager';
3
3
 
4
4
  export const ToastBatcher = {
5
5
  toastsType: new Map(),
6
+ toastCache: {},
6
7
  showToastInternal({ notification, duration, type }) {
7
8
  let notificationType = type;
8
9
  if (!type) {
@@ -40,7 +41,13 @@ export const ToastBatcher = {
40
41
  },
41
42
  showToast({ notification, duration = 3000, type }) {
42
43
  try {
43
- this.showToastInternal({ notification, duration, type });
44
+ if (!this.toastCache[notification.id]) {
45
+ this.showToastInternal({ notification, duration, type });
46
+ }
47
+ this.toastCache[notification.id] = true;
48
+ if (Object.keys(this.toastCache).length > 100) {
49
+ this.toastCache = {};
50
+ }
44
51
  } catch (err) {
45
52
  console.debug('Notifications', err);
46
53
  }
@@ -123,6 +123,23 @@ export const ToastConfig = {
123
123
  },
124
124
  },
125
125
  RAISE_HAND: {
126
+ single: notification => {
127
+ return {
128
+ title: `${notification.data?.name} raised hand`,
129
+ icon: <HandIcon />,
130
+ };
131
+ },
132
+ multiple: notifications => {
133
+ const count = new Set(notifications.map(notification => notification.data?.id)).size;
134
+ return {
135
+ title: `${notifications[notifications.length - 1].data?.name} ${
136
+ count > 1 ? `${count} and others` : ''
137
+ } raised hand`,
138
+ icon: <HandIcon />,
139
+ };
140
+ },
141
+ },
142
+ RAISE_HAND_HLS: {
126
143
  single: notification => {
127
144
  return {
128
145
  title: `${notification.data?.name} raised hand`,
@@ -14,7 +14,7 @@ import {
14
14
  useHMSStore,
15
15
  useHMSVanillaStore,
16
16
  } from '@100mslive/react-sdk';
17
- import { ColoredHandIcon, GoLiveIcon, PauseIcon, PlayIcon } from '@100mslive/react-icons';
17
+ import { BackwardArrowIcon, ColoredHandIcon, ForwardArrowIcon, GoLiveIcon } from '@100mslive/react-icons';
18
18
  import { ChatToggle } from '../components/Footer/ChatToggle';
19
19
  import { HlsStatsOverlay } from '../components/HlsStatsOverlay';
20
20
  import { HMSVideoPlayer } from '../components/HMSVideo';
@@ -24,6 +24,7 @@ import { HLSCaptionSelector } from '../components/HMSVideo/HLSCaptionSelector';
24
24
  import { HLSQualitySelector } from '../components/HMSVideo/HLSQualitySelector';
25
25
  import { HLSViewTitle } from '../components/HMSVideo/MwebHLSViewTitle';
26
26
  import { HMSPlayerContext } from '../components/HMSVideo/PlayerContext';
27
+ import { LeaveRoom } from '../components/Leave/LeaveRoom';
27
28
  import { ToastManager } from '../components/Toast/ToastManager';
28
29
  import { Button } from '../../Button';
29
30
  import { IconButton } from '../../IconButton';
@@ -58,6 +59,7 @@ const HLSView = () => {
58
59
  const [hasCaptions, setHasCaptions] = useState(false);
59
60
  const [currentSelectedQuality, setCurrentSelectedQuality] = useState(null);
60
61
  const [isHlsAutoplayBlocked, setIsHlsAutoplayBlocked] = useState(false);
62
+ const [isSeekEnabled, setIsSeekEnabled] = useState(false);
61
63
  const [isPaused, setIsPaused] = useState(false);
62
64
  const [show, toggle] = useToggle(false);
63
65
  const lastHlsUrl = usePrevious(hlsUrl);
@@ -276,13 +278,13 @@ const HLSView = () => {
276
278
  if (controlsTimerRef.current) {
277
279
  clearTimeout(controlsTimerRef.current);
278
280
  }
279
- controlsTimerRef.current = setTimeout(() => {
280
- setControlsVisible(false);
281
- }, 5000);
282
281
  }
283
282
  if (!isFullScreen && controlsTimerRef.current) {
284
283
  clearTimeout(controlsTimerRef.current);
285
284
  }
285
+ controlsTimerRef.current = setTimeout(() => {
286
+ setControlsVisible(false);
287
+ }, 5000);
286
288
  return () => {
287
289
  if (controlsTimerRef.current) {
288
290
  clearTimeout(controlsTimerRef.current);
@@ -290,31 +292,43 @@ const HLSView = () => {
290
292
  };
291
293
  }, [controlsVisible, isFullScreen, qualityDropDownOpen]);
292
294
 
293
- const onTouchHandler = useCallback(
295
+ const onSeekTo = useCallback(seek => {
296
+ hlsPlayer?.seekTo(videoRef.current?.currentTime + seek);
297
+ }, []);
298
+ const onDoubleClickHandler = useCallback(
294
299
  event => {
295
- event.preventDefault();
296
- // logic for invisible when tapping
297
- if (event.type === 'ontouchstart' && controlsVisible) {
298
- setControlsVisible(false);
300
+ if (!(isMobile || isLandscape)) {
299
301
  return;
300
302
  }
301
- // normal scemnario
302
- if (event.type === 'ontouchstart' || qualityDropDownOpen) {
303
- setControlsVisible(true);
304
- return;
305
- }
306
- if (isFullScreen && !controlsVisible && event.type === 'touchmove') {
307
- setControlsVisible(true);
308
- if (controlsTimerRef.current) {
309
- clearTimeout(controlsTimerRef.current);
310
- }
303
+ const sidePercentage = (event.screenX * 100) / event.target.clientWidth;
304
+ setIsSeekEnabled(true);
305
+ // there is space for pause/unpause button
306
+ if (sidePercentage < 45) {
307
+ onSeekTo(-10);
308
+ } else {
309
+ onSeekTo(10);
311
310
  }
311
+ setTimeout(() => {
312
+ setIsSeekEnabled(false);
313
+ }, 200);
312
314
  },
313
- [controlsVisible, isFullScreen, qualityDropDownOpen],
315
+ [isLandscape, isMobile, onSeekTo],
314
316
  );
317
+ const onClickHandler = useCallback(() => {
318
+ if (!(isMobile || isLandscape)) {
319
+ return;
320
+ }
321
+ setControlsVisible(value => !value);
322
+ if (controlsTimerRef.current) {
323
+ clearTimeout(controlsTimerRef.current);
324
+ }
325
+ }, [isLandscape, isMobile]);
315
326
  const onHoverHandler = useCallback(
316
327
  event => {
317
- // normal scemnario
328
+ event.preventDefault();
329
+ if (isMobile || isLandscape) {
330
+ return;
331
+ }
318
332
  if (event.type === 'mouseenter' || qualityDropDownOpen) {
319
333
  setControlsVisible(true);
320
334
  return;
@@ -328,7 +342,7 @@ const HLSView = () => {
328
342
  }
329
343
  }
330
344
  },
331
- [controlsVisible, isFullScreen, qualityDropDownOpen],
345
+ [controlsVisible, isFullScreen, isLandscape, isMobile, qualityDropDownOpen],
332
346
  );
333
347
 
334
348
  return (
@@ -338,8 +352,9 @@ const HLSView = () => {
338
352
  ref={hlsViewRef}
339
353
  direction={isMobile || isLandscape ? 'column' : 'row'}
340
354
  css={{
341
- w: sidepane !== '' && isLandscape ? '55%' : '100%',
355
+ w: sidepane !== '' && isLandscape ? '' : '100%',
342
356
  h: sidepane !== '' && isMobile ? '36%' : '100%',
357
+ flex: '1 1 0',
343
358
  }}
344
359
  >
345
360
  {hlsUrl && !streamEnded ? (
@@ -357,7 +372,9 @@ const HLSView = () => {
357
372
  margin: '0 auto',
358
373
  }}
359
374
  >
360
- <HLSAutoplayBlockedPrompt open={isHlsAutoplayBlocked} unblockAutoPlay={unblockAutoPlay} />
375
+ {!(isMobile || isLandscape) && (
376
+ <HLSAutoplayBlockedPrompt open={isHlsAutoplayBlocked} unblockAutoPlay={unblockAutoPlay} />
377
+ )}
361
378
  {showLoader && (
362
379
  <Flex
363
380
  align="center"
@@ -374,49 +391,55 @@ const HLSView = () => {
374
391
  onMouseEnter={onHoverHandler}
375
392
  onMouseMove={onHoverHandler}
376
393
  onMouseLeave={onHoverHandler}
377
- onTouchStart={onTouchHandler}
378
- onTouchMove={onTouchHandler}
394
+ onClick={onClickHandler}
395
+ onDoubleClick={e => {
396
+ onDoubleClickHandler(e);
397
+ }}
379
398
  >
380
399
  <>
381
400
  {isMobile || isLandscape ? (
382
401
  <>
383
402
  {!showLoader && (
384
- <Box
403
+ <Flex
404
+ align="center"
405
+ justify="center"
385
406
  css={{
386
- position: 'absolute',
387
- top: '40%',
388
- left: '50%',
389
- transform: 'translateY(-40%) translateX(-50%)',
390
- padding: '$4',
407
+ bg: '#00000066',
391
408
  display: 'inline-flex',
392
- r: '$round',
393
- gap: '$1',
394
- bg: 'rgba(0, 0, 0, 0.6)',
409
+ gap: '$2',
395
410
  zIndex: 1,
411
+ size: '100%',
396
412
  visibility: controlsVisible ? `` : `hidden`,
397
413
  opacity: controlsVisible ? `1` : '0',
398
414
  }}
399
415
  >
400
- {isPaused ? (
401
- <IconButton
402
- onClick={async () => {
403
- await hlsPlayer?.play();
404
- }}
405
- data-testid="play_btn"
406
- >
407
- <PlayIcon width="48px" height="48px" />
408
- </IconButton>
409
- ) : (
410
- <IconButton
411
- onClick={async () => {
412
- await hlsPlayer?.pause();
413
- }}
414
- data-testid="pause_btn"
415
- >
416
- <PauseIcon width="48px" height="48px" />
417
- </IconButton>
418
- )}
419
- </Box>
416
+ <HMSVideoPlayer.Seeker
417
+ title="backward"
418
+ css={{
419
+ visibility: isSeekEnabled ? `` : `hidden`,
420
+ opacity: isSeekEnabled ? `1` : '0',
421
+ }}
422
+ >
423
+ <BackwardArrowIcon width={32} height={32} />
424
+ </HMSVideoPlayer.Seeker>
425
+ <Box
426
+ css={{
427
+ bg: 'rgba(0, 0, 0, 0.6)',
428
+ r: '$round',
429
+ }}
430
+ >
431
+ <HMSVideoPlayer.PlayPauseButton isPaused={isPaused} width={48} height={48} />
432
+ </Box>
433
+ <HMSVideoPlayer.Seeker
434
+ title="forward"
435
+ css={{
436
+ visibility: isSeekEnabled ? `` : `hidden`,
437
+ opacity: isSeekEnabled ? `1` : '0',
438
+ }}
439
+ >
440
+ <ForwardArrowIcon width={32} height={32} />
441
+ </HMSVideoPlayer.Seeker>
442
+ </Flex>
420
443
  )}
421
444
  <Flex
422
445
  ref={controlsRef}
@@ -429,6 +452,7 @@ const HLSView = () => {
429
452
  left: '0',
430
453
  width: '100%',
431
454
  flexShrink: 0,
455
+ zIndex: 1,
432
456
  visibility: controlsVisible ? `` : `hidden`,
433
457
  opacity: controlsVisible ? `1` : '0',
434
458
  }}
@@ -438,10 +462,15 @@ const HLSView = () => {
438
462
  p: '$4 $8',
439
463
  }}
440
464
  >
465
+ <HMSVideoPlayer.Controls.Left>
466
+ <LeaveRoom screenType={screenType} />
467
+ </HMSVideoPlayer.Controls.Left>
441
468
  <HMSVideoPlayer.Controls.Right>
442
469
  {isLandscape && <ChatToggle />}
443
- {hasCaptions && <HLSCaptionSelector isEnabled={isCaptionEnabled} />}
444
- {availableLayers.length > 0 ? (
470
+ {hasCaptions && !isHlsAutoplayBlocked && (
471
+ <HLSCaptionSelector isEnabled={isCaptionEnabled} />
472
+ )}
473
+ {availableLayers.length > 0 && !isHlsAutoplayBlocked ? (
445
474
  <HLSQualitySelector
446
475
  layers={availableLayers}
447
476
  onOpenChange={setQualityDropDownOpen}
@@ -451,6 +480,7 @@ const HLSView = () => {
451
480
  isAuto={isUserSelectedAuto}
452
481
  />
453
482
  ) : null}
483
+ <HLSAutoplayBlockedPrompt open={isHlsAutoplayBlocked} unblockAutoPlay={unblockAutoPlay} />
454
484
  </HMSVideoPlayer.Controls.Right>
455
485
  </HMSVideoPlayer.Controls.Root>
456
486
  </Flex>
@@ -458,13 +488,14 @@ const HLSView = () => {
458
488
  ) : null}
459
489
  <Flex
460
490
  ref={controlsRef}
461
- direction="column"
491
+ direction={isMobile ? 'columnReverse' : 'column'}
462
492
  justify="end"
463
493
  align="start"
464
494
  css={{
465
495
  position: 'absolute',
466
496
  bottom: '0',
467
497
  left: '0',
498
+ zIndex: 1,
468
499
  background:
469
500
  isMobile || isLandscape
470
501
  ? ''
@@ -477,9 +508,7 @@ const HLSView = () => {
477
508
  opacity: controlsVisible ? `1` : '0',
478
509
  }}
479
510
  >
480
- {!(isMobile || isLandscape) && hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR && (
481
- <HMSVideoPlayer.Progress />
482
- )}
511
+ <HMSVideoPlayer.Progress isDvr={hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR} />
483
512
  <HMSVideoPlayer.Controls.Root
484
513
  css={{
485
514
  p: '$4 $8',
@@ -488,12 +517,23 @@ const HLSView = () => {
488
517
  <HMSVideoPlayer.Controls.Left>
489
518
  {!(isMobile || isLandscape) && (
490
519
  <>
491
- <HMSVideoPlayer.PlayButton
492
- onClick={async () => {
493
- isPaused ? await hlsPlayer?.play() : hlsPlayer?.pause();
520
+ <HMSVideoPlayer.Seeker
521
+ onClick={() => {
522
+ onSeekTo(-10);
523
+ }}
524
+ title="backward"
525
+ >
526
+ <BackwardArrowIcon width={20} height={20} />
527
+ </HMSVideoPlayer.Seeker>
528
+ <HMSVideoPlayer.PlayPauseButton isPaused={isPaused} />
529
+ <HMSVideoPlayer.Seeker
530
+ onClick={() => {
531
+ onSeekTo(10);
494
532
  }}
495
- isPaused={isPaused}
496
- />
533
+ title="forward"
534
+ >
535
+ <ForwardArrowIcon width={20} height={20} />
536
+ </HMSVideoPlayer.Seeker>
497
537
  {!isVideoLive ? <HMSVideoPlayer.Duration /> : null}
498
538
  <HMSVideoPlayer.Volume />
499
539
  </>
@@ -551,9 +591,6 @@ const HLSView = () => {
551
591
  ) : null}
552
592
  </HMSVideoPlayer.Controls.Right>
553
593
  </HMSVideoPlayer.Controls.Root>
554
- {(isMobile || isLandscape) && hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR ? (
555
- <HMSVideoPlayer.Progress />
556
- ) : null}
557
594
  </Flex>
558
595
  </>
559
596
  </HMSVideoPlayer.Root>
@@ -564,6 +601,9 @@ const HLSView = () => {
564
601
  </>
565
602
  ) : (
566
603
  <Flex align="center" justify="center" direction="column" css={{ size: '100%', px: '$10' }}>
604
+ <Flex align="center" gap="2" css={{ position: 'absolute', left: '$4', top: '$4', zIndex: 1 }}>
605
+ <LeaveRoom screenType={screenType} />
606
+ </Flex>
567
607
  <Flex css={{ c: '$on_surface_high', r: '$round', bg: '$surface_default', p: '$2' }}>
568
608
  {streamEnded ? <ColoredHandIcon height={56} width={56} /> : <GoLiveIcon height={56} width={56} />}
569
609
  </Flex>