@100mslive/roomkit-react 0.2.8-alpha.4 → 0.2.8-alpha.6

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 (49) hide show
  1. package/dist/{HLSView-UIPDGADR.js → HLSView-53PDKIS2.js} +239 -144
  2. package/dist/HLSView-53PDKIS2.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-J4NOQ2YL.js → chunk-2ZFAT7KY.js} +339 -218
  10. package/dist/chunk-2ZFAT7KY.js.map +7 -0
  11. package/dist/index.cjs.js +932 -708
  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 +17 -2
  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 +5 -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-UIPDGADR.js.map +0 -7
  48. package/dist/chunk-J4NOQ2YL.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>