@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 CHANGED
@@ -140,11 +140,23 @@ whether babies love cats because of their soft fur or their twitchy tails.”
140
140
 
141
141
  #### Optional
142
142
 
143
- **`template` [String | "consent-template-5"]**
143
+ **`template` [String option | "consent-template-5"]**
144
144
 
145
- Which consent document template to use. If you are setting up a new study, we
146
- recommend using the most recent (highest number) of these options. Options:
147
- `consent-template-5`, `consent-garden`.
145
+ Which consent document template to use. Options: `consent-template-5`,
146
+ `consent-garden`, `consent-recording-only`.
147
+
148
+ Most studies should use `consent-template-5` (the default).
149
+
150
+ If you are running a study that ONLY uses webcam recording for the consent
151
+ statement (nothing else is recording during the session), then you should use
152
+ the `consent-recording-only` template. This version removes/modifies some text
153
+ that references additional webcam recordings during the session.
154
+
155
+ By default, the `consent-recording-only` plugin assumes that, in addition to a
156
+ consent trial, you are using CHS to collect other data/responses. If you are not
157
+ collecting any other data on CHS (for instance, because the rest of the study
158
+ takes place on a different platform), then you should change this using the
159
+ `only_consent_on_chs` parameter (see below).
148
160
 
149
161
  **`additional_video_privacy_statement` [String | ""]**
150
162
 
@@ -220,6 +232,18 @@ Optional additional text for under header “Participation is voluntary”. E.g.
220
232
  “There are two sessions in this study; you will be invited to complete another
221
233
  session next month. It is okay not to do both sessions!”
222
234
 
235
+ **`only_consent_on_chs` [Boolean | false]**
236
+
237
+ This parameter is only relevant to the `consent-recording-only` template. If a
238
+ different template is used, this parameter will be ignored.
239
+
240
+ Whether or not the consent trial is the ONLY data being collected about the
241
+ participant on CHS (e.g. the study redirects to an external URL after the
242
+ consent trial). If `false` (the default), then the consent template will contain
243
+ information about how CHS handles data/responses. If `true`, then the template
244
+ will only reference the consent recording, and any statements about CHS access
245
+ to data/responses are omitted.
246
+
223
247
  #### Additional customization available if REQUIRED by your IRB
224
248
 
225
249
  To accommodate a variety of idiosyncratic IRB requirements, various other fields
@@ -297,7 +321,9 @@ Replace the default spoken consent statement with your custom text.
297
321
  Whether to omit the phrase “or in the very unlikely event of a research-related
298
322
  injury” from the contact section. (This was required by the Northwestern IRB.)
299
323
 
300
- ### Example
324
+ ### Examples
325
+
326
+ **Standard use case**
301
327
 
302
328
  ```javascript
303
329
  const videoConsent = {
@@ -332,6 +358,51 @@ const videoConsent = {
332
358
  };
333
359
  ```
334
360
 
361
+ **Study only uses webcam recording for consent**
362
+
363
+ Use the `"consent-recording-only"` template when the study collects (some or
364
+ all) data/responses on CHS, but webcam recording is only used for the consent
365
+ statement (nothing else is recorded).
366
+
367
+ ```javascript
368
+ const videoConsentRecOnly = {
369
+ type: chsRecord.VideoConsentPlugin,
370
+ template: "consent-recording-only",
371
+ PIName: "Jane Smith",
372
+ institution: "Science University",
373
+ PIContact: "Jane Smith at 123 456 7890",
374
+ purpose:
375
+ "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.",
376
+ procedures:
377
+ "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.",
378
+ payment:
379
+ "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.",
380
+ };
381
+ ```
382
+
383
+ **CHS study is only used for a consent recording**
384
+
385
+ Use the `"consent-recording-only"` template with `only_consent_on_chs: true`
386
+ when webcam recording is only used for the consent statement and no other
387
+ data/responses are collected on CHS.
388
+
389
+ ```javascript
390
+ const videoConsentRecOnly = {
391
+ type: chsRecord.VideoConsentPlugin,
392
+ template: "consent-recording-only",
393
+ only_consent_on_chs: true, // set this to true
394
+ PIName: "Jane Smith",
395
+ institution: "Science University",
396
+ PIContact: "Jane Smith at 123 456 7890",
397
+ purpose:
398
+ "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.",
399
+ procedures:
400
+ "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.",
401
+ payment:
402
+ "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.",
403
+ };
404
+ ```
405
+
335
406
  ## Trial Recording Extension
