@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/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: "3.0.0",
11
+ version: "4.0.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.0.0",
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 VideoContainerNotFoundError extends Error {
147
- constructor() {
148
- super("Video Container could not be found.");
149
- this.name = "VideoContainerError";
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
 
@@ -177,8 +166,10 @@ class Recorder {
177
166
  this.blobs = [];
178
167
  this.localDownload = "false"?.toLowerCase() === "true";
179
168
  this.webcam_element_id = "lookit-jspsych-webcam";
169
+ this.mimeType = "video/webm";
180
170
  this.streamClone = this.stream.clone();
181
171
  autoBind(this);
172
+ this.mimeType = this.recorder?.mimeType || this.mimeType;
182
173
  }
183
174
  get recorder() {
184
175
  return this.jsPsych.pluginAPI.getCameraRecorder() || this.jsPsych.pluginAPI.getMicrophoneRecorder();
@@ -196,7 +187,11 @@ class Recorder {
196
187
  this._s3 = value;
197
188
  }
198
189
  initializeRecorder(stream, opts) {
199
- this.jsPsych.pluginAPI.initializeCameraRecorder(stream, opts);
190
+ const recorder_options = {
191
+ ...opts,
192
+ mimeType: this.mimeType
193
+ };
194
+ this.jsPsych.pluginAPI.initializeCameraRecorder(stream, recorder_options);
200
195
  }
201
196
  reset() {
202
197
  if (this.stream.active) {
@@ -264,9 +259,9 @@ class Recorder {
264
259
  this.recorder.stop();
265
260
  this.stream.getTracks().map((t) => t.stop());
266
261
  }
267
- stop() {
262
+ stop(maintain_container_size = false) {
263
+ this.clearWebcamFeed(maintain_container_size);
268
264
  this.stopTracks();
269
- this.clearWebcamFeed();
270
265
  if (!this.stopPromise) {
271
266
  throw new NoStopPromiseError();
272
267
  }
@@ -311,11 +306,20 @@ class Recorder {
311
306
  link.click();
312
307
  }
313
308
  }
314
- clearWebcamFeed() {
309
+ clearWebcamFeed(maintain_container_size) {
315
310
  const webcam_feed_element = document.querySelector(
316
311
  `#${this.webcam_element_id}`
317
312
  );
318
313
  if (webcam_feed_element) {
314
+ if (maintain_container_size) {
315
+ const parent_div = webcam_feed_element.parentElement;
316
+ if (parent_div) {
317
+ const width = webcam_feed_element.offsetWidth;
318
+ const height = webcam_feed_element.offsetHeight;
319
+ parent_div.style.height = `${height}px`;
320
+ parent_div.style.width = `${width}px`;
321
+ }
322
+ }
319
323
  webcam_feed_element.remove();
320
324
  }
321
325
  }
@@ -385,6 +389,11 @@ const _VideoConsentPlugin = class {
385
389
  constructor(jsPsych) {
386
390
  this.jsPsych = jsPsych;
387
391
  this.video_container_id = "lookit-jspsych-video-container";
392
+ this.msg_container_id = "lookit-jspsych-video-msg-container";
393
+ this.uploadingMsg = null;
394
+ this.startingMsg = null;
395
+ this.recordingMsg = null;
396
+ this.notRecordingMsg = null;
388
397
  this.jsPsych = jsPsych;
389
398
  this.recorder = new Recorder(this.jsPsych);
390
399
  }
@@ -396,13 +405,25 @@ const _VideoConsentPlugin = class {
396
405
  this.stopButton(display);
397
406
  this.playButton(display);
398
407
  this.nextButton(display);
408
+ this.uploadingMsg = chsTemplates.translateString(
409
+ "exp-lookit-video-consent.Stopping-and-uploading"
410
+ );
411
+ this.startingMsg = chsTemplates.translateString(
412
+ "exp-lookit-video-consent.Starting-recorder"
413
+ );
414
+ this.recordingMsg = chsTemplates.translateString(
415
+ "exp-lookit-video-consent.Recording"
416
+ );
417
+ this.notRecordingMsg = chsTemplates.translateString(
418
+ "exp-lookit-video-consent.Not-recording"
419
+ );
399
420
  }
400
421
  getVideoContainer(display) {
401
422
  const videoContainer = display.querySelector(
402
423
  `div#${this.video_container_id}`
403
424
  );
404
425
  if (!videoContainer) {
405
- throw new VideoContainerNotFoundError();
426
+ throw new ElementNotFoundError(this.video_container_id, "div");
406
427
  }
407
428
  return videoContainer;
408
429
  }
@@ -413,14 +434,31 @@ const _VideoConsentPlugin = class {
413
434
  }
414
435
  playbackFeed(display) {
415
436
  const videoContainer = this.getVideoContainer(display);
416
- this.recorder.insertPlaybackFeed(videoContainer, this.onEnded(display));
437
+ this.recorder.insertPlaybackFeed(
438
+ videoContainer,
439
+ this.onPlaybackEnded(display)
440
+ );
417
441
  }
418
- onEnded(display) {
442
+ getMessageContainer(display) {
443
+ const msgContainer = display.querySelector(
444
+ `div#${this.msg_container_id}`
445
+ );
446
+ if (!msgContainer) {
447
+ throw new ElementNotFoundError(this.msg_container_id, "div");
448
+ }
449
+ return msgContainer;
450
+ }
451
+ addMessage(display, message) {
452
+ const msgContainer = this.getMessageContainer(display);
453
+ msgContainer.innerHTML = message;
454
+ }
455
+ onPlaybackEnded(display) {
419
456
  return () => {
420
457
  const next = this.getButton(display, "next");
421
458
  const play = this.getButton(display, "play");
422
459
  const record = this.getButton(display, "record");
423
460
  this.recordFeed(display);
461
+ this.addMessage(display, this.notRecordingMsg);
424
462
  next.disabled = false;
425
463
  play.disabled = false;
426
464
  record.disabled = false;
@@ -429,14 +467,14 @@ const _VideoConsentPlugin = class {
429
467
  getButton(display, id) {
430
468
  const btn = display.querySelector(`button#${id}`);
431
469
  if (!btn) {
432
- throw new ButtonNotFoundError(id);
470
+ throw new ElementNotFoundError(id, "button");
433
471
  }
434
472
  return btn;
435
473
  }
436
474
  getImg(display, id) {
437
475
  const img = display.querySelector(`img#${id}`);
438
476
  if (!img) {
439
- throw new ImageNotFoundError(id);
477
+ throw new ElementNotFoundError(id, "img");
440
478
  }
441
479
  return img;
442
480
  }
@@ -446,12 +484,14 @@ const _VideoConsentPlugin = class {
446
484
  const play = this.getButton(display, "play");
447
485
  const next = this.getButton(display, "next");
448
486
  record.addEventListener("click", async () => {
487
+ this.addMessage(display, this.startingMsg);
449
488
  record.disabled = true;
450
- stop.disabled = false;
451
489
  play.disabled = true;
452
490
  next.disabled = true;
453
- this.getImg(display, "record-icon").style.visibility = "visible";
454
491
  await this.recorder.start(true, _VideoConsentPlugin.info.name);
492
+ this.getImg(display, "record-icon").style.visibility = "visible";
493
+ this.addMessage(display, this.recordingMsg);
494
+ stop.disabled = false;
455
495
  });
456
496
  }
457
497
  playButton(display) {
@@ -469,11 +509,14 @@ const _VideoConsentPlugin = class {
469
509
  const play = this.getButton(display, "play");
470
510
  stop.addEventListener("click", async () => {
471
511
  stop.disabled = true;
472
- record.disabled = false;
473
- play.disabled = false;
474
- await this.recorder.stop();
512
+ this.addMessage(display, this.uploadingMsg);
513
+ await this.recorder.stop(true);
475
514
  this.recorder.reset();
476
515
  this.recordFeed(display);
516
+ this.getImg(display, "record-icon").style.visibility = "hidden";
517
+ this.addMessage(display, this.notRecordingMsg);
518
+ play.disabled = false;
519
+ record.disabled = false;
477
520
  });
478
521
  }
479
522
  nextButton(display) {
@@ -640,6 +683,7 @@ class VideoConfigPlugin {
640
683
  this.minVolume = 0.1;
641
684
  this.micChecked = false;
642
685
  this.processorNode = null;
686
+ this.mimeType = "video/webm";
643
687
  this.addHtmlContent = (trial) => {
644
688
  this.display_el.innerHTML = chsTemplates.videoConfig(trial, html_params);
645
689
  };
@@ -782,7 +826,12 @@ class VideoConfigPlugin {
782
826
  return { cameras: unique_cameras, mics: unique_mics };
783
827
  };
784
828
  this.initializeAndCreateRecorder = (stream, opts) => {
785
- this.jsPsych.pluginAPI.initializeCameraRecorder(stream, opts);
829
+ this.mimeType = this.getCompatibleMimeType() || this.mimeType;
830
+ const recorder_options = {
831
+ ...opts,
832
+ mimeType: this.mimeType
833
+ };
834
+ this.jsPsych.pluginAPI.initializeCameraRecorder(stream, recorder_options);
786
835
  this.recorder = new Recorder(this.jsPsych);
787
836
  };
788
837
  this.checkMic = async (minVol = this.minVolume) => {
@@ -935,6 +984,20 @@ class VideoConfigPlugin {
935
984
  };
936
985
  });
937
986
  }
987
+ getCompatibleMimeType() {
988
+ const mime_types = [
989
+ "video/webm;codecs=vp9,opus",
990
+ "video/webm;codecs=vp8,opus"
991
+ ];
992
+ let mime_type_index = 0;
993
+ while (mime_type_index < mime_types.length) {
994
+ if (MediaRecorder.isTypeSupported(mime_types[mime_type_index])) {
995
+ return mime_types[mime_type_index];
996
+ }
997
+ mime_type_index++;
998
+ }
999
+ return null;
1000
+ }
938
1001
  }
939
1002
  VideoConfigPlugin.info = info;
940
1003