Squashed commit of the following:
Browse filescommit 151299bcb4803525f9a5ed243fd567bf539a54b8
Author: Justin Haaheim <[email protected]>
Date: Thu Nov 30 23:00:42 2023 +0000
Prevent the server from being unlocked if were locking it completely
commit 2a6cf503c9729a410b57f1b7e164c7b5ff01dc46
Author: Justin Haaheim <[email protected]>
Date: Thu Nov 30 22:57:47 2023 +0000
Fix member object creation and add logging if lock completely is set
commit ea97d8b4145ede78fca9c249439e10be97ba1097
Author: Justin Haaheim <[email protected]>
Date: Thu Nov 30 22:51:06 2023 +0000
Enable fully locking server using env var
commit c40b4cb84fbbb74d3b70ff09f473b9612e1bd4ac
Author: Justin Haaheim <[email protected]>
Date: Thu Nov 30 13:16:38 2023 -0800
[Streaming] Add headphones notice and echo cancellation warning (#176)
commit 94a338e9127b1a3936ea1d1c6763e6a90ee8ee9d
Author: Justin Haaheim <[email protected]>
Date: Thu Nov 30 09:31:56 2023 -0800
[Streaming] Show full language name in dropdown (#173)
Co-authored-by: Mark Duppenthaler <[email protected]>
- seamless_server/app_pubsub.py +27 -11
- streaming-react-app/package.json +2 -3
- streaming-react-app/src/StreamingInterface.tsx +36 -19
- streaming-react-app/src/languageLookup.ts +105 -103
- streaming-react-app/src/react-xr/TextBlocks.tsx +191 -119
- streaming-react-app/src/react-xr/XRConfig.tsx +9 -69
- streaming-react-app/src/react-xr/XRDialog.tsx +1 -16
- streaming-react-app/src/types/StreamingTypes.ts +4 -3
- streaming-react-app/vite.config.ts +0 -5
- streaming-react-app/yarn.lock +0 -5
@@ -124,7 +124,22 @@ class ServerLock(TypedDict):
|
|
124 |
member_object: Member
|
125 |
|
126 |
|
127 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
128 |
|
129 |
server_id = str(uuid4())
|
130 |
|
@@ -497,16 +512,17 @@ async def join_room(sid, client_id, room_id_from_client, config_dict):
|
|
497 |
):
|
498 |
# If something goes wrong and the server gets stuck in a locked state the client can
|
499 |
# force the server to remove the lock by passing the special name ESCAPE_HATCH_SERVER_LOCK_RELEASE_NAME
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
|
|
510 |
|
511 |
# If the server is not locked, set a lock. If it's already locked to this client, update the lock object
|
512 |
if server_lock is None or server_lock.get("client_id") == client_id:
|
|
|
124 |
member_object: Member
|
125 |
|
126 |
|
127 |
+
if os.environ.get("LOCK_SERVER_COMPLETELY"):
|
128 |
+
logger.info("LOCK_SERVER_COMPLETELY is set. Server will be locked on startup.")
|
129 |
+
dummy_server_lock_member_object = Member(
|
130 |
+
client_id="seamless_user", session_id="dummy", name="Seamless User"
|
131 |
+
)
|
132 |
+
# Normally this would be an actual transcoder, but it's fine putting True here since currently we only check for the presence of the transcoder
|
133 |
+
dummy_server_lock_member_object.transcoder = True
|
134 |
+
server_lock: Optional[ServerLock] = (
|
135 |
+
{
|
136 |
+
"name": "Seamless User",
|
137 |
+
"client_id": "seamless_user",
|
138 |
+
"member_object": dummy_server_lock_member_object,
|
139 |
+
}
|
140 |
+
if os.environ.get("LOCK_SERVER_COMPLETELY")
|
141 |
+
else None
|
142 |
+
)
|
143 |
|
144 |
server_id = str(uuid4())
|
145 |
|
|
|
512 |
):
|
513 |
# If something goes wrong and the server gets stuck in a locked state the client can
|
514 |
# force the server to remove the lock by passing the special name ESCAPE_HATCH_SERVER_LOCK_RELEASE_NAME
|
515 |
+
if (
|
516 |
+
server_lock is not None
|
517 |
+
and config_dict.get("lockServerName")
|
518 |
+
== ESCAPE_HATCH_SERVER_LOCK_RELEASE_NAME
|
519 |
+
# If we are locking the server completely we don't want someone to be able to unlock it
|
520 |
+
and not os.environ.get("LOCK_SERVER_COMPLETELY")
|
521 |
+
):
|
522 |
+
server_lock = None
|
523 |
+
logger.info(
|
524 |
+
f"🔓 Server lock has been reset by {client_id} using the escape hatch name {ESCAPE_HATCH_SERVER_LOCK_RELEASE_NAME}"
|
525 |
+
)
|
526 |
|
527 |
# If the server is not locked, set a lock. If it's already locked to this client, update the lock object
|
528 |
if server_lock is None or server_lock.get("client_id") == client_id:
|
@@ -1,11 +1,11 @@
|
|
1 |
{
|
2 |
"name": "streaming-react-app",
|
3 |
"private": true,
|
4 |
-
"version": "0.0.
|
5 |
"type": "module",
|
6 |
"scripts": {
|
7 |
"dev": "vite --host --strictPort",
|
8 |
-
"build": "
|
9 |
"preview": "vite preview",
|
10 |
"clean:node-modules": "rm -rf node_modules/",
|
11 |
"ts-check": "tsc --noEmit",
|
@@ -24,7 +24,6 @@
|
|
24 |
"amazon-cognito-identity-js": "^6.3.6",
|
25 |
"audiobuffer-to-wav": "^1.0.0",
|
26 |
"aws-sdk": "^2.1472.0",
|
27 |
-
"iso-639-1": "^3.1.0",
|
28 |
"js-cookie": "^3.0.5",
|
29 |
"lodash": "4.17.21",
|
30 |
"react": "^18.2.0",
|
|
|
1 |
{
|
2 |
"name": "streaming-react-app",
|
3 |
"private": true,
|
4 |
+
"version": "0.0.14",
|
5 |
"type": "module",
|
6 |
"scripts": {
|
7 |
"dev": "vite --host --strictPort",
|
8 |
+
"build": "vite build",
|
9 |
"preview": "vite preview",
|
10 |
"clean:node-modules": "rm -rf node_modules/",
|
11 |
"ts-check": "tsc --noEmit",
|
|
|
24 |
"amazon-cognito-identity-js": "^6.3.6",
|
25 |
"audiobuffer-to-wav": "^1.0.0",
|
26 |
"aws-sdk": "^2.1472.0",
|
|
|
27 |
"js-cookie": "^3.0.5",
|
28 |
"lodash": "4.17.21",
|
29 |
"react": "^18.2.0",
|
@@ -57,12 +57,12 @@ import {CURSOR_BLINK_INTERVAL_MS} from './cursorBlinkInterval';
|
|
57 |
import {getURLParams} from './URLParams';
|
58 |
import debug from './debug';
|
59 |
import DebugSection from './DebugSection';
|
60 |
-
import
|
|
|
61 |
import {getLanguageFromThreeLetterCode} from './languageLookup';
|
|
|
62 |
|
63 |
-
const AUDIO_STREAM_DEFAULTS
|
64 |
-
[key in SupportedInputSource]: BrowserAudioStreamConfig;
|
65 |
-
} = {
|
66 |
userMedia: {
|
67 |
echoCancellation: false,
|
68 |
noiseSuppression: true,
|
@@ -71,13 +71,10 @@ const AUDIO_STREAM_DEFAULTS: {
|
|
71 |
echoCancellation: false,
|
72 |
noiseSuppression: false,
|
73 |
},
|
74 |
-
};
|
75 |
|
76 |
async function requestUserMediaAudioStream(
|
77 |
-
config: BrowserAudioStreamConfig =
|
78 |
-
echoCancellation: false,
|
79 |
-
noiseSuppression: true,
|
80 |
-
},
|
81 |
) {
|
82 |
const stream = await navigator.mediaDevices.getUserMedia({
|
83 |
audio: {...config, channelCount: 1},
|
@@ -90,10 +87,7 @@ async function requestUserMediaAudioStream(
|
|
90 |
}
|
91 |
|
92 |
async function requestDisplayMediaAudioStream(
|
93 |
-
config: BrowserAudioStreamConfig =
|
94 |
-
echoCancellation: false,
|
95 |
-
noiseSuppression: false,
|
96 |
-
},
|
97 |
) {
|
98 |
const stream = await navigator.mediaDevices.getDisplayMedia({
|
99 |
audio: {...config, channelCount: 1},
|
@@ -962,8 +956,9 @@ export default function StreamingInterface() {
|
|
962 |
</RadioGroup>
|
963 |
</FormControl>
|
964 |
</Box>
|
965 |
-
|
966 |
-
|
|
|
967 |
<FormLabel>Options</FormLabel>
|
968 |
<FormControlLabel
|
969 |
control={
|
@@ -980,9 +975,9 @@ export default function StreamingInterface() {
|
|
980 |
}
|
981 |
/>
|
982 |
}
|
983 |
-
label="Noise Suppression
|
984 |
/>
|
985 |
-
|
986 |
control={
|
987 |
<Checkbox
|
988 |
checked={
|
@@ -997,7 +992,7 @@ export default function StreamingInterface() {
|
|
997 |
}
|
998 |
/>
|
999 |
}
|
1000 |
-
label="Echo Cancellation (
|
1001 |
/>
|
1002 |
<FormControlLabel
|
1003 |
control={
|
@@ -1008,12 +1003,34 @@ export default function StreamingInterface() {
|
|
1008 |
) => setServerDebugFlag(event.target.checked)}
|
1009 |
/>
|
1010 |
}
|
1011 |
-
label="Server
|
1012 |
/>
|
1013 |
</FormControl>
|
1014 |
</Box>
|
1015 |
</Stack>
|
1016 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1017 |
<Stack direction="row" spacing={2}>
|
1018 |
{streamingStatus === 'stopped' ? (
|
1019 |
<Button
|
|
|
57 |
import {getURLParams} from './URLParams';
|
58 |
import debug from './debug';
|
59 |
import DebugSection from './DebugSection';
|
60 |
+
import Switch from '@mui/material/Switch';
|
61 |
+
import Grid from '@mui/material/Grid';
|
62 |
import {getLanguageFromThreeLetterCode} from './languageLookup';
|
63 |
+
import HeadphonesIcon from '@mui/icons-material/Headphones';
|
64 |
|
65 |
+
const AUDIO_STREAM_DEFAULTS = {
|
|
|
|
|
66 |
userMedia: {
|
67 |
echoCancellation: false,
|
68 |
noiseSuppression: true,
|
|
|
71 |
echoCancellation: false,
|
72 |
noiseSuppression: false,
|
73 |
},
|
74 |
+
} as const;
|
75 |
|
76 |
async function requestUserMediaAudioStream(
|
77 |
+
config: BrowserAudioStreamConfig = AUDIO_STREAM_DEFAULTS['userMedia'],
|
|
|
|
|
|
|
78 |
) {
|
79 |
const stream = await navigator.mediaDevices.getUserMedia({
|
80 |
audio: {...config, channelCount: 1},
|
|
|
87 |
}
|
88 |
|
89 |
async function requestDisplayMediaAudioStream(
|
90 |
+
config: BrowserAudioStreamConfig = AUDIO_STREAM_DEFAULTS['displayMedia'],
|
|
|
|
|
|
|
91 |
) {
|
92 |
const stream = await navigator.mediaDevices.getDisplayMedia({
|
93 |
audio: {...config, channelCount: 1},
|
|
|
956 |
</RadioGroup>
|
957 |
</FormControl>
|
958 |
</Box>
|
959 |
+
|
960 |
+
<Box sx={{flex: 1, flexGrow: 2}}>
|
961 |
+
<FormControl disabled={streamFixedConfigOptionsDisabled}>
|
962 |
<FormLabel>Options</FormLabel>
|
963 |
<FormControlLabel
|
964 |
control={
|
|
|
975 |
}
|
976 |
/>
|
977 |
}
|
978 |
+
label="Noise Suppression"
|
979 |
/>
|
980 |
+
<FormControlLabel
|
981 |
control={
|
982 |
<Checkbox
|
983 |
checked={
|
|
|
992 |
}
|
993 |
/>
|
994 |
}
|
995 |
+
label="Echo Cancellation (not recommended)"
|
996 |
/>
|
997 |
<FormControlLabel
|
998 |
control={
|
|
|
1003 |
) => setServerDebugFlag(event.target.checked)}
|
1004 |
/>
|
1005 |
}
|
1006 |
+
label="Enable Server Debugging"
|
1007 |
/>
|
1008 |
</FormControl>
|
1009 |
</Box>
|
1010 |
</Stack>
|
1011 |
|
1012 |
+
{isSpeaker &&
|
1013 |
+
isListener &&
|
1014 |
+
inputSource === 'userMedia' &&
|
1015 |
+
!enableEchoCancellation &&
|
1016 |
+
gain !== 0 && (
|
1017 |
+
<div>
|
1018 |
+
<Alert severity="warning" icon={<HeadphonesIcon />}>
|
1019 |
+
Headphones required to prevent feedback.
|
1020 |
+
</Alert>
|
1021 |
+
</div>
|
1022 |
+
)}
|
1023 |
+
|
1024 |
+
{isSpeaker && enableEchoCancellation && (
|
1025 |
+
<div>
|
1026 |
+
<Alert severity="warning">
|
1027 |
+
We don't recommend using echo cancellation as it may
|
1028 |
+
distort the input audio. If possible, use headphones and
|
1029 |
+
disable echo cancellation instead.
|
1030 |
+
</Alert>
|
1031 |
+
</div>
|
1032 |
+
)}
|
1033 |
+
|
1034 |
<Stack direction="row" spacing={2}>
|
1035 |
{streamingStatus === 'stopped' ? (
|
1036 |
<Button
|
@@ -1,108 +1,110 @@
|
|
1 |
-
const
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
};
|
102 |
|
103 |
-
export function getLanguageFromThreeLetterCode(
|
|
|
|
|
104 |
try {
|
105 |
-
const name =
|
106 |
if (name == null) {
|
107 |
return null;
|
108 |
}
|
@@ -111,7 +113,7 @@ export function getLanguageFromThreeLetterCode(code: string): string | null {
|
|
111 |
.map((word: string) => word[0].toUpperCase() + word.slice(1));
|
112 |
return capitalizedWords.join(' ');
|
113 |
} catch (e) {
|
114 |
-
console.warn(`Unable to get language name for code ${
|
115 |
}
|
116 |
return null;
|
117 |
}
|
|
|
1 |
+
const LANG3_TO_NAME = {
|
2 |
+
afr: 'afrikaans',
|
3 |
+
amh: 'amharic',
|
4 |
+
arb: 'arabic',
|
5 |
+
asm: 'assamese',
|
6 |
+
azj: 'azerbaijani',
|
7 |
+
bak: 'bashkir',
|
8 |
+
bel: 'belarusian',
|
9 |
+
ben: 'bengali',
|
10 |
+
bod: 'tibetan',
|
11 |
+
bos: 'bosnian',
|
12 |
+
bre: 'breton',
|
13 |
+
bul: 'bulgarian',
|
14 |
+
cat: 'catalan',
|
15 |
+
ces: 'czech',
|
16 |
+
cmn: 'chinese',
|
17 |
+
cym: 'welsh',
|
18 |
+
dan: 'danish',
|
19 |
+
deu: 'german',
|
20 |
+
ell: 'greek',
|
21 |
+
eng: 'english',
|
22 |
+
est: 'estonian',
|
23 |
+
eus: 'basque',
|
24 |
+
fao: 'faroese',
|
25 |
+
fin: 'finnish',
|
26 |
+
fra: 'french',
|
27 |
+
glg: 'galician',
|
28 |
+
guj: 'gujarati',
|
29 |
+
hat: 'haitian creole',
|
30 |
+
hau: 'hausa',
|
31 |
+
haw: 'hawaiian',
|
32 |
+
heb: 'hebrew',
|
33 |
+
hin: 'hindi',
|
34 |
+
hrv: 'croatian',
|
35 |
+
hun: 'hungarian',
|
36 |
+
hye: 'armenian',
|
37 |
+
ind: 'indonesian',
|
38 |
+
isl: 'icelandic',
|
39 |
+
ita: 'italian',
|
40 |
+
jav: 'javanese',
|
41 |
+
jpn: 'japanese',
|
42 |
+
kan: 'kannada',
|
43 |
+
kat: 'georgian',
|
44 |
+
kaz: 'kazakh',
|
45 |
+
khk: 'mongolian',
|
46 |
+
khm: 'khmer',
|
47 |
+
kor: 'korean',
|
48 |
+
lao: 'lao',
|
49 |
+
lat: 'latin',
|
50 |
+
lin: 'lingala',
|
51 |
+
lit: 'lithuanian',
|
52 |
+
ltz: 'luxembourgish',
|
53 |
+
lvs: 'latvian',
|
54 |
+
mal: 'malayalam',
|
55 |
+
mar: 'marathi',
|
56 |
+
mkd: 'macedonian',
|
57 |
+
mlg: 'malagasy',
|
58 |
+
mlt: 'maltese',
|
59 |
+
mri: 'maori',
|
60 |
+
mya: 'myanmar',
|
61 |
+
nld: 'dutch',
|
62 |
+
nno: 'nynorsk',
|
63 |
+
nob: 'norwegian',
|
64 |
+
npi: 'nepali',
|
65 |
+
oci: 'occitan',
|
66 |
+
pan: 'punjabi',
|
67 |
+
pbt: 'pashto',
|
68 |
+
pes: 'persian',
|
69 |
+
pol: 'polish',
|
70 |
+
por: 'portuguese',
|
71 |
+
ron: 'romanian',
|
72 |
+
rus: 'russian',
|
73 |
+
san: 'sanskrit',
|
74 |
+
sin: 'sinhala',
|
75 |
+
slk: 'slovak',
|
76 |
+
slv: 'slovenian',
|
77 |
+
sna: 'shona',
|
78 |
+
snd: 'sindhi',
|
79 |
+
som: 'somali',
|
80 |
+
spa: 'spanish',
|
81 |
+
sqi: 'albanian',
|
82 |
+
srp: 'serbian',
|
83 |
+
sun: 'sundanese',
|
84 |
+
swe: 'swedish',
|
85 |
+
swh: 'swahili',
|
86 |
+
tam: 'tamil',
|
87 |
+
tat: 'tatar',
|
88 |
+
tel: 'telugu',
|
89 |
+
tgk: 'tajik',
|
90 |
+
tgl: 'tagalog',
|
91 |
+
tha: 'thai',
|
92 |
+
tuk: 'turkmen',
|
93 |
+
tur: 'turkish',
|
94 |
+
ukr: 'ukrainian',
|
95 |
+
urd: 'urdu',
|
96 |
+
uzn: 'uzbek',
|
97 |
+
vie: 'vietnamese',
|
98 |
+
yid: 'yiddish',
|
99 |
+
yor: 'yoruba',
|
100 |
+
zlm: 'malay',
|
101 |
};
|
102 |
|
103 |
+
export function getLanguageFromThreeLetterCode(
|
104 |
+
lang3Code: string,
|
105 |
+
): string | null {
|
106 |
try {
|
107 |
+
const name = LANG3_TO_NAME[lang3Code] ?? null;
|
108 |
if (name == null) {
|
109 |
return null;
|
110 |
}
|
|
|
113 |
.map((word: string) => word[0].toUpperCase() + word.slice(1));
|
114 |
return capitalizedWords.join(' ');
|
115 |
} catch (e) {
|
116 |
+
console.warn(`Unable to get language name for code ${lang3Code}: ${e}`);
|
117 |
}
|
118 |
return null;
|
119 |
}
|
@@ -1,9 +1,8 @@
|
|
1 |
-
import {
|
2 |
import robotoFontFamilyJson from '../assets/RobotoMono-Regular-msdf.json?url';
|
3 |
import robotoFontTexture from '../assets/RobotoMono-Regular.png';
|
4 |
import ThreeMeshUIText, {ThreeMeshUITextType} from './ThreeMeshUIText';
|
5 |
-
import
|
6 |
-
import {CURSOR_BLINK_INTERVAL_MS} from '../cursorBlinkInterval';
|
7 |
|
8 |
const NUM_LINES = 3;
|
9 |
|
@@ -22,44 +21,80 @@ const SCROLL_Y_DELTA = 0.001;
|
|
22 |
const OFFSET = 0.01;
|
23 |
const OFFSET_WIDTH = OFFSET * 3;
|
24 |
|
25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
26 |
content: string;
|
27 |
// The actual position or end position when animating
|
28 |
y: number;
|
29 |
// The start position when animating
|
30 |
startY: number;
|
31 |
-
width: number;
|
32 |
-
height: number;
|
33 |
textOpacity: number;
|
34 |
backgroundOpacity: number;
|
35 |
-
|
36 |
-
|
37 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
};
|
39 |
|
40 |
function TextBlock({
|
41 |
content,
|
42 |
y,
|
43 |
startY,
|
44 |
-
width,
|
45 |
-
height,
|
46 |
textOpacity,
|
47 |
backgroundOpacity,
|
48 |
index,
|
49 |
-
|
50 |
-
}:
|
51 |
const [scrollY, setScrollY] = useState<number>(y);
|
52 |
-
|
53 |
// We are reusing text blocks so this keeps track of when we changed rows so we can restart animation
|
54 |
-
const lastIndex = useRef<
|
55 |
useEffect(() => {
|
56 |
if (index != lastIndex.current) {
|
57 |
lastIndex.current = index;
|
58 |
-
|
59 |
} else if (scrollY < y) {
|
60 |
setScrollY((prev) => prev + SCROLL_Y_DELTA);
|
61 |
}
|
62 |
-
}, [
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
|
64 |
// This is needed to update text content (doesn't work if we just update the content prop)
|
65 |
const textRef = useRef<ThreeMeshUITextType>();
|
@@ -111,125 +146,162 @@ function TextBlock({
|
|
111 |
);
|
112 |
}
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
]}
|
130 |
-
position={[
|
131 |
-
-OFFSET + xPosition,
|
132 |
-
Y_COORD_START + panelHeight / 2 - 2 * OFFSET,
|
133 |
-
Z_COORD,
|
134 |
-
]}></block>
|
135 |
-
);
|
136 |
}
|
137 |
|
138 |
export default function TextBlocks({
|
139 |
-
|
140 |
-
blinkCursor,
|
141 |
}: {
|
142 |
-
|
143 |
-
blinkCursor: boolean;
|
144 |
}) {
|
145 |
-
const
|
146 |
-
|
147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
148 |
|
149 |
-
const [cursorBlinkOn, setCursorBlinkOn] = useState(false);
|
150 |
useEffect(() => {
|
151 |
-
|
152 |
-
const
|
153 |
-
|
154 |
-
|
|
|
|
|
|
|
155 |
|
156 |
-
|
157 |
-
|
158 |
-
setCursorBlinkOn(false);
|
159 |
-
}
|
160 |
-
}, [blinkCursor]);
|
161 |
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
173 |
|
174 |
-
const
|
175 |
-
const
|
176 |
-
const y = currentY + LINE_HEIGHT / 2;
|
177 |
-
let textBlockLine = sentenceLines[j];
|
178 |
-
const numChars = textBlockLine.length;
|
179 |
|
180 |
-
if (
|
181 |
-
|
|
|
|
|
182 |
}
|
|
|
183 |
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
y={y}
|
193 |
-
startY={currentY}
|
194 |
-
index={`${sentences.length - i},${j}`}
|
195 |
-
textOpacity={textOpacity}
|
196 |
-
backgroundOpacity={0.98}
|
197 |
-
height={LINE_HEIGHT}
|
198 |
-
width={blockWidth}
|
199 |
-
// content={"BLOCK " + textBlocks.length + ": " + content}
|
200 |
-
content={textBlockLine}
|
201 |
-
enableAnimation={!isBottomLine}
|
202 |
-
/>,
|
203 |
-
);
|
204 |
|
205 |
-
|
|
|
|
|
|
|
|
|
|
|
206 |
}
|
207 |
-
currentY += showTranscriptionPanel ? BLOCK_SPACING / 3 : BLOCK_SPACING;
|
208 |
-
}
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
);
|
228 |
});
|
229 |
-
}
|
230 |
|
231 |
-
|
232 |
-
|
|
|
|
|
|
|
|
|
233 |
}
|
234 |
-
|
|
|
|
|
|
|
|
|
|
|
235 |
}
|
|
|
1 |
+
import {useEffect, useRef, useState} from 'react';
|
2 |
import robotoFontFamilyJson from '../assets/RobotoMono-Regular-msdf.json?url';
|
3 |
import robotoFontTexture from '../assets/RobotoMono-Regular.png';
|
4 |
import ThreeMeshUIText, {ThreeMeshUITextType} from './ThreeMeshUIText';
|
5 |
+
import supportedCharSet from './supportedCharSet';
|
|
|
6 |
|
7 |
const NUM_LINES = 3;
|
8 |
|
|
|
21 |
const OFFSET = 0.01;
|
22 |
const OFFSET_WIDTH = OFFSET * 3;
|
23 |
|
24 |
+
const CHARS_PER_SECOND = 10;
|
25 |
+
|
26 |
+
// The tick interval
|
27 |
+
const RENDER_INTERVAL = 300;
|
28 |
+
|
29 |
+
const CURSOR_BLINK_INTERVAL_MS = 1000;
|
30 |
+
|
31 |
+
type TextBlockProps = {
|
32 |
content: string;
|
33 |
// The actual position or end position when animating
|
34 |
y: number;
|
35 |
// The start position when animating
|
36 |
startY: number;
|
|
|
|
|
37 |
textOpacity: number;
|
38 |
backgroundOpacity: number;
|
39 |
+
index: number;
|
40 |
+
isBottomLine: boolean;
|
41 |
+
// key: number;
|
42 |
+
};
|
43 |
+
|
44 |
+
type TranscriptState = {
|
45 |
+
textBlocksProps: TextBlockProps[];
|
46 |
+
lastTranslationStringIndex: number;
|
47 |
+
lastTranslationLineStartIndex: number;
|
48 |
+
transcriptLines: string[];
|
49 |
+
lastRenderTime: number;
|
50 |
};
|
51 |
|
52 |
function TextBlock({
|
53 |
content,
|
54 |
y,
|
55 |
startY,
|
|
|
|
|
56 |
textOpacity,
|
57 |
backgroundOpacity,
|
58 |
index,
|
59 |
+
isBottomLine,
|
60 |
+
}: TextBlockProps) {
|
61 |
const [scrollY, setScrollY] = useState<number>(y);
|
|
|
62 |
// We are reusing text blocks so this keeps track of when we changed rows so we can restart animation
|
63 |
+
const lastIndex = useRef<number>(index);
|
64 |
useEffect(() => {
|
65 |
if (index != lastIndex.current) {
|
66 |
lastIndex.current = index;
|
67 |
+
!isBottomLine && setScrollY(startY);
|
68 |
} else if (scrollY < y) {
|
69 |
setScrollY((prev) => prev + SCROLL_Y_DELTA);
|
70 |
}
|
71 |
+
}, [isBottomLine, index, scrollY, setScrollY, startY, y]);
|
72 |
+
|
73 |
+
const [cursorBlinkOn, setCursorBlinkOn] = useState(false);
|
74 |
+
useEffect(() => {
|
75 |
+
if (isBottomLine) {
|
76 |
+
const interval = setInterval(() => {
|
77 |
+
setCursorBlinkOn((prev) => !prev);
|
78 |
+
}, CURSOR_BLINK_INTERVAL_MS);
|
79 |
+
|
80 |
+
return () => clearInterval(interval);
|
81 |
+
} else {
|
82 |
+
setCursorBlinkOn(false);
|
83 |
+
}
|
84 |
+
}, [isBottomLine]);
|
85 |
+
|
86 |
+
const numChars = content.length;
|
87 |
+
|
88 |
+
if (cursorBlinkOn) {
|
89 |
+
content = content + '|';
|
90 |
+
}
|
91 |
+
|
92 |
+
// Accounting for potential cursor for block width (the +1)
|
93 |
+
const width =
|
94 |
+
(numChars + (isBottomLine ? 1.1 : 0) + (numChars < 10 ? 1 : 0)) *
|
95 |
+
CHAR_WIDTH;
|
96 |
+
|
97 |
+
const height = LINE_HEIGHT;
|
98 |
|
99 |
// This is needed to update text content (doesn't work if we just update the content prop)
|
100 |
const textRef = useRef<ThreeMeshUITextType>();
|
|
|
146 |
);
|
147 |
}
|
148 |
|
149 |
+
function initialTextBlockProps(count: number): TextBlockProps[] {
|
150 |
+
return Array.from({length: count}).map(() => {
|
151 |
+
// Push in non display blocks because mesh UI crashes if elements are add / removed from screen.
|
152 |
+
return {
|
153 |
+
y: Y_COORD_START,
|
154 |
+
startY: 0,
|
155 |
+
index: 0,
|
156 |
+
textOpacity: 0,
|
157 |
+
backgroundOpacity: 0,
|
158 |
+
width: MAX_WIDTH,
|
159 |
+
height: LINE_HEIGHT,
|
160 |
+
content: '',
|
161 |
+
isBottomLine: true,
|
162 |
+
};
|
163 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
164 |
}
|
165 |
|
166 |
export default function TextBlocks({
|
167 |
+
translationText,
|
|
|
168 |
}: {
|
169 |
+
translationText: string;
|
|
|
170 |
}) {
|
171 |
+
const transcriptStateRef = useRef<TranscriptState>({
|
172 |
+
textBlocksProps: initialTextBlockProps(NUM_LINES),
|
173 |
+
lastTranslationStringIndex: 0,
|
174 |
+
lastTranslationLineStartIndex: 0,
|
175 |
+
transcriptLines: [],
|
176 |
+
lastRenderTime: new Date().getTime(),
|
177 |
+
});
|
178 |
+
|
179 |
+
const transcriptState = transcriptStateRef.current;
|
180 |
+
const {textBlocksProps, lastTranslationStringIndex, lastRenderTime} =
|
181 |
+
transcriptState;
|
182 |
+
|
183 |
+
const [charsToRender, setCharsToRender] = useState<number>(0);
|
184 |
|
|
|
185 |
useEffect(() => {
|
186 |
+
const interval = setInterval(() => {
|
187 |
+
const currentTime = new Date().getTime();
|
188 |
+
const charsToRender = Math.round(
|
189 |
+
((currentTime - lastRenderTime) * CHARS_PER_SECOND) / 1000,
|
190 |
+
);
|
191 |
+
setCharsToRender(charsToRender);
|
192 |
+
}, RENDER_INTERVAL);
|
193 |
|
194 |
+
return () => clearInterval(interval);
|
195 |
+
}, [lastRenderTime]);
|
|
|
|
|
|
|
196 |
|
197 |
+
const currentTime = new Date().getTime();
|
198 |
+
if (charsToRender < 1) {
|
199 |
+
return textBlocksProps.map((props, idx) => (
|
200 |
+
<TextBlock {...props} key={idx} />
|
201 |
+
));
|
202 |
+
}
|
203 |
+
|
204 |
+
const nextTranslationStringIndex = Math.min(
|
205 |
+
lastTranslationStringIndex + charsToRender,
|
206 |
+
translationText.length,
|
207 |
+
);
|
208 |
+
const newString = translationText.substring(
|
209 |
+
lastTranslationStringIndex,
|
210 |
+
nextTranslationStringIndex,
|
211 |
+
);
|
212 |
+
if (nextTranslationStringIndex === lastTranslationStringIndex) {
|
213 |
+
transcriptState.lastRenderTime = currentTime;
|
214 |
+
return textBlocksProps.map((props, idx) => (
|
215 |
+
<TextBlock {...props} key={idx} />
|
216 |
+
));
|
217 |
+
}
|
218 |
+
|
219 |
+
// Wait until more characters are accumulated if its just blankspace
|
220 |
+
if (/^\s*$/.test(newString)) {
|
221 |
+
transcriptState.lastRenderTime = currentTime;
|
222 |
+
return textBlocksProps.map((props, idx) => (
|
223 |
+
<TextBlock {...props} key={idx} />
|
224 |
+
));
|
225 |
+
}
|
226 |
+
|
227 |
+
// Ideally we continue where we left off but this is complicated when we have mid-words. Recalculating for now
|
228 |
+
const runAll = true;
|
229 |
+
const newSentences = runAll
|
230 |
+
? translationText.substring(0, nextTranslationStringIndex).split('\n')
|
231 |
+
: newString.split('\n');
|
232 |
+
const transcriptLines = runAll ? [''] : transcriptState.transcriptLines;
|
233 |
+
newSentences.forEach((newSentence, sentenceIdx) => {
|
234 |
+
const words = newSentence.split(/\s+/);
|
235 |
+
words.forEach((word) => {
|
236 |
+
const filteredWord = [...word]
|
237 |
+
.filter((c) => {
|
238 |
+
if (supportedCharSet().has(c)) {
|
239 |
+
return true;
|
240 |
+
}
|
241 |
+
console.error(
|
242 |
+
`Unsupported char ${c} - make sure this is supported in the font family msdf file`,
|
243 |
+
);
|
244 |
+
return false;
|
245 |
+
})
|
246 |
+
.join('');
|
247 |
|
248 |
+
const lastLineSoFar = transcriptLines[0];
|
249 |
+
const charCount = lastLineSoFar.length + filteredWord.length + 1;
|
|
|
|
|
|
|
250 |
|
251 |
+
if (charCount <= CHARS_PER_LINE) {
|
252 |
+
transcriptLines[0] = lastLineSoFar + ' ' + filteredWord;
|
253 |
+
} else {
|
254 |
+
transcriptLines.unshift(filteredWord);
|
255 |
}
|
256 |
+
});
|
257 |
|
258 |
+
if (sentenceIdx < newSentences.length - 1) {
|
259 |
+
transcriptLines.unshift('\n');
|
260 |
+
transcriptLines.unshift('');
|
261 |
+
}
|
262 |
+
});
|
263 |
+
|
264 |
+
transcriptState.transcriptLines = transcriptLines;
|
265 |
+
transcriptState.lastTranslationStringIndex = nextTranslationStringIndex;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
|
267 |
+
const newTextBlocksProps: TextBlockProps[] = [];
|
268 |
+
let currentY = Y_COORD_START;
|
269 |
+
|
270 |
+
transcriptLines.forEach((line, i) => {
|
271 |
+
if (newTextBlocksProps.length == NUM_LINES) {
|
272 |
+
return;
|
273 |
}
|
|
|
|
|
274 |
|
275 |
+
// const line = transcriptLines[i];
|
276 |
+
if (line === '\n') {
|
277 |
+
currentY += BLOCK_SPACING;
|
278 |
+
return;
|
279 |
+
}
|
280 |
+
const y = currentY + LINE_HEIGHT / 2;
|
281 |
+
const isBottomLine = newTextBlocksProps.length === 0;
|
282 |
+
|
283 |
+
const textOpacity = 1 - 0.1 * newTextBlocksProps.length;
|
284 |
+
newTextBlocksProps.push({
|
285 |
+
y,
|
286 |
+
startY: currentY,
|
287 |
+
index: i,
|
288 |
+
textOpacity,
|
289 |
+
backgroundOpacity: 0.98,
|
290 |
+
content: line,
|
291 |
+
isBottomLine,
|
|
|
292 |
});
|
|
|
293 |
|
294 |
+
currentY = y + LINE_HEIGHT / 2;
|
295 |
+
});
|
296 |
+
|
297 |
+
const numRemainingBlocks = NUM_LINES - newTextBlocksProps.length;
|
298 |
+
if (numRemainingBlocks > 0) {
|
299 |
+
newTextBlocksProps.push(...initialTextBlockProps(numRemainingBlocks));
|
300 |
}
|
301 |
+
|
302 |
+
transcriptState.textBlocksProps = newTextBlocksProps;
|
303 |
+
transcriptState.lastRenderTime = currentTime;
|
304 |
+
return newTextBlocksProps.map((props, idx) => (
|
305 |
+
<TextBlock {...props} key={idx} />
|
306 |
+
));
|
307 |
}
|
@@ -25,29 +25,15 @@ import {BLACK, WHITE} from './Colors';
|
|
25 |
import robotoFontFamilyJson from '../assets/RobotoMono-Regular-msdf.json?url';
|
26 |
import robotoFontTexture from '../assets/RobotoMono-Regular.png';
|
27 |
import {getURLParams} from '../URLParams';
|
28 |
-
import TextBlocks
|
29 |
import {BufferedSpeechPlayer} from '../createBufferedSpeechPlayer';
|
30 |
import {CURSOR_BLINK_INTERVAL_MS} from '../cursorBlinkInterval';
|
|
|
31 |
|
32 |
// Adds on react JSX for add-on libraries to react-three-fiber
|
33 |
extend(ThreeMeshUI);
|
34 |
extend({TextGeometry});
|
35 |
|
36 |
-
async function fetchSupportedCharSet(): Promise<Set<string>> {
|
37 |
-
try {
|
38 |
-
const response = await fetch(robotoFontFamilyJson);
|
39 |
-
const fontFamily = await response.json();
|
40 |
-
|
41 |
-
return new Set(fontFamily.info.charset);
|
42 |
-
} catch (e) {
|
43 |
-
console.error('Failed to fetch supported XR charset', e);
|
44 |
-
return new Set();
|
45 |
-
}
|
46 |
-
}
|
47 |
-
|
48 |
-
let supportedCharSet = new Set();
|
49 |
-
fetchSupportedCharSet().then((result) => (supportedCharSet = result));
|
50 |
-
|
51 |
// This component wraps any children so it is positioned relative to the camera, rather than from the origin
|
52 |
function CameraLinkedObject({children}) {
|
53 |
const camera = useThree((state) => state.camera);
|
@@ -76,10 +62,7 @@ function ThreeMeshUIComponents({
|
|
76 |
translationSentences={translationSentences}
|
77 |
/>
|
78 |
) : (
|
79 |
-
<TranscriptPanelBlocks
|
80 |
-
animateTextDisplay={animateTextDisplay}
|
81 |
-
translationSentences={translationSentences}
|
82 |
-
/>
|
83 |
)}
|
84 |
{skipARIntro ? null : (
|
85 |
<IntroPanel started={started} setStarted={setStarted} />
|
@@ -153,7 +136,7 @@ function TranscriptPanelSingleBlock({
|
|
153 |
(wordChunks, currentWord) => {
|
154 |
const filteredWord = [...currentWord]
|
155 |
.filter((c) => {
|
156 |
-
if (supportedCharSet.has(c)) {
|
157 |
return true;
|
158 |
}
|
159 |
console.error(
|
@@ -223,59 +206,14 @@ function TranscriptPanelSingleBlock({
|
|
223 |
// Splits up the lines into separate blocks to treat each one separately.
|
224 |
// This allows changing of opacity, animating per line, changing height / width per line etc
|
225 |
function TranscriptPanelBlocks({
|
226 |
-
animateTextDisplay,
|
227 |
translationSentences,
|
228 |
}: {
|
229 |
-
animateTextDisplay: boolean;
|
230 |
translationSentences: TranslationSentences;
|
231 |
}) {
|
232 |
-
const [didReceiveTranslationSentences, setDidReceiveTranslationSentences] =
|
233 |
-
// Currently causing issues with displaying dummy text, skip over
|
234 |
-
useState(false);
|
235 |
-
|
236 |
-
// Normally we don't setState in render, but here we need to for computed state, and this if statement assures it won't loop infinitely
|
237 |
-
if (!didReceiveTranslationSentences && translationSentences.length > 0) {
|
238 |
-
setDidReceiveTranslationSentences(true);
|
239 |
-
}
|
240 |
-
|
241 |
-
const initialPrompt = 'Listening...';
|
242 |
-
const transcriptSentences: string[] = didReceiveTranslationSentences
|
243 |
-
? translationSentences
|
244 |
-
: [initialPrompt];
|
245 |
-
|
246 |
-
// The transcript is an array of sentences. For each sentence we break this down into an array of words per line.
|
247 |
-
// This is needed so we can "scroll" through without changing the order of words in the transcript
|
248 |
-
const sentenceLines = transcriptSentences.map((sentence) => {
|
249 |
-
const words = sentence.split(/\s+/);
|
250 |
-
// Here we break each sentence up with newlines so all words per line fit within the panel
|
251 |
-
return words.reduce(
|
252 |
-
(wordChunks, currentWord) => {
|
253 |
-
const filteredWord = [...currentWord]
|
254 |
-
.filter((c) => {
|
255 |
-
if (supportedCharSet.has(c)) {
|
256 |
-
return true;
|
257 |
-
}
|
258 |
-
console.error(
|
259 |
-
`Unsupported char ${c} - make sure this is supported in the font family msdf file`,
|
260 |
-
);
|
261 |
-
return false;
|
262 |
-
})
|
263 |
-
.join('');
|
264 |
-
const lastLineSoFar = wordChunks[wordChunks.length - 1];
|
265 |
-
const charCount = lastLineSoFar.length + filteredWord.length + 1;
|
266 |
-
if (charCount <= CHARS_PER_LINE) {
|
267 |
-
wordChunks[wordChunks.length - 1] =
|
268 |
-
lastLineSoFar + ' ' + filteredWord;
|
269 |
-
} else {
|
270 |
-
wordChunks.push(filteredWord);
|
271 |
-
}
|
272 |
-
return wordChunks;
|
273 |
-
},
|
274 |
-
[''],
|
275 |
-
);
|
276 |
-
});
|
277 |
return (
|
278 |
-
<TextBlocks
|
|
|
|
|
279 |
);
|
280 |
}
|
281 |
|
@@ -361,6 +299,8 @@ export type XRConfigProps = {
|
|
361 |
startStreaming: () => Promise<void>;
|
362 |
stopStreaming: () => Promise<void>;
|
363 |
debugParam: boolean | null;
|
|
|
|
|
364 |
};
|
365 |
|
366 |
export default function XRConfig(props: XRConfigProps) {
|
|
|
25 |
import robotoFontFamilyJson from '../assets/RobotoMono-Regular-msdf.json?url';
|
26 |
import robotoFontTexture from '../assets/RobotoMono-Regular.png';
|
27 |
import {getURLParams} from '../URLParams';
|
28 |
+
import TextBlocks from './TextBlocks';
|
29 |
import {BufferedSpeechPlayer} from '../createBufferedSpeechPlayer';
|
30 |
import {CURSOR_BLINK_INTERVAL_MS} from '../cursorBlinkInterval';
|
31 |
+
import supportedCharSet from './supportedCharSet';
|
32 |
|
33 |
// Adds on react JSX for add-on libraries to react-three-fiber
|
34 |
extend(ThreeMeshUI);
|
35 |
extend({TextGeometry});
|
36 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
// This component wraps any children so it is positioned relative to the camera, rather than from the origin
|
38 |
function CameraLinkedObject({children}) {
|
39 |
const camera = useThree((state) => state.camera);
|
|
|
62 |
translationSentences={translationSentences}
|
63 |
/>
|
64 |
) : (
|
65 |
+
<TranscriptPanelBlocks translationSentences={translationSentences} />
|
|
|
|
|
|
|
66 |
)}
|
67 |
{skipARIntro ? null : (
|
68 |
<IntroPanel started={started} setStarted={setStarted} />
|
|
|
136 |
(wordChunks, currentWord) => {
|
137 |
const filteredWord = [...currentWord]
|
138 |
.filter((c) => {
|
139 |
+
if (supportedCharSet().has(c)) {
|
140 |
return true;
|
141 |
}
|
142 |
console.error(
|
|
|
206 |
// Splits up the lines into separate blocks to treat each one separately.
|
207 |
// This allows changing of opacity, animating per line, changing height / width per line etc
|
208 |
function TranscriptPanelBlocks({
|
|
|
209 |
translationSentences,
|
210 |
}: {
|
|
|
211 |
translationSentences: TranslationSentences;
|
212 |
}) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
return (
|
214 |
+
<TextBlocks
|
215 |
+
translationText={'Listening...\n' + translationSentences.join('\n')}
|
216 |
+
/>
|
217 |
);
|
218 |
}
|
219 |
|
|
|
299 |
startStreaming: () => Promise<void>;
|
300 |
stopStreaming: () => Promise<void>;
|
301 |
debugParam: boolean | null;
|
302 |
+
onARVisible?: () => void;
|
303 |
+
onARHidden?: () => void;
|
304 |
};
|
305 |
|
306 |
export default function XRConfig(props: XRConfigProps) {
|
@@ -8,27 +8,12 @@ import {
|
|
8 |
Typography,
|
9 |
} from '@mui/material';
|
10 |
import CloseIcon from '@mui/icons-material/Close';
|
|
|
11 |
import {useEffect, useRef, useState} from 'react';
|
12 |
import './XRDialog.css';
|
13 |
import {getRenderer, init, updatetranslationText} from './XRRendering';
|
14 |
import ARButton from './ARButton';
|
15 |
import {getURLParams} from '../URLParams';
|
16 |
-
import { BufferedSpeechPlayer } from '../createBufferedSpeechPlayer';
|
17 |
-
import { TranslationSentences } from '../types/StreamingTypes';
|
18 |
-
import { RoomState } from '../types/RoomState';
|
19 |
-
|
20 |
-
type XRConfigProps = {
|
21 |
-
animateTextDisplay: boolean;
|
22 |
-
bufferedSpeechPlayer: BufferedSpeechPlayer;
|
23 |
-
translationSentences: TranslationSentences;
|
24 |
-
roomState: RoomState | null;
|
25 |
-
roomID: string | null;
|
26 |
-
startStreaming: () => Promise<void>;
|
27 |
-
stopStreaming: () => Promise<void>;
|
28 |
-
debugParam: boolean | null;
|
29 |
-
onARVisible?: () => void;
|
30 |
-
onARHidden?: () => void;
|
31 |
-
};
|
32 |
|
33 |
function XRContent(props: XRConfigProps) {
|
34 |
const debugParam = getURLParams().debug;
|
|
|
8 |
Typography,
|
9 |
} from '@mui/material';
|
10 |
import CloseIcon from '@mui/icons-material/Close';
|
11 |
+
import {XRConfigProps} from './XRConfig';
|
12 |
import {useEffect, useRef, useState} from 'react';
|
13 |
import './XRDialog.css';
|
14 |
import {getRenderer, init, updatetranslationText} from './XRRendering';
|
15 |
import ARButton from './ARButton';
|
16 |
import {getURLParams} from '../URLParams';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
function XRContent(props: XRConfigProps) {
|
19 |
const debugParam = getURLParams().debug;
|
@@ -55,9 +55,9 @@ export const SUPPORTED_INPUT_SOURCES: Array<{
|
|
55 |
value: SupportedInputSource;
|
56 |
label: string;
|
57 |
}> = [
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
|
62 |
export type StartStreamEventConfig = {
|
63 |
event: 'config';
|
@@ -70,6 +70,7 @@ export type StartStreamEventConfig = {
|
|
70 |
};
|
71 |
|
72 |
export interface BrowserAudioStreamConfig {
|
|
|
73 |
noiseSuppression: boolean;
|
74 |
echoCancellation: boolean;
|
75 |
}
|
|
|
55 |
value: SupportedInputSource;
|
56 |
label: string;
|
57 |
}> = [
|
58 |
+
{value: 'userMedia', label: 'Microphone'},
|
59 |
+
{value: 'displayMedia', label: 'Browser Tab (Chrome only)'},
|
60 |
+
];
|
61 |
|
62 |
export type StartStreamEventConfig = {
|
63 |
event: 'config';
|
|
|
70 |
};
|
71 |
|
72 |
export interface BrowserAudioStreamConfig {
|
73 |
+
echoCancellation: boolean;
|
74 |
noiseSuppression: boolean;
|
75 |
echoCancellation: boolean;
|
76 |
}
|
@@ -1,10 +1,5 @@
|
|
1 |
import { defineConfig } from 'vite';
|
2 |
import react from '@vitejs/plugin-react';
|
3 |
-
// import {resolve} from 'path';
|
4 |
-
|
5 |
-
// const rootDir = resolve(__dirname, 'src');
|
6 |
-
// const assetsDir = resolve(rootDir, 'assets');
|
7 |
-
// const typesDir = resolve(__dirname, 'types');
|
8 |
|
9 |
// https://vitejs.dev/config/
|
10 |
export default defineConfig(({ command }) => {
|
|
|
1 |
import { defineConfig } from 'vite';
|
2 |
import react from '@vitejs/plugin-react';
|
|
|
|
|
|
|
|
|
|
|
3 |
|
4 |
// https://vitejs.dev/config/
|
5 |
export default defineConfig(({ command }) => {
|
@@ -1853,11 +1853,6 @@ isexe@^2.0.0:
|
|
1853 |
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
|
1854 |
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
1855 |
|
1856 |
-
iso-639-1@^3.1.0:
|
1857 |
-
version "3.1.0"
|
1858 |
-
resolved "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.0.tgz"
|
1859 |
-
integrity sha512-rWcHp9dcNbxa5C8jA/cxFlWNFNwy5Vup0KcFvgA8sPQs9ZeJHj/Eq0Y8Yz2eL8XlWYpxw4iwh9FfTeVxyqdRMw==
|
1860 |
-
|
1861 |
isomorphic-unfetch@^3.0.0:
|
1862 |
version "3.1.0"
|
1863 |
resolved "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz"
|
|
|
1853 |
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
|
1854 |
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
1855 |
|
|
|
|
|
|
|
|
|
|
|
1856 |
isomorphic-unfetch@^3.0.0:
|
1857 |
version "3.1.0"
|
1858 |
resolved "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz"
|