336
407
 
337
408
  Trial recording can be added to most jsPsych trials. This is a jsPsych extension
@@ -368,6 +439,13 @@ string, be sure to preload the media files with the `preload` plugin and
368
439
  Use a blank string (`""`) for no message/content. If a value is provided then
369
440
  the `locale` parameter will be ignored.
370
441
 
442
+ **`max_upload_seconds` [ Integer | 10 ]**
443
+
444
+ Maximum duration (in seconds) to wait for the trial recording to finish
445
+ uploading before continuing with the experiment. If the maximum upload duration
446
+ is reached and the upload has not finished, then the experiment will move on and
447
+ the trial recording upload will continue in the background.
448
+
371
449
  ### Examples
372
450
 
373
451
  **Basic usage**
@@ -452,6 +530,27 @@ const trialRec = {
452
530
  };
453
531
  ```
454
532
 
533
+ By default, the trial recording extension will wait up to 10 seconds for the
534
+ recording to finish uploading before moving on to the next trial. If the upload
535
+ does not finish in that time, it will continue to upload in the background. You
536
+ can adjust this duration with the `max_upload_seconds` parameter. You may want
537
+ to increase or decrease this duration depending on the duration of your recorded
538
+ trials, or you expect that some participants may have slow/unreliable internet
539
+ connections. This example decreases the maximum upload duration from 10 to 5
540
+ seconds:
541
+
542
+ ```javascript
543
+ const trialRec = {
544
+ // ... Other trial parameters ...
545
+ extensions: [
546
+ {
547
+ type: chsRecord.TrialRecordExtension,
548
+ params: { max_upload_seconds: 5 },
549
+ },
550
+ ],
551
+ };
552
+ ```
553
+
455
554
  ## Session Recording
456
555
 
457
556
  You might prefer to record across multiple trials in a study session. This can
@@ -480,8 +579,18 @@ const startRec = { type: chsRecord.StartRecordPlugin };
480
579
 
481
580
  #### Parameters
482
581
 
483
- This plugin does not accept any parameters, other those available in all
484
- plugins.
582
+ **`wait_for_connection_message` [`null` or HTML string | `null` ]**
583
+
584
+ This parameter determines what content should be displayed while the session
585
+ recording is initializing. If `null` (the default), then the message
586
+ 'establishing video connection, please wait' (or appropriate translation based
587
+ on `locale`) will be displayed. Otherwise this parameter can be set to a custom
588
+ string and can contain HTML markup. If you want to embed images/video/audio in
589
+ this HTML string, be sure to preload the media files with the `preload` plugin
590
+ and
591
+ [manual preloading](https://www.jspsych.org/latest/overview/media-preloading/#manual-preloading).
592
+ Use a blank string (`""`) for no message/content. If a value is provided then
593
+ the `locale` parameter will be ignored.
485
594
 
486
595
  ### Stop Recording Plugin
487
596
 
@@ -519,6 +628,13 @@ string, be sure to preload the media files with the `preload` plugin and
519
628
  Use a blank string (`""`) for no message/content. If a value is provided then
520
629
  the `locale` parameter will be ignored.
521
630
 
631
+ **`max_upload_seconds` [ Integer | 10 ]**
632
+
633
+ Maximum duration (in seconds) to wait for the session recording to finish
634
+ uploading before continuing with the experiment. If the maximum upload duration
635
+ is reached and the upload has not finished, then the experiment will move on and
636
+ the session recording upload will continue in the background.
637
+
522
638
  ### Examples
523
639
 
524
640
  **Basic usage**
@@ -570,28 +686,55 @@ jsPsych.run([
570
686
 
571
687
  **Setting parameters**
572
688
 
573
- By default, the stop session recording plugin will display "uploading video,
574
- please wait..." while the session recording is uploading. You can set the
575
- `locale` parameter value to translate this message to another language. For
576
- example, the trial below will display the Brazilian Portuguese translation of
577
- this message. If the locale string does not match any of the translation codes
578
- that we support, then the message will be displayed in English.
689
+ By default, the start session recording plugin will display "establishing video
690
+ connection, please wait" while establishing the connection to our video storage,
691
+ and the stop session recording plugin will display "uploading video, please
692
+ wait..." while the session recording is uploading. You can set the `locale`
693
+ parameter value to translate these messages to another language. For example,
694
+ the trial below will display the Brazilian Portuguese translation of these
695
+ messages. If the locale string does not match any of the translation codes that
696
+ we support, then the message will be displayed in English.
579
697
 
580
698
  ```javascript
