speaker-calibration 2.2.218 → 2.2.220

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speaker-calibration",
3
- "version": "2.2.218",
3
+ "version": "2.2.220",
4
4
  "description": "Speaker calibration library for auditory testing",
5
5
  "main": "dist/main.js",
6
6
  "directories": {
@@ -1,6 +1,6 @@
1
1
  // get element with id message
2
2
  import {phrases} from '../../dist/example/i18n.js';
3
- import {Listener} from '../main.js';
3
+ import Listener from '../peer-connection/listener.js';
4
4
  // get url query parameters
5
5
  const urlParams = new URLSearchParams(window.location.search);
6
6
 
@@ -16,117 +16,308 @@ const listenerParameters = {
16
16
  const container = document.getElementById('listenerContainer');
17
17
  const recordingInProgress = phrases.RC_soundRecording['en-US'];
18
18
  const backToExperimentWindow = phrases.RC_backToExperimentWindow['en-US'];
19
- const allowMicrophone = phrases.RC_allowMicrophoneUse['en-US'].replace(/\n/g, '<br>');
20
- const placeSmartphoneMicrophone = phrases.RC_placeSmartphoneMicrophone['en-US'].replace(
21
- /\n/g,
22
- '<br>'
23
- );
24
- const turnMeToReadBelow = phrases.RC_turnMeToReadBelow['en-US'].replace(/\n/g, '<br>');
19
+ const allowMicrophone = phrases.RC_allowMicrophoneUse['en-US'];
20
+ const placeSmartphoneMicrophone = phrases.RC_placeSmartphoneMicrophone['en-US'];
21
+ const turnMeToReadBelow = phrases.RC_turnMeToReadBelow['en-US'];
25
22
  const recordingInProgressElement = document.getElementById('recordingInProgress');
26
23
  const allowMicrophoneElement = document.getElementById('allowMicrophone');
27
24
  const turnMessageElement = document.getElementById('turnMeToReadBelow');
28
25
 
29
26
  switch (isSmartPhone) {
30
27
  case 'true':
31
- allowMicrophoneElement.innerHTML = placeSmartphoneMicrophone;
32
- allowMicrophoneElement.style.lineHeight = '1.2rem';
33
- allowMicrophoneElement.style.fontSize = '14px';
34
- turnMessageElement.innerHTML = turnMeToReadBelow;
35
- turnMessageElement.style.lineHeight = '1.2rem';
36
- turnMessageElement.style.fontSize = '14px';
37
- // show the html upsidedown
38
- const phrasesContainer = document.getElementById('phrases');
39
- // add class
40
- phrasesContainer.classList.add('phrases');
41
- const html = document.querySelector('html');
42
- html.style.overflow = 'hidden';
43
- const display = document.getElementById('updateDisplay');
44
- display.classList.add('updateDisplay');
45
- container.style.display = 'block';
46
- // event listener for id calibrationBeginButton
47
- const calibrationBeginButton = document.getElementById('calibrationBeginButton');
48
- console.log('Waiting for proceed button click');
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
+ };
49
36
 
50
- calibrationBeginButton.addEventListener('click', async () => {
51
- console.log('Proceed button clicked');
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';
52
42
 
53
- // remove the button
54
- calibrationBeginButton.remove();
55
- // remove turn message
56
- turnMessageElement.remove();
57
- // set the text of the html elements
58
- recordingInProgressElement.innerHTML = recordingInProgress;
59
- allowMicrophoneElement.innerHTML = allowMicrophone;
60
-
61
- recordingInProgressElement.style.whiteSpace = 'nowrap';
62
- recordingInProgressElement.style.fontWeight = 'bold';
63
- // fit content
64
- recordingInProgressElement.style.width = 'fit-content';
65
- let fontSize = 100;
66
- recordingInProgressElement.style.fontSize = fontSize + 'px';
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
+ }
67
59
 
68
- console.log('Adjusting font size for recording in progress text');
69
- while (recordingInProgressElement.scrollWidth > window.innerWidth * 0.9 && fontSize > 10) {
70
- fontSize--;
71
- recordingInProgressElement.style.fontSize = fontSize + 'px';
72
- }
73
- console.log('Done adjusting font size for recording in progress text');
74
- const webAudioDeviceNames = {microphone: '', deviceID: ''};
75
- const externalMicList = ['UMIK', 'Airpods', 'Bluetooth'];
76
- try {
77
- console.log('Getting user media...Should ask for microphone permission');
78
- const stream = await navigator.mediaDevices.getUserMedia({audio: true});
79
- console.log('Got user media');
80
- if (stream) {
81
- console.log('Getting devices');
82
- const devices = await navigator.mediaDevices.enumerateDevices();
83
- console.log(devices);
84
- const mics = devices.filter(device => device.kind === 'audioinput');
85
- mics.forEach(mic => {
86
- if (externalMicList.some(externalMic => mic.label.includes(externalMic))) {
87
- webAudioDeviceNames.microphone = mic.label;
88
- webAudioDeviceNames.deviceID = mic.deviceId;
89
- }
90
- });
91
- if (webAudioDeviceNames.microphone === '') {
92
- webAudioDeviceNames.microphone = mics[0].label;
93
- webAudioDeviceNames.deviceID = mics[0].deviceId;
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});
94
71
  }
95
72
  }
96
- } catch (err) {
97
- console.log(err);
98
73
  }
99
- listenerParameters.microphoneFromAPI = webAudioDeviceNames.microphone;
100
- listenerParameters.microphoneDeviceId = webAudioDeviceNames.microphone;
101
- let lock = null;
74
+
102
75
  try {
103
- if ('wakeLock' in navigator) {
104
- lock = await navigator.wakeLock.request('screen');
105
- }
76
+ await requestMicAccess();
106
77
  } catch (err) {
107
- console.log(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});
108
82
  }
