@lookit/record 3.0.0 → 4.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 CHANGED
@@ -25,7 +25,7 @@ following language codes:
25
25
  | Portuguese | Brazil | pt-BR |
26
26
  | Portuguese | | pt |
27
27
 
28
- ## Video Configuration
28
+ ## Video Configuration Plugin
29
29
 
30
30
  To record _any_ video during an experiment, including a consent video, you must
31
31
  add a video configuration trial. This trial allows the user to give permissions
@@ -61,51 +61,20 @@ const videoConfig = {
61
61
  };
62
62
  ```
63
63
 
64
- ## Video Consent
64
+ ## Video Consent Plugin
65
65
 
66
66
  Users will need to record themselves accepting the consent document for your
67
67
  study. This trial will allow the user to read the consent document and record a
68
68
  video accepting it.
69
69
 
70
- To create the video consent trial.
70
+ !!! caution "Don't forget a video config trial!"
71
71
 
72
- ```javascript
73
- const videoConsent = { type: chsRecord.VideoConsentPlugin, ...parameters };
74
- ```
72
+ You MUST have a video config trial in your experiment timeline before the video consent trial.
75
73
 
76
- ### Example
74
+ To create the video consent trial:
77
75
 
78
76
  ```javascript
79
- const videoConsent = {
80
- type: chsRecord.VideoConsentPlugin,
81
- PIName: "Jane Smith",
82
- institution: "Science University",
83
- PIContact: "Jane Smith at 123 456 7890",
84
- purpose:
85
- "Why do babies love cats? This study will help us find out whether babies love cats because of their soft fur or their twitchy tails.",
86
- procedures:
87
- "Your child will be shown pictures of lots of different cats, along with noises that cats make like meowing and purring. We are interested in which pictures and sounds make your child smile. We will ask you (the parent) to turn around to avoid influencing your child's responses.",
88
- risk_statement:
89
- "There are no expected risks if you participate in the study. (This is optional, but should typically be included. If you leave it out there's no 'risks' section and you should include risk information elsewhere.)",
90
- voluntary_participation:
91
- "There are two sessions in this study; you will be invited to complete another session next month. It is okay not to do both sessions! (This is optional; leave it out if you don't need to say anything besides participation in this session being voluntary.)",
92
- payment:
93
- "After you finish the study, we will email you a $5 BabyStore gift card within approximately three days. To be eligible for the gift card your child must be in the age range for this study, you need to submit a valid consent statement, and we need to see that there is a child with you. But we will send a gift card even if you do not finish the whole study or we are not able to use your child's data! There are no other direct benefits to you or your child from participating, but we hope you will enjoy the experience.",
94
- datause:
95
- "We are primarily interested in your child's emotional reactions to the images and sounds. A research assistant will watch your video to measure the precise amount of delight in your child's face as he or she sees each cat picture.",
96
- include_databrary: true,
97
- additional_video_privacy_statement:
98
- "We will also ask your permission to use your videos as stimuli for other parents. (This is optional; leave it out if there aren't additional ways you'll share video beyond as described in the participant's video privacy level and Databrary selections.)",
99
- gdpr: false,
100
- research_rights_statement:
101
- "You are not waiving any legal claims, rights or remedies because of your participation in this research study. If you feel you have been treated unfairly, or you have questions regarding your rights as a research subject, you may contact the [IRB NAME], [INSTITUTION], [ADDRESS/CONTACT]",
102
- additional_segments: [
103
- {
104
- title: "US Patriot Act Disclosure",
105
- text: "[EXAMPLE ONLY, PLEASE REMOVE ADDITIONAL_SEGMENTS UNLESS YOU NEED THEM.] Lookit is a U.S. organization and all information gathered from the website is stored on servers based in the U.S. Therefore, your video recordings are subject to U.S. laws, such as the US Patriot Act. This act allows authorities access to the records of internet service providers. If you choose to participate in this study, you understand that your video recording will be stored and accessed in the USA. The security and privacy policy for Lookit can be found at the following link: <a href='https://lookit.mit.edu/privacy/' target='_blank' rel='noopener'>https://lookit.mit.edu/privacy/</a>.",
106
- },
107
- ],
108
- };
77
+ const videoConsent = { type: chsRecord.VideoConsentPlugin, ...parameters };
109
78
  ```
110
79
 
111
80
  ### Parameters
@@ -328,7 +297,68 @@ Replace the default spoken consent statement with your custom text.
328
297
  Whether to omit the phrase “or in the very unlikely event of a research-related
329
298
  injury” from the contact section. (This was required by the Northwestern IRB.)
330
299
 
331
- ## Trial Recording
300
+ ### Example
301
+
302
+ ```javascript
303
+ const videoConsent = {
304
+ type: chsRecord.VideoConsentPlugin,
305
+ PIName: "Jane Smith",
306
+ institution: "Science University",
307
+ PIContact: "Jane Smith at 123 456 7890",
308
+ purpose:
309
+ "Why do babies love cats? This study will help us find out whether babies love cats because of their soft fur or their twitchy tails.",
310
+ procedures:
311
+ "Your child will be shown pictures of lots of different cats, along with noises that cats make like meowing and purring. We are interested in which pictures and sounds make your child smile. We will ask you (the parent) to turn around to avoid influencing your child's responses.",
312
+ risk_statement:
313
+ "There are no expected risks if you participate in the study. (This is optional, but should typically be included. If you leave it out there's no 'risks' section and you should include risk information elsewhere.)",
314
+ voluntary_participation:
315
+ "There are two sessions in this study; you will be invited to complete another session next month. It is okay not to do both sessions! (This is optional; leave it out if you don't need to say anything besides participation in this session being voluntary.)",
316
+ payment:
317
+ "After you finish the study, we will email you a $5 BabyStore gift card within approximately three days. To be eligible for the gift card your child must be in the age range for this study, you need to submit a valid consent statement, and we need to see that there is a child with you. But we will send a gift card even if you do not finish the whole study or we are not able to use your child's data! There are no other direct benefits to you or your child from participating, but we hope you will enjoy the experience.",
318
+ datause:
319
+ "We are primarily interested in your child's emotional reactions to the images and sounds. A research assistant will watch your video to measure the precise amount of delight in your child's face as he or she sees each cat picture.",
320
+ include_databrary: true,
321
+ additional_video_privacy_statement:
322
+ "We will also ask your permission to use your videos as stimuli for other parents. (This is optional; leave it out if there aren't additional ways you'll share video beyond as described in the participant's video privacy level and Databrary selections.)",
323
+ gdpr: false,
324
+ research_rights_statement:
325
+ "You are not waiving any legal claims, rights or remedies because of your participation in this research study. If you feel you have been treated unfairly, or you have questions regarding your rights as a research subject, you may contact the [IRB NAME], [INSTITUTION], [ADDRESS/CONTACT]",
326
+ additional_segments: [
327
+ {
328
+ title: "US Patriot Act Disclosure",
329
+ text: "[EXAMPLE ONLY, PLEASE REMOVE ADDITIONAL_SEGMENTS UNLESS YOU NEED THEM.] Lookit is a U.S. organization and all information gathered from the website is stored on servers based in the U.S. Therefore, your video recordings are subject to U.S. laws, such as the US Patriot Act. This act allows authorities access to the records of internet service providers. If you choose to participate in this study, you understand that your video recording will be stored and accessed in the USA. The security and privacy policy for Lookit can be found at the following link: <a href='https://lookit.mit.edu/privacy/' target='_blank' rel='noopener'>https://lookit.mit.edu/privacy/</a>.",
330
+ },
331
+ ],
332
+ };
333
+ ```
334
+
335
+ ## Trial Recording Extension
336
+
337
+ Trial recording can be added to most jsPsych trials. This is a jsPsych extension
338
+ that allows you to add video recording to the trial. The trial will start once
339
+ the video recording has been set up, and the video recording will finish as soon
340
+ as the trial has ended.
341
+
342
+ !!! important "When to use trial recording"
343
+
344
+ Trial recording should NOT occur simultaneously with session recording, or with any plugins that already use the webcam (e.g. video config, video consent).
345
+
346
+ To use the CHS trial recording extension, you need to:
347
+
348
+ 1. Add it to the experiment settings, which is an optional object passed to
349
+ `initjsPsych`.
350
+ 2. Add it to the `extensions` parameter for any trial(s) that you want to be
351
+ recorded.
352
+
353
+ !!! caution "Don't forget a video config trial!"
354
+
355
+ You MUST have a video config trial in your experiment timeline before doing any trial recording.
356
+
357
+ ### Parameters
358
+
359
+ This extension does not accept any parameters.
360
+
361
+ ### Example
332
362
 
333
363
  To record a single trial, you will have to first load the extension in
334
364
  `initJsPsych`.
@@ -362,7 +392,48 @@ You might prefer to record across multiple trials in a study session. This can
362
392
  be done by using trials created with the start and stop recording plugins. This
363
393
  gives a bit of flexibility over which of the study trials are recorded.
364
394
 
365
- To record a study session, create the start and stop recording trials.
395
+ !!! important "When to use session recording"
396
+
397
+ Sesssion recording should NOT occur simultaneously with trial recording, or with any plugins that already use the webcam (e.g. video config, video consent).
398
+
399
+ To record a set of trials, add a 'start' trial in your jsPsych experiment
400
+ timeline right before you'd like to start recording, and add a 'stop' trial at
401
+ the point in your timeline when you'd like to stop recording.
402
+
403
+ !!! caution "Don't forget a video config trial!"
404
+
405
+ You MUST have a video config trial in your experiment timeline before doing any session recording.
406
+
407
+ ### Start Recording Plugin
408
+
409
+ The plugin to start session recording is called `chsRecord.StartRecordPlugin`.
410
+
411
+ ```javascript
412
+ const startRec = { type: chsRecord.StartRecordPlugin };
413
+ ```
414
+
415
+ #### Parameters
416
+
417
+ This plugin does not accept any parameters, other those available in all
418
+ plugins.
419
+
420
+ ### Stop Recording Plugin
421
+
422
+ The plugin to stop session recording is called `chsRecord.StopRecordPlugin`.
423
+
424
+ ```javascript
425
+ const stopRec = { type: chsRecord.StopRecordPlugin };
426
+ ```
427
+
428
+ #### Parameters
429
+
430
+ This plugin does not accept any parameters, other those available in all
431
+ plugins.
432
+
433
+ ### Example
434
+
435
+ First, make sure that you've added a video config trial to your experiment
436
+ timeline. Then, create your start and stop recording trials:
366
437
 
367
438
  ```javascript
