@100mslive/roomkit-react 0.2.8-alpha.8 → 0.2.8-alpha.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,
@@ -32,12 +33,59 @@ const optionTextCSS = {
32
33
  whiteSpace: 'nowrap',
33
34
  };
34
35
 
35
- const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleName: string }) => {
36
- const vanillaStore = useHMSVanillaStore();
37
- const store = vanillaStore.getState();
38
- 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
+ React.Children.map(children, child => {
42
+ console.log({ child });
43
+ });
44
+ return (
45
+ <Dropdown.Root open={openOptions} onOpenChange={setOpenOptions}>
46
+ <Dropdown.Trigger
47
+ data-testid="role_group_options"
48
+ onClick={e => e.stopPropagation()}
49
+ className="role_actions"
50
+ asChild
51
+ css={{
52
+ p: '$1',
53
+ r: '$0',
54
+ c: '$on_surface_high',
55
+ visibility: openOptions ? 'visible' : 'hidden',
56
+ '&:hover': {
57
+ c: '$on_surface_medium',
58
+ },
59
+ '@md': {
60
+ visibility: 'visible',
61
+ },
62
+ }}
63
+ >
64
+ <Flex>
65
+ <VerticalMenuIcon />
66
+ </Flex>
67
+ </Dropdown.Trigger>
68
+ <Dropdown.Content
69
+ onClick={e => e.stopPropagation()}
70
+ css={{ w: 'max-content', bg: '$surface_default', py: 0 }}
71
+ align="end"
72
+ >
73
+ {children}
74
+ </Dropdown.Content>
75
+ </Dropdown.Root>
76
+ );
77
+ };
78
+
79
+ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList: HMSPeer[] }) => {
39
80
  const permissions = useHMSStore(selectPermissions);
81
+ const hmsActions = useHMSActions();
82
+ const { elements } = useRoomLayoutConferencingScreen();
83
+ const { on_stage_role, off_stage_roles = [] } = (elements as DefaultConferencingScreen_Elements)?.on_stage_exp || {};
84
+ const canRemoveRoleFromStage = permissions?.changeRole && roleName === on_stage_role;
40
85
  const role = useHMSStore(selectRoleByRoleName(roleName));
86
+ const canPublishAudio = role.publishParams.allowed.includes('audio');
87
+ const canPublishVideo = role.publishParams.allowed.includes('video');
88
+ const tracks = useHMSStore(selectTracksMap);
41
89
 
42
90
  let isVideoOnForSomePeers = false;
43
91
  let isAudioOnForSomePeers = false;
@@ -46,8 +94,8 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
46
94
  if (peer.isLocal) {
47
95
  return;
48
96
  }
49
- const isAudioOn = !!peer.audioTrack && store.tracks[peer.audioTrack]?.enabled;
50
- const isVideoOn = !!peer.videoTrack && store.tracks[peer.videoTrack]?.enabled;
97
+ const isAudioOn = !!peer.audioTrack && tracks[peer.audioTrack]?.enabled;
98
+ const isVideoOn = !!peer.videoTrack && tracks[peer.videoTrack]?.enabled;
51
99
  isAudioOnForSomePeers = isAudioOnForSomePeers || isAudioOn;
52
100
  isVideoOnForSomePeers = isVideoOnForSomePeers || isVideoOn;
53
101
  });
@@ -60,75 +108,11 @@ const MuteUnmuteOption = ({ roleName, peerList }: { peerList: HMSPeer[]; roleNam
60
108
  }
61
109
  };
62
110
 