109
- console.log(lock);
110
- console.log('Starting Calibration');
111
- console.log('Device id in example listenr:', listenerParameters.microphoneDeviceId);
112
- window.listener = new Listener(listenerParameters);
113
- console.log(window.listener);
114
- if (lock) {
115
- lock.release();
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
+ if (lock) {
271
+ lock.release();
272
+ }
273
+ });
274
+ }
275
+
276
+ // Wrap the initialization in an IIFE
277
+ (async function initializeSmartPhoneMode() {
278
+ await initializeListener();
279
+
280
+ const timeout = 30000; // 30 seconds timeout
281
+ const startTime = Date.now();
282
+
283
+ // Wait for peer connection setup with timeout
284
+ while (Date.now() - startTime < timeout) {
285
+ if (
286
+ window.listener.peer.id !== null &&
287
+ window.listener.conn !== null &&
288
+ window.listener.connOpen
289
+ ) {
290
+ console.log('Connection established successfully');
291
+ await checkAndRequestMicrophonePermission();
292
+ return;
293
+ }
294
+ console.log('Waiting for connection setup...');
295
+ await new Promise(resolve => setTimeout(resolve, 100));
116
296
  }
117
- });
297
+
298
+ // If we get here, we've timed out
299
+ console.error('Connection setup timed out after 30 seconds');
300
+ allowMicrophoneElement.innerText = phrases.RC_microphonePermissionDenied['en-US'];
301
+ await window.listener.sendPermissionStatus({
302
+ type: 'error',
303
+ error: 'Connection setup timed out after 30 seconds',
304
+ });
305
+ })().catch(console.error);
118
306
  break;
119
307
  case 'false':
308
+ // Initialize listener immediately
309
+ listenerParameters.microphoneDeviceId = urlParams.get('deviceId');
310
+ window.listener = new Listener(listenerParameters);
311
+
120
312
  // remove the button
121
313
  const calibrationBeginButton2 = document.getElementById('calibrationBeginButton');
122
314
  calibrationBeginButton2.remove();
123
315
  container.style.display = 'block';
124
- // event listener for when the page is loaded
125
316
 
