@100mslive/roomkit-react 0.3.8-alpha.1 → 0.3.8-alpha.2
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/dist/{HLSView-HUMJOIWO.js → HLSView-XZDT3RRC.js} +2 -2
- package/dist/Prebuilt/common/constants.d.ts +1 -0
- package/dist/Prebuilt/components/CaptionIcon.d.ts +2 -0
- package/dist/Prebuilt/plugins/CaptionsViewer.d.ts +2 -0
- package/dist/Text/Text.d.ts +1 -1
- package/dist/{chunk-IDAPJGWC.js → chunk-SQPIZNW2.js} +2136 -1881
- package/dist/chunk-SQPIZNW2.js.map +7 -0
- package/dist/index.cjs.js +3115 -2842
- package/dist/index.cjs.js.map +4 -4
- package/dist/index.js +1 -1
- package/dist/meta.cjs.json +181 -48
- package/dist/meta.esbuild.json +186 -53
- package/package.json +6 -6
- package/src/Prebuilt/common/constants.ts +1 -0
- package/src/Prebuilt/components/AppData/AppData.tsx +1 -0
- package/src/Prebuilt/components/AppData/useUISettings.js +10 -0
- package/src/Prebuilt/components/CaptionIcon.tsx +27 -0
- package/src/Prebuilt/components/ConferenceScreen.tsx +34 -2
- package/src/Prebuilt/components/Footer/Footer.tsx +2 -0
- package/src/Prebuilt/components/Footer/RoleAccordion.tsx +1 -1
- package/src/Prebuilt/components/MoreSettings/SplitComponents/MwebOptions.tsx +22 -1
- package/src/Prebuilt/layouts/VideoStreamingSection.tsx +0 -1
- package/src/Prebuilt/plugins/CaptionsViewer.tsx +191 -0
- package/dist/chunk-IDAPJGWC.js.map +0 -7
- /package/dist/{HLSView-HUMJOIWO.js.map → HLSView-XZDT3RRC.js.map} +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
2
|
+
import { useMedia } from 'react-use';
|
2
3
|
import { DefaultConferencingScreen_Elements } from '@100mslive/types-prebuilt';
|
3
4
|
import { v4 as uuid } from 'uuid';
|
4
5
|
import {
|
@@ -18,6 +19,7 @@ import { ActivatedPIP } from './PIP/PIPComponent';
|
|
18
19
|
import { PictureInPicture } from './PIP/PIPManager';
|
19
20
|
import { RoleChangeRequestModal } from './RoleChangeRequest/RoleChangeRequestModal';
|
20
21
|
import { Box, Flex } from '../../Layout';
|
22
|
+
import { config } from '../../Theme';
|
21
23
|
import { useHMSPrebuiltContext } from '../AppContext';
|
22
24
|
import { VideoStreamingSection } from '../layouts/VideoStreamingSection';
|
23
25
|
// @ts-ignore: No implicit Any
|
@@ -26,17 +28,21 @@ import FullPageProgress from './FullPageProgress';
|
|
26
28
|
import { Header } from './Header';
|
27
29
|
import { PreviousRoleInMetadata } from './PreviousRoleInMetadata';
|
28
30
|
import { RaiseHand } from './RaiseHand';
|
31
|
+
import { CaptionsViewer } from '../plugins/CaptionsViewer';
|
29
32
|
import {
|
30
33
|
useRoomLayoutConferencingScreen,
|
31
34
|
useRoomLayoutPreviewScreen,
|
32
35
|
} from '../provider/roomLayoutProvider/hooks/useRoomLayoutScreen';
|
33
36
|
// @ts-ignore: No implicit Any
|
34
|
-
import {
|
37
|
+
import { useIsSidepaneTypeOpen } from './AppData/useSidepane';
|
38
|
+
// @ts-ignore: No implicit Any
|
39
|
+
import { useAuthToken, useIsCaptionEnabled, useSetAppDataByKey } from './AppData/useUISettings';
|
35
40
|
import { useLandscapeHLSStream, useMobileHLSStream } from '../common/hooks';
|
36
|
-
import { APP_DATA, isAndroid, isIOS, isIPadOS } from '../common/constants';
|
41
|
+
import { APP_DATA, isAndroid, isIOS, isIPadOS, SIDE_PANE_OPTIONS } from '../common/constants';
|
37
42
|
|
38
43
|
export const ConferenceScreen = () => {
|
39
44
|
const { userName, endpoints, onJoin: onJoinFunc } = useHMSPrebuiltContext();
|
45
|
+
const isMobile = useMedia(config.media.md);
|
40
46
|
const screenProps = useRoomLayoutConferencingScreen();
|
41
47
|
const { isPreviewScreenEnabled } = useRoomLayoutPreviewScreen();
|
42
48
|
const roomState = useHMSStore(selectRoomState);
|
@@ -57,6 +63,10 @@ export const ConferenceScreen = () => {
|
|
57
63
|
const isMobileHLSStream = useMobileHLSStream();
|
58
64
|
const isLandscapeHLSStream = useLandscapeHLSStream();
|
59
65
|
const isMwebHLSStream = isMobileHLSStream || isLandscapeHLSStream;
|
66
|
+
const isCaptionEnabled = useIsCaptionEnabled();
|
67
|
+
const isChatOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.CHAT);
|
68
|
+
|
69
|
+
const showCaptionAtTop = screenProps.elements?.chat?.is_overlay && isChatOpen;
|
60
70
|
|
61
71
|
const toggleControls = () => {
|
62
72
|
if (dropdownListRef.current?.length === 0 && isMobileDevice && !isMwebHLSStream) {
|
@@ -124,6 +134,28 @@ export const ConferenceScreen = () => {
|
|
124
134
|
<FullPageProgress text="Starting live stream..." css={{ opacity: 0.8, bg: '$background_dim' }} />
|
125
135
|
</Box>
|
126
136
|
) : null}
|
137
|
+
{isCaptionEnabled && screenProps.screenType !== 'hls_live_streaming' && (
|
138
|
+
<Box
|
139
|
+
css={{
|
140
|
+
position: 'fixed',
|
141
|
+
maxWidth: isMobile ? '100%' : '40%',
|
142
|
+
bottom: showCaptionAtTop ? '' : hideControlsForStreaming ? '5%' : '10%',
|
143
|
+
top: showCaptionAtTop ? (hideControlsForStreaming ? '5%' : '10%') : '',
|
144
|
+
left: isMobile ? 0 : '50%',
|
145
|
+
transform: isMobile ? '' : 'translateX(-50%)',
|
146
|
+
background: '#000000A3',
|
147
|
+
overflow: 'clip',
|
148
|
+
zIndex: 10,
|
149
|
+
height: 'fit-content',
|
150
|
+
r: '$1',
|
151
|
+
p: '$6',
|
152
|
+
transition: 'bottom 0.3s ease-in-out',
|
153
|
+
'&:empty': { display: 'none' },
|
154
|
+
}}
|
155
|
+
>
|
156
|
+
<CaptionsViewer />
|
157
|
+
</Box>
|
158
|
+
)}
|
127
159
|
<Flex css={{ size: '100%', overflow: 'hidden' }} direction="column">
|
128
160
|
{!(screenProps.hideSections.includes('header') || isMwebHLSStream) && (
|
129
161
|
<Box
|
@@ -5,6 +5,7 @@ import { Chat_ChatState } from '@100mslive/types-prebuilt/elements/chat';
|
|
5
5
|
import { config as cssConfig, Footer as AppFooter } from '../../..';
|
6
6
|
// @ts-ignore: No implicit Any
|
7
7
|
import { AudioVideoToggle } from '../AudioVideoToggle';
|
8
|
+
import { CaptionIcon } from '../CaptionIcon';
|
8
9
|
// @ts-ignore: No implicit Any
|
9
10
|
import { EmojiReaction } from '../EmojiReaction';
|
10
11
|
// @ts-ignore: No implicit Any
|
@@ -96,6 +97,7 @@ export const Footer = ({
|
|
96
97
|
<>
|
97
98
|
<ScreenshareToggle />
|
98
99
|
<RaiseHand />
|
100
|
+
{screenType !== 'hls_live_streaming' && <CaptionIcon />}
|
99
101
|
{elements?.emoji_reactions && <EmojiReaction />}
|
100
102
|
<LeaveRoom screenType={screenType} />
|
101
103
|
</>
|
@@ -109,7 +109,7 @@ export const RoleAccordion = ({
|
|
109
109
|
},
|
110
110
|
}}
|
111
111
|
>
|
112
|
-
<Flex justify="between" css={{ flexGrow: 1, pr: '$6' }}>
|
112
|
+
<Flex justify="between" align="center" css={{ flexGrow: 1, pr: '$6' }}>
|
113
113
|
<Text
|
114
114
|
variant="sm"
|
115
115
|
css={{ fontWeight: '$semiBold', textTransform: 'capitalize', color: '$on_surface_medium' }}
|
@@ -5,6 +5,7 @@ import { match } from 'ts-pattern';
|
|
5
5
|
import {
|
6
6
|
selectIsConnectedToRoom,
|
7
7
|
selectIsLocalVideoEnabled,
|
8
|
+
selectIsTranscriptionEnabled,
|
8
9
|
selectPeerCount,
|
9
10
|
selectPermissions,
|
10
11
|
useHMSActions,
|
@@ -13,12 +14,14 @@ import {
|
|
13
14
|
} from '@100mslive/react-sdk';
|
14
15
|
import {
|
15
16
|
BrbIcon,
|
17
|
+
ClosedCaptionIcon,
|
16
18
|
CrossIcon,
|
17
19
|
EmojiIcon,
|
18
20
|
HamburgerMenuIcon,
|
19
21
|
HandIcon,
|
20
22
|
HandRaiseSlashedIcon,
|
21
23
|
InfoIcon,
|
24
|
+
OpenCaptionIcon,
|
22
25
|
PeopleIcon,
|
23
26
|
QuizActiveIcon,
|
24
27
|
QuizIcon,
|
@@ -49,7 +52,7 @@ import { useSheetToggle } from '../../AppData/useSheet';
|
|
49
52
|
// @ts-ignore: No implicit any
|
50
53
|
import { usePollViewToggle, useSidepaneToggle } from '../../AppData/useSidepane';
|
51
54
|
// @ts-ignore: No implicit Any
|
52
|
-
import { useShowPolls } from '../../AppData/useUISettings';
|
55
|
+
import { useSetIsCaptionEnabled, useShowPolls } from '../../AppData/useUISettings';
|
53
56
|
// @ts-ignore: No implicit any
|
54
57
|
import { useDropdownList } from '../../hooks/useDropdownList';
|
55
58
|
import { useMyMetadata } from '../../hooks/useMetadata';
|
@@ -102,6 +105,10 @@ export const MwebOptions = ({
|
|
102
105
|
const toggleVB = useSidepaneToggle(SIDE_PANE_OPTIONS.VB);
|
103
106
|
const isLocalVideoEnabled = useHMSStore(selectIsLocalVideoEnabled);
|
104
107
|
const { startRecording, isRecordingLoading } = useRecordingHandler();
|
108
|
+
|
109
|
+
const isCaptionPresent = useHMSStore(selectIsTranscriptionEnabled);
|
110
|
+
|
111
|
+
const [isCaptionEnabled, setIsCaptionEnabled] = useSetIsCaptionEnabled();
|
105
112
|
useDropdownList({ open: openModals.size > 0 || openOptionsSheet || openSettingsSheet, name: 'MoreSettings' });
|
106
113
|
|
107
114
|
const updateState = (modalName: string, value: boolean) => {
|
@@ -186,6 +193,20 @@ export const MwebOptions = ({
|
|
186
193
|
<ActionTile.Title>{isHandRaised ? 'Lower' : 'Raise'} Hand</ActionTile.Title>
|
187
194
|
</ActionTile.Root>
|
188
195
|
) : null}
|
196
|
+
{isCaptionPresent && screenType !== 'hls_live_streaming' ? (
|
197
|
+
<ActionTile.Root
|
198
|
+
onClick={() => {
|
199
|
+
setIsCaptionEnabled(!isCaptionEnabled);
|
200
|
+
}}
|
201
|
+
>
|
202
|
+
{isCaptionEnabled ? (
|
203
|
+
<ClosedCaptionIcon width="20" height="20px" />
|
204
|
+
) : (
|
205
|
+
<OpenCaptionIcon width="20" height="20px" />
|
206
|
+
)}
|
207
|
+
<ActionTile.Title>{isCaptionEnabled ? 'Hide Captions' : 'Captions Disabled'}</ActionTile.Title>
|
208
|
+
</ActionTile.Root>
|
209
|
+
) : null}
|
189
210
|
|
190
211
|
{isLocalVideoEnabled && !!elements?.virtual_background ? (
|
191
212
|
<ActionTile.Root
|
@@ -105,7 +105,6 @@ export const VideoStreamingSection = ({
|
|
105
105
|
// @ts-ignore
|
106
106
|
return <GridLayout {...(elements as DefaultConferencingScreen_Elements)?.video_tile_layout?.grid} />;
|
107
107
|
})}
|
108
|
-
|
109
108
|
<Box
|
110
109
|
css={{
|
111
110
|
flex: match({ isLandscapeHLSStream, isMobileHLSStream })
|
@@ -0,0 +1,191 @@
|
|
1
|
+
import React, { useEffect, useState } from 'react';
|
2
|
+
import { HMSTranscript, selectPeerNameByID, useHMSStore, useTranscript } from '@100mslive/react-sdk';
|
3
|
+
import { Flex } from '../../Layout';
|
4
|
+
import { Text } from '../../Text';
|
5
|
+
|
6
|
+
interface CaptionQueueData extends HMSTranscript {
|
7
|
+
transcriptQueue: SimpleQueue;
|
8
|
+
}
|
9
|
+
|
10
|
+
interface TranscriptData extends HMSTranscript {
|
11
|
+
timeout?: NodeJS.Timeout | undefined;
|
12
|
+
}
|
13
|
+
class SimpleQueue {
|
14
|
+
private storage: TranscriptData[] = [];
|
15
|
+
constructor(private capacity: number = 3, private MAX_STORAGE_TIME: number = 5000) {}
|
16
|
+
enqueue(data: TranscriptData): void {
|
17
|
+
if (this.size() === this.capacity && this.storage[this.size() - 1].final) {
|
18
|
+
this.dequeue(this.storage[this.size() - 1]);
|
19
|
+
}
|
20
|
+
if (this.size() === 0) {
|
21
|
+
this.storage.push(data);
|
22
|
+
this.addTimeout(this.storage[this.size() - 1], data.final);
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
if (this.size() > 0 && this.storage[this.size() - 1]?.final === true) {
|
26
|
+
this.storage.push(data);
|
27
|
+
this.addTimeout(this.storage[this.size() - 1], data.final);
|
28
|
+
return;
|
29
|
+
}
|
30
|
+
this.storage[this.size() - 1].transcript = data.transcript;
|
31
|
+
this.storage[this.size() - 1].final = data.final;
|
32
|
+
this.storage[this.size() - 1].end = data.end;
|
33
|
+
this.addTimeout(this.storage[this.size() - 1], data.final);
|
34
|
+
}
|
35
|
+
addTimeout(item: TranscriptData, isFinal: boolean) {
|
36
|
+
if (!isFinal) {
|
37
|
+
return;
|
38
|
+
}
|
39
|
+
item.timeout = setTimeout(() => {
|
40
|
+
this.dequeue(item);
|
41
|
+
}, this.MAX_STORAGE_TIME);
|
42
|
+
}
|
43
|
+
dequeue(item: TranscriptData): TranscriptData | undefined {
|
44
|
+
const index = this.storage.indexOf(item);
|
45
|
+
if (index === -1) {
|
46
|
+
return undefined;
|
47
|
+
}
|
48
|
+
const removedItem = this.storage.splice(index, 1);
|
49
|
+
if (removedItem.length <= 0) {
|
50
|
+
return undefined;
|
51
|
+
}
|
52
|
+
this.clearTimeout(removedItem[0]);
|
53
|
+
return item;
|
54
|
+
}
|
55
|
+
clearTimeout(item: TranscriptData) {
|
56
|
+
if (!item.timeout) {
|
57
|
+
return;
|
58
|
+
}
|
59
|
+
clearTimeout(item.timeout);
|
60
|
+
}
|
61
|
+
peek(): TranscriptData | undefined {
|
62
|
+
if (this.size() <= 0) {
|
63
|
+
return undefined;
|
64
|
+
}
|
65
|
+
return this.storage[0];
|
66
|
+
}
|
67
|
+
getTranscription(): string {
|
68
|
+
let script = '';
|
69
|
+
this.storage.forEach((value: TranscriptData) => (script += value.transcript + ' '));
|
70
|
+
return script;
|
71
|
+
}
|
72
|
+
reset() {
|
73
|
+
this.storage.length = 0;
|
74
|
+
}
|
75
|
+
size(): number {
|
76
|
+
return this.storage.length;
|
77
|
+
}
|
78
|
+
}
|
79
|
+
class Queue {
|
80
|
+
private storage: Record<string, CaptionQueueData> = {};
|
81
|
+
constructor(private capacity: number = 3) {}
|
82
|
+
|
83
|
+
enqueue(data: HMSTranscript): void {
|
84
|
+
if (this.size() === this.capacity) {
|
85
|
+
this.dequeue();
|
86
|
+
}
|
87
|
+
if (!this.storage[data.peer_id]) {
|
88
|
+
this.storage[data.peer_id] = {
|
89
|
+
peer_id: data.peer_id,
|
90
|
+
transcript: data.transcript,
|
91
|
+
final: data.final,
|
92
|
+
transcriptQueue: new SimpleQueue(),
|
93
|
+
start: data.start,
|
94
|
+
end: data.end,
|
95
|
+
};
|
96
|
+
this.storage[data.peer_id].transcriptQueue.enqueue(data as TranscriptData);
|
97
|
+
return;
|
98
|
+
}
|
99
|
+
this.storage[data.peer_id].transcriptQueue.enqueue(data as TranscriptData);
|
100
|
+
}
|
101
|
+
dequeue(): CaptionQueueData {
|
102
|
+
const key: string = Object.keys(this.storage).shift() || '';
|
103
|
+
const captionData = this.storage[key];
|
104
|
+
captionData.transcriptQueue.reset();
|
105
|
+
delete this.storage[key];
|
106
|
+
return captionData;
|
107
|
+
}
|
108
|
+
|
109
|
+
peek(): CaptionQueueData | undefined {
|
110
|
+
if (this.size() <= 0) return undefined;
|
111
|
+
const key: string = Object.keys(this.storage).shift() || '';
|
112
|
+
return this.storage[key];
|
113
|
+
}
|
114
|
+
|
115
|
+
findPeerData(): { [key: string]: string }[] {
|
116
|
+
const keys = Object.keys(this.storage);
|
117
|
+
const data = keys.map((key: string) => {
|
118
|
+
const data = this.storage[key];
|
119
|
+
const word = data.transcriptQueue.getTranscription();
|
120
|
+
return { [key]: word };
|
121
|
+
});
|
122
|
+
return data;
|
123
|
+
}
|
124
|
+
size(): number {
|
125
|
+
return Object.keys(this.storage).length;
|
126
|
+
}
|
127
|
+
}
|
128
|
+
|
129
|
+
class CaptionMaintainerQueue {
|
130
|
+
captionData: Queue = new Queue();
|
131
|
+
push(data: HMSTranscript[] = []) {
|
132
|
+
data.forEach((value: HMSTranscript) => {
|
133
|
+
this.captionData.enqueue(value);
|
134
|
+
});
|
135
|
+
}
|
136
|
+
}
|
137
|
+
const TranscriptView = ({ peer_id, data }: { peer_id: string; data: string }) => {
|
138
|
+
const peerName = useHMSStore(selectPeerNameByID(peer_id));
|
139
|
+
data = data.trim();
|
140
|
+
if (!data) return null;
|
141
|
+
return (
|
142
|
+
<Text
|
143
|
+
variant="body2"
|
144
|
+
css={{
|
145
|
+
fontWeight: '$normal',
|
146
|
+
}}
|
147
|
+
>
|
148
|
+
{`${peerName}: ${data}`}
|
149
|
+
</Text>
|
150
|
+
);
|
151
|
+
};
|
152
|
+
|
153
|
+
export const CaptionsViewer = () => {
|
154
|
+
const [captionQueue] = useState<CaptionMaintainerQueue>(new CaptionMaintainerQueue());
|
155
|
+
const [currentData, setCurrentData] = useState<{ [key: string]: string }[]>([]);
|
156
|
+
|
157
|
+
useEffect(() => {
|
158
|
+
const timeInterval = setInterval(() => {
|
159
|
+
if (!captionQueue) {
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
const data = captionQueue.captionData?.findPeerData();
|
163
|
+
setCurrentData(data);
|
164
|
+
}, 1000);
|
165
|
+
return () => clearInterval(timeInterval);
|
166
|
+
}, [captionQueue]);
|
167
|
+
|
168
|
+
useTranscript({
|
169
|
+
onTranscript: (data: HMSTranscript[]) => {
|
170
|
+
captionQueue && captionQueue.push(data as HMSTranscript[]);
|
171
|
+
},
|
172
|
+
});
|
173
|
+
const dataToShow = currentData.filter((data: { [key: string]: string }) => {
|
174
|
+
const key = Object.keys(data)[0];
|
175
|
+
if (data[key]) {
|
176
|
+
return true;
|
177
|
+
}
|
178
|
+
return false;
|
179
|
+
});
|
180
|
+
if (dataToShow.length <= 0) {
|
181
|
+
return null;
|
182
|
+
}
|
183
|
+
return (
|
184
|
+
<Flex direction="column" gap={1}>
|
185
|
+
{dataToShow.map((data: { [key: string]: string }, index: number) => {
|
186
|
+
const key = Object.keys(data)[0];
|
187
|
+
return <TranscriptView key={index} peer_id={key} data={data[key]} />;
|
188
|
+
})}
|
189
|
+
</Flex>
|
190
|
+
);
|
191
|
+
};
|