63
- if (!role) {
64
- return null;
65
- }
66
-
67
- return (
68
- <>
69
- {role.publishParams.allowed.includes('audio') && (
70
- <>
71
- {isAudioOnForSomePeers && permissions?.mute ? (
72
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', false)}>
73
- <MicOffIcon />
74
- <Text variant="sm" css={optionTextCSS}>
75
- Mute Audio for All
76
- </Text>
77
- </Dropdown.Item>
78
- ) : null}
79
-
80
- {!isAudioOnForSomePeers && permissions?.unmute ? (
81
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('audio', true)}>
82
- <MicOnIcon />
83
- <Text variant="sm" css={optionTextCSS}>
84
- Request to Unmute Audio for All
85
- </Text>
86
- </Dropdown.Item>
87
- ) : null}
88
- </>
89
- )}
90
-
91
- {role.publishParams.allowed.includes('video') && (
92
- <>
93
- {isVideoOnForSomePeers && permissions?.mute ? (
94
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', false)}>
95
- <VideoOffIcon />
96
- <Text variant="sm" css={optionTextCSS}>
97
- Mute Video for All
98
- </Text>
99
- </Dropdown.Item>
100
- ) : null}
101
-
102
- {!isVideoOnForSomePeers && permissions?.unmute ? (
103
- <Dropdown.Item css={dropdownItemCSS} onClick={() => setTrackEnabled('video', true)}>
104
- <VideoOnIcon />
105
- <Text variant="sm" css={optionTextCSS}>
106
- Request to Unmute Video for All
107
- </Text>
108
- </Dropdown.Item>
109
- ) : null}
110
- </>
111
- )}
112
- </>
113
- );
114
- };
115
-
116
- export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList: HMSPeer[] }) => {
117
- const [openOptions, setOpenOptions] = useState(false);
118
- const permissions = useHMSStore(selectPermissions);
119
- const hmsActions = useHMSActions();
120
- const { elements } = useRoomLayoutConferencingScreen();
121
- const { on_stage_role, off_stage_roles = [] } = (elements as DefaultConferencingScreen_Elements)?.on_stage_exp || {};
122
- const canMuteOrUnmute = permissions?.mute || permissions?.unmute;
123
- const canRemoveRoleFromStage = permissions?.changeRole && roleName === on_stage_role;
124
- const role = useHMSStore(selectRoleByRoleName(roleName));
125
-
126
111
  // on stage and off stage roles
127
112
  const canRemoveRoleFromRoom =
128
113
  permissions?.removeOthers && (on_stage_role === roleName || off_stage_roles?.includes(roleName));
129
114
 
130
115
  if (
131
- !(canMuteOrUnmute || canRemoveRoleFromStage || canRemoveRoleFromRoom) ||
132
116
  peerList.length === 0 ||
133
117
  // if only local peer is present no need to show any options
134
118
  (peerList.length === 1 && peerList[0].isLocal) ||
@@ -157,60 +141,75 @@ export const RoleOptions = ({ roleName, peerList }: { roleName: string; peerList
157
141
  };
158
142
 
159
143
  return (
160
- <Dropdown.Root open={openOptions} onOpenChange={setOpenOptions}>
161
- <Dropdown.Trigger
162
- data-testid="role_group_options"
163
- onClick={e => e.stopPropagation()}
164
- className="role_actions"
165
- asChild
166
- css={{
167
- p: '$1',
168
- r: '$0',
169
- c: '$on_surface_high',
170
- visibility: openOptions ? 'visible' : 'hidden',
171
- '&:hover': {
172
- c: '$on_surface_medium',
173
- },
174
- '@md': {
175
- visibility: 'visible',
176
- },
177
- }}
178
- >
179
- <Flex>
180
- <VerticalMenuIcon />
181
- </Flex>
182
- </Dropdown.Trigger>
183
- <Dropdown.Content
184
- onClick={e => e.stopPropagation()}
185
- css={{ w: 'max-content', bg: '$surface_default', py: 0 }}
186
- align="end"
187
- >
188
- {canRemoveRoleFromStage && (
189
- <Dropdown.Item
190
- css={{ ...dropdownItemCSS, borderBottom: '1px solid $border_bright' }}
191
- onClick={removeAllFromStage}
192
- >
193
- <PersonRectangleIcon />
194
- <Text variant="sm" css={optionTextCSS}>
195
- Remove all from Stage
196
- </Text>
197
- </Dropdown.Item>
198
- )}
199
-
200
- {canMuteOrUnmute && <MuteUnmuteOption peerList={peerList} roleName={roleName} />}
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
- )}
213
- </Dropdown.Content>
214
- </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>
215
214
  );
216
215
  };