126
- window.addEventListener('load', () => {
317
+ window.addEventListener('load', async () => {
127
318
  // set the text of the html elements
128
- recordingInProgressElement.innerHTML = recordingInProgress;
129
- allowMicrophoneElement.innerHTML = allowMicrophone;
319
+ recordingInProgressElement.innerText = recordingInProgress;
320
+ allowMicrophoneElement.innerText = allowMicrophone;
130
321
 
131
322
  recordingInProgressElement.style.whiteSpace = 'nowrap';
132
323
  recordingInProgressElement.style.fontWeight = 'bold';
@@ -143,10 +334,10 @@ switch (isSmartPhone) {
143
334
  const message = document.getElementById('message');
144
335
  message.style.lineHeight = '2.5rem';
145
336
  const p = document.createElement('p');
146
- p.innerHTML = backToExperimentWindow;
337
+ p.innerText = backToExperimentWindow;
147
338
  message.appendChild(p);
148
- listenerParameters.microphoneDeviceId = urlParams.get('deviceId');
149
- window.listener = new Listener(listenerParameters);
339
+
340
+ await window.listener.startCalibration();
150
341
  console.log(window.listener);
151
342
  });
152
343
  break;
package/src/main.js CHANGED
@@ -1,4 +1,4 @@
1
- import Listener from './peer-connection/listener';
1
+ // import Listener from './peer-connection/listener';
2
2
  import Speaker from './peer-connection/speaker';
3
3
 
4
4
  import VolumeCalibration from './tasks/volume/volume';
@@ -12,7 +12,6 @@ import {
12
12
  } from './peer-connection/peerErrors';
13
13
 
14
14
  export {
15
- Listener,
16
15
  Speaker,
17
16
  VolumeCalibration,
18
17
  ImpulseResponseCalibration,
@@ -30,40 +30,6 @@ class AudioPeer {
30
30
  * @example
31
31
  */
32
32
  constructor(param = initParameters) {
33
- // PeerJS
34
- /* Create the Peer object for our end of the connection. */
35
- this.peer = new Peer({
36
- secure: true,
37
- host: 'easyeyes-peer-server.herokuapp.com',
38
- port: 443,
39
- config: {
40
- iceServers: [
41
- {
42
- urls: 'stun:stun.relay.metered.ca:80',
43
- },
44
- {
45
- urls: 'turn:global.relay.metered.ca:80',
46
- username: 'de884cfc34189cdf1a5dd616',
47
- credential: 'IcOpouU9/TYBmpHU',
48
- },
49
- {
50
- urls: 'turn:global.relay.metered.ca:80?transport=tcp',
51
- username: 'de884cfc34189cdf1a5dd616',
52
- credential: 'IcOpouU9/TYBmpHU',
53
- },
54
- {
55
- urls: 'turn:global.relay.metered.ca:443',
56
- username: 'de884cfc34189cdf1a5dd616',
57
- credential: 'IcOpouU9/TYBmpHU',
58
- },
59
- {
60
- urls: 'turns:global.relay.metered.ca:443?transport=tcp',
61
- username: 'de884cfc34189cdf1a5dd616',
62
- credential: 'IcOpouU9/TYBmpHU',
63
- },
64
- ],
65
- },
66
- });
67
33
  this.conn = null;
68
34
  this.lastPeerId = null;
69
35
 
@@ -116,13 +82,12 @@ class AudioPeer {
116
82
  // Workaround for peer.reconnect deleting previous id
117
83
  try {
118
84
  this.peer.id = this.lastPeerId;
119
- // eslint-disable-next-line no-underscore-dangle
120
- this.peer._lastServerId = this.lastPeerId;
121
- this.peer.reconnect();
122
- } catch(e) {
85
+ // eslint-disable-next-line no-underscore-dangle
86
+ this.peer._lastServerId = this.lastPeerId;
87
+ this.peer.reconnect();
88
+ } catch (e) {
123
89
  console.log(e);
124
90
  }
125
-
126
91
  };
127
92
 
128
93
  /** .