368
439
  const startRec = { type: chsRecord.StartRecordPlugin };
@@ -390,3 +461,18 @@ stop or start recording trials within the timeline.
390
461
  ```javascript
391
462
  jsPsych.run([videoConfig, startRec, morning, evening, stopRec, night]);
392
463
  ```
464
+
465
+ You can also create more than one session recording:
466
+
467
+ ```javascript
468
+ jsPsych.run([
469
+ videoConfig,
470
+ startRec,
471
+ morning,
472
+ stopRec,
473
+ evening,
474
+ startRec,
475
+ night,
476
+ stopRec,
477
+ ]);
478
+ ```
@@ -32,7 +32,7 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
32
32
 
33
33
  var _package = {
34
34
  name: "@lookit/record",
35
- version: "3.0.0",
35
+ version: "4.0.0",
36
36
  description: "Recording extensions and plugins for CHS studies.",
37
37
  homepage: "https://github.com/lookit/lookit-jspsych#readme",
38
38
  bugs: {
@@ -74,7 +74,7 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
74
74
  },
75
75
  peerDependencies: {
76
76
  "@lookit/data": "^0.2.0",
77
- "@lookit/templates": "^2.0.0",
77
+ "@lookit/templates": "^2.1.0",
78
78
  jspsych: "^8.0.3"
79
79
  }
80
80
  };
