@lookit/record 3.0.1 → 4.1.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 +125 -6
- package/dist/index.browser.js +136 -41
- 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 +135 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +129 -12
- package/dist/index.js +135 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/consentVideo.spec.ts +38 -12
- package/src/consentVideo.ts +80 -19
- package/src/errors.ts +7 -27
- package/src/index.spec.ts +387 -12
- package/src/recorder.spec.ts +170 -6
- package/src/recorder.ts +37 -5
- package/src/start.ts +7 -1
- package/src/stop.ts +41 -7
- package/src/trial.ts +97 -11
package/dist/index.cjs
CHANGED
|
@@ -8,7 +8,7 @@ var Handlebars = require('handlebars');
|
|
|
8
8
|
|
|
9
9
|
var _package = {
|
|
10
10
|
name: "@lookit/record",
|
|
11
|
-
version: "
|
|
11
|
+
version: "4.1.0",
|
|
12
12
|
description: "Recording extensions and plugins for CHS studies.",
|
|
13
13
|
homepage: "https://github.com/lookit/lookit-jspsych#readme",
|
|
14
14
|
bugs: {
|
|
@@ -50,7 +50,7 @@ var _package = {
|
|
|
50
50
|
},
|
|
51
51
|
peerDependencies: {
|
|
52
52
|
"@lookit/data": "^0.2.0",
|
|
53
|
-
"@lookit/templates": "^2.
|
|
53
|
+
"@lookit/templates": "^2.1.0",
|
|
54
54
|
jspsych: "^8.0.3"
|
|
55
55
|
}
|
|
56
56
|
};
|
|
@@ -143,21 +143,10 @@ class CreateURLError extends Error {
|
|
|
143
143
|
this.name = "CreateURLError";
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
|
-
class
|
|
147
|
-
constructor() {
|
|
148
|
-
super("
|
|
149
|
-
this.name = "
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
class ButtonNotFoundError extends Error {
|
|
153
|
-
constructor(id) {
|
|
154
|
-
super(`"${id}" button not found.`);
|
|
155
|
-
this.name = "ButtonNotFoundError";
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
class ImageNotFoundError extends Error {
|
|
159
|
-
constructor(id) {
|
|
160
|
-
super(`"${id}" image not found.`);
|
|
146
|
+
class ElementNotFoundError extends Error {
|
|
147
|
+
constructor(id, tag) {
|
|
148
|
+
super(`"${id}" ${tag} not found.`);
|
|
149
|
+
this.name = "ElementNotFoundError";
|
|
161
150
|
}
|
|
162
151
|
}
|
|
163
152
|
|
|
@@ -270,9 +259,9 @@ class Recorder {
|
|
|
270
259
|
this.recorder.stop();
|
|
271
260
|
this.stream.getTracks().map((t) => t.stop());
|
|
272
261
|
}
|
|
273
|
-
stop() {
|
|
262
|
+
stop(maintain_container_size = false) {
|
|
263
|
+
this.clearWebcamFeed(maintain_container_size);
|
|
274
264
|
this.stopTracks();
|
|
275
|
-
this.clearWebcamFeed();
|
|
276
265
|
if (!this.stopPromise) {
|
|
277
266
|
throw new NoStopPromiseError();
|
|
278
267
|
}
|
|
@@ -300,6 +289,7 @@ class Recorder {
|
|
|
300
289
|
} else {
|
|
301
290
|
await this.s3.completeUpload();
|
|
302
291
|
}
|
|
292
|
+
this.reset();
|
|
303
293
|
resolve();
|
|
304
294
|
};
|
|
305
295
|
}
|
|
@@ -317,11 +307,20 @@ class Recorder {
|
|
|
317
307
|
link.click();
|
|
318
308
|
}
|
|
319
309
|
}
|
|
320
|
-
clearWebcamFeed() {
|
|
310
|
+
clearWebcamFeed(maintain_container_size) {
|
|
321
311
|
const webcam_feed_element = document.querySelector(
|
|
322
312
|
`#${this.webcam_element_id}`
|
|
323
313
|
);
|
|
324
314
|
if (webcam_feed_element) {
|
|
315
|
+
if (maintain_container_size) {
|
|
316
|
+
const parent_div = webcam_feed_element.parentElement;
|
|
317
|
+
if (parent_div) {
|
|
318
|
+
const width = webcam_feed_element.offsetWidth;
|
|
319
|
+
const height = webcam_feed_element.offsetHeight;
|
|
320
|
+
parent_div.style.height = `${height}px`;
|
|
321
|
+
parent_div.style.width = `${width}px`;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
325
324
|
webcam_feed_element.remove();
|
|
326
325
|
}
|
|
327
326
|
}
|
|
@@ -385,12 +384,22 @@ const info$3 = {
|
|
|
385
384
|
prompt_only_adults: { type: jspsych.ParameterType.BOOL, default: false },
|
|
386
385
|
consent_statement_text: { type: jspsych.ParameterType.STRING, default: "" },
|
|
387
386
|
omit_injury_phrase: { type: jspsych.ParameterType.BOOL, default: false }
|
|
387
|
+
},
|
|
388
|
+
data: {
|
|
389
|
+
chs_type: {
|
|
390
|
+
type: jspsych.ParameterType.STRING
|
|
391
|
+
}
|
|
388
392
|
}
|
|
389
393
|
};
|
|
390
394
|
const _VideoConsentPlugin = class {
|
|
391
395
|
constructor(jsPsych) {
|
|
392
396
|
this.jsPsych = jsPsych;
|
|
393
397
|
this.video_container_id = "lookit-jspsych-video-container";
|
|
398
|
+
this.msg_container_id = "lookit-jspsych-video-msg-container";
|
|
399
|
+
this.uploadingMsg = null;
|
|
400
|
+
this.startingMsg = null;
|
|
401
|
+
this.recordingMsg = null;
|
|
402
|
+
this.notRecordingMsg = null;
|
|
394
403
|
this.jsPsych = jsPsych;
|
|
395
404
|
this.recorder = new Recorder(this.jsPsych);
|
|
396
405
|
}
|
|
@@ -402,13 +411,25 @@ const _VideoConsentPlugin = class {
|
|
|
402
411
|
this.stopButton(display);
|
|
403
412
|
this.playButton(display);
|
|
404
413
|
this.nextButton(display);
|
|
414
|
+
this.uploadingMsg = chsTemplates.translateString(
|
|
415
|
+
"exp-lookit-video-consent.Stopping-and-uploading"
|
|
416
|
+
);
|
|
417
|
+
this.startingMsg = chsTemplates.translateString(
|
|
418
|
+
"exp-lookit-video-consent.Starting-recorder"
|
|
419
|
+
);
|
|
420
|
+
this.recordingMsg = chsTemplates.translateString(
|
|
421
|
+
"exp-lookit-video-consent.Recording"
|
|
422
|
+
);
|
|
423
|
+
this.notRecordingMsg = chsTemplates.translateString(
|
|
424
|
+
"exp-lookit-video-consent.Not-recording"
|
|
425
|
+
);
|
|
405
426
|
}
|
|
406
427
|
getVideoContainer(display) {
|
|
407
428
|
const videoContainer = display.querySelector(
|
|
408
429
|
`div#${this.video_container_id}`
|
|
409
430
|
);
|
|
410
431
|
if (!videoContainer) {
|
|
411
|
-
throw new
|
|
432
|
+
throw new ElementNotFoundError(this.video_container_id, "div");
|
|
412
433
|
}
|
|
413
434
|
return videoContainer;
|
|
414
435
|
}
|
|
@@ -419,14 +440,31 @@ const _VideoConsentPlugin = class {
|
|
|
419
440
|
}
|
|
420
441
|
playbackFeed(display) {
|
|
421
442
|
const videoContainer = this.getVideoContainer(display);
|
|
422
|
-
this.recorder.insertPlaybackFeed(
|
|
443
|
+
this.recorder.insertPlaybackFeed(
|
|
444
|
+
videoContainer,
|
|
445
|
+
this.onPlaybackEnded(display)
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
getMessageContainer(display) {
|
|
449
|
+
const msgContainer = display.querySelector(
|
|
450
|
+
`div#${this.msg_container_id}`
|
|
451
|
+
);
|
|
452
|
+
if (!msgContainer) {
|
|
453
|
+
throw new ElementNotFoundError(this.msg_container_id, "div");
|
|
454
|
+
}
|
|
455
|
+
return msgContainer;
|
|
456
|
+
}
|
|
457
|
+
addMessage(display, message) {
|
|
458
|
+
const msgContainer = this.getMessageContainer(display);
|
|
459
|
+
msgContainer.innerHTML = message;
|
|
423
460
|
}
|
|
424
|
-
|
|
461
|
+
onPlaybackEnded(display) {
|
|
425
462
|
return () => {
|
|
426
463
|
const next = this.getButton(display, "next");
|
|
427
464
|
const play = this.getButton(display, "play");
|
|
428
465
|
const record = this.getButton(display, "record");
|
|
429
466
|
this.recordFeed(display);
|
|
467
|
+
this.addMessage(display, this.notRecordingMsg);
|
|
430
468
|
next.disabled = false;
|
|
431
469
|
play.disabled = false;
|
|
432
470
|
record.disabled = false;
|
|
@@ -435,14 +473,14 @@ const _VideoConsentPlugin = class {
|
|
|
435
473
|
getButton(display, id) {
|
|
436
474
|
const btn = display.querySelector(`button#${id}`);
|
|
437
475
|
if (!btn) {
|
|
438
|
-
throw new
|
|
476
|
+
throw new ElementNotFoundError(id, "button");
|
|
439
477
|
}
|
|
440
478
|
return btn;
|
|
441
479
|
}
|
|
442
480
|
getImg(display, id) {
|
|
443
481
|
const img = display.querySelector(`img#${id}`);
|
|
444
482
|
if (!img) {
|
|
445
|
-
throw new
|
|
483
|
+
throw new ElementNotFoundError(id, "img");
|
|
446
484
|
}
|
|
447
485
|
return img;
|
|
448
486
|
}
|
|
@@ -452,12 +490,14 @@ const _VideoConsentPlugin = class {
|
|
|
452
490
|
const play = this.getButton(display, "play");
|
|
453
491
|
const next = this.getButton(display, "next");
|
|
454
492
|
record.addEventListener("click", async () => {
|
|
493
|
+
this.addMessage(display, this.startingMsg);
|
|
455
494
|
record.disabled = true;
|
|
456
|
-
stop.disabled = false;
|
|
457
495
|
play.disabled = true;
|
|
458
496
|
next.disabled = true;
|
|
459
|
-
this.getImg(display, "record-icon").style.visibility = "visible";
|
|
460
497
|
await this.recorder.start(true, _VideoConsentPlugin.info.name);
|
|
498
|
+
this.getImg(display, "record-icon").style.visibility = "visible";
|
|
499
|
+
this.addMessage(display, this.recordingMsg);
|
|
500
|
+
stop.disabled = false;
|
|
461
501
|
});
|
|
462
502
|
}
|
|
463
503
|
playButton(display) {
|
|
@@ -475,11 +515,13 @@ const _VideoConsentPlugin = class {
|
|
|
475
515
|
const play = this.getButton(display, "play");
|
|
476
516
|
stop.addEventListener("click", async () => {
|
|
477
517
|
stop.disabled = true;
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
await this.recorder.stop();
|
|
481
|
-
this.recorder.reset();
|
|
518
|
+
this.addMessage(display, this.uploadingMsg);
|
|
519
|
+
await this.recorder.stop(true);
|
|
482
520
|
this.recordFeed(display);
|
|
521
|
+
this.getImg(display, "record-icon").style.visibility = "hidden";
|
|
522
|
+
this.addMessage(display, this.notRecordingMsg);
|
|
523
|
+
play.disabled = false;
|
|
524
|
+
record.disabled = false;
|
|
483
525
|
});
|
|
484
526
|
}
|
|
485
527
|
nextButton(display) {
|
|
@@ -499,7 +541,12 @@ const _VideoConsentPlugin = class {
|
|
|
499
541
|
let VideoConsentPlugin = _VideoConsentPlugin;
|
|
500
542
|
VideoConsentPlugin.info = info$3;
|
|
501
543
|
|
|
502
|
-
const info$2 = {
|
|
544
|
+
const info$2 = {
|
|
545
|
+
name: "start-record-plugin",
|
|
546
|
+
version: _package.version,
|
|
547
|
+
parameters: {},
|
|
548
|
+
data: {}
|
|
549
|
+
};
|
|
503
550
|
const _StartRecordPlugin = class {
|
|
504
551
|
constructor(jsPsych) {
|
|
505
552
|
this.jsPsych = jsPsych;
|
|
@@ -521,9 +568,18 @@ StartRecordPlugin.info = info$2;
|
|
|
521
568
|
|
|
522
569
|
const info$1 = {
|
|
523
570
|
name: "stop-record-plugin",
|
|
571
|
+
version: _package.version,
|
|
524
572
|
parameters: {
|
|
525
|
-
|
|
526
|
-
|
|
573
|
+
wait_for_upload_message: {
|
|
574
|
+
type: jspsych.ParameterType.HTML_STRING,
|
|
575
|
+
default: null
|
|
576
|
+
},
|
|
577
|
+
locale: {
|
|
578
|
+
type: jspsych.ParameterType.STRING,
|
|
579
|
+
default: "en-us"
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
data: {}
|
|
527
583
|
};
|
|
528
584
|
class StopRecordPlugin {
|
|
529
585
|
constructor(jsPsych) {
|
|
@@ -535,11 +591,17 @@ class StopRecordPlugin {
|
|
|
535
591
|
}
|
|
536
592
|
}
|
|
537
593
|
trial(display_element, trial) {
|
|
538
|
-
|
|
594
|
+
if (trial.wait_for_upload_message == null) {
|
|
595
|
+
display_element.innerHTML = chsTemplates.uploadingVideo(trial);
|
|
596
|
+
} else {
|
|
597
|
+
display_element.innerHTML = trial.wait_for_upload_message;
|
|
598
|
+
}
|
|
539
599
|
this.recorder.stop().then(() => {
|
|
540
600
|
window.chs.sessionRecorder = null;
|
|
541
601
|
display_element.innerHTML = "";
|
|
542
602
|
this.jsPsych.finishTrial();
|
|
603
|
+
}).catch((err) => {
|
|
604
|
+
console.error("StopRecordPlugin: recorder stop/upload failed.", err);
|
|
543
605
|
});
|
|
544
606
|
}
|
|
545
607
|
}
|
|
@@ -548,19 +610,50 @@ StopRecordPlugin.info = info$1;
|
|
|
548
610
|
class TrialRecordExtension {
|
|
549
611
|
constructor(jsPsych) {
|
|
550
612
|
this.jsPsych = jsPsych;
|
|
613
|
+
this.uploadMsg = null;
|
|
614
|
+
this.locale = "en-us";
|
|
551
615
|
autoBind(this);
|
|
552
616
|
}
|
|
553
|
-
async initialize() {
|
|
617
|
+
async initialize(params) {
|
|
618
|
+
await new Promise((resolve) => {
|
|
619
|
+
this.uploadMsg = params?.wait_for_upload_message ? params.wait_for_upload_message : null;
|
|
620
|
+
this.locale = params?.locale ? params.locale : "en-us";
|
|
621
|
+
console.log(this.uploadMsg);
|
|
622
|
+
console.log(this.locale);
|
|
623
|
+
resolve();
|
|
624
|
+
});
|
|
554
625
|
}
|
|
555
|
-
on_start() {
|
|
626
|
+
on_start(startParams) {
|
|
627
|
+
if (startParams?.wait_for_upload_message) {
|
|
628
|
+
this.uploadMsg = startParams.wait_for_upload_message;
|
|
629
|
+
}
|
|
630
|
+
if (startParams?.locale) {
|
|
631
|
+
this.locale = startParams.locale;
|
|
632
|
+
}
|
|
633
|
+
console.log(this.uploadMsg);
|
|
634
|
+
console.log(this.locale);
|
|
556
635
|
this.recorder = new Recorder(this.jsPsych);
|
|
557
636
|
}
|
|
558
637
|
on_load() {
|
|
559
638
|
this.pluginName = this.getCurrentPluginName();
|
|
560
639
|
this.recorder?.start(false, `${this.pluginName}`);
|
|
561
640
|
}
|
|
562
|
-
on_finish() {
|
|
563
|
-
this.
|
|
641
|
+
async on_finish() {
|
|
642
|
+
const displayEl = this.jsPsych.getDisplayElement();
|
|
643
|
+
if (this.uploadMsg == null) {
|
|
644
|
+
displayEl.innerHTML = chsTemplates.uploadingVideo({
|
|
645
|
+
type: this.jsPsych.getCurrentTrial().type,
|
|
646
|
+
locale: this.locale
|
|
647
|
+
});
|
|
648
|
+
} else {
|
|
649
|
+
displayEl.innerHTML = this.uploadMsg;
|
|
650
|
+
}
|
|
651
|
+
try {
|
|
652
|
+
await this.recorder?.stop();
|
|
653
|
+
displayEl.innerHTML = "";
|
|
654
|
+
} catch (err) {
|
|
655
|
+
console.error("TrialRecordExtension: recorder stop/upload failed.", err);
|
|
656
|
+
}
|
|
564
657
|
return {};
|
|
565
658
|
}
|
|
566
659
|
getCurrentPluginName() {
|
|
@@ -569,7 +662,9 @@ class TrialRecordExtension {
|
|
|
569
662
|
}
|
|
570
663
|
}
|
|
571
664
|
TrialRecordExtension.info = {
|
|
572
|
-
name: "chs-trial-record-extension"
|
|
665
|
+
name: "chs-trial-record-extension",
|
|
666
|
+
version: _package.version,
|
|
667
|
+
data: {}
|
|
573
668
|
};
|
|
574
669
|
|
|
575
670
|
var img$6 = "data:image/png;base64,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";
|