speaker-calibration 2.2.255 → 2.2.256
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/.eslintignore +71 -71
- package/.eslintrc.json +40 -40
- package/.github/workflows/update-phrases.yml +37 -0
- package/.prettierignore +69 -69
- package/.prettierrc +14 -14
- package/LICENSE +20 -20
- package/README.md +133 -133
- package/__mocks__/fileMock.js +1 -1
- package/__mocks__/styleMock.js +1 -1
- package/babel.config.js +3 -3
- package/coverage/clover.xml +71 -71
- package/coverage/coverage-final.json +224 -224
- package/coverage/lcov-report/PythonServerInterface.js.html +265 -265
- package/coverage/lcov-report/base.css +354 -354
- package/coverage/lcov-report/block-navigation.js +82 -82
- package/coverage/lcov-report/index.html +123 -123
- package/coverage/lcov-report/prettify.css +101 -101
- package/coverage/lcov-report/prettify.js +937 -937
- package/coverage/lcov-report/sorter.js +189 -189
- package/coverage/lcov-report/src/index.html +121 -121
- package/coverage/lcov-report/src/server/PythonServerInterface.js.html +268 -268
- package/coverage/lcov-report/src/server/index.html +123 -123
- package/coverage/lcov-report/src/tasks/audioCalibrator.js.html +499 -499
- package/coverage/lcov-report/src/tasks/audioRecorder.js.html +412 -412
- package/coverage/lcov-report/src/tasks/index.html +143 -143
- package/coverage/lcov-report/src/tasks/volume/index.html +123 -123
- package/coverage/lcov-report/src/tasks/volume/volume.js.html +409 -409
- package/coverage/lcov-report/src/utils.js.html +172 -172
- package/coverage/lcov.info +91 -91
- package/dist/example/NoSleep.min.js +1 -1
- package/dist/example/fetch-languages-sheets.js +77 -77
- package/dist/example/i18n.js +29654 -29654
- package/dist/example/index.html +47 -47
- package/dist/example/listener.html +81 -81
- package/dist/example/server.js +51 -51
- package/dist/example/speaker.html +145 -145
- package/dist/example/speakerUI.js +273 -273
- package/dist/example/styles.css +152 -152
- package/dist/listener.js +3 -3
- package/dist/main.js +9 -9
- package/dist/mlsGen.js +6814 -6814
- package/dist/mlsGen.wasm +0 -0
- package/dist/package-lock.json +1018 -1018
- package/dist/package.json +18 -18
- package/dist/phonePeer.js +3 -3
- package/doc/AudioCalibrator.html +417 -417
- package/doc/AudioPeer.html +251 -251
- package/doc/AudioRecorder.html +195 -195
- package/doc/ImpulseResponse.html +215 -215
- package/doc/Listener.html +308 -308
- package/doc/MlsGenInterface.html +226 -226
- package/doc/MyEventEmitter.html +274 -274
- package/doc/PythonServerAPI.html +109 -109
- package/doc/Speaker.html +276 -276
- package/doc/Takes%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +128 -128
- package/doc/Takes%20the%20url%20of%20the%20current%20site%0Aand%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +138 -138
- package/doc/Takes%20the%20url%20of%20the%20current%20site%20and%20a%20target%20element%20where%20html%20elements%20will%20be%20appended..html +137 -137
- package/doc/Volume.html +88 -88
- package/doc/audioCalibrator.js.html +179 -179
- package/doc/audioPeer.js.html +175 -175
- package/doc/audioRecorder.js.html +163 -163
- package/doc/creates%20a%20new%20AudioRecorder%20instance.%20%0ASets%20up%20the%20audio%20context%20and%20file%20reader..html +114 -114
- package/doc/fonts/OpenSans-Bold-webfont.svg +1829 -1829
- package/doc/fonts/OpenSans-BoldItalic-webfont.svg +1829 -1829
- package/doc/fonts/OpenSans-Italic-webfont.svg +1829 -1829
- package/doc/fonts/OpenSans-Light-webfont.svg +1830 -1830
- package/doc/fonts/OpenSans-LightItalic-webfont.svg +1834 -1834
- package/doc/fonts/OpenSans-Regular-webfont.svg +1830 -1830
- package/doc/global.html +308 -308
- package/doc/index.html +58 -58
- package/doc/listener.js.html +170 -170
- package/doc/mlsGen_mlsGenInterface.js.html +117 -117
- package/doc/myEventEmitter.js.html +124 -124
- package/doc/peer-connection_audioPeer.js.html +188 -188
- package/doc/peer-connection_listener.js.html +311 -311
- package/doc/peer-connection_speaker.js.html +381 -381
- package/doc/scripts/linenumber.js +25 -25
- package/doc/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/doc/scripts/prettify/lang-css.js +24 -24
- package/doc/scripts/prettify/prettify.js +640 -640
- package/doc/server_PythonServerAPI.js.html +160 -160
- package/doc/speaker.js.html +248 -248
- package/doc/styles/jsdoc-default.css +371 -371
- package/doc/styles/prettify-jsdoc.css +111 -111
- package/doc/styles/prettify-tomorrow.css +163 -163
- package/doc/tasks_audioCalibrator.js.html +207 -207
- package/doc/tasks_audioRecorder.js.html +190 -190
- package/doc/tasks_impulse-response_impulseResponse.js.html +442 -442
- package/doc/tasks_impulse-response_mlsGen_mlsGenInterface.js.html +175 -175
- package/doc/tasks_volume_volume.js.html +185 -185
- package/doc/utils.js.html +105 -105
- package/jest.config.js +173 -173
- package/makefile +74 -0
- package/netlify.toml +26 -26
- package/package.json +78 -78
- package/src/config/firebase.js +26 -26
- package/src/index.html +21 -21
- package/src/listener-app/PhonePeer.js +499 -499
- package/src/listener-app/listener.js +380 -380
- package/src/main.js +22 -22
- package/src/myEventEmitter.js +83 -83
- package/src/peer-connection/audioPeer.js +100 -100
- package/src/peer-connection/listener.js +298 -298
- package/src/peer-connection/peerErrors.js +25 -25
- package/src/peer-connection/speaker.js +983 -975
- package/src/powerCheck.js +110 -110
- package/src/server/PythonServerAPI.js +1001 -1001
- package/src/tasks/combination/combination.js +3918 -3906
- package/src/tasks/combination/mlsGen/mlsGen.cpp +98 -98
- package/src/tasks/combination/mlsGen/mlsGen.hpp +303 -303
- package/src/tasks/combination/mlsGen/mlsGenInterface.js +131 -131
- package/src/tasks/combination/mlsGen/mlsGenTest.cpp +180 -180
- package/src/tasks/impulse-response/impulseResponse.js +610 -610
- package/src/tasks/impulse-response/mlsGen/mlsGen.cpp +98 -98
- package/src/tasks/impulse-response/mlsGen/mlsGen.hpp +303 -303
- package/src/tasks/impulse-response/mlsGen/mlsGenInterface.js +131 -131
- package/src/tasks/impulse-response/mlsGen/mlsGenTest.cpp +180 -180
- package/src/tasks/volume/volume.cpp +2 -2
- package/src/tasks/volume/volume.hpp +22 -22
- package/src/tasks/volume/volume.js +279 -279
- package/src/utils.js +205 -205
- package/webpack.config.js +65 -65
- package/.gitignore +0 -81
|
@@ -1,380 +1,380 @@
|
|
|
1
|
-
// get element with id message
|
|
2
|
-
import {phrases} from '../../dist/example/i18n.js';
|
|
3
|
-
import Listener from '../peer-connection/listener.js';
|
|
4
|
-
// get url query parameters
|
|
5
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
6
|
-
|
|
7
|
-
// get isSmartPhone query parameter
|
|
8
|
-
const isSmartPhone = urlParams.get('sp'); // previous isSmartPhone
|
|
9
|
-
|
|
10
|
-
const listenerParameters = {
|
|
11
|
-
targetElementId: 'display',
|
|
12
|
-
microphoneFromAPI: '',
|
|
13
|
-
microphoneDeviceId: '',
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const container = document.getElementById('listenerContainer');
|
|
17
|
-
const recordingInProgress = phrases.RC_soundRecording['en-US'];
|
|
18
|
-
const backToExperimentWindow = phrases.RC_backToExperimentWindow['en-US'];
|
|
19
|
-
const allowMicrophone = phrases.RC_allowMicrophoneUse['en-US'];
|
|
20
|
-
const placeSmartphoneMicrophone = phrases.RC_placeSmartphoneMicrophone['en-US'];
|
|
21
|
-
const turnMeToReadBelow = phrases.RC_turnMeToReadBelow['en-US'];
|
|
22
|
-
const recordingInProgressElement = document.getElementById('recordingInProgress');
|
|
23
|
-
const allowMicrophoneElement = document.getElementById('allowMicrophone');
|
|
24
|
-
const turnMessageElement = document.getElementById('turnMeToReadBelow');
|
|
25
|
-
|
|
26
|
-
switch (isSmartPhone) {
|
|
27
|
-
case 'true':
|
|
28
|
-
//hide target element
|
|
29
|
-
const targetElement = document.getElementById('display');
|
|
30
|
-
targetElement.style.display = 'none';
|
|
31
|
-
// Initialize Listener early
|
|
32
|
-
const initializeListener = async () => {
|
|
33
|
-
window.listener = new Listener(listenerParameters);
|
|
34
|
-
await window.listener.initializePeer();
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
// Check microphone permission first
|
|
38
|
-
async function checkAndRequestMicrophonePermission() {
|
|
39
|
-
// Show permission request message
|
|
40
|
-
allowMicrophoneElement.innerText = phrases.RC_microphonePermission['en-US'];
|
|
41
|
-
container.style.display = 'block';
|
|
42
|
-
|
|
43
|
-
// Function to request microphone access
|
|
44
|
-
async function requestMicAccess(attempt = 1) {
|
|
45
|
-
try {
|
|
46
|
-
await navigator.mediaDevices.getUserMedia({audio: true});
|
|
47
|
-
// Permission granted, proceed to normal flow
|
|
48
|
-
initializeSmartPhoneDisplay();
|
|
49
|
-
} catch (err) {
|
|
50
|
-
if (err.name === 'NotAllowedError') {
|
|
51
|
-
console.log('Permission explicitly denied');
|
|
52
|
-
// Permission explicitly denied
|
|
53
|
-
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
54
|
-
// Send denied status and end study
|
|
55
|
-
let error = JSON.stringify(err);
|
|
56
|
-
await window.listener.sendPermissionStatus({type: 'denied', error: error});
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// If 10 seconds passed, try again
|
|
61
|
-
if (attempt < 3) {
|
|
62
|
-
console.log('Retrying microphone access');
|
|
63
|
-
// Limit retries
|
|
64
|
-
setTimeout(() => requestMicAccess(attempt + 1), 10000);
|
|
65
|
-
} else {
|
|
66
|
-
console.log('All retries failed, treating as denied');
|
|
67
|
-
// After all retries failed, treat as denied
|
|
68
|
-
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
69
|
-
let error = JSON.stringify(err);
|
|
70
|
-
await window.listener.sendPermissionStatus({type: 'error', error: error});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await requestMicAccess();
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error('Error requesting microphone permission:', err);
|
|
79
|
-
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
80
|
-
let error = JSON.stringify(err);
|
|
81
|
-
await window.listener.sendPermissionStatus({type: 'error', error: error});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function initializeSmartPhoneDisplay() {
|
|
86
|
-
allowMicrophoneElement.innerText = placeSmartphoneMicrophone;
|
|
87
|
-
allowMicrophoneElement.style.lineHeight = '1.2rem';
|
|
88
|
-
allowMicrophoneElement.style.fontSize = '14px';
|
|
89
|
-
turnMessageElement.innerText = turnMeToReadBelow;
|
|
90
|
-
turnMessageElement.style.lineHeight = '1.2rem';
|
|
91
|
-
turnMessageElement.style.fontSize = '14px';
|
|
92
|
-
|
|
93
|
-
// Show the html upsidedown and adjust layout
|
|
94
|
-
const phrasesContainer = document.getElementById('phrases');
|
|
95
|
-
phrasesContainer.classList.add('phrases');
|
|
96
|
-
|
|
97
|
-
// Hide all elements except what's needed for calibration
|
|
98
|
-
const html = document.querySelector('html');
|
|
99
|
-
html.style.overflow = 'hidden';
|
|
100
|
-
|
|
101
|
-
// Adjust the display container
|
|
102
|
-
const display = document.getElementById('updateDisplay');
|
|
103
|
-
display.classList.add('updateDisplay');
|
|
104
|
-
display.style.position = 'absolute';
|
|
105
|
-
display.style.top = '50%';
|
|
106
|
-
display.style.left = '50%';
|
|
107
|
-
display.style.transform = 'translate(-50%, -50%) rotate(180deg)';
|
|
108
|
-
display.style.width = '100%';
|
|
109
|
-
display.style.textAlign = 'center';
|
|
110
|
-
|
|
111
|
-
container.style.display = 'block';
|
|
112
|
-
|
|
113
|
-
// event listener for id calibrationBeginButton
|
|
114
|
-
const calibrationBeginButton = document.getElementById('calibrationBeginButton');
|
|
115
|
-
console.log('Waiting for proceed button click');
|
|
116
|
-
|
|
117
|
-
calibrationBeginButton.addEventListener('click', async () => {
|
|
118
|
-
console.log('Proceed button clicked');
|
|
119
|
-
|
|
120
|
-
// Clear unnecessary elements
|
|
121
|
-
calibrationBeginButton.remove();
|
|
122
|
-
turnMessageElement.remove();
|
|
123
|
-
|
|
124
|
-
// Create a header container for fixed elements
|
|
125
|
-
const headerContainer = document.createElement('div');
|
|
126
|
-
headerContainer.id = 'headerContainer';
|
|
127
|
-
headerContainer.style.position = 'fixed';
|
|
128
|
-
headerContainer.style.bottom = '0';
|
|
129
|
-
headerContainer.style.left = '0';
|
|
130
|
-
headerContainer.style.width = '100%';
|
|
131
|
-
headerContainer.style.background = 'white';
|
|
132
|
-
headerContainer.style.padding = '10px';
|
|
133
|
-
headerContainer.style.zIndex = '1000';
|
|
134
|
-
headerContainer.style.transform = 'rotate(180deg)';
|
|
135
|
-
container.appendChild(headerContainer);
|
|
136
|
-
|
|
137
|
-
// Set title based on screen width
|
|
138
|
-
const title = document.createElement('h1');
|
|
139
|
-
const titleText =
|
|
140
|
-
window.innerWidth >= 1366
|
|
141
|
-
? phrases.RC_soundRecording['en-US']
|
|
142
|
-
: phrases.RC_soundRecordingSmallScreen['en-US'];
|
|
143
|
-
|
|
144
|
-
// Split small screen title into lines if needed
|
|
145
|
-
if (window.innerWidth < 1366 && titleText.includes('\n')) {
|
|
146
|
-
const lines = titleText.split('\n');
|
|
147
|
-
|
|
148
|
-
// Create container for title lines
|
|
149
|
-
const titleContainer = document.createElement('div');
|
|
150
|
-
titleContainer.style.display = 'flex';
|
|
151
|
-
titleContainer.style.flexDirection = 'column';
|
|
152
|
-
titleContainer.style.alignItems = 'left';
|
|
153
|
-
titleContainer.style.lineHeight = '1.2';
|
|
154
|
-
|
|
155
|
-
// Add each line
|
|
156
|
-
lines.forEach(line => {
|
|
157
|
-
const lineDiv = document.createElement('p');
|
|
158
|
-
lineDiv.textContent = line;
|
|
159
|
-
lineDiv.style.width = 'fit-content';
|
|
160
|
-
titleContainer.appendChild(lineDiv);
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
title.appendChild(titleContainer);
|
|
164
|
-
} else {
|
|
165
|
-
title.textContent = titleText;
|
|
166
|
-
title.style.lineHeight = '1.2';
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
title.style.margin = '0';
|
|
170
|
-
title.style.whiteSpace = 'pre-line'; // Preserve line breaks
|
|
171
|
-
headerContainer.appendChild(title);
|
|
172
|
-
|
|
173
|
-
// Function to adjust font size to fill width
|
|
174
|
-
const adjustFontSize = (element, maxWidth) => {
|
|
175
|
-
let fontSize = 20; // Start with a reasonable minimum size
|
|
176
|
-
element.style.fontSize = fontSize + 'px';
|
|
177
|
-
// Increase font size until text fills width (minus margins)
|
|
178
|
-
while (element.scrollWidth < maxWidth - 40 && fontSize < 200) {
|
|
179
|
-
fontSize++;
|
|
180
|
-
element.style.fontSize = fontSize + 'px';
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Step back one to ensure we don't overflow
|
|
184
|
-
fontSize--;
|
|
185
|
-
element.style.fontSize = fontSize + 'px';
|
|
186
|
-
return fontSize;
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// For small screen, ensure all lines use same font size
|
|
190
|
-
if (window.innerWidth < 1366 && titleText.includes('\n')) {
|
|
191
|
-
const lines = title.querySelectorAll('p');
|
|
192
|
-
let minFontSize = Infinity;
|
|
193
|
-
|
|
194
|
-
// First pass: find the smallest font size that fits for any line
|
|
195
|
-
lines.forEach(line => {
|
|
196
|
-
const fontSize = adjustFontSize(line, window.innerWidth);
|
|
197
|
-
minFontSize = Math.min(minFontSize, fontSize);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// Second pass: apply the smallest font size to all lines
|
|
201
|
-
lines.forEach(line => {
|
|
202
|
-
line.style.fontSize = minFontSize + 'px';
|
|
203
|
-
});
|
|
204
|
-
} else {
|
|
205
|
-
// For single line title, just adjust to fill width
|
|
206
|
-
adjustFontSize(title, window.innerWidth);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Get the header height after text is added and sized
|
|
210
|
-
const headerHeight = headerContainer.getBoundingClientRect().height;
|
|
211
|
-
|
|
212
|
-
// Adjust the display container to start after header
|
|
213
|
-
const display = document.getElementById('updateDisplay');
|
|
214
|
-
display.classList.add('updateDisplay');
|
|
215
|
-
display.style.position = 'fixed';
|
|
216
|
-
display.style.bottom = `${headerHeight}px`; // Start after header
|
|
217
|
-
display.style.left = '0';
|
|
218
|
-
display.style.right = '0';
|
|
219
|
-
display.style.top = '0';
|
|
220
|
-
display.style.transform = 'rotate(180deg)';
|
|
221
|
-
display.style.overflowY = 'auto';
|
|
222
|
-
display.style.padding = '20px';
|
|
223
|
-
display.style.background = 'white';
|
|
224
|
-
|
|
225
|
-
// Position microphone instruction at the top (appears at bottom due to rotation)
|
|
226
|
-
allowMicrophoneElement.innerText = '';
|
|
227
|
-
allowMicrophoneElement.style.position = 'fixed';
|
|
228
|
-
allowMicrophoneElement.style.top = '20px';
|
|
229
|
-
allowMicrophoneElement.style.left = '50%';
|
|
230
|
-
allowMicrophoneElement.style.transform = 'translateX(-50%) rotate(180deg)';
|
|
231
|
-
allowMicrophoneElement.style.width = '90%';
|
|
232
|
-
allowMicrophoneElement.style.textAlign = 'center';
|
|
233
|
-
allowMicrophoneElement.style.zIndex = '1000';
|
|
234
|
-
|
|
235
|
-
let lock = null;
|
|
236
|
-
try {
|
|
237
|
-
if ('wakeLock' in navigator) {
|
|
238
|
-
lock = await navigator.wakeLock.request('screen');
|
|
239
|
-
}
|
|
240
|
-
} catch (err) {
|
|
241
|
-
console.log(err);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const webAudioDeviceNames = {microphone: '', deviceID: ''};
|
|
245
|
-
const externalMicList = ['UMIK', 'Airpods', 'Bluetooth'];
|
|
246
|
-
try {
|
|
247
|
-
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
|
248
|
-
if (stream) {
|
|
249
|
-
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
250
|
-
const mics = devices.filter(device => device.kind === 'audioinput');
|
|
251
|
-
mics.forEach(mic => {
|
|
252
|
-
if (externalMicList.some(externalMic => mic.label.includes(externalMic))) {
|
|
253
|
-
webAudioDeviceNames.microphone = mic.label;
|
|
254
|
-
webAudioDeviceNames.deviceID = mic.deviceId;
|
|
255
|
-
}
|
|
256
|
-
});
|
|
257
|
-
if (webAudioDeviceNames.microphone === '') {
|
|
258
|
-
webAudioDeviceNames.microphone = mics[0].label;
|
|
259
|
-
webAudioDeviceNames.deviceID = mics[0].deviceId;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
} catch (err) {
|
|
263
|
-
console.log(err);
|
|
264
|
-
}
|
|
265
|
-
window.listener.setMicrophoneFromAPI(webAudioDeviceNames.microphone);
|
|
266
|
-
window.listener.setMicrophoneDeviceId(webAudioDeviceNames.microphone);
|
|
267
|
-
// show target element
|
|
268
|
-
targetElement.style.display = 'block';
|
|
269
|
-
await window.listener.startCalibration({
|
|
270
|
-
microphoneFromAPI: webAudioDeviceNames.microphone,
|
|
271
|
-
microphoneDeviceId: webAudioDeviceNames.microphone,
|
|
272
|
-
});
|
|
273
|
-
if (lock) {
|
|
274
|
-
lock.release();
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Wrap the initialization in an IIFE
|
|
280
|
-
(async function initializeSmartPhoneMode() {
|
|
281
|
-
await initializeListener();
|
|
282
|
-
|
|
283
|
-
const timeout = 30000; // 30 seconds timeout
|
|
284
|
-
const startTime = Date.now();
|
|
285
|
-
|
|
286
|
-
// Wait for peer connection setup with timeout
|
|
287
|
-
while (Date.now() - startTime < timeout) {
|
|
288
|
-
if (
|
|
289
|
-
window.listener.peer.id !== null &&
|
|
290
|
-
window.listener.conn !== null &&
|
|
291
|
-
window.listener.connOpen
|
|
292
|
-
) {
|
|
293
|
-
console.log('Connection established successfully');
|
|
294
|
-
await checkAndRequestMicrophonePermission();
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
console.log('Waiting for connection setup...');
|
|
298
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// If we get here, we've timed out
|
|
302
|
-
console.error('Connection setup timed out after 30 seconds');
|
|
303
|
-
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
304
|
-
await window.listener.sendPermissionStatus({
|
|
305
|
-
type: 'error',
|
|
306
|
-
error: 'Connection setup timed out after 30 seconds',
|
|
307
|
-
});
|
|
308
|
-
})().catch(console.error);
|
|
309
|
-
break;
|
|
310
|
-
case 'false':
|
|
311
|
-
// Initialize listener immediately
|
|
312
|
-
listenerParameters.microphoneDeviceId = urlParams.get('deviceId');
|
|
313
|
-
|
|
314
|
-
// Wrap initialization in an IIFE
|
|
315
|
-
(async function initializeDesktopMode() {
|
|
316
|
-
window.listener = new Listener(listenerParameters);
|
|
317
|
-
await window.listener.initializePeer();
|
|
318
|
-
|
|
319
|
-
const timeout = 30000; // 30 seconds timeout
|
|
320
|
-
const startTime = Date.now();
|
|
321
|
-
|
|
322
|
-
// Wait for peer connection setup with timeout
|
|
323
|
-
while (Date.now() - startTime < timeout) {
|
|
324
|
-
if (
|
|
325
|
-
window.listener.peer.id !== null &&
|
|
326
|
-
window.listener.conn !== null &&
|
|
327
|
-
window.listener.connOpen
|
|
328
|
-
) {
|
|
329
|
-
console.log('Connection established successfully');
|
|
330
|
-
// Continue with desktop setup
|
|
331
|
-
setupDesktopUI();
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
console.log('Waiting for connection setup...');
|
|
335
|
-
await new Promise(resolve => setTimeout(resolve, 100));
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// If we get here, we've timed out
|
|
339
|
-
console.error('Connection setup timed out after 30 seconds');
|
|
340
|
-
// const message = document.getElementById('message');
|
|
341
|
-
// message.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
342
|
-
})().catch(console.error);
|
|
343
|
-
|
|
344
|
-
function setupDesktopUI() {
|
|
345
|
-
// remove the button
|
|
346
|
-
const calibrationBeginButton2 = document.getElementById('calibrationBeginButton');
|
|
347
|
-
calibrationBeginButton2.remove();
|
|
348
|
-
container.style.display = 'block';
|
|
349
|
-
|
|
350
|
-
//update the display to be
|
|
351
|
-
const display = document.getElementById('display');
|
|
352
|
-
if (display) {
|
|
353
|
-
display.style.textAlign = 'left';
|
|
354
|
-
}
|
|
355
|
-
// set the text of the html elements
|
|
356
|
-
recordingInProgressElement.innerText = recordingInProgress;
|
|
357
|
-
allowMicrophoneElement.innerText = allowMicrophone;
|
|
358
|
-
|
|
359
|
-
recordingInProgressElement.style.whiteSpace = 'nowrap';
|
|
360
|
-
recordingInProgressElement.style.fontWeight = 'bold';
|
|
361
|
-
|
|
362
|
-
// fit content
|
|
363
|
-
recordingInProgressElement.style.width = 'fit-content';
|
|
364
|
-
let fontSize = 100;
|
|
365
|
-
recordingInProgressElement.style.fontSize = fontSize + 'px';
|
|
366
|
-
|
|
367
|
-
while (recordingInProgressElement.scrollWidth > window.innerWidth * 0.9 && fontSize > 10) {
|
|
368
|
-
fontSize--;
|
|
369
|
-
recordingInProgressElement.style.fontSize = fontSize + 'px';
|
|
370
|
-
}
|
|
371
|
-
const message = document.getElementById('message');
|
|
372
|
-
message.style.lineHeight = '2.5rem';
|
|
373
|
-
const p = document.createElement('p');
|
|
374
|
-
p.innerText = backToExperimentWindow;
|
|
375
|
-
message.appendChild(p);
|
|
376
|
-
|
|
377
|
-
window.listener.startCalibration();
|
|
378
|
-
}
|
|
379
|
-
break;
|
|
380
|
-
}
|
|
1
|
+
// get element with id message
|
|
2
|
+
import {phrases} from '../../dist/example/i18n.js';
|
|
3
|
+
import Listener from '../peer-connection/listener.js';
|
|
4
|
+
// get url query parameters
|
|
5
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
6
|
+
|
|
7
|
+
// get isSmartPhone query parameter
|
|
8
|
+
const isSmartPhone = urlParams.get('sp'); // previous isSmartPhone
|
|
9
|
+
|
|
10
|
+
const listenerParameters = {
|
|
11
|
+
targetElementId: 'display',
|
|
12
|
+
microphoneFromAPI: '',
|
|
13
|
+
microphoneDeviceId: '',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const container = document.getElementById('listenerContainer');
|
|
17
|
+
const recordingInProgress = phrases.RC_soundRecording['en-US'];
|
|
18
|
+
const backToExperimentWindow = phrases.RC_backToExperimentWindow['en-US'];
|
|
19
|
+
const allowMicrophone = phrases.RC_allowMicrophoneUse['en-US'];
|
|
20
|
+
const placeSmartphoneMicrophone = phrases.RC_placeSmartphoneMicrophone['en-US'];
|
|
21
|
+
const turnMeToReadBelow = phrases.RC_turnMeToReadBelow['en-US'];
|
|
22
|
+
const recordingInProgressElement = document.getElementById('recordingInProgress');
|
|
23
|
+
const allowMicrophoneElement = document.getElementById('allowMicrophone');
|
|
24
|
+
const turnMessageElement = document.getElementById('turnMeToReadBelow');
|
|
25
|
+
|
|
26
|
+
switch (isSmartPhone) {
|
|
27
|
+
case 'true':
|
|
28
|
+
//hide target element
|
|
29
|
+
const targetElement = document.getElementById('display');
|
|
30
|
+
targetElement.style.display = 'none';
|
|
31
|
+
// Initialize Listener early
|
|
32
|
+
const initializeListener = async () => {
|
|
33
|
+
window.listener = new Listener(listenerParameters);
|
|
34
|
+
await window.listener.initializePeer();
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Check microphone permission first
|
|
38
|
+
async function checkAndRequestMicrophonePermission() {
|
|
39
|
+
// Show permission request message
|
|
40
|
+
allowMicrophoneElement.innerText = phrases.RC_microphonePermission['en-US'];
|
|
41
|
+
container.style.display = 'block';
|
|
42
|
+
|
|
43
|
+
// Function to request microphone access
|
|
44
|
+
async function requestMicAccess(attempt = 1) {
|
|
45
|
+
try {
|
|
46
|
+
await navigator.mediaDevices.getUserMedia({audio: true});
|
|
47
|
+
// Permission granted, proceed to normal flow
|
|
48
|
+
initializeSmartPhoneDisplay();
|
|
49
|
+
} catch (err) {
|
|
50
|
+
if (err.name === 'NotAllowedError') {
|
|
51
|
+
console.log('Permission explicitly denied');
|
|
52
|
+
// Permission explicitly denied
|
|
53
|
+
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
54
|
+
// Send denied status and end study
|
|
55
|
+
let error = JSON.stringify(err);
|
|
56
|
+
await window.listener.sendPermissionStatus({type: 'denied', error: error});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If 10 seconds passed, try again
|
|
61
|
+
if (attempt < 3) {
|
|
62
|
+
console.log('Retrying microphone access');
|
|
63
|
+
// Limit retries
|
|
64
|
+
setTimeout(() => requestMicAccess(attempt + 1), 10000);
|
|
65
|
+
} else {
|
|
66
|
+
console.log('All retries failed, treating as denied');
|
|
67
|
+
// After all retries failed, treat as denied
|
|
68
|
+
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
69
|
+
let error = JSON.stringify(err);
|
|
70
|
+
await window.listener.sendPermissionStatus({type: 'error', error: error});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
await requestMicAccess();
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error('Error requesting microphone permission:', err);
|
|
79
|
+
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
80
|
+
let error = JSON.stringify(err);
|
|
81
|
+
await window.listener.sendPermissionStatus({type: 'error', error: error});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function initializeSmartPhoneDisplay() {
|
|
86
|
+
allowMicrophoneElement.innerText = placeSmartphoneMicrophone;
|
|
87
|
+
allowMicrophoneElement.style.lineHeight = '1.2rem';
|
|
88
|
+
allowMicrophoneElement.style.fontSize = '14px';
|
|
89
|
+
turnMessageElement.innerText = turnMeToReadBelow;
|
|
90
|
+
turnMessageElement.style.lineHeight = '1.2rem';
|
|
91
|
+
turnMessageElement.style.fontSize = '14px';
|
|
92
|
+
|
|
93
|
+
// Show the html upsidedown and adjust layout
|
|
94
|
+
const phrasesContainer = document.getElementById('phrases');
|
|
95
|
+
phrasesContainer.classList.add('phrases');
|
|
96
|
+
|
|
97
|
+
// Hide all elements except what's needed for calibration
|
|
98
|
+
const html = document.querySelector('html');
|
|
99
|
+
html.style.overflow = 'hidden';
|
|
100
|
+
|
|
101
|
+
// Adjust the display container
|
|
102
|
+
const display = document.getElementById('updateDisplay');
|
|
103
|
+
display.classList.add('updateDisplay');
|
|
104
|
+
display.style.position = 'absolute';
|
|
105
|
+
display.style.top = '50%';
|
|
106
|
+
display.style.left = '50%';
|
|
107
|
+
display.style.transform = 'translate(-50%, -50%) rotate(180deg)';
|
|
108
|
+
display.style.width = '100%';
|
|
109
|
+
display.style.textAlign = 'center';
|
|
110
|
+
|
|
111
|
+
container.style.display = 'block';
|
|
112
|
+
|
|
113
|
+
// event listener for id calibrationBeginButton
|
|
114
|
+
const calibrationBeginButton = document.getElementById('calibrationBeginButton');
|
|
115
|
+
console.log('Waiting for proceed button click');
|
|
116
|
+
|
|
117
|
+
calibrationBeginButton.addEventListener('click', async () => {
|
|
118
|
+
console.log('Proceed button clicked');
|
|
119
|
+
|
|
120
|
+
// Clear unnecessary elements
|
|
121
|
+
calibrationBeginButton.remove();
|
|
122
|
+
turnMessageElement.remove();
|
|
123
|
+
|
|
124
|
+
// Create a header container for fixed elements
|
|
125
|
+
const headerContainer = document.createElement('div');
|
|
126
|
+
headerContainer.id = 'headerContainer';
|
|
127
|
+
headerContainer.style.position = 'fixed';
|
|
128
|
+
headerContainer.style.bottom = '0';
|
|
129
|
+
headerContainer.style.left = '0';
|
|
130
|
+
headerContainer.style.width = '100%';
|
|
131
|
+
headerContainer.style.background = 'white';
|
|
132
|
+
headerContainer.style.padding = '10px';
|
|
133
|
+
headerContainer.style.zIndex = '1000';
|
|
134
|
+
headerContainer.style.transform = 'rotate(180deg)';
|
|
135
|
+
container.appendChild(headerContainer);
|
|
136
|
+
|
|
137
|
+
// Set title based on screen width
|
|
138
|
+
const title = document.createElement('h1');
|
|
139
|
+
const titleText =
|
|
140
|
+
window.innerWidth >= 1366
|
|
141
|
+
? phrases.RC_soundRecording['en-US']
|
|
142
|
+
: phrases.RC_soundRecordingSmallScreen['en-US'];
|
|
143
|
+
|
|
144
|
+
// Split small screen title into lines if needed
|
|
145
|
+
if (window.innerWidth < 1366 && titleText.includes('\n')) {
|
|
146
|
+
const lines = titleText.split('\n');
|
|
147
|
+
|
|
148
|
+
// Create container for title lines
|
|
149
|
+
const titleContainer = document.createElement('div');
|
|
150
|
+
titleContainer.style.display = 'flex';
|
|
151
|
+
titleContainer.style.flexDirection = 'column';
|
|
152
|
+
titleContainer.style.alignItems = 'left';
|
|
153
|
+
titleContainer.style.lineHeight = '1.2';
|
|
154
|
+
|
|
155
|
+
// Add each line
|
|
156
|
+
lines.forEach(line => {
|
|
157
|
+
const lineDiv = document.createElement('p');
|
|
158
|
+
lineDiv.textContent = line;
|
|
159
|
+
lineDiv.style.width = 'fit-content';
|
|
160
|
+
titleContainer.appendChild(lineDiv);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
title.appendChild(titleContainer);
|
|
164
|
+
} else {
|
|
165
|
+
title.textContent = titleText;
|
|
166
|
+
title.style.lineHeight = '1.2';
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
title.style.margin = '0';
|
|
170
|
+
title.style.whiteSpace = 'pre-line'; // Preserve line breaks
|
|
171
|
+
headerContainer.appendChild(title);
|
|
172
|
+
|
|
173
|
+
// Function to adjust font size to fill width
|
|
174
|
+
const adjustFontSize = (element, maxWidth) => {
|
|
175
|
+
let fontSize = 20; // Start with a reasonable minimum size
|
|
176
|
+
element.style.fontSize = fontSize + 'px';
|
|
177
|
+
// Increase font size until text fills width (minus margins)
|
|
178
|
+
while (element.scrollWidth < maxWidth - 40 && fontSize < 200) {
|
|
179
|
+
fontSize++;
|
|
180
|
+
element.style.fontSize = fontSize + 'px';
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Step back one to ensure we don't overflow
|
|
184
|
+
fontSize--;
|
|
185
|
+
element.style.fontSize = fontSize + 'px';
|
|
186
|
+
return fontSize;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// For small screen, ensure all lines use same font size
|
|
190
|
+
if (window.innerWidth < 1366 && titleText.includes('\n')) {
|
|
191
|
+
const lines = title.querySelectorAll('p');
|
|
192
|
+
let minFontSize = Infinity;
|
|
193
|
+
|
|
194
|
+
// First pass: find the smallest font size that fits for any line
|
|
195
|
+
lines.forEach(line => {
|
|
196
|
+
const fontSize = adjustFontSize(line, window.innerWidth);
|
|
197
|
+
minFontSize = Math.min(minFontSize, fontSize);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Second pass: apply the smallest font size to all lines
|
|
201
|
+
lines.forEach(line => {
|
|
202
|
+
line.style.fontSize = minFontSize + 'px';
|
|
203
|
+
});
|
|
204
|
+
} else {
|
|
205
|
+
// For single line title, just adjust to fill width
|
|
206
|
+
adjustFontSize(title, window.innerWidth);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Get the header height after text is added and sized
|
|
210
|
+
const headerHeight = headerContainer.getBoundingClientRect().height;
|
|
211
|
+
|
|
212
|
+
// Adjust the display container to start after header
|
|
213
|
+
const display = document.getElementById('updateDisplay');
|
|
214
|
+
display.classList.add('updateDisplay');
|
|
215
|
+
display.style.position = 'fixed';
|
|
216
|
+
display.style.bottom = `${headerHeight}px`; // Start after header
|
|
217
|
+
display.style.left = '0';
|
|
218
|
+
display.style.right = '0';
|
|
219
|
+
display.style.top = '0';
|
|
220
|
+
display.style.transform = 'rotate(180deg)';
|
|
221
|
+
display.style.overflowY = 'auto';
|
|
222
|
+
display.style.padding = '20px';
|
|
223
|
+
display.style.background = 'white';
|
|
224
|
+
|
|
225
|
+
// Position microphone instruction at the top (appears at bottom due to rotation)
|
|
226
|
+
allowMicrophoneElement.innerText = '';
|
|
227
|
+
allowMicrophoneElement.style.position = 'fixed';
|
|
228
|
+
allowMicrophoneElement.style.top = '20px';
|
|
229
|
+
allowMicrophoneElement.style.left = '50%';
|
|
230
|
+
allowMicrophoneElement.style.transform = 'translateX(-50%) rotate(180deg)';
|
|
231
|
+
allowMicrophoneElement.style.width = '90%';
|
|
232
|
+
allowMicrophoneElement.style.textAlign = 'center';
|
|
233
|
+
allowMicrophoneElement.style.zIndex = '1000';
|
|
234
|
+
|
|
235
|
+
let lock = null;
|
|
236
|
+
try {
|
|
237
|
+
if ('wakeLock' in navigator) {
|
|
238
|
+
lock = await navigator.wakeLock.request('screen');
|
|
239
|
+
}
|
|
240
|
+
} catch (err) {
|
|
241
|
+
console.log(err);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const webAudioDeviceNames = {microphone: '', deviceID: ''};
|
|
245
|
+
const externalMicList = ['UMIK', 'Airpods', 'Bluetooth'];
|
|
246
|
+
try {
|
|
247
|
+
const stream = await navigator.mediaDevices.getUserMedia({audio: true});
|
|
248
|
+
if (stream) {
|
|
249
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
250
|
+
const mics = devices.filter(device => device.kind === 'audioinput');
|
|
251
|
+
mics.forEach(mic => {
|
|
252
|
+
if (externalMicList.some(externalMic => mic.label.includes(externalMic))) {
|
|
253
|
+
webAudioDeviceNames.microphone = mic.label;
|
|
254
|
+
webAudioDeviceNames.deviceID = mic.deviceId;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
if (webAudioDeviceNames.microphone === '') {
|
|
258
|
+
webAudioDeviceNames.microphone = mics[0].label;
|
|
259
|
+
webAudioDeviceNames.deviceID = mics[0].deviceId;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
console.log(err);
|
|
264
|
+
}
|
|
265
|
+
window.listener.setMicrophoneFromAPI(webAudioDeviceNames.microphone);
|
|
266
|
+
window.listener.setMicrophoneDeviceId(webAudioDeviceNames.microphone);
|
|
267
|
+
// show target element
|
|
268
|
+
targetElement.style.display = 'block';
|
|
269
|
+
await window.listener.startCalibration({
|
|
270
|
+
microphoneFromAPI: webAudioDeviceNames.microphone,
|
|
271
|
+
microphoneDeviceId: webAudioDeviceNames.microphone,
|
|
272
|
+
});
|
|
273
|
+
if (lock) {
|
|
274
|
+
lock.release();
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Wrap the initialization in an IIFE
|
|
280
|
+
(async function initializeSmartPhoneMode() {
|
|
281
|
+
await initializeListener();
|
|
282
|
+
|
|
283
|
+
const timeout = 30000; // 30 seconds timeout
|
|
284
|
+
const startTime = Date.now();
|
|
285
|
+
|
|
286
|
+
// Wait for peer connection setup with timeout
|
|
287
|
+
while (Date.now() - startTime < timeout) {
|
|
288
|
+
if (
|
|
289
|
+
window.listener.peer.id !== null &&
|
|
290
|
+
window.listener.conn !== null &&
|
|
291
|
+
window.listener.connOpen
|
|
292
|
+
) {
|
|
293
|
+
console.log('Connection established successfully');
|
|
294
|
+
await checkAndRequestMicrophonePermission();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
console.log('Waiting for connection setup...');
|
|
298
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// If we get here, we've timed out
|
|
302
|
+
console.error('Connection setup timed out after 30 seconds');
|
|
303
|
+
allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
304
|
+
await window.listener.sendPermissionStatus({
|
|
305
|
+
type: 'error',
|
|
306
|
+
error: 'Connection setup timed out after 30 seconds',
|
|
307
|
+
});
|
|
308
|
+
})().catch(console.error);
|
|
309
|
+
break;
|
|
310
|
+
case 'false':
|
|
311
|
+
// Initialize listener immediately
|
|
312
|
+
listenerParameters.microphoneDeviceId = urlParams.get('deviceId');
|
|
313
|
+
|
|
314
|
+
// Wrap initialization in an IIFE
|
|
315
|
+
(async function initializeDesktopMode() {
|
|
316
|
+
window.listener = new Listener(listenerParameters);
|
|
317
|
+
await window.listener.initializePeer();
|
|
318
|
+
|
|
319
|
+
const timeout = 30000; // 30 seconds timeout
|
|
320
|
+
const startTime = Date.now();
|
|
321
|
+
|
|
322
|
+
// Wait for peer connection setup with timeout
|
|
323
|
+
while (Date.now() - startTime < timeout) {
|
|
324
|
+
if (
|
|
325
|
+
window.listener.peer.id !== null &&
|
|
326
|
+
window.listener.conn !== null &&
|
|
327
|
+
window.listener.connOpen
|
|
328
|
+
) {
|
|
329
|
+
console.log('Connection established successfully');
|
|
330
|
+
// Continue with desktop setup
|
|
331
|
+
setupDesktopUI();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
console.log('Waiting for connection setup...');
|
|
335
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// If we get here, we've timed out
|
|
339
|
+
console.error('Connection setup timed out after 30 seconds');
|
|
340
|
+
// const message = document.getElementById('message');
|
|
341
|
+
// message.innerText = phrases.RC_microphonePermissionDenied['en-US'];
|
|
342
|
+
})().catch(console.error);
|
|
343
|
+
|
|
344
|
+
function setupDesktopUI() {
|
|
345
|
+
// remove the button
|
|
346
|
+
const calibrationBeginButton2 = document.getElementById('calibrationBeginButton');
|
|
347
|
+
calibrationBeginButton2.remove();
|
|
348
|
+
container.style.display = 'block';
|
|
349
|
+
|
|
350
|
+
//update the display to be
|
|
351
|
+
const display = document.getElementById('display');
|
|
352
|
+
if (display) {
|
|
353
|
+
display.style.textAlign = 'left';
|
|
354
|
+
}
|
|
355
|
+
// set the text of the html elements
|
|
356
|
+
recordingInProgressElement.innerText = recordingInProgress;
|
|
357
|
+
allowMicrophoneElement.innerText = allowMicrophone;
|
|
358
|
+
|
|
359
|
+
recordingInProgressElement.style.whiteSpace = 'nowrap';
|
|
360
|
+
recordingInProgressElement.style.fontWeight = 'bold';
|
|
361
|
+
|
|
362
|
+
// fit content
|
|
363
|
+
recordingInProgressElement.style.width = 'fit-content';
|
|
364
|
+
let fontSize = 100;
|
|
365
|
+
recordingInProgressElement.style.fontSize = fontSize + 'px';
|
|
366
|
+
|
|
367
|
+
while (recordingInProgressElement.scrollWidth > window.innerWidth * 0.9 && fontSize > 10) {
|
|
368
|
+
fontSize--;
|
|
369
|
+
recordingInProgressElement.style.fontSize = fontSize + 'px';
|
|
370
|
+
}
|
|
371
|
+
const message = document.getElementById('message');
|
|
372
|
+
message.style.lineHeight = '2.5rem';
|
|
373
|
+
const p = document.createElement('p');
|
|
374
|
+
p.innerText = backToExperimentWindow;
|
|
375
|
+
message.appendChild(p);
|
|
376
|
+
|
|
377
|
+
window.listener.startCalibration();
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
}
|