Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Another Video PR #27

Merged
merged 29 commits into from
May 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e2b5b09
Update eslint package to fix warning message
okaycj Mar 22, 2024
978488a
Add exit survey pacakge
okaycj Mar 22, 2024
aebbc46
exit survey
okaycj Mar 22, 2024
6bb2f30
Use survey_function to format survey text
okaycj Mar 26, 2024
e11e74a
edit exit survey question titles and descriptions
becky-gilbert Mar 27, 2024
c0a77ae
change question names and MC values to match EFP names
becky-gilbert Mar 27, 2024
a2364fb
add withdrawal and feedback questions
becky-gilbert Mar 27, 2024
19eaadc
Change package name, exit survey parameters
okaycj Mar 27, 2024
58dac26
Added privacy and databrary params
okaycj Mar 27, 2024
282c70f
Get study to add to withdraw copu
okaycj Mar 28, 2024
661ddf8
disable the video sharing questions if video withdrawal is true
becky-gilbert Mar 28, 2024
5906084
Add MD link
okaycj Mar 28, 2024
59ec885
Add global data store
okaycj Apr 1, 2024
a00ff0b
fix withdrawal question value in data and logic for enabling/disablin…
becky-gilbert Apr 1, 2024
b398211
add validation for child birthdate: cannot be a future date
becky-gilbert Apr 1, 2024
c129c8c
Update root package
okaycj Apr 3, 2024
40db248
First pass at tests
okaycj Apr 9, 2024
abd35e5
Add data finish function
okaycj Apr 10, 2024
53527c2
Update tests
okaycj Apr 11, 2024
4c64ad8
Update versions of deps
okaycj Apr 11, 2024
0ad8956
freeze loaded data
okaycj Apr 18, 2024
a9f839e
Add s3 from EFP
okaycj Apr 18, 2024
b74b62d
Add s3 class
okaycj Apr 24, 2024
17797ca
First pass at code documentation
okaycj Apr 26, 2024
ea8b5e2
add auto bind dependency
okaycj Apr 30, 2024
649a099
Add trial recorder
okaycj Apr 30, 2024
57bf100
Session recording
okaycj May 6, 2024
27a7ded
More package updates
okaycj May 6, 2024
57fbcc7
Add comments to code
okaycj May 8, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 67 additions & 86 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/lookit-initjspsych/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const on_data_update = (responseUuid: string, userFunc?: UserFunc) => {
await Api.updateResponse(responseUuid, {
exp_data: [...exp_data, data],
});
await Api.finish();

// Don't call the function if not defined by user.
if (typeof userFunc === "function") {
Expand Down
1 change: 1 addition & 0 deletions packages/surveys/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"author": "Christopher J Green <[email protected]> (https://github.com/okaycj)",
"main": "dist/index.js",
"unpkg": "dist/index.browser.min.js",
"types": "./dist/index.d.ts",
"files": [
"src",
"dist"
Expand Down
6 changes: 6 additions & 0 deletions packages/video/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,20 @@
"author": "Christopher J Green <[email protected]> (https://github.com/okaycj)",
"main": "dist/index.js",
"unpkg": "dist/index.browser.min.js",
"types": "./dist/index.d.ts",
"files": [
"src",
"dist"
],
"scripts": {
"build": "rollup --config",
"dev": "rollup --config rollup.config.dev.mjs --watch",
"test": "jest --coverage"
},
"dependencies": {
"@lookit/data": "^0.0.1",
"auto-bind": "^5.0.1"
},
"devDependencies": {
"@jspsych/config": "^2.0.0"
},
Expand Down
6 changes: 5 additions & 1 deletion packages/video/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export default {};
import StartRecordPlugin from "./start";
import StopRecordPlugin from "./stop";
import TrialRecordExtension from "./trial";

export default { TrialRecordExtension, StartRecordPlugin, StopRecordPlugin };
102 changes: 102 additions & 0 deletions packages/video/src/recorder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import autoBind from "auto-bind";
import { JsPsych } from "jspsych";

/** Recorder handles state of recording and data storage. */
export default class Recorder {
private blobs: Blob[] = [];

/**
* Recorder for online experiments.
*
* @param jsPsych - Object supplied by jsPsych.
*/
public constructor(private jsPsych: JsPsych) {
autoBind(this);
}

/**
* Get recorder from jsPsydh plugin API.
*
* @returns MediaRecorder from the plugin API.
*/
private get recorder() {
return this.jsPsych.pluginAPI.getCameraRecorder();
}

/**
* Get stream from jsPsydh plugin API.
*
* @returns MediaStream from the plugin API.
*/
private get stream() {
return this.jsPsych.pluginAPI.getCameraStream();
}

/** Start recording. Also, adds event listeners for handling data. */
public start() {
this.recorder.addEventListener("dataavailable", this.handleDataAvailable);
this.recorder.addEventListener("stop", this.handleStop);
this.recorder.start();
}

/** Stop recording and camera. */
public stop() {
this.recorder.stop();
this.stream.getTracks().map((t: MediaStreamTrack) => t.stop());
}

/** Handle recorder's stop event. */
private async handleStop() {
await this.download();
}

/**
* Function ran at each time slice and when recorder has stopped.
*
* @param event - Event containing blob data.
*/
private handleDataAvailable(event: BlobEvent) {
this.blobs.push(event.data);
}

/** Temp method to download data url. */
private async download() {
const data = (await this.bytesToBase64DataUrl(
new Blob(this.blobs),
)) as string;
const link = document.createElement("a");
link.href = data;
link.download = `something_${new Date().getTime()}.webm`;
link.click();
}

/**
* Temp method to convert blobs to a data url.
*
* @param bytes - Bytes or blobs.
* @param type - Mimetype.
* @returns Result of reading data as url.
*/
private bytesToBase64DataUrl(
bytes: BlobPart,
type = "video/webm; codecs=vp8",
) {
return new Promise((resolve, reject) => {
const reader = Object.assign(new FileReader(), {
/**
* When promise resolves, it'll return the result.
*
* @returns Result of reading data as url.
*/
onload: () => resolve(reader.result),
/**
* On error, promise return reader error.
*
* @returns Error message.
*/
onerror: () => reject(Error(reader.error?.toString())),
});
reader.readAsDataURL(new File([bytes], "", { type }));
});
}
}
26 changes: 26 additions & 0 deletions packages/video/src/start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { JsPsych, JsPsychPlugin } from "jspsych";
import Recorder from "./recorder";

const info = <const>{ name: "start-record-plugin", parameters: {} };
type Info = typeof info;

/** Start recording. Used by researchers who want to record across trials. */
export default class StartRecordPlugin implements JsPsychPlugin<Info> {
public static readonly info = info;
private recorder: Recorder;

/**
* Plugin used to start recording.
*
* @param jsPsych - Object provided by jsPsych.
*/
public constructor(private jsPsych: JsPsych) {
this.recorder = new Recorder(this.jsPsych);
}

/** Trial function called by jsPsych. */
public trial(): void {
this.recorder.start();
setTimeout(() => this.jsPsych.finishTrial(), 1000);
}
}
25 changes: 25 additions & 0 deletions packages/video/src/stop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { JsPsych, JsPsychPlugin } from "jspsych";
import Recorder from "./recorder";

const info = <const>{ name: "stop-record-plugin", parameters: {} };
type Info = typeof info;

/** Stop recording. Used by researchers who want to record across trials. */
export default class StopRecordPlugin implements JsPsychPlugin<Info> {
public static readonly info = info;
private recorder: Recorder;

/**
* Plugin used to stop recording.
*
* @param jsPsych - Object provided by jsPsych.
*/
public constructor(private jsPsych: JsPsych) {
this.recorder = new Recorder(this.jsPsych);
}

/** Trial function called by jsPsych. */
public trial(): void {
setTimeout(() => this.recorder.stop(), 1000);
}
}
43 changes: 43 additions & 0 deletions packages/video/src/trial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import autoBind from "auto-bind";
import { JsPsych, JsPsychExtension, JsPsychExtensionInfo } from "jspsych";
import Recorder from "./recorder";

/** This extension will allow reasearchers to record trials. */
export default class TrialRecordExtension implements JsPsychExtension {
public static readonly info: JsPsychExtensionInfo = {
name: "chs-trial-record-extension",
};

private recorder: Recorder;

/**
* Video recording extension.
*
* @param jsPsych - JsPsych object passed into extensions.
*/
public constructor(private jsPsych: JsPsych) {
this.recorder = new Recorder(this.jsPsych);
autoBind(this);
}

/** Ran on the initialize step for extensions. */
public async initialize() {}

/** Ran at the start of a trail. */
public on_start() {}

/** Ran when the trial has loaded. */
public on_load() {
this.recorder.start();
}

/**
* Ran when trial has finished.
*
* @returns Trail data.
*/
public on_finish() {
this.recorder.stop();
return {};
}
}