@stream-io/video-react-sdk 0.3.28 → 0.3.30
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.
- package/CHANGELOG.md +19 -0
- package/README.md +7 -5
- package/dist/css/styles.css +100 -0
- package/dist/css/styles.css.map +1 -1
- package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js +2 -1
- package/dist/src/components/CallParticipantsList/CallParticipantListingItem.js.map +1 -1
- package/dist/src/core/components/Audio/ParticipantsAudio.d.ts +14 -0
- package/dist/src/core/components/Audio/ParticipantsAudio.js +11 -0
- package/dist/src/core/components/Audio/ParticipantsAudio.js.map +1 -0
- package/dist/src/core/components/Audio/index.d.ts +1 -0
- package/dist/src/core/components/Audio/index.js +1 -0
- package/dist/src/core/components/Audio/index.js.map +1 -1
- package/dist/src/core/components/CallLayout/LivestreamLayout.d.ts +39 -0
- package/dist/src/core/components/CallLayout/LivestreamLayout.js +91 -0
- package/dist/src/core/components/CallLayout/LivestreamLayout.js.map +1 -0
- package/dist/src/core/components/CallLayout/PaginatedGridLayout.js +5 -3
- package/dist/src/core/components/CallLayout/PaginatedGridLayout.js.map +1 -1
- package/dist/src/core/components/CallLayout/SpeakerLayout.d.ts +7 -2
- package/dist/src/core/components/CallLayout/SpeakerLayout.js +32 -37
- package/dist/src/core/components/CallLayout/SpeakerLayout.js.map +1 -1
- package/dist/src/core/components/CallLayout/hooks.d.ts +3 -0
- package/dist/src/core/components/CallLayout/hooks.js +41 -0
- package/dist/src/core/components/CallLayout/hooks.js.map +1 -0
- package/dist/src/core/components/CallLayout/index.d.ts +1 -0
- package/dist/src/core/components/CallLayout/index.js +1 -0
- package/dist/src/core/components/CallLayout/index.js.map +1 -1
- package/dist/src/core/hooks/useCalculateHardLimit.d.ts +4 -0
- package/dist/src/core/hooks/useCalculateHardLimit.js +56 -0
- package/dist/src/core/hooks/useCalculateHardLimit.js.map +1 -0
- package/dist/src/translations/en.json +1 -0
- package/dist/src/translations/index.d.ts +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/src/components/CallParticipantsList/CallParticipantListingItem.tsx +2 -1
- package/src/core/components/Audio/ParticipantsAudio.tsx +35 -0
- package/src/core/components/Audio/index.ts +1 -0
- package/src/core/components/CallLayout/LivestreamLayout.tsx +231 -0
- package/src/core/components/CallLayout/PaginatedGridLayout.tsx +33 -36
- package/src/core/components/CallLayout/SpeakerLayout.tsx +74 -56
- package/src/core/components/CallLayout/hooks.ts +54 -0
- package/src/core/components/CallLayout/index.ts +1 -0
- package/src/core/hooks/useCalculateHardLimit.ts +72 -0
- package/src/translations/en.json +2 -0
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
ParticipantView,
|
|
12
12
|
ParticipantViewProps,
|
|
13
13
|
} from '../ParticipantView';
|
|
14
|
-
import {
|
|
14
|
+
import { ParticipantsAudio } from '../Audio';
|
|
15
15
|
import { IconButton } from '../../../components';
|
|
16
16
|
import { chunk } from '../../../utilities';
|
|
17
|
+
import { usePaginatedLayoutSortPreset } from './hooks';
|
|
17
18
|
|
|
18
19
|
const GROUP_SIZE = 16;
|
|
19
20
|
|
|
@@ -85,6 +86,8 @@ export const PaginatedGridLayout = ({
|
|
|
85
86
|
// used to render audio elements
|
|
86
87
|
const remoteParticipants = useRemoteParticipants();
|
|
87
88
|
|
|
89
|
+
usePaginatedLayoutSortPreset(call);
|
|
90
|
+
|
|
88
91
|
// only used to render video elements
|
|
89
92
|
const participantGroups = useMemo(
|
|
90
93
|
() =>
|
|
@@ -109,41 +112,35 @@ export const PaginatedGridLayout = ({
|
|
|
109
112
|
if (!call) return null;
|
|
110
113
|
|
|
111
114
|
return (
|
|
112
|
-
|
|
113
|
-
{remoteParticipants
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
Math.min(pageCount - 1, currentPage + 1),
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
/>
|
|
144
|
-
)}
|
|
145
|
-
</div>
|
|
115
|
+
<div className="str-video__paginated-grid-layout__wrapper">
|
|
116
|
+
<ParticipantsAudio participants={remoteParticipants} />
|
|
117
|
+
<div className="str-video__paginated-grid-layout">
|
|
118
|
+
{pageArrowsVisible && pageCount > 1 && (
|
|
119
|
+
<IconButton
|
|
120
|
+
icon="caret-left"
|
|
121
|
+
disabled={page === 0}
|
|
122
|
+
onClick={() =>
|
|
123
|
+
setPage((currentPage) => Math.max(0, currentPage - 1))
|
|
124
|
+
}
|
|
125
|
+
/>
|
|
126
|
+
)}
|
|
127
|
+
{selectedGroup && (
|
|
128
|
+
<PaginatedGridLayoutGroup
|
|
129
|
+
group={participantGroups[page]}
|
|
130
|
+
VideoPlaceholder={VideoPlaceholder}
|
|
131
|
+
ParticipantViewUI={ParticipantViewUI}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
{pageArrowsVisible && pageCount > 1 && (
|
|
135
|
+
<IconButton
|
|
136
|
+
disabled={page === pageCount - 1}
|
|
137
|
+
icon="caret-right"
|
|
138
|
+
onClick={() =>
|
|
139
|
+
setPage((currentPage) => Math.min(pageCount - 1, currentPage + 1))
|
|
140
|
+
}
|
|
141
|
+
/>
|
|
142
|
+
)}
|
|
146
143
|
</div>
|
|
147
|
-
|
|
144
|
+
</div>
|
|
148
145
|
);
|
|
149
146
|
};
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { useEffect, useState } from 'react';
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
CallTypes,
|
|
5
|
-
combineComparators,
|
|
6
|
-
Comparator,
|
|
7
|
-
defaultSortPreset,
|
|
8
|
-
screenSharing,
|
|
9
|
-
SfuModels,
|
|
10
|
-
speakerLayoutSortPreset,
|
|
11
|
-
StreamVideoParticipant,
|
|
12
|
-
} from '@stream-io/video-client';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { SfuModels, StreamVideoParticipant } from '@stream-io/video-client';
|
|
13
4
|
import { useCall, useCallStateHooks } from '@stream-io/video-react-bindings';
|
|
14
5
|
|
|
15
6
|
import {
|
|
@@ -22,16 +13,23 @@ import {
|
|
|
22
13
|
useHorizontalScrollPosition,
|
|
23
14
|
useVerticalScrollPosition,
|
|
24
15
|
} from '../../../hooks';
|
|
25
|
-
import
|
|
16
|
+
import { useSpeakerLayoutSortPreset } from './hooks';
|
|
17
|
+
import { useCalculateHardLimit } from '../../hooks/useCalculateHardLimit';
|
|
18
|
+
import { ParticipantsAudio } from '../Audio';
|
|
26
19
|
|
|
27
20
|
export type SpeakerLayoutProps = {
|
|
28
21
|
ParticipantViewUISpotlight?: ParticipantViewProps['ParticipantViewUI'];
|
|
29
22
|
ParticipantViewUIBar?: ParticipantViewProps['ParticipantViewUI'];
|
|
30
23
|
/**
|
|
31
|
-
* The position of the
|
|
24
|
+
* The position of the participants who are not in focus.
|
|
32
25
|
* Providing `null` will hide the bar.
|
|
33
26
|
*/
|
|
34
27
|
participantsBarPosition?: 'top' | 'bottom' | 'left' | 'right' | null;
|
|
28
|
+
/**
|
|
29
|
+
* Hard limits the number of the participants rendered in the participants bar.
|
|
30
|
+
* Providing string `dynamic` will calculate hard limit based on screen width/height.
|
|
31
|
+
*/
|
|
32
|
+
participantsBarLimit?: 'dynamic' | number;
|
|
35
33
|
} & Pick<ParticipantViewProps, 'VideoPlaceholder'>;
|
|
36
34
|
|
|
37
35
|
const DefaultParticipantViewUIBar = () => (
|
|
@@ -45,45 +43,61 @@ export const SpeakerLayout = ({
|
|
|
45
43
|
ParticipantViewUISpotlight = DefaultParticipantViewUISpotlight,
|
|
46
44
|
VideoPlaceholder,
|
|
47
45
|
participantsBarPosition = 'bottom',
|
|
46
|
+
participantsBarLimit,
|
|
48
47
|
}: SpeakerLayoutProps) => {
|
|
49
48
|
const call = useCall();
|
|
50
|
-
const { useParticipants } = useCallStateHooks();
|
|
49
|
+
const { useParticipants, useRemoteParticipants } = useCallStateHooks();
|
|
51
50
|
const [participantInSpotlight, ...otherParticipants] = useParticipants();
|
|
52
|
-
const
|
|
53
|
-
|
|
51
|
+
const remoteParticipants = useRemoteParticipants();
|
|
52
|
+
const [participantsBarWrapperElement, setParticipantsBarWrapperElement] =
|
|
53
|
+
useState<HTMLDivElement | null>(null);
|
|
54
|
+
const [participantsBarElement, setParticipantsBarElement] =
|
|
55
|
+
useState<HTMLDivElement | null>(null);
|
|
56
|
+
const [buttonsWrapperElement, setButtonsWrapperElement] =
|
|
57
|
+
useState<HTMLDivElement | null>(null);
|
|
58
|
+
|
|
59
|
+
const isSpeakerScreenSharing = hasScreenShare(participantInSpotlight);
|
|
60
|
+
const hardLimit = useCalculateHardLimit(
|
|
61
|
+
buttonsWrapperElement,
|
|
62
|
+
participantsBarElement,
|
|
63
|
+
participantsBarLimit,
|
|
54
64
|
);
|
|
55
|
-
|
|
65
|
+
|
|
66
|
+
const isVertical =
|
|
67
|
+
participantsBarPosition === 'left' || participantsBarPosition === 'right';
|
|
68
|
+
const isHorizontal =
|
|
69
|
+
participantsBarPosition === 'top' || participantsBarPosition === 'bottom';
|
|
56
70
|
|
|
57
71
|
useEffect(() => {
|
|
58
|
-
if (!
|
|
72
|
+
if (!participantsBarWrapperElement || !call) return;
|
|
59
73
|
|
|
60
|
-
const cleanup = call.setViewport(
|
|
74
|
+
const cleanup = call.setViewport(participantsBarWrapperElement);
|
|
61
75
|
return () => cleanup();
|
|
62
|
-
}, [
|
|
76
|
+
}, [participantsBarWrapperElement, call]);
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
}
|
|
78
|
+
const isOneOnOneCall = otherParticipants.length === 1;
|
|
79
|
+
useSpeakerLayoutSortPreset(call, isOneOnOneCall);
|
|
80
|
+
|
|
81
|
+
let participantsWithAppliedLimit = otherParticipants;
|
|
82
|
+
|
|
83
|
+
if (typeof participantsBarLimit !== 'undefined') {
|
|
84
|
+
const hardLimitToApply = isVertical
|
|
85
|
+
? hardLimit.vertical
|
|
86
|
+
: hardLimit.horizontal;
|
|
87
|
+
|
|
88
|
+
participantsWithAppliedLimit = otherParticipants.slice(
|
|
89
|
+
0,
|
|
90
|
+
// subtract 1 if speaker is sharing screen as
|
|
91
|
+
// that one is rendered independently from otherParticipants array
|
|
92
|
+
hardLimitToApply - (isSpeakerScreenSharing ? 1 : 0),
|
|
93
|
+
);
|
|
94
|
+
}
|
|
81
95
|
|
|
82
96
|
if (!call) return null;
|
|
83
97
|
|
|
84
|
-
const isSpeakerScreenSharing = hasScreenShare(participantInSpotlight);
|
|
85
98
|
return (
|
|
86
99
|
<div className="str-video__speaker-layout__wrapper">
|
|
100
|
+
<ParticipantsAudio participants={remoteParticipants} />
|
|
87
101
|
<div
|
|
88
102
|
className={clsx(
|
|
89
103
|
'str-video__speaker-layout',
|
|
@@ -95,7 +109,7 @@ export const SpeakerLayout = ({
|
|
|
95
109
|
{participantInSpotlight && (
|
|
96
110
|
<ParticipantView
|
|
97
111
|
participant={participantInSpotlight}
|
|
98
|
-
muteAudio={
|
|
112
|
+
muteAudio={true}
|
|
99
113
|
trackType={
|
|
100
114
|
isSpeakerScreenSharing ? 'screenShareTrack' : 'videoTrack'
|
|
101
115
|
}
|
|
@@ -104,13 +118,19 @@ export const SpeakerLayout = ({
|
|
|
104
118
|
/>
|
|
105
119
|
)}
|
|
106
120
|
</div>
|
|
107
|
-
{
|
|
108
|
-
<div
|
|
121
|
+
{participantsWithAppliedLimit.length > 0 && participantsBarPosition && (
|
|
122
|
+
<div
|
|
123
|
+
ref={setButtonsWrapperElement}
|
|
124
|
+
className="str-video__speaker-layout__participants-bar-buttons-wrapper"
|
|
125
|
+
>
|
|
109
126
|
<div
|
|
110
127
|
className="str-video__speaker-layout__participants-bar-wrapper"
|
|
111
|
-
ref={
|
|
128
|
+
ref={setParticipantsBarWrapperElement}
|
|
112
129
|
>
|
|
113
|
-
<div
|
|
130
|
+
<div
|
|
131
|
+
ref={setParticipantsBarElement}
|
|
132
|
+
className="str-video__speaker-layout__participants-bar"
|
|
133
|
+
>
|
|
114
134
|
{isSpeakerScreenSharing && (
|
|
115
135
|
<div
|
|
116
136
|
className="str-video__speaker-layout__participant-tile"
|
|
@@ -120,10 +140,11 @@ export const SpeakerLayout = ({
|
|
|
120
140
|
participant={participantInSpotlight}
|
|
121
141
|
ParticipantViewUI={ParticipantViewUIBar}
|
|
122
142
|
VideoPlaceholder={VideoPlaceholder}
|
|
143
|
+
muteAudio={true}
|
|
123
144
|
/>
|
|
124
145
|
</div>
|
|
125
146
|
)}
|
|
126
|
-
{
|
|
147
|
+
{participantsWithAppliedLimit.map((participant) => (
|
|
127
148
|
<div
|
|
128
149
|
className="str-video__speaker-layout__participant-tile"
|
|
129
150
|
key={participant.sessionId}
|
|
@@ -132,18 +153,21 @@ export const SpeakerLayout = ({
|
|
|
132
153
|
participant={participant}
|
|
133
154
|
ParticipantViewUI={ParticipantViewUIBar}
|
|
134
155
|
VideoPlaceholder={VideoPlaceholder}
|
|
156
|
+
muteAudio={true}
|
|
135
157
|
/>
|
|
136
158
|
</div>
|
|
137
159
|
))}
|
|
138
160
|
</div>
|
|
139
161
|
</div>
|
|
140
|
-
{
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
{isVertical && (
|
|
163
|
+
<VerticalScrollButtons
|
|
164
|
+
scrollWrapper={participantsBarWrapperElement}
|
|
165
|
+
/>
|
|
143
166
|
)}
|
|
144
|
-
{
|
|
145
|
-
|
|
146
|
-
|
|
167
|
+
{isHorizontal && (
|
|
168
|
+
<HorizontalScrollButtons
|
|
169
|
+
scrollWrapper={participantsBarWrapperElement}
|
|
170
|
+
/>
|
|
147
171
|
)}
|
|
148
172
|
</div>
|
|
149
173
|
)}
|
|
@@ -222,9 +246,3 @@ const VerticalScrollButtons = <T extends HTMLElement>({
|
|
|
222
246
|
|
|
223
247
|
const hasScreenShare = (p?: StreamVideoParticipant) =>
|
|
224
248
|
!!p?.publishedTracks.includes(SfuModels.TrackType.SCREEN_SHARE);
|
|
225
|
-
|
|
226
|
-
const loggedIn: Comparator<StreamVideoParticipant> = (a, b) => {
|
|
227
|
-
if (a.isLocalParticipant) return 1;
|
|
228
|
-
if (b.isLocalParticipant) return -1;
|
|
229
|
-
return 0;
|
|
230
|
-
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Call,
|
|
4
|
+
CallTypes,
|
|
5
|
+
combineComparators,
|
|
6
|
+
Comparator,
|
|
7
|
+
defaultSortPreset,
|
|
8
|
+
paginatedLayoutSortPreset,
|
|
9
|
+
screenSharing,
|
|
10
|
+
speakerLayoutSortPreset,
|
|
11
|
+
StreamVideoParticipant,
|
|
12
|
+
} from '@stream-io/video-client';
|
|
13
|
+
|
|
14
|
+
export const usePaginatedLayoutSortPreset = (call: Call | undefined) => {
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
if (!call) return;
|
|
17
|
+
call.setSortParticipantsBy(paginatedLayoutSortPreset);
|
|
18
|
+
return () => {
|
|
19
|
+
resetSortPreset(call);
|
|
20
|
+
};
|
|
21
|
+
}, [call]);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const useSpeakerLayoutSortPreset = (
|
|
25
|
+
call: Call | undefined,
|
|
26
|
+
isOneOnOneCall: boolean,
|
|
27
|
+
) => {
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
if (!call) return;
|
|
30
|
+
// always show the remote participant in the spotlight
|
|
31
|
+
if (isOneOnOneCall) {
|
|
32
|
+
call.setSortParticipantsBy(combineComparators(screenSharing, loggedIn));
|
|
33
|
+
} else {
|
|
34
|
+
call.setSortParticipantsBy(speakerLayoutSortPreset);
|
|
35
|
+
}
|
|
36
|
+
return () => {
|
|
37
|
+
resetSortPreset(call);
|
|
38
|
+
};
|
|
39
|
+
}, [call, isOneOnOneCall]);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const resetSortPreset = (call: Call) => {
|
|
43
|
+
// reset the sorting to the default for the call type
|
|
44
|
+
const callConfig = CallTypes.get(call.type);
|
|
45
|
+
call.setSortParticipantsBy(
|
|
46
|
+
callConfig.options.sortParticipantsBy || defaultSortPreset,
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const loggedIn: Comparator<StreamVideoParticipant> = (a, b) => {
|
|
51
|
+
if (a.isLocalParticipant) return 1;
|
|
52
|
+
if (b.isLocalParticipant) return -1;
|
|
53
|
+
return 0;
|
|
54
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
export const useCalculateHardLimit = (
|
|
4
|
+
/**
|
|
5
|
+
* Element that stretches to 100% of the whole layout component
|
|
6
|
+
*/
|
|
7
|
+
wrapperElement: HTMLDivElement | null,
|
|
8
|
+
/**
|
|
9
|
+
* Element that directly hosts individual `ParticipantView` (or wrapper) elements
|
|
10
|
+
*/
|
|
11
|
+
hostElement: HTMLDivElement | null,
|
|
12
|
+
limit?: 'dynamic' | number,
|
|
13
|
+
) => {
|
|
14
|
+
const [calculatedLimit, setCalculatedLimit] = useState<{
|
|
15
|
+
vertical: number;
|
|
16
|
+
horizontal: number;
|
|
17
|
+
}>({
|
|
18
|
+
vertical: typeof limit === 'number' ? limit : 1,
|
|
19
|
+
horizontal: typeof limit === 'number' ? limit : 1,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (
|
|
24
|
+
!hostElement ||
|
|
25
|
+
!wrapperElement ||
|
|
26
|
+
typeof limit === 'number' ||
|
|
27
|
+
typeof limit === 'undefined'
|
|
28
|
+
)
|
|
29
|
+
return;
|
|
30
|
+
|
|
31
|
+
let childWidth: number | null = null;
|
|
32
|
+
let childHeight: number | null = null;
|
|
33
|
+
|
|
34
|
+
const resizeObserver = new ResizeObserver((entries, observer) => {
|
|
35
|
+
// this part should ideally run as little times as possible
|
|
36
|
+
// get child measurements and disconnect
|
|
37
|
+
// does not consider dynamically sized children
|
|
38
|
+
// this hook is for SpeakerLayout use only, where children in the bar are fixed size
|
|
39
|
+
if (entries.length > 1) {
|
|
40
|
+
const child = hostElement.firstChild as HTMLElement | null;
|
|
41
|
+
|
|
42
|
+
if (child) {
|
|
43
|
+
childHeight = child.clientHeight;
|
|
44
|
+
childWidth = child.clientWidth;
|
|
45
|
+
observer.unobserve(hostElement);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// keep the state at { vertical: 1, horizontal: 1 }
|
|
50
|
+
// until we get the proper child measurements
|
|
51
|
+
if (childHeight === null || childWidth === null) return;
|
|
52
|
+
|
|
53
|
+
const vertical = Math.floor(wrapperElement.clientHeight / childHeight);
|
|
54
|
+
const horizontal = Math.floor(wrapperElement.clientWidth / childWidth);
|
|
55
|
+
|
|
56
|
+
setCalculatedLimit((pv) => {
|
|
57
|
+
if (pv.vertical !== vertical || pv.horizontal !== horizontal)
|
|
58
|
+
return { vertical, horizontal };
|
|
59
|
+
return pv;
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
resizeObserver.observe(wrapperElement);
|
|
64
|
+
resizeObserver.observe(hostElement);
|
|
65
|
+
|
|
66
|
+
return () => {
|
|
67
|
+
resizeObserver.disconnect();
|
|
68
|
+
};
|
|
69
|
+
}, [hostElement, limit, wrapperElement]);
|
|
70
|
+
|
|
71
|
+
return calculatedLimit;
|
|
72
|
+
};
|
package/src/translations/en.json
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
"Video": "Video",
|
|
13
13
|
"You are muted. Unmute to speak.": "You are muted. Unmute to speak.",
|
|
14
14
|
|
|
15
|
+
"Live": "Live",
|
|
16
|
+
|
|
15
17
|
"You can now speak.": "You can now speak.",
|
|
16
18
|
"Awaiting for an approval to speak.": "Awaiting for an approval to speak.",
|
|
17
19
|
"You can no longer speak.": "You can no longer speak.",
|