@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/src/recorder.ts CHANGED
@@ -109,7 +109,13 @@ export default class Recorder {
109
109
  this.jsPsych.pluginAPI.initializeCameraRecorder(stream, recorder_options);
110
110
  }
111
111
 
112
- /** Reset the recorder to be used again. */
112
+ /**
113
+ * Reset the recorder. This is used internally after stopping/uploading a
114
+ * recording, in order to create a new active stream that can be used by a new
115
+ * Recorder instance. This can also be used by the consuming plugin/extension
116
+ * when a recorder needs to be reset without the stop/upload events (e.g. in
117
+ * the video config plugin).
118
+ */
113
119
  public reset() {
114
120
  if (this.stream.active) {
115
121
  throw new StreamActiveOnResetError();
@@ -276,13 +282,19 @@ export default class Recorder {
276
282
  * tracks, clear the webcam feed element (if there is one), and return the
277
283
  * stop promise. This should only be called after recording has started.
278
284
  *
285
+ * @param maintain_container_size - Optional boolean indicating whether or not
286
+ * to maintain the current size of the webcam feed container when removing
287
+ * the video element. Default is false. If true, the container will be
288
+ * resized to match the dimensions of the video element before it is
289
+ * removed. This is useful for avoiding layout jumps when the webcam
290
+ * container will be re-used during the trial.
279
291
  * @returns Promise that resolves after the media recorder has stopped and
280
292
  * final 'dataavailable' event has occurred, when the "stop" event-related
281
293
  * callback function is called.
282
294
  */
283
- public stop() {
295
+ public stop(maintain_container_size: boolean = false) {
296
+ this.clearWebcamFeed(maintain_container_size);
284
297
  this.stopTracks();
285
- this.clearWebcamFeed();
286
298
 
287
299
  if (!this.stopPromise) {
288
300
  throw new NoStopPromiseError();
@@ -326,6 +338,8 @@ export default class Recorder {
326
338
  } else {
327
339
  await this.s3.completeUpload();
328
340
  }
341
+ // Reset the recorder. This is necessary to create another active media stream from the stream clone, because the current stream is fully stopped/inactive and cannot be used again.
342
+ this.reset();
329
343
 
330
344
  resolve();
331
345
  };
@@ -353,12 +367,30 @@ export default class Recorder {
353
367
  }
354
368
  }
355
369
 
356
- /** Private helper to clear the webcam feed, if there is one. */
357
- private clearWebcamFeed() {
370
+ /**
371
+ * Private helper to clear the webcam feed, if there is one. If remove is
372
+ * false, the video element source attribute is cleared and the parent div
373
+ * will be set to the same dimensions. This is useful for avoiding layout
374
+ * jumps when the webcam container and video element will be re-used during
375
+ * the trial.
376
+ *
377
+ * @param maintain_container_size - Boolean indicating whether or not to set
378
+ * the webcam feed container size before removing the video element
379
+ */
380
+ private clearWebcamFeed(maintain_container_size: boolean) {
358
381
  const webcam_feed_element = document.querySelector(
359
382
  `#${this.webcam_element_id}`,
360
383
  ) as HTMLVideoElement;
361
384
  if (webcam_feed_element) {
385
+ if (maintain_container_size) {
386
+ const parent_div = webcam_feed_element.parentElement as HTMLDivElement;
387
+ if (parent_div) {
388
+ const width = webcam_feed_element.offsetWidth;
389
+ const height = webcam_feed_element.offsetHeight;
390
+ parent_div.style.height = `${height}px`;
391
+ parent_div.style.width = `${width}px`;
392
+ }
393
+ }
362
394
  webcam_feed_element.remove();
363
395
  }
364
396
  }
package/src/start.ts CHANGED
@@ -1,11 +1,17 @@
1
1
  import { LookitWindow } from "@lookit/data/dist/types";
2
2
  import { JsPsych, JsPsychPlugin } from "jspsych";
3
+ import { version } from "../package.json";
3
4
  import { ExistingRecordingError } from "./errors";
4
5
  import Recorder from "./recorder";
5
6
 
6
7
  declare let window: LookitWindow;
7
8
 
8
- const info = <const>{ name: "start-record-plugin", parameters: {} };
9
+ const info = <const>{
10
+ name: "start-record-plugin",
11
+ version,
12
+ parameters: {},
13
+ data: {},
14
+ };
9
15
  type Info = typeof info;
10
16
 
11
17
  /** Start recording. Used by researchers who want to record across trials. */
package/src/stop.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { LookitWindow } from "@lookit/data/dist/types";
2
2
  import chsTemplates from "@lookit/templates";
3
3
  import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from "jspsych";
4
+ import { version } from "../package.json";
4
5
  import { NoSessionRecordingError } from "./errors";
5
6
  import Recorder from "./recorder";
6
7
 
@@ -8,9 +9,32 @@ declare let window: LookitWindow;
8
9
 
9
10
  const info = <const>{
10
11
  name: "stop-record-plugin",
12
+ version,
11
13
  parameters: {
12
- locale: { type: ParameterType.STRING, default: "en-us" },
14
+ /**
15
+ * This string can contain HTML markup. Any content provided will be
16
+ * displayed while the recording is uploading. If null (the default), then
17
+ * the default 'uploading video, please wait' (or appropriate translation
18
+ * based on 'locale') will be displayed. Use a blank string for no
19
+ * message/content.
20
+ */
21
+ wait_for_upload_message: {
22
+ type: ParameterType.HTML_STRING,
23
+ default: null as null | string,
24
+ },
25
+ /**
26
+ * Locale code used for translating the default 'uploading video, please
27
+ * wait' message. This code must be present in the translation files. If the
28
+ * code is not found then English will be used. If the
29
+ * 'wait_for_upload_message' parameter is specified then this value is
30
+ * ignored.
31
+ */
32
+ locale: {
33
+ type: ParameterType.STRING,
34
+ default: "en-us",
35
+ },
13
36
  },
37
+ data: {},
14
38
  };
15
39
  type Info = typeof info;
16
40
 
@@ -41,11 +65,21 @@ export default class StopRecordPlugin implements JsPsychPlugin<Info> {
41
65
  * @param trial - Trial object with parameters/values.
42
66
  */
43
67
  public trial(display_element: HTMLElement, trial: TrialType<Info>): void {
44
- display_element.innerHTML = chsTemplates.uploadingVideo(trial);
45
- this.recorder.stop().then(() => {
46
- window.chs.sessionRecorder = null;
47
- display_element.innerHTML = "";
48
- this.jsPsych.finishTrial();
49
- });
68
+ if (trial.wait_for_upload_message == null) {
69
+ display_element.innerHTML = chsTemplates.uploadingVideo(trial);
70
+ } else {
71
+ display_element.innerHTML = trial.wait_for_upload_message;
72
+ }
73
+ this.recorder
74
+ .stop()
75
+ .then(() => {
76
+ window.chs.sessionRecorder = null;
77
+ display_element.innerHTML = "";
78
+ this.jsPsych.finishTrial();
79
+ })
80
+ .catch((err) => {
81
+ console.error("StopRecordPlugin: recorder stop/upload failed.", err);
82
+ // TO DO: display translated error msg and/or researcher contact info
83
+ });
50
84
  }
51
85
  }
package/src/trial.ts CHANGED
@@ -1,16 +1,55 @@
1
+ import chsTemplates from "@lookit/templates";
1
2
  import autoBind from "auto-bind";
2
- import { JsPsych, JsPsychExtension, JsPsychExtensionInfo } from "jspsych";
3
+ import {
4
+ JsPsych,
5
+ JsPsychExtension,
6
+ JsPsychExtensionInfo,
7
+ PluginInfo,
8
+ TrialType,
9
+ } from "jspsych";
10
+ import { version } from "../package.json";
3
11
  import Recorder from "./recorder";
4
12
  import { jsPsychPluginWithInfo } from "./types";
5
13
 
6
- /** This extension will allow reasearchers to record trials. */
14
+ // JsPsychExtensionInfo does not allow parameters, so we define them as interfaces and use these to type the arguments passed to extension initialize and on_start functions.
15
+ interface Parameters {
16
+ /**
17
+ * Content that should be displayed while the recording is uploading. If null
18
+ * (the default), then the default 'uploading video, please wait...' (or
19
+ * appropriate translation based on 'locale') will be displayed. Use a blank
20
+ * string for no message/content. Otherwise this parameter can be set to a
21
+ * custom string and can contain HTML markup. If you want to embed
22
+ * images/video/audio in this HTML string, be sure to preload the media files
23
+ * with the `preload` plugin and manual preloading. Use a blank string (`""`)
24
+ * for no message/content.
25
+ *
26
+ * @default null
27
+ */
28
+ wait_for_upload_message?: null | string;
29
+ /**
30
+ * Locale code used for translating the default 'uploading video, please
31
+ * wait...' message. This code must be present in the translation files. If
32
+ * the code is not found then English will be used. If the
33
+ * 'wait_for_upload_message' parameter is specified then this value
34
+ * isignored.
35
+ *
36
+ * @default "en-us"
37
+ */
38
+ locale?: string;
39
+ }
40
+
41
+ /** This extension allows researchers to record webcam audio/video during trials. */
7
42
  export default class TrialRecordExtension implements JsPsychExtension {
8
43
  public static readonly info: JsPsychExtensionInfo = {
9
44
  name: "chs-trial-record-extension",
45
+ version,
46
+ data: {},
10
47
  };
11
48
 
12
49
  private recorder?: Recorder;
13
50
  private pluginName: string | undefined;
51
+ private uploadMsg: null | string = null;
52
+ private locale: string = "en-us";
14
53
 
15
54
  /**
16
55
  * Video recording extension.
@@ -22,29 +61,76 @@ export default class TrialRecordExtension implements JsPsychExtension {
22
61
  }
23
62
 
24
63
  /**
25
- * Ran on the initialize step for extensions, called when an instance of
64
+ * Runs on the initialize step for extensions, called when an instance of
26
65
  * jsPsych is first initialized through initJsPsych().
66
+ *
67
+ * @param params - Parameters object
68
+ * @param params.wait_for_upload_message - Message to display while waiting
69
+ * for upload. String or null (default)
70
+ * @param params.locale - Message to display while waiting for upload. String
71
+ * or null (default).
27
72
  */
28
- public async initialize() {}
73
+ public async initialize(params?: Parameters) {
74
+ await new Promise<void>((resolve) => {
75
+ this.uploadMsg = params?.wait_for_upload_message
76
+ ? params.wait_for_upload_message
77
+ : null;
78
+ this.locale = params?.locale ? params.locale : "en-us";
79
+ console.log(this.uploadMsg);
80
+ console.log(this.locale);
81
+ resolve();
82
+ });
83
+ }
29
84
 
30
- /** Ran at the start of a trial. */
31
- public on_start() {
85
+ /**
86
+ * Runs at the start of a trial.
87
+ *
88
+ * @param startParams - Parameters object
89
+ * @param startParams.wait_for_upload_message - Message to display while
90
+ * waiting for upload. String or null (default). If given, this will
91
+ * overwrite the value used during initialization.
92
+ */
93
+ public on_start(startParams?: Parameters) {
94
+ if (startParams?.wait_for_upload_message) {
95
+ this.uploadMsg = startParams.wait_for_upload_message;
96
+ }
97
+ if (startParams?.locale) {
98
+ this.locale = startParams.locale;
99
+ }
100
+ console.log(this.uploadMsg);
101
+ console.log(this.locale);
32
102
  this.recorder = new Recorder(this.jsPsych);
33
103
  }
34
104
 
35
- /** Ran when the trial has loaded. */
105
+ /** Runs when the trial has loaded. */
36
106
  public on_load() {
37
107
  this.pluginName = this.getCurrentPluginName();
38
108
  this.recorder?.start(false, `${this.pluginName}`);
39
109
  }
40
110
 
41
111
  /**
42
- * Ran when trial has finished.
112
+ * Runs when trial has finished.
43
113
  *
44
- * @returns Trial data.
114
+ * @returns Any data from the trial extension that should be added to the rest
115
+ * of the trial data.
45
116
  */
46
- public on_finish() {
47
- this.recorder?.stop();
117
+ public async on_finish() {
118
+ const displayEl = this.jsPsych.getDisplayElement();
119
+ if (this.uploadMsg == null) {
120
+ displayEl.innerHTML = chsTemplates.uploadingVideo({
121
+ type: this.jsPsych.getCurrentTrial().type,
122
+ locale: this.locale,
123
+ } as TrialType<PluginInfo>);
124
+ } else {
125
+ displayEl.innerHTML = this.uploadMsg;
126
+ }
127
+ try {
128
+ await this.recorder?.stop();
129
+ displayEl.innerHTML = "";
130
+ } catch (err) {
131
+ console.error("TrialRecordExtension: recorder stop/upload failed.", err);
132
+ // TO DO: display translated error msg and/or researcher contact info
133
+ }
48
134
  return {};
49
135
  }
50
136