699
+ const startpRec = {
700
+ type: type: chsRecord.StartRecordPlugin,
701
+ locale: "pt-br",
702
+ };
581
703
  const stopRec = {
582
704
  type: chsRecord.StopRecordPlugin,
583
705
  locale: "pt-br",
584
706
  };
585
707
  ```
586
708
 
587
- You can also set custom content to be displayed while the session recording file
588
- is uploading. The value must be a string. It can include HTML-formatted content,
709
+ You can also set custom content to be displayed at the start of the session
710
+ recording, while it is initializing, and/or at the end, when the file is
711
+ uploading. The value must be a string. It can include HTML-formatted content,
589
712
  which means that you can embed audio, video, images etc. (be sure to preload any
590
713
  media files!).
591
714
 
592
715
  ```javascript
716
+ const startpRec = {
717
+ type: type: chsRecord.StartRecordPlugin,
718
+ wait_for_connection_message: "<p style='color:green;>Please wait...</p>"
719
+ };
593
720
  const stopRec = {
594
721
  type: chsRecord.StopRecordPlugin,
595
722
  wait_for_upload_message: "<p style='color:red;'>Hang on a sec!</p>",
596
723
  };
597
724
  ```
725
+
726
+ By default, the stop session recording plugin will wait up to 10 seconds for the
727
+ session recording to finish uploading before moving on with the experiment. If
728
+ the upload does not finish in that time, it will continue to upload in the
729
+ background. You can adjust this duration with the `max_upload_seconds`
730
+ parameter. You may want to increase this duration if, for example, your
731
+ experiment creates a very long session recording, or you expect that some
732
+ participants may have slow/unreliable internet connections. This example
733
+ increases the maximum upload duration from 10 to 20 seconds:
734
+
735
+ ```javascript
736
+ const stopRec = {
737
+ type: chsRecord.StopRecordPlugin,
738
+ max_upload_seconds: 20,
739
+ };
740
+ ```
@@ -32,7 +32,7 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
32
32
 
33
33
  var _package = {
34
34
  name: "@lookit/record",
35
- version: "4.1.0",
35
+ version: "6.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: {
@@ -73,8 +73,8 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
73
73
  typescript: "^5.6.2"
74
74
  },
75
75
  peerDependencies: {
76
- "@lookit/data": "^0.2.0",
77
- "@lookit/templates": "^2.1.0",
76
+ "@lookit/data": "^0.3.0",
77
+ "@lookit/templates": "^3.1.0",
78
78
  jspsych: "^8.0.3"
79
79
  }
80
80
  };
@@ -149,6 +149,12 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
149
149
  this.name = "S3UndefinedError";
150
150
  }
151
151
  }
152
+ class NoFileNameError extends Error {
153
+ constructor() {
154
+ super("No filename found for recording.");
155
+ this.name = "NoFileNameError";
156
+ }
157
+ }
152
158
  class StreamActiveOnResetError extends Error {
153
159
  constructor() {
154
160
  super("Won't reset recorder. Stream is still active.");
@@ -173,6 +179,12 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
173
179
  this.name = "ElementNotFoundError";
174
180
  }
175
181
  }
182
+ class TimeoutError extends Error {
183
+ constructor(msg) {
184
+ super(`${msg}`);
185
+ this.name = "TimeoutError";
186
+ }
187
+ }
176
188
 
177
189
  // Gets all non-builtin properties up the prototype chain.
178
190
  const getAllProperties = object => {
@@ -8465,6 +8477,31 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8465
8477
 
8466
8478
  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";
8467
8479
 
8480
+ const promiseWithTimeout = (promise, promiseId, timeoutMs, onTimeoutCleanup) => {
8481
+ let timeoutHandle;
8482
+ const timeout = new Promise((resolve) => {
8483
+ timeoutHandle = setTimeout(() => {
8484
+ onTimeoutCleanup?.();
8485
+ resolve("timeout");
8486
+ }, timeoutMs);
8487
+ });
8488
+ return Promise.race([promise, timeout]).then(
8489
+ (value) => {
8490
+ if (value == "timeout") {
8491
+ console.log(`Upload for ${promiseId} timed out.`);
8492
+ } else {
8493
+ console.log(`Upload for ${promiseId} completed.`);
8494
+ clearTimeout(timeoutHandle);
8495
+ }
8496
+ return value;
8497
+ },
8498
+ (err) => {
8499
+ clearTimeout(timeoutHandle);
8500
+ throw err;
8501
+ }
8502
+ );
8503
+ };
8504
+
8468
8505
  class Recorder {
8469
8506
  constructor(jsPsych) {
8470
8507
  this.jsPsych = jsPsych;
@@ -8569,13 +8606,73 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8569
8606
  this.recorder.stop();
8570
8607
  this.stream.getTracks().map((t) => t.stop());
8571
8608
  }
8572
- stop(maintain_container_size = false) {
8609
+ stop({
8610
+ maintain_container_size = false,
8611
+ stop_timeout_ms = null,
8612
+ upload_timeout_ms = 1e4
8613
+ } = {}) {
8614
+ this.preStopCheck();
8573
8615
  this.clearWebcamFeed(maintain_container_size);
8574
8616
  this.stopTracks();
8575
- if (!this.stopPromise) {
8576
- throw new NoStopPromiseError();
8577
- }
8578
- return this.stopPromise;
8617
+ const snapshot = {
8618
+ s3: !this.localDownload ? this.s3 : null,
8619
+ filename: this.filename,
8620
+ localDownload: this.localDownload,
8621
+ url: "null"
8622
+ };
8623
+ const stopped = stop_timeout_ms ? promiseWithTimeout(
8624
+ this.stopPromise,
8625
+ `${snapshot.filename}-stopped`,
8626
+ stop_timeout_ms,
8627
+ this.createTimeoutHandler("stop", snapshot.filename)
8628
+ ) : this.stopPromise;
8629
+ stopped.finally(() => {
8630
+ try {
8631
+ this.reset();
8632
+ } catch (err) {
8633
+ console.error("Error while resetting recorder after stop: ", err);
8634
+ }
8635
+ });
8636
+ const uploadPromise = (async () => {
8637
+ let url;
8638
+ try {
8639
+ url = await stopped;
8640
+ if (url == "timeout") {
8641
+ throw new TimeoutError("Recorder stop timed out.");
8642
+ }
8643
+ } catch (err) {
8644
+ console.warn("Upload failed because recorder stop timed out");
8645
+ throw err;
8646
+ }
8647
+ snapshot.url = url;
8648
+ if (snapshot.localDownload) {
8649
+ try {
8650
+ this.download(snapshot.filename, snapshot.url);
8651
+ await Promise.resolve();
8652
+ } catch (err) {
8653
+ console.error("Local download failed: ", err);
8654
+ throw err;
8655
+ }
8656
+ } else {
8657
+ try {
8658
+ await snapshot.s3.completeUpload();
8659
+ } catch (err) {
8660
+ console.error("Upload failed: ", err);
8661
+ throw err;
8662
+ }
8663
+ }
8664
+ })();
8665
+ const uploaded = upload_timeout_ms ? promiseWithTimeout(
8666
+ uploadPromise,
8667
+ `${snapshot.filename}-uploaded`,
8668
+ upload_timeout_ms,
8669
+ this.createTimeoutHandler("upload", snapshot.filename)
8670
+ ) : uploadPromise;
8671
+ window.chs.pendingUploads.push({
8672
+ promise: uploadPromise,
8673
+ file: snapshot.filename
8674
+ });
8675
+ return { stopped, uploaded };
8579
8676
  }
8580
8677
  initializeCheck() {
8581
8678
  if (!this.recorder) {
@@ -8588,19 +8685,27 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8588
8685
  throw new StreamDataInitializeError();
8589
8686
  }
8590
8687
  }
8688
+ preStopCheck() {
8689
+ if (!this.recorder) {
8690
+ throw new RecorderInitializeError();
8691
+ }
8692
+ if (!this.stream.active) {
8693
+ throw new StreamInactiveInitializeError();
8694
+ }
8695
+ if (!this.stopPromise) {
8696
+ throw new NoStopPromiseError();
8697
+ }
8698
+ if (!this.filename) {
8699
+ throw new NoFileNameError();
8700
+ }
8701
+ }
8591
8702
  handleStop(resolve) {
8592
- return async () => {
8703
+ return () => {
8593
8704
  if (this.blobs.length === 0) {
8594
8705
  throw new CreateURLError();
8595
8706
  }
8596
8707
  this.url = URL.createObjectURL(new Blob(this.blobs));
8597
- if (this.localDownload) {
8598
- this.download();
8599
- } else {
8600
- await this.s3.completeUpload();
8601
- }
8602
- this.reset();
8603
- resolve();
8708
+ resolve(this.url);
8604
8709
  };
8605
8710
  }
8606
8711
  handleDataAvailable(event) {
@@ -8609,11 +8714,11 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8609
8714
  this.s3.onDataAvailable(event.data);
8610
8715
  }
8611
8716
  }
8612
- download() {
8613
- if (this.filename && this.url) {
8717
+ download(filename, url) {
8718
+ if (filename && url) {
8614
8719
  const link = document.createElement("a");
8615
- link.href = this.url;
8616
- link.download = this.filename;
8720
+ link.href = url;
8721
+ link.download = filename;
8617
8722
  link.click();
8618
8723
  }
8619
8724
  }
@@ -8642,13 +8747,33 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8642
8747
  const rand_digits = Math.floor(Math.random() * 1e3);
8643
8748
  return `${prefix}_${window.chs.study.id}_${trial_id}_${window.chs.response.id}_${new Date().getTime()}_${rand_digits}.webm`;
8644
8749
  }
8750
+ createTimeoutHandler(eventName, id) {
8751
+ return () => {
8752
+ console.warn(`Recorder ${eventName} timed out: ${id}`);
8753
+ if (!this.stream.active) {
8754
+ try {
8755
+ this.reset();
8756
+ } catch (err) {
8757
+ console.error("Error while resetting recorder after timeout: ", err);
8758
+ }
8759
+ }
8760
+ };
8761
+ }
8645
8762
  }
8646
8763
 
8647
8764
  const info$3 = {
8648
8765
  name: "consent-video",
8649
8766
  version: _package.version,
8650
8767
  parameters: {
8651
- template: { type: jspsych.ParameterType.STRING, default: "consent-template-5" },
8768
+ template: {
8769
+ type: jspsych.ParameterType.SELECT,
8770
+ options: [
8771
+ "consent-template-5",
8772
+ "consent-garden",
8773
+ "consent-recording-only"
8774
+ ],
8775
+ default: "consent-template-5"
8776
+ },
8652
8777
  locale: { type: jspsych.ParameterType.STRING, default: "en-us" },
8653
8778
  additional_video_privacy_statement: {
8654
8779
  type: jspsych.ParameterType.STRING,
@@ -8693,7 +8818,8 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8693
8818
  prompt_all_adults: { type: jspsych.ParameterType.BOOL, default: false },
8694
8819
  prompt_only_adults: { type: jspsych.ParameterType.BOOL, default: false },
8695
8820
  consent_statement_text: { type: jspsych.ParameterType.STRING, default: "" },
8696
- omit_injury_phrase: { type: jspsych.ParameterType.BOOL, default: false }
8821
+ omit_injury_phrase: { type: jspsych.ParameterType.BOOL, default: false },
8822
+ only_consent_on_chs: { type: jspsych.ParameterType.BOOL, default: false }
8697
8823
  },
8698
8824
  data: {
8699
8825
  chs_type: {
@@ -8828,7 +8954,10 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8828
8954
  stop.addEventListener("click", async () => {
8829
8955
  stop.disabled = true;
8830
8956
  this.addMessage(display, this.uploadingMsg);
8831
- await this.recorder.stop(true);
8957
+ const { stopped, uploaded } = this.recorder.stop({
8958
+ maintain_container_size: true
8959
+ });
8960
+ await stopped;
8832
8961
  this.recordFeed(display);
8833
8962
  this.getImg(display, "record-icon").style.visibility = "hidden";
8834
8963
  this.addMessage(display, this.notRecordingMsg);
@@ -8854,7 +8983,16 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8854
8983
  const info$2 = {
8855
8984
  name: "start-record-plugin",
8856
8985
  version: _package.version,
8857
- parameters: {},
8986
+ parameters: {
8987
+ wait_for_connection_message: {
8988
+ type: jspsych.ParameterType.HTML_STRING,
8989
+ default: null
8990
+ },
8991
+ locale: {
8992
+ type: jspsych.ParameterType.STRING,
8993
+ default: "en-us"
8994
+ }
8995
+ },
8858
8996
  data: {}
8859
8997
  };
8860
8998
  class StartRecordPlugin {
@@ -8869,8 +9007,14 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8869
9007
  }
8870
9008
  static info = info$2;
8871
9009
  recorder;
8872
- trial() {
8873
- this.recorder.start(false, `${StartRecordPlugin.info.name}-multiframe`).then(() => {
9010
+ async trial(display_element, trial) {
9011
+ if (trial.wait_for_connection_message == null) {
9012
+ display_element.innerHTML = chsTemplates.establishingConnection(trial);
9013
+ } else {
9014
+ display_element.innerHTML = trial.wait_for_connection_message;
9015
+ }
9016
+ await this.recorder.start(false, `${StartRecordPlugin.info.name}-multiframe`).then(() => {
9017
+ display_element.innerHTML = "";
8874
9018
  this.jsPsych.finishTrial();
8875
9019
  });
8876
9020
  }
@@ -8887,6 +9031,10 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8887
9031
  locale: {
8888
9032
  type: jspsych.ParameterType.STRING,
8889
9033
  default: "en-us"
9034
+ },
9035
+ max_upload_seconds: {
9036
+ type: jspsych.ParameterType.INT,
9037
+ default: 10
8890
9038
  }
8891
9039
  },
8892
9040
  data: {}
@@ -8902,19 +9050,25 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8902
9050
  }
8903
9051
  static info = info$1;
8904
9052
  recorder;
8905
- trial(display_element, trial) {
9053
+ async trial(display_element, trial) {
8906
9054
  if (trial.wait_for_upload_message == null) {
8907
9055
  display_element.innerHTML = chsTemplates.uploadingVideo(trial);
8908
9056
  } else {
8909
9057
  display_element.innerHTML = trial.wait_for_upload_message;
8910
9058
  }
8911
- this.recorder.stop().then(() => {
9059
+ const { stopped, uploaded } = this.recorder.stop({
9060
+ upload_timeout_ms: trial.max_upload_seconds !== null ? trial.max_upload_seconds * 1e3 : null
9061
+ });
9062
+ try {
9063
+ await stopped;
9064
+ await uploaded;
9065
+ } catch (err) {
9066
+ console.error("StopRecordPlugin: recorder stop/upload failed.", err);
9067
+ } finally {
8912
9068
  window.chs.sessionRecorder = null;
8913
9069
  display_element.innerHTML = "";
8914
9070
  this.jsPsych.finishTrial();
8915
- }).catch((err) => {
8916
- console.error("StopRecordPlugin: recorder stop/upload failed.", err);
8917
- });
9071
+ }
8918
9072
  }
8919
9073
  }
8920
9074
 
@@ -8932,12 +9086,12 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8932
9086
  pluginName;
8933
9087
  uploadMsg = null;
8934
9088
  locale = "en-us";
9089
+ maxUploadSeconds = void 0;
8935
9090
  async initialize(params) {
8936
9091
  await new Promise((resolve) => {
8937
9092
  this.uploadMsg = params?.wait_for_upload_message ? params.wait_for_upload_message : null;
8938
9093
  this.locale = params?.locale ? params.locale : "en-us";
8939
- console.log(this.uploadMsg);
8940
- console.log(this.locale);
9094
+ this.maxUploadSeconds = params?.max_upload_seconds === void 0 ? 10 : params.max_upload_seconds;
8941
9095
  resolve();
8942
9096
  });
8943
9097
  }
@@ -8948,13 +9102,14 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8948
9102
  if (startParams?.locale) {
8949
9103
  this.locale = startParams.locale;
8950
9104
  }
8951
- console.log(this.uploadMsg);
8952
- console.log(this.locale);
9105
+ if (startParams?.max_upload_seconds !== void 0) {
9106
+ this.maxUploadSeconds = startParams?.max_upload_seconds;
9107
+ }
8953
9108
  this.recorder = new Recorder(this.jsPsych);
9109
+ this.pluginName = this.getCurrentPluginName();
9110
+ this.recorder.start(false, `${this.pluginName}`);
8954
9111
  }
8955
9112
  on_load() {
8956
- this.pluginName = this.getCurrentPluginName();
8957
- this.recorder?.start(false, `${this.pluginName}`);
8958
9113
  }
8959
9114
  async on_finish() {
8960
9115
  const displayEl = this.jsPsych.getDisplayElement();
@@ -8966,13 +9121,27 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
8966
9121
  } else {
8967
9122
  displayEl.innerHTML = this.uploadMsg;
8968
9123
  }
8969
- try {
8970
- await this.recorder?.stop();
9124
+ if (this.recorder) {
9125
+ const { stopped, uploaded } = this.recorder.stop({
9126
+ upload_timeout_ms: this.maxUploadSeconds !== null ? this.maxUploadSeconds * 1e3 : null
9127
+ });
9128
+ try {
9129
+ await stopped;
9130
+ await uploaded;
9131
+ displayEl.innerHTML = "";
9132
+ return {};
9133
+ } catch (err) {
9134
+ console.error(
9135
+ "TrialRecordExtension: recorder stop/upload failed.",
9136
+ err
9137
+ );
9138
+ displayEl.innerHTML = "";
9139
+ return {};
9140
+ }
9141
+ } else {
8971
9142
  displayEl.innerHTML = "";
8972
- } catch (err) {
8973
- console.error("TrialRecordExtension: recorder stop/upload failed.", err);
9143
+ return {};
8974
9144
  }
8975
- return {};
8976
9145
  }
8977
9146
  getCurrentPluginName() {
8978
9147
  const current_plugin_class = this.jsPsych.getCurrentTrial().type;
@@ -9383,4 +9552,4 @@ var chsRecord = (function (Data, chsTemplates, jspsych) {
9383
9552
  return index;
9384
9553
 
9385
9554
  })(chsData, chsTemplates, jsPsychModule);
9386
- //# sourceMappingURL=https://unpkg.com/@lookit/record@4.1.0/dist/index.browser.js.map
9555
+ //# sourceMappingURL=https://unpkg.com/@lookit/record@6.0.0/dist/index.browser.js.map