@lookit/record 4.1.0 → 6.0.0
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/README.md +158 -15
- package/dist/index.browser.js +212 -43
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +16 -16
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +211 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +113 -9
- package/dist/index.js +211 -42
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/consentVideo.spec.ts +9 -0
- package/src/consentVideo.ts +24 -2
- package/src/errors.ts +28 -0
- package/src/index.spec.ts +497 -54
- package/src/recorder.spec.ts +669 -109
- package/src/recorder.ts +184 -36
- package/src/start.ts +45 -5
- package/src/stop.ts +29 -12
- package/src/trial.ts +42 -16
- package/src/types.ts +21 -0
- package/src/utils.spec.ts +129 -0
- package/src/utils.ts +45 -0
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@ declare const info$3: {
|
|
|
5
5
|
readonly version: string;
|
|
6
6
|
readonly parameters: {
|
|
7
7
|
readonly template: {
|
|
8
|
-
readonly type: ParameterType.
|
|
8
|
+
readonly type: ParameterType.SELECT;
|
|
9
|
+
readonly options: readonly ["consent-template-5", "consent-garden", "consent-recording-only"];
|
|
9
10
|
readonly default: "consent-template-5";
|
|
10
11
|
};
|
|
11
12
|
readonly locale: {
|
|
@@ -131,6 +132,19 @@ declare const info$3: {
|
|
|
131
132
|
readonly type: ParameterType.BOOL;
|
|
132
133
|
readonly default: false;
|
|
133
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* This parameter is only relevant for the consent-recording-only template.
|
|
137
|
+
* If a different template is used, this parameter will be ignored. Whether
|
|
138
|
+
* or not the consent trial is the only data being collected on CHS (i.e.
|
|
139
|
+
* the study redirects to an external URL immediately after the consent
|
|
140
|
+
* trial). If false (the default), the consent template contains information
|
|
141
|
+
* about how CHS handles data/responses. If true, any statements about
|
|
142
|
+
* session data/responses are omitted.
|
|
143
|
+
*/
|
|
144
|
+
readonly only_consent_on_chs: {
|
|
145
|
+
readonly type: ParameterType.BOOL;
|
|
146
|
+
readonly default: false;
|
|
147
|
+
};
|
|
134
148
|
};
|
|
135
149
|
readonly data: {
|
|
136
150
|
readonly chs_type: {
|
|
@@ -147,7 +161,8 @@ declare class VideoConsentPlugin implements JsPsychPlugin<Info$3> {
|
|
|
147
161
|
readonly version: string;
|
|
148
162
|
readonly parameters: {
|
|
149
163
|
readonly template: {
|
|
150
|
-
readonly type: ParameterType.
|
|
164
|
+
readonly type: ParameterType.SELECT;
|
|
165
|
+
readonly options: readonly ["consent-template-5", "consent-garden", "consent-recording-only"];
|
|
151
166
|
readonly default: "consent-template-5";
|
|
152
167
|
};
|
|
153
168
|
readonly locale: {
|
|
@@ -273,6 +288,19 @@ declare class VideoConsentPlugin implements JsPsychPlugin<Info$3> {
|
|
|
273
288
|
readonly type: ParameterType.BOOL;
|
|
274
289
|
readonly default: false;
|
|
275
290
|
};
|
|
291
|
+
/**
|
|
292
|
+
* This parameter is only relevant for the consent-recording-only template.
|
|
293
|
+
* If a different template is used, this parameter will be ignored. Whether
|
|
294
|
+
* or not the consent trial is the only data being collected on CHS (i.e.
|
|
295
|
+
* the study redirects to an external URL immediately after the consent
|
|
296
|
+
* trial). If false (the default), the consent template contains information
|
|
297
|
+
* about how CHS handles data/responses. If true, any statements about
|
|
298
|
+
* session data/responses are omitted.
|
|
299
|
+
*/
|
|
300
|
+
readonly only_consent_on_chs: {
|
|
301
|
+
readonly type: ParameterType.BOOL;
|
|
302
|
+
readonly default: false;
|
|
303
|
+
};
|
|
276
304
|
};
|
|
277
305
|
readonly data: {
|
|
278
306
|
readonly chs_type: {
|
|
@@ -404,7 +432,30 @@ declare class VideoConsentPlugin implements JsPsychPlugin<Info$3> {
|
|
|
404
432
|
declare const info$2: {
|
|
405
433
|
readonly name: "start-record-plugin";
|
|
406
434
|
readonly version: string;
|
|
407
|
-
readonly parameters: {
|
|
435
|
+
readonly parameters: {
|
|
436
|
+
/**
|
|
437
|
+
* This string can contain HTML markup. Any content provided will be
|
|
438
|
+
* displayed while the video recording connection is established. If null
|
|
439
|
+
* (the default), then the default 'establishing video connection, please
|
|
440
|
+
* wait' (or appropriate translation based on 'locale') will be displayed.
|
|
441
|
+
* Use a blank string for no message/content.
|
|
442
|
+
*/
|
|
443
|
+
readonly wait_for_connection_message: {
|
|
444
|
+
readonly type: ParameterType.HTML_STRING;
|
|
445
|
+
readonly default: string | null;
|
|
446
|
+
};
|
|
447
|
+
/**
|
|
448
|
+
* Locale code used for translating the default 'establishing video
|
|
449
|
+
* connection, please wait' message. This code must be present in the
|
|
450
|
+
* translation files. If the code is not found then English will be used. If
|
|
451
|
+
* the 'wait_for_connection_message' parameter is specified then this value
|
|
452
|
+
* is ignored.
|
|
453
|
+
*/
|
|
454
|
+
readonly locale: {
|
|
455
|
+
readonly type: ParameterType.STRING;
|
|
456
|
+
readonly default: "en-us";
|
|
457
|
+
};
|
|
458
|
+
};
|
|
408
459
|
readonly data: {};
|
|
409
460
|
};
|
|
410
461
|
type Info$2 = typeof info$2;
|
|
@@ -414,7 +465,30 @@ declare class StartRecordPlugin implements JsPsychPlugin<Info$2> {
|
|
|
414
465
|
static readonly info: {
|
|
415
466
|
readonly name: "start-record-plugin";
|
|
416
467
|
readonly version: string;
|
|
417
|
-
readonly parameters: {
|
|
468
|
+
readonly parameters: {
|
|
469
|
+
/**
|
|
470
|
+
* This string can contain HTML markup. Any content provided will be
|
|
471
|
+
* displayed while the video recording connection is established. If null
|
|
472
|
+
* (the default), then the default 'establishing video connection, please
|
|
473
|
+
* wait' (or appropriate translation based on 'locale') will be displayed.
|
|
474
|
+
* Use a blank string for no message/content.
|
|
475
|
+
*/
|
|
476
|
+
readonly wait_for_connection_message: {
|
|
477
|
+
readonly type: ParameterType.HTML_STRING;
|
|
478
|
+
readonly default: string | null;
|
|
479
|
+
};
|
|
480
|
+
/**
|
|
481
|
+
* Locale code used for translating the default 'establishing video
|
|
482
|
+
* connection, please wait' message. This code must be present in the
|
|
483
|
+
* translation files. If the code is not found then English will be used. If
|
|
484
|
+
* the 'wait_for_connection_message' parameter is specified then this value
|
|
485
|
+
* is ignored.
|
|
486
|
+
*/
|
|
487
|
+
readonly locale: {
|
|
488
|
+
readonly type: ParameterType.STRING;
|
|
489
|
+
readonly default: "en-us";
|
|
490
|
+
};
|
|
491
|
+
};
|
|
418
492
|
readonly data: {};
|
|
419
493
|
};
|
|
420
494
|
private recorder;
|
|
@@ -424,8 +498,15 @@ declare class StartRecordPlugin implements JsPsychPlugin<Info$2> {
|
|
|
424
498
|
* @param jsPsych - Object provided by jsPsych.
|
|
425
499
|
*/
|
|
426
500
|
constructor(jsPsych: JsPsych);
|
|
427
|
-
/**
|
|
428
|
-
|
|
501
|
+
/**
|
|
502
|
+
* Trial function called by jsPsych.
|
|
503
|
+
*
|
|
504
|
+
* @param display_element - DOM element where jsPsych content is being
|
|
505
|
+
* rendered (set in initJsPsych and automatically made available to a
|
|
506
|
+
* plugin's trial method via jsPsych core).
|
|
507
|
+
* @param trial - Trial object with parameters/values.
|
|
508
|
+
*/
|
|
509
|
+
trial(display_element: HTMLElement, trial: TrialType<Info$2>): Promise<void>;
|
|
429
510
|
}
|
|
430
511
|
|
|
431
512
|
declare const info$1: {
|
|
@@ -454,6 +535,14 @@ declare const info$1: {
|
|
|
454
535
|
readonly type: ParameterType.STRING;
|
|
455
536
|
readonly default: "en-us";
|
|
456
537
|
};
|
|
538
|
+
/**
|
|
539
|
+
* Maximum duration (in seconds) to wait for the session recording to finish
|
|
540
|
+
* uploading before continuing with the experiment.
|
|
541
|
+
*/
|
|
542
|
+
readonly max_upload_seconds: {
|
|
543
|
+
readonly type: ParameterType.INT;
|
|
544
|
+
readonly default: 10;
|
|
545
|
+
};
|
|
457
546
|
};
|
|
458
547
|
readonly data: {};
|
|
459
548
|
};
|
|
@@ -487,6 +576,14 @@ declare class StopRecordPlugin implements JsPsychPlugin<Info$1> {
|
|
|
487
576
|
readonly type: ParameterType.STRING;
|
|
488
577
|
readonly default: "en-us";
|
|
489
578
|
};
|
|
579
|
+
/**
|
|
580
|
+
* Maximum duration (in seconds) to wait for the session recording to finish
|
|
581
|
+
* uploading before continuing with the experiment.
|
|
582
|
+
*/
|
|
583
|
+
readonly max_upload_seconds: {
|
|
584
|
+
readonly type: ParameterType.INT;
|
|
585
|
+
readonly default: 10;
|
|
586
|
+
};
|
|
490
587
|
};
|
|
491
588
|
readonly data: {};
|
|
492
589
|
};
|
|
@@ -505,7 +602,7 @@ declare class StopRecordPlugin implements JsPsychPlugin<Info$1> {
|
|
|
505
602
|
* plugin's trial method via jsPsych core).
|
|
506
603
|
* @param trial - Trial object with parameters/values.
|
|
507
604
|
*/
|
|
508
|
-
trial(display_element: HTMLElement, trial: TrialType<Info$1>): void
|
|
605
|
+
trial(display_element: HTMLElement, trial: TrialType<Info$1>): Promise<void>;
|
|
509
606
|
}
|
|
510
607
|
|
|
511
608
|
interface Parameters {
|
|
@@ -526,12 +623,18 @@ interface Parameters {
|
|
|
526
623
|
* Locale code used for translating the default 'uploading video, please
|
|
527
624
|
* wait...' message. This code must be present in the translation files. If
|
|
528
625
|
* the code is not found then English will be used. If the
|
|
529
|
-
* 'wait_for_upload_message' parameter is specified then this value
|
|
530
|
-
*
|
|
626
|
+
* 'wait_for_upload_message' parameter is specified then this value is
|
|
627
|
+
* ignored.
|
|
531
628
|
*
|
|
532
629
|
* @default "en-us"
|
|
533
630
|
*/
|
|
534
631
|
locale?: string;
|
|
632
|
+
/**
|
|
633
|
+
* Maximum duration (in seconds) to wait for the trial recording to finish
|
|
634
|
+
* uploading before continuing with the experiment. Default is 10 seconds (set
|
|
635
|
+
* during initialize).
|
|
636
|
+
*/
|
|
637
|
+
max_upload_seconds?: null | number;
|
|
535
638
|
}
|
|
536
639
|
/** This extension allows researchers to record webcam audio/video during trials. */
|
|
537
640
|
declare class TrialRecordExtension implements JsPsychExtension {
|
|
@@ -541,6 +644,7 @@ declare class TrialRecordExtension implements JsPsychExtension {
|
|
|
541
644
|
private pluginName;
|
|
542
645
|
private uploadMsg;
|
|
543
646
|
private locale;
|
|
647
|
+
private maxUploadSeconds;
|
|
544
648
|
/**
|
|
545
649
|
* Video recording extension.
|
|
546
650
|
*
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import Handlebars from 'handlebars';
|
|
|
6
6
|
|
|
7
7
|
var _package = {
|
|
8
8
|
name: "@lookit/record",
|
|
9
|
-
version: "
|
|
9
|
+
version: "6.0.0",
|
|
10
10
|
description: "Recording extensions and plugins for CHS studies.",
|
|
11
11
|
homepage: "https://github.com/lookit/lookit-jspsych#readme",
|
|
12
12
|
bugs: {
|
|
@@ -47,8 +47,8 @@ var _package = {
|
|
|
47
47
|
typescript: "^5.6.2"
|
|
48
48
|
},
|
|
49
49
|
peerDependencies: {
|
|
50
|
-
"@lookit/data": "^0.
|
|
51
|
-
"@lookit/templates": "^
|
|
50
|
+
"@lookit/data": "^0.3.0",
|
|
51
|
+
"@lookit/templates": "^3.1.0",
|
|
52
52
|
jspsych: "^8.0.3"
|
|
53
53
|
}
|
|
54
54
|
};
|
|
@@ -123,6 +123,12 @@ class S3UndefinedError extends Error {
|
|
|
123
123
|
this.name = "S3UndefinedError";
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
class NoFileNameError extends Error {
|
|
127
|
+
constructor() {
|
|
128
|
+
super("No filename found for recording.");
|
|
129
|
+
this.name = "NoFileNameError";
|
|
130
|
+
}
|
|
131
|
+
}
|
|
126
132
|
class StreamActiveOnResetError extends Error {
|
|
127
133
|
constructor() {
|
|
128
134
|
super("Won't reset recorder. Stream is still active.");
|
|
@@ -147,6 +153,12 @@ class ElementNotFoundError extends Error {
|
|
|
147
153
|
this.name = "ElementNotFoundError";
|
|
148
154
|
}
|
|
149
155
|
}
|
|
156
|
+
class TimeoutError extends Error {
|
|
157
|
+
constructor(msg) {
|
|
158
|
+
super(`${msg}`);
|
|
159
|
+
this.name = "TimeoutError";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
150
162
|
|
|
151
163
|
var playbackFeed = "<video\n autoplay\n playsinline\n src=\"{{{src}}}\"\n class=\"webcam-feed\"\n id=\"{{webcam_element_id}}\"\n width=\"{{width}}\"\n height=\"{{height}}\"\n controls\n></video>";
|
|
152
164
|
|
|
@@ -158,6 +170,31 @@ var img$8 = "data:image/svg+xml,%3c%3fxml version='1.0' encoding='utf-8' %3f%3e%
|
|
|
158
170
|
|
|
159
171
|
var img$7 = "data:image/svg+xml,%3c%3fxml version='1.0' encoding='utf-8' %3f%3e%3csvg viewBox='-1 -1 18 18' xmlns='http://www.w3.org/2000/svg'%3e %3ccircle fill='red' stroke='black' stroke-width='0.5' cx='8' cy='8' r='8'%3e%3c/circle%3e%3c/svg%3e";
|
|
160
172
|
|
|
173
|
+
const promiseWithTimeout = (promise, promiseId, timeoutMs, onTimeoutCleanup) => {
|
|
174
|
+
let timeoutHandle;
|
|
175
|
+
const timeout = new Promise((resolve) => {
|
|
176
|
+
timeoutHandle = setTimeout(() => {
|
|
177
|
+
onTimeoutCleanup?.();
|
|
178
|
+
resolve("timeout");
|
|
179
|
+
}, timeoutMs);
|
|
180
|
+
});
|
|
181
|
+
return Promise.race([promise, timeout]).then(
|
|
182
|
+
(value) => {
|
|
183
|
+
if (value == "timeout") {
|
|
184
|
+
console.log(`Upload for ${promiseId} timed out.`);
|
|
185
|
+
} else {
|
|
186
|
+
console.log(`Upload for ${promiseId} completed.`);
|
|
187
|
+
clearTimeout(timeoutHandle);
|
|
188
|
+
}
|
|
189
|
+
return value;
|
|
190
|
+
},
|
|
191
|
+
(err) => {
|
|
192
|
+
clearTimeout(timeoutHandle);
|
|
193
|
+
throw err;
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
161
198
|
class Recorder {
|
|
162
199
|
constructor(jsPsych) {
|
|
163
200
|
this.jsPsych = jsPsych;
|
|
@@ -257,13 +294,73 @@ class Recorder {
|
|
|
257
294
|
this.recorder.stop();
|
|
258
295
|
this.stream.getTracks().map((t) => t.stop());
|
|
259
296
|
}
|
|
260
|
-
stop(
|
|
297
|
+
stop({
|
|
298
|
+
maintain_container_size = false,
|
|
299
|
+
stop_timeout_ms = null,
|
|
300
|
+
upload_timeout_ms = 1e4
|
|
301
|
+
} = {}) {
|
|
302
|
+
this.preStopCheck();
|
|
261
303
|
this.clearWebcamFeed(maintain_container_size);
|
|
262
304
|
this.stopTracks();
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
305
|
+
const snapshot = {
|
|
306
|
+
s3: !this.localDownload ? this.s3 : null,
|
|
307
|
+
filename: this.filename,
|
|
308
|
+
localDownload: this.localDownload,
|
|
309
|
+
url: "null"
|
|
310
|
+
};
|
|
311
|
+
const stopped = stop_timeout_ms ? promiseWithTimeout(
|
|
312
|
+
this.stopPromise,
|
|
313
|
+
`${snapshot.filename}-stopped`,
|
|
314
|
+
stop_timeout_ms,
|
|
315
|
+
this.createTimeoutHandler("stop", snapshot.filename)
|
|
316
|
+
) : this.stopPromise;
|
|
317
|
+
stopped.finally(() => {
|
|
318
|
+
try {
|
|
319
|
+
this.reset();
|
|
320
|
+
} catch (err) {
|
|
321
|
+
console.error("Error while resetting recorder after stop: ", err);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
const uploadPromise = (async () => {
|
|
325
|
+
let url;
|
|
326
|
+
try {
|
|
327
|
+
url = await stopped;
|
|
328
|
+
if (url == "timeout") {
|
|
329
|
+
throw new TimeoutError("Recorder stop timed out.");
|
|
330
|
+
}
|
|
331
|
+
} catch (err) {
|
|
332
|
+
console.warn("Upload failed because recorder stop timed out");
|
|
333
|
+
throw err;
|
|
334
|
+
}
|
|
335
|
+
snapshot.url = url;
|
|
336
|
+
if (snapshot.localDownload) {
|
|
337
|
+
try {
|
|
338
|
+
this.download(snapshot.filename, snapshot.url);
|
|
339
|
+
await Promise.resolve();
|
|
340
|
+
} catch (err) {
|
|
341
|
+
console.error("Local download failed: ", err);
|
|
342
|
+
throw err;
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
try {
|
|
346
|
+
await snapshot.s3.completeUpload();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
console.error("Upload failed: ", err);
|
|
349
|
+
throw err;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
})();
|
|
353
|
+
const uploaded = upload_timeout_ms ? promiseWithTimeout(
|
|
354
|
+
uploadPromise,
|
|
355
|
+
`${snapshot.filename}-uploaded`,
|
|
356
|
+
upload_timeout_ms,
|
|
357
|
+
this.createTimeoutHandler("upload", snapshot.filename)
|
|
358
|
+
) : uploadPromise;
|
|
359
|
+
window.chs.pendingUploads.push({
|
|
360
|
+
promise: uploadPromise,
|
|
361
|
+
file: snapshot.filename
|
|
362
|
+
});
|
|
363
|
+
return { stopped, uploaded };
|
|
267
364
|
}
|
|
268
365
|
initializeCheck() {
|
|
269
366
|
if (!this.recorder) {
|
|
@@ -276,19 +373,27 @@ class Recorder {
|
|
|
276
373
|
throw new StreamDataInitializeError();
|
|
277
374
|
}
|
|
278
375
|
}
|
|
376
|
+
preStopCheck() {
|
|
377
|
+
if (!this.recorder) {
|
|
378
|
+
throw new RecorderInitializeError();
|
|
379
|
+
}
|
|
380
|
+
if (!this.stream.active) {
|
|
381
|
+
throw new StreamInactiveInitializeError();
|
|
382
|
+
}
|
|
383
|
+
if (!this.stopPromise) {
|
|
384
|
+
throw new NoStopPromiseError();
|
|
385
|
+
}
|
|
386
|
+
if (!this.filename) {
|
|
387
|
+
throw new NoFileNameError();
|
|
388
|
+
}
|
|
389
|
+
}
|
|
279
390
|
handleStop(resolve) {
|
|
280
|
-
return
|
|
391
|
+
return () => {
|
|
281
392
|
if (this.blobs.length === 0) {
|
|
282
393
|
throw new CreateURLError();
|
|
283
394
|
}
|
|
284
395
|
this.url = URL.createObjectURL(new Blob(this.blobs));
|
|
285
|
-
|
|
286
|
-
this.download();
|
|
287
|
-
} else {
|
|
288
|
-
await this.s3.completeUpload();
|
|
289
|
-
}
|
|
290
|
-
this.reset();
|
|
291
|
-
resolve();
|
|
396
|
+
resolve(this.url);
|
|
292
397
|
};
|
|
293
398
|
}
|
|
294
399
|
handleDataAvailable(event) {
|
|
@@ -297,11 +402,11 @@ class Recorder {
|
|
|
297
402
|
this.s3.onDataAvailable(event.data);
|
|
298
403
|
}
|
|
299
404
|
}
|
|
300
|
-
download() {
|
|
301
|
-
if (
|
|
405
|
+
download(filename, url) {
|
|
406
|
+
if (filename && url) {
|
|
302
407
|
const link = document.createElement("a");
|
|
303
|
-
link.href =
|
|
304
|
-
link.download =
|
|
408
|
+
link.href = url;
|
|
409
|
+
link.download = filename;
|
|
305
410
|
link.click();
|
|
306
411
|
}
|
|
307
412
|
}
|
|
@@ -330,13 +435,33 @@ class Recorder {
|
|
|
330
435
|
const rand_digits = Math.floor(Math.random() * 1e3);
|
|
331
436
|
return `${prefix}_${window.chs.study.id}_${trial_id}_${window.chs.response.id}_${new Date().getTime()}_${rand_digits}.webm`;
|
|
332
437
|
}
|
|
438
|
+
createTimeoutHandler(eventName, id) {
|
|
439
|
+
return () => {
|
|
440
|
+
console.warn(`Recorder ${eventName} timed out: ${id}`);
|
|
441
|
+
if (!this.stream.active) {
|
|
442
|
+
try {
|
|
443
|
+
this.reset();
|
|
444
|
+
} catch (err) {
|
|
445
|
+
console.error("Error while resetting recorder after timeout: ", err);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
}
|
|
333
450
|
}
|
|
334
451
|
|
|
335
452
|
const info$3 = {
|
|
336
453
|
name: "consent-video",
|
|
337
454
|
version: _package.version,
|
|
338
455
|
parameters: {
|
|
339
|
-
template: {
|
|
456
|
+
template: {
|
|
457
|
+
type: ParameterType.SELECT,
|
|
458
|
+
options: [
|
|
459
|
+
"consent-template-5",
|
|
460
|
+
"consent-garden",
|
|
461
|
+
"consent-recording-only"
|
|
462
|
+
],
|
|
463
|
+
default: "consent-template-5"
|
|
464
|
+
},
|
|
340
465
|
locale: { type: ParameterType.STRING, default: "en-us" },
|
|
341
466
|
additional_video_privacy_statement: {
|
|
342
467
|
type: ParameterType.STRING,
|
|
@@ -381,7 +506,8 @@ const info$3 = {
|
|
|
381
506
|
prompt_all_adults: { type: ParameterType.BOOL, default: false },
|
|
382
507
|
prompt_only_adults: { type: ParameterType.BOOL, default: false },
|
|
383
508
|
consent_statement_text: { type: ParameterType.STRING, default: "" },
|
|
384
|
-
omit_injury_phrase: { type: ParameterType.BOOL, default: false }
|
|
509
|
+
omit_injury_phrase: { type: ParameterType.BOOL, default: false },
|
|
510
|
+
only_consent_on_chs: { type: ParameterType.BOOL, default: false }
|
|
385
511
|
},
|
|
386
512
|
data: {
|
|
387
513
|
chs_type: {
|
|
@@ -514,7 +640,10 @@ const _VideoConsentPlugin = class {
|
|
|
514
640
|
stop.addEventListener("click", async () => {
|
|
515
641
|
stop.disabled = true;
|
|
516
642
|
this.addMessage(display, this.uploadingMsg);
|
|
517
|
-
|
|
643
|
+
const { stopped, uploaded } = this.recorder.stop({
|
|
644
|
+
maintain_container_size: true
|
|
645
|
+
});
|
|
646
|
+
await stopped;
|
|
518
647
|
this.recordFeed(display);
|
|
519
648
|
this.getImg(display, "record-icon").style.visibility = "hidden";
|
|
520
649
|
this.addMessage(display, this.notRecordingMsg);
|
|
@@ -542,7 +671,16 @@ VideoConsentPlugin.info = info$3;
|
|
|
542
671
|
const info$2 = {
|
|
543
672
|
name: "start-record-plugin",
|
|
544
673
|
version: _package.version,
|
|
545
|
-
parameters: {
|
|
674
|
+
parameters: {
|
|
675
|
+
wait_for_connection_message: {
|
|
676
|
+
type: ParameterType.HTML_STRING,
|
|
677
|
+
default: null
|
|
678
|
+
},
|
|
679
|
+
locale: {
|
|
680
|
+
type: ParameterType.STRING,
|
|
681
|
+
default: "en-us"
|
|
682
|
+
}
|
|
683
|
+
},
|
|
546
684
|
data: {}
|
|
547
685
|
};
|
|
548
686
|
const _StartRecordPlugin = class {
|
|
@@ -555,8 +693,14 @@ const _StartRecordPlugin = class {
|
|
|
555
693
|
throw new ExistingRecordingError();
|
|
556
694
|
}
|
|
557
695
|
}
|
|
558
|
-
trial() {
|
|
559
|
-
|
|
696
|
+
async trial(display_element, trial) {
|
|
697
|
+
if (trial.wait_for_connection_message == null) {
|
|
698
|
+
display_element.innerHTML = chsTemplates.establishingConnection(trial);
|
|
699
|
+
} else {
|
|
700
|
+
display_element.innerHTML = trial.wait_for_connection_message;
|
|
701
|
+
}
|
|
702
|
+
await this.recorder.start(false, `${_StartRecordPlugin.info.name}-multiframe`).then(() => {
|
|
703
|
+
display_element.innerHTML = "";
|
|
560
704
|
this.jsPsych.finishTrial();
|
|
561
705
|
});
|
|
562
706
|
}
|
|
@@ -575,6 +719,10 @@ const info$1 = {
|
|
|
575
719
|
locale: {
|
|
576
720
|
type: ParameterType.STRING,
|
|
577
721
|
default: "en-us"
|
|
722
|
+
},
|
|
723
|
+
max_upload_seconds: {
|
|
724
|
+
type: ParameterType.INT,
|
|
725
|
+
default: 10
|
|
578
726
|
}
|
|
579
727
|
},
|
|
580
728
|
data: {}
|
|
@@ -588,19 +736,25 @@ class StopRecordPlugin {
|
|
|
588
736
|
throw new NoSessionRecordingError();
|
|
589
737
|
}
|
|
590
738
|
}
|
|
591
|
-
trial(display_element, trial) {
|
|
739
|
+
async trial(display_element, trial) {
|
|
592
740
|
if (trial.wait_for_upload_message == null) {
|
|
593
741
|
display_element.innerHTML = chsTemplates.uploadingVideo(trial);
|
|
594
742
|
} else {
|
|
595
743
|
display_element.innerHTML = trial.wait_for_upload_message;
|
|
596
744
|
}
|
|
597
|
-
this.recorder.stop(
|
|
745
|
+
const { stopped, uploaded } = this.recorder.stop({
|
|
746
|
+
upload_timeout_ms: trial.max_upload_seconds !== null ? trial.max_upload_seconds * 1e3 : null
|
|
747
|
+
});
|
|
748
|
+
try {
|
|
749
|
+
await stopped;
|
|
750
|
+
await uploaded;
|
|
751
|
+
} catch (err) {
|
|
752
|
+
console.error("StopRecordPlugin: recorder stop/upload failed.", err);
|
|
753
|
+
} finally {
|
|
598
754
|
window.chs.sessionRecorder = null;
|
|
599
755
|
display_element.innerHTML = "";
|
|
600
756
|
this.jsPsych.finishTrial();
|
|
601
|
-
}
|
|
602
|
-
console.error("StopRecordPlugin: recorder stop/upload failed.", err);
|
|
603
|
-
});
|
|
757
|
+
}
|
|
604
758
|
}
|
|
605
759
|
}
|
|
606
760
|
StopRecordPlugin.info = info$1;
|
|
@@ -610,14 +764,14 @@ class TrialRecordExtension {
|
|
|
610
764
|
this.jsPsych = jsPsych;
|
|
611
765
|
this.uploadMsg = null;
|
|
612
766
|
this.locale = "en-us";
|
|
767
|
+
this.maxUploadSeconds = void 0;
|
|
613
768
|
autoBind(this);
|
|
614
769
|
}
|
|
615
770
|
async initialize(params) {
|
|
616
771
|
await new Promise((resolve) => {
|
|
617
772
|
this.uploadMsg = params?.wait_for_upload_message ? params.wait_for_upload_message : null;
|
|
618
773
|
this.locale = params?.locale ? params.locale : "en-us";
|
|
619
|
-
|
|
620
|
-
console.log(this.locale);
|
|
774
|
+
this.maxUploadSeconds = params?.max_upload_seconds === void 0 ? 10 : params.max_upload_seconds;
|
|
621
775
|
resolve();
|
|
622
776
|
});
|
|
623
777
|
}
|
|
@@ -628,13 +782,14 @@ class TrialRecordExtension {
|
|
|
628
782
|
if (startParams?.locale) {
|
|
629
783
|
this.locale = startParams.locale;
|
|
630
784
|
}
|
|
631
|
-
|
|
632
|
-
|
|
785
|
+
if (startParams?.max_upload_seconds !== void 0) {
|
|
786
|
+
this.maxUploadSeconds = startParams?.max_upload_seconds;
|
|
787
|
+
}
|
|
633
788
|
this.recorder = new Recorder(this.jsPsych);
|
|
789
|
+
this.pluginName = this.getCurrentPluginName();
|
|
790
|
+
this.recorder.start(false, `${this.pluginName}`);
|
|
634
791
|
}
|
|
635
792
|
on_load() {
|
|
636
|
-
this.pluginName = this.getCurrentPluginName();
|
|
637
|
-
this.recorder?.start(false, `${this.pluginName}`);
|
|
638
793
|
}
|
|
639
794
|
async on_finish() {
|
|
640
795
|
const displayEl = this.jsPsych.getDisplayElement();
|
|
@@ -646,13 +801,27 @@ class TrialRecordExtension {
|
|
|
646
801
|
} else {
|
|
647
802
|
displayEl.innerHTML = this.uploadMsg;
|
|
648
803
|
}
|
|
649
|
-
|
|
650
|
-
|
|
804
|
+
if (this.recorder) {
|
|
805
|
+
const { stopped, uploaded } = this.recorder.stop({
|
|
806
|
+
upload_timeout_ms: this.maxUploadSeconds !== null ? this.maxUploadSeconds * 1e3 : null
|
|
807
|
+
});
|
|
808
|
+
try {
|
|
809
|
+
await stopped;
|
|
810
|
+
await uploaded;
|
|
811
|
+
displayEl.innerHTML = "";
|
|
812
|
+
return {};
|
|
813
|
+
} catch (err) {
|
|
814
|
+
console.error(
|
|
815
|
+
"TrialRecordExtension: recorder stop/upload failed.",
|
|
816
|
+
err
|
|
817
|
+
);
|
|
818
|
+
displayEl.innerHTML = "";
|
|
819
|
+
return {};
|
|
820
|
+
}
|
|
821
|
+
} else {
|
|
651
822
|
displayEl.innerHTML = "";
|
|
652
|
-
|
|
653
|
-
console.error("TrialRecordExtension: recorder stop/upload failed.", err);
|
|
823
|
+
return {};
|
|
654
824
|
}
|
|
655
|
-
return {};
|
|
656
825
|
}
|
|
657
826
|
getCurrentPluginName() {
|
|
658
827
|
const current_plugin_class = this.jsPsych.getCurrentTrial().type;
|