@@ -3,7 +3,7 @@ import { Box, Flex, Slider } from '../../..';
3
3
  import { useHMSPlayerContext } from './PlayerContext';
4
4
  import { getPercentage } from './utils';
5
5
 
6
- export const VideoProgress = ({ isDvr = true }: { isDvr: boolean }) => {
6
+ export const VideoProgress = () => {
7
7
  const { hlsPlayer } = useHMSPlayerContext();
8
8
  const [videoProgress, setVideoProgress] = useState<number>(0);
9
9
  const [bufferProgress, setBufferProgress] = useState(0);
@@ -20,10 +20,11 @@ export const VideoProgress = ({ isDvr = true }: { isDvr: boolean }) => {
20
20
  if (!videoEl) {
21
21
  return;
22
22
  }
23
- const videoProgress = Math.floor(getPercentage(videoEl.currentTime, videoEl.duration));
23
+ const duration = isFinite(videoEl.duration) ? videoEl.duration : videoEl.seekable?.end(0) || 0;
24
+ const videoProgress = Math.floor(getPercentage(videoEl.currentTime, duration));
24
25
  let bufferProgress = 0;
25
26
  if (videoEl.buffered.length > 0) {
26
- bufferProgress = Math.floor(getPercentage(videoEl.buffered?.end(0), videoEl.duration));
27
+ bufferProgress = Math.floor(getPercentage(videoEl.buffered?.end(0), duration));
27
28
  }
28
29
 
29
30
  setVideoProgress(isNaN(videoProgress) ? 0 : videoProgress);
@@ -48,7 +49,7 @@ export const VideoProgress = ({ isDvr = true }: { isDvr: boolean }) => {
48
49
  return null;
49
50
  }
50
51
  return (
51
- <Flex align="center" css={{ cursor: 'pointer', h: '$2', alignSelf: 'stretch', pointerEvents: isDvr ? '' : 'none' }}>
52
+ <Flex align="center" css={{ cursor: 'pointer', h: '$2', alignSelf: 'stretch' }}>
52
53
  <Slider
53
54
  id="video-actual-rest"
54
55
  css={{
@@ -56,7 +57,6 @@ export const VideoProgress = ({ isDvr = true }: { isDvr: boolean }) => {
56
57
  h: '$2',
57
58
  zIndex: 1,
58
59
  transition: `all .2s ease .5s`,
59
- pointerEvents: isDvr ? '' : 'none',
60
60
  }}
61
61
  min={0}
62
62
  max={100}
@@ -64,7 +64,7 @@ export const VideoProgress = ({ isDvr = true }: { isDvr: boolean }) => {
64
64
  value={[videoProgress]}
65
65
  showTooltip={false}
66
66
  onValueChange={onProgress}
67
- thumbStyles={{ w: '$6', h: '$6', display: isDvr ? '' : 'none' }}
67
+ thumbStyles={{ w: '$6', h: '$6' }}
68
68
  />
69
69
  <Box
70
70
  id="video-buffer"
@@ -16,7 +16,7 @@ export const getFormattedTime = (milliseconds: number | undefined, precise = tru
16
16
  if (!precise && (hours || minutes)) {
17
17
  return formattedTime;
18
18
  }
19
- formattedTime += `${minutes >= 1 ? Math.floor(seconds) : seconds.toFixed(3)}s`;
19
+ formattedTime += `${precise ? seconds.toFixed(3) : Math.floor(seconds)}s`;
20
20
 
21
21
  return formattedTime;
22
22
  };
@@ -299,7 +299,7 @@ const HLSView = () => {
299
299
  }, []);
300
300
  const onDoubleClickHandler = useCallback(
301
301
  event => {
302
- if (!(isMobile || isLandscape)) {
302
+ if (!(isMobile || isLandscape) || hlsState?.variants[0]?.playlist_type !== HLSPlaylistType.DVR) {
303
303
  return;
304
304
  }
305
305
  const sidePercentage = (event.screenX * 100) / event.target.clientWidth;
@@ -314,7 +314,7 @@ const HLSView = () => {
314
314
  setIsSeekEnabled(false);
315
315
  }, 200);
316
316
  },
317
- [isLandscape, isMobile, onSeekTo],
317
+ [hlsState?.variants, isLandscape, isMobile, onSeekTo],
318
318
  );
319
319
  const onClickHandler = useCallback(() => {
320
320
  if (!(isMobile || isLandscape)) {
@@ -401,7 +401,7 @@ const HLSView = () => {
401
401
  <>
402
402
  {isMobile || isLandscape ? (
403
403
  <>
404
- {!showLoader && (
404
+ {!showLoader && hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR && (
405
405
  <Flex
406
406
  align="center"
407
407
  justify="center"
@@ -511,7 +511,7 @@ const HLSView = () => {
511
511
  opacity: controlsVisible ? `1` : '0',
512
512
  }}
513
513
  >
514
- <HMSVideoPlayer.Progress isDvr={hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR} />
514
+ {hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR ? <HMSVideoPlayer.Progress /> : null}
515
515
  <HMSVideoPlayer.Controls.Root
516
516
  css={{
517
517
  p: '$4 $8',
@@ -520,24 +520,28 @@ const HLSView = () => {
520
520
  <HMSVideoPlayer.Controls.Left>
521
521
  {!(isMobile || isLandscape) && (
522
522
  <>
523
- <HMSVideoPlayer.Seeker
524
- onClick={() => {
525
- onSeekTo(-10);
526
- }}
527
- title="backward"
528
- >
529
- <BackwardArrowIcon width={20} height={20} />
530
- </HMSVideoPlayer.Seeker>
531
- <HMSVideoPlayer.PlayPauseButton isPaused={isPaused} />
532
- <HMSVideoPlayer.Seeker
533
- onClick={() => {
534
- onSeekTo(10);
535
- }}
536
- title="forward"
537
- >
538
- <ForwardArrowIcon width={20} height={20} />
539
- </HMSVideoPlayer.Seeker>
540
- {!isVideoLive ? <HMSVideoPlayer.Duration /> : null}
523
+ {hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR ? (
524
+ <>
525
+ <HMSVideoPlayer.Seeker
526
+ onClick={() => {
527
+ onSeekTo(-10);
528
+ }}
529
+ title="backward"
530
+ >
531
+ <BackwardArrowIcon width={20} height={20} />
532
+ </HMSVideoPlayer.Seeker>
533
+ <HMSVideoPlayer.PlayPauseButton isPaused={isPaused} />
534
+ <HMSVideoPlayer.Seeker
535
+ onClick={() => {
536
+ onSeekTo(10);
537
+ }}
538
+ title="forward"
539
+ >
540
+ <ForwardArrowIcon width={20} height={20} />
541
+ </HMSVideoPlayer.Seeker>
542
+ {!isVideoLive ? <HMSVideoPlayer.Duration /> : null}
543
+ </>
544
+ ) : null}
541
545
  <HMSVideoPlayer.Volume />
542
546
  </>
543
547
  )}
@@ -572,7 +576,11 @@ const HLSView = () => {
572
576
  </Flex>
573
577
  </Tooltip>
574
578
  </IconButton>
575
- {(isMobile || isLandscape) && !isVideoLive ? <HMSVideoPlayer.Duration /> : null}
579
+ {(isMobile || isLandscape) &&
580
+ !isVideoLive &&
581
+ hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR ? (
582
+ <HMSVideoPlayer.Duration />
583
+ ) : null}
576
584
  </HMSVideoPlayer.Controls.Left>
577
585
 
578
586
  <HMSVideoPlayer.Controls.Right>