@@ -167,21 +167,10 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
167
167
  this.name = "CreateURLError";
168
168
  }
169
169
  }
170
- class VideoContainerNotFoundError extends Error {
171
- constructor() {
172
- super("Video Container could not be found.");
173
- this.name = "VideoContainerError";
174
- }
175
- }
176
- class ButtonNotFoundError extends Error {
177
- constructor(id) {
178
- super(`"${id}" button not found.`);
179
- this.name = "ButtonNotFoundError";
180
- }
181
- }
182
- class ImageNotFoundError extends Error {
183
- constructor(id) {
184
- super(`"${id}" image not found.`);
170
+ class ElementNotFoundError extends Error {
171
+ constructor(id, tag) {
172
+ super(`"${id}" ${tag} not found.`);
173
+ this.name = "ElementNotFoundError";
185
174
  }
186
175
  }
187
176
 
@@ -8481,6 +8470,7 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8481
8470
  this.jsPsych = jsPsych;
8482
8471
  this.streamClone = this.stream.clone();
8483
8472
  autoBind(this);
8473
+ this.mimeType = this.recorder?.mimeType || this.mimeType;
8484
8474
  }
8485
8475
  url;
8486
8476
  _s3;
@@ -8489,6 +8479,7 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8489
8479
  filename;
8490
8480
  stopPromise;
8491
8481
  webcam_element_id = "lookit-jspsych-webcam";
8482
+ mimeType = "video/webm";
8492
8483
  streamClone;
8493
8484
  get recorder() {
8494
8485
  return this.jsPsych.pluginAPI.getCameraRecorder() || this.jsPsych.pluginAPI.getMicrophoneRecorder();
@@ -8506,7 +8497,11 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8506
8497
  this._s3 = value;
8507
8498
  }
8508
8499
  initializeRecorder(stream, opts) {
8509
- this.jsPsych.pluginAPI.initializeCameraRecorder(stream, opts);
8500
+ const recorder_options = {
8501
+ ...opts,
8502
+ mimeType: this.mimeType
8503
+ };
8504
+ this.jsPsych.pluginAPI.initializeCameraRecorder(stream, recorder_options);
8510
8505
  }
8511
8506
  reset() {
8512
8507
  if (this.stream.active) {
@@ -8574,9 +8569,9 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8574
8569
  this.recorder.stop();
8575
8570
  this.stream.getTracks().map((t) => t.stop());
8576
8571
  }
8577
- stop() {
8572
+ stop(maintain_container_size = false) {
8573
+ this.clearWebcamFeed(maintain_container_size);
8578
8574
  this.stopTracks();
8579
- this.clearWebcamFeed();
8580
8575
  if (!this.stopPromise) {
8581
8576
  throw new NoStopPromiseError();
8582
8577
  }
@@ -8621,11 +8616,20 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8621
8616
  link.click();
8622
8617
  }
8623
8618
  }
8624
- clearWebcamFeed() {
8619
+ clearWebcamFeed(maintain_container_size) {
8625
8620
  const webcam_feed_element = document.querySelector(
8626
8621
  `#${this.webcam_element_id}`
8627
8622
  );
8628
8623
  if (webcam_feed_element) {
8624
+ if (maintain_container_size) {
8625
+ const parent_div = webcam_feed_element.parentElement;
8626
+ if (parent_div) {
8627
+ const width = webcam_feed_element.offsetWidth;
8628
+ const height = webcam_feed_element.offsetHeight;
8629
+ parent_div.style.height = `${height}px`;
8630
+ parent_div.style.width = `${width}px`;
8631
+ }
8632
+ }
8629
8633
  webcam_feed_element.remove();
8630
8634
  }
8631
8635
  }
@@ -8700,6 +8704,11 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8700
8704
  static info = info$3;
8701
8705
  recorder;
8702
8706
  video_container_id = "lookit-jspsych-video-container";
8707
+ msg_container_id = "lookit-jspsych-video-msg-container";
8708
+ uploadingMsg = null;
8709
+ startingMsg = null;
8710
+ recordingMsg = null;
8711
+ notRecordingMsg = null;
8703
8712
  trial(display, trial) {
8704
8713
  const consentVideo = chsTemplates.consentVideo(trial);
8705
8714
  display.insertAdjacentHTML("afterbegin", consentVideo);
@@ -8708,13 +8717,25 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8708
8717
  this.stopButton(display);
8709
8718
  this.playButton(display);
8710
8719
  this.nextButton(display);
8720
+ this.uploadingMsg = chsTemplates.translateString(
8721
+ "exp-lookit-video-consent.Stopping-and-uploading"
8722
+ );
8723
+ this.startingMsg = chsTemplates.translateString(
8724
+ "exp-lookit-video-consent.Starting-recorder"
8725
+ );
8726
+ this.recordingMsg = chsTemplates.translateString(
8727
+ "exp-lookit-video-consent.Recording"
8728
+ );
8729
+ this.notRecordingMsg = chsTemplates.translateString(
8730
+ "exp-lookit-video-consent.Not-recording"
8731
+ );
8711
8732
  }
8712
8733
  getVideoContainer(display) {
8713
8734
  const videoContainer = display.querySelector(
8714
8735
  `div#${this.video_container_id}`
8715
8736
  );
8716
8737
  if (!videoContainer) {
8717
- throw new VideoContainerNotFoundError();
8738
+ throw new ElementNotFoundError(this.video_container_id, "div");
8718
8739
  }
8719
8740
  return videoContainer;
8720
8741
  }
@@ -8725,14 +8746,31 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8725
8746
  }
8726
8747
  playbackFeed(display) {
8727
8748
  const videoContainer = this.getVideoContainer(display);
8728
- this.recorder.insertPlaybackFeed(videoContainer, this.onEnded(display));
8749
+ this.recorder.insertPlaybackFeed(
8750
+ videoContainer,
8751
+ this.onPlaybackEnded(display)
8752
+ );
8729
8753
  }
8730
- onEnded(display) {
8754
+ getMessageContainer(display) {
8755
+ const msgContainer = display.querySelector(
8756
+ `div#${this.msg_container_id}`
8757
+ );
8758
+ if (!msgContainer) {
8759
+ throw new ElementNotFoundError(this.msg_container_id, "div");
8760
+ }
8761
+ return msgContainer;
8762
+ }
8763
+ addMessage(display, message) {
8764
+ const msgContainer = this.getMessageContainer(display);
8765
+ msgContainer.innerHTML = message;
8766
+ }
8767
+ onPlaybackEnded(display) {
8731
8768
  return () => {
8732
8769
  const next = this.getButton(display, "next");
8733
8770
  const play = this.getButton(display, "play");
8734
8771
  const record = this.getButton(display, "record");
8735
8772
  this.recordFeed(display);
8773
+ this.addMessage(display, this.notRecordingMsg);
8736
8774
  next.disabled = false;
8737
8775
  play.disabled = false;
8738
8776
  record.disabled = false;
@@ -8741,14 +8779,14 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8741
8779
  getButton(display, id) {
8742
8780
  const btn = display.querySelector(`button#${id}`);
8743
8781
  if (!btn) {
8744
- throw new ButtonNotFoundError(id);
8782
+ throw new ElementNotFoundError(id, "button");
8745
8783
  }
8746
8784
  return btn;
8747
8785
  }
8748
8786
  getImg(display, id) {
8749
8787
  const img = display.querySelector(`img#${id}`);
8750
8788
  if (!img) {
8751
- throw new ImageNotFoundError(id);
8789
+ throw new ElementNotFoundError(id, "img");
8752
8790
  }
8753
8791
  return img;
8754
8792
  }
@@ -8758,12 +8796,14 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8758
8796
  const play = this.getButton(display, "play");
8759
8797
  const next = this.getButton(display, "next");
8760
8798
  record.addEventListener("click", async () => {
8799
+ this.addMessage(display, this.startingMsg);
8761
8800
  record.disabled = true;
8762
- stop.disabled = false;
8763
8801
  play.disabled = true;
8764
8802
  next.disabled = true;
8765
- this.getImg(display, "record-icon").style.visibility = "visible";
8766
8803
  await this.recorder.start(true, VideoConsentPlugin.info.name);
8804
+ this.getImg(display, "record-icon").style.visibility = "visible";
8805
+ this.addMessage(display, this.recordingMsg);
8806
+ stop.disabled = false;
8767
8807
  });
8768
8808
  }
8769
8809
  playButton(display) {
@@ -8781,11 +8821,14 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8781
8821
  const play = this.getButton(display, "play");
8782
8822
  stop.addEventListener("click", async () => {
8783
8823
  stop.disabled = true;
8784
- record.disabled = false;
8785
- play.disabled = false;
8786
- await this.recorder.stop();
8824
+ this.addMessage(display, this.uploadingMsg);
8825
+ await this.recorder.stop(true);
8787
8826
  this.recorder.reset();
8788
8827
  this.recordFeed(display);
8828
+ this.getImg(display, "record-icon").style.visibility = "hidden";
8829
+ this.addMessage(display, this.notRecordingMsg);
8830
+ play.disabled = false;
8831
+ record.disabled = false;
8789
8832
  });
8790
8833
  }
8791
8834
  nextButton(display) {
@@ -8955,6 +8998,7 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8955
8998
  minVolume = 0.1;
8956
8999
  micChecked = false;
8957
9000
  processorNode = null;
9001
+ mimeType = "video/webm";
8958
9002
  trial(display_element, trial) {
8959
9003
  this.display_el = display_element;
8960
9004
  navigator.mediaDevices.ondevicechange = this.onDeviceChange;
@@ -9109,7 +9153,12 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
9109
9153
  return { cameras: unique_cameras, mics: unique_mics };
9110
9154
  };
9111
9155
  initializeAndCreateRecorder = (stream, opts) => {
9112
- this.jsPsych.pluginAPI.initializeCameraRecorder(stream, opts);
9156
+ this.mimeType = this.getCompatibleMimeType() || this.mimeType;
9157
+ const recorder_options = {
9158
+ ...opts,
9159
+ mimeType: this.mimeType
9160
+ };
9161
+ this.jsPsych.pluginAPI.initializeCameraRecorder(stream, recorder_options);
9113
9162
  this.recorder = new Recorder(this.jsPsych);
9114
9163
  };
9115
9164
  checkMic = async (minVol = this.minVolume) => {
@@ -9249,6 +9298,20 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
9249
9298
  next_button_el.classList.remove(`${html_params.step_complete_class}`);
9250
9299
  }
9251
9300
  };
9301
+ getCompatibleMimeType() {
9302
+ const mime_types = [
9303
+ "video/webm;codecs=vp9,opus",
9304
+ "video/webm;codecs=vp8,opus"
9305
+ ];
9306
+ let mime_type_index = 0;
9307
+ while (mime_type_index < mime_types.length) {
9308
+ if (MediaRecorder.isTypeSupported(mime_types[mime_type_index])) {
9309
+ return mime_types[mime_type_index];
9310
+ }
9311
+ mime_type_index++;
9312
+ }
9313
+ return null;
9314
+ }
9252
9315
  }
9253
9316
 
9254
9317
  var index = {
@@ -9262,4 +9325,4 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
9262
9325
  return index;
9263
9326
 
9264
9327
  })(chsData, chsTemplates, jsPsychModule);
9265
- //# sourceMappingURL=https://unpkg.com/@lookit/record@3.0.0/dist/index.browser.js.map
9328
+ //# sourceMappingURL=https://unpkg.com/@lookit/record@4.0.0/dist/index.browser.js.map