speaker-calibration 2.2.249 → 2.2.251
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 +1 -1
- 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 -474
- package/src/listener-app/listener.js +380 -377
- 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 -299
- package/src/peer-connection/peerErrors.js +25 -25
- package/src/peer-connection/speaker.js +963 -963
- package/src/powerCheck.js +110 -110
- package/src/server/PythonServerAPI.js +959 -959
- package/src/tasks/combination/combination.js +3697 -3707
- 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,963 +1,963 @@
|
|
|
1
|
-
import QRCode from 'qrcode';
|
|
2
|
-
import AudioPeer from './audioPeer';
|
|
3
|
-
import {sleep, formatLineBreak, createAndShowPopup} from '../utils';
|
|
4
|
-
import {
|
|
5
|
-
UnsupportedDeviceError,
|
|
6
|
-
MissingSpeakerIdError,
|
|
7
|
-
CalibrationTimedOutError,
|
|
8
|
-
} from './peerErrors';
|
|
9
|
-
import Peer from 'peerjs';
|
|
10
|
-
|
|
11
|
-
//import {phrases} from '../../dist/example/i18n';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* @class Handles the speaker's side of the connection. Responsible for calibration process
|
|
15
|
-
* and communication with the listener.
|
|
16
|
-
* @augments AudioPeer
|
|
17
|
-
*/
|
|
18
|
-
class Speaker extends AudioPeer {
|
|
19
|
-
/**
|
|
20
|
-
* Takes the parameters for calibration and a connection manager instance.
|
|
21
|
-
*
|
|
22
|
-
* @param params - See type definition for initParameters.
|
|
23
|
-
* @param CalibratorInstance - An instance of the AudioCalibrator class
|
|
24
|
-
* @param connectionManager - An instance of the ConnectionManager class
|
|
25
|
-
*/
|
|
26
|
-
constructor(params, CalibratorInstance, connectionManager = null) {
|
|
27
|
-
super(params);
|
|
28
|
-
this.language = params?.language ?? 'en-US';
|
|
29
|
-
this.siteUrl += '/listener?';
|
|
30
|
-
// this.ac = CalibratorInstance;
|
|
31
|
-
this.result = null;
|
|
32
|
-
this.debug = params?.debug ?? false;
|
|
33
|
-
this.isSmartPhone = params?.isSmartPhone ?? false;
|
|
34
|
-
this.calibrateSoundHz = params?.calibrateSoundHz ?? 48000;
|
|
35
|
-
this.calibrateSoundSamplingDesiredBits = params?.calibrateSoundSamplingDesiredBits ?? 24;
|
|
36
|
-
this.instructionDisplayId = params?.instructionDisplayId ?? '';
|
|
37
|
-
this.soundSubtitleId = params?.soundSubtitleId ?? '';
|
|
38
|
-
this.timeToCalibrateDisplay = params?.timeToCalibrateId ?? '';
|
|
39
|
-
this.soundMessageId = params?.soundMessageId ?? '';
|
|
40
|
-
this.titleDisplayId = params?.titleDisplayId ?? '';
|
|
41
|
-
this.timeToCalibrate = params?.timeToCalibrate ?? 10;
|
|
42
|
-
this.isParticipant = params?.isParticipant ?? false;
|
|
43
|
-
this.isLoudspeakerCalibration = params?.isLoudspeakerCalibration ?? false;
|
|
44
|
-
this.deviceId = params?.micrpohoneIdFromWebAudioApi ?? '';
|
|
45
|
-
this.buttonsContainer = params?.buttonsContainer ?? document.createElement('div');
|
|
46
|
-
this.phrases = params?.phrases ?? {};
|
|
47
|
-
this.permissionStatus = 'pending';
|
|
48
|
-
this.calibrateSoundHz = params?.calibrateSoundHz ?? 48000;
|
|
49
|
-
this.name = 'SoundCalibration'; // Name used for submodule registration
|
|
50
|
-
this.connectionManager = connectionManager;
|
|
51
|
-
|
|
52
|
-
// Register with connection manager if provided
|
|
53
|
-
if (this.connectionManager) {
|
|
54
|
-
this.connectionManager.registerSubmodule(this);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
uri = '';
|
|
59
|
-
qrImage;
|
|
60
|
-
shortURL;
|
|
61
|
-
|
|
62
|
-
// Required method for submodule interface
|
|
63
|
-
onMessage(data, connectionManager) {
|
|
64
|
-
if (!data || (!data.name && !data.type)) {
|
|
65
|
-
console.error('Received malformed data: ', data);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Convert type to name if needed for backward compatibility
|
|
70
|
-
const messageName = data.payload.name;
|
|
71
|
-
const payload = data.payload.payload;
|
|
72
|
-
|
|
73
|
-
switch (messageName) {
|
|
74
|
-
case 'samplingRate':
|
|
75
|
-
console.log('Received sampling rate from listener: ', payload);
|
|
76
|
-
if (!payload) {
|
|
77
|
-
window.speaker.ac.setSamplingRates(window.speaker.calibrateSoundHz);
|
|
78
|
-
} else {
|
|
79
|
-
window.speaker.ac.setSamplingRates(payload);
|
|
80
|
-
}
|
|
81
|
-
break;
|
|
82
|
-
case 'sampleSize':
|
|
83
|
-
window.speaker.ac.setSampleSize(payload);
|
|
84
|
-
break;
|
|
85
|
-
case 'deviceType':
|
|
86
|
-
window.speaker.ac.setDeviceType(payload);
|
|
87
|
-
break;
|
|
88
|
-
case 'deviceName':
|
|
89
|
-
window.speaker.ac.setDeviceName(payload);
|
|
90
|
-
break;
|
|
91
|
-
case 'flags':
|
|
92
|
-
console.log('FLAGS');
|
|
93
|
-
console.log(payload);
|
|
94
|
-
window.speaker.ac.setFlags(payload);
|
|
95
|
-
break;
|
|
96
|
-
case 'deviceInfo':
|
|
97
|
-
window.speaker.ac.setDeviceInfo(payload);
|
|
98
|
-
console.log('Received device info from listener: ', payload);
|
|
99
|
-
break;
|
|
100
|
-
case 'permissionStatus':
|
|
101
|
-
console.log('Received permission status from listener: ', payload);
|
|
102
|
-
if (payload.type === 'error') {
|
|
103
|
-
this.permissionStatus = 'error';
|
|
104
|
-
window.speaker.ac.setPermissionStatus('error');
|
|
105
|
-
} else if (payload.type === 'denied') {
|
|
106
|
-
this.permissionStatus = 'denied';
|
|
107
|
-
window.speaker.ac.setPermissionStatus('denied');
|
|
108
|
-
} else if (payload.type === 'granted') {
|
|
109
|
-
this.permissionStatus = 'granted';
|
|
110
|
-
window.speaker.ac.setPermissionStatus('granted');
|
|
111
|
-
console.log('Permission granted');
|
|
112
|
-
}
|
|
113
|
-
break;
|
|
114
|
-
case UnsupportedDeviceError.name:
|
|
115
|
-
case MissingSpeakerIdError.name:
|
|
116
|
-
throw payload;
|
|
117
|
-
default:
|
|
118
|
-
break;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Prepares the parameters to send to the listener
|
|
124
|
-
* @returns {Object} Parameters for the listener
|
|
125
|
-
*/
|
|
126
|
-
prepareConnectionParams() {
|
|
127
|
-
const params = {
|
|
128
|
-
type: 'SoundCalibration',
|
|
129
|
-
name: 'SoundCalibration',
|
|
130
|
-
message: 'connectionParams',
|
|
131
|
-
payload: {
|
|
132
|
-
speakerPeerId: this.connectionManager?.getPeerID(),
|
|
133
|
-
sp: this.isSmartPhone,
|
|
134
|
-
hz: this.calibrateSoundHz,
|
|
135
|
-
bits: this.calibrateSoundSamplingDesiredBits,
|
|
136
|
-
lang: this.language,
|
|
137
|
-
deviceId: this.deviceId,
|
|
138
|
-
},
|
|
139
|
-
};
|
|
140
|
-
console.log('prepareConnectionParams', params);
|
|
141
|
-
return params;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
//no need to connect to the listener, just simulate the calibration
|
|
145
|
-
static simulateCalibration = async (
|
|
146
|
-
params,
|
|
147
|
-
CalibratorInstance,
|
|
148
|
-
connectionManager,
|
|
149
|
-
timeOut = 180000
|
|
150
|
-
) => {
|
|
151
|
-
window.speaker = new Speaker(params, CalibratorInstance, connectionManager);
|
|
152
|
-
const {speaker} = window;
|
|
153
|
-
speaker.ac = CalibratorInstance;
|
|
154
|
-
//set sampling rates
|
|
155
|
-
speaker.ac.setSamplingRates(speaker.calibrateSoundHz);
|
|
156
|
-
speaker.ac.setSampleSize(speaker.calibrateSoundSamplingDesiredBits);
|
|
157
|
-
speaker.result = await speaker.ac.startCalibration(
|
|
158
|
-
null,
|
|
159
|
-
params.gainValues,
|
|
160
|
-
params.ICalib,
|
|
161
|
-
params.knownIR,
|
|
162
|
-
params.microphoneName,
|
|
163
|
-
params.calibrateSoundCheck,
|
|
164
|
-
params.isSmartPhone,
|
|
165
|
-
params.calibrateSoundBurstDb,
|
|
166
|
-
params.calibrateSoundBurstFilteredExtraDb,
|
|
167
|
-
params.calibrateSoundBurstLevelReTBool,
|
|
168
|
-
params.calibrateSoundBurstUses1000HzGainBool,
|
|
169
|
-
params.calibrateSoundBurstRepeats,
|
|
170
|
-
params.calibrateSoundBurstSec,
|
|
171
|
-
params._calibrateSoundBurstPreSec,
|
|
172
|
-
params._calibrateSoundBurstPostSec,
|
|
173
|
-
params.calibrateSoundHz,
|
|
174
|
-
params.calibrateSoundIRSec,
|
|
175
|
-
params.calibrateSoundIIRSec,
|
|
176
|
-
params.calibrateSoundIIRPhase,
|
|
177
|
-
params.calibrateSound1000HzPreSec,
|
|
178
|
-
params.calibrateSound1000HzSec,
|
|
179
|
-
params.calibrateSound1000HzPostSec,
|
|
180
|
-
params.calibrateSoundBackgroundSecs,
|
|
181
|
-
params.calibrateSoundSmoothOctaves,
|
|
182
|
-
params.calibrateSoundSmoothMinBandwidthHz,
|
|
183
|
-
params.calibrateSoundPowerBinDesiredSec,
|
|
184
|
-
params.calibrateSoundPowerDbSDToleratedDb,
|
|
185
|
-
params.calibrateSoundTaperSec,
|
|
186
|
-
params.micManufacturer,
|
|
187
|
-
params.micSerialNumber,
|
|
188
|
-
params.micModelNumber,
|
|
189
|
-
params.micModelName,
|
|
190
|
-
params.calibrateMicrophonesBool,
|
|
191
|
-
params.authorEmails,
|
|
192
|
-
params.webAudioDeviceNames,
|
|
193
|
-
params.IDsToSaveInSoundProfileLibrary,
|
|
194
|
-
params.restartButton,
|
|
195
|
-
params.reminder,
|
|
196
|
-
params.calibrateSoundLimit,
|
|
197
|
-
params.calibrateSoundBurstNormalizeBy1000HzGainBool,
|
|
198
|
-
params.calibrateSoundBurstScalarDB,
|
|
199
|
-
params.calibrateSound1000HzMaxSD_dB,
|
|
200
|
-
params.calibrateSound1000HzMaxTries,
|
|
201
|
-
params._calibrateSoundBurstMaxSD_dB,
|
|
202
|
-
params.calibrateSoundSamplingDesiredBits,
|
|
203
|
-
params.language,
|
|
204
|
-
params.loudspeakerModelName,
|
|
205
|
-
params.phrases,
|
|
206
|
-
params.soundSubtitleId,
|
|
207
|
-
params.calibrateSoundBurstDownsample,
|
|
208
|
-
params.calibrateSoundSimulateMicrophone,
|
|
209
|
-
params.calibrateSoundSimulateMicrophoneTime,
|
|
210
|
-
params.calibrateSoundSimulateLoudspeaker,
|
|
211
|
-
params.calibrateSoundSimulateLoudspeakerTime,
|
|
212
|
-
params.isLoudspeakerCalibration
|
|
213
|
-
);
|
|
214
|
-
speaker.#removeUIElems();
|
|
215
|
-
return speaker.result;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.
|
|
220
|
-
*
|
|
221
|
-
* @param params - The parameters to be passed to the peer object.
|
|
222
|
-
* @param CalibratorInstance - The class that defines the calibration process.
|
|
223
|
-
* @param connectionManager - Instance of the ConnectionManager
|
|
224
|
-
* @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).
|
|
225
|
-
* @public
|
|
226
|
-
*/
|
|
227
|
-
static startCalibration = async (
|
|
228
|
-
params,
|
|
229
|
-
CalibratorInstance,
|
|
230
|
-
connectionManager,
|
|
231
|
-
timeOut = 180000
|
|
232
|
-
) => {
|
|
233
|
-
// Create a speaker instance and register with the connection manager
|
|
234
|
-
window.speaker = new Speaker(params, CalibratorInstance, connectionManager);
|
|
235
|
-
const {speaker} = window;
|
|
236
|
-
speaker.ac = CalibratorInstance;
|
|
237
|
-
|
|
238
|
-
await speaker.connectionManager.waitForPeerConnection();
|
|
239
|
-
await speaker.connectionManager.resolveWhenHandshakeReceived();
|
|
240
|
-
speaker.connectionManager.sendPageTitle('EasyEyes Microphone');
|
|
241
|
-
speaker.connectionManager.send({
|
|
242
|
-
name: 'CompatibilityPeer',
|
|
243
|
-
message: 'Text',
|
|
244
|
-
text: 'Loading...',
|
|
245
|
-
});
|
|
246
|
-
speaker.connectionManager.send({
|
|
247
|
-
name: 'SoundCalibration',
|
|
248
|
-
message: 'phrases',
|
|
249
|
-
payload: speaker.phrases,
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Send connection parameters to the listener
|
|
253
|
-
speaker.connectionManager.send(speaker.prepareConnectionParams());
|
|
254
|
-
|
|
255
|
-
// wrap the calibration process in a promise so we can await it
|
|
256
|
-
return new Promise((resolve, reject) => {
|
|
257
|
-
// Add a permission check handler
|
|
258
|
-
const permissionCheckInterval = setInterval(() => {
|
|
259
|
-
if (speaker.permissionStatus === 'error' || speaker.permissionStatus === 'denied') {
|
|
260
|
-
clearInterval(permissionCheckInterval);
|
|
261
|
-
speaker.#removeUIElems();
|
|
262
|
-
resolve('permission denied');
|
|
263
|
-
}
|
|
264
|
-
}, 100);
|
|
265
|
-
|
|
266
|
-
console.log('Setting up call handler on the peer');
|
|
267
|
-
// Set up call handler on the peer
|
|
268
|
-
speaker.connectionManager.peer.on('call', async call => {
|
|
269
|
-
console.log('Received call from listener');
|
|
270
|
-
clearInterval(permissionCheckInterval); // Clear interval when call is received
|
|
271
|
-
|
|
272
|
-
// Answer the call
|
|
273
|
-
call.answer();
|
|
274
|
-
speaker.#removeUIElems();
|
|
275
|
-
speaker.#showSpinner();
|
|
276
|
-
speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));
|
|
277
|
-
|
|
278
|
-
// when we start receiving audio
|
|
279
|
-
call.on('stream', async stream => {
|
|
280
|
-
window.localStream = stream;
|
|
281
|
-
window.localAudio.srcObject = stream;
|
|
282
|
-
window.localAudio.autoplay = false;
|
|
283
|
-
|
|
284
|
-
// if the sinkSamplingRate is not set sleep
|
|
285
|
-
while (!speaker.ac.sampleRatesSet()) {
|
|
286
|
-
console.log('SinkSamplingRate is undefined, sleeping');
|
|
287
|
-
await sleep(1);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
if (params.displayUpdate) {
|
|
291
|
-
params.displayUpdate.style.display = '';
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// resolve when we have a result
|
|
295
|
-
speaker.result = await speaker.ac.startCalibration(
|
|
296
|
-
stream,
|
|
297
|
-
params.gainValues,
|
|
298
|
-
params.ICalib,
|
|
299
|
-
params.knownIR,
|
|
300
|
-
params.microphoneName,
|
|
301
|
-
params.calibrateSoundCheck,
|
|
302
|
-
params.isSmartPhone,
|
|
303
|
-
params.calibrateSoundBurstDb,
|
|
304
|
-
params.calibrateSoundBurstFilteredExtraDb,
|
|
305
|
-
params.calibrateSoundBurstLevelReTBool,
|
|
306
|
-
params.calibrateSoundBurstUses1000HzGainBool,
|
|
307
|
-
params.calibrateSoundBurstRepeats,
|
|
308
|
-
params.calibrateSoundBurstSec,
|
|
309
|
-
params._calibrateSoundBurstPreSec,
|
|
310
|
-
params._calibrateSoundBurstPostSec,
|
|
311
|
-
params.calibrateSoundHz,
|
|
312
|
-
params.calibrateSoundIRSec,
|
|
313
|
-
params.calibrateSoundIIRSec,
|
|
314
|
-
params.calibrateSoundIIRPhase,
|
|
315
|
-
params.calibrateSound1000HzPreSec,
|
|
316
|
-
params.calibrateSound1000HzSec,
|
|
317
|
-
params.calibrateSound1000HzPostSec,
|
|
318
|
-
params.calibrateSoundBackgroundSecs,
|
|
319
|
-
params.calibrateSoundSmoothOctaves,
|
|
320
|
-
params.calibrateSoundSmoothMinBandwidthHz,
|
|
321
|
-
params.calibrateSoundPowerBinDesiredSec,
|
|
322
|
-
params.calibrateSoundPowerDbSDToleratedDb,
|
|
323
|
-
params.calibrateSoundTaperSec,
|
|
324
|
-
params.micManufacturer,
|
|
325
|
-
params.micSerialNumber,
|
|
326
|
-
params.micModelNumber,
|
|
327
|
-
params.micModelName,
|
|
328
|
-
params.calibrateMicrophonesBool,
|
|
329
|
-
params.authorEmails,
|
|
330
|
-
params.webAudioDeviceNames,
|
|
331
|
-
params.IDsToSaveInSoundProfileLibrary,
|
|
332
|
-
params.restartButton,
|
|
333
|
-
params.reminder,
|
|
334
|
-
params.calibrateSoundLimit,
|
|
335
|
-
params.calibrateSoundBurstNormalizeBy1000HzGainBool,
|
|
336
|
-
params.calibrateSoundBurstScalarDB,
|
|
337
|
-
params.calibrateSound1000HzMaxSD_dB,
|
|
338
|
-
params.calibrateSound1000HzMaxTries,
|
|
339
|
-
params._calibrateSoundBurstMaxSD_dB,
|
|
340
|
-
params.calibrateSoundSamplingDesiredBits,
|
|
341
|
-
params.language,
|
|
342
|
-
params.loudspeakerModelName,
|
|
343
|
-
params.phrases,
|
|
344
|
-
params.soundSubtitleId,
|
|
345
|
-
params.calibrateSoundBurstDownsample,
|
|
346
|
-
params.calibrateSoundSimulateMicrophone,
|
|
347
|
-
params.calibrateSoundSimulateMicrophoneTime,
|
|
348
|
-
params.calibrateSoundSimulateLoudspeaker,
|
|
349
|
-
params.calibrateSoundSimulateLoudspeakerTime,
|
|
350
|
-
params.isLoudspeakerCalibration
|
|
351
|
-
);
|
|
352
|
-
speaker.#removeUIElems();
|
|
353
|
-
//remove the call
|
|
354
|
-
speaker.connectionManager.peer.off('call');
|
|
355
|
-
resolve(speaker.result);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
// if we do not receive a result within the timeout, reject
|
|
359
|
-
setTimeout(() => {
|
|
360
|
-
clearInterval(permissionCheckInterval);
|
|
361
|
-
reject(
|
|
362
|
-
new CalibrationTimedOutError(
|
|
363
|
-
`Calibration failed to produce a result after ${
|
|
364
|
-
timeOut / 1000
|
|
365
|
-
} seconds. Try increasing "_timeoutSec", which is currently ${
|
|
366
|
-
timeOut / 1000
|
|
367
|
-
} seconds.`
|
|
368
|
-
)
|
|
369
|
-
);
|
|
370
|
-
}, timeOut);
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
console.log('Call handler set up', speaker.connectionManager.peer);
|
|
374
|
-
});
|
|
375
|
-
};
|
|
376
|
-
|
|
377
|
-
static testIIR = async (params, CalibratorInstance, IIR, connectionManager, timeOut = 180000) => {
|
|
378
|
-
window.speaker = new Speaker(params, CalibratorInstance, connectionManager);
|
|
379
|
-
const {speaker} = window;
|
|
380
|
-
|
|
381
|
-
// Set QR code parameters
|
|
382
|
-
connectionManager.setQueryParams(speaker.prepareQRParams());
|
|
383
|
-
|
|
384
|
-
// wrap the calibration process in a promise so we can await it
|
|
385
|
-
return new Promise((resolve, reject) => {
|
|
386
|
-
// when a call is received
|
|
387
|
-
connectionManager.peer.on('call', async call => {
|
|
388
|
-
// Answer the call (one way)
|
|
389
|
-
call.answer();
|
|
390
|
-
speaker.#removeUIElems();
|
|
391
|
-
speaker.#showSpinner();
|
|
392
|
-
speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));
|
|
393
|
-
// when we start receiving audio
|
|
394
|
-
call.on('stream', async stream => {
|
|
395
|
-
window.localStream = stream;
|
|
396
|
-
window.localAudio.srcObject = stream;
|
|
397
|
-
window.localAudio.autoplay = false;
|
|
398
|
-
|
|
399
|
-
// if the sinkSamplingRate is not set sleep
|
|
400
|
-
while (!speaker.ac.sampleRatesSet()) {
|
|
401
|
-
console.log('SinkSamplingRate is undefined, sleeping');
|
|
402
|
-
await sleep(1);
|
|
403
|
-
}
|
|
404
|
-
// resolve when we have a result
|
|
405
|
-
speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);
|
|
406
|
-
speaker.#removeUIElems();
|
|
407
|
-
resolve(speaker.result);
|
|
408
|
-
});
|
|
409
|
-
// if we do not receive a result within the timeout, reject
|
|
410
|
-
setTimeout(() => {
|
|
411
|
-
reject(
|
|
412
|
-
new CalibrationTimedOutError(
|
|
413
|
-
`Calibration failed to produce a result after ${
|
|
414
|
-
timeOut / 1000
|
|
415
|
-
} seconds. Try increasing "_timeoutSec", which is currently ${
|
|
416
|
-
timeOut / 1000
|
|
417
|
-
} seconds.`
|
|
418
|
-
)
|
|
419
|
-
);
|
|
420
|
-
}, timeOut);
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* Called after the peer conncection has been opened.
|
|
427
|
-
* Generates a QR code for the connection and displays it.
|
|
428
|
-
*
|
|
429
|
-
* @private
|
|
430
|
-
* @example
|
|
431
|
-
*/
|
|
432
|
-
|
|
433
|
-
#showQRCode = async () => {
|
|
434
|
-
const queryStringParameters = {
|
|
435
|
-
speakerPeerId: this.peer.id,
|
|
436
|
-
sp: this.isSmartPhone,
|
|
437
|
-
hz: this.calibrateSoundHz,
|
|
438
|
-
bits: this.calibrateSoundSamplingDesiredBits,
|
|
439
|
-
lang: this.language,
|
|
440
|
-
deviceId: this.deviceId,
|
|
441
|
-
};
|
|
442
|
-
const queryString = this.queryStringFromObject(queryStringParameters);
|
|
443
|
-
this.uri = this.siteUrl + queryString;
|
|
444
|
-
|
|
445
|
-
if (this.isSmartPhone) {
|
|
446
|
-
// Generate QR code
|
|
447
|
-
const qrCanvas = document.createElement('canvas');
|
|
448
|
-
qrCanvas.setAttribute('id', 'qrCanvas');
|
|
449
|
-
QRCode.toCanvas(qrCanvas, this.uri, error => {
|
|
450
|
-
if (error) console.error(error);
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
// Create QR image
|
|
454
|
-
const qrImage = new Image();
|
|
455
|
-
qrImage.setAttribute('id', 'compatibilityCheckQRImage');
|
|
456
|
-
qrImage.style.zIndex = Infinity;
|
|
457
|
-
qrImage.style.height = '150px';
|
|
458
|
-
qrImage.style.width = '150px';
|
|
459
|
-
qrImage.style.margin = '-10px';
|
|
460
|
-
qrImage.style.aspectRatio = 1;
|
|
461
|
-
qrImage.src = qrCanvas.toDataURL();
|
|
462
|
-
this.qrImage = qrImage;
|
|
463
|
-
|
|
464
|
-
// Get shortened URL
|
|
465
|
-
let shortURL = this.uri;
|
|
466
|
-
try {
|
|
467
|
-
const response = await fetch('https://api.short.io/links/public', {
|
|
468
|
-
method: 'POST',
|
|
469
|
-
headers: {
|
|
470
|
-
Accept: 'application/json',
|
|
471
|
-
'Content-Type': 'application/json',
|
|
472
|
-
Authorization: 'pk_fysLKGj3legZz4XZ',
|
|
473
|
-
},
|
|
474
|
-
body: JSON.stringify({
|
|
475
|
-
domain: 'listeners.link',
|
|
476
|
-
originalURL: this.uri,
|
|
477
|
-
}),
|
|
478
|
-
});
|
|
479
|
-
if (!response.ok) {
|
|
480
|
-
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
481
|
-
}
|
|
482
|
-
const data = await response.json();
|
|
483
|
-
shortURL = data.shortURL;
|
|
484
|
-
} catch (error) {
|
|
485
|
-
console.error('Error:', error.message);
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// Main container with 3 columns
|
|
489
|
-
const container = document.createElement('div');
|
|
490
|
-
container.style.display = 'flex';
|
|
491
|
-
container.style.alignItems = 'flex-start';
|
|
492
|
-
container.style.paddingTop = '0';
|
|
493
|
-
container.id = 'skipQRContainer';
|
|
494
|
-
|
|
495
|
-
// Column 1: QR Code
|
|
496
|
-
const qrColumn = document.createElement('div');
|
|
497
|
-
qrColumn.style.flex = '0 0 auto';
|
|
498
|
-
qrColumn.appendChild(qrImage);
|
|
499
|
-
|
|
500
|
-
// Column 2: Explanation Text
|
|
501
|
-
const textColumn = document.createElement('div');
|
|
502
|
-
textColumn.style.flex = '1';
|
|
503
|
-
textColumn.style.padding = '0 20px';
|
|
504
|
-
textColumn.style.maxWidth = '560px';
|
|
505
|
-
|
|
506
|
-
const explanation = document.createElement('h2');
|
|
507
|
-
explanation.style.fontSize = '1.1rem';
|
|
508
|
-
explanation.id = 'skipQRExplanation';
|
|
509
|
-
explanation.style.margin = '0';
|
|
510
|
-
explanation.style.textAlign = 'left';
|
|
511
|
-
explanation.style.lineHeight = '1.5';
|
|
512
|
-
explanation.innerHTML = formatLineBreak(
|
|
513
|
-
this.phrases.RC_skipQR_ExplanationWithoutPreferNot[this.language]
|
|
514
|
-
.replace('xxx', `<b style="user-select: text">${shortURL}</b>`)
|
|
515
|
-
.replace('XXX', `<b style="user-select: text">${shortURL}</b>`),
|
|
516
|
-
this.phrases.RC_checkInternetConnection[this.language]
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
const language = this.language;
|
|
520
|
-
const phrases = this.phrases;
|
|
521
|
-
const checkConnection = document.createElement('a');
|
|
522
|
-
checkConnection.id = 'check-connection';
|
|
523
|
-
checkConnection.href = '#';
|
|
524
|
-
checkConnection.innerHTML = "check the phone's internet connection";
|
|
525
|
-
checkConnection.addEventListener('click', function (event) {
|
|
526
|
-
event.preventDefault();
|
|
527
|
-
createAndShowPopup(language, phrases);
|
|
528
|
-
});
|
|
529
|
-
explanation.querySelector('a#check-connection').replaceWith(checkConnection);
|
|
530
|
-
textColumn.appendChild(explanation);
|
|
531
|
-
|
|
532
|
-
// Column 3: Buttons
|
|
533
|
-
const buttonColumn = document.createElement('div');
|
|
534
|
-
buttonColumn.style.display = 'flex';
|
|
535
|
-
buttonColumn.style.flexDirection = 'column';
|
|
536
|
-
buttonColumn.style.gap = '10px';
|
|
537
|
-
buttonColumn.style.flex = '0 0 auto';
|
|
538
|
-
buttonColumn.style.alignItems = 'flex-end';
|
|
539
|
-
buttonColumn.appendChild(this.buttonsContainer);
|
|
540
|
-
|
|
541
|
-
// Assemble the columns
|
|
542
|
-
container.appendChild(qrColumn);
|
|
543
|
-
container.appendChild(textColumn);
|
|
544
|
-
container.appendChild(buttonColumn);
|
|
545
|
-
|
|
546
|
-
document.getElementById(this.targetElement).appendChild(container);
|
|
547
|
-
} else {
|
|
548
|
-
// show the link to the user
|
|
549
|
-
// If specified HTML Id is available, show QR code there
|
|
550
|
-
if (document.getElementById(this.targetElement)) {
|
|
551
|
-
// const linkTag = document.createElement('a');
|
|
552
|
-
// linkTag.setAttribute('href', uri);
|
|
553
|
-
// linkTag.innerHTML = 'Click here to start the calibration';
|
|
554
|
-
// linkTag.target = '_blank';
|
|
555
|
-
// document.getElementById(this.targetElement).appendChild(linkTag);
|
|
556
|
-
// document.getElementById(this.targetElement).appendChild(qrCanvas);
|
|
557
|
-
|
|
558
|
-
const proceedButton = document.createElement('button');
|
|
559
|
-
proceedButton.setAttribute('id', 'calibrationProceedButton');
|
|
560
|
-
proceedButton.setAttribute('class', 'btn btn-success');
|
|
561
|
-
proceedButton.innerHTML = this.phrases.T_proceed[this.language];
|
|
562
|
-
proceedButton.onclick = () => {
|
|
563
|
-
// open the link in a new tab
|
|
564
|
-
window.open(this.uri, '_blank');
|
|
565
|
-
// remove the button
|
|
566
|
-
document.getElementById('calibrationProceedButton').remove();
|
|
567
|
-
};
|
|
568
|
-
document.getElementById(this.targetElement).appendChild(proceedButton);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
// or just print it to console
|
|
572
|
-
console.log('TEST: Peer reachable at: ', this.uri);
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
#showSpinner = () => {
|
|
576
|
-
const spinner = document.createElement('div');
|
|
577
|
-
spinner.className = 'spinner-border ml-auto';
|
|
578
|
-
spinner.role = 'status';
|
|
579
|
-
spinner.ariaHidden = 'true';
|
|
580
|
-
spinner.style.marginTop = '0.8rem';
|
|
581
|
-
document.getElementById(this.targetElement).appendChild(spinner);
|
|
582
|
-
|
|
583
|
-
// clear instructionDisplay
|
|
584
|
-
const soundMessage = document.getElementById(this.soundMessageId);
|
|
585
|
-
soundMessage.innerHTML = '';
|
|
586
|
-
soundMessage.style.display = 'none';
|
|
587
|
-
const instructionDisplay = document.getElementById(this.instructionDisplayId);
|
|
588
|
-
const background = document.getElementById('background'); // todo: get background id from params
|
|
589
|
-
const subtitle = document.getElementById(this.soundSubtitleId);
|
|
590
|
-
if (subtitle) {
|
|
591
|
-
subtitle.innerHTML = '';
|
|
592
|
-
}
|
|
593
|
-
if (instructionDisplay) {
|
|
594
|
-
instructionDisplay.innerHTML = '';
|
|
595
|
-
instructionDisplay.style.whiteSpace = 'nowrap';
|
|
596
|
-
instructionDisplay.style.fontWeight = 'bold';
|
|
597
|
-
instructionDisplay.style.width = 'fit-content';
|
|
598
|
-
instructionDisplay.innerHTML = this.phrases.RC_soundRecording[this.language];
|
|
599
|
-
let fontSize = 100;
|
|
600
|
-
instructionDisplay.style.fontSize = fontSize + 'px';
|
|
601
|
-
while (instructionDisplay.scrollWidth > background.scrollWidth * 0.9 && fontSize > 10) {
|
|
602
|
-
fontSize--;
|
|
603
|
-
instructionDisplay.style.fontSize = fontSize + 'px';
|
|
604
|
-
}
|
|
605
|
-
// const p = document.createElement('p');
|
|
606
|
-
// // font size
|
|
607
|
-
// p.style.fontSize = '1.1rem';
|
|
608
|
-
// p.style.fontWeight = 'normal';
|
|
609
|
-
// p.style.paddingTop = '20px';
|
|
610
|
-
// const timeToCalibrateText = phrases.RC_howLongToCalibrate['en-US'];
|
|
611
|
-
// p.innerHTML = timeToCalibrateText.replace('111', this.timeToCalibrate);
|
|
612
|
-
// instructionDisplay.appendChild(p);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
const timeToCalibrateDisplay = document.getElementById(this.timeToCalibrateDisplay);
|
|
616
|
-
if (timeToCalibrateDisplay) {
|
|
617
|
-
const timeToCalibrateText = this.phrases.RC_howLongToCalibrate[this.language];
|
|
618
|
-
timeToCalibrateDisplay.innerHTML = timeToCalibrateText.replace('111', this.timeToCalibrate);
|
|
619
|
-
timeToCalibrateDisplay.style.fontWeight = 'normal';
|
|
620
|
-
timeToCalibrateDisplay.style.fontSize = '1rem';
|
|
621
|
-
// timeToCalibrateDisplay.style.paddingTop = '20px';
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
// Update title - titleDisplayId
|
|
625
|
-
const titleDisplay = document.getElementById(this.titleDisplayId);
|
|
626
|
-
if (titleDisplay) {
|
|
627
|
-
// if (this.isParticipant) {
|
|
628
|
-
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('3', '4');
|
|
629
|
-
// } else if (this.isSmartPhone) {
|
|
630
|
-
// if (this.isLoudspeakerCalibration) {
|
|
631
|
-
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('6', '7');
|
|
632
|
-
// } else {
|
|
633
|
-
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('5', '6');
|
|
634
|
-
// }
|
|
635
|
-
// } else {
|
|
636
|
-
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('5', '6');
|
|
637
|
-
// }
|
|
638
|
-
if (this.isLoudspeakerCalibration) {
|
|
639
|
-
if (this.isParticipant) {
|
|
640
|
-
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('3', '4');
|
|
641
|
-
} else if (this.isSmartPhone) {
|
|
642
|
-
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('6', '7');
|
|
643
|
-
} else {
|
|
644
|
-
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('4', '5');
|
|
645
|
-
}
|
|
646
|
-
} else {
|
|
647
|
-
if (this.isSmartPhone) {
|
|
648
|
-
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('5', '6');
|
|
649
|
-
} else {
|
|
650
|
-
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('3', '4');
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
};
|
|
655
|
-
|
|
656
|
-
#removeUIElems = () => {
|
|
657
|
-
const parent = document.getElementById(this.targetElement);
|
|
658
|
-
while (parent.firstChild) {
|
|
659
|
-
parent.firstChild.remove();
|
|
660
|
-
}
|
|
661
|
-
};
|
|
662
|
-
|
|
663
|
-
/**
|
|
664
|
-
* Called when the peer connection is opened.
|
|
665
|
-
* Saves the peer id and calls the QR code generator.
|
|
666
|
-
*
|
|
667
|
-
* @param peerId - The peer id of the peer connection.
|
|
668
|
-
* @param id
|
|
669
|
-
* @private
|
|
670
|
-
* @example
|
|
671
|
-
*/
|
|
672
|
-
#onPeerOpen = id => {
|
|
673
|
-
// Workaround for peer.reconnect deleting previous id
|
|
674
|
-
try {
|
|
675
|
-
if (id === null) {
|
|
676
|
-
console.error('Received null id from peer open');
|
|
677
|
-
this.peer.id = this.lastPeerId;
|
|
678
|
-
} else {
|
|
679
|
-
this.lastPeerId = this.peer.id;
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
if (id !== this.peer.id) {
|
|
683
|
-
console.warn('DEBUG Check you assumption that id === this.peer.id');
|
|
684
|
-
}
|
|
685
|
-
} catch (error) {
|
|
686
|
-
console.error('Error in #onPeerOpen: ', error);
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
this.#showQRCode();
|
|
690
|
-
};
|
|
691
|
-
|
|
692
|
-
/**
|
|
693
|
-
* Called when the peer connection is established.
|
|
694
|
-
* Enforces a single connection.
|
|
695
|
-
*
|
|
696
|
-
* @param connection - The connection object.
|
|
697
|
-
* @private
|
|
698
|
-
* @example
|
|
699
|
-
*/
|
|
700
|
-
#onPeerConnection = connection => {
|
|
701
|
-
// Allow only a single connection
|
|
702
|
-
if (this.conn && this.conn.open) {
|
|
703
|
-
connection.on('open', () => {
|
|
704
|
-
connection.send('Already connected to another client');
|
|
705
|
-
setTimeout(() => {
|
|
706
|
-
connection.close();
|
|
707
|
-
}, 500);
|
|
708
|
-
});
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
this.conn = connection;
|
|
713
|
-
console.log('Connected to: ', this.conn.peer);
|
|
714
|
-
this.#ready();
|
|
715
|
-
};
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* Called when the peer connection is closed.
|
|
719
|
-
*
|
|
720
|
-
* @private
|
|
721
|
-
* @example
|
|
722
|
-
*/
|
|
723
|
-
onPeerClose = () => {
|
|
724
|
-
this.conn = null;
|
|
725
|
-
console.log('Connection destroyed');
|
|
726
|
-
};
|
|
727
|
-
|
|
728
|
-
static closeConnection = () => {
|
|
729
|
-
this.conn = null;
|
|
730
|
-
console.log('Connection destroyed');
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Called when the peer connection is disconnected.
|
|
735
|
-
* Attempts to reconnect.
|
|
736
|
-
*
|
|
737
|
-
* @private
|
|
738
|
-
* @example
|
|
739
|
-
*/
|
|
740
|
-
#onPeerDisconnected = () => {
|
|
741
|
-
console.log('Connection lost. Please reconnect');
|
|
742
|
-
|
|
743
|
-
try {
|
|
744
|
-
// Workaround for peer.reconnect deleting previous id
|
|
745
|
-
this.peer.id = this.lastPeerId;
|
|
746
|
-
// eslint-disable-next-line no-underscore-dangle
|
|
747
|
-
this.peer._lastServerId = this.lastPeerId;
|
|
748
|
-
this.peer.reconnect();
|
|
749
|
-
} catch (error) {
|
|
750
|
-
console.error('Error in #onPeerDisconnected: ', error);
|
|
751
|
-
}
|
|
752
|
-
};
|
|
753
|
-
|
|
754
|
-
/**
|
|
755
|
-
* Called when the peer connection encounters an error.
|
|
756
|
-
*
|
|
757
|
-
* @param error
|
|
758
|
-
* @private
|
|
759
|
-
* @example
|
|
760
|
-
*/
|
|
761
|
-
#onPeerError = error => {
|
|
762
|
-
// TODO: check if this function is needed or not
|
|
763
|
-
console.error(error);
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* Called when data is received from the peer connection.
|
|
768
|
-
*
|
|
769
|
-
* @param data
|
|
770
|
-
* @private
|
|
771
|
-
* @example
|
|
772
|
-
*/
|
|
773
|
-
#onIncomingData = data => {
|
|
774
|
-
// enforce object type
|
|
775
|
-
if (
|
|
776
|
-
!Object.prototype.hasOwnProperty.call(data, 'name') ||
|
|
777
|
-
!Object.prototype.hasOwnProperty.call(data, 'payload')
|
|
778
|
-
) {
|
|
779
|
-
console.error('Received malformed data: ', data);
|
|
780
|
-
return;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const name = data.payload.name;
|
|
784
|
-
const payload = data.payload.payload;
|
|
785
|
-
|
|
786
|
-
switch (name) {
|
|
787
|
-
case 'samplingRate':
|
|
788
|
-
console.log('Received sampling rate from listener: ', payload);
|
|
789
|
-
if (!payload) {
|
|
790
|
-
window.speaker.ac.setSamplingRates(window.speaker.calibrateSoundHz);
|
|
791
|
-
} else {
|
|
792
|
-
window.speaker.ac.setSamplingRates(payload);
|
|
793
|
-
}
|
|
794
|
-
break;
|
|
795
|
-
case 'sampleSize':
|
|
796
|
-
window.speaker.ac.setSampleSize(payload);
|
|
797
|
-
break;
|
|
798
|
-
case 'deviceType':
|
|
799
|
-
window.speaker.ac.setDeviceType(payload);
|
|
800
|
-
break;
|
|
801
|
-
case 'deviceName':
|
|
802
|
-
window.speaker.ac.setDeviceName(payload);
|
|
803
|
-
break;
|
|
804
|
-
case 'flags':
|
|
805
|
-
//this.ac.setDeviceName(data.payload);
|
|
806
|
-
console.log('FLAGS');
|
|
807
|
-
console.log(payload);
|
|
808
|
-
window.speaker.ac.setFlags(payload);
|
|
809
|
-
break;
|
|
810
|
-
case 'deviceInfo':
|
|
811
|
-
window.speaker.ac.setDeviceInfo(payload);
|
|
812
|
-
console.log('Received device info from listener: ', payload);
|
|
813
|
-
break;
|
|
814
|
-
case 'permissionStatus':
|
|
815
|
-
console.log('Received permission status from listener: ', payload);
|
|
816
|
-
if (payload.type === 'error') {
|
|
817
|
-
this.permissionStatus = 'error';
|
|
818
|
-
window.speaker.ac.setPermissionStatus('error');
|
|
819
|
-
} else if (payload.type === 'denied') {
|
|
820
|
-
this.permissionStatus = 'denied';
|
|
821
|
-
window.speaker.ac.setPermissionStatus('denied');
|
|
822
|
-
} else if (payload.type === 'granted') {
|
|
823
|
-
this.permissionStatus = 'granted';
|
|
824
|
-
window.speaker.ac.setPermissionStatus('granted');
|
|
825
|
-
console.log('Permission granted');
|
|
826
|
-
}
|
|
827
|
-
break;
|
|
828
|
-
case UnsupportedDeviceError.name:
|
|
829
|
-
case MissingSpeakerIdError.name:
|
|
830
|
-
throw payload;
|
|
831
|
-
break;
|
|
832
|
-
default:
|
|
833
|
-
break;
|
|
834
|
-
}
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
/**
|
|
838
|
-
* Called when the peer connection is #ready.
|
|
839
|
-
*
|
|
840
|
-
* @private
|
|
841
|
-
* @example
|
|
842
|
-
*/
|
|
843
|
-
#ready = () => {
|
|
844
|
-
// Perform callback with data
|
|
845
|
-
this.conn.on('data', this.#onIncomingData);
|
|
846
|
-
this.conn.on('close', () => {
|
|
847
|
-
console.log('Connection reset<br>Awaiting connection...');
|
|
848
|
-
this.conn = null;
|
|
849
|
-
});
|
|
850
|
-
};
|
|
851
|
-
|
|
852
|
-
/** .
|
|
853
|
-
* .
|
|
854
|
-
* .
|
|
855
|
-
* Debug method for downloading the recorded audio
|
|
856
|
-
*
|
|
857
|
-
* @public
|
|
858
|
-
* @example
|
|
859
|
-
*/
|
|
860
|
-
downloadData = () => {
|
|
861
|
-
this.ac.downloadData();
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
static repeatCalibration = async (params, stream, CalibratorInstance) => {
|
|
865
|
-
window.speaker.ac = CalibratorInstance;
|
|
866
|
-
window.speaker.#removeUIElems();
|
|
867
|
-
window.speaker.#showSpinner();
|
|
868
|
-
|
|
869
|
-
console.log('This is a repeat');
|
|
870
|
-
// wrap the calibration process in a promise so we can await it
|
|
871
|
-
return new Promise((resolve, reject) => {
|
|
872
|
-
// Add a permission check handler
|
|
873
|
-
const permissionCheckInterval = setInterval(() => {
|
|
874
|
-
if (
|
|
875
|
-
window.speaker.permissionStatus === 'error' ||
|
|
876
|
-
window.speaker.permissionStatus === 'denied'
|
|
877
|
-
) {
|
|
878
|
-
clearInterval(permissionCheckInterval);
|
|
879
|
-
window.speaker.#removeUIElems();
|
|
880
|
-
resolve('permission denied');
|
|
881
|
-
}
|
|
882
|
-
}, 100);
|
|
883
|
-
|
|
884
|
-
// Start calibration process
|
|
885
|
-
(async () => {
|
|
886
|
-
try {
|
|
887
|
-
const result = await window.speaker.ac.startCalibration(
|
|
888
|
-
stream,
|
|
889
|
-
params.gainValues,
|
|
890
|
-
params.ICalib,
|
|
891
|
-
params.knownIR,
|
|
892
|
-
params.microphoneName,
|
|
893
|
-
params.calibrateSoundCheck,
|
|
894
|
-
params.isSmartPhone,
|
|
895
|
-
params.calibrateSoundBurstDb,
|
|
896
|
-
params.calibrateSoundBurstFilteredExtraDb,
|
|
897
|
-
params.calibrateSoundBurstLevelReTBool,
|
|
898
|
-
params.calibrateSoundBurstUses1000HzGainBool,
|
|
899
|
-
params.calibrateSoundBurstRepeats,
|
|
900
|
-
params.calibrateSoundBurstSec,
|
|
901
|
-
params._calibrateSoundBurstPreSec,
|
|
902
|
-
params._calibrateSoundBurstPostSec,
|
|
903
|
-
params.calibrateSoundHz,
|
|
904
|
-
params.calibrateSoundIRSec,
|
|
905
|
-
params.calibrateSoundIIRSec,
|
|
906
|
-
params.calibrateSoundIIRPhase,
|
|
907
|
-
params.calibrateSound1000HzPreSec,
|
|
908
|
-
params.calibrateSound1000HzSec,
|
|
909
|
-
params.calibrateSound1000HzPostSec,
|
|
910
|
-
params.calibrateSoundBackgroundSecs,
|
|
911
|
-
params.calibrateSoundSmoothOctaves,
|
|
912
|
-
params.calibrateSoundSmoothMinBandwidthHz,
|
|
913
|
-
params.calibrateSoundPowerBinDesiredSec,
|
|
914
|
-
params.calibrateSoundPowerDbSDToleratedDb,
|
|
915
|
-
params.calibrateSoundTaperSec,
|
|
916
|
-
params.micManufacturer,
|
|
917
|
-
params.micSerialNumber,
|
|
918
|
-
params.micModelNumber,
|
|
919
|
-
params.micModelName,
|
|
920
|
-
params.calibrateMicrophonesBool,
|
|
921
|
-
params.authorEmails,
|
|
922
|
-
params.webAudioDeviceNames,
|
|
923
|
-
params.IDsToSaveInSoundProfileLibrary,
|
|
924
|
-
params.restartButton,
|
|
925
|
-
params.reminder,
|
|
926
|
-
params.calibrateSoundLimit,
|
|
927
|
-
params.calibrateSoundBurstNormalizeBy1000HzGainBool,
|
|
928
|
-
params.calibrateSoundBurstScalarDB,
|
|
929
|
-
params.calibrateSound1000HzMaxSD_dB,
|
|
930
|
-
params.calibrateSound1000HzMaxTries,
|
|
931
|
-
params._calibrateSoundBurstMaxSD_dB,
|
|
932
|
-
params.calibrateSoundSamplingDesiredBits,
|
|
933
|
-
params.language,
|
|
934
|
-
params.loudspeakerModelName,
|
|
935
|
-
params.phrases,
|
|
936
|
-
params.soundSubtitleId,
|
|
937
|
-
params.calibrateSoundBurstDownsample,
|
|
938
|
-
params.calibrateSoundSimulateMicrophone,
|
|
939
|
-
params.calibrateSoundSimulateMicrophoneTime,
|
|
940
|
-
params.calibrateSoundSimulateLoudspeaker,
|
|
941
|
-
params.calibrateSoundSimulateLoudspeakerTime,
|
|
942
|
-
params.isLoudspeakerCalibration
|
|
943
|
-
);
|
|
944
|
-
clearInterval(permissionCheckInterval);
|
|
945
|
-
window.speaker.#removeUIElems();
|
|
946
|
-
resolve(result);
|
|
947
|
-
} catch (error) {
|
|
948
|
-
clearInterval(permissionCheckInterval);
|
|
949
|
-
reject(error);
|
|
950
|
-
}
|
|
951
|
-
})();
|
|
952
|
-
});
|
|
953
|
-
};
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
/*
|
|
957
|
-
Referenced links:
|
|
958
|
-
https://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676
|
|
959
|
-
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
|
|
960
|
-
https://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]
|
|
961
|
-
*/
|
|
962
|
-
|
|
963
|
-
export default Speaker;
|
|
1
|
+
import QRCode from 'qrcode';
|
|
2
|
+
import AudioPeer from './audioPeer';
|
|
3
|
+
import {sleep, formatLineBreak, createAndShowPopup} from '../utils';
|
|
4
|
+
import {
|
|
5
|
+
UnsupportedDeviceError,
|
|
6
|
+
MissingSpeakerIdError,
|
|
7
|
+
CalibrationTimedOutError,
|
|
8
|
+
} from './peerErrors';
|
|
9
|
+
import Peer from 'peerjs';
|
|
10
|
+
|
|
11
|
+
//import {phrases} from '../../dist/example/i18n';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @class Handles the speaker's side of the connection. Responsible for calibration process
|
|
15
|
+
* and communication with the listener.
|
|
16
|
+
* @augments AudioPeer
|
|
17
|
+
*/
|
|
18
|
+
class Speaker extends AudioPeer {
|
|
19
|
+
/**
|
|
20
|
+
* Takes the parameters for calibration and a connection manager instance.
|
|
21
|
+
*
|
|
22
|
+
* @param params - See type definition for initParameters.
|
|
23
|
+
* @param CalibratorInstance - An instance of the AudioCalibrator class
|
|
24
|
+
* @param connectionManager - An instance of the ConnectionManager class
|
|
25
|
+
*/
|
|
26
|
+
constructor(params, CalibratorInstance, connectionManager = null) {
|
|
27
|
+
super(params);
|
|
28
|
+
this.language = params?.language ?? 'en-US';
|
|
29
|
+
this.siteUrl += '/listener?';
|
|
30
|
+
// this.ac = CalibratorInstance;
|
|
31
|
+
this.result = null;
|
|
32
|
+
this.debug = params?.debug ?? false;
|
|
33
|
+
this.isSmartPhone = params?.isSmartPhone ?? false;
|
|
34
|
+
this.calibrateSoundHz = params?.calibrateSoundHz ?? 48000;
|
|
35
|
+
this.calibrateSoundSamplingDesiredBits = params?.calibrateSoundSamplingDesiredBits ?? 24;
|
|
36
|
+
this.instructionDisplayId = params?.instructionDisplayId ?? '';
|
|
37
|
+
this.soundSubtitleId = params?.soundSubtitleId ?? '';
|
|
38
|
+
this.timeToCalibrateDisplay = params?.timeToCalibrateId ?? '';
|
|
39
|
+
this.soundMessageId = params?.soundMessageId ?? '';
|
|
40
|
+
this.titleDisplayId = params?.titleDisplayId ?? '';
|
|
41
|
+
this.timeToCalibrate = params?.timeToCalibrate ?? 10;
|
|
42
|
+
this.isParticipant = params?.isParticipant ?? false;
|
|
43
|
+
this.isLoudspeakerCalibration = params?.isLoudspeakerCalibration ?? false;
|
|
44
|
+
this.deviceId = params?.micrpohoneIdFromWebAudioApi ?? '';
|
|
45
|
+
this.buttonsContainer = params?.buttonsContainer ?? document.createElement('div');
|
|
46
|
+
this.phrases = params?.phrases ?? {};
|
|
47
|
+
this.permissionStatus = 'pending';
|
|
48
|
+
this.calibrateSoundHz = params?.calibrateSoundHz ?? 48000;
|
|
49
|
+
this.name = 'SoundCalibration'; // Name used for submodule registration
|
|
50
|
+
this.connectionManager = connectionManager;
|
|
51
|
+
|
|
52
|
+
// Register with connection manager if provided
|
|
53
|
+
if (this.connectionManager) {
|
|
54
|
+
this.connectionManager.registerSubmodule(this);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
uri = '';
|
|
59
|
+
qrImage;
|
|
60
|
+
shortURL;
|
|
61
|
+
|
|
62
|
+
// Required method for submodule interface
|
|
63
|
+
onMessage(data, connectionManager) {
|
|
64
|
+
if (!data || (!data.name && !data.type)) {
|
|
65
|
+
console.error('Received malformed data: ', data);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Convert type to name if needed for backward compatibility
|
|
70
|
+
const messageName = data.payload.name;
|
|
71
|
+
const payload = data.payload.payload;
|
|
72
|
+
|
|
73
|
+
switch (messageName) {
|
|
74
|
+
case 'samplingRate':
|
|
75
|
+
console.log('Received sampling rate from listener: ', payload);
|
|
76
|
+
if (!payload) {
|
|
77
|
+
window.speaker.ac.setSamplingRates(window.speaker.calibrateSoundHz);
|
|
78
|
+
} else {
|
|
79
|
+
window.speaker.ac.setSamplingRates(payload);
|
|
80
|
+
}
|
|
81
|
+
break;
|
|
82
|
+
case 'sampleSize':
|
|
83
|
+
window.speaker.ac.setSampleSize(payload);
|
|
84
|
+
break;
|
|
85
|
+
case 'deviceType':
|
|
86
|
+
window.speaker.ac.setDeviceType(payload);
|
|
87
|
+
break;
|
|
88
|
+
case 'deviceName':
|
|
89
|
+
window.speaker.ac.setDeviceName(payload);
|
|
90
|
+
break;
|
|
91
|
+
case 'flags':
|
|
92
|
+
console.log('FLAGS');
|
|
93
|
+
console.log(payload);
|
|
94
|
+
window.speaker.ac.setFlags(payload);
|
|
95
|
+
break;
|
|
96
|
+
case 'deviceInfo':
|
|
97
|
+
window.speaker.ac.setDeviceInfo(payload);
|
|
98
|
+
console.log('Received device info from listener: ', payload);
|
|
99
|
+
break;
|
|
100
|
+
case 'permissionStatus':
|
|
101
|
+
console.log('Received permission status from listener: ', payload);
|
|
102
|
+
if (payload.type === 'error') {
|
|
103
|
+
this.permissionStatus = 'error';
|
|
104
|
+
window.speaker.ac.setPermissionStatus('error');
|
|
105
|
+
} else if (payload.type === 'denied') {
|
|
106
|
+
this.permissionStatus = 'denied';
|
|
107
|
+
window.speaker.ac.setPermissionStatus('denied');
|
|
108
|
+
} else if (payload.type === 'granted') {
|
|
109
|
+
this.permissionStatus = 'granted';
|
|
110
|
+
window.speaker.ac.setPermissionStatus('granted');
|
|
111
|
+
console.log('Permission granted');
|
|
112
|
+
}
|
|
113
|
+
break;
|
|
114
|
+
case UnsupportedDeviceError.name:
|
|
115
|
+
case MissingSpeakerIdError.name:
|
|
116
|
+
throw payload;
|
|
117
|
+
default:
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Prepares the parameters to send to the listener
|
|
124
|
+
* @returns {Object} Parameters for the listener
|
|
125
|
+
*/
|
|
126
|
+
prepareConnectionParams() {
|
|
127
|
+
const params = {
|
|
128
|
+
type: 'SoundCalibration',
|
|
129
|
+
name: 'SoundCalibration',
|
|
130
|
+
message: 'connectionParams',
|
|
131
|
+
payload: {
|
|
132
|
+
speakerPeerId: this.connectionManager?.getPeerID(),
|
|
133
|
+
sp: this.isSmartPhone,
|
|
134
|
+
hz: this.calibrateSoundHz,
|
|
135
|
+
bits: this.calibrateSoundSamplingDesiredBits,
|
|
136
|
+
lang: this.language,
|
|
137
|
+
deviceId: this.deviceId,
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
console.log('prepareConnectionParams', params);
|
|
141
|
+
return params;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
//no need to connect to the listener, just simulate the calibration
|
|
145
|
+
static simulateCalibration = async (
|
|
146
|
+
params,
|
|
147
|
+
CalibratorInstance,
|
|
148
|
+
connectionManager,
|
|
149
|
+
timeOut = 180000
|
|
150
|
+
) => {
|
|
151
|
+
window.speaker = new Speaker(params, CalibratorInstance, connectionManager);
|
|
152
|
+
const {speaker} = window;
|
|
153
|
+
speaker.ac = CalibratorInstance;
|
|
154
|
+
//set sampling rates
|
|
155
|
+
speaker.ac.setSamplingRates(speaker.calibrateSoundHz);
|
|
156
|
+
speaker.ac.setSampleSize(speaker.calibrateSoundSamplingDesiredBits);
|
|
157
|
+
speaker.result = await speaker.ac.startCalibration(
|
|
158
|
+
null,
|
|
159
|
+
params.gainValues,
|
|
160
|
+
params.ICalib,
|
|
161
|
+
params.knownIR,
|
|
162
|
+
params.microphoneName,
|
|
163
|
+
params.calibrateSoundCheck,
|
|
164
|
+
params.isSmartPhone,
|
|
165
|
+
params.calibrateSoundBurstDb,
|
|
166
|
+
params.calibrateSoundBurstFilteredExtraDb,
|
|
167
|
+
params.calibrateSoundBurstLevelReTBool,
|
|
168
|
+
params.calibrateSoundBurstUses1000HzGainBool,
|
|
169
|
+
params.calibrateSoundBurstRepeats,
|
|
170
|
+
params.calibrateSoundBurstSec,
|
|
171
|
+
params._calibrateSoundBurstPreSec,
|
|
172
|
+
params._calibrateSoundBurstPostSec,
|
|
173
|
+
params.calibrateSoundHz,
|
|
174
|
+
params.calibrateSoundIRSec,
|
|
175
|
+
params.calibrateSoundIIRSec,
|
|
176
|
+
params.calibrateSoundIIRPhase,
|
|
177
|
+
params.calibrateSound1000HzPreSec,
|
|
178
|
+
params.calibrateSound1000HzSec,
|
|
179
|
+
params.calibrateSound1000HzPostSec,
|
|
180
|
+
params.calibrateSoundBackgroundSecs,
|
|
181
|
+
params.calibrateSoundSmoothOctaves,
|
|
182
|
+
params.calibrateSoundSmoothMinBandwidthHz,
|
|
183
|
+
params.calibrateSoundPowerBinDesiredSec,
|
|
184
|
+
params.calibrateSoundPowerDbSDToleratedDb,
|
|
185
|
+
params.calibrateSoundTaperSec,
|
|
186
|
+
params.micManufacturer,
|
|
187
|
+
params.micSerialNumber,
|
|
188
|
+
params.micModelNumber,
|
|
189
|
+
params.micModelName,
|
|
190
|
+
params.calibrateMicrophonesBool,
|
|
191
|
+
params.authorEmails,
|
|
192
|
+
params.webAudioDeviceNames,
|
|
193
|
+
params.IDsToSaveInSoundProfileLibrary,
|
|
194
|
+
params.restartButton,
|
|
195
|
+
params.reminder,
|
|
196
|
+
params.calibrateSoundLimit,
|
|
197
|
+
params.calibrateSoundBurstNormalizeBy1000HzGainBool,
|
|
198
|
+
params.calibrateSoundBurstScalarDB,
|
|
199
|
+
params.calibrateSound1000HzMaxSD_dB,
|
|
200
|
+
params.calibrateSound1000HzMaxTries,
|
|
201
|
+
params._calibrateSoundBurstMaxSD_dB,
|
|
202
|
+
params.calibrateSoundSamplingDesiredBits,
|
|
203
|
+
params.language,
|
|
204
|
+
params.loudspeakerModelName,
|
|
205
|
+
params.phrases,
|
|
206
|
+
params.soundSubtitleId,
|
|
207
|
+
params.calibrateSoundBurstDownsample,
|
|
208
|
+
params.calibrateSoundSimulateMicrophone,
|
|
209
|
+
params.calibrateSoundSimulateMicrophoneTime,
|
|
210
|
+
params.calibrateSoundSimulateLoudspeaker,
|
|
211
|
+
params.calibrateSoundSimulateLoudspeakerTime,
|
|
212
|
+
params.isLoudspeakerCalibration
|
|
213
|
+
);
|
|
214
|
+
speaker.#removeUIElems();
|
|
215
|
+
return speaker.result;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Async factory method that creates the Speaker object, and returns a promise that resolves to the result of the calibration.
|
|
220
|
+
*
|
|
221
|
+
* @param params - The parameters to be passed to the peer object.
|
|
222
|
+
* @param CalibratorInstance - The class that defines the calibration process.
|
|
223
|
+
* @param connectionManager - Instance of the ConnectionManager
|
|
224
|
+
* @param timeOut - The amount of time to wait before timing out the connection (in milliseconds).
|
|
225
|
+
* @public
|
|
226
|
+
*/
|
|
227
|
+
static startCalibration = async (
|
|
228
|
+
params,
|
|
229
|
+
CalibratorInstance,
|
|
230
|
+
connectionManager,
|
|
231
|
+
timeOut = 180000
|
|
232
|
+
) => {
|
|
233
|
+
// Create a speaker instance and register with the connection manager
|
|
234
|
+
window.speaker = new Speaker(params, CalibratorInstance, connectionManager);
|
|
235
|
+
const {speaker} = window;
|
|
236
|
+
speaker.ac = CalibratorInstance;
|
|
237
|
+
|
|
238
|
+
await speaker.connectionManager.waitForPeerConnection();
|
|
239
|
+
await speaker.connectionManager.resolveWhenHandshakeReceived();
|
|
240
|
+
speaker.connectionManager.sendPageTitle('EasyEyes Microphone');
|
|
241
|
+
speaker.connectionManager.send({
|
|
242
|
+
name: 'CompatibilityPeer',
|
|
243
|
+
message: 'Text',
|
|
244
|
+
text: 'Loading...',
|
|
245
|
+
});
|
|
246
|
+
speaker.connectionManager.send({
|
|
247
|
+
name: 'SoundCalibration',
|
|
248
|
+
message: 'phrases',
|
|
249
|
+
payload: speaker.phrases,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Send connection parameters to the listener
|
|
253
|
+
speaker.connectionManager.send(speaker.prepareConnectionParams());
|
|
254
|
+
|
|
255
|
+
// wrap the calibration process in a promise so we can await it
|
|
256
|
+
return new Promise((resolve, reject) => {
|
|
257
|
+
// Add a permission check handler
|
|
258
|
+
const permissionCheckInterval = setInterval(() => {
|
|
259
|
+
if (speaker.permissionStatus === 'error' || speaker.permissionStatus === 'denied') {
|
|
260
|
+
clearInterval(permissionCheckInterval);
|
|
261
|
+
speaker.#removeUIElems();
|
|
262
|
+
resolve('permission denied');
|
|
263
|
+
}
|
|
264
|
+
}, 100);
|
|
265
|
+
|
|
266
|
+
console.log('Setting up call handler on the peer');
|
|
267
|
+
// Set up call handler on the peer
|
|
268
|
+
speaker.connectionManager.peer.on('call', async call => {
|
|
269
|
+
console.log('Received call from listener');
|
|
270
|
+
clearInterval(permissionCheckInterval); // Clear interval when call is received
|
|
271
|
+
|
|
272
|
+
// Answer the call
|
|
273
|
+
call.answer();
|
|
274
|
+
speaker.#removeUIElems();
|
|
275
|
+
speaker.#showSpinner();
|
|
276
|
+
speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));
|
|
277
|
+
|
|
278
|
+
// when we start receiving audio
|
|
279
|
+
call.on('stream', async stream => {
|
|
280
|
+
window.localStream = stream;
|
|
281
|
+
window.localAudio.srcObject = stream;
|
|
282
|
+
window.localAudio.autoplay = false;
|
|
283
|
+
|
|
284
|
+
// if the sinkSamplingRate is not set sleep
|
|
285
|
+
while (!speaker.ac.sampleRatesSet()) {
|
|
286
|
+
console.log('SinkSamplingRate is undefined, sleeping');
|
|
287
|
+
await sleep(1);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (params.displayUpdate) {
|
|
291
|
+
params.displayUpdate.style.display = '';
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// resolve when we have a result
|
|
295
|
+
speaker.result = await speaker.ac.startCalibration(
|
|
296
|
+
stream,
|
|
297
|
+
params.gainValues,
|
|
298
|
+
params.ICalib,
|
|
299
|
+
params.knownIR,
|
|
300
|
+
params.microphoneName,
|
|
301
|
+
params.calibrateSoundCheck,
|
|
302
|
+
params.isSmartPhone,
|
|
303
|
+
params.calibrateSoundBurstDb,
|
|
304
|
+
params.calibrateSoundBurstFilteredExtraDb,
|
|
305
|
+
params.calibrateSoundBurstLevelReTBool,
|
|
306
|
+
params.calibrateSoundBurstUses1000HzGainBool,
|
|
307
|
+
params.calibrateSoundBurstRepeats,
|
|
308
|
+
params.calibrateSoundBurstSec,
|
|
309
|
+
params._calibrateSoundBurstPreSec,
|
|
310
|
+
params._calibrateSoundBurstPostSec,
|
|
311
|
+
params.calibrateSoundHz,
|
|
312
|
+
params.calibrateSoundIRSec,
|
|
313
|
+
params.calibrateSoundIIRSec,
|
|
314
|
+
params.calibrateSoundIIRPhase,
|
|
315
|
+
params.calibrateSound1000HzPreSec,
|
|
316
|
+
params.calibrateSound1000HzSec,
|
|
317
|
+
params.calibrateSound1000HzPostSec,
|
|
318
|
+
params.calibrateSoundBackgroundSecs,
|
|
319
|
+
params.calibrateSoundSmoothOctaves,
|
|
320
|
+
params.calibrateSoundSmoothMinBandwidthHz,
|
|
321
|
+
params.calibrateSoundPowerBinDesiredSec,
|
|
322
|
+
params.calibrateSoundPowerDbSDToleratedDb,
|
|
323
|
+
params.calibrateSoundTaperSec,
|
|
324
|
+
params.micManufacturer,
|
|
325
|
+
params.micSerialNumber,
|
|
326
|
+
params.micModelNumber,
|
|
327
|
+
params.micModelName,
|
|
328
|
+
params.calibrateMicrophonesBool,
|
|
329
|
+
params.authorEmails,
|
|
330
|
+
params.webAudioDeviceNames,
|
|
331
|
+
params.IDsToSaveInSoundProfileLibrary,
|
|
332
|
+
params.restartButton,
|
|
333
|
+
params.reminder,
|
|
334
|
+
params.calibrateSoundLimit,
|
|
335
|
+
params.calibrateSoundBurstNormalizeBy1000HzGainBool,
|
|
336
|
+
params.calibrateSoundBurstScalarDB,
|
|
337
|
+
params.calibrateSound1000HzMaxSD_dB,
|
|
338
|
+
params.calibrateSound1000HzMaxTries,
|
|
339
|
+
params._calibrateSoundBurstMaxSD_dB,
|
|
340
|
+
params.calibrateSoundSamplingDesiredBits,
|
|
341
|
+
params.language,
|
|
342
|
+
params.loudspeakerModelName,
|
|
343
|
+
params.phrases,
|
|
344
|
+
params.soundSubtitleId,
|
|
345
|
+
params.calibrateSoundBurstDownsample,
|
|
346
|
+
params.calibrateSoundSimulateMicrophone,
|
|
347
|
+
params.calibrateSoundSimulateMicrophoneTime,
|
|
348
|
+
params.calibrateSoundSimulateLoudspeaker,
|
|
349
|
+
params.calibrateSoundSimulateLoudspeakerTime,
|
|
350
|
+
params.isLoudspeakerCalibration
|
|
351
|
+
);
|
|
352
|
+
speaker.#removeUIElems();
|
|
353
|
+
//remove the call
|
|
354
|
+
speaker.connectionManager.peer.off('call');
|
|
355
|
+
resolve(speaker.result);
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// if we do not receive a result within the timeout, reject
|
|
359
|
+
setTimeout(() => {
|
|
360
|
+
clearInterval(permissionCheckInterval);
|
|
361
|
+
reject(
|
|
362
|
+
new CalibrationTimedOutError(
|
|
363
|
+
`Calibration failed to produce a result after ${
|
|
364
|
+
timeOut / 1000
|
|
365
|
+
} seconds. Try increasing "_timeoutSec", which is currently ${
|
|
366
|
+
timeOut / 1000
|
|
367
|
+
} seconds.`
|
|
368
|
+
)
|
|
369
|
+
);
|
|
370
|
+
}, timeOut);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
console.log('Call handler set up', speaker.connectionManager.peer);
|
|
374
|
+
});
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
static testIIR = async (params, CalibratorInstance, IIR, connectionManager, timeOut = 180000) => {
|
|
378
|
+
window.speaker = new Speaker(params, CalibratorInstance, connectionManager);
|
|
379
|
+
const {speaker} = window;
|
|
380
|
+
|
|
381
|
+
// Set QR code parameters
|
|
382
|
+
connectionManager.setQueryParams(speaker.prepareQRParams());
|
|
383
|
+
|
|
384
|
+
// wrap the calibration process in a promise so we can await it
|
|
385
|
+
return new Promise((resolve, reject) => {
|
|
386
|
+
// when a call is received
|
|
387
|
+
connectionManager.peer.on('call', async call => {
|
|
388
|
+
// Answer the call (one way)
|
|
389
|
+
call.answer();
|
|
390
|
+
speaker.#removeUIElems();
|
|
391
|
+
speaker.#showSpinner();
|
|
392
|
+
speaker.ac.createLocalAudio(document.getElementById(speaker.targetElement));
|
|
393
|
+
// when we start receiving audio
|
|
394
|
+
call.on('stream', async stream => {
|
|
395
|
+
window.localStream = stream;
|
|
396
|
+
window.localAudio.srcObject = stream;
|
|
397
|
+
window.localAudio.autoplay = false;
|
|
398
|
+
|
|
399
|
+
// if the sinkSamplingRate is not set sleep
|
|
400
|
+
while (!speaker.ac.sampleRatesSet()) {
|
|
401
|
+
console.log('SinkSamplingRate is undefined, sleeping');
|
|
402
|
+
await sleep(1);
|
|
403
|
+
}
|
|
404
|
+
// resolve when we have a result
|
|
405
|
+
speaker.result = await speaker.ac.playMLSwithIIR(stream, IIR);
|
|
406
|
+
speaker.#removeUIElems();
|
|
407
|
+
resolve(speaker.result);
|
|
408
|
+
});
|
|
409
|
+
// if we do not receive a result within the timeout, reject
|
|
410
|
+
setTimeout(() => {
|
|
411
|
+
reject(
|
|
412
|
+
new CalibrationTimedOutError(
|
|
413
|
+
`Calibration failed to produce a result after ${
|
|
414
|
+
timeOut / 1000
|
|
415
|
+
} seconds. Try increasing "_timeoutSec", which is currently ${
|
|
416
|
+
timeOut / 1000
|
|
417
|
+
} seconds.`
|
|
418
|
+
)
|
|
419
|
+
);
|
|
420
|
+
}, timeOut);
|
|
421
|
+
});
|
|
422
|
+
});
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Called after the peer conncection has been opened.
|
|
427
|
+
* Generates a QR code for the connection and displays it.
|
|
428
|
+
*
|
|
429
|
+
* @private
|
|
430
|
+
* @example
|
|
431
|
+
*/
|
|
432
|
+
|
|
433
|
+
#showQRCode = async () => {
|
|
434
|
+
const queryStringParameters = {
|
|
435
|
+
speakerPeerId: this.peer.id,
|
|
436
|
+
sp: this.isSmartPhone,
|
|
437
|
+
hz: this.calibrateSoundHz,
|
|
438
|
+
bits: this.calibrateSoundSamplingDesiredBits,
|
|
439
|
+
lang: this.language,
|
|
440
|
+
deviceId: this.deviceId,
|
|
441
|
+
};
|
|
442
|
+
const queryString = this.queryStringFromObject(queryStringParameters);
|
|
443
|
+
this.uri = this.siteUrl + queryString;
|
|
444
|
+
|
|
445
|
+
if (this.isSmartPhone) {
|
|
446
|
+
// Generate QR code
|
|
447
|
+
const qrCanvas = document.createElement('canvas');
|
|
448
|
+
qrCanvas.setAttribute('id', 'qrCanvas');
|
|
449
|
+
QRCode.toCanvas(qrCanvas, this.uri, error => {
|
|
450
|
+
if (error) console.error(error);
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Create QR image
|
|
454
|
+
const qrImage = new Image();
|
|
455
|
+
qrImage.setAttribute('id', 'compatibilityCheckQRImage');
|
|
456
|
+
qrImage.style.zIndex = Infinity;
|
|
457
|
+
qrImage.style.height = '150px';
|
|
458
|
+
qrImage.style.width = '150px';
|
|
459
|
+
qrImage.style.margin = '-10px';
|
|
460
|
+
qrImage.style.aspectRatio = 1;
|
|
461
|
+
qrImage.src = qrCanvas.toDataURL();
|
|
462
|
+
this.qrImage = qrImage;
|
|
463
|
+
|
|
464
|
+
// Get shortened URL
|
|
465
|
+
let shortURL = this.uri;
|
|
466
|
+
try {
|
|
467
|
+
const response = await fetch('https://api.short.io/links/public', {
|
|
468
|
+
method: 'POST',
|
|
469
|
+
headers: {
|
|
470
|
+
Accept: 'application/json',
|
|
471
|
+
'Content-Type': 'application/json',
|
|
472
|
+
Authorization: 'pk_fysLKGj3legZz4XZ',
|
|
473
|
+
},
|
|
474
|
+
body: JSON.stringify({
|
|
475
|
+
domain: 'listeners.link',
|
|
476
|
+
originalURL: this.uri,
|
|
477
|
+
}),
|
|
478
|
+
});
|
|
479
|
+
if (!response.ok) {
|
|
480
|
+
throw new Error(`HTTP error! Status: ${response.status}`);
|
|
481
|
+
}
|
|
482
|
+
const data = await response.json();
|
|
483
|
+
shortURL = data.shortURL;
|
|
484
|
+
} catch (error) {
|
|
485
|
+
console.error('Error:', error.message);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Main container with 3 columns
|
|
489
|
+
const container = document.createElement('div');
|
|
490
|
+
container.style.display = 'flex';
|
|
491
|
+
container.style.alignItems = 'flex-start';
|
|
492
|
+
container.style.paddingTop = '0';
|
|
493
|
+
container.id = 'skipQRContainer';
|
|
494
|
+
|
|
495
|
+
// Column 1: QR Code
|
|
496
|
+
const qrColumn = document.createElement('div');
|
|
497
|
+
qrColumn.style.flex = '0 0 auto';
|
|
498
|
+
qrColumn.appendChild(qrImage);
|
|
499
|
+
|
|
500
|
+
// Column 2: Explanation Text
|
|
501
|
+
const textColumn = document.createElement('div');
|
|
502
|
+
textColumn.style.flex = '1';
|
|
503
|
+
textColumn.style.padding = '0 20px';
|
|
504
|
+
textColumn.style.maxWidth = '560px';
|
|
505
|
+
|
|
506
|
+
const explanation = document.createElement('h2');
|
|
507
|
+
explanation.style.fontSize = '1.1rem';
|
|
508
|
+
explanation.id = 'skipQRExplanation';
|
|
509
|
+
explanation.style.margin = '0';
|
|
510
|
+
explanation.style.textAlign = 'left';
|
|
511
|
+
explanation.style.lineHeight = '1.5';
|
|
512
|
+
explanation.innerHTML = formatLineBreak(
|
|
513
|
+
this.phrases.RC_skipQR_ExplanationWithoutPreferNot[this.language]
|
|
514
|
+
.replace('xxx', `<b style="user-select: text">${shortURL}</b>`)
|
|
515
|
+
.replace('XXX', `<b style="user-select: text">${shortURL}</b>`),
|
|
516
|
+
this.phrases.RC_checkInternetConnection[this.language]
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
const language = this.language;
|
|
520
|
+
const phrases = this.phrases;
|
|
521
|
+
const checkConnection = document.createElement('a');
|
|
522
|
+
checkConnection.id = 'check-connection';
|
|
523
|
+
checkConnection.href = '#';
|
|
524
|
+
checkConnection.innerHTML = "check the phone's internet connection";
|
|
525
|
+
checkConnection.addEventListener('click', function (event) {
|
|
526
|
+
event.preventDefault();
|
|
527
|
+
createAndShowPopup(language, phrases);
|
|
528
|
+
});
|
|
529
|
+
explanation.querySelector('a#check-connection').replaceWith(checkConnection);
|
|
530
|
+
textColumn.appendChild(explanation);
|
|
531
|
+
|
|
532
|
+
// Column 3: Buttons
|
|
533
|
+
const buttonColumn = document.createElement('div');
|
|
534
|
+
buttonColumn.style.display = 'flex';
|
|
535
|
+
buttonColumn.style.flexDirection = 'column';
|
|
536
|
+
buttonColumn.style.gap = '10px';
|
|
537
|
+
buttonColumn.style.flex = '0 0 auto';
|
|
538
|
+
buttonColumn.style.alignItems = 'flex-end';
|
|
539
|
+
buttonColumn.appendChild(this.buttonsContainer);
|
|
540
|
+
|
|
541
|
+
// Assemble the columns
|
|
542
|
+
container.appendChild(qrColumn);
|
|
543
|
+
container.appendChild(textColumn);
|
|
544
|
+
container.appendChild(buttonColumn);
|
|
545
|
+
|
|
546
|
+
document.getElementById(this.targetElement).appendChild(container);
|
|
547
|
+
} else {
|
|
548
|
+
// show the link to the user
|
|
549
|
+
// If specified HTML Id is available, show QR code there
|
|
550
|
+
if (document.getElementById(this.targetElement)) {
|
|
551
|
+
// const linkTag = document.createElement('a');
|
|
552
|
+
// linkTag.setAttribute('href', uri);
|
|
553
|
+
// linkTag.innerHTML = 'Click here to start the calibration';
|
|
554
|
+
// linkTag.target = '_blank';
|
|
555
|
+
// document.getElementById(this.targetElement).appendChild(linkTag);
|
|
556
|
+
// document.getElementById(this.targetElement).appendChild(qrCanvas);
|
|
557
|
+
|
|
558
|
+
const proceedButton = document.createElement('button');
|
|
559
|
+
proceedButton.setAttribute('id', 'calibrationProceedButton');
|
|
560
|
+
proceedButton.setAttribute('class', 'btn btn-success');
|
|
561
|
+
proceedButton.innerHTML = this.phrases.T_proceed[this.language];
|
|
562
|
+
proceedButton.onclick = () => {
|
|
563
|
+
// open the link in a new tab
|
|
564
|
+
window.open(this.uri, '_blank');
|
|
565
|
+
// remove the button
|
|
566
|
+
document.getElementById('calibrationProceedButton').remove();
|
|
567
|
+
};
|
|
568
|
+
document.getElementById(this.targetElement).appendChild(proceedButton);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
// or just print it to console
|
|
572
|
+
console.log('TEST: Peer reachable at: ', this.uri);
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
#showSpinner = () => {
|
|
576
|
+
const spinner = document.createElement('div');
|
|
577
|
+
spinner.className = 'spinner-border ml-auto';
|
|
578
|
+
spinner.role = 'status';
|
|
579
|
+
spinner.ariaHidden = 'true';
|
|
580
|
+
spinner.style.marginTop = '0.8rem';
|
|
581
|
+
document.getElementById(this.targetElement).appendChild(spinner);
|
|
582
|
+
|
|
583
|
+
// clear instructionDisplay
|
|
584
|
+
const soundMessage = document.getElementById(this.soundMessageId);
|
|
585
|
+
soundMessage.innerHTML = '';
|
|
586
|
+
soundMessage.style.display = 'none';
|
|
587
|
+
const instructionDisplay = document.getElementById(this.instructionDisplayId);
|
|
588
|
+
const background = document.getElementById('background'); // todo: get background id from params
|
|
589
|
+
const subtitle = document.getElementById(this.soundSubtitleId);
|
|
590
|
+
if (subtitle) {
|
|
591
|
+
subtitle.innerHTML = '';
|
|
592
|
+
}
|
|
593
|
+
if (instructionDisplay) {
|
|
594
|
+
instructionDisplay.innerHTML = '';
|
|
595
|
+
instructionDisplay.style.whiteSpace = 'nowrap';
|
|
596
|
+
instructionDisplay.style.fontWeight = 'bold';
|
|
597
|
+
instructionDisplay.style.width = 'fit-content';
|
|
598
|
+
instructionDisplay.innerHTML = this.phrases.RC_soundRecording[this.language];
|
|
599
|
+
let fontSize = 100;
|
|
600
|
+
instructionDisplay.style.fontSize = fontSize + 'px';
|
|
601
|
+
while (instructionDisplay.scrollWidth > background.scrollWidth * 0.9 && fontSize > 10) {
|
|
602
|
+
fontSize--;
|
|
603
|
+
instructionDisplay.style.fontSize = fontSize + 'px';
|
|
604
|
+
}
|
|
605
|
+
// const p = document.createElement('p');
|
|
606
|
+
// // font size
|
|
607
|
+
// p.style.fontSize = '1.1rem';
|
|
608
|
+
// p.style.fontWeight = 'normal';
|
|
609
|
+
// p.style.paddingTop = '20px';
|
|
610
|
+
// const timeToCalibrateText = phrases.RC_howLongToCalibrate['en-US'];
|
|
611
|
+
// p.innerHTML = timeToCalibrateText.replace('111', this.timeToCalibrate);
|
|
612
|
+
// instructionDisplay.appendChild(p);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const timeToCalibrateDisplay = document.getElementById(this.timeToCalibrateDisplay);
|
|
616
|
+
if (timeToCalibrateDisplay) {
|
|
617
|
+
const timeToCalibrateText = this.phrases.RC_howLongToCalibrate[this.language];
|
|
618
|
+
timeToCalibrateDisplay.innerHTML = timeToCalibrateText.replace('111', this.timeToCalibrate);
|
|
619
|
+
timeToCalibrateDisplay.style.fontWeight = 'normal';
|
|
620
|
+
timeToCalibrateDisplay.style.fontSize = '1rem';
|
|
621
|
+
// timeToCalibrateDisplay.style.paddingTop = '20px';
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Update title - titleDisplayId
|
|
625
|
+
const titleDisplay = document.getElementById(this.titleDisplayId);
|
|
626
|
+
if (titleDisplay) {
|
|
627
|
+
// if (this.isParticipant) {
|
|
628
|
+
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('3', '4');
|
|
629
|
+
// } else if (this.isSmartPhone) {
|
|
630
|
+
// if (this.isLoudspeakerCalibration) {
|
|
631
|
+
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('6', '7');
|
|
632
|
+
// } else {
|
|
633
|
+
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('5', '6');
|
|
634
|
+
// }
|
|
635
|
+
// } else {
|
|
636
|
+
// titleDisplay.innerHTML = titleDisplay.innerHTML.replace('5', '6');
|
|
637
|
+
// }
|
|
638
|
+
if (this.isLoudspeakerCalibration) {
|
|
639
|
+
if (this.isParticipant) {
|
|
640
|
+
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('3', '4');
|
|
641
|
+
} else if (this.isSmartPhone) {
|
|
642
|
+
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('6', '7');
|
|
643
|
+
} else {
|
|
644
|
+
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('4', '5');
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
if (this.isSmartPhone) {
|
|
648
|
+
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('5', '6');
|
|
649
|
+
} else {
|
|
650
|
+
titleDisplay.innerHTML = titleDisplay.innerHTML.replace('3', '4');
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
|
|
656
|
+
#removeUIElems = () => {
|
|
657
|
+
const parent = document.getElementById(this.targetElement);
|
|
658
|
+
while (parent.firstChild) {
|
|
659
|
+
parent.firstChild.remove();
|
|
660
|
+
}
|
|
661
|
+
};
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Called when the peer connection is opened.
|
|
665
|
+
* Saves the peer id and calls the QR code generator.
|
|
666
|
+
*
|
|
667
|
+
* @param peerId - The peer id of the peer connection.
|
|
668
|
+
* @param id
|
|
669
|
+
* @private
|
|
670
|
+
* @example
|
|
671
|
+
*/
|
|
672
|
+
#onPeerOpen = id => {
|
|
673
|
+
// Workaround for peer.reconnect deleting previous id
|
|
674
|
+
try {
|
|
675
|
+
if (id === null) {
|
|
676
|
+
console.error('Received null id from peer open');
|
|
677
|
+
this.peer.id = this.lastPeerId;
|
|
678
|
+
} else {
|
|
679
|
+
this.lastPeerId = this.peer.id;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
if (id !== this.peer.id) {
|
|
683
|
+
console.warn('DEBUG Check you assumption that id === this.peer.id');
|
|
684
|
+
}
|
|
685
|
+
} catch (error) {
|
|
686
|
+
console.error('Error in #onPeerOpen: ', error);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
this.#showQRCode();
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Called when the peer connection is established.
|
|
694
|
+
* Enforces a single connection.
|
|
695
|
+
*
|
|
696
|
+
* @param connection - The connection object.
|
|
697
|
+
* @private
|
|
698
|
+
* @example
|
|
699
|
+
*/
|
|
700
|
+
#onPeerConnection = connection => {
|
|
701
|
+
// Allow only a single connection
|
|
702
|
+
if (this.conn && this.conn.open) {
|
|
703
|
+
connection.on('open', () => {
|
|
704
|
+
connection.send('Already connected to another client');
|
|
705
|
+
setTimeout(() => {
|
|
706
|
+
connection.close();
|
|
707
|
+
}, 500);
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
this.conn = connection;
|
|
713
|
+
console.log('Connected to: ', this.conn.peer);
|
|
714
|
+
this.#ready();
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Called when the peer connection is closed.
|
|
719
|
+
*
|
|
720
|
+
* @private
|
|
721
|
+
* @example
|
|
722
|
+
*/
|
|
723
|
+
onPeerClose = () => {
|
|
724
|
+
this.conn = null;
|
|
725
|
+
console.log('Connection destroyed');
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
static closeConnection = () => {
|
|
729
|
+
this.conn = null;
|
|
730
|
+
console.log('Connection destroyed');
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Called when the peer connection is disconnected.
|
|
735
|
+
* Attempts to reconnect.
|
|
736
|
+
*
|
|
737
|
+
* @private
|
|
738
|
+
* @example
|
|
739
|
+
*/
|
|
740
|
+
#onPeerDisconnected = () => {
|
|
741
|
+
console.log('Connection lost. Please reconnect');
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
// Workaround for peer.reconnect deleting previous id
|
|
745
|
+
this.peer.id = this.lastPeerId;
|
|
746
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
747
|
+
this.peer._lastServerId = this.lastPeerId;
|
|
748
|
+
this.peer.reconnect();
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error('Error in #onPeerDisconnected: ', error);
|
|
751
|
+
}
|
|
752
|
+
};
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Called when the peer connection encounters an error.
|
|
756
|
+
*
|
|
757
|
+
* @param error
|
|
758
|
+
* @private
|
|
759
|
+
* @example
|
|
760
|
+
*/
|
|
761
|
+
#onPeerError = error => {
|
|
762
|
+
// TODO: check if this function is needed or not
|
|
763
|
+
console.error(error);
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Called when data is received from the peer connection.
|
|
768
|
+
*
|
|
769
|
+
* @param data
|
|
770
|
+
* @private
|
|
771
|
+
* @example
|
|
772
|
+
*/
|
|
773
|
+
#onIncomingData = data => {
|
|
774
|
+
// enforce object type
|
|
775
|
+
if (
|
|
776
|
+
!Object.prototype.hasOwnProperty.call(data, 'name') ||
|
|
777
|
+
!Object.prototype.hasOwnProperty.call(data, 'payload')
|
|
778
|
+
) {
|
|
779
|
+
console.error('Received malformed data: ', data);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const name = data.payload.name;
|
|
784
|
+
const payload = data.payload.payload;
|
|
785
|
+
|
|
786
|
+
switch (name) {
|
|
787
|
+
case 'samplingRate':
|
|
788
|
+
console.log('Received sampling rate from listener: ', payload);
|
|
789
|
+
if (!payload) {
|
|
790
|
+
window.speaker.ac.setSamplingRates(window.speaker.calibrateSoundHz);
|
|
791
|
+
} else {
|
|
792
|
+
window.speaker.ac.setSamplingRates(payload);
|
|
793
|
+
}
|
|
794
|
+
break;
|
|
795
|
+
case 'sampleSize':
|
|
796
|
+
window.speaker.ac.setSampleSize(payload);
|
|
797
|
+
break;
|
|
798
|
+
case 'deviceType':
|
|
799
|
+
window.speaker.ac.setDeviceType(payload);
|
|
800
|
+
break;
|
|
801
|
+
case 'deviceName':
|
|
802
|
+
window.speaker.ac.setDeviceName(payload);
|
|
803
|
+
break;
|
|
804
|
+
case 'flags':
|
|
805
|
+
//this.ac.setDeviceName(data.payload);
|
|
806
|
+
console.log('FLAGS');
|
|
807
|
+
console.log(payload);
|
|
808
|
+
window.speaker.ac.setFlags(payload);
|
|
809
|
+
break;
|
|
810
|
+
case 'deviceInfo':
|
|
811
|
+
window.speaker.ac.setDeviceInfo(payload);
|
|
812
|
+
console.log('Received device info from listener: ', payload);
|
|
813
|
+
break;
|
|
814
|
+
case 'permissionStatus':
|
|
815
|
+
console.log('Received permission status from listener: ', payload);
|
|
816
|
+
if (payload.type === 'error') {
|
|
817
|
+
this.permissionStatus = 'error';
|
|
818
|
+
window.speaker.ac.setPermissionStatus('error');
|
|
819
|
+
} else if (payload.type === 'denied') {
|
|
820
|
+
this.permissionStatus = 'denied';
|
|
821
|
+
window.speaker.ac.setPermissionStatus('denied');
|
|
822
|
+
} else if (payload.type === 'granted') {
|
|
823
|
+
this.permissionStatus = 'granted';
|
|
824
|
+
window.speaker.ac.setPermissionStatus('granted');
|
|
825
|
+
console.log('Permission granted');
|
|
826
|
+
}
|
|
827
|
+
break;
|
|
828
|
+
case UnsupportedDeviceError.name:
|
|
829
|
+
case MissingSpeakerIdError.name:
|
|
830
|
+
throw payload;
|
|
831
|
+
break;
|
|
832
|
+
default:
|
|
833
|
+
break;
|
|
834
|
+
}
|
|
835
|
+
};
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Called when the peer connection is #ready.
|
|
839
|
+
*
|
|
840
|
+
* @private
|
|
841
|
+
* @example
|
|
842
|
+
*/
|
|
843
|
+
#ready = () => {
|
|
844
|
+
// Perform callback with data
|
|
845
|
+
this.conn.on('data', this.#onIncomingData);
|
|
846
|
+
this.conn.on('close', () => {
|
|
847
|
+
console.log('Connection reset<br>Awaiting connection...');
|
|
848
|
+
this.conn = null;
|
|
849
|
+
});
|
|
850
|
+
};
|
|
851
|
+
|
|
852
|
+
/** .
|
|
853
|
+
* .
|
|
854
|
+
* .
|
|
855
|
+
* Debug method for downloading the recorded audio
|
|
856
|
+
*
|
|
857
|
+
* @public
|
|
858
|
+
* @example
|
|
859
|
+
*/
|
|
860
|
+
downloadData = () => {
|
|
861
|
+
this.ac.downloadData();
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
static repeatCalibration = async (params, stream, CalibratorInstance) => {
|
|
865
|
+
window.speaker.ac = CalibratorInstance;
|
|
866
|
+
window.speaker.#removeUIElems();
|
|
867
|
+
window.speaker.#showSpinner();
|
|
868
|
+
|
|
869
|
+
console.log('This is a repeat');
|
|
870
|
+
// wrap the calibration process in a promise so we can await it
|
|
871
|
+
return new Promise((resolve, reject) => {
|
|
872
|
+
// Add a permission check handler
|
|
873
|
+
const permissionCheckInterval = setInterval(() => {
|
|
874
|
+
if (
|
|
875
|
+
window.speaker.permissionStatus === 'error' ||
|
|
876
|
+
window.speaker.permissionStatus === 'denied'
|
|
877
|
+
) {
|
|
878
|
+
clearInterval(permissionCheckInterval);
|
|
879
|
+
window.speaker.#removeUIElems();
|
|
880
|
+
resolve('permission denied');
|
|
881
|
+
}
|
|
882
|
+
}, 100);
|
|
883
|
+
|
|
884
|
+
// Start calibration process
|
|
885
|
+
(async () => {
|
|
886
|
+
try {
|
|
887
|
+
const result = await window.speaker.ac.startCalibration(
|
|
888
|
+
stream,
|
|
889
|
+
params.gainValues,
|
|
890
|
+
params.ICalib,
|
|
891
|
+
params.knownIR,
|
|
892
|
+
params.microphoneName,
|
|
893
|
+
params.calibrateSoundCheck,
|
|
894
|
+
params.isSmartPhone,
|
|
895
|
+
params.calibrateSoundBurstDb,
|
|
896
|
+
params.calibrateSoundBurstFilteredExtraDb,
|
|
897
|
+
params.calibrateSoundBurstLevelReTBool,
|
|
898
|
+
params.calibrateSoundBurstUses1000HzGainBool,
|
|
899
|
+
params.calibrateSoundBurstRepeats,
|
|
900
|
+
params.calibrateSoundBurstSec,
|
|
901
|
+
params._calibrateSoundBurstPreSec,
|
|
902
|
+
params._calibrateSoundBurstPostSec,
|
|
903
|
+
params.calibrateSoundHz,
|
|
904
|
+
params.calibrateSoundIRSec,
|
|
905
|
+
params.calibrateSoundIIRSec,
|
|
906
|
+
params.calibrateSoundIIRPhase,
|
|
907
|
+
params.calibrateSound1000HzPreSec,
|
|
908
|
+
params.calibrateSound1000HzSec,
|
|
909
|
+
params.calibrateSound1000HzPostSec,
|
|
910
|
+
params.calibrateSoundBackgroundSecs,
|
|
911
|
+
params.calibrateSoundSmoothOctaves,
|
|
912
|
+
params.calibrateSoundSmoothMinBandwidthHz,
|
|
913
|
+
params.calibrateSoundPowerBinDesiredSec,
|
|
914
|
+
params.calibrateSoundPowerDbSDToleratedDb,
|
|
915
|
+
params.calibrateSoundTaperSec,
|
|
916
|
+
params.micManufacturer,
|
|
917
|
+
params.micSerialNumber,
|
|
918
|
+
params.micModelNumber,
|
|
919
|
+
params.micModelName,
|
|
920
|
+
params.calibrateMicrophonesBool,
|
|
921
|
+
params.authorEmails,
|
|
922
|
+
params.webAudioDeviceNames,
|
|
923
|
+
params.IDsToSaveInSoundProfileLibrary,
|
|
924
|
+
params.restartButton,
|
|
925
|
+
params.reminder,
|
|
926
|
+
params.calibrateSoundLimit,
|
|
927
|
+
params.calibrateSoundBurstNormalizeBy1000HzGainBool,
|
|
928
|
+
params.calibrateSoundBurstScalarDB,
|
|
929
|
+
params.calibrateSound1000HzMaxSD_dB,
|
|
930
|
+
params.calibrateSound1000HzMaxTries,
|
|
931
|
+
params._calibrateSoundBurstMaxSD_dB,
|
|
932
|
+
params.calibrateSoundSamplingDesiredBits,
|
|
933
|
+
params.language,
|
|
934
|
+
params.loudspeakerModelName,
|
|
935
|
+
params.phrases,
|
|
936
|
+
params.soundSubtitleId,
|
|
937
|
+
params.calibrateSoundBurstDownsample,
|
|
938
|
+
params.calibrateSoundSimulateMicrophone,
|
|
939
|
+
params.calibrateSoundSimulateMicrophoneTime,
|
|
940
|
+
params.calibrateSoundSimulateLoudspeaker,
|
|
941
|
+
params.calibrateSoundSimulateLoudspeakerTime,
|
|
942
|
+
params.isLoudspeakerCalibration
|
|
943
|
+
);
|
|
944
|
+
clearInterval(permissionCheckInterval);
|
|
945
|
+
window.speaker.#removeUIElems();
|
|
946
|
+
resolve(result);
|
|
947
|
+
} catch (error) {
|
|
948
|
+
clearInterval(permissionCheckInterval);
|
|
949
|
+
reject(error);
|
|
950
|
+
}
|
|
951
|
+
})();
|
|
952
|
+
});
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
/*
|
|
957
|
+
Referenced links:
|
|
958
|
+
https://stackoverflow.com/questions/28016664/when-you-pass-this-as-an-argument/28016676#28016676
|
|
959
|
+
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
|
|
960
|
+
https://stackoverflow.com/questions/879152/how-do-i-make-javascript-beep [3]
|
|
961
|
+
*/
|
|
962
|
+
|
|
963
|
+
export default Speaker;
|