From f4c8ab531ad47143c7c5e5b3006eef2303f4cc8e Mon Sep 17 00:00:00 2001 From: CJ Green <44074998+okaycj@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:16:38 -0400 Subject: [PATCH] Add replace webcam feed function with record feed function --- packages/record/src/consentVideo.ts | 66 ++++++-------- packages/record/src/recorder.ts | 89 +++++++++++++------ packages/record/src/utils.ts | 7 +- .../record/templates/consent-document.hbs | 4 +- .../record/templates/playback-feed.mustache | 2 +- packages/record/templates/record-feed.hbs | 10 +++ 6 files changed, 106 insertions(+), 72 deletions(-) create mode 100644 packages/record/templates/record-feed.hbs diff --git a/packages/record/src/consentVideo.ts b/packages/record/src/consentVideo.ts index 33aa115e..c90da041 100644 --- a/packages/record/src/consentVideo.ts +++ b/packages/record/src/consentVideo.ts @@ -19,32 +19,36 @@ const info = { version, parameters: { template: { type: ParameterType.STRING, default: "consent_005" }, - additional_video_privacy_statement: { type: ParameterType.STRING }, - datause: { type: ParameterType.STRING }, + locale: { type: ParameterType.STRING, default: "en-us" }, + additional_video_privacy_statement: { + type: ParameterType.STRING, + default: "", + }, + datause: { type: ParameterType.STRING, default: "" }, gdpr: { type: ParameterType.BOOL, default: false }, - gdpr_personal_data: { type: ParameterType.STRING }, - gdpr_sensitive_data: { type: ParameterType.STRING }, - PIName: { type: ParameterType.STRING }, + gdpr_personal_data: { type: ParameterType.STRING, default: "" }, + gdpr_sensitive_data: { type: ParameterType.STRING, default: "" }, + PIName: { type: ParameterType.STRING, default: "" }, include_databrary: { type: ParameterType.BOOL, default: false }, - institution: { type: ParameterType.STRING }, - PIContact: { type: ParameterType.STRING }, - payment: { type: ParameterType.STRING }, + institution: { type: ParameterType.STRING, default: "" }, + PIContact: { type: ParameterType.STRING, default: "" }, + payment: { type: ParameterType.STRING, default: "" }, private_level_only: { type: ParameterType.BOOL, default: false }, - procedures: { type: ParameterType.STRING }, + procedures: { type: ParameterType.STRING, default: "" }, purpose: { type: ParameterType.STRING, default: "" }, - research_rights_statement: { type: ParameterType.STRING }, - risk_statement: { type: ParameterType.STRING }, - voluntary_participation: { type: ParameterType.STRING }, + research_rights_statement: { type: ParameterType.STRING, default: "" }, + risk_statement: { type: ParameterType.STRING, default: "" }, + voluntary_participation: { type: ParameterType.STRING, default: "" }, purpose_header: { type: ParameterType.STRING, default: "" }, procedures_header: { type: ParameterType.STRING, default: "" }, participation_header: { type: ParameterType.STRING, default: "" }, benefits_header: { type: ParameterType.STRING, default: "" }, risk_header: { type: ParameterType.STRING, default: "" }, - summary_statement: { type: ParameterType.STRING }, + summary_statement: { type: ParameterType.STRING, default: "" }, additional_segments: { type: ParameterType.COMPLEX }, prompt_all_adults: { type: ParameterType.BOOL, default: false }, prompt_only_adults: { type: ParameterType.BOOL, default: false }, - consent_statement_text: { type: ParameterType.STRING }, + consent_statement_text: { type: ParameterType.STRING, default: "" }, omit_injury_phrase: { type: ParameterType.BOOL, default: false }, }, }; @@ -99,7 +103,7 @@ export class VideoConsentPlugin implements JsPsychPlugin { display.insertAdjacentHTML("afterbegin", consentVideoTrial); // Video recording HTML - this.webcamFeed(display); + this.recordFeed(display); this.recordButton(display); this.stopButton(display); this.playButton(display); @@ -128,9 +132,9 @@ export class VideoConsentPlugin implements JsPsychPlugin { * * @param display - HTML element for experiment. */ - private webcamFeed(display: HTMLElement) { + private recordFeed(display: HTMLElement) { const videoContainer = this.getVideoContainer(display); - this.recorder.insertWebcamFeed(videoContainer); + this.recorder.insertRecordFeed(videoContainer); this.getImg(display, "record-icon").style.visibility = "hidden"; } @@ -155,9 +159,12 @@ export class VideoConsentPlugin implements JsPsychPlugin { return () => { const next = this.getButton(display, "next"); const play = this.getButton(display, "play"); - this.webcamFeed(display); + const record = this.getButton(display, "record"); + + this.recordFeed(display); next.disabled = false; play.disabled = false; + record.disabled = false; }; } @@ -224,9 +231,11 @@ export class VideoConsentPlugin implements JsPsychPlugin { */ private playButton(display: HTMLElement) { const play = this.getButton(display, "play"); + const record = this.getButton(display, "record"); play.addEventListener("click", () => { play.disabled = true; + record.disabled = true; this.playbackFeed(display); }); } @@ -240,13 +249,14 @@ export class VideoConsentPlugin implements JsPsychPlugin { const stop = this.getButton(display, "stop"); const record = this.getButton(display, "record"); const play = this.getButton(display, "play"); + stop.addEventListener("click", async () => { stop.disabled = true; record.disabled = false; play.disabled = false; await this.recorder.stop(); this.recorder.reset(); - this.webcamFeed(display); + this.recordFeed(display); }); } /** @@ -259,21 +269,3 @@ export class VideoConsentPlugin implements JsPsychPlugin { next.addEventListener("click", () => this.jsPsych.finishTrial()); } } - -// type Objectx ={ -// type:string -// } - -// interface ObjectA extends Objectx { -// type: "A"; -// value1: string; -// value2: number; -// }; - -// interface ObjectB extends Objectx { -// type: "B"; -// valueA: string; -// valueB: number; -// }; - -// type TypeA diff --git a/packages/record/src/recorder.ts b/packages/record/src/recorder.ts index 8a13038f..aec1b815 100644 --- a/packages/record/src/recorder.ts +++ b/packages/record/src/recorder.ts @@ -6,10 +6,10 @@ import Mustache from "mustache"; import play_icon from "../img/play-icon.svg"; import record_icon from "../img/record-icon.svg"; import playbackFeed from "../templates/playback-feed.mustache"; +import recordFeed from "../templates/record-feed.hbs"; import webcamFeed from "../templates/webcam-feed.mustache"; import { CreateURLError, - NoPlayBackElementError, NoStopPromiseError, NoWebCamElementError, RecorderInitializeError, @@ -31,7 +31,6 @@ export default class Recorder { private filename?: string; private stopPromise?: Promise; private webcam_element_id = "lookit-jspsych-webcam"; - private playback_element_id = "lookit-jspsych-playback"; private streamClone: MediaStream; @@ -109,6 +108,39 @@ export default class Recorder { this.blobs = []; } + /** + * Insert a rendered template into an element. + * + * @param element - Element to have video inserted into. + * @param template - Template string + * @param insertStream - Should the stream be attributed to the webcam + * element. + * @returns Webcam element + */ + private insertVideoFeed( + element: HTMLDivElement, + template: string, + insertStream: boolean = true, + ) { + const { webcam_element_id, stream } = this; + + element.innerHTML = template; + + const webcam = element.querySelector( + `#${webcam_element_id}`, + ); + + if (!webcam) { + throw new NoWebCamElementError(); + } + + if (insertStream) { + webcam.srcObject = stream; + } + + return webcam; + } + /** * Insert a video element containing the webcam feed onto the page. * @@ -124,18 +156,9 @@ export default class Recorder { width: CSSWidthHeight = "100%", height: CSSWidthHeight = "auto", ) { - const { webcam_element_id, stream } = this; - const view = { height, width, webcam_element_id, record_icon }; - element.innerHTML = Mustache.render(webcamFeed, view); - const webcam = element.querySelector( - `#${webcam_element_id}`, - ); - - if (!webcam) { - throw new NoWebCamElementError(); - } - - webcam.srcObject = stream; + const { webcam_element_id } = this; + const view = { height, width, webcam_element_id }; + this.insertVideoFeed(element, Mustache.render(webcamFeed, view)); } /** @@ -155,33 +178,43 @@ export default class Recorder { width: CSSWidthHeight = "100%", height: CSSWidthHeight = "auto", ) { - const { playback_element_id } = this; + const { webcam_element_id } = this; const view = { src: this.url, width, height, - playback_element_id, + webcam_element_id, play_icon, }; - this.clearWebcamFeed(); - - element.insertAdjacentHTML( - "afterbegin", + const playbackElement = this.insertVideoFeed( + element, Mustache.render(playbackFeed, view), + false, ); - const playbackElement = element.querySelector( - `video#${this.playback_element_id}`, - ); - - if (!playbackElement) { - throw new NoPlayBackElementError(); - } - playbackElement.addEventListener("ended", on_ended, { once: true }); } + /** + * Insert a feed to be used for recording into an element. + * + * @param element - Element to have record feed inserted into. + * @param width - The width of the video element containing the webcam feed, + * in CSS units (optional). Default is `'100%'` + * @param height - The height of the video element containing the webcam feed, + * in CSS units (optional). Default is `'auto'` + */ + public insertRecordFeed( + element: HTMLDivElement, + width: CSSWidthHeight = "100%", + height: CSSWidthHeight = "auto", + ) { + const { webcam_element_id } = this; + const view = { height, width, webcam_element_id, record_icon }; + this.insertVideoFeed(element, Mustache.render(recordFeed, view)); + } + /** * Start recording. Also, adds event listeners for handling data and checks * for recorder initialization. diff --git a/packages/record/src/utils.ts b/packages/record/src/utils.ts index c8eb204b..e41a9e6f 100644 --- a/packages/record/src/utils.ts +++ b/packages/record/src/utils.ts @@ -31,16 +31,15 @@ export const expFormat = (text?: string | string[]) => { * @param trial - Trial data including user supplied parameters. */ const initI18next = (trial: TrialType) => { - const { locale } = trial; const translation = Yaml.load(en_us) as Record; - const a2Code = locale.split("-")[0]; const debug = process.env.DEBUG === "true"; + const { baseName, language } = new Intl.Locale(trial.locale); i18next.use(ICU).init({ - lng: locale, + lng: baseName, debug, resources: { - [a2Code]: { + [language]: { translation, }, }, diff --git a/packages/record/templates/consent-document.hbs b/packages/record/templates/consent-document.hbs index 332ca498..5425f331 100644 --- a/packages/record/templates/consent-document.hbs +++ b/packages/record/templates/consent-document.hbs @@ -97,7 +97,7 @@

{{t "consent-template-5.video-privacy-withdraw"}}

{{/if}} {{#if include_databrary}} -

{{& t "consent-template-5.databrary"}}

+

{{{t "consent-template-5.databrary"}}}

{{/if}} {{#if additional_video_privacy_statement}}

{{exp-format additional_video_privacy_statement}}

@@ -147,7 +147,7 @@ {{/if}}

{{t "consent-template-5.research-subject-rights-header"}}

-

{{& exp-format research_rights_statement}}

+

{{{exp-format research_rights_statement}}}

{{#if gdpr}}

{{t "consent-template-5.gdpr-header"}}

diff --git a/packages/record/templates/playback-feed.mustache b/packages/record/templates/playback-feed.mustache index 4e57735d..5011d5c0 100644 --- a/packages/record/templates/playback-feed.mustache +++ b/packages/record/templates/playback-feed.mustache @@ -3,7 +3,7 @@ playsinline muted src="{{{src}}}" - id="{{playback_element_id}}" + id="{{webcam_element_id}}" width="{{width}}" height="{{height}}" controls diff --git a/packages/record/templates/record-feed.hbs b/packages/record/templates/record-feed.hbs new file mode 100644 index 00000000..3aeb5b1d --- /dev/null +++ b/packages/record/templates/record-feed.hbs @@ -0,0 +1,10 @@ + + \ No newline at end of file